| // 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 '../../deferred_load.dart' show OutputUnit; |
| 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 '../../world.dart' show JClosedWorld; |
| import '../js_emitter.dart' hide Emitter, EmitterFactory; |
| import '../model.dart'; |
| import 'emitter.dart'; |
| |
| class ClassEmitter extends CodeEmitterHelper { |
| final JClosedWorld 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; |
| |
| emitter.needsClassSupport = true; |
| |
| ClassEntity superclass = _elementEnvironment.getSuperClass(classElement); |
| jsAst.Name superName; |
| if (superclass != null) { |
| superName = namer.className(superclass); |
| } |
| |
| if (cls.mixinClass != null) { |
| jsAst.Name mixinName = cls.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) { |
| if (!compiler.options.useContentSecurityPolicy) return; |
| |
| List<jsAst.Name> fieldNames = <jsAst.Name>[]; |
| 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 = |
| closedWorld.outputUnitData.outputUnitForClass(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; |
| } |
| |
| 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) { |
| List<jsAst.Literal> fieldNameParts = <jsAst.Literal>[]; |
| if (field.nullInitializerInAllocator) { |
| fieldNameParts.add(js.stringPart('0')); |
| } |
| 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])); |
| } |
| 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; |
| } |
| } |
| |
| 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)); |
| } |
| } |
| |
| /// 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; |
| |
| if (cls.onlyForRti || cls.isSimpleMixinApplication) 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; |
| |
| 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) { |
| // TODO(herhut): Fix use of reflection name here. |
| enclosingBuilder.addPropertyByName("+$reflectionName", js.number(0)); |
| } |
| } |
| |
| 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 = |
| closedWorld.outputUnitData.outputUnitForMember(member); |
| emitter |
| .cspPrecompiledFunctionFor(outputUnit) |
| .add(js('#.prototype.# = #', [className, getterName, function])); |
| } |
| |
| 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 = |
| closedWorld.outputUnitData.outputUnitForMember(member); |
| emitter |
| .cspPrecompiledFunctionFor(outputUnit) |
| .add(js('#.prototype.# = #', [className, setterName, function])); |
| } |
| } |