| // 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, bool constantsAreAlwaysInlined: true}) { |
| VerifyingVisitor.check(component, |
| isOutline: isOutline, |
| afterConst: afterConst, |
| constantsAreAlwaysInlined: constantsAreAlwaysInlined); |
| } |
| |
| class VerificationError { |
| final TreeNode? context; |
| |
| final TreeNode? node; |
| |
| final String details; |
| |
| VerificationError(this.context, this.node, this.details); |
| |
| @override |
| String 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 RecursiveResultVisitor<void> { |
| final Set<Class> classes = new Set<Class>(); |
| final Set<Typedef> typedefs = new Set<Typedef>(); |
| Set<TypeParameter> typeParametersInScope = new Set<TypeParameter>(); |
| Set<VariableDeclaration> variableDeclarationsInScope = |
| new Set<VariableDeclaration>(); |
| 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; |
| |
| /// If true, constant fields and local variables are expected to be inlined. |
| final bool constantsAreAlwaysInlined; |
| |
| AsyncMarker currentAsyncMarker = AsyncMarker.Sync; |
| |
| bool inCatchBlock = false; |
| |
| bool inUnevaluatedConstant = false; |
| |
| bool inConstant = false; |
| |
| Library? currentLibrary; |
| |
| Member? currentMember; |
| |
| Class? currentClass; |
| |
| Extension? currentExtension; |
| |
| TreeNode? currentParent; |
| |
| TreeNode? get currentClassOrExtensionOrMember => |
| currentMember ?? currentClass ?? currentExtension; |
| |
| static void check(Component component, |
| {bool? isOutline, |
| bool? afterConst, |
| required bool constantsAreAlwaysInlined}) { |
| component.accept(new VerifyingVisitor( |
| isOutline: isOutline, |
| afterConst: afterConst, |
| constantsAreAlwaysInlined: constantsAreAlwaysInlined)); |
| } |
| |
| VerifyingVisitor( |
| {bool? isOutline, |
| bool? afterConst, |
| required this.constantsAreAlwaysInlined}) |
| : isOutline = isOutline ?? false, |
| afterConst = afterConst ?? !(isOutline ?? false); |
| |
| @override |
| void defaultTreeNode(TreeNode node) { |
| visitChildren(node); |
| } |
| |
| @override |
| void defaultConstantReference(Constant constant) { |
| if (seenConstants.add(constant)) { |
| constant.accept(this); |
| } |
| } |
| |
| @override |
| void defaultConstant(Constant constant) { |
| constant.visitChildren(this); |
| } |
| |
| void problem(TreeNode? node, String details, {TreeNode? context}) { |
| context ??= currentClassOrExtensionOrMember; |
| 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); |
| } |
| TreeNode? 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) { |
| TreeNode? 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 (variableDeclarationsInScope.contains(variable)) { |
| problem(variable, "Variable '$variable' declared more than once."); |
| } |
| variableDeclarationsInScope.add(variable); |
| variableStack.add(variable); |
| } |
| |
| void undeclareVariable(VariableDeclaration variable) { |
| variableDeclarationsInScope.remove(variable); |
| } |
| |
| void declareTypeParameters(List<TypeParameter> parameters) { |
| for (int i = 0; i < parameters.length; ++i) { |
| TypeParameter parameter = parameters[i]; |
| if (identical(parameter.bound, TypeParameter.unsetBoundSentinel)) { |
| problem( |
| currentParent, "Missing bound for type parameter '$parameter'."); |
| } |
| if (identical( |
| parameter.defaultType, TypeParameter.unsetDefaultTypeSentinel)) { |
| 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 (!variableDeclarationsInScope.contains(variable)) { |
| problem(where, "Variable '$variable' used out of scope."); |
| } |
| } |
| |
| @override |
| void visitComponent(Component component) { |
| try { |
| for (Library library in component.libraries) { |
| for (Class class_ in library.classes) { |
| if (!classes.add(class_)) { |
| problem(class_, "Class '$class_' declared more than once."); |
| } |
| } |
| for (Typedef typedef_ in library.typedefs) { |
| if (!typedefs.add(typedef_)) { |
| problem(typedef_, "Typedef '$typedef_' declared more than once."); |
| } |
| } |
| library.members.forEach(declareMember); |
| for (Class class_ in library.classes) { |
| class_.members.forEach(declareMember); |
| } |
| } |
| visitChildren(component); |
| } finally { |
| for (Library library in component.libraries) { |
| library.members.forEach(undeclareMember); |
| for (Class class_ in library.classes) { |
| class_.members.forEach(undeclareMember); |
| } |
| } |
| variableStack.forEach(undeclareVariable); |
| } |
| } |
| |
| @override |
| void visitLibrary(Library node) { |
| currentLibrary = node; |
| super.visitLibrary(node); |
| currentLibrary = null; |
| } |
| |
| @override |
| void visitExtension(Extension node) { |
| currentExtension = node; |
| declareTypeParameters(node.typeParameters); |
| final TreeNode? oldParent = enterParent(node); |
| node.visitChildren(this); |
| exitParent(oldParent); |
| undeclareTypeParameters(node.typeParameters); |
| currentExtension = null; |
| } |
| |
| void checkTypedef(Typedef node) { |
| TypedefState? 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; |
| Set<TypeParameter> savedTypeParameters = typeParametersInScope; |
| typeParametersInScope = node.typeParameters.toSet(); |
| TreeNode? 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; |
| } |
| |
| @override |
| void visitTypedef(Typedef node) { |
| checkTypedef(node); |
| // Enter and exit the node to check the parent pointer on the typedef node. |
| exitParent(enterParent(node)); |
| } |
| |
| @override |
| void visitField(Field node) { |
| currentMember = node; |
| TreeNode? oldParent = enterParent(node); |
| bool isTopLevel = node.parent == currentLibrary; |
| if (isTopLevel && !node.isStatic) { |
| problem(node, "The top-level field '${node.name.text}' should be static", |
| context: node); |
| } |
| if (node.isConst && !node.isStatic) { |
| problem(node, "The const field '${node.name.text}' should be static", |
| context: node); |
| } |
| bool isImmutable = node.isLate |
| ? (node.isFinal && node.initializer != null) |
| : (node.isFinal || node.isConst); |
| if (isImmutable == node.hasSetter) { |
| if (node.hasSetter) { |
| problem(node, |
| "The immutable field '${node.name.text}' has a setter reference", |
| context: node); |
| } else { |
| if (isOutline && node.isLate) { |
| // TODO(johnniwinther): Should we add a flag on Field for having |
| // a declared initializer? |
| // The initializer is not included in the outline so we can't tell |
| // whether it has an initializer or not. |
| } else { |
| problem(node, |
| "The mutable field '${node.name.text}' has no setter reference", |
| context: node); |
| } |
| } |
| } |
| classTypeParametersAreInScope = !node.isStatic; |
| node.initializer?.accept(this); |
| node.type.accept(this); |
| classTypeParametersAreInScope = false; |
| visitList(node.annotations, this); |
| exitParent(oldParent); |
| currentMember = null; |
| } |
| |
| @override |
| void visitProcedure(Procedure node) { |
| currentMember = node; |
| TreeNode? 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.abstractForwardingStubTarget != null && |
| !(node.isForwardingStub || node.isForwardingSemiStub)) { |
| problem( |
| node, |
| "Only forwarding stubs can have a forwarding stub interface target " |
| "$node."); |
| } |
| if (node.concreteForwardingStubTarget != 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; |
| } |
| |
| @override |
| void visitConstructor(Constructor node) { |
| currentMember = node; |
| classTypeParametersAreInScope = true; |
| // The constructor member needs special treatment due to parameters being |
| // in scope in the initializer list. |
| TreeNode? 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; |
| } |
| |
| @override |
| void visitClass(Class node) { |
| currentClass = node; |
| declareTypeParameters(node.typeParameters); |
| TreeNode? 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; |
| } |
| |
| @override |
| void visitFunctionNode(FunctionNode node) { |
| declareTypeParameters(node.typeParameters); |
| bool savedInCatchBlock = inCatchBlock; |
| AsyncMarker savedAsyncMarker = currentAsyncMarker; |
| currentAsyncMarker = node.asyncMarker; |
| if (!isOutline && |
| node.asyncMarker == AsyncMarker.Async && |
| node.futureValueType == null) { |
| problem(node, |
| "No future value type set for async function in opt-in library."); |
| } |
| inCatchBlock = false; |
| visitWithLocalScope(node); |
| inCatchBlock = savedInCatchBlock; |
| currentAsyncMarker = savedAsyncMarker; |
| undeclareTypeParameters(node.typeParameters); |
| } |
| |
| @override |
| void 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 (TypeParameter 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); |
| } |
| |
| @override |
| void visitBlock(Block node) { |
| visitWithLocalScope(node); |
| } |
| |
| @override |
| void visitForStatement(ForStatement node) { |
| visitWithLocalScope(node); |
| } |
| |
| @override |
| void visitForInStatement(ForInStatement node) { |
| visitWithLocalScope(node); |
| } |
| |
| @override |
| void visitLet(Let node) { |
| if (_isCompileTimeErrorEncoding(node)) return; |
| visitWithLocalScope(node); |
| } |
| |
| @override |
| void visitInvalidExpression(InvalidExpression node) { |
| return; |
| } |
| |
| @override |
| void 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); |
| } |
| |
| @override |
| void visitCatch(Catch node) { |
| bool savedInCatchBlock = inCatchBlock; |
| inCatchBlock = true; |
| visitWithLocalScope(node); |
| inCatchBlock = savedInCatchBlock; |
| } |
| |
| @override |
| void visitReturnStatement(ReturnStatement node) { |
| switch (currentAsyncMarker) { |
| case AsyncMarker.Sync: |
| case AsyncMarker.Async: |
| // ok |
| break; |
| case AsyncMarker.SyncStar: |
| case AsyncMarker.AsyncStar: |
| if (node.expression != null) { |
| problem( |
| node, |
| "Return statement in function with async marker: " |
| "$currentAsyncMarker"); |
| } |
| break; |
| } |
| super.visitReturnStatement(node); |
| } |
| |
| @override |
| void visitYieldStatement(YieldStatement node) { |
| switch (currentAsyncMarker) { |
| case AsyncMarker.Sync: |
| case AsyncMarker.Async: |
| problem( |
| node, |
| "Yield statement in function with async marker: " |
| "$currentAsyncMarker"); |
| break; |
| case AsyncMarker.SyncStar: |
| case AsyncMarker.AsyncStar: |
| // ok |
| break; |
| } |
| super.visitYieldStatement(node); |
| } |
| |
| @override |
| void visitRethrow(Rethrow node) { |
| if (!inCatchBlock) { |
| problem(node, "Rethrow must be inside a Catch block."); |
| } |
| } |
| |
| @override |
| void visitVariableDeclaration(VariableDeclaration node) { |
| TreeNode? 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 && |
| parent is! Typedef) { |
| 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 (constantsAreAlwaysInlined) { |
| if (!(initializer is InvalidExpression || |
| initializer is ConstantExpression && |
| initializer.constant is UnevaluatedConstant)) { |
| problem(node, "Constant VariableDeclaration"); |
| } |
| } |
| } |
| } |
| |
| @override |
| void visitVariableGet(VariableGet node) { |
| checkVariableInScope(node.variable, node); |
| visitChildren(node); |
| if (constantsAreAlwaysInlined && afterConst && node.variable.isConst) { |
| problem(node, "VariableGet of const variable '${node.variable}'."); |
| } |
| } |
| |
| @override |
| void visitVariableSet(VariableSet node) { |
| checkVariableInScope(node.variable, node); |
| visitChildren(node); |
| } |
| |
| @override |
| void visitStaticGet(StaticGet node) { |
| visitChildren(node); |
| // ignore: unnecessary_null_comparison |
| 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 (constantsAreAlwaysInlined && |
| afterConst && |
| node.target is Field && |
| node.target.isConst) { |
| problem(node, "StaticGet of const field '${node.target}'."); |
| } |
| } |
| |
| @override |
| void visitStaticSet(StaticSet node) { |
| visitChildren(node); |
| // ignore: unnecessary_null_comparison |
| 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 |
| void 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."); |
| } |
| } |
| |
| @override |
| void visitTypedefTearOff(TypedefTearOff node) { |
| declareTypeParameters(node.typeParameters); |
| super.visitTypedefTearOff(node); |
| undeclareTypeParameters(node.typeParameters); |
| } |
| |
| void checkTargetedInvocation(Member target, InvocationExpression node) { |
| visitChildren(node); |
| // ignore: unnecessary_null_comparison |
| 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 |
| void 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) { |
| NamedExpression 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 |
| void visitListLiteral(ListLiteral node) { |
| visitChildren(node); |
| if (afterConst && node.isConst) { |
| problem(node, "Constant list literal."); |
| } |
| } |
| |
| @override |
| void visitSetLiteral(SetLiteral node) { |
| visitChildren(node); |
| if (afterConst && node.isConst) { |
| problem(node, "Constant set literal."); |
| } |
| } |
| |
| @override |
| void visitMapLiteral(MapLiteral node) { |
| visitChildren(node); |
| if (afterConst && node.isConst) { |
| problem(node, "Constant map literal."); |
| } |
| } |
| |
| @override |
| void visitSymbolLiteral(SymbolLiteral node) { |
| if (afterConst) { |
| problem(node, "Symbol literal."); |
| } |
| } |
| |
| @override |
| void visitContinueSwitchStatement(ContinueSwitchStatement node) { |
| // ignore: unnecessary_null_comparison |
| 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 as SwitchStatement; |
| for (SwitchCase switchCase in statement.cases) { |
| if (switchCase == node.target) return; |
| } |
| problem(node, "Switch case isn't child of parent."); |
| } |
| } |
| |
| @override |
| void 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 |
| void visitUnevaluatedConstant(UnevaluatedConstant constant) { |
| bool savedInUnevaluatedConstant = inUnevaluatedConstant; |
| inUnevaluatedConstant = true; |
| TreeNode? oldParent = currentParent; |
| currentParent = null; |
| constant.expression.accept(this); |
| currentParent = oldParent; |
| inUnevaluatedConstant = savedInUnevaluatedConstant; |
| } |
| |
| @override |
| void defaultMemberReference(Member node) { |
| if (node.transformerFlags & TransformerFlag.seenByVerifier == 0) { |
| problem( |
| node, "Dangling reference to '$node', parent is: '${node.parent}'."); |
| } |
| } |
| |
| @override |
| void visitClassReference(Class node) { |
| if (!classes.contains(node)) { |
| problem( |
| node, "Dangling reference to '$node', parent is: '${node.parent}'."); |
| } |
| } |
| |
| @override |
| void visitTypedefReference(Typedef node) { |
| if (!typedefs.contains(node)) { |
| problem( |
| node, "Dangling reference to '$node', parent is: '${node.parent}'"); |
| } |
| } |
| |
| @override |
| void visitTypeParameterType(TypeParameterType node) { |
| TypeParameter parameter = node.parameter; |
| if (!typeParametersInScope.contains(parameter)) { |
| TreeNode? owner = parameter.parent is FunctionNode |
| ? parameter.parent!.parent |
| : parameter.parent; |
| problem( |
| currentParent, |
| "Type parameter '$parameter' referenced out of" |
| " scope, owner is: '${owner}'."); |
| } |
| if (parameter.parent is Class && !classTypeParametersAreInScope) { |
| problem( |
| currentParent, |
| "Type parameter '$parameter' referenced from" |
| " static context, parent is: '${parameter.parent}'."); |
| } |
| } |
| |
| @override |
| void 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) { |
| bool isOk = false; |
| if (currentParent is FunctionNode) { |
| TreeNode? functionNodeParent = currentParent!.parent; |
| if (functionNodeParent is Constructor) { |
| if (functionNodeParent.parent == node.classNode) { |
| // We only allow references to anonymous mixins in types as the |
| // return type of its own constructor. |
| isOk = true; |
| } |
| } |
| } |
| if (!isOk) { |
| problem( |
| currentParent, "Type $node references an anonymous mixin class."); |
| } |
| } |
| defaultDartType(node); |
| } |
| |
| @override |
| void 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."); |
| } |
| } |
| |
| @override |
| void visitConstantExpression(ConstantExpression node) { |
| bool oldInConstant = inConstant; |
| inConstant = true; |
| visitChildren(node); |
| inConstant = oldInConstant; |
| } |
| |
| @override |
| void visitTypeParameter(TypeParameter node) { |
| if (inConstant) { |
| // Don't expect the type parameters to have the current parent as parent. |
| node.visitChildren(this); |
| } else { |
| visitChildren(node); |
| } |
| } |
| |
| @override |
| void visitTypedefTearOffConstant(TypedefTearOffConstant node) { |
| declareTypeParameters(node.parameters); |
| super.visitTypedefTearOffConstant(node); |
| undeclareTypeParameters(node.parameters); |
| } |
| |
| @override |
| void visitInstanceInvocation(InstanceInvocation node) { |
| if (node.name != node.interfaceTarget.name) { |
| problem( |
| node, |
| "Instance invocation with name '${node.name}' has a " |
| "target with name '${node.interfaceTarget.name}'."); |
| } |
| super.visitInstanceInvocation(node); |
| } |
| |
| @override |
| void visitInstanceGet(InstanceGet node) { |
| if (node.name != node.interfaceTarget.name) { |
| problem( |
| node, |
| "Instance get with name '${node.name}' has a " |
| "target with name '${node.interfaceTarget.name}'."); |
| } |
| super.visitInstanceGet(node); |
| } |
| |
| @override |
| void visitInstanceTearOff(InstanceTearOff node) { |
| if (node.name != node.interfaceTarget.name) { |
| problem( |
| node, |
| "Instance tear-off with name '${node.name}' has a " |
| "target with name '${node.interfaceTarget.name}'."); |
| } |
| super.visitInstanceTearOff(node); |
| } |
| |
| @override |
| void visitInstanceSet(InstanceSet node) { |
| if (node.name != node.interfaceTarget.name) { |
| problem( |
| node, |
| "Instance set with name '${node.name}' has a " |
| "target with name '${node.interfaceTarget.name}'."); |
| } |
| super.visitInstanceSet(node); |
| } |
| } |
| |
| 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 |
| void visitLibrary(Library node) { |
| _staticTypeContext.enterLibrary(node); |
| super.visitLibrary(node); |
| _staticTypeContext.leaveLibrary(node); |
| } |
| |
| @override |
| void visitField(Field node) { |
| currentMember = node; |
| _staticTypeContext.enterMember(node); |
| super.visitField(node); |
| _staticTypeContext.leaveMember(node); |
| currentMember = node; |
| } |
| |
| @override |
| void visitProcedure(Procedure node) { |
| currentMember = node; |
| _staticTypeContext.enterMember(node); |
| super.visitProcedure(node); |
| _staticTypeContext.leaveMember(node); |
| currentMember = node; |
| } |
| |
| @override |
| void visitConstructor(Constructor node) { |
| currentMember = node; |
| _staticTypeContext.enterMember(node); |
| super.visitConstructor(node); |
| _staticTypeContext.leaveMember(node); |
| currentMember = null; |
| } |
| |
| @override |
| void visitLet(Let node) { |
| if (_isCompileTimeErrorEncoding(node)) return; |
| super.visitLet(node); |
| } |
| |
| @override |
| void visitInvalidExpression(InvalidExpression node) { |
| return; |
| } |
| |
| @override |
| void 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<void> with VisitorVoidMixin { |
| static void check(TreeNode node) { |
| node.accept(new CheckParentPointers(node.parent)); |
| } |
| |
| TreeNode? parent; |
| |
| CheckParentPointers([this.parent]); |
| |
| @override |
| void 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}'."); |
| } |
| TreeNode? oldParent = parent; |
| parent = node; |
| node.visitChildren(this); |
| parent = oldParent; |
| } |
| } |
| |
| void checkInitializers(Constructor constructor) { |
| // TODO(ahe): I'll add more here in other CLs. |
| } |
| |
| bool _isCompileTimeErrorEncoding(TreeNode? node) { |
| return node is Let && node.variable.initializer is InvalidExpression; |
| } |