Support checking of malbounded types.

BUG=
R=karlklose@google.com

Review URL: https://codereview.chromium.org//48383003

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@29531 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
johnniwinther@google.com 2013-10-30 11:26:37 +00:00
parent 0354366832
commit 70e0cd4207
15 changed files with 296 additions and 41 deletions

View file

@ -174,6 +174,14 @@ abstract class Backend {
Enqueuer enqueuer,
TreeElements elements) {}
/// Register a runtime type variable bound tests between [typeArgument] and
/// [bound].
void registerTypeVariableBoundsSubtypeCheck(DartType typeArgument,
DartType bound) {}
/// Registers that a type variable bounds check might occur at runtime.
void registerTypeVariableBoundCheck(TreeElements elements) {}
/// Register that the application may throw a [NoSuchMethodError].
void registerThrowNoSuchMethod(TreeElements elements) {}

View file

@ -1207,6 +1207,16 @@ class SubtypeVisitor extends MoreSpecificVisitor {
}
}
/**
* Callback used to check whether the [typeArgument] of [type] is a valid
* substitute for the bound of [typeVariable]. [bound] holds the bound against
* which [typeArgument] should be checked.
*/
typedef void CheckTypeVariableBound(GenericType type,
DartType typeArgument,
TypeVariableType typeVariable,
DartType bound);
class Types {
final Compiler compiler;
// TODO(karlklose): should we have a class Void?
@ -1250,12 +1260,44 @@ class Types {
return subtypeVisitor.isAssignable(r, s);
}
static const int IS_SUBTYPE = 1;
static const int MAYBE_SUBTYPE = 0;
static const int NOT_SUBTYPE = -1;
int computeSubtypeRelation(DartType t, DartType s) {
// TODO(johnniwinther): Compute this directly in [isPotentialSubtype].
if (isSubtype(t, s)) return IS_SUBTYPE;
return isPotentialSubtype(t, s) ? MAYBE_SUBTYPE : NOT_SUBTYPE;
}
bool isPotentialSubtype(DartType t, DartType s) {
// TODO(johnniwinther): Return a set of variable points in the positive
// cases.
return potentialSubtypeVisitor.isSubtype(t, s);
}
/**
* Checks the type arguments of [type] against the type variable bounds
* declared on [element]. Calls [checkTypeVariableBound] on each type
* argument and bound.
*/
void checkTypeVariableBounds(GenericType type,
CheckTypeVariableBound checkTypeVariableBound) {
TypeDeclarationElement element = type.element;
Link<DartType> typeArguments = type.typeArguments;
Link<DartType> typeVariables = element.typeVariables;
while (!typeVariables.isEmpty && !typeArguments.isEmpty) {
TypeVariableType typeVariable = typeVariables.head;
DartType bound = typeVariable.element.bound.subst(
type.typeArguments, element.typeVariables);
DartType typeArgument = typeArguments.head;
checkTypeVariableBound(type, typeArgument, typeVariable, bound);
typeVariables = typeVariables.tail;
typeArguments = typeArguments.tail;
}
assert(typeVariables.isEmpty && typeArguments.isEmpty);
}
/**
* Helper method for performing substitution of a linked list of types.
*

View file

@ -1078,6 +1078,16 @@ class JavaScriptBackend extends Backend {
enqueueClass(compiler.enqueuer.resolution, compiler.stringClass, elements);
}
void registerTypeVariableBoundsSubtypeCheck(DartType typeArgument,
DartType bound) {
rti.registerTypeVariableBoundsSubtypeCheck(typeArgument, bound);
}
void registerTypeVariableBoundCheck(TreeElements elements) {
enqueueInResolution(getThrowTypeError(), elements);
enqueueInResolution(getAssertIsSubtype(), elements);
}
void registerAbstractClassInstantiation(TreeElements elements) {
enqueueInResolution(getThrowAbstractClassInstantiationError(), elements);
// Also register the types of the arguments passed to this method.
@ -1483,6 +1493,10 @@ class JavaScriptBackend extends Backend {
return compiler.findHelper('throwRuntimeError');
}
Element getThrowTypeError() {
return compiler.findHelper('throwTypeError');
}
Element getThrowAbstractClassInstantiationError() {
return compiler.findHelper('throwAbstractClassInstantiationError');
}
@ -1535,6 +1549,10 @@ class JavaScriptBackend extends Backend {
return compiler.findHelper('runtimeTypeToString');
}
Element getAssertIsSubtype() {
return compiler.findHelper('assertIsSubtype');
}
Element getCheckSubtype() {
return compiler.findHelper('checkSubtype');
}

View file

@ -24,6 +24,10 @@ class RuntimeTypes {
// The set of classes that use one of their type variables as expressions
// to get the runtime type.
final Set<ClassElement> classesUsingTypeVariableExpression;
// The set of type arguments tested against type variable bounds.
final Set<DartType> checkedTypeArguments;
// The set of tested type variable bounds.
final Set<DartType> checkedBounds;
JavaScriptBackend get backend => compiler.backend;
@ -33,7 +37,9 @@ class RuntimeTypes {
classesNeedingRti = new Set<ClassElement>(),
methodsNeedingRti = new Set<Element>(),
rtiDependencies = new Map<ClassElement, Set<ClassElement>>(),
classesUsingTypeVariableExpression = new Set<ClassElement>();
classesUsingTypeVariableExpression = new Set<ClassElement>(),
checkedTypeArguments = new Set<DartType>(),
checkedBounds = new Set<DartType>();
Set<ClassElement> directlyInstantiatedArguments;
Set<ClassElement> allInstantiatedArguments;
@ -56,6 +62,12 @@ class RuntimeTypes {
classes.add(dependency);
}
void registerTypeVariableBoundsSubtypeCheck(DartType typeArgument,
DartType bound) {
checkedTypeArguments.add(typeArgument);
checkedBounds.add(bound);
}
bool usingFactoryWithTypeArguments = false;
/**
@ -298,19 +310,29 @@ class RuntimeTypes {
// We need to add classes occuring in function type arguments, like for
// instance 'I' for [: o is C<f> :] where f is [: typedef I f(); :].
for (DartType type in isChecks) {
functionArgumentCollector.collect(type);
void collectFunctionTypeArguments(Iterable<DartType> types) {
for (DartType type in types) {
functionArgumentCollector.collect(type);
}
}
collectFunctionTypeArguments(isChecks);
collectFunctionTypeArguments(checkedBounds);
for (DartType type in instantiatedTypes) {
directCollector.collect(type);
if (type.kind == TypeKind.INTERFACE) {
ClassElement cls = type.element;
for (DartType supertype in cls.allSupertypes) {
superCollector.collect(supertype);
void collectTypeArguments(Iterable<DartType> types,
{bool isTypeArgument: false}) {
for (DartType type in types) {
directCollector.collect(type, isTypeArgument: isTypeArgument);
if (type.kind == TypeKind.INTERFACE) {
ClassElement cls = type.element;
for (DartType supertype in cls.allSupertypes) {
superCollector.collect(supertype, isTypeArgument: isTypeArgument);
}
}
}
}
collectTypeArguments(instantiatedTypes);
collectTypeArguments(checkedTypeArguments, isTypeArgument: true);
for (ClassElement cls in superCollector.classes.toList()) {
for (DartType supertype in cls.allSupertypes) {
superCollector.collect(supertype);
@ -333,13 +355,22 @@ class RuntimeTypes {
// We need to add types occuring in function type arguments, like for
// instance 'J' for [: (J j) {} is f :] where f is
// [: typedef void f(I i); :] and 'J' is a subtype of 'I'.
for (DartType type in instantiatedTypes) {
functionArgumentCollector.collect(type);
void collectFunctionTypeArguments(Iterable<DartType> types) {
for (DartType type in types) {
functionArgumentCollector.collect(type);
}
}
collectFunctionTypeArguments(instantiatedTypes);
collectFunctionTypeArguments(checkedTypeArguments);
for (DartType type in isChecks) {
collector.collect(type);
void collectTypeArguments(Iterable<DartType> types,
{bool isTypeArgument: false}) {
for (DartType type in types) {
collector.collect(type, isTypeArgument: isTypeArgument);
}
}
collectTypeArguments(isChecks);
collectTypeArguments(checkedBounds, isTypeArgument: true);
checkedArguments =
collector.classes..addAll(functionArgumentCollector.classes);
@ -718,8 +749,8 @@ class ArgumentCollector extends DartTypeVisitor {
ArgumentCollector(this.backend);
collect(DartType type) {
type.accept(this, false);
collect(DartType type, {bool isTypeArgument: false}) {
type.accept(this, isTypeArgument);
}
/// Collect all types in the list as if they were arguments of an

View file

@ -235,9 +235,10 @@ class TypeTestEmitter extends CodeEmitterHelper {
Map<FunctionType, bool> getFunctionTypeChecksOn(DartType type) {
Map<FunctionType, bool> functionTypeMap = new Map<FunctionType, bool>();
for (FunctionType functionType in checkedFunctionTypes) {
if (compiler.types.isSubtype(type, functionType)) {
int maybeSubtype = compiler.types.computeSubtypeRelation(type, functionType);
if (maybeSubtype == Types.IS_SUBTYPE) {
functionTypeMap[functionType] = true;
} else if (compiler.types.isPotentialSubtype(type, functionType)) {
} else if (maybeSubtype == Types.MAYBE_SUBTYPE) {
functionTypeMap[functionType] = false;
}
}

View file

@ -1779,7 +1779,7 @@ class TypeResolver {
addTypeVariableBoundsCheck) {
visitor.addDeferredAction(
visitor.enclosingElement,
() => checkTypeVariableBounds(node, type));
() => checkTypeVariableBounds(visitor.mapping, node, type));
}
}
visitor.useType(node, type);
@ -1787,26 +1787,23 @@ class TypeResolver {
}
/// Checks the type arguments of [type] against the type variable bounds.
void checkTypeVariableBounds(TypeAnnotation node, GenericType type) {
TypeDeclarationElement element = type.element;
Link<DartType> typeArguments = type.typeArguments;
Link<DartType> typeVariables = element.typeVariables;
while (!typeVariables.isEmpty && !typeArguments.isEmpty) {
TypeVariableType typeVariable = typeVariables.head;
DartType bound = typeVariable.element.bound.subst(
type.typeArguments, element.typeVariables);
DartType typeArgument = typeArguments.head;
void checkTypeVariableBounds(TreeElements elements,
TypeAnnotation node, GenericType type) {
void checkTypeVariableBound(_, DartType typeArgument,
TypeVariableType typeVariable,
DartType bound) {
compiler.backend.registerTypeVariableBoundCheck(elements);
if (!compiler.types.isSubtype(typeArgument, bound)) {
compiler.reportWarningCode(node,
MessageKind.INVALID_TYPE_VARIABLE_BOUND,
{'typeVariable': typeVariable,
'bound': bound,
'typeArgument': typeArgument,
'thisType': element.thisType});
'thisType': type.element.thisType});
}
typeVariables = typeVariables.tail;
typeArguments = typeArguments.tail;
}
};
compiler.types.checkTypeVariableBounds(type, checkTypeVariableBound);
}
/**

View file

@ -1932,6 +1932,22 @@ class SsaBuilder extends ResolvedVisitor implements Visitor {
return other;
}
void assertIsSubtype(Node node, DartType subtype, DartType supertype,
String message) {
HInstruction subtypeInstruction = analyzeTypeArgument(subtype);
HInstruction supertypeInstruction = analyzeTypeArgument(supertype);
HInstruction messageInstruction =
graph.addConstantString(new DartString.literal(message),
node, compiler);
Element element = backend.getAssertIsSubtype();
var inputs = <HInstruction>[subtypeInstruction, supertypeInstruction,
messageInstruction];
HInstruction assertIsSubtype = new HInvokeStatic(
element, inputs, subtypeInstruction.instructionType);
compiler.backend.registerTypeVariableBoundsSubtypeCheck(subtype, supertype);
add(assertIsSubtype);
}
HGraph closeFunction() {
// TODO(kasperl): Make this goto an implicit return.
if (!isAborted()) closeAndGotoExit(new HGoto());
@ -3689,6 +3705,8 @@ class SsaBuilder extends ResolvedVisitor implements Visitor {
type = functionElement.computeTargetType(compiler, type);
}
if (checkTypeVariableBounds(node, type)) return;
var inputs = <HInstruction>[];
if (constructor.isGenerativeConstructor() &&
Elements.isNativeOrExtendsNative(constructor.getEnclosingClass())) {
@ -3746,6 +3764,62 @@ class SsaBuilder extends ResolvedVisitor implements Visitor {
}
}
/// In checked mode checks the [type] of [node] to be well-bounded. The method
/// returns [:true:] if an error can be statically determined.
bool checkTypeVariableBounds(NewExpression node, InterfaceType type) {
if (!compiler.enableTypeAssertions) return false;
Map<DartType, Set<DartType>> seenChecksMap =
new Map<DartType, Set<DartType>>();
bool definitelyFails = false;
addTypeVariableBoundCheck(GenericType instance,
DartType typeArgument,
TypeVariableType typeVariable,
DartType bound) {
if (definitelyFails) return;
int subtypeRelation = compiler.types.computeSubtypeRelation(typeArgument, bound);
if (subtypeRelation == Types.IS_SUBTYPE) return;
String message =
"Can't create an instance of malbounded type '$type': "
"'${typeArgument}' is not a subtype of bound '${bound}' for "
"type variable '${typeVariable}' of type "
"${type == instance
? "'${type.element.thisType}'"
: "'${instance.element.thisType}' on the supertype "
"'${instance}' of '${type}'"
}.";
if (subtypeRelation == Types.NOT_SUBTYPE) {
generateTypeError(node, message);
definitelyFails = true;
return;
} else if (subtypeRelation == Types.MAYBE_SUBTYPE) {
Set<DartType> seenChecks =
seenChecksMap.putIfAbsent(typeArgument, () => new Set<DartType>());
if (!seenChecks.contains(bound)) {
seenChecks.add(bound);
assertIsSubtype(node, typeArgument, bound, message);
}
}
}
compiler.types.checkTypeVariableBounds(type, addTypeVariableBoundCheck);
if (definitelyFails) {
return true;
}
for (InterfaceType supertype in type.element.allSupertypes) {
DartType instance = type.asInstanceOf(supertype.element);
compiler.types.checkTypeVariableBounds(instance,
addTypeVariableBoundCheck);
if (definitelyFails) {
return true;
}
}
return false;
}
visitAssert(node) {
if (!compiler.enableUserAssertions) {
stack.add(graph.addConstantNull(compiler));
@ -3860,6 +3934,10 @@ class SsaBuilder extends ResolvedVisitor implements Visitor {
generateError(node, message, backend.getThrowRuntimeError());
}
void generateTypeError(Node node, String message) {
generateError(node, message, backend.getThrowTypeError());
}
void generateAbstractClassInstantiationError(Node node, String message) {
generateError(node,
message,

View file

@ -1943,6 +1943,8 @@ class TypeErrorImplementation extends Error implements TypeError {
: message = "type '${Primitives.objectTypeName(value)}' is not a subtype "
"of type '$type'";
TypeErrorImplementation.fromMessage(String this.message);
String toString() => message;
}

View file

@ -286,6 +286,18 @@ Object assertSubtype(Object object, String isField, List checks,
return object;
}
/// Checks that the type represented by [subtype] is a subtype of [supertype].
/// If not a type error with [message] is thrown.
assertIsSubtype(var subtype, var supertype, String message) {
if (!isSubtype(subtype, supertype)) {
throwTypeError(message);
}
}
throwTypeError(message) {
throw new TypeErrorImplementation.fromMessage(message);
}
/**
* Check that the types in the list [arguments] are subtypes of the types in
* list [checks] (at the respective positions), possibly applying [substitution]

View file

@ -27,6 +27,9 @@ implicit_setter_test: Fail # Issue 13917
[ $compiler == none && $runtime == vm ]
class_keyword_test/02: MissingCompileTimeError # Issue 13627
[ $compiler == none && $checked ]
type_variable_bounds4_test/01: Fail # Issue 14006
[ $compiler == none && $unchecked ]
# Only checked mode reports an error on type assignment
# problems in compile time constants.

View file

@ -450,8 +450,6 @@ try_catch4_test: StaticWarning
try_catch5_test: StaticWarning
type_argument_in_super_type_test: StaticWarning
typed_selector2_test: StaticWarning
type_variable_bounds2_test/05: StaticWarning
type_variable_bounds3_test/00: StaticWarning
type_variable_identifier_expression_test: StaticWarning
type_variable_scope2_test: StaticWarning
unary_plus_negative_test: CompileTimeError

View file

@ -33,13 +33,9 @@ vm/*: Skip # Issue 12699
[ $compiler == dart2js && $checked ]
checked_setter2_test: Fail # Issue 11273
default_factory2_test/01: Fail # Issue 12700
type_variable_bounds_test/01: Fail # Issue 12702
type_variable_bounds_test/02: Fail # Issue 12702
type_variable_bounds_test/04: Fail # Issue 12702
type_variable_bounds_test/05: Fail # Issue 12702
type_variable_bounds2_test/00: Fail # Issue 12702
type_variable_bounds2_test/03: Fail # Issue 12702
type_variable_bounds2_test/05: Fail # Issue 12702
type_variable_bounds2_test/01: Fail # Issue 12702
type_variable_bounds2_test/04: Fail # Issue 12702
type_variable_bounds2_test/06: Pass # Issue 12702 (pass for the wrong reasons).
type_variable_bounds3_test/00: Fail # Issue 12702
f_bounded_quantification_test/01: Fail # Issue 12703

View file

@ -41,7 +41,7 @@ main() {
// However, while unsuccessfully trying to prove that A<String> is a K,
// a malformed type is encountered in checked mode, resulting in a dynamic
// type error.
Expect.isTrue(a is !K); /// 05: dynamic type error
Expect.isTrue(a is !K); /// 05: dynamic type error, static type warning
}
a = new A<int>();

View file

@ -9,7 +9,7 @@ class A<K extends int> {
class B<X, Y> {
foo(x) {
return x is A<X>; /// 00: dynamic type error
return x is A<X>; /// 00: dynamic type error, static type warning
}
}

View file

@ -0,0 +1,69 @@
// Copyright (c) 2013, 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.
// Test instantiation of object with malbounded types.
class A<T
extends num /// 01: static type warning
> {}
class B<T> implements A<T> {}
class C<T
extends num /// 01: continued
> implements B<T> {}
class Class<T> {
newA() {
new A<T>(); /// 01: continued
}
newB() {
new B<T>(); /// 01: continued
}
newC() {
new C<T>(); /// 01: continued
}
}
bool inCheckedMode() {
try {
var i = 42;
String s = i;
} on TypeError catch (e) {
return true;
}
return false;
}
void test(bool expectTypeError, f()) {
try {
var v = f();
if (expectTypeError && inCheckedMode()) {
throw 'Missing type error instantiating ${v.runtimeType}';
}
} on TypeError catch (e) {
if (!expectTypeError || !inCheckedMode()) {
throw 'Unexpected type error: $e';
}
}
}
void main() {
test(false, () => new A<int>());
test(false, () => new B<int>());
test(false, () => new C<int>());
test(true, () => new A<String>()); /// 01: continued
test(true, () => new B<String>()); /// 01: continued
test(true, () => new C<String>()); /// 01: continued
var c = new Class<int>();
test(false, () => c.newA());
test(false, () => c.newB());
test(false, () => c.newC());
c = new Class<String>();
test(true, () => c.newA()); /// 01: continued
test(true, () => c.newB()); /// 01: continued
test(true, () => c.newC()); /// 01: continued
}