| // 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.type_propagation.builder; |
| |
| import '../ast.dart'; |
| import '../class_hierarchy.dart'; |
| import '../core_types.dart'; |
| import 'canonicalizer.dart'; |
| import 'constraints.dart'; |
| import 'type_propagation.dart'; |
| import 'visualizer.dart'; |
| |
| /// Maps AST nodes to constraint variables at the level of function boundaries. |
| /// |
| /// Bindings internally in a function are only preserved by the [Visualizer]. |
| class VariableMapping { |
| /// Variable holding all values that may flow into the given field. |
| final Map<Field, int> fields = <Field, int>{}; |
| |
| /// Variable holding all values that may be returned from the given function. |
| final Map<FunctionNode, int> returns = <FunctionNode, int>{}; |
| |
| /// Variable holding all values that may be passed into the given function |
| /// parameter (possibly through a default parameter value). |
| final Map<VariableDeclaration, int> parameters = <VariableDeclaration, int>{}; |
| |
| /// Variable holding the function object for the given function. |
| final Map<FunctionNode, int> functions = <FunctionNode, int>{}; |
| |
| static VariableMapping make(int _) => new VariableMapping(); |
| } |
| |
| /// Maps AST nodes to the lattice employed by the constraint system. |
| class LatticeMapping { |
| /// Lattice point containing the torn-off functions originating from an |
| /// instance procedure that overrides the given procedure. |
| final Map<Procedure, int> functionsOverridingMethod = <Procedure, int>{}; |
| |
| /// Lattice point containing all torn-off functions originating from an |
| /// instance procedure of the given name, |
| /// |
| /// This ensures that calls to a method with unknown receiver may still |
| /// recover some information about the callee based on the name alone. |
| final Map<Name, int> functionsWithName = <Name, int>{}; |
| |
| /// Maps a class index to a lattice point containing all values that are |
| /// subtypes of that class. |
| final List<int> subtypesOfClass; |
| |
| /// Maps a class index to a lattice point containing all values that are |
| /// subclasses of that class. |
| final List<int> subclassesOfClass; |
| |
| LatticeMapping(int numberOfClasses) |
| : subtypesOfClass = new List<int>(numberOfClasses), |
| subclassesOfClass = new List<int>(numberOfClasses); |
| } |
| |
| /// Generates a [ConstraintSystem] to be solved by [Solver]. |
| class Builder { |
| final Program program; |
| final ClassHierarchy hierarchy; |
| final CoreTypes coreTypes; |
| final ConstraintSystem constraints; |
| final FieldNames fieldNames; |
| final Visualizer visualizer; |
| |
| final LatticeMapping lattice; |
| |
| /// Bindings for all members. The values inferred for these variables is the |
| /// output of the analysis. |
| /// |
| /// For static members, these are the canonical variables representing the |
| /// member. |
| /// |
| /// For instance members, these are the context-insensitive joins over all |
| /// the specialized copies of the instance member. |
| final VariableMapping global = new VariableMapping(); |
| |
| /// Maps a class index to the bindings for instance members specific to that |
| /// class as the host class. |
| final List<VariableMapping> classMapping; |
| |
| final Map<TypeParameter, int> functionTypeParameters = <TypeParameter, int>{}; |
| |
| /// Variable holding the result of the declaration-site field initializer |
| /// for the given field. |
| final Map<Field, int> declarationSiteFieldInitializer = <Field, int>{}; |
| |
| /// Maps a class index to the result of [getInterfaceEscapeVariable]. |
| final List<int> interfaceEscapeVariables; |
| |
| /// Maps a class index to the result of [getExternalInstanceVariable]. |
| final List<int> externalClassVariables; |
| final List<int> externalClassValues; |
| |
| final List<int> externalClassWorklist = <int>[]; |
| |
| final Uint31PairMap<int> _stores = new Uint31PairMap<int>(); |
| final Uint31PairMap<int> _loads = new Uint31PairMap<int>(); |
| final List<InferredValue> _baseTypeOfLatticePoint = <InferredValue>[]; |
| |
| int bottomNode; |
| int dynamicNode; |
| int boolNode; |
| int intNode; |
| int doubleNode; |
| int stringNode; |
| int symbolNode; |
| int typeNode; |
| int listNode; |
| int mapNode; |
| int nullNode; |
| int iterableNode; |
| int futureNode; |
| int streamNode; |
| int functionValueNode; |
| |
| int iteratorField; |
| int currentField; |
| |
| /// Lattice point containing all function values. |
| int latticePointForAllFunctions; |
| |
| Member identicalFunction; |
| |
| bool verbose; |
| |
| Builder(Program program, |
| {ClassHierarchy hierarchy, |
| FieldNames names, |
| CoreTypes coreTypes, |
| Visualizer visualizer, |
| bool verbose: false}) |
| : this._internal( |
| program, |
| hierarchy ?? new ClassHierarchy(program), |
| names ?? new FieldNames(), |
| coreTypes ?? new CoreTypes(program), |
| visualizer, |
| verbose); |
| |
| Builder._internal(this.program, ClassHierarchy hierarchy, FieldNames names, |
| this.coreTypes, Visualizer visualizer, this.verbose) |
| : this.hierarchy = hierarchy, |
| this.fieldNames = names, |
| this.visualizer = visualizer, |
| this.constraints = new ConstraintSystem(), |
| this.classMapping = new List<VariableMapping>.generate( |
| hierarchy.classes.length, VariableMapping.make), |
| this.interfaceEscapeVariables = new List<int>(hierarchy.classes.length), |
| this.externalClassVariables = new List<int>(hierarchy.classes.length), |
| this.externalClassValues = new List<int>(hierarchy.classes.length), |
| this.lattice = new LatticeMapping(hierarchy.classes.length) { |
| if (visualizer != null) { |
| visualizer.builder = this; |
| visualizer.constraints = constraints; |
| visualizer.fieldNames = fieldNames; |
| } |
| |
| // Build the subtype lattice points. |
| // The order in which lattice points are created determines how ambiguous |
| // upper bounds are resolved. The lattice point with highest index among |
| // the potential upper bounds is the result of a join. |
| // We create all the subtype lattice point before all the subclass lattice |
| // points, to ensure that subclass information takes precedence over |
| // subtype information. |
| for (int i = 0; i < hierarchy.classes.length; ++i) { |
| Class class_ = hierarchy.classes[i]; |
| List<int> supers = <int>[]; |
| if (class_.supertype != null) { |
| supers.add(getLatticePointForSubtypesOfClass(class_.superclass)); |
| } |
| if (class_.mixedInType != null) { |
| supers.add(getLatticePointForSubtypesOfClass(class_.mixedInClass)); |
| } |
| for (Supertype supertype in class_.implementedTypes) { |
| supers.add(getLatticePointForSubtypesOfClass(supertype.classNode)); |
| } |
| int subtypePoint = newLatticePoint(supers, class_, |
| i == 0 ? BaseClassKind.Subclass : BaseClassKind.Subtype); |
| lattice.subtypesOfClass[i] = subtypePoint; |
| visualizer?.annotateLatticePoint(subtypePoint, class_, 'subtype'); |
| } |
| |
| // Build the lattice points for subclasses and exact classes. |
| for (int i = 0; i < hierarchy.classes.length; ++i) { |
| Class class_ = hierarchy.classes[i]; |
| int subtypePoint = lattice.subtypesOfClass[i]; |
| assert(subtypePoint != null); |
| int subclassPoint; |
| if (class_.supertype == null) { |
| subclassPoint = subtypePoint; |
| } else { |
| subclassPoint = newLatticePoint(<int>[ |
| getLatticePointForSubclassesOf(class_.superclass), |
| subtypePoint |
| ], class_, BaseClassKind.Subclass); |
| } |
| lattice.subclassesOfClass[i] = subclassPoint; |
| int concretePoint = |
| newLatticePoint(<int>[subclassPoint], class_, BaseClassKind.Exact); |
| int value = constraints.newValue(concretePoint); |
| int variable = constraints.newVariable(); |
| // We construct the constraint system so the first N variables and values |
| // correspond to the N classes in the program. |
| assert(variable == i); |
| assert(value == -i); |
| visualizer?.annotateLatticePoint(subclassPoint, class_, 'subclass'); |
| visualizer?.annotateLatticePoint(concretePoint, class_, 'concrete'); |
| visualizer?.annotateVariable(variable, class_); |
| visualizer?.annotateValue(value, class_); |
| addInput(value, ValueBit.other, variable); |
| } |
| |
| bottomNode = newVariable(null, 'bottom'); |
| dynamicNode = getExternalInstanceVariable(coreTypes.objectClass); |
| boolNode = getExternalInstanceVariable(coreTypes.boolClass); |
| intNode = getExternalInstanceVariable(coreTypes.intClass); |
| doubleNode = getExternalInstanceVariable(coreTypes.doubleClass); |
| stringNode = getExternalInstanceVariable(coreTypes.stringClass); |
| symbolNode = getExternalInstanceVariable(coreTypes.symbolClass); |
| typeNode = getExternalInstanceVariable(coreTypes.typeClass); |
| listNode = getExternalInstanceVariable(coreTypes.listClass); |
| mapNode = getExternalInstanceVariable(coreTypes.mapClass); |
| iterableNode = getExternalInstanceVariable(coreTypes.iterableClass); |
| futureNode = getExternalInstanceVariable(coreTypes.futureClass); |
| streamNode = getExternalInstanceVariable(coreTypes.streamClass); |
| functionValueNode = getExternalInstanceVariable(coreTypes.functionClass); |
| nullNode = newVariable(null, 'Null'); |
| |
| iteratorField = getPropertyField(Names.iterator); |
| currentField = getPropertyField(Names.current); |
| |
| latticePointForAllFunctions = |
| getLatticePointForSubtypesOfClass(coreTypes.functionClass); |
| |
| identicalFunction = coreTypes.getCoreProcedure('dart:core', 'identical'); |
| |
| // Seed bitmasks for built-in values. |
| constraints.addBitmaskInput(ValueBit.null_, nullNode); |
| constraints.addBitmaskInput(ValueBit.all, dynamicNode); |
| |
| for (Library library in program.libraries) { |
| for (Procedure procedure in library.procedures) { |
| buildProcedure(null, procedure); |
| } |
| for (Field field in library.fields) { |
| buildStaticField(field); |
| } |
| for (Class class_ in library.classes) { |
| for (Procedure procedure in class_.procedures) { |
| if (procedure.isStatic) { |
| buildProcedure(null, procedure); |
| } |
| } |
| for (Field field in class_.fields) { |
| if (field.isStatic) { |
| buildStaticField(field); |
| } |
| } |
| if (!class_.isAbstract) { |
| buildInstanceValue(class_); |
| } |
| } |
| } |
| |
| // We don't track the values flowing into the identical function, as it |
| // causes a lot of spurious escape. Every class that inherits Object.== |
| // would escape its 'this' value into a dynamic context. |
| // Mark the identical() parameters as 'dynamic' so the output is sound. |
| for (int i = 0; i < 2; ++i) { |
| constraints.addAssign( |
| dynamicNode, |
| getSharedParameterVariable( |
| identicalFunction.function.positionalParameters[i])); |
| } |
| |
| // Build constraints mocking the external interfaces. |
| while (externalClassWorklist.isNotEmpty) { |
| int classIndex = externalClassWorklist.removeLast(); |
| _buildExternalClassValue(classIndex); |
| } |
| } |
| |
| int newLatticePoint( |
| List<int> parentLatticePoints, Class baseClass, BaseClassKind kind) { |
| _baseTypeOfLatticePoint.add(new InferredValue(baseClass, kind, 0)); |
| return constraints.newLatticePoint(parentLatticePoints); |
| } |
| |
| void addInput(int value, int bitmask, int destination) { |
| constraints.addAllocation(value, destination); |
| constraints.addBitmaskInput(bitmask, destination); |
| } |
| |
| /// Returns an [InferredValue] with the base type relation for the given |
| /// lattice point but whose bitmask is 0. The bitmask must be filled in |
| /// before this value is exposed to analysis clients. |
| InferredValue getBaseTypeOfLatticePoint(int latticePoint) { |
| return _baseTypeOfLatticePoint[latticePoint]; |
| } |
| |
| /// Returns the lattice point containing all subtypes of the given class. |
| int getLatticePointForSubtypesOfClass(Class classNode) { |
| int index = hierarchy.getClassIndex(classNode); |
| return lattice.subtypesOfClass[index]; |
| } |
| |
| /// Returns the lattice point containing all subclasses of the given class. |
| int getLatticePointForSubclassesOf(Class classNode) { |
| int index = hierarchy.getClassIndex(classNode); |
| return lattice.subclassesOfClass[index]; |
| } |
| |
| /// Returns the lattice point containing all function implementing the given |
| /// instance method. |
| int getLatticePointForFunctionsOverridingMethod(Procedure node) { |
| assert(!node.isStatic); |
| if (node.isAccessor) return latticePointForAllFunctions; |
| if (node.enclosingClass.supertype == null) |
| return latticePointForAllFunctions; |
| return lattice.functionsOverridingMethod[node] ??= |
| _makeLatticePointForFunctionsOverridingMethod(node); |
| } |
| |
| int _makeLatticePointForFunctionsOverridingMethod(Procedure node) { |
| Class host = node.enclosingClass; |
| Member superMember = host.supertype == null |
| ? null |
| : hierarchy.getInterfaceMember(host.superclass, node.name); |
| int super_; |
| if (superMember is Procedure && !superMember.isAccessor) { |
| super_ = getLatticePointForFunctionsOverridingMethod(superMember); |
| } else { |
| super_ = getLatticePointForFunctionsWithName(node.name); |
| } |
| int point = newLatticePoint( |
| <int>[super_], coreTypes.functionClass, BaseClassKind.Subtype); |
| visualizer?.annotateLatticePoint(point, node, 'overriders'); |
| return point; |
| } |
| |
| int newVariable([TreeNode node, String info]) { |
| int variable = constraints.newVariable(); |
| visualizer?.annotateVariable(variable, node, info); |
| return variable; |
| } |
| |
| VariableMapping getClassMapping(Class host) { |
| if (host == null) return global; |
| int index = hierarchy.getClassIndex(host); |
| return classMapping[index]; |
| } |
| |
| /// Returns a variable that should contain all values that may be contained |
| /// in any copy the given field (hence "shared" between the copies). |
| int getSharedFieldVariable(Field field) { |
| return global.fields[field] ??= newVariable(field); |
| } |
| |
| /// Returns a variable representing the given field on the given class. |
| /// |
| /// If the field is static, [host] should be `null`. |
| int getFieldVariable(Class host, Field field) { |
| if (host == null) return getSharedFieldVariable(field); |
| VariableMapping mapping = getClassMapping(host); |
| return mapping.fields[field] ??= _makeFieldVariable(host, field); |
| } |
| |
| int _makeFieldVariable(Class host, Field field) { |
| // Create a variable specific to this host class, and add an assignment |
| // to the global sink for this field. |
| assert(host != null); |
| int variable = newVariable(field); |
| int sink = getSharedFieldVariable(field); |
| constraints.addSink(variable, sink); |
| visualizer?.annotateSink(variable, sink, field); |
| return variable; |
| } |
| |
| /// Variable containing all values that may be passed into the given parameter |
| /// of any instantiation of the given function (hence "shared" between them). |
| int getSharedParameterVariable(VariableDeclaration node) { |
| return global.parameters[node] ??= newVariable(node, 'shared parameter'); |
| } |
| |
| int getParameterVariable(Class host, VariableDeclaration node) { |
| if (host == null) return getSharedParameterVariable(node); |
| VariableMapping mapping = getClassMapping(host); |
| return mapping.parameters[node] ??= _makeParameterVariable(host, node); |
| } |
| |
| int _makeParameterVariable(Class host, VariableDeclaration node) { |
| assert(host != null); |
| int variable = newVariable(node, 'parameter'); |
| int sink = getSharedParameterVariable(node); |
| constraints.addSink(variable, sink); |
| visualizer?.annotateSink(variable, sink, node); |
| return variable; |
| } |
| |
| /// Returns the variable representing all the values that would be checked |
| /// against the given function type parameter in checked mode. |
| /// |
| /// This is used to model the behavior of external generic methods. |
| /// |
| /// For example: |
| /// |
| /// class List { |
| /// external static factory List<T> filled<T>(int length, T value); |
| /// } |
| /// |
| /// A variable `v` representing `T` will be generated. All values that are |
| /// passed into the `value` parameter will flow into `v`, and `v` will |
| /// in turn flow into the type parameter field of `List`, because the method |
| /// returns `List<T>`. Also see [FieldNames.getTypeParameterField]. |
| int getFunctionTypeParameterVariable(TypeParameter node) { |
| return functionTypeParameters[node] ??= newVariable(node); |
| } |
| |
| /// Variable containing all values that may be returned from any instantiation |
| /// of the given function (hence "shared" between them). |
| int getSharedReturnVariable(FunctionNode node) { |
| return global.returns[node] ??= newVariable(node, 'return'); |
| } |
| |
| int getReturnVariable(Class host, Procedure node) { |
| if (host == null) return getSharedReturnVariable(node.function); |
| VariableMapping mapping = getClassMapping(host); |
| return mapping.returns[node.function] ??= _makeReturnVariable(host, node); |
| } |
| |
| int _makeReturnVariable(Class host, Procedure node) { |
| assert(host != null); |
| int variable = newVariable(node, 'return'); |
| int sink = getSharedReturnVariable(node.function); |
| constraints.addSink(variable, sink); |
| visualizer?.annotateSink(variable, sink, node); |
| return variable; |
| } |
| |
| /// Returns a variable containing all the function objects for all |
| /// instantiations of the given function. |
| int getSharedTearOffVariable(FunctionNode node) { |
| return global.functions[node] ??= newVariable(node); |
| } |
| |
| /// Returns a variable containing the torn-off copy of the given function |
| /// occurring in static context. |
| int getStaticTearOffVariable(FunctionNode node) { |
| return global.functions[node] ??= _makeStaticTearOffVariable(node); |
| } |
| |
| int _makeStaticTearOffVariable(FunctionNode node) { |
| return newFunction(node); |
| } |
| |
| /// Returns a variable containing the torn-off copy of the given procedure. |
| int getTearOffVariable(Class host, Procedure node) { |
| if (host == null) return getStaticTearOffVariable(node.function); |
| VariableMapping mapping = getClassMapping(host); |
| return mapping.functions[node.function] ??= |
| _makeTearOffVariable(host, node); |
| } |
| |
| int _makeTearOffVariable(Class host, Procedure node) { |
| int variable = newFunction(node.function, node); |
| int sink = getSharedTearOffVariable(node.function); |
| constraints.addSink(variable, sink); |
| visualizer?.annotateSink(variable, sink, node); |
| return variable; |
| } |
| |
| /// Returns the variable holding the result of a 'get' selector dispatched |
| /// to the given member, or `null` if the member cannot respond to a 'get' |
| /// selector. |
| int getMemberGetter(Class host, Member member) { |
| if (member is Field) { |
| return getFieldVariable(host, member); |
| } else if (member is Procedure) { |
| if (member.isGetter) { |
| return getReturnVariable(host, member); |
| } else if (!member.isAccessor) { |
| return getTearOffVariable(host, member); |
| } |
| } |
| return null; |
| } |
| |
| /// Returns the variable holding the argument to a 'set' selector dispatched |
| /// to the given member, or `null` if the member cannot respond to a 'set' |
| /// selector. |
| int getMemberSetter(Class host, Member member) { |
| if (member is Field && !member.isFinal) { |
| return getFieldVariable(host, member); |
| } else if (member is Procedure && member.isSetter) { |
| return getParameterVariable( |
| host, member.function.positionalParameters[0]); |
| } |
| return null; |
| } |
| |
| /// Returns a lattice point containing all instance methods with the given |
| /// name. |
| int getLatticePointForFunctionsWithName(Name name) { |
| if (name == null) return latticePointForAllFunctions; |
| return lattice.functionsWithName[name] ??= |
| _makeLatticePointForFunctionsWithName(name); |
| } |
| |
| int _makeLatticePointForFunctionsWithName(Name name) { |
| int point = newLatticePoint(<int>[latticePointForAllFunctions], |
| coreTypes.functionClass, BaseClassKind.Subtype); |
| visualizer?.annotateLatticePoint(point, null, 'Methods of name $name'); |
| return point; |
| } |
| |
| /// Returns a variable holding a new function value annotated with given AST |
| /// node. |
| /// |
| /// If the function is the body of an instance procedure, it should be passed |
| /// as [member] to ensure an effective lattice is built for it. |
| /// Otherwise, [member] should be omitted. |
| int newFunction(FunctionNode node, [Procedure member]) { |
| assert(node != null); |
| int functionVariable = newVariable(node); |
| int baseLatticePoint = member == null |
| ? latticePointForAllFunctions |
| : getLatticePointForFunctionsOverridingMethod(member); |
| int latticePoint = newLatticePoint(<int>[baseLatticePoint], |
| coreTypes.functionClass, BaseClassKind.Subtype); |
| visualizer?.annotateLatticePoint(latticePoint, member, 'function'); |
| int minArity = node.requiredParameterCount; |
| int maxArity = node.positionalParameters.length; |
| int functionValue = constraints.newValue(latticePoint); |
| for (int i = 0; i < node.positionalParameters.length; ++i) { |
| int variable = newVariable(); |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| int field = fieldNames.getPositionalParameterField(arity, i); |
| constraints.setStoreLocation(functionValue, field, variable); |
| constraints.setLoadLocation(functionValue, field, variable); |
| } |
| } |
| for (int i = 0; i < node.namedParameters.length; ++i) { |
| int variable = newVariable(); |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| int field = fieldNames.getNamedParameterField( |
| arity, node.namedParameters[i].name); |
| constraints.setStoreLocation(functionValue, field, variable); |
| constraints.setLoadLocation(functionValue, field, variable); |
| } |
| } |
| int returnVariable = newVariable(); |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| int returnField = fieldNames.getReturnField(arity); |
| constraints.setStoreLocation(functionValue, returnField, returnVariable); |
| constraints.setLoadLocation(functionValue, returnField, returnVariable); |
| } |
| visualizer?.annotateFunction(functionValue, node); |
| visualizer?.annotateValue(functionValue, member, 'function'); |
| addInput(functionValue, ValueBit.other, functionVariable); |
| constraints.setLoadLocation( |
| functionValue, fieldNames.callHandlerField, functionVariable); |
| constraints.setLoadLocation(functionValue, |
| fieldNames.getPropertyField(Names.call_), functionVariable); |
| return functionVariable; |
| } |
| |
| /// Returns a variable containing the concrete instances of the given class. |
| int getInstanceVariable(Class node) { |
| assert(!node.isAbstract); |
| return hierarchy.getClassIndex(node); |
| } |
| |
| /// Returns the value representing the concrete instances of the given class. |
| int getInstanceValue(Class node) { |
| assert(!node.isAbstract); |
| // Values are negated to help distinguish them from variables and |
| // lattice points. |
| return -hierarchy.getClassIndex(node); |
| } |
| |
| /// Returns a variable containing the external instances of the given class. |
| /// |
| /// An "external instance of C" is an instance allocated by external code, |
| /// and is either a direct instance of C or an instance of an external class |
| /// that implements C. |
| /// |
| /// For the moment, basic types like `int` and `bool` are treated as external |
| /// instances of their respective classes. |
| /// |
| /// Unlike [getInstanceVariable], this method ensures that the relevant |
| /// constraints have been generated to model an external implementation of the |
| /// class. |
| int getExternalInstanceVariable(Class node) { |
| int classIndex = hierarchy.getClassIndex(node); |
| return externalClassVariables[classIndex] ??= |
| _makeExternalInstanceVariable(node, classIndex); |
| } |
| |
| int getValueBitForExternalClass(Class node) { |
| if (node == coreTypes.intClass) { |
| return ValueBit.integer; |
| } else if (node == coreTypes.doubleClass) { |
| return ValueBit.double_; |
| } else if (node == coreTypes.stringClass) { |
| return ValueBit.string; |
| } else { |
| return ValueBit.other; |
| } |
| } |
| |
| int _makeExternalInstanceVariable(Class node, int classIndex) { |
| if (node == coreTypes.numClass) { |
| // Don't build an interface based on the "num" class, instead treat it |
| // as the union of "int" and "double". |
| int variable = newVariable(node); |
| constraints.addAssign(intNode, variable); |
| constraints.addAssign(doubleNode, variable); |
| return variable; |
| } |
| int baseLatticePoint = getLatticePointForSubtypesOfClass(node); |
| // TODO(asgerf): Use more fine-grained handling of externals, based on |
| // metadata or on a specification read from a separate file (issue #22). |
| int latticePoint = |
| newLatticePoint(<int>[baseLatticePoint], node, BaseClassKind.Subtype); |
| visualizer?.annotateLatticePoint(latticePoint, node, 'external'); |
| int value = constraints.newValue(latticePoint); |
| int variable = newVariable(node, 'external'); |
| addInput(value, getValueBitForExternalClass(node), variable); |
| externalClassValues[classIndex] = value; |
| externalClassWorklist.add(classIndex); |
| return variable; |
| } |
| |
| void _buildExternalClassValue(int index) { |
| Class node = hierarchy.classes[index]; |
| int variable = externalClassVariables[index]; |
| int externalObject = externalClassValues[index]; |
| Name previousName = null; |
| for (Member member in hierarchy.getInterfaceMembers(node, setters: false)) { |
| // Do not generate an interface member for a given name more than once. |
| // This can happen if a class inherits two methods through different |
| // inheritance paths. |
| if (member.name == previousName) continue; |
| previousName = member.name; |
| _buildExternalInterfaceMember(node, member, externalObject, variable, |
| isSetter: false); |
| } |
| previousName = null; |
| for (Member member in hierarchy.getInterfaceMembers(node, setters: true)) { |
| if (member.name == previousName) continue; |
| previousName = member.name; |
| _buildExternalInterfaceMember(node, member, externalObject, variable, |
| isSetter: true); |
| } |
| for (TypeParameter parameter in node.typeParameters) { |
| int field = fieldNames.getTypeParameterField(parameter); |
| int location = newVariable(parameter); |
| constraints.setStoreLocation(externalObject, field, location); |
| constraints.setLoadLocation(externalObject, field, location); |
| } |
| } |
| |
| void _buildExternalInterfaceMember( |
| Class host, Member member, int object, int variable, |
| {bool isSetter}) { |
| // TODO(asgerf): Handle nullability of return values. |
| TypeEnvironment environment = |
| new TypeEnvironment(this, host, member, thisVariable: variable); |
| int propertyField = fieldNames.getPropertyField(member.name); |
| if (member is Field) { |
| int fieldType = buildCovariantType(member.type, environment); |
| if (isSetter) { |
| constraints.setStoreLocation(object, propertyField, fieldType); |
| } else { |
| constraints.setLoadLocation(object, propertyField, fieldType); |
| } |
| } else { |
| Procedure procedure = member; |
| FunctionNode function = procedure.function; |
| if (procedure.isGetter) { |
| int returned = buildCovariantType(function.returnType, environment); |
| constraints.setLoadLocation(object, propertyField, returned); |
| } else if (procedure.isSetter) { |
| int escaping = environment.getLoad(variable, propertyField); |
| buildContravariantType( |
| function.positionalParameters[0].type, environment, escaping); |
| } else { |
| int externalMember = buildCovariantFunctionType(function, environment); |
| constraints.setLoadLocation(object, propertyField, externalMember); |
| } |
| } |
| } |
| |
| /// Returns a variable that is exposed to external calls through the |
| /// given interface. |
| /// |
| /// For example, consider this code with a simplified version of SendPort: |
| /// |
| /// abstract class SendPort { |
| /// void send(dynamic x); |
| /// } |
| /// |
| /// class MySendPort implements SendPort { |
| /// void send(x) { ... } |
| /// } |
| /// |
| /// external void spawnFunction(SendPort readyPort); |
| /// |
| /// main() { |
| /// spawnFunction(new MySendPort()); |
| /// } |
| /// |
| /// We must ensure that the parameter to `MySendPort::send` is inferred to |
| /// be unknown because the external function `spawnFunction` may cause an |
| /// invocation of its `send` method with an unknown argument. |
| /// |
| /// The interface escape variable for this version of `SendPort` would be a |
| /// variable `v` with constraints corresponding to a call `v.send(<dynamic>)`. |
| /// |
| /// Values that escape into an external parameter typed as `SendPort`, such |
| /// as `new MySendPort()` must then be made to flow into `v`. |
| int getInterfaceEscapeVariable(Class node) { |
| int index = hierarchy.getClassIndex(node); |
| return interfaceEscapeVariables[index] ?? |
| _buildInterfaceEscapeVariable(node, index); |
| } |
| |
| int _buildInterfaceEscapeVariable(Class node, int index) { |
| int escapingObject = constraints.newVariable(); |
| visualizer?.annotateVariable(escapingObject, node, 'escape point'); |
| interfaceEscapeVariables[index] = escapingObject; |
| for (Member member in hierarchy.getInterfaceMembers(node, setters: false)) { |
| _buildEscapingInterfaceMember(node, member, escapingObject); |
| } |
| for (Member member in hierarchy.getInterfaceMembers(node, setters: true)) { |
| _buildEscapingInterfaceMember(node, member, escapingObject); |
| } |
| return escapingObject; |
| } |
| |
| /// Models the behavior of external code invoking [member] on |
| /// [escapingObject]. |
| void _buildEscapingInterfaceMember( |
| Class host, Member member, int escapingObject) { |
| TypeEnvironment environment = |
| new TypeEnvironment(this, host, member, thisVariable: escapingObject); |
| int propertyField = fieldNames.getPropertyField(member.name); |
| if (member is Field) { |
| int escapingMember = environment.getLoad(escapingObject, propertyField); |
| buildContravariantType(member.type, environment, escapingMember); |
| } else { |
| Procedure procedure = member; |
| FunctionNode function = procedure.function; |
| if (procedure.isGetter) { |
| int escapingMember = environment.getLoad(escapingObject, propertyField); |
| buildContravariantType( |
| function.returnType, environment, escapingMember); |
| } else if (procedure.isSetter) { |
| VariableDeclaration parameter = function.positionalParameters[0]; |
| int argument = buildCovariantType(parameter.type, environment); |
| environment.addStore(escapingObject, propertyField, argument); |
| } else { |
| int escapingMember = environment.getLoad(escapingObject, propertyField); |
| buildContravariantFunctionType(function, environment, escapingMember); |
| } |
| } |
| } |
| |
| /// Returns a variable with the possible values of [type] as provided by |
| /// external code. |
| int buildCovariantType(DartType type, TypeEnvironment environment) { |
| return new CovariantExternalTypeVisitor(this, environment).visit(type); |
| } |
| |
| /// Like [buildCovariantType], but for the function type implied by the |
| /// type annotations on a function AST node. |
| int buildCovariantFunctionType( |
| FunctionNode node, TypeEnvironment environment) { |
| return new CovariantExternalTypeVisitor(this, environment) |
| .buildFunctionNode(node); |
| } |
| |
| /// Generates constraints to model the behavior of [input] escaping into |
| /// external code through a parameter annotated with [type]. |
| void buildContravariantType( |
| DartType type, TypeEnvironment environment, int input) { |
| new ContravariantExternalTypeVisitor(this, environment, input).visit(type); |
| } |
| |
| /// Like [buildContravariantType], but for the function type implied by the |
| /// type annotations on a function AST node. |
| void buildContravariantFunctionType( |
| FunctionNode node, TypeEnvironment environment, int input) { |
| new ContravariantExternalTypeVisitor(this, environment, input) |
| .buildFunctionNode(node); |
| } |
| |
| int getPropertyField(Name name) { |
| return fieldNames.getPropertyField(name); |
| } |
| |
| int getPositionalParameterField(int arity, int position) { |
| return fieldNames.getPositionalParameterField(arity, position); |
| } |
| |
| int getNamedParameterField(int arity, String name) { |
| return fieldNames.getNamedParameterField(arity, name); |
| } |
| |
| int getReturnField(int arity) { |
| return fieldNames.getReturnField(arity); |
| } |
| |
| void buildInstanceValue(Class host) { |
| int value = getInstanceValue(host); |
| for (Member target in hierarchy.getDispatchTargets(host, setters: false)) { |
| var getter = getMemberGetter(host, target); |
| constraints.setLoadLocation(value, getPropertyField(target.name), getter); |
| } |
| for (Member target in hierarchy.getDispatchTargets(host, setters: true)) { |
| constraints.setStoreLocation( |
| value, getPropertyField(target.name), getMemberSetter(host, target)); |
| } |
| for (Class node = host; node != null; node = node.superclass) { |
| for (Procedure procedure in node.mixin.procedures) { |
| if (!procedure.isStatic) { |
| buildProcedure(host, procedure); |
| } |
| } |
| for (Constructor constructor in node.constructors) { |
| buildConstructor(host, constructor); |
| } |
| } |
| // If the object is callable as a function, set up its call handler. |
| Member callHandler = hierarchy.getDispatchTarget(host, Names.call_); |
| if (callHandler != null) { |
| if (callHandler is Procedure && !callHandler.isAccessor) { |
| constraints.setLoadLocation(value, fieldNames.callHandlerField, |
| getTearOffVariable(host, callHandler)); |
| } else { |
| // Generate `this.[call] = this.call.[call]` where [call] is the |
| // call handler field, corresponding to repeatedly reading "call". |
| var environment = new TypeEnvironment(this, host, callHandler); |
| int getter = getMemberGetter(host, callHandler); |
| constraints.setLoadLocation(value, fieldNames.callHandlerField, |
| environment.getLoad(getter, fieldNames.callHandlerField)); |
| } |
| } |
| } |
| |
| void buildStaticField(Field field) { |
| var environment = new Environment(this, null, field); |
| int initializer = field.initializer == null |
| ? nullNode |
| : new StatementBuilder(this, environment) |
| .buildExpression(field.initializer); |
| environment.addAssign(initializer, getSharedFieldVariable(field)); |
| } |
| |
| void buildProcedure(Class hostClass, Procedure node) { |
| if (node.isAbstract) return; |
| int host = hostClass == null ? null : getInstanceVariable(hostClass); |
| int function = getTearOffVariable(hostClass, node); |
| int returnVariable = getReturnVariable(hostClass, node); |
| var environment = new Environment(this, hostClass, node, |
| returnVariable: returnVariable, thisVariable: host); |
| buildFunctionNode(node.function, environment, |
| addTypeBasedSummary: node.isExternal, function: function); |
| } |
| |
| int getDeclarationSiteFieldInitializer(Field field) { |
| if (field.initializer == null) return nullNode; |
| return declarationSiteFieldInitializer[field] ??= |
| _makeDeclarationSiteFieldInitializer(field); |
| } |
| |
| int _makeDeclarationSiteFieldInitializer(Field field) { |
| final initializerEnvironment = new Environment(this, null, field); |
| return new StatementBuilder(this, initializerEnvironment) |
| .buildExpression(field.initializer); |
| } |
| |
| void buildConstructor(Class hostClass, Constructor node) { |
| int host = getInstanceVariable(hostClass); |
| var environment = |
| new Environment(this, hostClass, node, thisVariable: host); |
| buildFunctionNode(node.function, environment); |
| InitializerBuilder builder = new InitializerBuilder(this, environment); |
| Set<Field> initializedFields = new Set<Field>(); |
| for (Initializer initializer in node.initializers) { |
| builder.build(initializer); |
| if (initializer is FieldInitializer) { |
| initializedFields.add(initializer.field); |
| } |
| } |
| for (Field field in node.enclosingClass.mixin.fields) { |
| if (field.isInstanceMember) { |
| // Note: ensure the initializer is built even if it is not used. |
| int initializer = getDeclarationSiteFieldInitializer(field); |
| if (!initializedFields.contains(field)) { |
| int variable = getFieldVariable(hostClass, field); |
| environment.addAssign(initializer, variable); |
| } |
| } |
| } |
| } |
| |
| /// Builds constraints to model the behavior of the given function. |
| /// |
| /// If the function is external, [addTypeBasedSummary] should be `true`; |
| /// its parameter and return type are then used to model its behavior instead |
| /// of the body. |
| /// |
| /// [function] should be a variable holding the function object itself, if |
| /// such an object exists (which is always the case except for constructors, |
| /// which currently do have function values). |
| void buildFunctionNode(FunctionNode node, Environment environment, |
| {int function, bool addTypeBasedSummary: false}) { |
| var expressionBuilder = |
| new StatementBuilder(this, environment).expressionBuilder; |
| int minArity = node.requiredParameterCount; |
| int maxArity = node.positionalParameters.length; |
| for (int i = 0; i < node.positionalParameters.length; ++i) { |
| var parameter = node.positionalParameters[i]; |
| int variable = getParameterVariable(environment.host, parameter); |
| environment.localVariables[parameter] = variable; |
| if (function != null) { |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| if (i < arity) { |
| environment.addLoad( |
| function, getPositionalParameterField(arity, i), variable); |
| } |
| } |
| } |
| if (i >= node.requiredParameterCount) { |
| int parameterDefault = parameter.initializer == null |
| ? nullNode |
| : expressionBuilder.build(parameter.initializer); |
| environment.addAssign(parameterDefault, variable); |
| } |
| if (addTypeBasedSummary) { |
| buildContravariantType(parameter.type, environment, variable); |
| } |
| } |
| for (int i = 0; i < node.namedParameters.length; ++i) { |
| var parameter = node.namedParameters[i]; |
| int variable = getParameterVariable(environment.host, parameter); |
| environment.localVariables[parameter] = variable; |
| if (function != null) { |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| environment.addLoad(function, |
| getNamedParameterField(arity, parameter.name), variable); |
| } |
| } |
| int parameterDefault = parameter.initializer == null |
| ? nullNode |
| : expressionBuilder.build(parameter.initializer); |
| environment.addAssign(parameterDefault, variable); |
| if (addTypeBasedSummary) { |
| buildContravariantType(parameter.type, environment, variable); |
| } |
| } |
| if (environment.returnVariable == null) { |
| environment.returnVariable = newVariable(node, 'return'); |
| environment.addSink( |
| environment.returnVariable, getSharedReturnVariable(node)); |
| } else { |
| visualizer?.annotateVariable(environment.returnVariable, node, 'return'); |
| } |
| if (function != null) { |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| environment.addStore( |
| function, getReturnField(arity), environment.returnVariable); |
| } |
| } |
| if (addTypeBasedSummary) { |
| int returnFromType = buildCovariantType(node.returnType, environment); |
| environment.addAssign(returnFromType, environment.returnVariable); |
| } else if (node.body != null) { |
| Completion completes = |
| new StatementBuilder(this, environment).build(node.body); |
| if (completes == Completion.Maybe) { |
| // Null is returned when control falls over the end. |
| environment.addAssign(nullNode, environment.returnVariable); |
| } |
| } |
| } |
| |
| /// Returns true if we can assume that externals treat the given types as |
| /// covariant. |
| /// |
| /// For example, if an external method returns a `List`, the values stored |
| /// in the list from user code are not considered escaping. |
| bool isAssumedCovariant(Class classNode) { |
| return classNode == coreTypes.listClass || |
| classNode == coreTypes.mapClass || |
| classNode == coreTypes.iterableClass || |
| classNode == coreTypes.iteratorClass || |
| classNode == coreTypes.futureClass || |
| classNode == coreTypes.streamClass; |
| } |
| |
| Set<String> _unsupportedNodes = new Set<String>(); |
| |
| int unsupported(Node node) { |
| if (verbose && _unsupportedNodes.add('${node.runtimeType}')) { |
| print('Unsupported: ${node.runtimeType}'); |
| } |
| return dynamicNode; |
| } |
| } |
| |
| /// Generates unique IDs for fields in the constraint system. |
| /// |
| /// We use several fields in the constraint system that do not correspond to |
| /// Dart fields. A "field" in this context should be seen as a storage location |
| /// that is specific to an instance. |
| class FieldNames { |
| final TupleCanonicalizer _table = new TupleCanonicalizer(); |
| static const int _TagName = 1; |
| static const int _TagPositionalParameter = 2; |
| static const int _TagNamedParameter = 3; |
| static const int _TagReturn = 4; |
| static const int _TagTypeParameter = 5; |
| static const int _TagCallHandler = 6; |
| |
| /// Field mapping an object to the function value that should be invoked when |
| /// the object is called as a function. |
| /// |
| /// This is the equivalent of repeatedly reading the "call" property of an |
| /// object until a function value is found. |
| int callHandlerField; |
| |
| FieldNames() { |
| callHandlerField = _table.get1(_TagCallHandler); |
| } |
| |
| /// Field representing the value returned from a getter, passed into a setter, |
| /// or stored in a Dart field with the given name. |
| int getPropertyField(Name name) { |
| return _table.get2(_TagName, name); |
| } |
| |
| /// Field representing the given positional parameter passed to a method |
| /// invoked with the given arity. |
| int getPositionalParameterField(int arity, int position) { |
| return _table.get3(_TagPositionalParameter, arity, position); |
| } |
| |
| /// Field representing the given named parameter passed to a method invoked |
| /// with the given arity. |
| int getNamedParameterField(int arity, String name) { |
| return _table.get3(_TagNamedParameter, arity, name); |
| } |
| |
| /// Field representing the return value of a method invoked the given arity. |
| int getReturnField(int arity) { |
| return _table.get2(_TagReturn, arity); |
| } |
| |
| /// Field representing the values that would be checked against the given |
| /// type parameter in checked mode. |
| /// |
| /// The type-based modeling of externals uses this to handle types that |
| /// involve type variables. Roughly speaking, we assume that a method whose |
| /// return type is a type variable T can return any value that was passed into |
| /// any parameter of type T. In particular, this is used to model the |
| /// external backend storage in collection types. |
| /// |
| /// This field keeps track of the values that may flow into and out of a |
| /// type variable for a given instance. |
| int getTypeParameterField(TypeParameter parameter) { |
| return _table.get2(_TagTypeParameter, parameter); |
| } |
| |
| int get length => _table.length; |
| |
| String getDiagnosticNameOfField(int field) { |
| List<Object> tuple = _table.getFromIndex(field); |
| switch (tuple[0]) { |
| case _TagName: |
| return '${tuple[1]}'; |
| case _TagPositionalParameter: |
| return 'pos(${tuple[1]},${tuple[2]})'; |
| case _TagNamedParameter: |
| return 'named(${tuple[1]},${tuple[2]})'; |
| case _TagReturn: |
| return 'return(${tuple[1]})'; |
| case _TagTypeParameter: |
| return 'type-param(${tuple[1]})'; |
| case _TagCallHandler: |
| return 'call-handler()'; |
| default: |
| return '!error'; |
| } |
| } |
| } |
| |
| class TypeEnvironment { |
| final Builder builder; |
| final Class host; |
| final Member member; |
| int thisVariable; |
| |
| ConstraintSystem get constraints => builder.constraints; |
| Visualizer get visualizer => builder.visualizer; |
| |
| TypeEnvironment(this.builder, this.host, this.member, {this.thisVariable}); |
| |
| void addAssign(int source, int destination) { |
| constraints.addAssign(source, destination); |
| visualizer?.annotateAssign(source, destination, member); |
| } |
| |
| int getJoin(int first, int second) { |
| // TODO(asgerf): Avoid redundant joins in common cases. |
| int joinPoint = constraints.newVariable(); |
| addAssign(first, joinPoint); |
| addAssign(second, joinPoint); |
| return joinPoint; |
| } |
| |
| int getLoad(int object, int field) { |
| int variable = builder._loads.lookup(object, field); |
| if (variable != null) return variable; |
| variable = constraints.newVariable(); |
| constraints.addLoad(object, field, variable); |
| visualizer?.annotateLoad(object, field, variable, member); |
| builder._loads.put(variable); |
| return variable; |
| } |
| |
| void addLoad(int object, int field, int destination) { |
| constraints.addLoad(object, field, destination); |
| visualizer?.annotateLoad(object, field, destination, member); |
| } |
| |
| int getStore(int object, int field) { |
| int variable = builder._stores.lookup(object, field); |
| if (variable != null) return variable; |
| variable = constraints.newVariable(); |
| constraints.addStore(object, field, variable); |
| visualizer?.annotateStore(object, field, variable, member); |
| builder._stores.put(variable); |
| return variable; |
| } |
| |
| void addStore(int object, int field, int source) { |
| addAssign(source, getStore(object, field)); |
| } |
| |
| void addSink(int source, int sink) { |
| constraints.addSink(source, sink); |
| visualizer?.annotateSink(source, sink, member); |
| } |
| } |
| |
| class Environment extends TypeEnvironment { |
| final Map<VariableDeclaration, int> localVariables; |
| int returnVariable; |
| |
| Environment(Builder builder, Class host, Member member, |
| {int thisVariable, this.returnVariable}) |
| : localVariables = <VariableDeclaration, int>{}, |
| super(builder, host, member, thisVariable: thisVariable); |
| |
| Environment.inner(Environment outer, {this.returnVariable}) |
| : localVariables = outer.localVariables, |
| super(outer.builder, outer.host, outer.member, |
| thisVariable: outer.thisVariable); |
| |
| int getVariable(VariableDeclaration variable) { |
| return localVariables[variable] ??= builder.newVariable(variable); |
| } |
| } |
| |
| class ExpressionBuilder extends ExpressionVisitor<int> { |
| final Builder builder; |
| final Environment environment; |
| final StatementBuilder statementBuilder; |
| |
| ConstraintSystem get constraints => builder.constraints; |
| Visualizer get visualizer => builder.visualizer; |
| FieldNames get fieldNames => builder.fieldNames; |
| |
| ExpressionBuilder(this.builder, this.statementBuilder, this.environment); |
| |
| int build(Expression node) { |
| int variable = node.accept(this); |
| visualizer?.annotateVariable(variable, node); |
| return variable; |
| } |
| |
| int unsupported(Expression node) { |
| return builder.unsupported(node); |
| } |
| |
| defaultExpression(Expression node) { |
| return unsupported(node); |
| } |
| |
| int visitInvalidExpression(InvalidExpression node) { |
| return builder.bottomNode; |
| } |
| |
| int visitVariableGet(VariableGet node) { |
| return environment.getVariable(node.variable); |
| } |
| |
| int visitVariableSet(VariableSet node) { |
| int rightHandSide = build(node.value); |
| int variable = environment.getVariable(node.variable); |
| environment.addAssign(rightHandSide, variable); |
| return rightHandSide; |
| } |
| |
| int visitPropertyGet(PropertyGet node) { |
| if (node.receiver is ThisExpression) { |
| Class host = environment.host; |
| Member target = builder.hierarchy.getDispatchTarget(host, node.name); |
| int source = builder.getMemberGetter(host, target); |
| return source == null ? builder.bottomNode : source; |
| } |
| int object = build(node.receiver); |
| int field = fieldNames.getPropertyField(node.name); |
| return environment.getLoad(object, field); |
| } |
| |
| int visitPropertySet(PropertySet node) { |
| int object = build(node.receiver); |
| int rightHandSide = build(node.value); |
| if (node.receiver is ThisExpression) { |
| Class host = environment.host; |
| Member target = |
| builder.hierarchy.getDispatchTarget(host, node.name, setter: true); |
| int destination = builder.getMemberSetter(host, target); |
| if (destination != null) { |
| environment.addAssign(rightHandSide, destination); |
| } |
| return rightHandSide; |
| } |
| int field = fieldNames.getPropertyField(node.name); |
| environment.addStore(object, field, rightHandSide); |
| return rightHandSide; |
| } |
| |
| int visitDirectPropertyGet(DirectPropertyGet node) { |
| return builder.getMemberGetter(environment.host, node.target); |
| } |
| |
| int visitDirectPropertySet(DirectPropertySet node) { |
| int rightHandSide = build(node.value); |
| int destination = builder.getMemberSetter(environment.host, node.target); |
| if (destination != null) { |
| environment.addAssign(rightHandSide, destination); |
| } |
| return rightHandSide; |
| } |
| |
| int visitSuperPropertyGet(SuperPropertyGet node) { |
| return unsupported(node); |
| } |
| |
| int visitSuperPropertySet(SuperPropertySet node) { |
| build(node.value); |
| return unsupported(node); |
| } |
| |
| int visitStaticGet(StaticGet node) { |
| return builder.getMemberGetter(null, node.target); |
| } |
| |
| int visitStaticSet(StaticSet node) { |
| int rightHandSide = build(node.value); |
| int destination = builder.getMemberSetter(null, node.target); |
| assert(destination != null); // Static accessors must be valid. |
| environment.addAssign(rightHandSide, destination); |
| return rightHandSide; |
| } |
| |
| int visitMethodInvocation(MethodInvocation node) { |
| // Resolve calls on 'this' directly. |
| if (node.receiver is ThisExpression) { |
| Class host = environment.host; |
| Member target = builder.hierarchy.getDispatchTarget(host, node.name); |
| if (target is Procedure && !target.isAccessor) { |
| FunctionNode function = target.function; |
| passArgumentsToFunction(node.arguments, host, function); |
| return builder.getReturnVariable(host, target); |
| } |
| } |
| // Dispatch call dynamically. |
| int receiver = build(node.receiver); |
| int methodProperty = builder.getPropertyField(node.name); |
| int function = node.name.name == 'call' |
| ? receiver |
| : environment.getLoad(receiver, methodProperty); |
| // We have to dispatch through any number of 'call' getters to get to |
| // the actual function. The 'call handler' field unfolds all the 'call' |
| // getters and refers directly to the actual function (if it exists). |
| // TODO(asgerf): When we have strong mode types, skip the 'call handler' |
| // load if the static type system resolves the target to a method. |
| // It is only needed for getters, fields, and untyped calls. |
| int handler = environment.getLoad(function, fieldNames.callHandlerField); |
| visualizer?.annotateVariable(function, node, 'function'); |
| visualizer?.annotateVariable(handler, node, 'call handler'); |
| int arity = node.arguments.positional.length; |
| for (int i = 0; i < node.arguments.positional.length; ++i) { |
| int field = builder.getPositionalParameterField(arity, i); |
| int argument = build(node.arguments.positional[i]); |
| environment.addStore(handler, field, argument); |
| } |
| for (int i = 0; i < node.arguments.named.length; ++i) { |
| NamedExpression namedNode = node.arguments.named[i]; |
| int field = builder.getNamedParameterField(arity, namedNode.name); |
| int argument = build(namedNode.value); |
| environment.addStore(handler, field, argument); |
| } |
| return environment.getLoad(handler, builder.getReturnField(arity)); |
| } |
| |
| void passArgumentsToFunction( |
| Arguments node, Class host, FunctionNode function) { |
| // TODO(asgerf): Check that arity matches (although mismatches are rare). |
| for (int i = 0; i < node.positional.length; ++i) { |
| int argument = build(node.positional[i]); |
| if (i < function.positionalParameters.length) { |
| int parameter = builder.getParameterVariable( |
| host, function.positionalParameters[i]); |
| environment.addAssign(argument, parameter); |
| } |
| } |
| for (int i = 0; i < node.named.length; ++i) { |
| NamedExpression namedNode = node.named[i]; |
| int argument = build(namedNode.value); |
| // TODO(asgerf): Avoid the slow lookup for named parameters. |
| for (int j = 0; j < function.namedParameters.length; ++j) { |
| var namedParameter = function.namedParameters[j]; |
| if (namedParameter.name == namedNode.name) { |
| int parameter = builder.getParameterVariable(host, namedParameter); |
| environment.addAssign(argument, parameter); |
| break; |
| } |
| } |
| } |
| } |
| |
| int visitDirectMethodInvocation(DirectMethodInvocation node) { |
| // TODO(asgerf): Support cases where the receiver is not 'this'. |
| passArgumentsToFunction( |
| node.arguments, environment.host, node.target.function); |
| return builder.getReturnVariable(environment.host, node.target); |
| } |
| |
| int visitSuperMethodInvocation(SuperMethodInvocation node) { |
| return unsupported(node); |
| } |
| |
| void passArgumentsNowhere(Arguments node) { |
| for (int i = 0; i < node.positional.length; ++i) { |
| build(node.positional[i]); |
| } |
| for (int i = 0; i < node.named.length; ++i) { |
| build(node.named[i].value); |
| } |
| } |
| |
| int visitStaticInvocation(StaticInvocation node) { |
| if (node.target == builder.identicalFunction) { |
| // Ignore calls to identical() as they cause a lot of spurious escape. |
| passArgumentsNowhere(node.arguments); |
| return builder.boolNode; |
| } |
| passArgumentsToFunction(node.arguments, null, node.target.function); |
| return builder.getReturnVariable(null, node.target); |
| } |
| |
| int visitConstructorInvocation(ConstructorInvocation node) { |
| Class host = node.target.enclosingClass; |
| passArgumentsToFunction(node.arguments, host, node.target.function); |
| return builder.getInstanceVariable(host); |
| } |
| |
| int visitNot(Not node) { |
| build(node.operand); |
| return builder.boolNode; |
| } |
| |
| int visitLogicalExpression(LogicalExpression node) { |
| build(node.left); |
| build(node.right); |
| return builder.boolNode; |
| } |
| |
| int visitConditionalExpression(ConditionalExpression node) { |
| build(node.condition); |
| int then = build(node.then); |
| int otherwise = build(node.otherwise); |
| return environment.getJoin(then, otherwise); |
| } |
| |
| int visitStringConcatenation(StringConcatenation node) { |
| for (int i = 0; i < node.expressions.length; ++i) { |
| build(node.expressions[i]); |
| } |
| return builder.stringNode; |
| } |
| |
| int visitIsExpression(IsExpression node) { |
| build(node.operand); |
| return builder.boolNode; |
| } |
| |
| int visitAsExpression(AsExpression node) { |
| return build(node.operand); |
| } |
| |
| int visitSymbolLiteral(SymbolLiteral node) { |
| return builder.symbolNode; |
| } |
| |
| int visitTypeLiteral(TypeLiteral node) { |
| return builder.typeNode; |
| } |
| |
| int visitThisExpression(ThisExpression node) { |
| return environment.thisVariable; |
| } |
| |
| int visitRethrow(Rethrow node) { |
| return builder.bottomNode; |
| } |
| |
| int visitThrow(Throw node) { |
| build(node.expression); |
| return builder.bottomNode; |
| } |
| |
| int visitListLiteral(ListLiteral node) { |
| var object = builder.listNode; |
| TypeParameter parameter = builder.coreTypes.listClass.typeParameters.single; |
| int field = fieldNames.getTypeParameterField(parameter); |
| for (int i = 0; i < node.expressions.length; ++i) { |
| int content = build(node.expressions[i]); |
| environment.addStore(object, field, content); |
| } |
| return object; |
| } |
| |
| int visitMapLiteral(MapLiteral node) { |
| var object = builder.mapNode; |
| List<TypeParameter> parameters = builder.coreTypes.mapClass.typeParameters; |
| int keys = fieldNames.getTypeParameterField(parameters[0]); |
| int values = fieldNames.getTypeParameterField(parameters[1]); |
| for (int i = 0; i < node.entries.length; ++i) { |
| var entry = node.entries[i]; |
| environment.addStore(object, keys, build(entry.key)); |
| environment.addStore(object, values, build(entry.value)); |
| } |
| return object; |
| } |
| |
| int visitAwaitExpression(AwaitExpression node) { |
| return unsupported(node); |
| } |
| |
| int visitFunctionExpression(FunctionExpression node) { |
| return buildInnerFunction(node.function); |
| } |
| |
| int visitStringLiteral(StringLiteral node) { |
| return builder.stringNode; |
| } |
| |
| int visitIntLiteral(IntLiteral node) { |
| return builder.intNode; |
| } |
| |
| int visitDoubleLiteral(DoubleLiteral node) { |
| return builder.doubleNode; |
| } |
| |
| int visitBoolLiteral(BoolLiteral node) { |
| return builder.boolNode; |
| } |
| |
| int visitNullLiteral(NullLiteral node) { |
| return builder.nullNode; |
| } |
| |
| int visitLet(Let node) { |
| environment.localVariables[node.variable] = |
| build(node.variable.initializer); |
| return build(node.body); |
| } |
| |
| int buildInnerFunction(FunctionNode node, {VariableDeclaration self}) { |
| int variable = builder.newFunction(node); |
| if (self != null) { |
| assert(!environment.localVariables.containsKey(self)); |
| environment.localVariables[self] = variable; |
| } |
| Environment inner = new Environment.inner(environment); |
| builder.buildFunctionNode(node, inner, function: variable); |
| return variable; |
| } |
| } |
| |
| /// Indicates whether a statement can complete normally. |
| enum Completion { |
| /// The statement might complete normally. |
| Maybe, |
| |
| /// The statement never completes normally, because it throws, returns, |
| /// breaks, loops forever, etc. |
| Never, |
| } |
| |
| Completion neverCompleteIf(bool condition) { |
| return condition ? Completion.Never : Completion.Maybe; |
| } |
| |
| Completion completeIfBoth(Completion first, Completion second) { |
| return first == Completion.Maybe && second == Completion.Maybe |
| ? Completion.Maybe |
| : Completion.Never; |
| } |
| |
| Completion completeIfEither(Completion first, Completion second) { |
| return first == Completion.Maybe || second == Completion.Maybe |
| ? Completion.Maybe |
| : Completion.Never; |
| } |
| |
| bool _isTrueConstant(Expression node) { |
| return node is BoolLiteral && node.value == true; |
| } |
| |
| bool _isThrowing(Expression node) { |
| return node is Throw || node is Rethrow; |
| } |
| |
| /// Translates a statement to constraints. |
| /// |
| /// The visit methods return a [Completion] indicating if the statement can |
| /// complete normally. This is used to check if null can be returned due to |
| /// control falling over the end of the method. |
| class StatementBuilder extends StatementVisitor<Completion> { |
| final Builder builder; |
| final Environment environment; |
| ExpressionBuilder expressionBuilder; |
| |
| ConstraintSystem get constraints => builder.constraints; |
| Visualizer get visualizer => builder.visualizer; |
| FieldNames get names => builder.fieldNames; |
| |
| StatementBuilder(this.builder, this.environment) { |
| expressionBuilder = new ExpressionBuilder(builder, this, environment); |
| } |
| |
| Completion build(Statement node) => node.accept(this); |
| |
| Completion buildOptional(Statement node) { |
| return node != null ? node.accept(this) : Completion.Maybe; |
| } |
| |
| int buildExpression(Expression node) { |
| return expressionBuilder.build(node); |
| } |
| |
| void unsupported(Statement node) { |
| builder.unsupported(node); |
| } |
| |
| Completion visitInvalidStatement(InvalidStatement node) => Completion.Never; |
| |
| visitExpressionStatement(ExpressionStatement node) { |
| buildExpression(node.expression); |
| return neverCompleteIf(_isThrowing(node.expression)); |
| } |
| |
| visitBlock(Block node) { |
| for (int i = 0; i < node.statements.length; ++i) { |
| if (build(node.statements[i]) == Completion.Never) { |
| return Completion.Never; |
| } |
| } |
| return Completion.Maybe; |
| } |
| |
| visitEmptyStatement(EmptyStatement node) => Completion.Maybe; |
| |
| visitAssertStatement(AssertStatement node) { |
| unsupported(node); |
| return Completion.Maybe; |
| } |
| |
| visitLabeledStatement(LabeledStatement node) { |
| build(node.body); |
| // We don't track reachability of breaks in the body, so just assume we |
| // might hit a break. |
| return Completion.Maybe; |
| } |
| |
| visitBreakStatement(BreakStatement node) => Completion.Never; |
| |
| visitWhileStatement(WhileStatement node) { |
| buildExpression(node.condition); |
| build(node.body); |
| return neverCompleteIf(_isTrueConstant(node.condition)); |
| } |
| |
| visitDoStatement(DoStatement node) { |
| build(node.body); |
| buildExpression(node.condition); |
| return neverCompleteIf(_isTrueConstant(node.condition)); |
| } |
| |
| visitForStatement(ForStatement node) { |
| for (int i = 0; i < node.variables.length; ++i) { |
| build(node.variables[i]); |
| } |
| if (node.condition != null) { |
| buildExpression(node.condition); |
| } |
| for (int i = 0; i < node.updates.length; ++i) { |
| buildExpression(node.updates[i]); |
| } |
| build(node.body); |
| return neverCompleteIf(_isTrueConstant(node.condition)); |
| } |
| |
| visitForInStatement(ForInStatement node) { |
| int iterable = buildExpression(node.iterable); |
| int iterator = environment.getLoad(iterable, builder.iteratorField); |
| int current = environment.getLoad(iterator, builder.currentField); |
| int variable = environment.getVariable(node.variable); |
| environment.addAssign(current, variable); |
| build(node.body); |
| return Completion.Maybe; |
| } |
| |
| visitSwitchStatement(SwitchStatement node) { |
| buildExpression(node.expression); |
| Completion lastCanComplete = Completion.Maybe; |
| for (int i = 0; i < node.cases.length; ++i) { |
| // There is no need to visit the expression since constants cannot |
| // have side effects. |
| // Note that only the last case can actually fall out of the switch, |
| // as the others will throw an exception if they fall through. |
| // Also note that breaks from the switch have been desugared to breaks |
| // to a [LabeledStatement]. |
| lastCanComplete = build(node.cases[i].body); |
| } |
| return lastCanComplete; |
| } |
| |
| visitContinueSwitchStatement(ContinueSwitchStatement node) { |
| return Completion.Never; |
| } |
| |
| visitIfStatement(IfStatement node) { |
| buildExpression(node.condition); |
| Completion thenCompletes = build(node.then); |
| Completion elseCompletes = buildOptional(node.otherwise); |
| return completeIfEither(thenCompletes, elseCompletes); |
| } |
| |
| visitReturnStatement(ReturnStatement node) { |
| if (node.expression != null) { |
| int returned = buildExpression(node.expression); |
| environment.addAssign(returned, environment.returnVariable); |
| } |
| return Completion.Never; |
| } |
| |
| visitTryCatch(TryCatch node) { |
| Completion bodyCompletes = build(node.body); |
| Completion catchCompletes = Completion.Never; |
| for (int i = 0; i < node.catches.length; ++i) { |
| Catch catchNode = node.catches[i]; |
| if (catchNode.exception != null) { |
| environment.localVariables[catchNode.exception] = builder.dynamicNode; |
| } |
| if (catchNode.stackTrace != null) { |
| environment.localVariables[catchNode.stackTrace] = builder.dynamicNode; |
| } |
| if (build(catchNode.body) == Completion.Maybe) { |
| catchCompletes = Completion.Maybe; |
| } |
| } |
| return completeIfEither(bodyCompletes, catchCompletes); |
| } |
| |
| visitTryFinally(TryFinally node) { |
| Completion bodyCompletes = build(node.body); |
| Completion finalizerCompletes = build(node.finalizer); |
| return completeIfBoth(bodyCompletes, finalizerCompletes); |
| } |
| |
| visitYieldStatement(YieldStatement node) { |
| unsupported(node); |
| return Completion.Maybe; |
| } |
| |
| visitVariableDeclaration(VariableDeclaration node) { |
| int initializer = node.initializer == null |
| ? builder.nullNode |
| : buildExpression(node.initializer); |
| int variable = environment.getVariable(node); |
| environment.addAssign(initializer, variable); |
| return neverCompleteIf(_isThrowing(node.initializer)); |
| } |
| |
| visitFunctionDeclaration(FunctionDeclaration node) { |
| expressionBuilder.buildInnerFunction(node.function, self: node.variable); |
| return Completion.Maybe; |
| } |
| } |
| |
| class InitializerBuilder extends InitializerVisitor<Null> { |
| final Builder builder; |
| final Environment environment; |
| ExpressionBuilder expressionBuilder; |
| |
| FieldNames get fieldNames => builder.fieldNames; |
| |
| InitializerBuilder(this.builder, this.environment) { |
| expressionBuilder = |
| new StatementBuilder(builder, environment).expressionBuilder; |
| } |
| |
| void build(Initializer node) { |
| node.accept(this); |
| } |
| |
| int buildExpression(Expression node) { |
| return expressionBuilder.build(node); |
| } |
| |
| visitInvalidInitializer(InvalidInitializer node) {} |
| |
| visitFieldInitializer(FieldInitializer node) { |
| int fieldVariable = builder.getFieldVariable(environment.host, node.field); |
| int rightHandSide = buildExpression(node.value); |
| environment.addAssign(rightHandSide, fieldVariable); |
| } |
| |
| visitSuperInitializer(SuperInitializer node) { |
| expressionBuilder.passArgumentsToFunction( |
| node.arguments, environment.host, node.target.function); |
| } |
| |
| visitRedirectingInitializer(RedirectingInitializer node) { |
| expressionBuilder.passArgumentsToFunction( |
| node.arguments, environment.host, node.target.function); |
| } |
| |
| visitLocalInitializer(LocalInitializer node) { |
| environment.localVariables[node.variable] = |
| buildExpression(node.variable.initializer); |
| } |
| } |
| |
| class Names { |
| static final Name current = new Name('current'); |
| static final Name iterator = new Name('iterator'); |
| static final Name then = new Name('then'); |
| static final Name call_ = new Name('call'); |
| } |
| |
| /// Returns a variable with the possible values of a given type, as provided |
| /// by external code. |
| class CovariantExternalTypeVisitor extends DartTypeVisitor<int> { |
| final Builder builder; |
| final TypeEnvironment environment; |
| |
| FieldNames get fieldNames => builder.fieldNames; |
| |
| CovariantExternalTypeVisitor(this.builder, this.environment); |
| |
| void visitContravariant(DartType type, int input) { |
| return new ContravariantExternalTypeVisitor(builder, environment, input) |
| .visit(type); |
| } |
| |
| int visit(DartType type) => type.accept(this); |
| |
| int visitInvalidType(InvalidType node) { |
| return builder.bottomNode; |
| } |
| |
| int visitDynamicType(DynamicType node) { |
| return builder.dynamicNode; |
| } |
| |
| int visitVoidType(VoidType node) { |
| return builder.nullNode; |
| } |
| |
| int visitInterfaceType(InterfaceType node) { |
| int object = builder.getExternalInstanceVariable(node.classNode); |
| for (int i = 0; i < node.typeArguments.length; ++i) { |
| int field = |
| fieldNames.getTypeParameterField(node.classNode.typeParameters[i]); |
| int outputValue = visit(node.typeArguments[i]); |
| environment.addStore(object, field, outputValue); |
| if (!builder.isAssumedCovariant(node.classNode)) { |
| int userValue = environment.getLoad(object, field); |
| visitContravariant(node.typeArguments[i], userValue); |
| } |
| } |
| return object; |
| } |
| |
| int visitTypeParameterType(TypeParameterType node) { |
| if (node.parameter.parent is Class) { |
| assert(environment.thisVariable != null); |
| return environment.getLoad(environment.thisVariable, |
| fieldNames.getTypeParameterField(node.parameter)); |
| } else { |
| return builder.getFunctionTypeParameterVariable(node.parameter); |
| } |
| } |
| |
| int visitFunctionType(FunctionType node) { |
| // TODO: Handle arity range. |
| int arity = node.positionalParameters.length; |
| int function = builder.functionValueNode; |
| for (int i = 0; i < node.positionalParameters.length; ++i) { |
| int field = fieldNames.getPositionalParameterField(arity, i); |
| int argument = environment.getLoad(function, field); |
| visitContravariant(node.positionalParameters[i], argument); |
| } |
| for (int i = 0; i < node.namedParameters.length; ++i) { |
| var parameter = node.namedParameters[i]; |
| int field = fieldNames.getNamedParameterField(arity, parameter.name); |
| int argument = environment.getLoad(function, field); |
| visitContravariant(parameter.type, argument); |
| } |
| int returnVariable = visit(node.returnType); |
| environment.addStore( |
| function, fieldNames.getReturnField(arity), returnVariable); |
| return function; |
| } |
| |
| /// Equivalent to visiting the FunctionType for the given function. |
| int buildFunctionNode(FunctionNode node) { |
| int minArity = node.requiredParameterCount; |
| int maxArity = node.positionalParameters.length; |
| Member member = node.parent is Member ? node.parent : null; |
| int function = builder.newFunction(node, member); |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| for (int i = 0; i < arity; ++i) { |
| int field = fieldNames.getPositionalParameterField(arity, i); |
| int argument = environment.getLoad(function, field); |
| visitContravariant(node.positionalParameters[i].type, argument); |
| } |
| } |
| for (int i = 0; i < node.namedParameters.length; ++i) { |
| VariableDeclaration variable = node.namedParameters[i]; |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| int field = fieldNames.getNamedParameterField(arity, variable.name); |
| int argument = environment.getLoad(function, field); |
| visitContravariant(variable.type, argument); |
| } |
| } |
| int returnVariable = visit(node.returnType); |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| environment.addStore( |
| function, fieldNames.getReturnField(arity), returnVariable); |
| } |
| return function; |
| } |
| } |
| |
| /// Generates constraints to model the behavior of a value escaping into |
| /// external code through a given type. |
| class ContravariantExternalTypeVisitor extends DartTypeVisitor<Null> { |
| final Builder builder; |
| final TypeEnvironment environment; |
| final int input; |
| |
| FieldNames get fieldNames => builder.fieldNames; |
| ConstraintSystem get constraints => builder.constraints; |
| |
| ContravariantExternalTypeVisitor(this.builder, this.environment, this.input); |
| |
| void visit(DartType type) { |
| type.accept(this); |
| } |
| |
| void visitContravariant(DartType type, int input) { |
| return new ContravariantExternalTypeVisitor(builder, environment, input) |
| .visit(type); |
| } |
| |
| int visitCovariant(DartType type) { |
| return new CovariantExternalTypeVisitor(builder, environment).visit(type); |
| } |
| |
| visitInvalidType(InvalidType node) {} |
| |
| visitDynamicType(DynamicType node) {} |
| |
| visitVoidType(VoidType node) {} |
| |
| visitInterfaceType(InterfaceType node) { |
| int escapePoint = builder.getInterfaceEscapeVariable(node.classNode); |
| environment.addAssign(input, escapePoint); |
| } |
| |
| visitTypeParameterType(TypeParameterType node) { |
| if (node.parameter.parent is Class) { |
| assert(environment.thisVariable != null); |
| environment.addStore(environment.thisVariable, |
| fieldNames.getTypeParameterField(node.parameter), input); |
| } else { |
| environment.addAssign( |
| input, builder.getFunctionTypeParameterVariable(node.parameter)); |
| } |
| } |
| |
| visitFunctionType(FunctionType node) { |
| int minArity = node.requiredParameterCount; |
| int maxArity = node.positionalParameters.length; |
| for (int i = 0; i < node.positionalParameters.length; ++i) { |
| int argument = visitCovariant(node.positionalParameters[i]); |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| int field = fieldNames.getPositionalParameterField(arity, i); |
| environment.addStore(input, field, argument); |
| } |
| } |
| for (var parameter in node.namedParameters) { |
| int argument = visitCovariant(parameter.type); |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| int field = fieldNames.getNamedParameterField(arity, parameter.name); |
| environment.addStore(input, field, argument); |
| } |
| } |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| int returnLocation = |
| environment.getLoad(input, fieldNames.getReturnField(arity)); |
| visitContravariant(node.returnType, returnLocation); |
| } |
| } |
| |
| /// Equivalent to visiting the FunctionType for the given function. |
| void buildFunctionNode(FunctionNode node) { |
| int minArity = node.requiredParameterCount; |
| int maxArity = node.positionalParameters.length; |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| for (int i = 0; i < arity; ++i) { |
| int argument = visitCovariant(node.positionalParameters[i].type); |
| int field = fieldNames.getPositionalParameterField(arity, i); |
| environment.addStore(input, field, argument); |
| } |
| } |
| for (int i = 0; i < node.namedParameters.length; ++i) { |
| VariableDeclaration variable = node.namedParameters[i]; |
| int argument = visitCovariant(variable.type); |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| int field = fieldNames.getNamedParameterField(arity, variable.name); |
| environment.addStore(input, field, argument); |
| } |
| } |
| for (int arity = minArity; arity <= maxArity; ++arity) { |
| int returnLocation = |
| environment.getLoad(input, fieldNames.getReturnField(arity)); |
| visitContravariant(node.returnType, returnLocation); |
| } |
| } |
| } |