blob: 116f509f6e8098b48bfedc4de9db19bbe06ddd7d [file] [log] [blame] [edit]
// 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.
/// Handling of native code and entry points.
library;
import 'dart:core' hide Type;
import 'package:kernel/ast.dart';
import 'package:kernel/library_index.dart' show LibraryIndex;
import 'calls.dart';
import 'types.dart';
import '../pragma.dart';
abstract class EntryPointsListener {
/// Add call by the given selector with arbitrary ('raw') arguments.
void addRawCall(Selector selector);
/// Sets the type of the given field.
void addFieldUsedInConstant(Field field, Type instance, Type value);
/// Add instantiation of the given class.
ConcreteType addAllocatedClass(Class c);
/// Returns Field representing positional field of a record with given shape.
Field getRecordPositionalField(RecordShape shape, int pos);
/// Returns Field representing named field of a record with given shape.
Field getRecordNamedField(RecordShape shape, String name);
/// Record the fact that given member is called via interface selector
/// (not dynamically, and not from `this`).
void recordMemberCalledViaInterfaceSelector(Member target);
/// Record the fact that given member is called from this.
void recordMemberCalledViaThis(Member target);
/// Record the fact that given member is torn off.
void recordTearOff(Member target) {}
/// Artificial call method corresponding to the given [closure].
Procedure getClosureCallMethod(Closure closure);
/// Add class which can be extended by a dynamically loaded class
/// (unknown at compilation time).
void addDynamicallyExtendableClass(Class c);
}
class PragmaEntryPointsVisitor extends RecursiveVisitor {
final EntryPointsListener entryPoints;
final NativeCodeOracle nativeCodeOracle;
final PragmaAnnotationParser matcher;
PragmaEntryPointsVisitor(
this.entryPoints, this.nativeCodeOracle, this.matcher);
// Returns list of entry point types specified by
// pragmas in the given annotations.
List<PragmaEntryPointType> entryPointTypesFromPragmas(
List<Expression> annotations) {
List<PragmaEntryPointType>? types;
for (var annotation in annotations) {
ParsedPragma? pragma = matcher.parsePragma(annotation);
if (pragma == null) continue;
if (pragma is ParsedEntryPointPragma) {
if (types == null) {
types = [pragma.type];
} else {
// Duplicate entry point types are rare and harmless.
types.add(pragma.type);
}
}
}
return types ?? const [];
}
static const _referenceToDocumentation =
"See https://github.com/dart-lang/sdk/blob/master/runtime/docs/compiler/"
"aot/entry_point_pragma.md.";
@override
visitLibrary(Library library) {
for (final type in entryPointTypesFromPragmas(library.annotations)) {
if (type == PragmaEntryPointType.Default) {
nativeCodeOracle.addLibraryReferencedFromNativeCode(library);
} else {
throw "Error: The argument to an entry-point pragma annotation "
"on a library must evaluate to null, true, or false.\n"
"$_referenceToDocumentation";
}
}
library.visitChildren(this);
}
@override
visitClass(Class klass) {
for (final type in entryPointTypesFromPragmas(klass.annotations)) {
if (type == PragmaEntryPointType.Default) {
if (!klass.isAbstract) {
entryPoints.addAllocatedClass(klass);
}
nativeCodeOracle.addClassReferencedFromNativeCode(klass);
} else if (type == PragmaEntryPointType.Extendable) {
entryPoints.addDynamicallyExtendableClass(klass);
nativeCodeOracle.addClassReferencedFromNativeCode(klass);
} else {
throw "Error: The argument to an entry-point pragma annotation "
"on a class must evaluate to null, true, or false.\n"
"$_referenceToDocumentation";
}
}
klass.visitChildren(this);
}
@override
visitProcedure(Procedure proc) {
final types = entryPointTypesFromPragmas(proc.annotations);
if (types.isEmpty) return;
void addSelector(CallKind ck) {
entryPoints.addRawCall(proc.isInstanceMember
? new InterfaceSelector(proc, callKind: ck)
: new DirectSelector(proc, callKind: ck));
}
for (final type in types) {
switch (type) {
case PragmaEntryPointType.CallOnly:
if (proc.isGetter) {
throw "Error: The argument to an entry-point pragma annotation on "
"a getter ($proc) must evaluate to null, true, false, or "
"'get'.\n$_referenceToDocumentation";
}
if (proc.isSetter) {
throw "Error: The argument to an entry-point pragma annotation on "
"a setter ($proc) must evaluate to null, true, false, or "
"'set'.\n$_referenceToDocumentation";
}
addSelector(CallKind.Method);
break;
case PragmaEntryPointType.SetterOnly:
if (!proc.isSetter) {
throw "Error: cannot generate a setter for a method or getter "
"($proc).\n$_referenceToDocumentation";
}
addSelector(CallKind.PropertySet);
break;
case PragmaEntryPointType.GetterOnly:
if (proc.isSetter) {
throw "Error: cannot closurize a setter ($proc).\n"
"$_referenceToDocumentation";
}
if (proc.isFactory) {
throw "Error: cannot closurize a factory ($proc).\n"
"$_referenceToDocumentation";
}
addSelector(CallKind.PropertyGet);
break;
case PragmaEntryPointType.Default:
if (proc.isGetter) {
addSelector(CallKind.PropertyGet);
} else if (proc.isSetter) {
addSelector(CallKind.PropertySet);
} else {
addSelector(CallKind.Method);
if (!proc.isFactory) {
addSelector(CallKind.PropertyGet);
}
}
break;
case PragmaEntryPointType.Extendable:
throw "Error: only class can be extendable";
case PragmaEntryPointType.CanBeOverridden:
nativeCodeOracle.addDynamicallyOverriddenMember(proc);
break;
}
}
nativeCodeOracle.setMemberReferencedFromNativeCode(proc);
}
@override
visitConstructor(Constructor ctor) {
for (final type in entryPointTypesFromPragmas(ctor.annotations)) {
if (type != PragmaEntryPointType.Default &&
type != PragmaEntryPointType.CallOnly) {
throw "Error: The argument to an entry-point pragma annotation on a "
"constructor ($ctor) must evaluate to null, true, false or "
"'call'.\n$_referenceToDocumentation";
}
entryPoints
.addRawCall(new DirectSelector(ctor, callKind: CallKind.Method));
entryPoints.addAllocatedClass(ctor.enclosingClass);
nativeCodeOracle.setMemberReferencedFromNativeCode(ctor);
}
}
@override
visitField(Field field) {
final types = entryPointTypesFromPragmas(field.annotations);
if (types.isEmpty) return;
void addSelector(CallKind ck) {
entryPoints.addRawCall(field.isInstanceMember
? new InterfaceSelector(field, callKind: ck)
: new DirectSelector(field, callKind: ck));
}
for (final type in types) {
switch (type) {
case PragmaEntryPointType.GetterOnly:
addSelector(CallKind.PropertyGet);
break;
case PragmaEntryPointType.SetterOnly:
if (!field.hasSetter) {
throw "Error: can't use 'set' in an entry-point pragma annotation "
"for a field that has no setter ($field).\n"
"$_referenceToDocumentation";
}
addSelector(CallKind.PropertySet);
break;
case PragmaEntryPointType.Default:
addSelector(CallKind.PropertyGet);
if (field.hasSetter) {
addSelector(CallKind.PropertySet);
}
break;
case PragmaEntryPointType.CallOnly:
throw "Error: 'call' is not a valid entry-point pragma annotation "
"argument for the field $field.\n$_referenceToDocumentation";
case PragmaEntryPointType.Extendable:
throw "Error: only class can be extendable";
case PragmaEntryPointType.CanBeOverridden:
nativeCodeOracle.addDynamicallyOverriddenMember(field);
break;
}
}
nativeCodeOracle.setMemberReferencedFromNativeCode(field);
}
}
/// Provides insights into the behavior of native code.
class NativeCodeOracle {
final LibraryIndex _libraryIndex;
final Set<Member> _membersReferencedFromNativeCode = new Set<Member>();
final Set<Member> _dynamicallyOverriddenMembers = new Set<Member>();
final Set<Class> _classesReferencedFromNativeCode = new Set<Class>();
final Set<Library> _librariesReferencedFromNativeCode = new Set<Library>();
final PragmaAnnotationParser _matcher;
NativeCodeOracle(this._libraryIndex, this._matcher);
void addLibraryReferencedFromNativeCode(Library library) {
_librariesReferencedFromNativeCode.add(library);
}
bool isLibraryReferencedFromNativeCode(Library library) =>
_librariesReferencedFromNativeCode.contains(library);
void addClassReferencedFromNativeCode(Class klass) {
_classesReferencedFromNativeCode.add(klass);
}
bool isClassReferencedFromNativeCode(Class klass) =>
_classesReferencedFromNativeCode.contains(klass);
void setMemberReferencedFromNativeCode(Member member) {
_membersReferencedFromNativeCode.add(member);
}
bool isMemberReferencedFromNativeCode(Member member) =>
_membersReferencedFromNativeCode.contains(member);
void addDynamicallyOverriddenMember(Member member) {
_dynamicallyOverriddenMembers.add(member);
}
bool isDynamicallyOverriddenMember(Member member) =>
_dynamicallyOverriddenMembers.contains(member);
PragmaRecognizedType? recognizedType(Member member) {
for (var annotation in member.annotations) {
ParsedPragma? pragma = _matcher.parsePragma(annotation);
if (pragma is ParsedRecognized) {
return pragma.type;
}
}
return null;
}
bool isRecognized(Member member,
[List<PragmaRecognizedType>? expectedTypes]) {
PragmaRecognizedType? type = recognizedType(member);
return type != null &&
(expectedTypes == null || expectedTypes.contains(type));
}
bool hasDisableUnboxedParameters(Member member) {
for (var annotation in member.annotations) {
ParsedPragma? pragma = _matcher.parsePragma(annotation);
if (pragma is ParsedDisableUnboxedParameters) {
if (!member.enclosingLibrary.importUri.isScheme("dart")) {
throw "ERROR: Cannot use @pragma(vm:disable-unboxed-parameters) outside core libraries.";
}
return true;
}
}
return false;
}
/// Simulate the execution of a native method by adding its entry points
/// using [entryPointsListener]. Returns result type of the native method.
TypeExpr handleNativeProcedure(
Member member,
EntryPointsListener entryPointsListener,
TypesBuilder typesBuilder,
RuntimeTypeTranslator translator) {
TypeExpr? returnType = null;
for (var annotation in member.annotations) {
ParsedPragma? pragma = _matcher.parsePragma(annotation);
if (pragma == null) continue;
if (pragma is ParsedResultTypeByTypePragma ||
pragma is ParsedResultTypeByPathPragma) {
// We can only use the 'vm:exact-result-type' pragma on methods in core
// libraries for safety reasons. See 'result_type_pragma.md', detail 1.2
// for explanation.
if (!member.enclosingLibrary.importUri.isScheme("dart")) {
throw "ERROR: Cannot use $kVmExactResultTypePragmaName "
"outside core libraries.";
}
}
if (pragma is ParsedResultTypeByTypePragma) {
var type = pragma.type;
if (type is InterfaceType) {
returnType = entryPointsListener.addAllocatedClass(type.classNode);
if (pragma.resultTypeUsesPassedTypeArguments) {
returnType = translator.instantiateConcreteType(
returnType as ConcreteType,
member.function!.typeParameters
.map((t) => TypeParameterType.withDefaultNullability(t))
.toList());
}
continue;
}
throw "ERROR: Invalid return type for native method: ${pragma.type}";
} else if (pragma is ParsedResultTypeByPathPragma) {
List<String> parts = pragma.path.split("#");
if (parts.length != 2) {
throw "ERROR: Could not parse native method return type: ${pragma.path}";
}
String libName = parts[0];
String klassName = parts[1];
// Error is thrown on the next line if the class is not found.
Class klass = _libraryIndex.getClass(libName, klassName);
Type concreteClass = entryPointsListener.addAllocatedClass(klass);
returnType = concreteClass;
}
}
if (returnType != null) {
return returnType;
} else {
return typesBuilder.fromStaticType(member.function!.returnType, true);
}
}
}