| Index: pkg/kernel/lib/verifier.dart
|
| diff --git a/pkg/kernel/lib/verifier.dart b/pkg/kernel/lib/verifier.dart
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..27cc492260ae32ead00e22b3e067b2030556802f
|
| --- /dev/null
|
| +++ b/pkg/kernel/lib/verifier.dart
|
| @@ -0,0 +1,292 @@
|
| +// 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 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) {
|
| + classes.addAll(library.classes);
|
| + 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;
|
| + typeParameters.addAll(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);
|
| + typeParameters.removeAll(node.typeParameters);
|
| + currentClass = null;
|
| + }
|
| +
|
| + visitFunctionNode(FunctionNode node) {
|
| + typeParameters.addAll(node.typeParameters);
|
| + visitWithLocalScope(node);
|
| + typeParameters.removeAll(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';
|
| + }
|
| + }
|
| + typeParameters.addAll(node.typeParameters);
|
| + for (var typeParameter in node.typeParameters) {
|
| + typeParameter.bound?.accept(this);
|
| + }
|
| + visitList(node.positionalParameters, this);
|
| + visitList(node.namedParameters, this);
|
| + node.returnType.accept(this);
|
| + typeParameters.removeAll(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
|
| + 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;
|
| + }
|
| +}
|
|
|