Linter Demo Errors: 12Warnings: 32File: /home/fstrocco/Dart/dart/benchmark/compiler/lib/src/native/behavior.dart // Copyright (c) 2014, 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. part of native; /// This class is a temporary work-around until we get a more powerful DartType. class SpecialType { final String name; const SpecialType._(this.name); /// The type Object, but no subtypes: static const JsObject = const SpecialType._('=Object'); int get hashCode => name.hashCode; } /// Description of the exception behaviour of native code. /// /// TODO(sra): Replace with something that better supports specialization on /// first argument properties. class NativeThrowBehavior { static const NativeThrowBehavior NEVER = const NativeThrowBehavior._(0); static const NativeThrowBehavior MAY_THROW_ONLY_ON_FIRST_ARGUMENT_ACCESS = const NativeThrowBehavior._(1); static const NativeThrowBehavior MAY = const NativeThrowBehavior._(2); static const NativeThrowBehavior MUST = const NativeThrowBehavior._(3); final int _bits; const NativeThrowBehavior._(this._bits); String toString() { if (this == NEVER) return 'never'; if (this == MAY) return 'may'; if (this == MAY_THROW_ONLY_ON_FIRST_ARGUMENT_ACCESS) return 'null(1)'; if (this == MUST) return 'must'; return 'NativeThrowBehavior($_bits)'; } } /** * A summary of the behavior of a native element. * * Native code can return values of one type and cause native subtypes of * another type to be instantiated. By default, we compute both from the * declared type. * * A field might yield any native type that 'is' the field type. * * A method might create and return instances of native subclasses of its * declared return type, and a callback argument may be called with instances of * the callback parameter type (e.g. Event). * * If there is one or more `@Creates` annotations, the union of the named types * replaces the inferred instantiated type, and the return type is ignored for * the purpose of inferring instantiated types. * * @Creates('IDBCursor') // Created asynchronously. * @Creates('IDBRequest') // Created synchronously (for return value). * IDBRequest openCursor(); * * If there is one or more `@Returns` annotations, the union of the named types * replaces the declared return type. * * @Returns('IDBRequest') * IDBRequest openCursor(); * * Types in annotations are non-nullable, so include `@Returns('Null')` if * `null` may be returned. */ class NativeBehavior { /// [DartType]s or [SpecialType]s returned or yielded by the native element. final List typesReturned = []; /// [DartType]s or [SpecialType]s instantiated by the native element. final List typesInstantiated = []; // If this behavior is for a JS expression, [codeTemplate] contains the // parsed tree. js.Template codeTemplate; final SideEffects sideEffects = new SideEffects.empty(); NativeThrowBehavior throwBehavior = NativeThrowBehavior.MAY; bool isAllocation = false; bool useGvn = false; String toString() { return 'NativeBehavior(' 'returns: ${typesReturned}, ' 'creates: ${typesInstantiated}, ' 'sideEffects: ${sideEffects}, ' 'throws: ${throwBehavior}' '${isAllocation ? ", isAllocation" : ""}' '${useGvn ? ", useGvn" : ""}' ')'; } /// Processes the type specification string of a call to JS and stores the /// result in the [typesReturned] and [typesInstantiated]. It furthermore /// computes the side effects, and, if given, invokes [setSideEffects] with /// the computed effects. If no side effects are encoded in the [specString] /// the [setSideEffects] method is not invoked. /// /// Two forms of the string is supported: /// /// 1) A single type string of the form 'void', '', 'var' or 'T1|...|Tn' /// which defines the types returned and for the later form also created by /// the call to JS. /// /// 2) A sequence of : pairs of the following kinds /// /// : /// : /// throws: /// gvn: /// new: /// /// A is either 'returns' or 'creates' and is a /// type string like in 1). The type string marked by 'returns' defines the /// types returned and 'creates' defines the types created by the call to /// JS. /// /// An is either 'effects' or 'depends' and is /// either 'all', 'none' or a comma-separated list of 'no-index', /// 'no-instance', 'no-static'. /// /// The flag 'all' indicates that the call affects/depends on every /// side-effect. The flag 'none' indicates that the call does not affect /// (resp. depends on) anything. /// /// 'no-index' indicates that the call does *not* do any array index-store /// (for 'effects'), or depends on any value in an array (for 'depends'). /// The flag 'no-instance' indicates that the call does not modify (resp. /// depends on) any instance variable. Similarly static variables are /// indicated with 'no-static'. The flags 'effects' and 'depends' must be /// used in unison (either both are present or none is). /// /// The values are 'never', 'may', 'must', and 'null(1)'. /// The default if unspecified is 'may'. 'null(1)' means that the template /// expression throws if and only if the first template parameter is `null` /// or `undefined`. /// TODO(sra): Can we simplify to must/may/never and add null(1) by /// inspection as an orthogonal attribute? /// /// values are 'true' and 'false'. The default if unspecified /// is 'false'. /// /// values are 'true' and 'false'. The default if unspecified /// is 'false'. A 'true' value means that each evaluation returns a fresh /// (new) object that cannot be unaliased with existing objects. /// /// Each tag kind (including the 'type-tag's) can only occur once in the /// sequence. /// /// [specString] is the specification string, [resolveType] resolves named /// types into type values, [typesReturned] and [typesInstantiated] collects /// the types defined by the specification string, and [objectType] and /// [nullType] define the types for `Object` and `Null`, respectively. The /// latter is used for the type strings of the form '' and 'var'. /// [validTags] can be used to restrict which tags are accepted. static void processSpecString( DiagnosticListener listener, Spannable spannable, String specString, {Iterable validTags, void setSideEffects(SideEffects newEffects), void setThrows(NativeThrowBehavior throwKind), void setIsAllocation(bool isAllocation), void setUseGvn(bool useGvn), dynamic resolveType(String typeString), List typesReturned, List typesInstantiated, objectType, nullType}) { const List knownTags = const [ 'creates', 'returns', 'depends', 'effects', 'throws', 'gvn', 'new']; /// Resolve a type string of one of the three forms: /// * 'void' - in which case [onVoid] is called, /// * '' or 'var' - in which case [onVar] is called, /// * 'T1|...|Tn' - in which case [onType] is called for each resolved Ti. void resolveTypesString(String typesString, {onVoid(), onVar(), onType(type)}) { // Various things that are not in fact types. if (typesString == 'void') { if (onVoid != null) { onVoid(); } return; } if (typesString == '' || typesString == 'var') { if (onVar != null) { onVar(); } return; } for (final typeString in typesString.split('|')) { onType(resolveType(typeString.trim())); } } if (!specString.contains(';') && !specString.contains(':')) { // Form (1), types or pseudo-types like 'void' and 'var'. resolveTypesString(specString.trim(), onVar: () { typesReturned.add(objectType); typesReturned.add(nullType); }, onType: (type) { typesInstantiated.add(type); typesReturned.add(type); }); return; } List specs = specString.split(';') .map((s) => s.trim()) .toList(); if (specs.last == "") specs.removeLast(); // Allow separator to terminate. assert(validTags == null || (validTags.toSet()..removeAll(validTags)).isEmpty); if (validTags == null) validTags = knownTags; Map values = {}; for (String spec in specs) { List tagAndValue = spec.split(':'); if (tagAndValue.length != 2) { listener.internalError(spannable, "Invalid : pair '$spec'."); } String tag = tagAndValue[0].trim(); String value = tagAndValue[1].trim(); if (validTags.contains(tag)) { if (values[tag] == null) { values[tag] = value; } else { listener.internalError(spannable, "Duplicate tag '$tag'."); } } else { if (knownTags.contains(tag)) { listener.internalError(spannable, "Tag '$tag' is not valid here."); } else { listener.internalError(spannable, "Unknown tag '$tag'."); } } } // Enum-like tags are looked up in a map. True signature is: // // T tagValueLookup(String tag, Map map); // dynamic tagValueLookup(String tag, Map map) { String tagString = values[tag]; if (tagString == null) return null; var value = map[tagString]; if (value == null) { listener.internalError(spannable, "Unknown '$tag' specification: '$tagString'"); } return value; } String returns = values['returns']; if (returns != null) { resolveTypesString(returns, onVar: () { typesReturned.add(objectType); typesReturned.add(nullType); }, onType: (type) { typesReturned.add(type); }); } String creates = values['creates']; if (creates != null) { resolveTypesString(creates, onVoid: () { listener.internalError(spannable, "Invalid type string 'creates:$creates'"); }, onVar: () { listener.internalError(spannable, "Invalid type string 'creates:$creates'"); }, onType: (type) { typesInstantiated.add(type); }); } const throwsOption = const { 'never': NativeThrowBehavior.NEVER, 'null(1)': NativeThrowBehavior.MAY_THROW_ONLY_ON_FIRST_ARGUMENT_ACCESS, 'may': NativeThrowBehavior.MAY, 'must': NativeThrowBehavior.MUST }; const boolOptions = const{'true': true, 'false': false}; SideEffects sideEffects = processEffects(listener, spannable, values['effects'], values['depends']); NativeThrowBehavior throwsKind = tagValueLookup('throws', throwsOption); bool isAllocation = tagValueLookup('new', boolOptions); bool useGvn = tagValueLookup('gvn', boolOptions); if (isAllocation == true && useGvn == true) { listener.internalError(spannable, "'new' and 'gvn' are incompatible"); } if (sideEffects != null) setSideEffects(sideEffects); if (throwsKind != null) setThrows(throwsKind); if (isAllocation != null) setIsAllocation(isAllocation); if (useGvn != null) setUseGvn(useGvn); } static SideEffects processEffects( DiagnosticListener listener, Spannable spannable, String effects, String depends) { if (effects == null && depends == null) return null; if (effects == null || depends == null) { listener.internalError(spannable, "Invalid JS spec string. " "'effects' and 'depends' must occur together."); return null; } SideEffects sideEffects = new SideEffects(); if (effects == "none") { sideEffects.clearAllSideEffects(); } else if (effects == "all") { // Don't do anything. } else { List splitEffects = effects.split(","); if (splitEffects.isEmpty) { listener.internalError(spannable, "Missing side-effect flag."); } for (String effect in splitEffects) { switch (effect) { case "no-index": sideEffects.clearChangesIndex(); break; case "no-instance": sideEffects.clearChangesInstanceProperty(); break; case "no-static": sideEffects.clearChangesStaticProperty(); break; default: listener.internalError(spannable, "Unrecognized side-effect flag: '$effect'."); } } } if (depends == "none") { sideEffects.clearAllDependencies(); } else if (depends == "all") { // Don't do anything. } else { List splitDependencies = depends.split(","); if (splitDependencies.isEmpty) { listener.internalError(spannable, "Missing side-effect dependency flag."); } for (String dependency in splitDependencies) { switch (dependency) { case "no-index": sideEffects.clearDependsOnIndexStore(); break; case "no-instance": sideEffects.clearDependsOnInstancePropertyStore(); break; case "no-static": sideEffects.clearDependsOnStaticPropertyStore(); break; default: listener.internalError(spannable, "Unrecognized side-effect flag: '$dependency'."); } } } return sideEffects; } static NativeBehavior ofJsCall(Send jsCall, Compiler compiler, resolver) { // The first argument of a JS-call is a string encoding various attributes // of the code. // // 'Type1|Type2'. A union type. // '=Object'. A JavaScript Object, no subtype. var argNodes = jsCall.arguments; if (argNodes.isEmpty) { compiler.internalError(jsCall, "JS expression has no type."); } var code = argNodes.tail.head; if (code is !StringNode || code.isInterpolation) { compiler.internalError(code, 'JS code must be a string literal.'); } LiteralString specLiteral = argNodes.head.asLiteralString(); if (specLiteral == null) { // TODO(sra): We could accept a type identifier? e.g. JS(bool, '1<2'). It // is not very satisfactory because it does not work for void, dynamic. compiler.internalError(argNodes.head, "Unexpected JS first argument."); } NativeBehavior behavior = new NativeBehavior(); behavior.codeTemplate = js.js.parseForeignJS(code.dartString.slowToString()); String specString = specLiteral.dartString.slowToString(); dynamic resolveType(String typeString) { return _parseType( typeString, compiler, (name) => resolver.resolveTypeFromString(specLiteral, name), jsCall); } bool sideEffectsAreEncodedInSpecString = false; void setSideEffects(SideEffects newEffects) { sideEffectsAreEncodedInSpecString = true; behavior.sideEffects.setTo(newEffects); } bool throwBehaviorFromSpecString = false; void setThrows(NativeThrowBehavior throwBehavior) { throwBehaviorFromSpecString = true; behavior.throwBehavior = throwBehavior; } void setIsAllocation(bool isAllocation) { behavior.isAllocation = isAllocation; } void setUseGvn(bool useGvn) { behavior.useGvn = useGvn; } processSpecString(compiler, jsCall, specString, setSideEffects: setSideEffects, setThrows: setThrows, setIsAllocation: setIsAllocation, setUseGvn: setUseGvn, resolveType: resolveType, typesReturned: behavior.typesReturned, typesInstantiated: behavior.typesInstantiated, objectType: compiler.objectClass.computeType(compiler), nullType: compiler.nullClass.computeType(compiler)); if (!sideEffectsAreEncodedInSpecString) { new SideEffectsVisitor(behavior.sideEffects) .visit(behavior.codeTemplate.ast); } // TODO(sra): Simplify [throwBehavior] using [sideEffects]. return behavior; } static NativeBehavior ofJsEmbeddedGlobalCall(Send jsGlobalCall, Compiler compiler, resolver) { // The first argument of a JS-embedded global call is a string encoding // the type of the code. // // 'Type1|Type2'. A union type. // '=Object'. A JavaScript Object, no subtype. Link argNodes = jsGlobalCall.arguments; if (argNodes.isEmpty) { compiler.internalError(jsGlobalCall, "JS embedded global expression has no type."); } // We don't check the given name. That needs to be done at a later point. // This is, because we want to allow non-literals as names. if (argNodes.tail.isEmpty) { compiler.internalError(jsGlobalCall, 'Embedded Global is missing name.'); } if (!argNodes.tail.tail.isEmpty) { compiler.internalError(argNodes.tail.tail.head, 'Embedded Global has more than 2 arguments'); } LiteralString specLiteral = argNodes.head.asLiteralString(); if (specLiteral == null) { // TODO(sra): We could accept a type identifier? e.g. JS(bool, '1<2'). It // is not very satisfactory because it does not work for void, dynamic. compiler.internalError(argNodes.head, "Unexpected first argument."); } NativeBehavior behavior = new NativeBehavior(); String specString = specLiteral.dartString.slowToString(); dynamic resolveType(String typeString) { return _parseType( typeString, compiler, (name) => resolver.resolveTypeFromString(specLiteral, name), jsGlobalCall); } processSpecString(compiler, jsGlobalCall, specString, validTags: const ['returns', 'creates'], resolveType: resolveType, typesReturned: behavior.typesReturned, typesInstantiated: behavior.typesInstantiated, objectType: compiler.objectClass.computeType(compiler), nullType: compiler.nullClass.computeType(compiler)); return behavior; } static NativeBehavior ofMethod(FunctionElement method, Compiler compiler) { FunctionType type = method.computeType(compiler); var behavior = new NativeBehavior(); behavior.typesReturned.add(type.returnType); if (!type.returnType.isVoid) { // Declared types are nullable. behavior.typesReturned.add(compiler.nullClass.computeType(compiler)); } behavior._capture(type, compiler); // TODO(sra): Optional arguments are currently missing from the // DartType. This should be fixed so the following work-around can be // removed. method.functionSignature.forEachOptionalParameter( (ParameterElement parameter) { behavior._escape(parameter.type, compiler); }); behavior._overrideWithAnnotations(method, compiler); return behavior; } static NativeBehavior ofFieldLoad(Element field, Compiler compiler) { DartType type = field.computeType(compiler); var behavior = new NativeBehavior(); behavior.typesReturned.add(type); // Declared types are nullable. behavior.typesReturned.add(compiler.nullClass.computeType(compiler)); behavior._capture(type, compiler); behavior._overrideWithAnnotations(field, compiler); return behavior; } static NativeBehavior ofFieldStore(Element field, Compiler compiler) { DartType type = field.computeType(compiler); var behavior = new NativeBehavior(); behavior._escape(type, compiler); // We don't override the default behaviour - the annotations apply to // loading the field. return behavior; } void _overrideWithAnnotations(Element element, Compiler compiler) { if (element.metadata.isEmpty) return; DartType lookup(String name) { Element e = element.buildScope().lookup(name); if (e == null) return null; if (e is! ClassElement) return null; ClassElement cls = e; cls.ensureResolved(compiler); return cls.thisType; } NativeEnqueuer enqueuer = compiler.enqueuer.resolution.nativeEnqueuer; var creates = _collect(element, compiler, enqueuer.annotationCreatesClass, lookup); var returns = _collect(element, compiler, enqueuer.annotationReturnsClass, lookup); if (creates != null) { typesInstantiated..clear()..addAll(creates); } if (returns != null) { typesReturned..clear()..addAll(returns); } } /** * Returns a list of type constraints from the annotations of * [annotationClass]. * Returns `null` if no constraints. */ static _collect(Element element, Compiler compiler, Element annotationClass, lookup(str)) { var types = null; for (Link link = element.metadata; !link.isEmpty; link = link.tail) { MetadataAnnotation annotation = link.head.ensureResolved(compiler); ConstantValue value = annotation.constant.value; if (!value.isConstructedObject) continue; ConstructedConstantValue constructedObject = value; if (constructedObject.type.element != annotationClass) continue; List fields = constructedObject.fields; // TODO(sra): Better validation of the constant. if (fields.length != 1 || !fields[0].isString) { PartialMetadataAnnotation partial = annotation; compiler.internalError(annotation, 'Annotations needs one string: ${partial.parseNode(compiler)}'); } StringConstantValue specStringConstant = fields[0]; String specString = specStringConstant.toDartString().slowToString(); for (final typeString in specString.split('|')) { var type = _parseType(typeString, compiler, lookup, annotation); if (types == null) types = []; types.add(type); } } return types; } /// Models the behavior of having intances of [type] escape from Dart code /// into native code. void _escape(DartType type, Compiler compiler) { type = type.unalias(compiler); if (type is FunctionType) { FunctionType functionType = type; // A function might be called from native code, passing us novel // parameters. _escape(functionType.returnType, compiler); for (DartType parameter in functionType.parameterTypes) { _capture(parameter, compiler); } } } /// Models the behavior of Dart code receiving instances and methods of [type] /// from native code. We usually start the analysis by capturing a native /// method that has been used. void _capture(DartType type, Compiler compiler) { type = type.unalias(compiler); if (type is FunctionType) { FunctionType functionType = type; _capture(functionType.returnType, compiler); for (DartType parameter in functionType.parameterTypes) { _escape(parameter, compiler); } } else { typesInstantiated.add(type); } } static dynamic _parseType(String typeString, Compiler compiler, lookup(name), locationNodeOrElement) { if (typeString == '=Object') return SpecialType.JsObject; if (typeString == 'dynamic') { return const DynamicType(); } var type = lookup(typeString); if (type != null) return type; int index = typeString.indexOf('<'); if (index < 1) { compiler.internalError( _errorNode(locationNodeOrElement, compiler), "Type '$typeString' not found."); } type = lookup(typeString.substring(0, index)); if (type != null) { // TODO(sra): Parse type parameters. return type; } compiler.internalError( _errorNode(locationNodeOrElement, compiler), "Type '$typeString' not found."); return null; } static _errorNode(locationNodeOrElement, compiler) { if (locationNodeOrElement is Node) return locationNodeOrElement; return locationNodeOrElement.parseNode(compiler); } }