blob: 15db202c9eaa9f697e50f830cc777c45ff4084a3 [file] [log] [blame]
// 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 '../common_elements.dart' show KCommonElements, KElementEnvironment;
import '../constants/values.dart';
import '../diagnostics/diagnostic_listener.dart';
import '../diagnostics/messages.dart';
import '../elements/entities.dart';
import '../kernel/dart2js_target.dart';
import '../serialization/serialization.dart';
/// Returns `true` if inference of parameter types is disabled for [element].
bool _assumeDynamic(KElementEnvironment elementEnvironment,
KCommonElements commonElements, MemberEntity element) {
return _hasAnnotation(
elementEnvironment, element, commonElements.expectAssumeDynamicClass);
}
/// Returns `true` if [element] is annotated with [annotationClass].
bool _hasAnnotation(KElementEnvironment elementEnvironment,
MemberEntity element, ClassEntity annotationClass) {
if (annotationClass == null) return false;
for (ConstantValue value in elementEnvironment.getMemberMetadata(element)) {
if (value.isConstructedObject) {
ConstructedConstantValue constructedConstant = value;
if (constructedConstant.type.element == annotationClass) {
return true;
}
}
}
return false;
}
enum PragmaAnnotation {
noInline,
tryInline,
disableFinal,
noThrows,
noSideEffects,
trustTypeAnnotations,
assumeDynamic,
}
Set<PragmaAnnotation> processMemberAnnotations(
DiagnosticReporter reporter,
KCommonElements commonElements,
KElementEnvironment elementEnvironment,
AnnotationsDataBuilder annotationsDataBuilder,
MemberEntity element) {
Set<PragmaAnnotation> values = new Set<PragmaAnnotation>();
bool hasNoInline = false;
bool hasTryInline = false;
bool disableFinal = false;
if (_assumeDynamic(elementEnvironment, commonElements, element)) {
values.add(PragmaAnnotation.assumeDynamic);
annotationsDataBuilder.registerAssumeDynamic(element);
}
// TODO(sra): Check for inappropriate annotations on fields.
if (element.isField) {
return values;
}
FunctionEntity method = element;
LibraryEntity library = element.library;
bool platformAnnotationsAllowed = library.canonicalUri.scheme == 'dart' ||
maybeEnableNative(library.canonicalUri);
bool hasNoThrows = false;
bool hasNoSideEffects = false;
for (ConstantValue constantValue
in elementEnvironment.getMemberMetadata(method)) {
if (!constantValue.isConstructedObject) continue;
ConstructedConstantValue value = constantValue;
ClassEntity cls = value.type.element;
assert(cls != null); // Unresolved classes null.
if (platformAnnotationsAllowed) {
if (cls == commonElements.forceInlineClass) {
hasTryInline = true;
} else if (cls == commonElements.noInlineClass) {
hasNoInline = true;
} else if (cls == commonElements.noThrowsClass) {
hasNoThrows = true;
bool isValid = true;
if (method.isTopLevel) {
isValid = true;
} else if (method.isStatic) {
isValid = true;
} else if (method is ConstructorEntity && method.isFactoryConstructor) {
isValid = true;
}
if (!isValid) {
reporter.internalError(
method,
"@NoThrows() is currently limited to top-level"
" or static functions and factory constructors.");
}
annotationsDataBuilder.registerCannotThrow(method);
} else if (cls == commonElements.noSideEffectsClass) {
hasNoSideEffects = true;
annotationsDataBuilder.registerSideEffectsFree(method);
}
}
if (cls == commonElements.expectNoInlineClass) {
hasNoInline = true;
} else if (cls == commonElements.metaNoInlineClass) {
hasNoInline = true;
} else if (cls == commonElements.metaTryInlineClass) {
hasTryInline = true;
} else if (cls == commonElements.pragmaClass) {
// Recognize:
//
// @pragma('dart2js:noInline')
// @pragma('dart2js:tryInline')
//
ConstantValue nameValue =
value.fields[commonElements.pragmaClassNameField];
if (nameValue == null || !nameValue.isString) continue;
String name = (nameValue as StringConstantValue).stringValue;
if (!name.startsWith('dart2js:')) continue;
ConstantValue optionsValue =
value.fields[commonElements.pragmaClassOptionsField];
if (name == 'dart2js:noInline') {
if (!optionsValue.isNull) {
reporter.reportErrorMessage(element, MessageKind.GENERIC,
{'text': "@pragma('$name') annotation does not take options"});
}
hasNoInline = true;
} else if (name == 'dart2js:tryInline') {
if (!optionsValue.isNull) {
reporter.reportErrorMessage(element, MessageKind.GENERIC,
{'text': "@pragma('$name') annotation does not take options"});
}
hasTryInline = true;
} else if (!platformAnnotationsAllowed) {
reporter.reportErrorMessage(element, MessageKind.GENERIC,
{'text': "Unknown dart2js pragma @pragma('$name')"});
} else {
// Handle platform-only `@pragma` annotations.
if (name == 'dart2js:disableFinal') {
if (!optionsValue.isNull) {
reporter.reportErrorMessage(element, MessageKind.GENERIC,
{'text': "@pragma('$name') annotation does not take options"});
}
disableFinal = true;
}
}
}
}
if (hasTryInline && hasNoInline) {
reporter.reportErrorMessage(element, MessageKind.GENERIC,
{'text': '@tryInline must not be used with @noInline.'});
hasTryInline = false;
}
if (hasNoInline) {
values.add(PragmaAnnotation.noInline);
annotationsDataBuilder.markAsNonInlinable(method);
}
if (hasTryInline) {
values.add(PragmaAnnotation.tryInline);
annotationsDataBuilder.markAsTryInline(method);
}
if (disableFinal) {
values.add(PragmaAnnotation.disableFinal);
annotationsDataBuilder.markAsDisableFinal(method);
}
if (hasNoThrows && !hasNoInline) {
reporter.internalError(
method, "@NoThrows() should always be combined with @noInline.");
}
if (hasNoSideEffects && !hasNoInline) {
reporter.internalError(
method, "@NoSideEffects() should always be combined with @noInline.");
}
return values;
}
abstract class AnnotationsData {
/// Deserializes a [AnnotationsData] object from [source].
factory AnnotationsData.readFromDataSource(DataSource source) =
AnnotationsDataImpl.readFromDataSource;
/// Serializes this [AnnotationsData] to [sink].
void writeToDataSink(DataSink sink);
/// Functions with a `@NoInline()`, `@noInline`, or
/// `@pragma('dart2js:noInline')` annotation.
Iterable<FunctionEntity> get nonInlinableFunctions;
/// Functions with a `@ForceInline()`, `@tryInline`, or
/// `@pragma('dart2js:tryInline')` annotation.
Iterable<FunctionEntity> get tryInlineFunctions;
/// Functions with a `@pragma('dart2js:disable-final')` annotation.
Iterable<FunctionEntity> get disableFinalFunctions;
/// Functions with a `@NoThrows()` annotation.
Iterable<FunctionEntity> get cannotThrowFunctions;
/// Functions with a `@NoSideEffects()` annotation.
Iterable<FunctionEntity> get sideEffectFreeFunctions;
/// Members with a `@AssumeDynamic()` annotation.
Iterable<MemberEntity> get assumeDynamicMembers;
}
class AnnotationsDataImpl implements AnnotationsData {
/// Tag used for identifying serialized [AnnotationsData] objects in a
/// debugging data stream.
static const String tag = 'annotations-data';
final Iterable<FunctionEntity> nonInlinableFunctions;
final Iterable<FunctionEntity> tryInlineFunctions;
final Iterable<FunctionEntity> disableFinalFunctions;
final Iterable<FunctionEntity> cannotThrowFunctions;
final Iterable<FunctionEntity> sideEffectFreeFunctions;
final Iterable<MemberEntity> assumeDynamicMembers;
AnnotationsDataImpl(
this.nonInlinableFunctions,
this.tryInlineFunctions,
this.disableFinalFunctions,
this.cannotThrowFunctions,
this.sideEffectFreeFunctions,
this.assumeDynamicMembers);
factory AnnotationsDataImpl.readFromDataSource(DataSource source) {
source.begin(tag);
Iterable<FunctionEntity> nonInlinableFunctions =
source.readMembers<FunctionEntity>(emptyAsNull: true) ??
const <FunctionEntity>[];
Iterable<FunctionEntity> tryInlineFunctions =
source.readMembers<FunctionEntity>(emptyAsNull: true) ??
const <FunctionEntity>[];
Iterable<FunctionEntity> disableFinalFunctions =
source.readMembers<FunctionEntity>(emptyAsNull: true) ??
const <FunctionEntity>[];
Iterable<FunctionEntity> cannotThrowFunctions =
source.readMembers<FunctionEntity>(emptyAsNull: true) ??
const <FunctionEntity>[];
Iterable<FunctionEntity> sideEffectFreeFunctions =
source.readMembers<FunctionEntity>(emptyAsNull: true) ??
const <FunctionEntity>[];
Iterable<MemberEntity> assumeDynamicMembers =
source.readMembers<MemberEntity>(emptyAsNull: true) ??
const <MemberEntity>[];
source.end(tag);
return new AnnotationsDataImpl(
nonInlinableFunctions,
tryInlineFunctions,
disableFinalFunctions,
cannotThrowFunctions,
sideEffectFreeFunctions,
assumeDynamicMembers);
}
void writeToDataSink(DataSink sink) {
sink.begin(tag);
sink.writeMembers(nonInlinableFunctions);
sink.writeMembers(tryInlineFunctions);
sink.writeMembers(disableFinalFunctions);
sink.writeMembers(cannotThrowFunctions);
sink.writeMembers(sideEffectFreeFunctions);
sink.writeMembers(assumeDynamicMembers);
sink.end(tag);
}
}
class AnnotationsDataBuilder implements AnnotationsData {
List<FunctionEntity> _nonInlinableFunctions;
List<FunctionEntity> _tryInlinableFunctions;
List<FunctionEntity> _disableFinalFunctions;
List<FunctionEntity> _cannotThrowFunctions;
List<FunctionEntity> _sideEffectFreeFunctions;
List<MemberEntity> _trustTypeAnnotationsMembers;
List<MemberEntity> _assumeDynamicMembers;
void markAsNonInlinable(FunctionEntity function) {
_nonInlinableFunctions ??= <FunctionEntity>[];
_nonInlinableFunctions.add(function);
}
void markAsTryInline(FunctionEntity function) {
_tryInlinableFunctions ??= <FunctionEntity>[];
_tryInlinableFunctions.add(function);
}
void markAsDisableFinal(FunctionEntity function) {
_disableFinalFunctions ??= <FunctionEntity>[];
_disableFinalFunctions.add(function);
}
void registerCannotThrow(FunctionEntity function) {
_cannotThrowFunctions ??= <FunctionEntity>[];
_cannotThrowFunctions.add(function);
}
void registerSideEffectsFree(FunctionEntity function) {
_sideEffectFreeFunctions ??= <FunctionEntity>[];
_sideEffectFreeFunctions.add(function);
}
void registerAssumeDynamic(MemberEntity member) {
_assumeDynamicMembers ??= <MemberEntity>[];
_assumeDynamicMembers.add(member);
}
Iterable<FunctionEntity> get nonInlinableFunctions =>
_nonInlinableFunctions ?? const <FunctionEntity>[];
Iterable<FunctionEntity> get tryInlineFunctions =>
_tryInlinableFunctions ?? const <FunctionEntity>[];
Iterable<FunctionEntity> get disableFinalFunctions =>
_disableFinalFunctions ?? const <FunctionEntity>[];
Iterable<FunctionEntity> get cannotThrowFunctions =>
_cannotThrowFunctions ?? const <FunctionEntity>[];
Iterable<FunctionEntity> get sideEffectFreeFunctions =>
_sideEffectFreeFunctions ?? const <FunctionEntity>[];
Iterable<MemberEntity> get trustTypeAnnotationsMembers =>
_trustTypeAnnotationsMembers ?? const <MemberEntity>[];
Iterable<MemberEntity> get assumeDynamicMembers =>
_assumeDynamicMembers ?? const <MemberEntity>[];
void writeToDataSink(DataSink sink) {
throw new UnsupportedError('AnnotationsDataBuilder.writeToDataSink');
}
}