| Index: pkg/kernel/lib/transformations/insert_covariance_checks.dart | 
| diff --git a/pkg/kernel/lib/transformations/insert_covariance_checks.dart b/pkg/kernel/lib/transformations/insert_covariance_checks.dart | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..b4e1869830dbe3e57e28671fe510cd38a90f986e | 
| --- /dev/null | 
| +++ b/pkg/kernel/lib/transformations/insert_covariance_checks.dart | 
| @@ -0,0 +1,516 @@ | 
| +// 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.transformations.insert_covariance_checks; | 
| + | 
| +import '../class_hierarchy.dart'; | 
| +import '../clone.dart'; | 
| +import '../core_types.dart'; | 
| +import '../kernel.dart'; | 
| +import '../log.dart'; | 
| +import '../type_algebra.dart'; | 
| +import '../type_environment.dart'; | 
| + | 
| +// TODO: Should helper be removed? | 
| +DartType substituteBounds(DartType type, Map<TypeParameter, DartType> upper, | 
| +    Map<TypeParameter, DartType> lower) { | 
| +  return Substitution | 
| +      .fromUpperAndLowerBounds(upper, lower) | 
| +      .substituteType(type); | 
| +} | 
| + | 
| +/// Inserts checked entry points for methods in order to enforce type safety | 
| +/// in face on covariant subtyping. | 
| +/// | 
| +/// An 'unsafe parameter' is a parameter whose type mentions a class type | 
| +/// parameter T, but is not contravariant in T.  For instance, the argument | 
| +/// to `List.add` is unsafe, whereas the function parameter to `List.forEach` | 
| +/// is safe: | 
| +/// | 
| +///     class List<T> { | 
| +///       ... | 
| +///       void add(T x) {...} // unsafe | 
| +///       void forEach(void action(T x)) {...} // safe | 
| +///     } | 
| +/// | 
| +/// For every method with unsafe parameters, a checked entry point suffixed | 
| +/// with `$cc` is inserted, which casts the unsafe parameters to their expected | 
| +/// types and calls the actual implementation: | 
| +/// | 
| +///     class List<T> { | 
| +///       ... | 
| +///       void add$cc(Object x) => this.add(x as T); | 
| +///     } | 
| +/// | 
| +/// Calls whose interface target declares unsafe parameters are then rewritten | 
| +/// to target the `$cc` entry point instead, unless it can be determined that | 
| +/// the type argument is exact.  For example: | 
| +/// | 
| +///     void foo(List<num> numbers) { | 
| +///       numbers.add(3.5); // before | 
| +///       numbers.add$cc(3.5); // after | 
| +///     } | 
| +/// | 
| +/// Currently, we only deduce that the type arguments are exact when the | 
| +/// receiver is `this`. | 
| +class InsertCovarianceChecks { | 
| +  ClassHierarchy hierarchy; | 
| +  CoreTypes coreTypes; | 
| +  TypeEnvironment types; | 
| + | 
| +  /// Maps unsafe members to their checked entry point, to be used at call sites | 
| +  /// where the arguments cannot be guaranteed to satisfy the generic parameter | 
| +  /// types of the actual target. | 
| +  final Map<Member, Procedure> unsafeMemberEntryPoint = <Member, Procedure>{}; | 
| + | 
| +  /// Members that may be invoked through a checked entry point. | 
| +  /// | 
| +  /// Note that these members are not necessarily unsafe, because a safe member | 
| +  /// can override an unsafe member, and thereby be invoked through a checked | 
| +  /// entry point.  This set is not therefore not the same as the set of keys | 
| +  /// in [unsafeMemberEntryPoint]. | 
| +  final Set<Member> membersWithCheckedEntryPoint = new Set<Member>(); | 
| + | 
| +  InsertCovarianceChecks({this.hierarchy, this.coreTypes}); | 
| + | 
| +  void transformProgram(Program program) { | 
| +    hierarchy ??= new ClassHierarchy(program); | 
| +    coreTypes ??= new CoreTypes(program); | 
| +    types = new TypeEnvironment(coreTypes, hierarchy); | 
| +    // We transform every class before their subtypes. | 
| +    // This ensures that transitive overrides are taken into account. | 
| +    hierarchy.classes.forEach(transformClass); | 
| + | 
| +    program.accept(new _CallTransformer(this)); | 
| +  } | 
| + | 
| +  void transformClass(Class class_) { | 
| +    new _ClassTransformer(class_, this).transformClass(); | 
| +  } | 
| +} | 
| + | 
| +class _ClassTransformer { | 
| +  final Class host; | 
| +  final ClassHierarchy hierarchy; | 
| +  final TypeEnvironment types; | 
| +  final InsertCovarianceChecks global; | 
| + | 
| +  final Map<Field, VariableDeclaration> fieldSetterParameter = | 
| +      <Field, VariableDeclaration>{}; | 
| + | 
| +  final Map<VariableDeclaration, List<DartType>> unsafeParameterTypes = | 
| +      new Map<VariableDeclaration, List<DartType>>(); | 
| + | 
| +  // The following four maps translate types from the context of a supertype | 
| +  // into the context of the current class. | 
| +  // | 
| +  // When analyzing an override relation "ownMember <: superMember", the two | 
| +  // "own" maps translate types from the context of the ownMember, while the | 
| +  // "super" maps translate types from the context of superMember. | 
| +  // | 
| +  // The "substitution" maps translate type parameters to their exact type, | 
| +  // while the "upper bound" maps translate type parameters to their erased | 
| +  // upper bounds. | 
| +  Map<TypeParameter, DartType> ownSubstitution; | 
| +  Map<TypeParameter, DartType> ownUpperBounds; | 
| +  Map<TypeParameter, DartType> superSubstitution; | 
| +  Map<TypeParameter, DartType> superUpperBounds; | 
| + | 
| +  /// Members for which a checked entry point must be created in this current | 
| +  /// class. | 
| +  Set<Member> membersNeedingCheckedEntryPoint = new Set<Member>(); | 
| + | 
| +  _ClassTransformer(this.host, InsertCovarianceChecks global) | 
| +      : hierarchy = global.hierarchy, | 
| +        types = global.types, | 
| +        this.global = global; | 
| + | 
| +  /// Mark [parameter] unsafe, with [type] as a potential argument type. | 
| +  void addUnsafeParameter( | 
| +      VariableDeclaration parameter, DartType type, Member member) { | 
| +    unsafeParameterTypes.putIfAbsent(parameter, () => <DartType>[]).add(type); | 
| +    requireLocalCheckedEntryPoint(member); | 
| +  } | 
| + | 
| +  /// Get a parameter representing the argument to the implicit setter | 
| +  /// for [field]. | 
| +  VariableDeclaration getFieldSetterParameter(Field field) { | 
| +    return fieldSetterParameter.putIfAbsent(field, () { | 
| +      return new VariableDeclaration('${field.name.name}_', type: field.type); | 
| +    }); | 
| +  } | 
| + | 
| +  /// Mark [field] as unsafe, with [type] as a potential argument to its setter. | 
| +  void addUnsafeField(Field field, DartType type) { | 
| +    addUnsafeParameter(getFieldSetterParameter(field), type, field); | 
| +  } | 
| + | 
| +  /// True if [member] can be invoked through a checked entry point. | 
| +  /// | 
| +  /// This does not imply that the member has unsafe parameters. | 
| +  bool hasCheckedEntryPoint(Member member, {bool setter: false}) { | 
| +    if (!setter && member is Field) { | 
| +      return false; // Field getters never have checked entry points. | 
| +    } | 
| +    return global.membersWithCheckedEntryPoint.contains(member); | 
| +  } | 
| + | 
| +  /// Ensures that a checked entry point for [member] will be emitted in the | 
| +  /// current class. | 
| +  void requireLocalCheckedEntryPoint(Member member) { | 
| +    if (membersNeedingCheckedEntryPoint.add(member)) { | 
| +      global.membersWithCheckedEntryPoint.add(member); | 
| +    } | 
| +  } | 
| + | 
| +  void transformClass() { | 
| +    if (host.isMixinApplication) { | 
| +      // TODO(asgerf): We need a way to support mixin applications with unsafe | 
| +      //   overrides. This version assumes mixins have been resolved by cloning. | 
| +      //   We could generate a subclass of the mixin application containing the | 
| +      //   checked entry points. | 
| +      throw 'Mixin applications must be resolved before inserting covariance ' | 
| +          'checks'; | 
| +    } | 
| +    // Find parameters with an unsafe reference to a class type parameter. | 
| +    if (host.typeParameters.isNotEmpty) { | 
| +      var upperBounds = getUpperBoundSubstitutionMap(host); | 
| +      for (var field in host.fields) { | 
| +        if (field.hasImplicitSetter) { | 
| +          var rawType = substituteBounds(field.type, upperBounds, {}); | 
| +          if (!identical(rawType, field.type)) { | 
| +            requireLocalCheckedEntryPoint(field); | 
| +            addUnsafeField(field, rawType); | 
| +          } | 
| +        } | 
| +      } | 
| +      for (var procedure in host.procedures) { | 
| +        if (procedure.isStatic) continue; | 
| +        void handleParameter(VariableDeclaration parameter) { | 
| +          var rawType = substituteBounds(parameter.type, upperBounds, {}); | 
| +          if (!identical(rawType, parameter.type)) { | 
| +            requireLocalCheckedEntryPoint(procedure); | 
| +            addUnsafeParameter(parameter, rawType, procedure); | 
| +          } | 
| +        } | 
| + | 
| +        procedure.function.positionalParameters.forEach(handleParameter); | 
| +        procedure.function.namedParameters.forEach(handleParameter); | 
| +      } | 
| +    } | 
| + | 
| +    // Find (possibly inherited) members that override a method that has | 
| +    // unsafe parameters. | 
| +    hierarchy.forEachOverridePair(host, | 
| +        (Member ownMember, Member superMember, bool isSetter) { | 
| +      if (hasCheckedEntryPoint(superMember, setter: isSetter)) { | 
| +        requireLocalCheckedEntryPoint(ownMember); | 
| +      } | 
| +      if (superMember.enclosingClass.typeParameters.isEmpty) return; | 
| +      ownSubstitution = getSubstitutionMap( | 
| +          hierarchy.getClassAsInstanceOf(host, ownMember.enclosingClass)); | 
| +      ownUpperBounds = getUpperBoundSubstitutionMap(ownMember.enclosingClass); | 
| +      superSubstitution = getSubstitutionMap( | 
| +          hierarchy.getClassAsInstanceOf(host, superMember.enclosingClass)); | 
| +      superUpperBounds = | 
| +          getUpperBoundSubstitutionMap(superMember.enclosingClass); | 
| +      if (ownMember is Procedure) { | 
| +        if (superMember is Procedure) { | 
| +          checkProcedureOverride(ownMember, superMember); | 
| +        } else if (superMember is Field && isSetter) { | 
| +          checkSetterFieldOverride(ownMember, superMember); | 
| +        } | 
| +      } else if (isSetter) { | 
| +        checkFieldOverride(ownMember, superMember); | 
| +      } | 
| +    }); | 
| + | 
| +    for (Member member in membersNeedingCheckedEntryPoint) { | 
| +      ownSubstitution = getSubstitutionMap( | 
| +          hierarchy.getClassAsInstanceOf(host, member.enclosingClass)); | 
| +      ownSubstitution = ensureMutable(ownSubstitution); | 
| +      generateCheckedEntryPoint(member); | 
| +    } | 
| +  } | 
| + | 
| +  /// Compute an upper bound of the types in [inputTypes]. | 
| +  /// | 
| +  /// We use this to compute a trustworthy type for a parameter, given a list | 
| +  /// of types that may actually be passed into the parameter. | 
| +  DartType getSafeType(List<DartType> inputTypes) { | 
| +    var safeType = inputTypes[0]; | 
| +    for (int i = 1; i < inputTypes.length; ++i) { | 
| +      if (inputTypes[i] != safeType) { | 
| +        // Multiple types are being overridden. Fall back to dynamic. | 
| +        // There are cases where a better upper bound could be found, but they | 
| +        // are quite rare. | 
| +        return const DynamicType(); | 
| +      } | 
| +    } | 
| +    return safeType; | 
| +  } | 
| + | 
| +  void fail(String message) { | 
| +    log.warning('[unsoundness] $message'); | 
| +  } | 
| + | 
| +  void checkFieldOverride(Field field, Member superMember) { | 
| +    var fieldType = | 
| +        substituteBounds(field.type, ownUpperBounds, ownSubstitution); | 
| +    var superType = substituteBounds( | 
| +        superMember.setterType, superUpperBounds, superSubstitution); | 
| +    if (!types.isSubtypeOf(superType, fieldType)) { | 
| +      addUnsafeField(field, superType); | 
| +    } | 
| +  } | 
| + | 
| +  void checkSetterFieldOverride(Procedure ownMember, Field superMember) { | 
| +    assert(ownMember.isSetter); | 
| +    var ownParameter = ownMember.function.positionalParameters[0]; | 
| +    var ownType = | 
| +        substituteBounds(ownParameter.type, ownUpperBounds, ownSubstitution); | 
| +    var superType = substituteBounds( | 
| +        superMember.setterType, superUpperBounds, superSubstitution); | 
| +    if (!types.isSubtypeOf(superType, ownType)) { | 
| +      addUnsafeParameter(ownParameter, superType, ownMember); | 
| +    } | 
| +  } | 
| + | 
| +  void checkProcedureOverride(Procedure ownMember, Procedure superMember) { | 
| +    var ownFunction = ownMember.function; | 
| +    var superFunction = superMember.function; | 
| +    // We perform some checks here to avoid crashing, but the frontend is | 
| +    // responsible for generating IR that does not violate these restrictions. | 
| +    if (ownFunction.requiredParameterCount > | 
| +        superFunction.requiredParameterCount) { | 
| +      fail('$ownMember requires more parameters than $superMember'); | 
| +      return; | 
| +    } | 
| +    if (ownFunction.positionalParameters.length < | 
| +        superFunction.positionalParameters.length) { | 
| +      fail('$ownMember allows fewer parameters than $superMember'); | 
| +      return; | 
| +    } | 
| +    if (ownFunction.typeParameters.length != | 
| +        superFunction.typeParameters.length) { | 
| +      fail('$ownMember declares a different number of type parameters ' | 
| +          'than $superMember'); | 
| +      return; | 
| +    } | 
| +    if (superFunction.typeParameters.isNotEmpty) { | 
| +      // Ensure these maps are not constant, so we can add bindings for the | 
| +      // function type parameters. | 
| +      superSubstitution = ensureMutable(superSubstitution); | 
| +      superUpperBounds = ensureMutable(superUpperBounds); | 
| +    } | 
| +    for (int i = 0; i < superFunction.typeParameters.length; ++i) { | 
| +      var ownTypeParameter = ownFunction.typeParameters[i]; | 
| +      var superTypeParameter = superFunction.typeParameters[i]; | 
| +      var type = new TypeParameterType(ownTypeParameter); | 
| +      superSubstitution[superTypeParameter] = type; | 
| +      superUpperBounds[superTypeParameter] = type; | 
| +    } | 
| +    void checkParameterPair( | 
| +        VariableDeclaration ownParameter, VariableDeclaration superParameter) { | 
| +      var ownType = substitute(ownParameter.type, ownSubstitution); | 
| +      var superType = substituteBounds( | 
| +          superParameter.type, superUpperBounds, superSubstitution); | 
| +      if (!types.isSubtypeOf(superType, ownType)) { | 
| +        addUnsafeParameter(ownParameter, superType, ownMember); | 
| +      } | 
| +    } | 
| + | 
| +    for (int i = 0; i < superFunction.positionalParameters.length; ++i) { | 
| +      checkParameterPair(ownFunction.positionalParameters[i], | 
| +          superFunction.positionalParameters[i]); | 
| +    } | 
| +    for (int i = 0; i < superFunction.namedParameters.length; ++i) { | 
| +      var superParameter = superFunction.namedParameters[i]; | 
| +      bool found = false; | 
| +      for (int j = 0; j < ownFunction.namedParameters.length; ++j) { | 
| +        var ownParameter = ownFunction.namedParameters[j]; | 
| +        if (ownParameter.name == superParameter.name) { | 
| +          found = true; | 
| +          checkParameterPair(ownParameter, superParameter); | 
| +          break; | 
| +        } | 
| +      } | 
| +      if (!found) { | 
| +        fail('$ownMember is missing the named parameter ' | 
| +            '${superParameter.name} from $superMember'); | 
| +      } | 
| +    } | 
| +  } | 
| + | 
| +  void generateCheckedEntryPoint(Member member) { | 
| +    // TODO(asgerf): It may be worthwhile to try to reuse a checked entry | 
| +    //   point from the supertype when the same checks are needed and the | 
| +    //   dispatch target is the same. | 
| +    if (member is Procedure) { | 
| +      generateCheckedProcedure(member); | 
| +    } else { | 
| +      generateCheckedFieldSetter(member); | 
| +    } | 
| +  } | 
| + | 
| +  void generateCheckedProcedure(Procedure procedure) { | 
| +    var function = procedure.function; | 
| + | 
| +    // Clone the function without its body. | 
| +    var body = function.body; | 
| +    function.body = null; | 
| +    var cloner = new CloneVisitor(typeSubstitution: ownSubstitution); | 
| +    Procedure checkedProcedure = cloner.clone(procedure); | 
| +    FunctionNode checkedFunction = checkedProcedure.function; | 
| +    function.body = body; | 
| + | 
| +    checkedFunction.asyncMarker = AsyncMarker.Sync; | 
| +    checkedProcedure.isExternal = false; | 
| + | 
| +    Expression getParameter(VariableDeclaration parameter) { | 
| +      var cloneParameter = cloner.variables[parameter]; | 
| +      var unsafeInputs = unsafeParameterTypes[parameter]; | 
| +      if (unsafeInputs == null) { | 
| +        return new VariableGet(cloneParameter); // No check needed. | 
| +      } | 
| +      // Change the actual parameter type to the safe type, and cast to the | 
| +      // type declared on the original parameter. | 
| +      // Use the cloner to map function type parameters to the cloned | 
| +      // function type parameters (in case the function is generic). | 
| +      var targetType = cloneParameter.type; | 
| +      cloneParameter.type = cloner.visitType(getSafeType(unsafeInputs)); | 
| +      return new AsExpression(new VariableGet(cloneParameter), targetType); | 
| +    } | 
| + | 
| +    // TODO: Insert checks for type parameter bounds. | 
| +    var types = checkedFunction.typeParameters | 
| +        .map((p) => new TypeParameterType(p)) | 
| +        .toList(); | 
| +    var positional = function.positionalParameters.map(getParameter).toList(); | 
| +    var named = function.namedParameters | 
| +        .map((p) => new NamedExpression(p.name, getParameter(p))) | 
| +        .toList(); | 
| + | 
| +    checkedProcedure.name = covariantCheckedName(procedure.name); | 
| +    host.addMember(checkedProcedure); | 
| + | 
| +    // Only generate a body if the original method had one. | 
| +    if (!procedure.isAbstract && !procedure.isInExternalLibrary) { | 
| +      var call = procedure.isSetter | 
| +          ? new DirectPropertySet( | 
| +              new ThisExpression(), procedure, positional[0]) | 
| +          : new DirectMethodInvocation(new ThisExpression(), procedure, | 
| +              new Arguments(positional, named: named, types: types)); | 
| +      var checkedBody = function.returnType is VoidType | 
| +          ? new ExpressionStatement(call) | 
| +          : new ReturnStatement(call); | 
| +      checkedFunction.body = checkedBody..parent = checkedFunction; | 
| +    } | 
| + | 
| +    if (procedure.enclosingClass == host) { | 
| +      global.unsafeMemberEntryPoint[procedure] = checkedProcedure; | 
| +    } | 
| +  } | 
| + | 
| +  void generateCheckedFieldSetter(Field field) { | 
| +    var parameter = getFieldSetterParameter(field); | 
| +    var unsafeTypes = unsafeParameterTypes[parameter]; | 
| +    Expression argument = new VariableGet(parameter); | 
| +    if (unsafeTypes != null) { | 
| +      var castType = substitute(field.type, ownSubstitution); | 
| +      argument = new AsExpression(argument, castType); | 
| +      var inputType = substitute(getSafeType(unsafeTypes), ownSubstitution); | 
| +      parameter.type = inputType; | 
| +    } | 
| + | 
| +    Statement body = field.isInExternalLibrary | 
| +        ? null | 
| +        : new ExpressionStatement( | 
| +            new DirectPropertySet(new ThisExpression(), field, argument)); | 
| + | 
| +    var setter = new Procedure( | 
| +        covariantCheckedName(field.name), | 
| +        ProcedureKind.Setter, | 
| +        new FunctionNode(body, positionalParameters: [parameter])); | 
| +    host.addMember(setter); | 
| + | 
| +    if (field.enclosingClass == host) { | 
| +      global.unsafeMemberEntryPoint[field] = setter; | 
| +    } | 
| +  } | 
| + | 
| +  /// Generates a synthetic name representing the covariant-checked entry point | 
| +  /// to a method. | 
| +  static Name covariantCheckedName(Name name) { | 
| +    return new Name('${name.name}\$cc', name.library); | 
| +  } | 
| + | 
| +  static Map<TypeParameter, DartType> ensureMutable( | 
| +      Map<TypeParameter, DartType> map) { | 
| +    if (map.isEmpty) return <TypeParameter, DartType>{}; | 
| +    return map; | 
| +  } | 
| +} | 
| + | 
| +// TODO(asgerf): We should be able to avoid checked calls in a lot more cases: | 
| +//  - the arguments to every unsafe parameter is null or is omitted | 
| +//  - allocation site of receiver can easily be seen statically | 
| +class _CallTransformer extends RecursiveVisitor { | 
| +  final InsertCovarianceChecks global; | 
| +  final TypeEnvironment types; | 
| +  final Map<Member, Procedure> checkedInterfaceMethod; | 
| + | 
| +  _CallTransformer(InsertCovarianceChecks global) | 
| +      : checkedInterfaceMethod = global.unsafeMemberEntryPoint, | 
| +        types = global.types, | 
| +        this.global = global; | 
| + | 
| +  Member getChecked(Expression receiver, Member member) { | 
| +    var checked = checkedInterfaceMethod[member]; | 
| +    if (checked == null) return member; | 
| +    if (!receiverNeedsChecks(receiver)) return member; | 
| +    return checked; | 
| +  } | 
| + | 
| +  bool receiverNeedsChecks(Expression node) { | 
| +    if (node is ThisExpression) return false; | 
| +    var type = node.getStaticType(types); | 
| +    if (type is InterfaceType && type.typeArguments.every(isSealedType)) { | 
| +      return false; | 
| +    } | 
| +    return true; | 
| +  } | 
| + | 
| +  bool isSealedType(DartType type) { | 
| +    return type is InterfaceType && types.isSealedClass(type.classNode); | 
| +  } | 
| + | 
| +  bool isTrustedLibrary(Library node) { | 
| +    return node.importUri.scheme == 'dart'; | 
| +  } | 
| + | 
| +  @override | 
| +  visitClass(Class node) { | 
| +    types.thisType = node.thisType; | 
| +    node.visitChildren(this); | 
| +  } | 
| + | 
| +  @override | 
| +  visitLibrary(Library node) { | 
| +    if (!isTrustedLibrary(node)) { | 
| +      node.visitChildren(this); | 
| +    } | 
| +  } | 
| + | 
| +  @override | 
| +  visitMethodInvocation(MethodInvocation node) { | 
| +    node.interfaceTarget = getChecked(node.receiver, node.interfaceTarget); | 
| +    node.visitChildren(this); | 
| +  } | 
| + | 
| +  @override | 
| +  visitPropertySet(PropertySet node) { | 
| +    node.interfaceTarget = getChecked(node.receiver, node.interfaceTarget); | 
| +    node.visitChildren(this); | 
| +  } | 
| +} | 
|  |