| Index: pkg/kernel/lib/verifier.dart
|
| diff --git a/pkg/kernel/lib/verifier.dart b/pkg/kernel/lib/verifier.dart
|
| index b2ceb9cf7f0a9cef5248ad3a01b7cfdc0980c41e..2600f8dbf08bfd201b3e9aba0a3c377356974d45 100644
|
| --- a/pkg/kernel/lib/verifier.dart
|
| +++ b/pkg/kernel/lib/verifier.dart
|
| @@ -10,6 +10,33 @@ void verifyProgram(Program program) {
|
| VerifyingVisitor.check(program);
|
| }
|
|
|
| +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 ?? "";
|
| + return "$file:${location.line}:${location.column}: Verification error:"
|
| + " $details";
|
| + } else {
|
| + return
|
| + "Verification error: $details\nContext: '$context'.\nNode: '$node'.";
|
| + }
|
| + }
|
| +}
|
| +
|
| /// Checks that a kernel program is well-formed.
|
| ///
|
| /// This does not include any kind of type checking.
|
| @@ -37,11 +64,15 @@ class VerifyingVisitor extends RecursiveVisitor {
|
| visitChildren(node);
|
| }
|
|
|
| + problem(TreeNode node, String details) {
|
| + throw new VerificationError(context, node, details);
|
| + }
|
| +
|
| 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}.';
|
| + problem(node,
|
| + "Incorrect parent pointer: expected '${node.parent.runtimeType}',"
|
| + " but found: '${currentParent.runtimeType}'.");
|
| }
|
| var oldParent = currentParent;
|
| currentParent = node;
|
| @@ -75,7 +106,8 @@ class VerifyingVisitor extends RecursiveVisitor {
|
|
|
| void declareMember(Member member) {
|
| if (member.transformerFlags & TransformerFlag.seenByVerifier != 0) {
|
| - throw '$member has been declared more than once (${member.location})';
|
| + problem(member.function,
|
| + "Member '$member' has been declared more than once.");
|
| }
|
| member.transformerFlags |= TransformerFlag.seenByVerifier;
|
| }
|
| @@ -86,7 +118,7 @@ class VerifyingVisitor extends RecursiveVisitor {
|
|
|
| void declareVariable(VariableDeclaration variable) {
|
| if (variable.flags & VariableDeclaration.FlagInScope != 0) {
|
| - throw '$variable declared more than once (${variable.location})';
|
| + problem(variable, "Variable '$variable' declared more than once.");
|
| }
|
| variable.flags |= VariableDeclaration.FlagInScope;
|
| variableStack.add(variable);
|
| @@ -100,7 +132,7 @@ class VerifyingVisitor extends RecursiveVisitor {
|
| for (int i = 0; i < parameters.length; ++i) {
|
| var parameter = parameters[i];
|
| if (!typeParameters.add(parameter)) {
|
| - throw 'Type parameter $parameter redeclared in $context';
|
| + problem(parameter, "Type parameter '$parameter' redeclared.");
|
| }
|
| }
|
| }
|
| @@ -111,8 +143,7 @@ class VerifyingVisitor extends RecursiveVisitor {
|
|
|
| void checkVariableInScope(VariableDeclaration variable, TreeNode where) {
|
| if (variable.flags & VariableDeclaration.FlagInScope == 0) {
|
| - throw 'Variable $variable used out of scope in $context '
|
| - '(${where.location})';
|
| + problem(where, "Variable '$variable' used out of scope.");
|
| }
|
| }
|
|
|
| @@ -121,7 +152,7 @@ class VerifyingVisitor extends RecursiveVisitor {
|
| for (var library in program.libraries) {
|
| for (var class_ in library.classes) {
|
| if (!classes.add(class_)) {
|
| - throw 'Class $class_ declared more than once.';
|
| + problem(class_, "Class '$class_' declared more than once.");
|
| }
|
| }
|
| library.members.forEach(declareMember);
|
| @@ -208,8 +239,8 @@ class VerifyingVisitor extends RecursiveVisitor {
|
| 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';
|
| + problem(currentParent,
|
| + "Named parameters are not sorted on function type ($node).");
|
| }
|
| }
|
| declareTypeParameters(node.typeParameters);
|
| @@ -260,13 +291,13 @@ class VerifyingVisitor extends RecursiveVisitor {
|
| visitStaticGet(StaticGet node) {
|
| visitChildren(node);
|
| if (node.target == null) {
|
| - throw 'StaticGet without target found in $context.';
|
| + problem(node, "StaticGet without target.");
|
| }
|
| if (!node.target.hasGetter) {
|
| - throw 'StaticGet to ${node.target} without getter found in $context';
|
| + problem(node, "StaticGet of '${node.target}' without getter.");
|
| }
|
| if (node.target.isInstanceMember) {
|
| - throw 'StaticGet to ${node.target} that is not static found in $context';
|
| + problem(node, "StaticGet of '${node.target}' that's an instance member.");
|
| }
|
| }
|
|
|
| @@ -274,34 +305,49 @@ class VerifyingVisitor extends RecursiveVisitor {
|
| visitStaticSet(StaticSet node) {
|
| visitChildren(node);
|
| if (node.target == null) {
|
| - throw 'StaticSet without target found in $context.';
|
| + problem(node, "StaticSet without target.");
|
| }
|
| if (!node.target.hasSetter) {
|
| - throw 'StaticSet to ${node.target} without setter found in $context';
|
| + problem(node, "StaticSet to '${node.target}' without setter.");
|
| }
|
| if (node.target.isInstanceMember) {
|
| - throw 'StaticSet to ${node.target} that is not static found in $context';
|
| + 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.");
|
| + }
|
| + }
|
| +
|
| + void checkTargetedInvocation(Member target, InvocationExpression node) {
|
| visitChildren(node);
|
| - if (node.target == null) {
|
| - throw 'StaticInvocation without target found in $context.';
|
| + if (target == null) {
|
| + problem(node, "${node.runtimeType} without target.");
|
| }
|
| - if (node.target.isInstanceMember) {
|
| - throw 'StaticInvocation to ${node.target} that is not static found in '
|
| - '$context';
|
| + if (target.function == null) {
|
| + problem(node, "${node.runtimeType} without function.");
|
| }
|
| - if (!areArgumentsCompatible(node.arguments, node.target.function)) {
|
| - throw 'StaticInvocation with incompatible arguments to '
|
| - '${node.target} found in $context';
|
| + if (!areArgumentsCompatible(node.arguments, target.function)) {
|
| + problem(node,
|
| + "${node.runtimeType} with incompatible arguments for '${target}'.");
|
| }
|
| - 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';
|
| + 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}'.");
|
| }
|
| }
|
|
|
| @@ -309,15 +355,14 @@ class VerifyingVisitor extends RecursiveVisitor {
|
| visitDirectPropertyGet(DirectPropertyGet node) {
|
| visitChildren(node);
|
| if (node.target == null) {
|
| - throw 'DirectPropertyGet without target found in $context.';
|
| + problem(node, "DirectPropertyGet without target.");
|
| }
|
| if (!node.target.hasGetter) {
|
| - throw 'DirectPropertyGet to ${node.target} without getter found in '
|
| - '$context';
|
| + problem(node, "DirectPropertyGet of '${node.target}' without getter.");
|
| }
|
| if (!node.target.isInstanceMember) {
|
| - throw 'DirectPropertyGet to ${node.target} that is static found in '
|
| - '$context';
|
| + problem(node, "DirectPropertyGet of '${node.target}' that isn't an"
|
| + " instance member.");
|
| }
|
| }
|
|
|
| @@ -325,56 +370,33 @@ class VerifyingVisitor extends RecursiveVisitor {
|
| visitDirectPropertySet(DirectPropertySet node) {
|
| visitChildren(node);
|
| if (node.target == null) {
|
| - throw 'DirectPropertySet without target found in $context.';
|
| + problem(node, "DirectPropertySet without target.");
|
| }
|
| if (!node.target.hasSetter) {
|
| - throw 'DirectPropertyGet to ${node.target} without setter found in '
|
| - '$context';
|
| + problem(node, "DirectPropertySet of '${node.target}' without setter.");
|
| }
|
| if (!node.target.isInstanceMember) {
|
| - throw 'DirectPropertySet to ${node.target} that is static found in '
|
| - '$context';
|
| + problem(node, "DirectPropertySet of '${node.target}' that is static.");
|
| }
|
| }
|
|
|
| @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';
|
| + checkTargetedInvocation(node.target, node);
|
| + if (node.receiver == null) {
|
| + problem(node, "DirectMethodInvocation without receiver.");
|
| }
|
| }
|
|
|
| @override
|
| visitConstructorInvocation(ConstructorInvocation node) {
|
| - visitChildren(node);
|
| - if (node.target == null) {
|
| - throw 'ConstructorInvocation without target found in $context.';
|
| - }
|
| + checkTargetedInvocation(node.target, node);
|
| 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';
|
| + problem(node, "ConstructorInvocation of abstract class.");
|
| }
|
| - 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';
|
| + if (node.isConst && !node.target.isConst) {
|
| + problem(node, "Constant ConstructorInvocation fo '${node.target}' that"
|
| + " isn't const.");
|
| }
|
| }
|
|
|
| @@ -400,16 +422,16 @@ class VerifyingVisitor extends RecursiveVisitor {
|
| @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}';
|
| + problem(node,
|
| + "Dangling reference to '$node', parent is: '${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}';
|
| + problem(node,
|
| + "Dangling reference to '$node', parent is: '${node.parent}'.");
|
| }
|
| }
|
|
|
| @@ -417,13 +439,12 @@ class VerifyingVisitor extends RecursiveVisitor {
|
| 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}';
|
| + problem(currentParent, "Type parameter '$parameter' referenced out of"
|
| + " scope, parent is: '${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}';
|
| + problem(currentParent, "Type parameter '$parameter' referenced from"
|
| + " static context, parent is '${parameter.parent}'.");
|
| }
|
| }
|
|
|
| @@ -431,9 +452,9 @@ class VerifyingVisitor extends RecursiveVisitor {
|
| 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.';
|
| + problem(currentParent, "Type $node provides ${node.typeArguments.length}"
|
| + " type arguments but the class declares"
|
| + " ${node.classNode.typeParameters.length} parameters.");
|
| }
|
| }
|
| }
|
| @@ -449,9 +470,10 @@ class CheckParentPointers extends Visitor {
|
|
|
| defaultTreeNode(TreeNode node) {
|
| if (node.parent != parent) {
|
| - throw 'Parent pointer on ${node.runtimeType} '
|
| - 'is ${node.parent.runtimeType} '
|
| - 'but should be ${parent.runtimeType}';
|
| + throw new VerificationError(parent, node,
|
| + "Parent pointer on '${node.runtimeType}' "
|
| + "is '${node.parent.runtimeType}' "
|
| + "but should be '${parent.runtimeType}'.");
|
| }
|
| var oldParent = parent;
|
| parent = node;
|
|
|