blob: 1a1b5ad606f82b05a9988524b00003cb8aecf4b1 [file] [log] [blame]
// 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);
visitChildren(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;
}
}