Description
As a variant of the example shown in #29913, the following example illustrates that DDC does not generate all the needed checks in order to ensure heap soundness when we mix covariance (with a generic class) and contravariance (with a function type), using a call
method. The program is accepted by dartanalyzer --strong n018.dart
with no issues.
class A {}
class B extends A {}
class C extends B {}
typedef void F<T>(T t);
class ClassF<T> {
T x;
void call(T t) {
x = t;
}
}
main() {
ClassF<C> cc = new ClassF<C>();
ClassF<A> ca = cc; // An upcast, per covariance.
F<A> f = ca; // "No-op cast to function", but should fail in strong mode.
// Still running, though!
print(f.runtimeType); // 'ClassF<C>', cannot be considered an `F<A>`.
f(new A()); // Statically safe, but dynamically the arg should be a C.
}
When compiling and executing the program using dartdevc.dart from 5982ace, 2017-06-15 14:18:09, ddc n018.dart, the resulting output is as follows:
ClassF<C>
/usr/local/google/home/eernst/devel/dart/work/sdk/pkg/dev_compiler/lib/js/common/dart_sdk.js:9877
throw e;
^
Type 'A' is not a subtype of type 'C'
This differs from the situation in #29913 in that we do get the dynamic argument type check at the invocation of the call
method (presumably because that's a normal class-covariance check), but we still have an unsound heap for a while, because f
in main
refers to an instance of ClassF<C>
, and such an object cannot be considered to have type F<A>
as declared.