a8f9f08253
InterfaceType's referring to unnamed mixin applications continuously cause problems for backends since these might not correspond to types supported at runtime. The LUB specification does not mention how to handle these so it is valid skip these in the computation of LUB. Change-Id: I38f46bf43d5858dec473c26326b741cf36eb57d8 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/145420 Commit-Queue: Johnni Winther <johnniwinther@google.com> Reviewed-by: Dmitry Stefantsov <dmitryas@google.com>
917 lines
28 KiB
Dart
917 lines
28 KiB
Dart
// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
library kernel.checks;
|
|
|
|
import 'ast.dart';
|
|
import 'transformations/flags.dart';
|
|
import 'type_environment.dart' show StatefulStaticTypeContext, TypeEnvironment;
|
|
|
|
void verifyComponent(Component component, {bool isOutline, bool afterConst}) {
|
|
VerifyingVisitor.check(component,
|
|
isOutline: isOutline, afterConst: afterConst);
|
|
}
|
|
|
|
class VerificationError {
|
|
final TreeNode context;
|
|
|
|
final TreeNode node;
|
|
|
|
final String details;
|
|
|
|
VerificationError(this.context, this.node, this.details);
|
|
|
|
toString() {
|
|
Location location;
|
|
try {
|
|
location = node?.location ?? context?.location;
|
|
} catch (_) {
|
|
// TODO(ahe): Fix the compiler instead.
|
|
}
|
|
if (location != null) {
|
|
String file = location.file?.toString() ?? "";
|
|
return "$file:${location.line}:${location.column}: Verification error:"
|
|
" $details";
|
|
} else {
|
|
return "Verification error: $details\n"
|
|
"Context: '$context'.\n"
|
|
"Node: '$node'.";
|
|
}
|
|
}
|
|
}
|
|
|
|
enum TypedefState { Done, BeingChecked }
|
|
|
|
/// Checks that a kernel component is well-formed.
|
|
///
|
|
/// This does not include any kind of type checking.
|
|
class VerifyingVisitor extends RecursiveVisitor<void> {
|
|
final Set<Class> classes = new Set<Class>();
|
|
final Set<Typedef> typedefs = new Set<Typedef>();
|
|
Set<TypeParameter> typeParametersInScope = new Set<TypeParameter>();
|
|
final List<VariableDeclaration> variableStack = <VariableDeclaration>[];
|
|
final Map<Typedef, TypedefState> typedefState = <Typedef, TypedefState>{};
|
|
final Set<Constant> seenConstants = <Constant>{};
|
|
bool classTypeParametersAreInScope = false;
|
|
|
|
/// If true, relax certain checks for *outline* mode. For example, don't
|
|
/// attempt to validate constructor initializers.
|
|
final bool isOutline;
|
|
|
|
/// If true, assume that constant evaluation has been performed (with a
|
|
/// target that did not opt out of any of the constant inlining) and report
|
|
/// a verification error for anything that should have been removed by it.
|
|
final bool afterConst;
|
|
|
|
bool inCatchBlock = false;
|
|
|
|
bool inUnevaluatedConstant = false;
|
|
|
|
Library currentLibrary;
|
|
|
|
Member currentMember;
|
|
|
|
Class currentClass;
|
|
|
|
TreeNode currentParent;
|
|
|
|
TreeNode get currentClassOrMember => currentMember ?? currentClass;
|
|
|
|
static void check(Component component, {bool isOutline, bool afterConst}) {
|
|
component.accept(
|
|
new VerifyingVisitor(isOutline: isOutline, afterConst: afterConst));
|
|
}
|
|
|
|
VerifyingVisitor({bool isOutline, bool afterConst})
|
|
: isOutline = isOutline ?? false,
|
|
afterConst = afterConst ?? !(isOutline ?? false);
|
|
|
|
@override
|
|
defaultTreeNode(TreeNode node) {
|
|
visitChildren(node);
|
|
}
|
|
|
|
@override
|
|
defaultConstantReference(Constant constant) {
|
|
if (seenConstants.add(constant)) {
|
|
constant.accept(this);
|
|
}
|
|
}
|
|
|
|
@override
|
|
defaultConstant(Constant constant) {
|
|
constant.visitChildren(this);
|
|
}
|
|
|
|
problem(TreeNode node, String details, {TreeNode context}) {
|
|
context ??= currentClassOrMember;
|
|
throw new VerificationError(context, node, details);
|
|
}
|
|
|
|
TreeNode enterParent(TreeNode node) {
|
|
if (!identical(node.parent, currentParent)) {
|
|
problem(
|
|
node,
|
|
"Incorrect parent pointer on ${node.runtimeType}:"
|
|
" expected '${currentParent.runtimeType}',"
|
|
" but found: '${node.parent.runtimeType}'.",
|
|
context: currentParent);
|
|
}
|
|
var oldParent = currentParent;
|
|
currentParent = node;
|
|
return oldParent;
|
|
}
|
|
|
|
void exitParent(TreeNode oldParent) {
|
|
currentParent = oldParent;
|
|
}
|
|
|
|
int enterLocalScope() => variableStack.length;
|
|
|
|
void exitLocalScope(int stackHeight) {
|
|
for (int i = stackHeight; i < variableStack.length; ++i) {
|
|
undeclareVariable(variableStack[i]);
|
|
}
|
|
variableStack.length = stackHeight;
|
|
}
|
|
|
|
void visitChildren(TreeNode node) {
|
|
var oldParent = enterParent(node);
|
|
node.visitChildren(this);
|
|
exitParent(oldParent);
|
|
}
|
|
|
|
void visitWithLocalScope(TreeNode node) {
|
|
int stackHeight = enterLocalScope();
|
|
visitChildren(node);
|
|
exitLocalScope(stackHeight);
|
|
}
|
|
|
|
void declareMember(Member member) {
|
|
if (member.transformerFlags & TransformerFlag.seenByVerifier != 0) {
|
|
problem(member.function,
|
|
"Member '$member' has been declared more than once.");
|
|
}
|
|
member.transformerFlags |= TransformerFlag.seenByVerifier;
|
|
}
|
|
|
|
void undeclareMember(Member member) {
|
|
member.transformerFlags &= ~TransformerFlag.seenByVerifier;
|
|
}
|
|
|
|
void declareVariable(VariableDeclaration variable) {
|
|
if (variable.flags & VariableDeclaration.FlagInScope != 0) {
|
|
problem(variable, "Variable '$variable' declared more than once.");
|
|
}
|
|
variable.flags |= VariableDeclaration.FlagInScope;
|
|
variableStack.add(variable);
|
|
}
|
|
|
|
void undeclareVariable(VariableDeclaration variable) {
|
|
variable.flags &= ~VariableDeclaration.FlagInScope;
|
|
}
|
|
|
|
void declareTypeParameters(List<TypeParameter> parameters) {
|
|
for (int i = 0; i < parameters.length; ++i) {
|
|
var parameter = parameters[i];
|
|
if (parameter.bound == null) {
|
|
problem(
|
|
currentParent, "Missing bound for type parameter '$parameter'.");
|
|
}
|
|
if (parameter.defaultType == null) {
|
|
problem(currentParent,
|
|
"Missing default type for type parameter '$parameter'.");
|
|
}
|
|
if (!typeParametersInScope.add(parameter)) {
|
|
problem(parameter, "Type parameter '$parameter' redeclared.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void undeclareTypeParameters(List<TypeParameter> parameters) {
|
|
typeParametersInScope.removeAll(parameters);
|
|
}
|
|
|
|
void checkVariableInScope(VariableDeclaration variable, TreeNode where) {
|
|
if (variable.flags & VariableDeclaration.FlagInScope == 0) {
|
|
problem(where, "Variable '$variable' used out of scope.");
|
|
}
|
|
}
|
|
|
|
visitComponent(Component component) {
|
|
try {
|
|
for (var library in component.libraries) {
|
|
for (var class_ in library.classes) {
|
|
if (!classes.add(class_)) {
|
|
problem(class_, "Class '$class_' declared more than once.");
|
|
}
|
|
}
|
|
for (var typedef_ in library.typedefs) {
|
|
if (!typedefs.add(typedef_)) {
|
|
problem(typedef_, "Typedef '$typedef_' declared more than once.");
|
|
}
|
|
}
|
|
library.members.forEach(declareMember);
|
|
for (var class_ in library.classes) {
|
|
class_.members.forEach(declareMember);
|
|
}
|
|
}
|
|
visitChildren(component);
|
|
} finally {
|
|
for (var library in component.libraries) {
|
|
library.members.forEach(undeclareMember);
|
|
for (var class_ in library.classes) {
|
|
class_.members.forEach(undeclareMember);
|
|
}
|
|
}
|
|
variableStack.forEach(undeclareVariable);
|
|
}
|
|
}
|
|
|
|
void visitLibrary(Library node) {
|
|
currentLibrary = node;
|
|
super.visitLibrary(node);
|
|
currentLibrary = null;
|
|
}
|
|
|
|
visitExtension(Extension node) {
|
|
declareTypeParameters(node.typeParameters);
|
|
final oldParent = enterParent(node);
|
|
node.visitChildren(this);
|
|
exitParent(oldParent);
|
|
undeclareTypeParameters(node.typeParameters);
|
|
}
|
|
|
|
void checkTypedef(Typedef node) {
|
|
var state = typedefState[node];
|
|
if (state == TypedefState.Done) return;
|
|
if (state == TypedefState.BeingChecked) {
|
|
problem(node, "The typedef '$node' refers to itself", context: node);
|
|
}
|
|
assert(state == null);
|
|
typedefState[node] = TypedefState.BeingChecked;
|
|
var savedTypeParameters = typeParametersInScope;
|
|
typeParametersInScope = node.typeParameters.toSet();
|
|
var savedParent = currentParent;
|
|
currentParent = node;
|
|
// Visit children without checking the parent pointer on the typedef itself
|
|
// since this can be called from a context other than its true parent.
|
|
node.visitChildren(this);
|
|
currentParent = savedParent;
|
|
typeParametersInScope = savedTypeParameters;
|
|
typedefState[node] = TypedefState.Done;
|
|
}
|
|
|
|
visitTypedef(Typedef node) {
|
|
checkTypedef(node);
|
|
// Enter and exit the node to check the parent pointer on the typedef node.
|
|
exitParent(enterParent(node));
|
|
}
|
|
|
|
visitField(Field node) {
|
|
currentMember = node;
|
|
var oldParent = enterParent(node);
|
|
bool isTopLevel = node.parent == currentLibrary;
|
|
if (isTopLevel && !node.isStatic) {
|
|
problem(node, "The top-level field '${node.name.name}' should be static",
|
|
context: node);
|
|
}
|
|
if (node.isConst && !node.isStatic) {
|
|
problem(node, "The const field '${node.name.name}' should be static",
|
|
context: node);
|
|
}
|
|
classTypeParametersAreInScope = !node.isStatic;
|
|
node.initializer?.accept(this);
|
|
node.type.accept(this);
|
|
classTypeParametersAreInScope = false;
|
|
visitList(node.annotations, this);
|
|
exitParent(oldParent);
|
|
currentMember = null;
|
|
}
|
|
|
|
visitProcedure(Procedure node) {
|
|
currentMember = node;
|
|
var oldParent = enterParent(node);
|
|
classTypeParametersAreInScope = !node.isStatic;
|
|
if (node.isAbstract && node.isExternal) {
|
|
problem(node, "Procedure cannot be both abstract and external.");
|
|
}
|
|
if (node.isMemberSignature && node.isForwardingStub) {
|
|
problem(
|
|
node,
|
|
"Procedure cannot be both a member signature and a forwarding stub: "
|
|
"$node.");
|
|
}
|
|
if (node.isMemberSignature && node.isForwardingSemiStub) {
|
|
problem(
|
|
node,
|
|
"Procedure cannot be both a member signature and a forwarding semi "
|
|
"stub $node.");
|
|
}
|
|
if (node.isMemberSignature && node.isNoSuchMethodForwarder) {
|
|
problem(
|
|
node,
|
|
"Procedure cannot be both a member signature and a noSuchMethod "
|
|
"forwarder $node.");
|
|
}
|
|
if (node.isMemberSignature && node.memberSignatureOrigin == null) {
|
|
problem(
|
|
node, "Member signature must have a member signature origin $node.");
|
|
}
|
|
if (node.forwardingStubInterfaceTarget != null &&
|
|
!(node.isForwardingStub || node.isForwardingSemiStub)) {
|
|
problem(
|
|
node,
|
|
"Only forwarding stubs can have a forwarding stub interface target "
|
|
"$node.");
|
|
}
|
|
if (node.forwardingStubSuperTarget != null &&
|
|
!(node.isForwardingStub || node.isForwardingSemiStub)) {
|
|
problem(
|
|
node,
|
|
"Only forwarding stubs can have a forwarding stub super target "
|
|
"$node.");
|
|
}
|
|
node.function.accept(this);
|
|
classTypeParametersAreInScope = false;
|
|
visitList(node.annotations, this);
|
|
exitParent(oldParent);
|
|
currentMember = null;
|
|
}
|
|
|
|
visitConstructor(Constructor node) {
|
|
currentMember = node;
|
|
classTypeParametersAreInScope = true;
|
|
// The constructor member needs special treatment due to parameters being
|
|
// in scope in the initializer list.
|
|
var oldParent = enterParent(node);
|
|
int stackHeight = enterLocalScope();
|
|
visitChildren(node.function);
|
|
visitList(node.initializers, this);
|
|
if (!isOutline) {
|
|
checkInitializers(node);
|
|
}
|
|
exitLocalScope(stackHeight);
|
|
classTypeParametersAreInScope = false;
|
|
visitList(node.annotations, this);
|
|
exitParent(oldParent);
|
|
classTypeParametersAreInScope = false;
|
|
currentMember = null;
|
|
}
|
|
|
|
visitClass(Class node) {
|
|
currentClass = node;
|
|
declareTypeParameters(node.typeParameters);
|
|
var oldParent = enterParent(node);
|
|
classTypeParametersAreInScope = false;
|
|
visitList(node.annotations, this);
|
|
classTypeParametersAreInScope = true;
|
|
visitList(node.typeParameters, this);
|
|
visitList(node.fields, this);
|
|
visitList(node.constructors, this);
|
|
visitList(node.procedures, this);
|
|
exitParent(oldParent);
|
|
undeclareTypeParameters(node.typeParameters);
|
|
currentClass = null;
|
|
}
|
|
|
|
visitFunctionNode(FunctionNode node) {
|
|
declareTypeParameters(node.typeParameters);
|
|
bool savedInCatchBlock = inCatchBlock;
|
|
inCatchBlock = false;
|
|
visitWithLocalScope(node);
|
|
inCatchBlock = savedInCatchBlock;
|
|
undeclareTypeParameters(node.typeParameters);
|
|
}
|
|
|
|
visitFunctionType(FunctionType node) {
|
|
for (int i = 1; i < node.namedParameters.length; ++i) {
|
|
if (node.namedParameters[i - 1].compareTo(node.namedParameters[i]) >= 0) {
|
|
problem(currentParent,
|
|
"Named parameters are not sorted on function type ($node).");
|
|
}
|
|
}
|
|
declareTypeParameters(node.typeParameters);
|
|
for (var typeParameter in node.typeParameters) {
|
|
typeParameter.bound?.accept(this);
|
|
if (typeParameter.annotations.isNotEmpty) {
|
|
problem(
|
|
typeParameter, "Annotation on type parameter in function type.");
|
|
}
|
|
}
|
|
visitList(node.positionalParameters, this);
|
|
visitList(node.namedParameters, this);
|
|
node.returnType.accept(this);
|
|
undeclareTypeParameters(node.typeParameters);
|
|
}
|
|
|
|
visitBlock(Block node) {
|
|
visitWithLocalScope(node);
|
|
}
|
|
|
|
visitForStatement(ForStatement node) {
|
|
visitWithLocalScope(node);
|
|
}
|
|
|
|
visitForInStatement(ForInStatement node) {
|
|
visitWithLocalScope(node);
|
|
}
|
|
|
|
visitLet(Let node) {
|
|
visitWithLocalScope(node);
|
|
}
|
|
|
|
visitBlockExpression(BlockExpression node) {
|
|
int stackHeight = enterLocalScope();
|
|
// Do not visit the block directly because the value expression needs to
|
|
// be in its scope.
|
|
TreeNode oldParent = enterParent(node);
|
|
enterParent(node.body);
|
|
for (int i = 0; i < node.body.statements.length; ++i) {
|
|
node.body.statements[i].accept(this);
|
|
}
|
|
exitParent(node);
|
|
node.value.accept(this);
|
|
exitParent(oldParent);
|
|
exitLocalScope(stackHeight);
|
|
}
|
|
|
|
visitCatch(Catch node) {
|
|
bool savedInCatchBlock = inCatchBlock;
|
|
inCatchBlock = true;
|
|
visitWithLocalScope(node);
|
|
inCatchBlock = savedInCatchBlock;
|
|
}
|
|
|
|
@override
|
|
visitRethrow(Rethrow node) {
|
|
if (!inCatchBlock) {
|
|
problem(node, "Rethrow must be inside a Catch block.");
|
|
}
|
|
}
|
|
|
|
visitVariableDeclaration(VariableDeclaration node) {
|
|
var parent = node.parent;
|
|
if (parent is! Block &&
|
|
!(parent is Catch && parent.body != node) &&
|
|
!(parent is FunctionNode && parent.body != node) &&
|
|
parent is! FunctionDeclaration &&
|
|
!(parent is ForStatement && parent.body != node) &&
|
|
!(parent is ForInStatement && parent.body != node) &&
|
|
parent is! Let &&
|
|
parent is! LocalInitializer) {
|
|
problem(
|
|
node,
|
|
"VariableDeclaration must be a direct child of a Block, "
|
|
"not ${parent.runtimeType}.");
|
|
}
|
|
visitChildren(node);
|
|
declareVariable(node);
|
|
if (afterConst && node.isConst) {
|
|
Expression initializer = node.initializer;
|
|
if (!(initializer is InvalidExpression ||
|
|
initializer is ConstantExpression &&
|
|
initializer.constant is UnevaluatedConstant)) {
|
|
problem(node, "Constant VariableDeclaration");
|
|
}
|
|
}
|
|
}
|
|
|
|
visitVariableGet(VariableGet node) {
|
|
checkVariableInScope(node.variable, node);
|
|
visitChildren(node);
|
|
if (afterConst && node.variable.isConst) {
|
|
problem(node, "VariableGet of const variable '${node.variable}'.");
|
|
}
|
|
}
|
|
|
|
visitVariableSet(VariableSet node) {
|
|
checkVariableInScope(node.variable, node);
|
|
visitChildren(node);
|
|
}
|
|
|
|
@override
|
|
visitStaticGet(StaticGet node) {
|
|
visitChildren(node);
|
|
if (node.target == null) {
|
|
problem(node, "StaticGet without target.");
|
|
}
|
|
// Currently Constructor.hasGetter returns `false` even though fasta uses it
|
|
// as a getter for internal purposes:
|
|
//
|
|
// Fasta is letting all call site of a redirecting constructor be resolved
|
|
// to the real target. In order to resolve it, it seems to add a body into
|
|
// the redirecting-factory constructor which caches the target constructor.
|
|
// That cache is via a `StaticGet(real-constructor)` node, which we make
|
|
// here pass the verifier.
|
|
if (!node.target.hasGetter && node.target is! Constructor) {
|
|
problem(node, "StaticGet of '${node.target}' without getter.");
|
|
}
|
|
if (node.target.isInstanceMember) {
|
|
problem(node, "StaticGet of '${node.target}' that's an instance member.");
|
|
}
|
|
if (afterConst && node.target is Field && node.target.isConst) {
|
|
problem(node, "StaticGet of const field '${node.target}'.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitStaticSet(StaticSet node) {
|
|
visitChildren(node);
|
|
if (node.target == null) {
|
|
problem(node, "StaticSet without target.");
|
|
}
|
|
if (!node.target.hasSetter) {
|
|
problem(node, "StaticSet to '${node.target}' without setter.");
|
|
}
|
|
if (node.target.isInstanceMember) {
|
|
problem(node, "StaticSet to '${node.target}' that's an instance member.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitStaticInvocation(StaticInvocation node) {
|
|
checkTargetedInvocation(node.target, node);
|
|
if (node.target.isInstanceMember) {
|
|
problem(node,
|
|
"StaticInvocation of '${node.target}' that's an instance member.");
|
|
}
|
|
if (node.isConst &&
|
|
(!node.target.isConst ||
|
|
!node.target.isExternal ||
|
|
node.target.kind != ProcedureKind.Factory)) {
|
|
problem(
|
|
node,
|
|
"Constant StaticInvocation of '${node.target}' that isn't"
|
|
" a const external factory.");
|
|
}
|
|
if (afterConst && node.isConst && !inUnevaluatedConstant) {
|
|
problem(node, "Constant StaticInvocation.");
|
|
}
|
|
}
|
|
|
|
void checkTargetedInvocation(Member target, InvocationExpression node) {
|
|
visitChildren(node);
|
|
if (target == null) {
|
|
problem(node, "${node.runtimeType} without target.");
|
|
}
|
|
if (target.function == null) {
|
|
problem(node, "${node.runtimeType} without function.");
|
|
}
|
|
if (!areArgumentsCompatible(node.arguments, target.function)) {
|
|
problem(node,
|
|
"${node.runtimeType} with incompatible arguments for '${target}'.");
|
|
}
|
|
int expectedTypeParameters = target is Constructor
|
|
? target.enclosingClass.typeParameters.length
|
|
: target.function.typeParameters.length;
|
|
if (node.arguments.types.length != expectedTypeParameters) {
|
|
problem(
|
|
node,
|
|
"${node.runtimeType} with wrong number of type arguments"
|
|
" for '${target}'.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitDirectPropertyGet(DirectPropertyGet node) {
|
|
visitChildren(node);
|
|
if (node.target == null) {
|
|
problem(node, "DirectPropertyGet without target.");
|
|
}
|
|
if (!node.target.hasGetter) {
|
|
problem(node, "DirectPropertyGet of '${node.target}' without getter.");
|
|
}
|
|
if (!node.target.isInstanceMember) {
|
|
problem(
|
|
node,
|
|
"DirectPropertyGet of '${node.target}' that isn't an"
|
|
" instance member.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitDirectPropertySet(DirectPropertySet node) {
|
|
visitChildren(node);
|
|
if (node.target == null) {
|
|
problem(node, "DirectPropertySet without target.");
|
|
}
|
|
if (!node.target.hasSetter) {
|
|
problem(node, "DirectPropertySet of '${node.target}' without setter.");
|
|
}
|
|
if (!node.target.isInstanceMember) {
|
|
problem(node, "DirectPropertySet of '${node.target}' that is static.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitDirectMethodInvocation(DirectMethodInvocation node) {
|
|
checkTargetedInvocation(node.target, node);
|
|
if (node.receiver == null) {
|
|
problem(node, "DirectMethodInvocation without receiver.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitConstructorInvocation(ConstructorInvocation node) {
|
|
checkTargetedInvocation(node.target, node);
|
|
if (node.target.enclosingClass.isAbstract) {
|
|
problem(node, "ConstructorInvocation of abstract class.");
|
|
}
|
|
if (node.isConst && !node.target.isConst) {
|
|
problem(
|
|
node,
|
|
"Constant ConstructorInvocation fo '${node.target}' that"
|
|
" isn't const.");
|
|
}
|
|
if (afterConst && node.isConst) {
|
|
problem(node, "Invocation of const constructor '${node.target}'.");
|
|
}
|
|
}
|
|
|
|
bool areArgumentsCompatible(Arguments arguments, FunctionNode function) {
|
|
if (arguments.positional.length < function.requiredParameterCount) {
|
|
return false;
|
|
}
|
|
if (arguments.positional.length > function.positionalParameters.length) {
|
|
return false;
|
|
}
|
|
namedLoop:
|
|
for (int i = 0; i < arguments.named.length; ++i) {
|
|
var argument = arguments.named[i];
|
|
String name = argument.name;
|
|
for (int j = 0; j < function.namedParameters.length; ++j) {
|
|
if (function.namedParameters[j].name == name) continue namedLoop;
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
visitListLiteral(ListLiteral node) {
|
|
visitChildren(node);
|
|
if (afterConst && node.isConst) {
|
|
problem(node, "Constant list literal.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitSetLiteral(SetLiteral node) {
|
|
visitChildren(node);
|
|
if (afterConst && node.isConst) {
|
|
problem(node, "Constant set literal.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitMapLiteral(MapLiteral node) {
|
|
visitChildren(node);
|
|
if (afterConst && node.isConst) {
|
|
problem(node, "Constant map literal.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitSymbolLiteral(SymbolLiteral node) {
|
|
if (afterConst) {
|
|
problem(node, "Symbol literal.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitContinueSwitchStatement(ContinueSwitchStatement node) {
|
|
if (node.target == null) {
|
|
problem(node, "No target.");
|
|
} else if (node.target.parent == null) {
|
|
problem(node, "Target has no parent.");
|
|
} else {
|
|
SwitchStatement statement = node.target.parent;
|
|
for (SwitchCase switchCase in statement.cases) {
|
|
if (switchCase == node.target) return;
|
|
}
|
|
problem(node, "Switch case isn't child of parent.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitInstanceConstant(InstanceConstant constant) {
|
|
constant.visitChildren(this);
|
|
if (constant.typeArguments.length !=
|
|
constant.classNode.typeParameters.length) {
|
|
problem(
|
|
currentParent,
|
|
"Constant $constant provides ${constant.typeArguments.length}"
|
|
" type arguments, but the class declares"
|
|
" ${constant.classNode.typeParameters.length} parameters.");
|
|
}
|
|
Set<Class> superClasses = <Class>{};
|
|
int fieldCount = 0;
|
|
for (Class cls = constant.classNode; cls != null; cls = cls.superclass) {
|
|
superClasses.add(cls);
|
|
for (Field f in cls.fields) {
|
|
if (!f.isStatic && !f.isConst) fieldCount++;
|
|
}
|
|
}
|
|
if (constant.fieldValues.length != fieldCount) {
|
|
problem(
|
|
currentParent,
|
|
"Constant $constant provides ${constant.fieldValues.length}"
|
|
" field values, but the class declares"
|
|
" $fieldCount fields.");
|
|
}
|
|
constant.fieldValues.forEach((Reference fieldRef, Constant value) {
|
|
Field field = fieldRef.asField;
|
|
if (!superClasses.contains(field.enclosingClass)) {
|
|
problem(
|
|
currentParent,
|
|
"Constant $constant refers to field $field,"
|
|
" which does not belong to the right class.");
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
visitUnevaluatedConstant(UnevaluatedConstant constant) {
|
|
if (inUnevaluatedConstant) {
|
|
problem(currentParent, "UnevaluatedConstant in UnevaluatedConstant.");
|
|
}
|
|
bool savedInUnevaluatedConstant = inUnevaluatedConstant;
|
|
inUnevaluatedConstant = true;
|
|
TreeNode oldParent = currentParent;
|
|
currentParent = null;
|
|
constant.expression.accept(this);
|
|
currentParent = oldParent;
|
|
inUnevaluatedConstant = savedInUnevaluatedConstant;
|
|
}
|
|
|
|
@override
|
|
defaultMemberReference(Member node) {
|
|
if (node.transformerFlags & TransformerFlag.seenByVerifier == 0) {
|
|
problem(
|
|
node, "Dangling reference to '$node', parent is: '${node.parent}'.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitClassReference(Class node) {
|
|
if (!classes.contains(node)) {
|
|
problem(
|
|
node, "Dangling reference to '$node', parent is: '${node.parent}'.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitTypedefReference(Typedef node) {
|
|
if (!typedefs.contains(node)) {
|
|
problem(
|
|
node, "Dangling reference to '$node', parent is: '${node.parent}'");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitTypeParameterType(TypeParameterType node) {
|
|
var parameter = node.parameter;
|
|
if (!typeParametersInScope.contains(parameter)) {
|
|
problem(
|
|
currentParent,
|
|
"Type parameter '$parameter' referenced out of"
|
|
" scope, parent is: '${parameter.parent}'.");
|
|
}
|
|
if (parameter.parent is Class && !classTypeParametersAreInScope) {
|
|
problem(
|
|
currentParent,
|
|
"Type parameter '$parameter' referenced from"
|
|
" static context, parent is: '${parameter.parent}'.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitInterfaceType(InterfaceType node) {
|
|
node.visitChildren(this);
|
|
if (node.typeArguments.length != node.classNode.typeParameters.length) {
|
|
problem(
|
|
currentParent,
|
|
"Type $node provides ${node.typeArguments.length}"
|
|
" type arguments, but the class declares"
|
|
" ${node.classNode.typeParameters.length} parameters.");
|
|
}
|
|
if (node.classNode.isAnonymousMixin) {
|
|
if (currentParent is FunctionNode) {
|
|
TreeNode functionNodeParent = currentParent.parent;
|
|
if (functionNodeParent is Constructor ||
|
|
functionNodeParent is Procedure &&
|
|
functionNodeParent.kind == ProcedureKind.Factory) {
|
|
if (functionNodeParent.parent == node.classNode) {
|
|
// We only allow references to anonymous mixins in types as the
|
|
// return type of its own constructor.
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
problem(currentParent, "Type $node references an anonymous mixin class.");
|
|
}
|
|
}
|
|
|
|
@override
|
|
visitTypedefType(TypedefType node) {
|
|
checkTypedef(node.typedefNode);
|
|
node.visitChildren(this);
|
|
if (node.typeArguments.length != node.typedefNode.typeParameters.length) {
|
|
problem(
|
|
currentParent,
|
|
"The typedef type $node provides ${node.typeArguments.length}"
|
|
" type arguments, but the typedef declares"
|
|
" ${node.typedefNode.typeParameters.length} parameters.");
|
|
}
|
|
}
|
|
}
|
|
|
|
void verifyGetStaticType(TypeEnvironment env, Component component) {
|
|
component.accept(new VerifyGetStaticType(env));
|
|
}
|
|
|
|
class VerifyGetStaticType extends RecursiveVisitor {
|
|
final TypeEnvironment env;
|
|
Member currentMember;
|
|
final StatefulStaticTypeContext _staticTypeContext;
|
|
|
|
VerifyGetStaticType(this.env)
|
|
: _staticTypeContext = new StatefulStaticTypeContext.stacked(env);
|
|
|
|
@override
|
|
visitLibrary(Library node) {
|
|
_staticTypeContext.enterLibrary(node);
|
|
super.visitLibrary(node);
|
|
_staticTypeContext.leaveLibrary(node);
|
|
}
|
|
|
|
@override
|
|
visitField(Field node) {
|
|
currentMember = node;
|
|
_staticTypeContext.enterMember(node);
|
|
super.visitField(node);
|
|
_staticTypeContext.leaveMember(node);
|
|
currentMember = node;
|
|
}
|
|
|
|
@override
|
|
visitProcedure(Procedure node) {
|
|
currentMember = node;
|
|
_staticTypeContext.enterMember(node);
|
|
super.visitProcedure(node);
|
|
_staticTypeContext.leaveMember(node);
|
|
currentMember = node;
|
|
}
|
|
|
|
@override
|
|
visitConstructor(Constructor node) {
|
|
currentMember = node;
|
|
_staticTypeContext.enterMember(node);
|
|
super.visitConstructor(node);
|
|
_staticTypeContext.leaveMember(node);
|
|
currentMember = null;
|
|
}
|
|
|
|
@override
|
|
defaultExpression(Expression node) {
|
|
try {
|
|
node.getStaticType(_staticTypeContext);
|
|
} catch (_) {
|
|
print('Error in $currentMember in ${currentMember?.fileUri}: '
|
|
'$node (${node.runtimeType})');
|
|
rethrow;
|
|
}
|
|
super.defaultExpression(node);
|
|
}
|
|
}
|
|
|
|
class CheckParentPointers extends Visitor {
|
|
static void check(TreeNode node) {
|
|
node.accept(new CheckParentPointers(node.parent));
|
|
}
|
|
|
|
TreeNode parent;
|
|
|
|
CheckParentPointers([this.parent]);
|
|
|
|
defaultTreeNode(TreeNode node) {
|
|
if (node.parent != parent) {
|
|
throw new VerificationError(
|
|
parent,
|
|
node,
|
|
"Parent pointer on '${node.runtimeType}' "
|
|
"is '${node.parent.runtimeType}' "
|
|
"but should be '${parent.runtimeType}'.");
|
|
}
|
|
var oldParent = parent;
|
|
parent = node;
|
|
node.visitChildren(this);
|
|
parent = oldParent;
|
|
}
|
|
}
|
|
|
|
void checkInitializers(Constructor constructor) {
|
|
// TODO(ahe): I'll add more here in other CLs.
|
|
}
|