| // Copyright (c) 2017, 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 js_backend.backend.annotations; | |
| import 'package:kernel/ast.dart' as ir; | |
| import '../common.dart'; | |
| import '../diagnostics/diagnostic_listener.dart'; | |
| import '../diagnostics/messages.dart'; | |
| import '../elements/entities.dart'; | |
| import '../ir/annotations.dart'; | |
| import '../ir/util.dart'; | |
| import '../kernel/dart2js_target.dart'; | |
| import '../options.dart'; | |
| import '../serialization/serialization.dart'; | |
| import '../util/enumset.dart'; | |
| class PragmaAnnotation { | |
| final int _index; | |
| final String name; | |
| final bool forFunctionsOnly; | |
| final bool forFieldsOnly; | |
| final bool internalOnly; | |
| const PragmaAnnotation(this._index, this.name, | |
| {this.forFunctionsOnly: false, | |
| this.forFieldsOnly: false, | |
| this.internalOnly: false}); | |
| int get index { | |
| assert(_index == values.indexOf(this)); | |
| return _index; | |
| } | |
| /// Tells the optimizing compiler to not inline the annotated method. | |
| static const PragmaAnnotation noInline = | |
| const PragmaAnnotation(0, 'noInline', forFunctionsOnly: true); | |
| /// Tells the optimizing compiler to always inline the annotated method, if | |
| /// possible. | |
| static const PragmaAnnotation tryInline = | |
| const PragmaAnnotation(1, 'tryInline', forFunctionsOnly: true); | |
| static const PragmaAnnotation disableFinal = const PragmaAnnotation( | |
| 2, 'disableFinal', | |
| forFunctionsOnly: true, internalOnly: true); | |
| static const PragmaAnnotation noElision = | |
| const PragmaAnnotation(3, 'noElision'); | |
| /// Tells the optimizing compiler that the annotated method cannot throw. | |
| /// Requires @pragma('dart2js:noInline') to function correctly. | |
| static const PragmaAnnotation noThrows = const PragmaAnnotation(4, 'noThrows', | |
| forFunctionsOnly: true, internalOnly: true); | |
| /// Tells the optimizing compiler that the annotated method has no | |
| /// side-effects. Allocations don't count as side-effects, since they can be | |
| /// dropped without changing the semantics of the program. | |
| /// | |
| /// Requires @pragma('dart2js:noInline') to function correctly. | |
| static const PragmaAnnotation noSideEffects = const PragmaAnnotation( | |
| 5, 'noSideEffects', | |
| forFunctionsOnly: true, internalOnly: true); | |
| /// Use this as metadata on method declarations to disable closed world | |
| /// assumptions on parameters, effectively assuming that the runtime arguments | |
| /// could be any value. Note that the constraints due to static types still | |
| /// apply. | |
| static const PragmaAnnotation assumeDynamic = const PragmaAnnotation( | |
| 6, 'assumeDynamic', | |
| forFunctionsOnly: true, internalOnly: true); | |
| static const PragmaAnnotation asTrust = const PragmaAnnotation(7, 'as:trust', | |
| forFunctionsOnly: false, internalOnly: false); | |
| static const PragmaAnnotation asCheck = const PragmaAnnotation(8, 'as:check', | |
| forFunctionsOnly: false, internalOnly: false); | |
| static const PragmaAnnotation typesTrust = const PragmaAnnotation( | |
| 9, 'types:trust', | |
| forFunctionsOnly: false, internalOnly: false); | |
| static const PragmaAnnotation typesCheck = const PragmaAnnotation( | |
| 10, 'types:check', | |
| forFunctionsOnly: false, internalOnly: false); | |
| static const PragmaAnnotation parameterTrust = const PragmaAnnotation( | |
| 11, 'parameter:trust', | |
| forFunctionsOnly: false, internalOnly: false); | |
| static const PragmaAnnotation parameterCheck = const PragmaAnnotation( | |
| 12, 'parameter:check', | |
| forFunctionsOnly: false, internalOnly: false); | |
| static const PragmaAnnotation downcastTrust = const PragmaAnnotation( | |
| 13, 'downcast:trust', | |
| forFunctionsOnly: false, internalOnly: false); | |
| static const PragmaAnnotation downcastCheck = const PragmaAnnotation( | |
| 14, 'downcast:check', | |
| forFunctionsOnly: false, internalOnly: false); | |
| static const List<PragmaAnnotation> values = [ | |
| noInline, | |
| tryInline, | |
| disableFinal, | |
| noElision, | |
| noThrows, | |
| noSideEffects, | |
| assumeDynamic, | |
| asTrust, | |
| asCheck, | |
| typesTrust, | |
| typesCheck, | |
| parameterTrust, | |
| parameterCheck, | |
| downcastTrust, | |
| downcastCheck, | |
| ]; | |
| static const Map<PragmaAnnotation, Set<PragmaAnnotation>> implies = { | |
| typesTrust: {parameterTrust, downcastTrust}, | |
| typesCheck: {parameterCheck, downcastCheck}, | |
| }; | |
| static const Map<PragmaAnnotation, Set<PragmaAnnotation>> excludes = { | |
| noInline: {tryInline}, | |
| tryInline: {noInline}, | |
| typesTrust: {typesCheck, parameterCheck, downcastCheck}, | |
| typesCheck: {typesTrust, parameterTrust, downcastTrust}, | |
| parameterTrust: {parameterCheck}, | |
| parameterCheck: {parameterTrust}, | |
| downcastTrust: {downcastCheck}, | |
| downcastCheck: {downcastTrust}, | |
| asTrust: {asCheck}, | |
| asCheck: {asTrust}, | |
| }; | |
| static const Map<PragmaAnnotation, Set<PragmaAnnotation>> requires = { | |
| noThrows: {noInline}, | |
| noSideEffects: {noInline}, | |
| }; | |
| } | |
| EnumSet<PragmaAnnotation> processMemberAnnotations( | |
| CompilerOptions options, | |
| DiagnosticReporter reporter, | |
| ir.Member member, | |
| List<PragmaAnnotationData> pragmaAnnotationData) { | |
| EnumSet<PragmaAnnotation> annotations = new EnumSet<PragmaAnnotation>(); | |
| Uri uri = member.enclosingLibrary.importUri; | |
| bool platformAnnotationsAllowed = | |
| options.testMode || uri.scheme == 'dart' || maybeEnableNative(uri); | |
| for (PragmaAnnotationData data in pragmaAnnotationData) { | |
| String name = data.name; | |
| String suffix = data.suffix; | |
| bool found = false; | |
| for (PragmaAnnotation annotation in PragmaAnnotation.values) { | |
| if (annotation.name == suffix) { | |
| found = true; | |
| annotations.add(annotation); | |
| if (data.hasOptions) { | |
| reporter.reportErrorMessage( | |
| computeSourceSpanFromTreeNode(member), | |
| MessageKind.GENERIC, | |
| {'text': "@pragma('$name') annotation does not take options"}); | |
| } | |
| if (annotation.forFunctionsOnly) { | |
| if (member is! ir.Procedure && member is! ir.Constructor) { | |
| reporter.reportErrorMessage( | |
| computeSourceSpanFromTreeNode(member), MessageKind.GENERIC, { | |
| 'text': "@pragma('$name') annotation is only supported " | |
| "for methods and constructors." | |
| }); | |
| } | |
| } | |
| if (annotation.forFieldsOnly) { | |
| if (member is! ir.Field) { | |
| reporter.reportErrorMessage( | |
| computeSourceSpanFromTreeNode(member), MessageKind.GENERIC, { | |
| 'text': "@pragma('$name') annotation is only supported " | |
| "for fields." | |
| }); | |
| } | |
| } | |
| if (annotation.internalOnly && !platformAnnotationsAllowed) { | |
| reporter.reportErrorMessage( | |
| computeSourceSpanFromTreeNode(member), | |
| MessageKind.GENERIC, | |
| {'text': "Unrecognized dart2js pragma @pragma('$name')"}); | |
| } | |
| break; | |
| } | |
| } | |
| if (!found) { | |
| reporter.reportErrorMessage( | |
| computeSourceSpanFromTreeNode(member), | |
| MessageKind.GENERIC, | |
| {'text': "Unknown dart2js pragma @pragma('$name')"}); | |
| } | |
| } | |
| Map<PragmaAnnotation, EnumSet<PragmaAnnotation>> reportedExclusions = {}; | |
| for (PragmaAnnotation annotation | |
| in annotations.iterable(PragmaAnnotation.values)) { | |
| Set<PragmaAnnotation> implies = PragmaAnnotation.implies[annotation]; | |
| if (implies != null) { | |
| for (PragmaAnnotation other in implies) { | |
| if (annotations.contains(other)) { | |
| reporter.reportHintMessage( | |
| computeSourceSpanFromTreeNode(member), MessageKind.GENERIC, { | |
| 'text': "@pragma('dart2js:${annotation.name}') implies " | |
| "@pragma('dart2js:${other.name}')." | |
| }); | |
| } | |
| } | |
| } | |
| Set<PragmaAnnotation> excludes = PragmaAnnotation.excludes[annotation]; | |
| if (excludes != null) { | |
| for (PragmaAnnotation other in excludes) { | |
| if (annotations.contains(other) && | |
| !(reportedExclusions[other]?.contains(annotation) ?? false)) { | |
| reporter.reportErrorMessage( | |
| computeSourceSpanFromTreeNode(member), MessageKind.GENERIC, { | |
| 'text': "@pragma('dart2js:${annotation.name}') must not be used " | |
| "with @pragma('dart2js:${other.name}')." | |
| }); | |
| (reportedExclusions[annotation] ??= new EnumSet()).add(other); | |
| } | |
| } | |
| } | |
| Set<PragmaAnnotation> requires = PragmaAnnotation.requires[annotation]; | |
| if (requires != null) { | |
| for (PragmaAnnotation other in requires) { | |
| if (!annotations.contains(other)) { | |
| reporter.reportErrorMessage( | |
| computeSourceSpanFromTreeNode(member), MessageKind.GENERIC, { | |
| 'text': "@pragma('dart2js:${annotation.name}') should always be " | |
| "combined with @pragma('dart2js:${other.name}')." | |
| }); | |
| } | |
| } | |
| } | |
| } | |
| return annotations; | |
| } | |
| abstract class AnnotationsData { | |
| /// Deserializes a [AnnotationsData] object from [source]. | |
| factory AnnotationsData.readFromDataSource( | |
| CompilerOptions options, DataSource source) = | |
| AnnotationsDataImpl.readFromDataSource; | |
| /// Serializes this [AnnotationsData] to [sink]. | |
| void writeToDataSink(DataSink sink); | |
| /// Returns `true` if [member] has an `@pragma('dart2js:assumeDynamic')` annotation. | |
| bool hasAssumeDynamic(MemberEntity member); | |
| /// Returns `true` if [member] has a `@pragma('dart2js:noInline')`, or | |
| /// `@pragma('dart2js:noInline')` annotation. | |
| bool hasNoInline(MemberEntity member); | |
| /// Returns `true` if [member] has a `@pragma('dart2js:tryInline')`, or | |
| /// `@pragma('dart2js:tryInline')` annotation. | |
| bool hasTryInline(MemberEntity member); | |
| /// Returns `true` if [member] has a `@pragma('dart2js:disableFinal')` | |
| /// annotation. | |
| bool hasDisableFinal(MemberEntity member); | |
| /// Returns `true` if [member] has a `@pragma('dart2js:noElision')` | |
| /// annotation. | |
| bool hasNoElision(MemberEntity member); | |
| /// Returns `true` if [member] has a `@NoThrows()` annotation. | |
| bool hasNoThrows(MemberEntity member); | |
| /// Returns `true` if [member] has a `@NoSideEffects()` annotation. | |
| bool hasNoSideEffects(MemberEntity member); | |
| /// Calls [f] for all functions with a `@pragma('dart2js:noInline')`, or | |
| /// `@pragma('dart2js:noInline')` annotation. | |
| void forEachNoInline(void f(FunctionEntity function)); | |
| /// Calls [f] for all functions with a `@pragma('dart2js:tryInline')`, or | |
| /// `@pragma('dart2js:tryInline')` annotation. | |
| void forEachTryInline(void f(FunctionEntity function)); | |
| /// Calls [f] for all functions with a `@pragma('dart2js:noThrows')` | |
| /// annotation. | |
| void forEachNoThrows(void f(FunctionEntity function)); | |
| /// Calls [f] for all functions with a `@pragma('dart2js:noSideEffects')` | |
| /// annotation. | |
| void forEachNoSideEffects(void f(FunctionEntity function)); | |
| /// What should the compiler do with parameter type assertions in [member]. | |
| /// | |
| /// If [member] is `null`, the default policy is returned. | |
| CheckPolicy getParameterCheckPolicy(MemberEntity member); | |
| /// What should the compiler do with implicit downcasts in [member]. | |
| /// | |
| /// If [member] is `null`, the default policy is returned. | |
| CheckPolicy getImplicitDowncastCheckPolicy(MemberEntity member); | |
| /// What the compiler should do with a boolean value in a condition context | |
| /// in [member] when the language specification says it is a runtime error for | |
| /// it to be null. | |
| /// | |
| /// If [member] is `null`, the default policy is returned. | |
| CheckPolicy getConditionCheckPolicy(MemberEntity member); | |
| /// Whether should the compiler do with explicit casts in [member]. | |
| /// | |
| /// If [member] is `null`, the default policy is returned. | |
| CheckPolicy getExplicitCastCheckPolicy(MemberEntity member); | |
| } | |
| class AnnotationsDataImpl implements AnnotationsData { | |
| /// Tag used for identifying serialized [AnnotationsData] objects in a | |
| /// debugging data stream. | |
| static const String tag = 'annotations-data'; | |
| final CheckPolicy _defaultParameterCheckPolicy; | |
| final CheckPolicy _defaultImplicitDowncastCheckPolicy; | |
| final CheckPolicy _defaultConditionCheckPolicy; | |
| final CheckPolicy _defaultExplicitCastCheckPolicy; | |
| final Map<MemberEntity, EnumSet<PragmaAnnotation>> pragmaAnnotations; | |
| AnnotationsDataImpl(CompilerOptions options, this.pragmaAnnotations) | |
| : this._defaultParameterCheckPolicy = options.defaultParameterCheckPolicy, | |
| this._defaultImplicitDowncastCheckPolicy = | |
| options.defaultImplicitDowncastCheckPolicy, | |
| this._defaultConditionCheckPolicy = options.defaultConditionCheckPolicy, | |
| this._defaultExplicitCastCheckPolicy = | |
| options.defaultExplicitCastCheckPolicy; | |
| factory AnnotationsDataImpl.readFromDataSource( | |
| CompilerOptions options, DataSource source) { | |
| source.begin(tag); | |
| Map<MemberEntity, EnumSet<PragmaAnnotation>> pragmaAnnotations = | |
| source.readMemberMap( | |
| (MemberEntity member) => new EnumSet.fromValue(source.readInt())); | |
| source.end(tag); | |
| return new AnnotationsDataImpl(options, pragmaAnnotations); | |
| } | |
| @override | |
| void writeToDataSink(DataSink sink) { | |
| sink.begin(tag); | |
| sink.writeMemberMap(pragmaAnnotations, | |
| (MemberEntity member, EnumSet<PragmaAnnotation> set) { | |
| sink.writeInt(set.value); | |
| }); | |
| sink.end(tag); | |
| } | |
| bool _hasPragma(MemberEntity member, PragmaAnnotation annotation) { | |
| EnumSet<PragmaAnnotation> set = pragmaAnnotations[member]; | |
| return set != null && set.contains(annotation); | |
| } | |
| @override | |
| bool hasAssumeDynamic(MemberEntity member) => | |
| _hasPragma(member, PragmaAnnotation.assumeDynamic); | |
| @override | |
| bool hasNoInline(MemberEntity member) => | |
| _hasPragma(member, PragmaAnnotation.noInline); | |
| @override | |
| bool hasTryInline(MemberEntity member) => | |
| _hasPragma(member, PragmaAnnotation.tryInline); | |
| @override | |
| bool hasDisableFinal(MemberEntity member) => | |
| _hasPragma(member, PragmaAnnotation.disableFinal); | |
| @override | |
| bool hasNoElision(MemberEntity member) => | |
| _hasPragma(member, PragmaAnnotation.noElision); | |
| @override | |
| bool hasNoThrows(MemberEntity member) => | |
| _hasPragma(member, PragmaAnnotation.noThrows); | |
| @override | |
| bool hasNoSideEffects(MemberEntity member) => | |
| _hasPragma(member, PragmaAnnotation.noSideEffects); | |
| @override | |
| void forEachNoInline(void f(FunctionEntity function)) { | |
| pragmaAnnotations | |
| .forEach((MemberEntity member, EnumSet<PragmaAnnotation> set) { | |
| if (set.contains(PragmaAnnotation.noInline)) { | |
| f(member); | |
| } | |
| }); | |
| } | |
| @override | |
| void forEachTryInline(void f(FunctionEntity function)) { | |
| pragmaAnnotations | |
| .forEach((MemberEntity member, EnumSet<PragmaAnnotation> set) { | |
| if (set.contains(PragmaAnnotation.tryInline)) { | |
| f(member); | |
| } | |
| }); | |
| } | |
| @override | |
| void forEachNoThrows(void f(FunctionEntity function)) { | |
| pragmaAnnotations | |
| .forEach((MemberEntity member, EnumSet<PragmaAnnotation> set) { | |
| if (set.contains(PragmaAnnotation.noThrows)) { | |
| f(member); | |
| } | |
| }); | |
| } | |
| @override | |
| void forEachNoSideEffects(void f(FunctionEntity function)) { | |
| pragmaAnnotations | |
| .forEach((MemberEntity member, EnumSet<PragmaAnnotation> set) { | |
| if (set.contains(PragmaAnnotation.noSideEffects)) { | |
| f(member); | |
| } | |
| }); | |
| } | |
| @override | |
| CheckPolicy getParameterCheckPolicy(MemberEntity member) { | |
| if (member != null) { | |
| EnumSet<PragmaAnnotation> annotations = pragmaAnnotations[member]; | |
| if (annotations != null) { | |
| if (annotations.contains(PragmaAnnotation.typesTrust)) { | |
| return CheckPolicy.trusted; | |
| } else if (annotations.contains(PragmaAnnotation.typesCheck)) { | |
| return CheckPolicy.checked; | |
| } else if (annotations.contains(PragmaAnnotation.parameterTrust)) { | |
| return CheckPolicy.trusted; | |
| } else if (annotations.contains(PragmaAnnotation.parameterCheck)) { | |
| return CheckPolicy.checked; | |
| } | |
| } | |
| } | |
| return _defaultParameterCheckPolicy; | |
| } | |
| @override | |
| CheckPolicy getImplicitDowncastCheckPolicy(MemberEntity member) { | |
| if (member != null) { | |
| EnumSet<PragmaAnnotation> annotations = pragmaAnnotations[member]; | |
| if (annotations != null) { | |
| if (annotations.contains(PragmaAnnotation.typesTrust)) { | |
| return CheckPolicy.trusted; | |
| } else if (annotations.contains(PragmaAnnotation.typesCheck)) { | |
| return CheckPolicy.checked; | |
| } else if (annotations.contains(PragmaAnnotation.downcastTrust)) { | |
| return CheckPolicy.trusted; | |
| } else if (annotations.contains(PragmaAnnotation.downcastCheck)) { | |
| return CheckPolicy.checked; | |
| } | |
| } | |
| } | |
| return _defaultImplicitDowncastCheckPolicy; | |
| } | |
| @override | |
| CheckPolicy getConditionCheckPolicy(MemberEntity member) { | |
| if (member != null) { | |
| EnumSet<PragmaAnnotation> annotations = pragmaAnnotations[member]; | |
| if (annotations != null) { | |
| if (annotations.contains(PragmaAnnotation.typesTrust)) { | |
| return CheckPolicy.trusted; | |
| } else if (annotations.contains(PragmaAnnotation.typesCheck)) { | |
| return CheckPolicy.checked; | |
| } else if (annotations.contains(PragmaAnnotation.downcastTrust)) { | |
| return CheckPolicy.trusted; | |
| } else if (annotations.contains(PragmaAnnotation.downcastCheck)) { | |
| return CheckPolicy.checked; | |
| } | |
| } | |
| } | |
| return _defaultConditionCheckPolicy; | |
| } | |
| @override | |
| CheckPolicy getExplicitCastCheckPolicy(MemberEntity member) { | |
| if (member != null) { | |
| EnumSet<PragmaAnnotation> annotations = pragmaAnnotations[member]; | |
| if (annotations != null) { | |
| if (annotations.contains(PragmaAnnotation.asTrust)) { | |
| return CheckPolicy.trusted; | |
| } else if (annotations.contains(PragmaAnnotation.asCheck)) { | |
| return CheckPolicy.checked; | |
| } | |
| } | |
| } | |
| return _defaultExplicitCastCheckPolicy; | |
| } | |
| } | |
| class AnnotationsDataBuilder { | |
| Map<MemberEntity, EnumSet<PragmaAnnotation>> pragmaAnnotations = {}; | |
| void registerPragmaAnnotations( | |
| MemberEntity member, EnumSet<PragmaAnnotation> annotations) { | |
| if (annotations.isNotEmpty) { | |
| pragmaAnnotations[member] = annotations; | |
| } | |
| } | |
| AnnotationsData close(CompilerOptions options) { | |
| return new AnnotationsDataImpl(options, pragmaAnnotations); | |
| } | |
| } |