Chromium Code Reviews| Index: lib/compiler/implementation/js_backend/emitter.dart |
| diff --git a/lib/compiler/implementation/js_backend/emitter.dart b/lib/compiler/implementation/js_backend/emitter.dart |
| index 82e80a80fa84e0598aef19aa8e8faeddc80addee..34a7714084830bb6bc7a806d6a38f1e4676703bc 100644 |
| --- a/lib/compiler/implementation/js_backend/emitter.dart |
| +++ b/lib/compiler/implementation/js_backend/emitter.dart |
| @@ -46,14 +46,15 @@ class CodeEmitterTask extends CompilerTask { |
| Set<ClassElement> checkedClasses; |
| final bool generateSourceMap; |
| + final bool useContentSecurityPolicy; |
| CodeEmitterTask(Compiler compiler, Namer namer, |
| - [bool generateSourceMap = false]) |
| + this.generateSourceMap, |
| + this.useContentSecurityPolicy) |
| : boundClosureBuffer = new CodeBuffer(), |
| mainBuffer = new CodeBuffer(), |
| this.namer = namer, |
| boundClosureCache = new Map<int, String>(), |
| - generateSourceMap = generateSourceMap, |
| constantEmitter = new ConstantEmitter(compiler, namer), |
| super(compiler) { |
| nativeEmitter = new NativeEmitter(this); |
| @@ -98,6 +99,10 @@ class CodeEmitterTask extends CompilerTask { |
| final String GETTER_SETTER_SUFFIX = "="; |
| String get generateGetterSetterFunction { |
| + if (useContentSecurityPolicy) { |
| + compiler.internalError("Dynamic Getters/Setters are unused in CSP"); |
|
kasperl
2012/10/17 09:37:11
Getters/Setters -> getters/setters
floitsch
2012/10/17 21:15:01
Refactored.
|
| + } |
| + |
| return """ |
| function(field, prototype) { |
| var len = field.length; |
| @@ -122,6 +127,13 @@ class CodeEmitterTask extends CompilerTask { |
| } |
| String get defineClassFunction { |
| + if (useContentSecurityPolicy) { |
|
kasperl
2012/10/17 09:37:11
It feels a little bit like subclassing would help
floitsch
2012/10/17 21:15:01
Done.
|
| + return """ |
| +function(cls, constructor, prototype) { |
| + constructor.prototype = prototype; |
| + return constructor; |
| +}"""; |
| + } |
| // First the class name, then the super class name, followed by the fields |
| // (in an array) and the members (inside an Object literal). |
| // The caller can also pass in the constructor as a function if needed. |
| @@ -162,9 +174,16 @@ function(cls, fields, prototype) { |
| /** Needs defineClass to be defined. */ |
| String get protoSupportCheck { |
| + if (useContentSecurityPolicy) { |
| + // We don't modify the prototypes in CSP mode. Therefore we can have an |
| + // easier prototype-check. |
| + return 'var $supportsProtoName = !!{}.__proto__;'; |
| + } |
| // On Firefox and Webkit browsers we can manipulate the __proto__ |
| // directly. Opera claims to have __proto__ support, but it is buggy. |
| // So we have to do more checks. |
| + // Opera bug was filed as DSK-370158, and fixed as CORE-47615 |
| + // (http://my.opera.com/desktopteam/blog/2012/07/20/more-12-01-fixes). |
| // If the browser does not support __proto__ we need to instantiate an |
| // object with the correct (internal) prototype set up correctly, and then |
| // copy the members. |
| @@ -242,6 +261,34 @@ function(collectedClasses) { |
| } |
| String get finishIsolateConstructorFunction { |
| + if (useContentSecurityPolicy) { |
| + // We replace the old Isolate function with a new one that initializes |
| + // all its field with the initial (and often final) value of all globals. |
| + // |
| + // We also copy over old values like the prototype, and the |
| + // isolateProperties themselves. |
| + return """ |
| +function(oldIsolate) { |
| + var isolateProperties = oldIsolate.${namer.ISOLATE_PROPERTIES}; |
|
kasperl
2012/10/17 09:37:11
Isn't there a way to construct the isolate object
floitsch
2012/10/17 21:15:01
There is, but since it's not necessary.
|
| + function Isolate() { |
| + for (var staticName in isolateProperties) { |
| + if (Object.prototype.hasOwnProperty.call(isolateProperties, staticName)) { |
| + this[staticName] = isolateProperties[staticName]; |
| + } |
| + } |
| + // Use the newly created object as prototype. In Chrome this creates a |
|
floitsch
2012/10/16 18:15:36
Thanks to Erik Corry for this tip. Without these l
|
| + // hidden class for the object and makes sure it is fast to access. |
| + function t(){} |
|
kasperl
2012/10/17 09:37:11
Maybe give t a better name? Space after ).
floitsch
2012/10/17 21:15:01
Done.
|
| + t.prototype = this; |
| + new t(); |
| + } |
| + Isolate.prototype = oldIsolate.prototype; |
| + Isolate.prototype.constructor = Isolate; |
| + Isolate.${namer.ISOLATE_PROPERTIES} = isolateProperties; |
| + return Isolate; |
| +}"""; |
| + } |
| + |
| String isolate = namer.ISOLATE; |
| // We replace the old Isolate function with a new one that initializes |
| // all its field with the initial (and often final) value of all globals. |
| @@ -620,6 +667,16 @@ function(prototype, staticName, fieldName, getterName, lazyValue) { |
| emitExtraAccessors(member, defineInstanceMember); |
| } |
| + String generateGetter(Element member, String fieldName) { |
| + String getterName = namer.getterName(member.getLibrary(), member.name); |
| + return "$getterName: function() { return this.$fieldName; }"; |
| + } |
| + |
| + String generateSetter(Element member, String fieldName) { |
| + String setterName = namer.setterName(member.getLibrary(), member.name); |
| + return "$setterName: function(v) { this.$fieldName = v; }"; |
| + } |
| + |
| String generateCheckedSetter(Element member, String fieldName) { |
| DartType type = member.computeType(compiler); |
| if (type.element.isTypeVariable() |
| @@ -635,7 +692,8 @@ function(prototype, staticName, fieldName, getterName, lazyValue) { |
| if (helperElement.computeSignature(compiler).parameterCount != 1) { |
| additionalArgument = ", '${namer.operatorIs(type.element)}'"; |
| } |
| - return " set\$$fieldName: function(v) { " |
| + String setterName = namer.setterName(member.getLibrary(), member.name); |
| + return " $setterName: function(v) { " |
| "this.$fieldName = $helperName(v$additionalArgument); }"; |
| } |
| } |
| @@ -645,16 +703,19 @@ function(prototype, staticName, fieldName, getterName, lazyValue) { |
| * |
| * Invariant: [classElement] must be a declaration element. |
| */ |
| - List<String> emitClassFields(ClassElement classElement, CodeBuffer buffer) { |
| + void visitClassFields(ClassElement classElement, |
| + void addField(Element member, |
| + String name, |
| + bool needsGetter, |
| + bool needsSetter, |
| + bool needsCheckedSetter)) { |
| assert(invariant(classElement, classElement.isDeclaration)); |
| // 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.instantiatedClasses.contains(classElement); |
| - List<String> checkedSetters = <String>[]; |
| - bool isFirstField = true; |
| - void addField(ClassElement enclosingClass, Element member) { |
| + void visitField(ClassElement enclosingClass, Element member) { |
| assert(!member.isNative()); |
| assert(invariant(classElement, member.isDeclaration)); |
| @@ -665,48 +726,38 @@ function(prototype, staticName, fieldName, getterName, lazyValue) { |
| // We can only generate getters and setters for [classElement] since |
| // the fields of super classes could be overwritten with getters or |
| // setters. |
| - bool needsDynamicGetter = false; |
| - bool needsDynamicSetter = false; |
| + bool needsGetter = false; |
| + bool needsSetter = false; |
| // We need to name shadowed fields differently, so they don't clash with |
| // the non-shadowed field. |
| bool isShadowed = false; |
| if (enclosingClass === classElement) { |
| - needsDynamicGetter = instanceFieldNeedsGetter(member); |
| - needsDynamicSetter = instanceFieldNeedsSetter(member); |
| + needsGetter = instanceFieldNeedsGetter(member); |
| + needsSetter = instanceFieldNeedsSetter(member); |
| } else { |
| isShadowed = classElement.isShadowedByField(member); |
| } |
| if ((isInstantiated && !enclosingClass.isNative()) |
| - || needsDynamicGetter |
| - || needsDynamicSetter) { |
| - if (isFirstField) { |
| - isFirstField = false; |
| - } else { |
| - buffer.add(", "); |
| - } |
| + || needsGetter |
| + || needsSetter) { |
| String fieldName = isShadowed |
| ? namer.shadowedFieldName(member) |
| : namer.getName(member); |
| - if (needsDynamicSetter && compiler.enableTypeAssertions) { |
| + bool needsCheckedSetter = false; |
| + if (needsSetter && compiler.enableTypeAssertions) { |
| String setter = generateCheckedSetter(member, fieldName); |
| if (setter != null) { |
| - needsDynamicSetter = false; |
| - checkedSetters.add(setter); |
| + needsSetter = false; |
| + needsCheckedSetter = true; |
| } |
| } |
| // Getters and setters with suffixes will be generated dynamically. |
| - buffer.add('"$fieldName'); |
| - if (needsDynamicGetter || needsDynamicSetter) { |
| - if (needsDynamicGetter && needsDynamicSetter) { |
| - buffer.add(GETTER_SETTER_SUFFIX); |
| - } else if (needsDynamicGetter) { |
| - buffer.add(GETTER_SUFFIX); |
| - } else { |
| - buffer.add(SETTER_SUFFIX); |
| - } |
| - } |
| - buffer.add('"'); |
| + addField(member, |
| + fieldName, |
| + needsGetter, |
| + needsSetter, |
| + needsCheckedSetter); |
| } |
| } |
| @@ -715,13 +766,100 @@ function(prototype, staticName, fieldName, getterName, lazyValue) { |
| // allowed on fields that are in [classElement] we don't need to visit |
| // superclasses for non-instantiated classes. |
| classElement.implementation.forEachInstanceField( |
| - addField, |
| + visitField, |
| includeBackendMembers: true, |
| includeSuperMembers: isInstantiated && !classElement.isNative()); |
| + } |
| + |
| + List<String> emitClassFields(ClassElement classElement, CodeBuffer buffer) { |
| + buffer.add(' ['); |
| + bool isFirstField = true; |
| + List<String> checkedSetters = <String>[]; |
| + visitClassFields(classElement, (Element member, |
| + String name, |
| + bool needsGetter, |
| + bool needsSetter, |
| + bool needsCheckedSetter) { |
| + if (isFirstField) { |
| + isFirstField = false; |
| + } else { |
| + buffer.add(", "); |
| + } |
| + if (needsCheckedSetter) { |
| + assert(!needsSetter); |
| + String setter = generateCheckedSetter(member, name); |
| + if (setter !== null) { |
| + needsSetter = null; |
| + } else { |
| + checkedSetters.add(setter); |
| + } |
| + } |
| + buffer.add('"$name'); |
| + if (needsGetter && needsSetter) { |
| + buffer.add(GETTER_SETTER_SUFFIX); |
| + } else if (needsGetter) { |
| + buffer.add(GETTER_SUFFIX); |
| + } else if (needsSetter) { |
| + buffer.add(SETTER_SUFFIX); |
| + } |
| + buffer.add('"'); |
| + }); |
| + buffer.add(']'); |
| return checkedSetters; |
| } |
| /** |
| + * This is the equivalent of [emitClassFields] when running under the |
| + * Content-Security Policy. |
| + */ |
| + List<String> emitClassConstructorGettersSetters(ClassElement classElement, |
| + CodeBuffer buffer) { |
| + // Say we have a class A with fields b, c and d, where c needs a getter and |
| + // d needs both a getter and a setter. Then we produce: |
| + // - a constructor (directly into the given [buffer]): |
| + // function A(b, c, d) { this.b = b, this.c = c, this.d = d; } |
| + // - getters and setters (stored in the [explicitGettersSetters] list): |
| + // get$c : function() { return this.c; } |
| + // get$d : function() { return this.d; } |
| + // set$d : function(x) { this.d = x; } |
| + List<String> explicitGettersSetters = <String>[]; |
| + List<String> fields = <String>[]; |
| + visitClassFields(classElement, (Element member, |
| + String name, |
| + bool needsGetter, |
| + bool needsSetter, |
| + bool needsCheckedSetter) { |
| + fields.add(name); |
| + if (needsCheckedSetter) { |
| + assert(!needsSetter); |
| + String setter = generateCheckedSetter(member, name); |
| + if (setter !== null) { |
| + needsSetter = null; |
| + } else { |
| + explicitGettersSetters.add(setter); |
| + } |
| + } |
| + if (needsGetter) { |
| + explicitGettersSetters.add(generateGetter(member, name)); |
| + } |
| + if (needsSetter) { |
| + explicitGettersSetters.add(generateSetter(member, name)); |
| + } |
| + }); |
| + |
| + String constructorName = namer.safeName(classElement.name.slowToString()); |
| + // Generate the constructor. |
| + buffer.add("function $constructorName("); |
| + buffer.add(Strings.join(fields, ", ")); |
| + buffer.add(") {"); |
| + for (String field in fields) { |
| + buffer.add(" this.$field = $field;"); |
| + } |
| + buffer.add(' }'); |
| + return explicitGettersSetters; |
| + } |
| + |
| + /** |
| * Documentation wanted -- johnniwinther |
| * |
| * Invariant: [classElement] must be a declaration element. |
| @@ -796,19 +934,22 @@ function(prototype, staticName, fieldName, getterName, lazyValue) { |
| if (superclass !== null) { |
| superName = namer.getName(superclass); |
| } |
| - String constructorName = namer.safeName(classElement.name.slowToString()); |
| buffer.add('$classesCollector.$className = {"":\n'); |
| - buffer.add(' ['); |
| - List<String> checkedSetters = emitClassFields(classElement, buffer); |
| - buffer.add('],\n'); |
| + List<String> explicitGettersSetters; |
| + if (useContentSecurityPolicy) { |
| + explicitGettersSetters = |
| + emitClassConstructorGettersSetters(classElement, buffer); |
| + } else { |
| + explicitGettersSetters = emitClassFields(classElement, buffer); |
| + } |
| // TODO(floitsch): the emitInstanceMember should simply always emit a ',\n'. |
| // That does currently not work because the native classes have a different |
| // syntax. |
| - buffer.add(' "super": "$superName"'); |
| - if (!checkedSetters.isEmpty()) { |
| + buffer.add(',\n "super": "$superName"'); |
| + if (!explicitGettersSetters.isEmpty()) { |
| buffer.add(',\n'); |
| - buffer.add('${Strings.join(checkedSetters, ",\n")}'); |
| + buffer.add('${Strings.join(explicitGettersSetters, ",\n")}'); |
| } |
| emitInstanceMembers(classElement, buffer, true); |
| buffer.add('\n};\n\n'); |
| @@ -1020,11 +1161,19 @@ function(prototype, staticName, fieldName, getterName, lazyValue) { |
| // Define the constructor with a name so that Object.toString can |
| // find the class name of the closure class. |
| - boundClosureBuffer.add(""" |
| + if (useContentSecurityPolicy) { |
| + boundClosureBuffer.add(""" |
| +$classesCollector.$mangledName = {'': |
| +function $mangledName(self, target) { this.self = self; this.target = target; }, |
| + 'super': '$superName', |
| +"""); |
| + } else { |
| + boundClosureBuffer.add(""" |
| $classesCollector.$mangledName = {'': |
| ['self', 'target'], |
| 'super': '$superName', |
| """); |
| + } |
| // Now add the methods on the closure class. The instance method does not |
| // have the correct name. Since [addParameterStubs] use the name to create |
| // its stubs we simply create a fake element with the correct name. |