blob: 0193ba25b95472978af13d76329126726cdc2a3a [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.
part of dart2js.js_emitter;
class ClassEmitter extends CodeEmitterHelper {
ClassStubGenerator get _stubGenerator =>
new ClassStubGenerator(compiler, namer, backend);
/**
* Documentation wanted -- johnniwinther
*/
void emitClass(Class cls, ClassBuilder enclosingBuilder) {
ClassElement classElement = cls.element;
assert(invariant(classElement, classElement.isDeclaration));
emitter.needsClassSupport = true;
ClassElement superclass = classElement.superclass;
String superName = "";
if (superclass != null) {
superName = namer.getNameOfClass(superclass);
}
if (cls.isMixinApplication) {
MixinApplication mixinApplication = cls;
String mixinName = mixinApplication.mixinClass.name;
superName = '$superName+$mixinName';
emitter.needsMixinSupport = true;
}
ClassBuilder builder = new ClassBuilder(classElement, namer);
builder.superName = superName;
emitConstructorsForCSP(cls);
emitFields(cls, builder);
emitCheckedClassSetters(cls, builder);
emitClassGettersSettersForCSP(cls, builder);
emitInstanceMembers(cls, builder);
emitStubs(cls.callStubs, builder);
emitStubs(cls.typeVariableReaderStubs, builder);
emitRuntimeTypeInformation(cls, builder);
emitNativeInfo(cls, builder);
if (classElement == backend.closureClass) {
// We add a special getter here to allow for tearing off a closure from
// itself.
String name = namer.getMappedInstanceName(Compiler.CALL_OPERATOR_NAME);
jsAst.Fun function = js('function() { return this; }');
builder.addProperty(namer.getterNameFromAccessorName(name), function);
}
emitClassBuilderWithReflectionData(cls, builder, enclosingBuilder);
}
/**
* Emits the precompiled constructor when in CSP mode.
*/
void emitConstructorsForCSP(Class cls) {
List<String> fieldNames = <String>[];
if (!compiler.useContentSecurityPolicy) return;
if (!cls.onlyForRti && !cls.isNative) {
fieldNames = cls.fields.map((Field field) => field.name).toList();
}
ClassElement classElement = cls.element;
jsAst.Expression constructorAst =
_stubGenerator.generateClassConstructor(classElement, fieldNames);
String constructorName = namer.getNameOfClass(classElement);
OutputUnit outputUnit =
compiler.deferredLoadTask.outputUnitForElement(classElement);
emitter.emitPrecompiledConstructor(
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 = [];
bool hasMetadata = false;
bool fieldsAdded = false;
for (Field field in fields) {
VariableElement fieldElement = field.element;
String name = field.name;
String 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) {
var metadata =
task.metadataCollector.buildMetadataFunction(fieldElement);
if (metadata != null) {
hasMetadata = true;
} else {
metadata = new jsAst.LiteralNull();
}
fieldMetadata.add(metadata);
recordMangledField(fieldElement, accessorName,
namer.privateName(fieldElement.library, fieldElement.name));
String fieldName = name;
String fieldCode = '';
String reflectionMarker = '';
if (!needsAccessor) {
// Emit field for constructor generation.
assert(!classIsNative);
} else {
// Emit (possibly renaming) field name so we can add accessors at
// runtime.
if (name != accessorName) {
fieldName = '$accessorName:$name';
}
if (field.needsInterceptedGetter) {
emitter.interceptorEmitter.interceptorInvocationNames.add(
namer.getterName(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.setterName(fieldElement));
}
int code = field.getterFlags + (field.setterFlags << 2);
if (code == 0) {
compiler.internalError(fieldElement,
'Field code is 0 ($fieldElement).');
} else {
fieldCode = FIELD_CODE_CHARACTERS[code - FIRST_FIELD_CODE];
}
}
if (backend.isAccessibleByReflection(fieldElement)) {
DartType type = fieldElement.type;
reflectionMarker = '-${task.metadataCollector.reifyType(type)}';
}
String builtFieldname = '$fieldName$fieldCode$reflectionMarker';
builder.addField(builtFieldname);
// Add 1 because adding a field to the class also requires a comma
compiler.dumpInfoTask.recordFieldNameSize(fieldElement,
builtFieldname.length + 1);
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 (Field field in cls.fields) {
if (field.needsCheckedSetter) {
assert(!field.needsUncheckedSetter);
compiler.withCurrentElement(field.element, () {
generateCheckedSetter(
field.element, field.name, field.accessorName, builder);
});
}
}
}
/// Emits getters/setters for fields if compiling in CSP mode.
void emitClassGettersSettersForCSP(Class cls, ClassBuilder builder) {
if (!compiler.useContentSecurityPolicy || cls.onlyForRti) return;
for (Field field in cls.fields) {
Element member = field.element;
compiler.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.registerElementAst(method.element, property);
}
}
/**
* Documentation wanted -- johnniwinther
*
* Invariant: [classElement] must be a declaration element.
*/
void emitInstanceMembers(Class cls,
ClassBuilder builder) {
ClassElement classElement = cls.element;
assert(invariant(classElement, classElement.isDeclaration));
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(invariant(classElement, method.element.isDeclaration));
assert(invariant(classElement, method.element.isInstanceMember));
emitter.containerBuilder.addMemberMethod(method, builder);
}
if (identical(classElement, compiler.objectClass)
&& compiler.enabledNoSuchMethod) {
// 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.
if (!emitter.nativeEmitter.handleNoSuchMethod) {
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) {
if (cls.nativeInfo != null) {
builder.addProperty(namer.nativeSpecProperty, js.string(cls.nativeInfo));
}
}
void emitClassBuilderWithReflectionData(Class cls,
ClassBuilder classBuilder,
ClassBuilder enclosingBuilder) {
ClassElement classElement = cls.element;
String className = cls.name;
var metadata = task.metadataCollector.buildMetadataFunction(classElement);
if (metadata != null) {
classBuilder.addProperty("@", metadata);
}
if (backend.isAccessibleByReflection(classElement)) {
List<DartType> typeVars = classElement.typeVariables;
Iterable typeVariableProperties = emitter.typeVariableHandler
.typeVariablesOf(classElement).map(js.number);
ClassElement superclass = classElement.superclass;
bool hasSuper = superclass != null;
if ((!typeVariableProperties.isEmpty && !hasSuper) ||
(hasSuper && !equalElements(superclass.typeVariables, typeVars))) {
classBuilder.addProperty('<>',
new jsAst.ArrayInitializer(typeVariableProperties.toList()));
}
}
List<jsAst.Property> statics = new List<jsAst.Property>();
ClassBuilder staticsBuilder = new ClassBuilder(classElement, namer);
if (emitFields(cls, staticsBuilder, emitStatics: true)) {
jsAst.ObjectInitializer initializer =
staticsBuilder.toObjectInitializer();
compiler.dumpInfoTask.registerElementAst(classElement,
initializer);
jsAst.Node property = initializer.properties.single;
compiler.dumpInfoTask.registerElementAst(classElement, property);
statics.add(property);
}
ClassBuilder classProperties =
emitter.elementDescriptors.remove(classElement);
if (classProperties != null) {
statics.addAll(classProperties.properties);
}
if (!statics.isEmpty) {
classBuilder.addProperty('static', new jsAst.ObjectInitializer(statics));
}
// TODO(ahe): This method (generateClass) should return a jsAst.Expression.
jsAst.ObjectInitializer propertyValue = classBuilder.toObjectInitializer();
compiler.dumpInfoTask.registerElementAst(classBuilder.element, propertyValue);
enclosingBuilder.addProperty(className, propertyValue);
String reflectionName = emitter.getReflectionName(classElement, className);
if (reflectionName != null) {
if (!backend.isAccessibleByReflection(classElement)) {
enclosingBuilder.addProperty("+$reflectionName", js.number(0));
} else {
List<int> types = <int>[];
if (classElement.supertype != null) {
types.add(task.metadataCollector.reifyType(classElement.supertype));
}
for (DartType interface in classElement.interfaces) {
types.add(task.metadataCollector.reifyType(interface));
}
enclosingBuilder.addProperty("+$reflectionName",
new jsAst.ArrayInitializer(types.map(js.number).toList()));
}
}
}
/**
* Invokes [f] for each of the fields of [element].
*
* [element] must be a [ClassElement] or a [LibraryElement].
*
* If [element] is a [ClassElement], the static fields of the class are
* visited if [visitStatics] is true and the instance fields are visited if
* [visitStatics] is false.
*
* If [element] is a [LibraryElement], [visitStatics] must be true.
*
* When visiting the instance fields of a class, the fields of its superclass
* are also visited if the class is instantiated.
*
* Invariant: [element] must be a declaration element.
*/
void visitFields(Element element, bool visitStatics, AcceptField f) {
assert(invariant(element, element.isDeclaration));
bool isClass = false;
bool isLibrary = false;
if (element.isClass) {
isClass = true;
} else if (element.isLibrary) {
isLibrary = true;
assert(invariant(element, visitStatics));
} else {
throw new SpannableAssertionFailure(
element, 'Expected a ClassElement or a LibraryElement.');
}
// If the class is never instantiated we still need to set it up for
// inheritance purposes, but we can simplify its JavaScript constructor.
bool isInstantiated =
compiler.codegenWorld.directlyInstantiatedClasses.contains(element);
void visitField(Element holder, VariableElement field) {
assert(invariant(element, field.isDeclaration));
String name = field.name;
// Keep track of whether or not we're dealing with a field mixin
// into a native class.
bool isMixinNativeField =
isClass && element.isNative && holder.isMixinApplication;
// See if we can dynamically create getters and setters.
// We can only generate getters and setters for [element] since
// the fields of super classes could be overwritten with getters or
// setters.
bool needsGetter = false;
bool needsSetter = false;
if (isLibrary || isMixinNativeField || holder == element) {
needsGetter = fieldNeedsGetter(field);
needsSetter = fieldNeedsSetter(field);
}
if ((isInstantiated && !holder.isNative)
|| needsGetter
|| needsSetter) {
String accessorName = namer.fieldAccessorName(field);
String fieldName = namer.fieldPropertyName(field);
bool needsCheckedSetter = false;
if (compiler.enableTypeAssertions
&& needsSetter
&& !canAvoidGeneratedCheckedSetter(field)) {
needsCheckedSetter = true;
needsSetter = false;
}
// Getters and setters with suffixes will be generated dynamically.
f(field, fieldName, accessorName, needsGetter, needsSetter,
needsCheckedSetter);
}
}
if (isLibrary) {
LibraryElement library = element;
library.implementation.forEachLocalMember((Element member) {
if (member.isField) visitField(library, member);
});
} else if (visitStatics) {
ClassElement cls = element;
cls.implementation.forEachStaticField(visitField);
} else {
ClassElement cls = element;
// TODO(kasperl): We should make sure to only emit one version of
// overridden fields. Right now, we rely on the ordering so the
// fields pulled in from mixins are replaced with the fields from
// the class definition.
// If a class is not instantiated then we add the field just so we can
// generate the field getter/setter dynamically. Since this is only
// allowed on fields that are in [element] we don't need to visit
// superclasses for non-instantiated classes.
cls.implementation.forEachInstanceField(
visitField, includeSuperAndInjectedMembers: isInstantiated);
}
}
void recordMangledField(Element member,
String accessorName,
String memberName) {
if (!backend.shouldRetainGetter(member)) return;
String previousName;
if (member.isInstanceMember) {
previousName = emitter.mangledFieldNames.putIfAbsent(
'${namer.getterPrefix}$accessorName',
() => memberName);
} else {
previousName = emitter.mangledGlobalFieldNames.putIfAbsent(
accessorName,
() => memberName);
}
assert(invariant(member, previousName == memberName,
message: '$previousName != ${memberName}'));
}
bool fieldNeedsGetter(VariableElement field) {
assert(field.isField);
if (fieldAccessNeverThrows(field)) return false;
return backend.shouldRetainGetter(field)
|| compiler.codegenWorld.hasInvokedGetter(field, compiler.world);
}
bool fieldNeedsSetter(VariableElement field) {
assert(field.isField);
if (fieldAccessNeverThrows(field)) return false;
return (!field.isFinal && !field.isConst)
&& (backend.shouldRetainSetter(field)
|| compiler.codegenWorld.hasInvokedSetter(field, compiler.world));
}
// We never access a field in a closure (a captured variable) without knowing
// that it is there. Therefore we don't need to use a getter (that will throw
// if the getter method is missing), but can always access the field directly.
static bool fieldAccessNeverThrows(VariableElement field) {
return field is ClosureFieldElement;
}
bool canAvoidGeneratedCheckedSetter(VariableElement member) {
// We never generate accessors for top-level/static fields.
if (!member.isInstanceMember) return true;
DartType type = member.type;
return type.treatAsDynamic || (type.element == compiler.objectClass);
}
void generateCheckedSetter(Element member,
String fieldName,
String accessorName,
ClassBuilder builder) {
jsAst.Expression code = backend.generatedCode[member];
assert(code != null);
String setterName = namer.setterNameFromAccessorName(accessorName);
compiler.dumpInfoTask.registerElementAst(member,
builder.addProperty(setterName, code));
generateReflectionDataForFieldGetterOrSetter(
member, setterName, builder, isGetter: false);
}
void emitGetterForCSP(Element member, String fieldName, String accessorName,
ClassBuilder builder) {
jsAst.Expression function =
_stubGenerator.generateGetter(member, fieldName);
String getterName = namer.getterNameFromAccessorName(accessorName);
ClassElement cls = member.enclosingClass;
String className = namer.getNameOfClass(cls);
OutputUnit outputUnit =
compiler.deferredLoadTask.outputUnitForElement(member);
emitter.cspPrecompiledFunctionFor(outputUnit).add(
js('#.prototype.# = #', [className, getterName, function]));
if (backend.isAccessibleByReflection(member)) {
emitter.cspPrecompiledFunctionFor(outputUnit).add(
js('#.prototype.#.${namer.reflectableField} = 1',
[className, getterName]));
}
}
void emitSetterForCSP(Element member, String fieldName, String accessorName,
ClassBuilder builder) {
jsAst.Expression function =
_stubGenerator.generateSetter(member, fieldName);
String setterName = namer.setterNameFromAccessorName(accessorName);
ClassElement cls = member.enclosingClass;
String className = namer.getNameOfClass(cls);
OutputUnit outputUnit =
compiler.deferredLoadTask.outputUnitForElement(member);
emitter.cspPrecompiledFunctionFor(outputUnit).add(
js('#.prototype.# = #', [className, setterName, function]));
if (backend.isAccessibleByReflection(member)) {
emitter.cspPrecompiledFunctionFor(outputUnit).add(
js('#.prototype.#.${namer.reflectableField} = 1',
[className, setterName]));
}
}
void generateReflectionDataForFieldGetterOrSetter(Element member,
String name,
ClassBuilder builder,
{bool isGetter}) {
Selector selector = isGetter
? new Selector.getter(member.name, member.library)
: new Selector.setter(member.name, member.library);
String reflectionName = emitter.getReflectionName(selector, name);
if (reflectionName != null) {
var reflectable =
js(backend.isAccessibleByReflection(member) ? '1' : '0');
builder.addProperty('+$reflectionName', reflectable);
}
}
}