| // 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'; |
| |
| void verifyProgram(Program program) { |
| VerifyingVisitor.check(program); |
| } |
| |
| /// Checks that a kernel program is well-formed. |
| /// |
| /// This does not include any kind of type checking. |
| class VerifyingVisitor extends RecursiveVisitor { |
| final Set<Class> classes = new Set<Class>(); |
| final Set<TypeParameter> typeParameters = new Set<TypeParameter>(); |
| final List<VariableDeclaration> variableStack = <VariableDeclaration>[]; |
| bool classTypeParametersAreInScope = false; |
| |
| Member currentMember; |
| Class currentClass; |
| TreeNode currentParent; |
| |
| TreeNode get context => currentMember ?? currentClass; |
| |
| static void check(Program program) { |
| program.accept(new VerifyingVisitor()); |
| } |
| |
| defaultTreeNode(TreeNode node) { |
| visitChildren(node); |
| } |
| |
| TreeNode enterParent(TreeNode node) { |
| if (!identical(node.parent, currentParent)) { |
| throw 'Incorrect parent pointer on ${node.runtimeType} in $context. ' |
| 'Parent pointer is ${node.parent.runtimeType}, ' |
| 'actual parent is ${currentParent.runtimeType}.'; |
| } |
| 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) { |
| throw '$member has been declared more than once (${member.location})'; |
| } |
| member.transformerFlags |= TransformerFlag.seenByVerifier; |
| } |
| |
| void undeclareMember(Member member) { |
| member.transformerFlags &= ~TransformerFlag.seenByVerifier; |
| } |
| |
| void declareVariable(VariableDeclaration variable) { |
| if (variable.flags & VariableDeclaration.FlagInScope != 0) { |
| throw '$variable declared more than once (${variable.location})'; |
| } |
| 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 (!typeParameters.add(parameter)) { |
| throw 'Type parameter $parameter redeclared in $context'; |
| } |
| } |
| } |
| |
| void undeclareTypeParameters(List<TypeParameter> parameters) { |
| typeParameters.removeAll(parameters); |
| } |
| |
| void checkVariableInScope(VariableDeclaration variable, TreeNode where) { |
| if (variable.flags & VariableDeclaration.FlagInScope == 0) { |
| throw 'Variable $variable used out of scope in $context ' |
| '(${where.location})'; |
| } |
| } |
| |
| visitProgram(Program program) { |
| for (var library in program.libraries) { |
| for (var class_ in library.classes) { |
| if (!classes.add(class_)) { |
| throw 'Class $class_ declared more than once'; |
| } |
| } |
| library.members.forEach(declareMember); |
| for (var class_ in library.classes) { |
| class_.members.forEach(declareMember); |
| } |
| } |
| visitChildren(program); |
| for (var library in program.libraries) { |
| library.members.forEach(undeclareMember); |
| for (var class_ in library.classes) { |
| class_.members.forEach(undeclareMember); |
| } |
| } |
| } |
| |
| visitField(Field node) { |
| currentMember = node; |
| var oldParent = enterParent(node); |
| classTypeParametersAreInScope = !node.isStatic; |
| node.initializer?.accept(this); |
| classTypeParametersAreInScope = false; |
| visitList(node.annotations, this); |
| exitParent(oldParent); |
| currentMember = null; |
| } |
| |
| visitProcedure(Procedure node) { |
| currentMember = node; |
| var oldParent = enterParent(node); |
| classTypeParametersAreInScope = !node.isStatic; |
| 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); |
| 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); |
| visitWithLocalScope(node); |
| 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) { |
| throw 'Named parameters are not sorted on function type found in ' |
| '$context'; |
| } |
| } |
| declareTypeParameters(node.typeParameters); |
| for (var typeParameter in node.typeParameters) { |
| typeParameter.bound?.accept(this); |
| } |
| 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); |
| } |
| |
| visitCatch(Catch node) { |
| visitWithLocalScope(node); |
| } |
| |
| visitVariableDeclaration(VariableDeclaration node) { |
| visitChildren(node); |
| declareVariable(node); |
| } |
| |
| visitVariableGet(VariableGet node) { |
| checkVariableInScope(node.variable, node); |
| } |
| |
| visitVariableSet(VariableSet node) { |
| checkVariableInScope(node.variable, node); |
| visitChildren(node); |
| } |
| |
| @override |
| visitStaticGet(StaticGet node) { |
| visitChildren(node); |
| if (node.target == null) { |
| throw 'StaticGet without target found in $context.'; |
| } |
| if (!node.target.hasGetter) { |
| throw 'StaticGet to ${node.target} without getter found in $context'; |
| } |
| if (node.target.isInstanceMember) { |
| throw 'StaticGet to ${node.target} that is not static found in $context'; |
| } |
| } |
| |
| @override |
| visitStaticSet(StaticSet node) { |
| visitChildren(node); |
| if (node.target == null) { |
| throw 'StaticSet without target found in $context.'; |
| } |
| if (!node.target.hasSetter) { |
| throw 'StaticSet to ${node.target} without setter found in $context'; |
| } |
| if (node.target.isInstanceMember) { |
| throw 'StaticSet to ${node.target} that is not static found in $context'; |
| } |
| } |
| |
| @override |
| visitStaticInvocation(StaticInvocation node) { |
| visitChildren(node); |
| if (node.target == null) { |
| throw 'StaticInvocation without target found in $context.'; |
| } |
| if (node.target.isInstanceMember) { |
| throw 'StaticInvocation to ${node.target} that is not static found in ' |
| '$context'; |
| } |
| if (!areArgumentsCompatible(node.arguments, node.target.function)) { |
| throw 'StaticInvocation with incompatible arguments to ' |
| '${node.target} found in $context'; |
| } |
| if (node.arguments.types.length != |
| node.target.function.typeParameters.length) { |
| throw 'Wrong number of type arguments provided in StaticInvocation ' |
| 'to ${node.target} found in $context'; |
| } |
| } |
| |
| @override |
| visitDirectPropertyGet(DirectPropertyGet node) { |
| visitChildren(node); |
| if (node.target == null) { |
| throw 'DirectPropertyGet without target found in $context.'; |
| } |
| if (!node.target.hasGetter) { |
| throw 'DirectPropertyGet to ${node.target} without getter found in ' |
| '$context'; |
| } |
| if (!node.target.isInstanceMember) { |
| throw 'DirectPropertyGet to ${node.target} that is static found in ' |
| '$context'; |
| } |
| } |
| |
| @override |
| visitDirectPropertySet(DirectPropertySet node) { |
| visitChildren(node); |
| if (node.target == null) { |
| throw 'DirectPropertySet without target found in $context.'; |
| } |
| if (!node.target.hasSetter) { |
| throw 'DirectPropertyGet to ${node.target} without setter found in ' |
| '$context'; |
| } |
| if (!node.target.isInstanceMember) { |
| throw 'DirectPropertySet to ${node.target} that is static found in ' |
| '$context'; |
| } |
| } |
| |
| @override |
| visitDirectMethodInvocation(DirectMethodInvocation node) { |
| visitChildren(node); |
| if (node.target == null) { |
| throw 'DirectMethodInvocation without target found in $context.'; |
| } |
| if (!node.target.isInstanceMember) { |
| throw 'DirectMethodInvocation to ${node.target} that is static found in ' |
| '$context'; |
| } |
| if (!areArgumentsCompatible(node.arguments, node.target.function)) { |
| throw 'DirectMethodInvocation with incompatible arguments to ' |
| '${node.target} found in $context'; |
| } |
| if (node.arguments.types.length != |
| node.target.function.typeParameters.length) { |
| throw 'Wrong number of type arguments provided in DirectMethodInvocation ' |
| 'to ${node.target} found in $context'; |
| } |
| } |
| |
| @override |
| visitConstructorInvocation(ConstructorInvocation node) { |
| visitChildren(node); |
| if (node.target == null) { |
| throw 'ConstructorInvocation without target found in $context.'; |
| } |
| if (node.target.enclosingClass.isAbstract) { |
| throw 'ConstructorInvocation to abstract class found in $context'; |
| } |
| if (!areArgumentsCompatible(node.arguments, node.target.function)) { |
| throw 'ConstructorInvocation with incompatible arguments to ' |
| '${node.target} found in $context'; |
| } |
| if (node.arguments.types.length != |
| node.target.enclosingClass.typeParameters.length) { |
| throw 'Wrong number of type arguments provided in ConstructorInvocation ' |
| 'to ${node.target} found in $context'; |
| } |
| } |
| |
| 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 |
| defaultMemberReference(Member node) { |
| if (node.transformerFlags & TransformerFlag.seenByVerifier == 0) { |
| throw 'Dangling reference to $node found in $context.\n' |
| 'Parent pointer is set to ${node.parent}'; |
| } |
| } |
| |
| @override |
| visitClassReference(Class node) { |
| if (!classes.contains(node)) { |
| throw 'Dangling reference to $node found in $context.\n' |
| 'Parent pointer is set to ${node.parent}'; |
| } |
| } |
| |
| @override |
| visitTypeParameterType(TypeParameterType node) { |
| var parameter = node.parameter; |
| if (!typeParameters.contains(parameter)) { |
| throw 'Type parameter $parameter referenced out of scope in $context.\n' |
| 'Parent pointer is set to ${parameter.parent}'; |
| } |
| if (parameter.parent is Class && !classTypeParametersAreInScope) { |
| throw 'Type parameter $parameter referenced from static context ' |
| 'in $context.\n' |
| 'Parent pointer is set to ${parameter.parent}'; |
| } |
| } |
| |
| @override |
| visitInterfaceType(InterfaceType node) { |
| node.visitChildren(this); |
| if (node.typeArguments.length != node.classNode.typeParameters.length) { |
| throw 'Type $node provides ${node.typeArguments.length} type arguments ' |
| 'but the class declares ${node.classNode.typeParameters.length} ' |
| 'parameters. Found in $context.'; |
| } |
| } |
| } |
| |
| 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 'Parent pointer on ${node.runtimeType} ' |
| 'is ${node.parent.runtimeType} ' |
| 'but should be ${parent.runtimeType}'; |
| } |
| var oldParent = parent; |
| parent = node; |
| node.visitChildren(this); |
| parent = oldParent; |
| } |
| } |