blob: 7655b13a79a6a7b655d79931f5211bfd0c3cecea [file] [log] [blame]
// Copyright (c) 2013, 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 dart2js.js_emitter.full_emitter.class_emitter;
import '../../common.dart';
import '../../common/names.dart' show Names;
import '../../common_elements.dart';
import '../../elements/resolution_types.dart' show ResolutionDartType;
import '../../deferred_load.dart' show OutputUnit;
import '../../elements/elements.dart' show ClassElement, FieldElement;
import '../../elements/entities.dart';
import '../../js/js.dart' as jsAst;
import '../../js/js.dart' show js;
import '../../js_backend/js_backend.dart' show CompoundName, Namer;
import '../../universe/selector.dart' show Selector;
import '../../util/util.dart' show equalElements;
import '../../world.dart' show ClosedWorld;
import '../js_emitter.dart' hide Emitter, EmitterFactory;
import '../model.dart';
import 'emitter.dart';
class ClassEmitter extends CodeEmitterHelper {
final ClosedWorld closedWorld;
ClassEmitter(this.closedWorld);
ClassStubGenerator get _stubGenerator => new ClassStubGenerator(task.emitter,
closedWorld.commonElements, namer, codegenWorldBuilder, closedWorld,
enableMinification: compiler.options.enableMinification);
ElementEnvironment get _elementEnvironment => closedWorld.elementEnvironment;
/**
* Documentation wanted -- johnniwinther
*/
void emitClass(Class cls, ClassBuilder enclosingBuilder, Fragment fragment) {
ClassEntity classElement = cls.element;
assert(!(classElement is ClassElement && !classElement.isDeclaration),
failedAt(classElement));
emitter.needsClassSupport = true;
ClassEntity superclass = _elementEnvironment.getSuperClass(classElement);
jsAst.Name superName;
if (superclass != null) {
superName = namer.className(superclass);
}
if (cls.isMixinApplication) {
MixinApplication mixinApplication = cls;
jsAst.Name mixinName = mixinApplication.mixinClass.name;
superName = new CompoundName([superName, Namer.literalPlus, mixinName]);
emitter.needsMixinSupport = true;
}
ClassBuilder builder = new ClassBuilder.forClass(classElement, namer);
builder.superName = superName;
emitConstructorsForCSP(cls);
emitFields(cls, builder);
if (cls.hasRtiField) {
builder.addField(namer.rtiFieldJsName);
}
emitCheckedClassSetters(cls, builder);
emitClassGettersSettersForCSP(cls, builder);
emitInstanceMembers(cls, builder);
emitStubs(cls.callStubs, builder);
emitRuntimeTypeInformation(cls, builder);
emitNativeInfo(cls, builder);
if (classElement == closedWorld.commonElements.closureClass) {
// We add a special getter here to allow for tearing off a closure from
// itself.
jsAst.Fun function = js('function() { return this; }');
jsAst.Name name = namer.getterForMember(Names.call);
builder.addProperty(name, function);
}
emitClassBuilderWithReflectionData(
cls, builder, enclosingBuilder, fragment);
}
/**
* Emits the precompiled constructor when in CSP mode.
*/
void emitConstructorsForCSP(Class cls) {
List<jsAst.Name> fieldNames = <jsAst.Name>[];
if (!compiler.options.useContentSecurityPolicy) return;
if (!cls.onlyForRti && !cls.isNative) {
fieldNames = cls.fields.map((Field field) => field.name).toList();
}
ClassEntity classElement = cls.element;
jsAst.Expression constructorAst = _stubGenerator.generateClassConstructor(
classElement, fieldNames, cls.hasRtiField);
jsAst.Name constructorName = namer.className(classElement);
OutputUnit outputUnit =
compiler.deferredLoadTask.outputUnitForEntity(classElement);
emitter.assemblePrecompiledConstructor(
outputUnit, constructorName, constructorAst, fieldNames);
}
/// Returns `true` if fields added.
bool emitFields(FieldContainer container, ClassBuilder builder,
{bool classIsNative: false, bool emitStatics: false}) {
Iterable<Field> fields;
if (container is Class) {
if (emitStatics) {
fields = container.staticFieldsForReflection;
} else if (container.onlyForRti) {
return false;
} else {
fields = container.fields;
}
} else {
assert(container is Library);
assert(emitStatics);
fields = container.staticFieldsForReflection;
}
var fieldMetadata = <jsAst.Expression>[];
bool hasMetadata = false;
bool fieldsAdded = false;
for (Field field in fields) {
FieldEntity fieldElement = field.element;
jsAst.Name name = field.name;
jsAst.Name accessorName = field.accessorName;
bool needsGetter = field.needsGetter;
bool needsSetter = field.needsUncheckedSetter;
// Ignore needsCheckedSetter - that is handled below.
bool needsAccessor = (needsGetter || needsSetter);
// We need to output the fields for non-native classes so we can auto-
// generate the constructor. For native classes there are no
// constructors, so we don't need the fields unless we are generating
// accessors at runtime.
bool needsFieldsForConstructor = !emitStatics && !classIsNative;
if (needsFieldsForConstructor || needsAccessor) {
dynamic metadata =
task.metadataCollector.buildFieldMetadataFunction(fieldElement);
if (metadata != null) {
hasMetadata = true;
} else {
metadata = new jsAst.LiteralNull();
}
fieldMetadata.add(metadata);
recordMangledField(fieldElement, accessorName,
namer.privateName(fieldElement.memberName));
List<jsAst.Literal> fieldNameParts = <jsAst.Literal>[];
if (!needsAccessor) {
// Emit field for constructor generation.
assert(!classIsNative);
fieldNameParts.add(name);
} else {
// Emit (possibly renaming) field name so we can add accessors at
// runtime.
if (name != accessorName) {
fieldNameParts.add(accessorName);
fieldNameParts.add(js.stringPart(':'));
}
fieldNameParts.add(name);
if (field.needsInterceptedGetter) {
emitter.interceptorEmitter.interceptorInvocationNames
.add(namer.getterForElement(fieldElement));
}
// TODO(16168): The setter creator only looks at the getter-name.
// Even though the setter could avoid the interceptor convention we
// currently still need to add the additional argument.
if (field.needsInterceptedGetter || field.needsInterceptedSetter) {
emitter.interceptorEmitter.interceptorInvocationNames
.add(namer.setterForMember(fieldElement));
}
int code = field.getterFlags + (field.setterFlags << 2);
if (code == 0) {
reporter.internalError(
fieldElement, 'Field code is 0 ($fieldElement).');
}
fieldNameParts.add(
js.stringPart(FIELD_CODE_CHARACTERS[code - FIRST_FIELD_CODE]));
}
// Fields can only be reflected if their declaring class is reflectable
// (as they are only accessible via [ClassMirror.declarations]).
// However, set/get operations can be performed on them, so they are
// reflectable in some sense, which leads to [isAccessibleByReflection]
// reporting `true`.
if (backend.mirrorsData.isMemberAccessibleByReflection(fieldElement)) {
fieldNameParts.add(new jsAst.LiteralString('-'));
if (fieldElement.isTopLevel ||
backend.mirrorsData
.isClassAccessibleByReflection(fieldElement.enclosingClass)) {
// TODO(redemption): Support field entities.
FieldElement element = fieldElement;
ResolutionDartType type = element.type;
fieldNameParts.add(task.metadataCollector.reifyType(type));
}
}
jsAst.Literal fieldNameAst = js.concatenateStrings(fieldNameParts);
builder.addField(fieldNameAst);
// Add 1 because adding a field to the class also requires a comma
compiler.dumpInfoTask.registerEntityAst(fieldElement, fieldNameAst);
fieldsAdded = true;
}
}
if (hasMetadata) {
builder.fieldMetadata = fieldMetadata;
}
return fieldsAdded;
}
/// Emits checked setters for fields.
void emitCheckedClassSetters(Class cls, ClassBuilder builder) {
if (cls.onlyForRti) return;
for (StubMethod method in cls.checkedSetters) {
MemberEntity member = method.element;
assert(member != null);
jsAst.Expression code = method.code;
jsAst.Name setterName = method.name;
compiler.dumpInfoTask
.registerEntityAst(member, builder.addProperty(setterName, code));
generateReflectionDataForFieldGetterOrSetter(member, setterName, builder,
isGetter: false);
}
}
/// Emits getters/setters for fields if compiling in CSP mode.
void emitClassGettersSettersForCSP(Class cls, ClassBuilder builder) {
if (!compiler.options.useContentSecurityPolicy || cls.onlyForRti) return;
for (Field field in cls.fields) {
FieldEntity member = field.element;
reporter.withCurrentElement(member, () {
if (field.needsGetter) {
emitGetterForCSP(member, field.name, field.accessorName, builder);
}
if (field.needsUncheckedSetter) {
emitSetterForCSP(member, field.name, field.accessorName, builder);
}
});
}
}
void emitStubs(Iterable<StubMethod> stubs, ClassBuilder builder) {
for (Method method in stubs) {
jsAst.Property property = builder.addProperty(method.name, method.code);
compiler.dumpInfoTask.registerEntityAst(method.element, property);
}
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [classElement] must be a declaration element.
*/
void emitInstanceMembers(Class cls, ClassBuilder builder) {
ClassEntity classElement = cls.element;
assert(!(classElement is ClassElement && !classElement.isDeclaration),
failedAt(classElement));
if (cls.onlyForRti || cls.isMixinApplication) return;
// TODO(herhut): This is a no-op. Should it be removed?
for (Field field in cls.fields) {
emitter.containerBuilder.addMemberField(field, builder);
}
for (Method method in cls.methods) {
assert(method.element.isInstanceMember, failedAt(classElement));
emitter.containerBuilder.addMemberMethod(method, builder);
}
if (classElement == closedWorld.commonElements.objectClass &&
closedWorld.backendUsage.isNoSuchMethodUsed) {
// Emit the noSuchMethod handlers on the Object prototype now,
// so that the code in the dynamicFunction helper can find
// them. Note that this helper is invoked before analyzing the
// full JS script.
emitter.nsmEmitter.emitNoSuchMethodHandlers(builder.addProperty);
}
}
/// Emits the members from the model.
void emitRuntimeTypeInformation(Class cls, ClassBuilder builder) {
assert(builder.functionType == null);
if (cls.functionTypeIndex != null) {
builder.functionType = cls.functionTypeIndex;
}
for (Method method in cls.isChecks) {
builder.addProperty(method.name, method.code);
}
}
void emitNativeInfo(Class cls, ClassBuilder builder) {
jsAst.Expression nativeInfo = NativeGenerator.encodeNativeInfo(cls);
if (nativeInfo != null) {
builder.addPropertyByName(namer.nativeSpecProperty, nativeInfo);
}
}
void emitClassBuilderWithReflectionData(Class cls, ClassBuilder classBuilder,
ClassBuilder enclosingBuilder, Fragment fragment) {
ClassEntity classEntity = cls.element;
jsAst.Name className = cls.name;
var metadata =
task.metadataCollector.buildClassMetadataFunction(classEntity);
if (metadata != null) {
classBuilder.addPropertyByName("@", metadata);
}
if (backend.mirrorsData.isClassAccessibleByReflection(classEntity)) {
// TODO(redemption): Handle class entities.
ClassElement classElement = classEntity;
List<ResolutionDartType> typeVars = classElement.typeVariables;
Iterable typeVariableProperties =
emitter.typeVariableCodegenAnalysis.typeVariablesOf(classElement);
ClassElement superclass = classElement.superclass;
bool hasSuper = superclass != null;
if ((!typeVariableProperties.isEmpty && !hasSuper) ||
(hasSuper && !equalElements(superclass.typeVariables, typeVars))) {
classBuilder.addPropertyByName(
'<>', new jsAst.ArrayInitializer(typeVariableProperties.toList()));
}
}
List<jsAst.Property> statics = new List<jsAst.Property>();
ClassBuilder staticsBuilder =
new ClassBuilder.forStatics(classEntity, namer);
if (emitFields(cls, staticsBuilder, emitStatics: true)) {
jsAst.ObjectInitializer initializer =
staticsBuilder.toObjectInitializer();
compiler.dumpInfoTask.registerEntityAst(classEntity, initializer);
jsAst.Node property = initializer.properties.single;
compiler.dumpInfoTask.registerEntityAst(classEntity, property);
statics.add(property);
}
// TODO(herhut): Do not grab statics out of the properties.
ClassBuilder classProperties =
emitter.classDescriptors[fragment]?.remove(classEntity);
if (classProperties != null) {
statics.addAll(classProperties.properties);
}
if (!statics.isEmpty) {
classBuilder.addProperty(
namer.staticsPropertyName, // 'static' or its minified name.
new jsAst.ObjectInitializer(statics, isOneLiner: false));
}
// TODO(ahe): This method (generateClass) should return a jsAst.Expression.
jsAst.ObjectInitializer propertyValue = classBuilder.toObjectInitializer();
compiler.dumpInfoTask
.registerEntityAst(classBuilder.element, propertyValue);
enclosingBuilder.addProperty(className, propertyValue);
String reflectionName =
emitter.getReflectionClassName(classEntity, className);
if (reflectionName != null) {
if (!backend.mirrorsData.isClassAccessibleByReflection(classEntity) ||
cls.onlyForRti) {
// TODO(herhut): Fix use of reflection name here.
enclosingBuilder.addPropertyByName("+$reflectionName", js.number(0));
} else {
// TODO(redemption): Handle class entities.
ClassElement classElement = classEntity;
List<jsAst.Expression> types = <jsAst.Expression>[];
if (classElement.supertype != null) {
types.add(task.metadataCollector.reifyType(classElement.supertype));
}
for (ResolutionDartType interface in classElement.interfaces) {
types.add(task.metadataCollector.reifyType(interface));
}
// TODO(herhut): Fix use of reflection name here.
enclosingBuilder.addPropertyByName(
"+$reflectionName", new jsAst.ArrayInitializer(types));
}
}
}
void recordMangledField(
FieldEntity member, jsAst.Name accessorName, String memberName) {
if (!backend.mirrorsData.shouldRetainGetter(member)) return;
String previousName;
if (member.isInstanceMember) {
previousName = emitter.mangledFieldNames
.putIfAbsent(namer.deriveGetterName(accessorName), () => memberName);
} else {
previousName = emitter.mangledGlobalFieldNames
.putIfAbsent(accessorName, () => memberName);
}
assert(previousName == memberName,
failedAt(member, '$previousName != ${memberName}'));
}
void emitGetterForCSP(FieldEntity member, jsAst.Name fieldName,
jsAst.Name accessorName, ClassBuilder builder) {
jsAst.Expression function =
_stubGenerator.generateGetter(member, fieldName);
jsAst.Name getterName = namer.deriveGetterName(accessorName);
ClassEntity cls = member.enclosingClass;
jsAst.Name className = namer.className(cls);
OutputUnit outputUnit =
compiler.deferredLoadTask.outputUnitForEntity(member);
emitter
.cspPrecompiledFunctionFor(outputUnit)
.add(js('#.prototype.# = #', [className, getterName, function]));
if (backend.mirrorsData.isMemberAccessibleByReflection(member)) {
emitter.cspPrecompiledFunctionFor(outputUnit).add(js(
'#.prototype.#.${namer.reflectableField} = 1',
[className, getterName]));
}
}
void emitSetterForCSP(FieldEntity member, jsAst.Name fieldName,
jsAst.Name accessorName, ClassBuilder builder) {
jsAst.Expression function =
_stubGenerator.generateSetter(member, fieldName);
jsAst.Name setterName = namer.deriveSetterName(accessorName);
ClassEntity cls = member.enclosingClass;
jsAst.Name className = namer.className(cls);
OutputUnit outputUnit =
compiler.deferredLoadTask.outputUnitForEntity(member);
emitter
.cspPrecompiledFunctionFor(outputUnit)
.add(js('#.prototype.# = #', [className, setterName, function]));
if (backend.mirrorsData.isMemberAccessibleByReflection(member)) {
emitter.cspPrecompiledFunctionFor(outputUnit).add(js(
'#.prototype.#.${namer.reflectableField} = 1',
[className, setterName]));
}
}
void generateReflectionDataForFieldGetterOrSetter(
MemberEntity member, jsAst.Name name, ClassBuilder builder,
{bool isGetter}) {
Selector selector = isGetter
? new Selector.getter(member.memberName.getter)
: new Selector.setter(member.memberName.setter);
String reflectionName = emitter.getReflectionSelectorName(selector, name);
if (reflectionName != null) {
var reflectable = js(
backend.mirrorsData.isMemberAccessibleByReflection(member)
? '1'
: '0');
builder.addPropertyByName('+$reflectionName', reflectable);
}
}
}