// 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. import 'package:kernel/ast.dart'; import 'package:kernel/text/ast_to_text.dart'; import 'package:kernel/verifier.dart'; import 'package:test/test.dart'; const String varRegexp = "#t[0-9]+"; const String tvarRegexp = "#T[0-9]+"; /// Checks that the verifier correctly find errors in invalid components. /// /// The frontend should never generate invalid components, so we have to test /// these by manually constructing invalid ASTs. /// /// We mostly test negative cases here, as we get plenty of positive cases by /// compiling the Dart test suite with the verifier enabled. main() { positiveTest('Test harness has no errors', (TestHarness test) { test.addNode(NullLiteral()); }); negativeTest('VariableGet out of scope', matches("Variable '$varRegexp' used out of scope\\."), (TestHarness test) { test.addNode(VariableGet(test.makeVariable())); }); negativeTest('VariableSet out of scope', matches("Variable '$varRegexp' used out of scope\\."), (TestHarness test) { test.addNode(VariableSet(test.makeVariable(), new NullLiteral())); }); negativeTest('Variable block scope', matches("Variable '$varRegexp' used out of scope\\."), (TestHarness test) { VariableDeclaration variable = test.makeVariable(); test.addNode(Block([ new Block([variable]), new ReturnStatement(new VariableGet(variable)) ])); }); negativeTest('Variable let scope', matches("Variable '$varRegexp' used out of scope\\."), (TestHarness test) { VariableDeclaration variable = test.makeVariable(); test.addNode(LogicalExpression(new Let(variable, new VariableGet(variable)), '&&', new VariableGet(variable))); }); negativeTest('Variable redeclared', matches("Variable '$varRegexp' declared more than once\\."), (TestHarness test) { VariableDeclaration variable = test.makeVariable(); test.addNode(Block([variable, variable])); }); negativeTest('Member redeclared', "Member 'test_lib::Test::field' has been declared more than once.", (TestHarness test) { Field field = new Field(new Name('field'), initializer: new NullLiteral()); test.addNode(Class( name: 'Test', supertype: test.objectClass.asRawSupertype, fields: [field, field])); }); negativeTest('Class redeclared', "Class 'test_lib::OtherClass' declared more than once.", (TestHarness test) { test.addNode( test.otherClass); // Test harness also adds otherClass to component. }); negativeTest('Class type parameter redeclared', matches("Type parameter 'test_lib::Test::$tvarRegexp' redeclared\\."), (TestHarness test) { var parameter = test.makeTypeParameter(); test.addNode(Class( name: 'Test', supertype: test.objectClass.asRawSupertype, typeParameters: [parameter, parameter])); }); negativeTest('Member type parameter redeclared', matches("Type parameter '$tvarRegexp' redeclared\\."), (TestHarness test) { var parameter = test.makeTypeParameter(); test.addNode(Procedure( new Name('bar'), ProcedureKind.Method, new FunctionNode(new ReturnStatement(new NullLiteral()), typeParameters: [parameter, parameter]))); }); negativeTest( 'Type parameter out of scope', matches("Type parameter '$tvarRegexp' referenced out of scope," " parent is: 'null'\\."), (TestHarness test) { var parameter = test.makeTypeParameter(); test.addNode( ListLiteral([], typeArgument: new TypeParameterType(parameter))); }); negativeTest( 'Class type parameter from another class', "Type parameter 'test_lib::OtherClass::OtherT' referenced out of scope," " parent is: 'test_lib::OtherClass'.", (TestHarness test) { test.addNode( TypeLiteral(new TypeParameterType(test.otherClass.typeParameters[0]))); }); negativeTest( 'Class type parameter in static method', "Type parameter 'test_lib::TestClass::T' referenced from static context," " parent is: 'test_lib::TestClass'.", (TestHarness test) { test.addNode(Procedure( new Name('bar'), ProcedureKind.Method, new FunctionNode(new ReturnStatement( new TypeLiteral(new TypeParameterType(test.classTypeParameter)))), isStatic: true)); }); negativeTest( 'Class type parameter in static field', "Type parameter 'test_lib::TestClass::T' referenced from static context," " parent is: 'test_lib::TestClass'.", (TestHarness test) { test.addNode(Field(new Name('field'), initializer: new TypeLiteral(new TypeParameterType(test.classTypeParameter)), isStatic: true)); }); negativeTest( 'Method type parameter out of scope', matches("Type parameter '$tvarRegexp' referenced out of scope," " parent is: ''\\."), (TestHarness test) { var parameter = test.makeTypeParameter(); test.addNode(Class( name: 'Test', supertype: test.objectClass.asRawSupertype, procedures: [ new Procedure( new Name('generic'), ProcedureKind.Method, new FunctionNode(new EmptyStatement(), typeParameters: [parameter])), new Procedure( new Name('use'), ProcedureKind.Method, new FunctionNode(new ReturnStatement( new TypeLiteral(new TypeParameterType(parameter))))) ])); }); negativeTest( 'Interface type arity too low', "Type test_lib::OtherClass provides 0 type arguments" " but the class declares 1 parameters.", (TestHarness test) { test.addNode(TypeLiteral(new InterfaceType(test.otherClass, []))); }); negativeTest( 'Interface type arity too high', "Type test_lib::OtherClass provides 2 type arguments" " but the class declares 1 parameters.", (TestHarness test) { test.addNode(TypeLiteral(new InterfaceType( test.otherClass, [new DynamicType(), new DynamicType()]))); }); negativeTest( 'Dangling interface type', matches("Dangling reference to 'null::#class[0-9]+'," " parent is: 'null'\\."), (TestHarness test) { var orphan = new Class(); test.addNode(TypeLiteral(new InterfaceType(orphan))); }); negativeTest('Dangling field get', "Dangling reference to 'null::foo', parent is: 'null'.", (TestHarness test) { var orphan = new Field(new Name('foo')); test.addNode(DirectPropertyGet(new NullLiteral(), orphan)); }); negativeTest( 'Missing block parent pointer', "Incorrect parent pointer on ReturnStatement:" " expected 'Block', but found: 'Null'.", (TestHarness test) { var block = new Block([]); block.statements.add(new ReturnStatement()); test.addNode(block); }); negativeTest( 'Missing function parent pointer', "Incorrect parent pointer on FunctionNode:" " expected 'Procedure', but found: 'Null'.", (TestHarness test) { var procedure = new Procedure(new Name('bar'), ProcedureKind.Method, null); procedure.function = new FunctionNode(new EmptyStatement()); test.addNode(procedure); }); negativeTest('StaticGet without target', "StaticGet without target.", (TestHarness test) { test.addNode(StaticGet(null)); }); negativeTest('StaticSet without target', "StaticSet without target.", (TestHarness test) { test.addNode(StaticSet(null, new NullLiteral())); }); negativeTest( 'StaticInvocation without target', "StaticInvocation without target.", (TestHarness test) { test.addNode(StaticInvocation(null, new Arguments.empty())); }); positiveTest('Correct StaticInvocation', (TestHarness test) { var method = new Procedure( new Name('foo'), ProcedureKind.Method, new FunctionNode(new EmptyStatement(), positionalParameters: [new VariableDeclaration('p')]), isStatic: true); test.enclosingClass.addMember(method); test.addNode(StaticInvocation(method, new Arguments([new NullLiteral()]))); }); negativeTest( 'StaticInvocation with too many parameters', "StaticInvocation with incompatible arguments for" " 'test_lib::TestClass::bar'.", (TestHarness test) { var method = new Procedure(new Name('bar'), ProcedureKind.Method, new FunctionNode(new EmptyStatement()), isStatic: true); test.enclosingClass.addMember(method); test.addNode(StaticInvocation(method, new Arguments([new NullLiteral()]))); }); negativeTest( 'StaticInvocation with too few parameters', "StaticInvocation with incompatible arguments for" " 'test_lib::TestClass::bar'.", (TestHarness test) { var method = new Procedure( new Name('bar'), ProcedureKind.Method, new FunctionNode(new EmptyStatement(), positionalParameters: [new VariableDeclaration('p')]), isStatic: true); test.enclosingClass.addMember(method); test.addNode(StaticInvocation(method, new Arguments.empty())); }); negativeTest( 'StaticInvocation with unmatched named parameter', "StaticInvocation with incompatible arguments for" " 'test_lib::TestClass::bar'.", (TestHarness test) { var method = new Procedure(new Name('bar'), ProcedureKind.Method, new FunctionNode(new EmptyStatement()), isStatic: true); test.enclosingClass.addMember(method); test.addNode(StaticInvocation( method, new Arguments([], named: [new NamedExpression('p', new NullLiteral())]))); }); negativeTest( 'StaticInvocation with missing type argument', "StaticInvocation with wrong number of type arguments for" " 'test_lib::TestClass::bar'.", (TestHarness test) { var method = new Procedure( new Name('bar'), ProcedureKind.Method, new FunctionNode(new EmptyStatement(), typeParameters: [test.makeTypeParameter()]), isStatic: true); test.enclosingClass.addMember(method); test.addNode(StaticInvocation(method, new Arguments.empty())); }); negativeTest( 'ConstructorInvocation with missing type argument', "ConstructorInvocation with wrong number of type arguments for" " 'test_lib::TestClass::foo'.", (TestHarness test) { var class_ = new Class( name: 'Test', typeParameters: [test.makeTypeParameter()], supertype: test.objectClass.asRawSupertype); test.enclosingLibrary.addClass(class_); var constructor = new Constructor(new FunctionNode(new EmptyStatement()), name: new Name('foo')); test.enclosingClass.addMember(constructor); test.addNode(ConstructorInvocation(constructor, new Arguments.empty())); }); positiveTest('Valid typedef Foo = `(C) => void`', (TestHarness test) { var typedef_ = new Typedef( 'Foo', new FunctionType([test.otherClass.rawType], const VoidType())); test.addNode(typedef_); }); positiveTest('Valid typedef Foo = C', (TestHarness test) { var typedef_ = new Typedef( 'Foo', new InterfaceType(test.otherClass, [const DynamicType()])); test.addNode(typedef_); }); positiveTest('Valid typedefs Foo = Bar, Bar = C', (TestHarness test) { var foo = new Typedef('Foo', null); var bar = new Typedef('Bar', null); foo.type = new TypedefType(bar); bar.type = test.otherClass.rawType; test.enclosingLibrary.addTypedef(foo); test.enclosingLibrary.addTypedef(bar); }); positiveTest('Valid typedefs Foo = C, Bar = C', (TestHarness test) { var foo = new Typedef('Foo', null); var bar = new Typedef('Bar', null); foo.type = new InterfaceType(test.otherClass, [new TypedefType(bar)]); bar.type = test.otherClass.rawType; test.enclosingLibrary.addTypedef(foo); test.enclosingLibrary.addTypedef(bar); }); positiveTest('Valid typedef type in field', (TestHarness test) { var typedef_ = new Typedef( 'Foo', new FunctionType([test.otherClass.rawType], const VoidType())); var field = new Field(new Name('field'), type: new TypedefType(typedef_), isStatic: true); test.enclosingLibrary.addTypedef(typedef_); test.enclosingLibrary.addMember(field); }); negativeTest( 'Invalid typedef Foo = Foo', "The typedef 'typedef Foo = test_lib::Foo;\n'" " refers to itself", (TestHarness test) { var typedef_ = new Typedef('Foo', null); typedef_.type = new TypedefType(typedef_); test.addNode(typedef_); }); negativeTest( 'Invalid typedef Foo = `(Foo) => void`', "The typedef 'typedef Foo = (test_lib::Foo) → void;\n'" " refers to itself", (TestHarness test) { var typedef_ = new Typedef('Foo', null); typedef_.type = new FunctionType([new TypedefType(typedef_)], const VoidType()); test.addNode(typedef_); }); negativeTest( 'Invalid typedef Foo = `() => Foo`', "The typedef 'typedef Foo = () → test_lib::Foo;\n'" " refers to itself", (TestHarness test) { var typedef_ = new Typedef('Foo', null); typedef_.type = new FunctionType([], new TypedefType(typedef_)); test.addNode(typedef_); }); negativeTest( 'Invalid typedef Foo = C', "The typedef 'typedef Foo = test_lib::OtherClass;\n'" " refers to itself", (TestHarness test) { var typedef_ = new Typedef('Foo', null); typedef_.type = new InterfaceType(test.otherClass, [new TypedefType(typedef_)]); test.addNode(typedef_); }); negativeTest( 'Invalid typedefs Foo = Bar, Bar = Foo', "The typedef 'typedef Foo = test_lib::Bar;\n'" " refers to itself", (TestHarness test) { var foo = new Typedef('Foo', null); var bar = new Typedef('Bar', null); foo.type = new TypedefType(bar); bar.type = new TypedefType(foo); test.enclosingLibrary.addTypedef(foo); test.enclosingLibrary.addTypedef(bar); }); negativeTest( 'Invalid typedefs Foo = Bar, Bar = C', "The typedef 'typedef Foo = test_lib::Bar;\n'" " refers to itself", (TestHarness test) { var foo = new Typedef('Foo', null); var bar = new Typedef('Bar', null); foo.type = new TypedefType(bar); bar.type = new InterfaceType(test.otherClass, [new TypedefType(foo)]); test.enclosingLibrary.addTypedef(foo); test.enclosingLibrary.addTypedef(bar); }); negativeTest( 'Invalid typedefs Foo = C, Bar = C', "The typedef 'typedef Foo = test_lib::OtherClass;\n'" " refers to itself", (TestHarness test) { var foo = new Typedef('Foo', null); var bar = new Typedef('Bar', null); foo.type = new InterfaceType(test.otherClass, [new TypedefType(bar)]); bar.type = new InterfaceType(test.otherClass, [new TypedefType(foo)]); test.enclosingLibrary.addTypedef(foo); test.enclosingLibrary.addTypedef(bar); }); positiveTest('Valid long typedefs C20 = C19 = ... = C1 = C0 = dynamic', (TestHarness test) { var typedef_ = new Typedef('C0', const DynamicType()); test.enclosingLibrary.addTypedef(typedef_); for (int i = 1; i < 20; ++i) { typedef_ = new Typedef('C$i', new TypedefType(typedef_)); test.enclosingLibrary.addTypedef(typedef_); } }); negativeTest( 'Invalid long typedefs C20 = C19 = ... = C1 = C0 = C20', "The typedef 'typedef C0 = test_lib::C19;\n'" " refers to itself", (TestHarness test) { var typedef_ = new Typedef('C0', null); test.enclosingLibrary.addTypedef(typedef_); var first = typedef_; for (int i = 1; i < 20; ++i) { typedef_ = new Typedef('C$i', new TypedefType(typedef_)); test.enclosingLibrary.addTypedef(typedef_); } first.type = new TypedefType(typedef_); }); positiveTest('Valid typedef Foo = C', (TestHarness test) { var param = new TypeParameter('T', test.otherClass.rawType); var foo = new Typedef('Foo', new InterfaceType(test.otherClass, [new TypeParameterType(param)]), typeParameters: [param]); test.addNode(foo); }); positiveTest('Valid typedef Foo> = C', (TestHarness test) { var param = new TypeParameter('T', test.otherClass.rawType); param.bound = new InterfaceType(test.otherClass, [new TypeParameterType(param)]); var foo = new Typedef('Foo', new InterfaceType(test.otherClass, [new TypeParameterType(param)]), typeParameters: [param]); test.addNode(foo); }); positiveTest('Valid typedef Foo = dynamic, Bar> = C', (TestHarness test) { var fooParam = test.makeTypeParameter('T'); var foo = new Typedef('Foo', const DynamicType(), typeParameters: [fooParam]); var barParam = new TypeParameter('T', null); barParam.bound = new TypedefType(foo, [new TypeParameterType(barParam)]); var bar = new Typedef('Bar', new InterfaceType(test.otherClass, [new TypeParameterType(barParam)]), typeParameters: [barParam]); test.enclosingLibrary.addTypedef(foo); test.enclosingLibrary.addTypedef(bar); }); negativeTest( 'Invalid typedefs Foo>, Bar>', "The typedef 'typedef Foo> = dynamic;\n'" " refers to itself", (TestHarness test) { var fooParam = test.makeTypeParameter('T'); var foo = new Typedef('Foo', const DynamicType(), typeParameters: [fooParam]); var barParam = new TypeParameter('T', null); barParam.bound = new TypedefType(foo, [new TypeParameterType(barParam)]); var bar = new Typedef('Bar', new InterfaceType(test.otherClass, [new TypeParameterType(barParam)]), typeParameters: [barParam]); fooParam.bound = new TypedefType(bar, [new TypeParameterType(fooParam)]); test.enclosingLibrary.addTypedef(foo); test.enclosingLibrary.addTypedef(bar); }); negativeTest( 'Invalid typedef Foo = C', "The typedef 'typedef Foo> = " "test_lib::OtherClass;\n'" " refers to itself", (TestHarness test) { var param = new TypeParameter('T', null); var foo = new Typedef('Foo', new InterfaceType(test.otherClass, [new TypeParameterType(param)]), typeParameters: [param]); param.bound = new TypedefType(foo, [const DynamicType()]); test.addNode(foo); }); negativeTest( 'Typedef arity error', "The typedef type test_lib::Foo provides 0 type arguments" " but the typedef declares 1 parameters.", (TestHarness test) { var param = test.makeTypeParameter('T'); var foo = new Typedef('Foo', test.otherClass.rawType, typeParameters: [param]); var field = new Field(new Name('field'), type: new TypedefType(foo, []), isStatic: true); test.enclosingLibrary.addTypedef(foo); test.enclosingLibrary.addMember(field); }); negativeTest( 'Dangling typedef reference', "Dangling reference to 'typedef Foo = test_lib::OtherClass;\n'" ", parent is: 'null'", (TestHarness test) { var foo = new Typedef('Foo', test.otherClass.rawType, typeParameters: []); var field = new Field(new Name('field'), type: new TypedefType(foo, []), isStatic: true); test.enclosingLibrary.addMember(field); }); negativeTest('Non-static top-level field', "The top-level field 'field' should be static", (TestHarness test) { var field = new Field(new Name('field')); test.enclosingLibrary.addMember(field); }); } checkHasError(Component component, Matcher matcher) { try { verifyComponent(component); } on VerificationError catch (e) { expect(e.details, matcher); return; } fail('Failed to reject invalid component:\n${componentToString(component)}'); } class TestHarness { Component component; Class objectClass; Library stubLibrary; TypeParameter classTypeParameter; Library enclosingLibrary; Class enclosingClass; Procedure enclosingMember; Class otherClass; void addNode(TreeNode node) { if (node is Expression) { addExpression(node); } else if (node is Statement) { addStatement(node); } else if (node is Member) { addClassMember(node); } else if (node is Class) { addClass(node); } else if (node is Typedef) { addTypedef(node); } } void addExpression(Expression node) { addStatement(new ReturnStatement(node)); } void addStatement(Statement node) { var function = enclosingMember.function; function.body = node..parent = function; } void addClassMember(Member node) { enclosingClass.addMember(node); } void addTopLevelMember(Member node) { enclosingLibrary.addMember(node); } void addClass(Class node) { enclosingLibrary.addClass(node); } void addTypedef(Typedef node) { enclosingLibrary.addTypedef(node); } VariableDeclaration makeVariable() => new VariableDeclaration(null); TypeParameter makeTypeParameter([String name]) { return new TypeParameter(name, new InterfaceType(objectClass)); } TestHarness() { setupComponent(); } void setupComponent() { component = new Component(); stubLibrary = new Library(Uri.parse('dart:core')); component.libraries.add(stubLibrary..parent = component); stubLibrary.name = 'dart.core'; objectClass = new Class(name: 'Object'); stubLibrary.addClass(objectClass); enclosingLibrary = new Library(Uri.parse('file://test.dart')); component.libraries.add(enclosingLibrary..parent = component); enclosingLibrary.name = 'test_lib'; classTypeParameter = makeTypeParameter('T'); enclosingClass = new Class( name: 'TestClass', typeParameters: [classTypeParameter], supertype: objectClass.asRawSupertype); enclosingLibrary.addClass(enclosingClass); enclosingMember = new Procedure(new Name('test'), ProcedureKind.Method, new FunctionNode(new EmptyStatement())); enclosingClass.addMember(enclosingMember); otherClass = new Class( name: 'OtherClass', typeParameters: [makeTypeParameter('OtherT')], supertype: objectClass.asRawSupertype); enclosingLibrary.addClass(otherClass); } } negativeTest(String name, matcher, void makeTestCase(TestHarness test)) { if (matcher is String) { matcher = equals(matcher); } test(name, () { var test = new TestHarness(); makeTestCase(test); checkHasError(test.component, matcher); }); } positiveTest(String name, void makeTestCase(TestHarness test)) { test(name, () { var test = new TestHarness(); makeTestCase(test); verifyComponent(test.component); }); }