Files
sdk/pkg/kernel/lib/verifier.dart
T
Aske Simon Christensen de2ce48e17 Finish type variables earlier so their bounds are available when building the program.
Check bounds in verifier.
Fixed an inconsistency in the printing of parent nodes in the verifier.

Closes https://github.com/dart-lang/sdk/issues/30838
Closes https://github.com/dart-lang/sdk/issues/31213
Closes https://github.com/dart-lang/sdk/issues/31700

Change-Id: I328ca2c5bfa01a6753a806abd2510c98fa2b7cfe
Reviewed-on: https://dart-review.googlesource.com/22500
Commit-Queue: Aske Simon Christensen <askesc@google.com>
Reviewed-by: Peter von der Ahé <ahe@google.com>
2017-12-22 11:06:52 +00:00

642 lines
19 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';
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\n"
"Context: '$context'.\n"
"Node: '$node'.";
}
}
}
enum TypedefState { Done, BeingChecked }
/// 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<Typedef> typedefs = new Set<Typedef>();
Set<TypeParameter> typeParametersInScope = new Set<TypeParameter>();
final List<VariableDeclaration> variableStack = <VariableDeclaration>[];
final Map<Typedef, TypedefState> typedefState = <Typedef, TypedefState>{};
bool classTypeParametersAreInScope = false;
/// If true, relax certain checks for *outline* mode. For example, don't
/// attempt to validate constructor initializers.
bool isOutline = false;
bool inCatchBlock = false;
Library currentLibrary;
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);
}
problem(TreeNode node, String details, {TreeNode context}) {
context ??= this.context;
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}'.");
}
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 (!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.");
}
}
visitProgram(Program program) {
try {
for (var library in program.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(program);
} finally {
for (var library in program.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;
}
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;
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);
}
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) {
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);
}
visitVariableGet(VariableGet node) {
checkVariableInScope(node.variable, node);
visitChildren(node);
}
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.");
}
}
@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.");
}
}
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.");
}
}
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
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
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.");
}
}
@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.");
}
}
}
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.
}