blob: 36d2095c77488966572312d7f85e131dddb18f46 [file] [log] [blame] [edit]
// 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';
void runSanityChecks(Program program) {
CheckParentPointers.check(program);
CheckReferences.check(program);
CheckFileUris.check(program);
}
class CheckParentPointers extends FakeNodeVisitor {
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;
}
}
/// Checks that references refer to something in scope.
///
/// Currently only checks member, class, and type parameter references.
class CheckReferences extends RecursiveVisitor {
final Set<Member> members = new Set<Member>();
final Set<Class> classes = new Set<Class>();
final Set<TypeParameter> typeParameters = new Set<TypeParameter>();
Member currentMember;
Class currentClass;
TreeNode get context => currentMember ?? currentClass;
static void check(Program program) {
program.accept(new CheckReferences());
}
visitProgram(Program program) {
for (var library in program.libraries) {
classes.addAll(library.classes);
members.addAll(library.members);
for (var class_ in library.classes) {
members.addAll(class_.members);
}
}
program.visitChildren(this);
}
defaultMember(Member node) {
currentMember = node;
node.visitChildren(this);
currentMember = null;
}
visitClass(Class node) {
currentClass = node;
typeParameters.addAll(node.typeParameters);
node.visitChildren(this);
typeParameters.removeAll(node.typeParameters);
currentClass = null;
}
visitFunctionNode(FunctionNode node) {
typeParameters.addAll(node.typeParameters);
node.visitChildren(this);
typeParameters.removeAll(node.typeParameters);
}
@override
defaultMemberReference(Member node) {
if (!members.contains(node)) {
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) {
if (!typeParameters.contains(node.parameter)) {
throw 'Type parameter ${node.parameter} referenced out of scope '
'in $context.\n'
'Parent pointer is set to ${node.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.';
}
}
}
abstract class FakeNode implements TreeNode {}
abstract class FakeNodeVisitor extends Visitor {
visitFakeNode(FakeNode node) => defaultNode(node);
}
class FakeExpression extends Expression implements FakeNode {
Expression node;
FakeExpression(this.node) {
node?.parent = this;
}
accept(FakeNodeVisitor v) => v.visitFakeNode(this);
visitChildren(Visitor v) {
node?.accept(v);
}
transformChildren(Transformer v) {
if (node != null) {
node = node.accept(v);
node?.parent = this;
}
}
DartType getStaticType(types) => const BottomType();
}
class FakeStatement extends Statement implements FakeNode {
Statement node;
FakeStatement(this.node) {
node?.parent = this;
}
accept(FakeNodeVisitor v) => v.visitFakeNode(this);
visitChildren(Visitor v) {
node?.accept(v);
}
transformChildren(Transformer v) {
if (node != null) {
node = node.accept(v);
node?.parent = this;
}
}
}
class InsertWrappers extends Transformer {
defaultExpression(node) => new FakeExpression(defaultTreeNode(node));
defaultStatement(node) => new FakeStatement(defaultTreeNode(node));
visitVariableDeclaration(VariableDeclaration node) {
return defaultTreeNode(node);
}
}
class CheckTransformers extends FakeNodeVisitor {
static void transformAndCheck(TreeNode node) {
var transformed = node.accept(new InsertWrappers());
CheckParentPointers.check(transformed);
transformed.accept(new CheckTransformers());
}
defaultNode(TreeNode node) {
if (node is FakeNode) {
if (node.parent is FakeNode) {
throw 'FakeNode was wrapped multiple times';
}
} else if (node is Expression ||
node is Statement && node is! VariableDeclaration) {
if (node.parent is! FakeNode) {
throw '${node.runtimeType} inside ${node.parent.runtimeType} was not wrapped';
}
}
node.visitChildren(this);
}
}
class SizeCounter extends RecursiveVisitor {
int size = 0;
int emptyArguments = 0;
void visit(TreeNode node) => node.accept(this);
visitArguments(Arguments node) {
super.visitArguments(node);
if (node.positional.isEmpty &&
node.positional.isEmpty &&
node.types.isEmpty) {
++emptyArguments;
}
}
defaultNode(TreeNode node) {
++size;
node.visitChildren(this);
}
}
class CheckFileUris extends RecursiveVisitor {
final Program program;
final List<String> problems = <String>[];
CheckFileUris(this.program);
visitLibrary(Library node) {
checkFileUri(node.fileUri, node);
return super.visitLibrary(node);
}
visitClass(Class node) {
if (!node.name.contains("^")) {
// TODO(ahe): These are mixin applications, find out where they come from
// and fix them.
checkFileUri(node.fileUri, node);
}
return super.visitClass(node);
}
visitField(Field node) {
checkFileUri(node.fileUri, node);
return super.visitField(node);
}
visitProcedure(Procedure node) {
checkFileUri(node.fileUri, node);
return super.visitProcedure(node);
}
checkFileUri(String uri, TreeNode node) {
if (uri == null) {
problems.add("${node.runtimeType} has no fileUri: $node");
return;
}
List<int> lineStarts = program.uriToLineStarts[uri];
if (lineStarts == null) {
problems.add("No line starts for $uri (via $node)");
return;
}
if (lineStarts.isEmpty) {
problems.add("No line starts are empty for $uri (via $node)");
return;
}
}
static void check(Program program) {
CheckFileUris checker = new CheckFileUris(program);
program.accept(checker);
if (checker.problems.isNotEmpty) {
throw checker.problems.join("\n");
}
}
}