blob: fc014bc01191a02ff1307f09e382e110df90efd8 [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;
// TODO(ahe): Share these with js_helper.dart.
const FUNCTION_INDEX = 0;
const NAME_INDEX = 1;
const CALL_NAME_INDEX = 2;
const REQUIRED_PARAMETER_INDEX = 3;
const OPTIONAL_PARAMETER_INDEX = 4;
const DEFAULT_ARGUMENTS_INDEX = 5;
const bool VALIDATE_DATA = false;
// TODO(zarah): Rename this when renaming this file.
String get parseReflectionDataName => 'parseReflectionData';
jsAst.Expression getReflectionDataParser(OldEmitter oldEmitter,
JavaScriptBackend backend,
bool needsNativeSupport) {
Namer namer = backend.namer;
Compiler compiler = backend.compiler;
CodeEmitterTask emitter = backend.emitter;
String reflectableField = namer.reflectableField;
String reflectionInfoField = namer.reflectionInfoField;
String reflectionNameField = namer.reflectionNameField;
String metadataIndexField = namer.metadataIndexField;
String defaultValuesField = namer.defaultValuesField;
String methodsWithOptionalArgumentsField =
namer.methodsWithOptionalArgumentsField;
String unmangledNameIndex = backend.mustRetainMetadata
? ' 3 * optionalParameterCount + 2 * requiredParameterCount + 3'
: ' 2 * optionalParameterCount + requiredParameterCount + 3';
jsAst.Expression typeInformationAccess =
emitter.generateEmbeddedGlobalAccess(embeddedNames.TYPE_INFORMATION);
jsAst.Expression globalFunctionsAccess =
emitter.generateEmbeddedGlobalAccess(embeddedNames.GLOBAL_FUNCTIONS);
jsAst.Expression staticsAccess =
emitter.generateEmbeddedGlobalAccess(embeddedNames.STATICS);
jsAst.Expression interceptedNamesAccess =
emitter.generateEmbeddedGlobalAccess(embeddedNames.INTERCEPTED_NAMES);
jsAst.Expression mangledGlobalNamesAccess =
emitter.generateEmbeddedGlobalAccess(embeddedNames.MANGLED_GLOBAL_NAMES);
jsAst.Expression mangledNamesAccess =
emitter.generateEmbeddedGlobalAccess(embeddedNames.MANGLED_NAMES);
jsAst.Expression librariesAccess =
emitter.generateEmbeddedGlobalAccess(embeddedNames.LIBRARIES);
jsAst.Expression metadataAccess =
emitter.generateEmbeddedGlobalAccess(embeddedNames.METADATA);
jsAst.Statement processClassData = js.statement('''{
function processClassData(cls, descriptor, processedClasses) {
var newDesc = {};
var previousProperty;
var properties = Object.keys(descriptor);
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
var firstChar = property.substring(0, 1);
if (property === "static") {
processStatics(#embeddedStatics[cls] = descriptor[property],
processedClasses);
} else if (firstChar === "+") {
mangledNames[previousProperty] = property.substring(1);
var flag = descriptor[property];
if (flag > 0)
descriptor[previousProperty].$reflectableField = flag;
} else if (firstChar === "*") {
newDesc[previousProperty].$defaultValuesField = descriptor[property];
var optionalMethods = newDesc.$methodsWithOptionalArgumentsField;
if (!optionalMethods) {
newDesc.$methodsWithOptionalArgumentsField = optionalMethods={}
}
optionalMethods[property] = previousProperty;
} else {
var elem = descriptor[property];
if (property !== "${namer.classDescriptorProperty}" &&
elem != null &&
elem.constructor === Array &&
property !== "<>") {
addStubs(newDesc, elem, property, false, descriptor, []);
} else {
newDesc[previousProperty = property] = elem;
}
}
}
/* The 'fields' are either a constructor function or a
* string encoding fields, constructor and superclass. Gets the
* superclass and fields in the format
* 'Super;field1,field2'
* from the CLASS_DESCRIPTOR_PROPERTY property on the descriptor.
*/
var classData = newDesc["${namer.classDescriptorProperty}"],
split, supr, fields = classData;
if (#hasRetainedMetadata)
if (typeof classData == "object" &&
classData instanceof Array) {
classData = fields = classData[0];
}
// ${ClassBuilder.fieldEncodingDescription}.
var s = fields.split(";");
fields = s[1] == "" ? [] : s[1].split(",");
supr = s[0];
// ${ClassBuilder.functionTypeEncodingDescription}.
split = supr.split(":");
if (split.length == 2) {
supr = split[0];
var functionSignature = split[1];
if (functionSignature)
newDesc.${namer.operatorSignature} = function(s) {
return function() {
return #metadata[s];
};
}(functionSignature);
}
if (supr) processedClasses.pending[cls] = supr;
if (#notInCspMode) {
processedClasses.combinedConstructorFunction += defineClass(cls, fields);
processedClasses.constructorsList.push(cls);
}
processedClasses.collected[cls] = [globalObject, newDesc];
classes.push(cls);
}
}''', {'embeddedStatics': staticsAccess,
'hasRetainedMetadata': backend.hasRetainedMetadata,
'metadata': metadataAccess,
'notInCspMode': !compiler.useContentSecurityPolicy});
// TODO(zarah): Remove empty else branches in output when if(#hole) is false.
jsAst.Statement processStatics = js.statement('''
function processStatics(descriptor, processedClasses) {
var properties = Object.keys(descriptor);
for (var i = 0; i < properties.length; i++) {
var property = properties[i];
if (property === "${namer.classDescriptorProperty}") continue;
var element = descriptor[property];
var firstChar = property.substring(0, 1);
var previousProperty;
if (firstChar === "+") {
mangledGlobalNames[previousProperty] = property.substring(1);
var flag = descriptor[property];
if (flag > 0)
descriptor[previousProperty].$reflectableField = flag;
if (element && element.length)
#typeInformation[previousProperty] = element;
} else if (firstChar === "*") {
globalObject[previousProperty].$defaultValuesField = element;
var optionalMethods = descriptor.$methodsWithOptionalArgumentsField;
if (!optionalMethods) {
descriptor.$methodsWithOptionalArgumentsField = optionalMethods = {}
}
optionalMethods[property] = previousProperty;
} else if (typeof element === "function") {
globalObject[previousProperty = property] = element;
functions.push(property);
#globalFunctions[property] = element;
} else if (element.constructor === Array) {
if (#needsStructuredMemberInfo) {
addStubs(globalObject, element, property,
true, descriptor, functions);
}
} else {
// We will not enter this case if no classes are defined.
if (#hasClasses) {
previousProperty = property;
processClassData(property, element, processedClasses);
}
}
}
}
''', {'typeInformation': typeInformationAccess,
'globalFunctions': globalFunctionsAccess,
'hasClasses': oldEmitter.needsClassSupport,
'needsStructuredMemberInfo': oldEmitter.needsStructuredMemberInfo});
/**
* See [dart2js.js_emitter.ContainerBuilder.addMemberMethod] for format of
* [array].
*/
jsAst.Statement addStubs = js.statement('''
function addStubs(descriptor, array, name, isStatic,
originalDescriptor, functions) {
var index = $FUNCTION_INDEX, alias = array[index], f;
if (typeof alias == "string") {
f = array[++index];
} else {
f = alias;
alias = name;
}
var funcs = [originalDescriptor[name] = descriptor[name] =
descriptor[alias] = f];
f.\$stubName = name;
functions.push(name);
for (; index < array.length; index += 2) {
f = array[index + 1];
if (typeof f != "function") break;
f.\$stubName = ${readString("array", "index + 2")};
funcs.push(f);
if (f.\$stubName) {
originalDescriptor[f.\$stubName] = descriptor[f.\$stubName] = f;
functions.push(f.\$stubName);
}
}
for (var i = 0; i < funcs.length; index++, i++) {
funcs[i].\$callName = ${readString("array", "index + 1")};
}
var getterStubName = ${readString("array", "++index")};
array = array.slice(++index);
var requiredParameterInfo = ${readInt("array", "0")};
var requiredParameterCount = requiredParameterInfo >> 1;
var isAccessor = (requiredParameterInfo & 1) === 1;
var isSetter = requiredParameterInfo === 3;
var isGetter = requiredParameterInfo === 1;
var optionalParameterInfo = ${readInt("array", "1")};
var optionalParameterCount = optionalParameterInfo >> 1;
var optionalParametersAreNamed = (optionalParameterInfo & 1) === 1;
var isIntercepted =
requiredParameterCount + optionalParameterCount != funcs[0].length;
var functionTypeIndex = ${readFunctionType("array", "2")};
var unmangledNameIndex = $unmangledNameIndex;
if (getterStubName) {
f = tearOff(funcs, array, isStatic, name, isIntercepted);
descriptor[name].\$getter = f;
f.\$getterStub = true;
// Used to create an isolate using spawnFunction.
if (isStatic) #globalFunctions[name] = f;
originalDescriptor[getterStubName] = descriptor[getterStubName] = f;
funcs.push(f);
if (getterStubName) functions.push(getterStubName);
f.\$stubName = getterStubName;
f.\$callName = null;
// Update the interceptedNames map (which only exists if `invokeOn` was
// enabled).
if (#enabledInvokeOn)
if (isIntercepted) #interceptedNames[getterStubName] = 1;
}
if (#usesMangledNames) {
var isReflectable = array.length > unmangledNameIndex;
if (isReflectable) {
for (var i = 0; i < funcs.length; i++) {
funcs[i].$reflectableField = 1;
funcs[i].$reflectionInfoField = array;
}
var mangledNames = isStatic ? #mangledGlobalNames : #mangledNames;
var unmangledName = ${readString("array", "unmangledNameIndex")};
// The function is either a getter, a setter, or a method.
// If it is a method, it might also have a tear-off closure.
// The unmangledName is the same as the getter-name.
var reflectionName = unmangledName;
if (getterStubName) mangledNames[getterStubName] = reflectionName;
if (isSetter) {
reflectionName += "=";
} else if (!isGetter) {
reflectionName += ":" + requiredParameterCount +
":" + optionalParameterCount;
}
mangledNames[name] = reflectionName;
funcs[0].$reflectionNameField = reflectionName;
funcs[0].$metadataIndexField = unmangledNameIndex + 1;
if (optionalParameterCount) descriptor[unmangledName + "*"] = funcs[0];
}
}
}
''', {'globalFunctions': globalFunctionsAccess,
'enabledInvokeOn': compiler.enabledInvokeOn,
'interceptedNames': interceptedNamesAccess,
'usesMangledNames':
compiler.mirrorsLibrary != null || compiler.enabledFunctionApply,
'mangledGlobalNames': mangledGlobalNamesAccess,
'mangledNames': mangledNamesAccess});
List<jsAst.Statement> tearOffCode = buildTearOffCode(backend);
jsAst.ObjectInitializer interceptedNamesSet =
oldEmitter.interceptorEmitter.generateInterceptedNamesSet();
jsAst.Statement init = js.statement('''{
var functionCounter = 0;
if (!#libraries) #libraries = [];
if (!#mangledNames) #mangledNames = map();
if (!#mangledGlobalNames) #mangledGlobalNames = map();
if (!#statics) #statics = map();
if (!#typeInformation) #typeInformation = map();
if (!#globalFunctions) #globalFunctions = map();
if (#enabledInvokeOn)
if (!#interceptedNames) #interceptedNames = #interceptedNamesSet;
var libraries = #libraries;
var mangledNames = #mangledNames;
var mangledGlobalNames = #mangledGlobalNames;
var hasOwnProperty = Object.prototype.hasOwnProperty;
var length = reflectionData.length;
var processedClasses = Object.create(null);
processedClasses.collected = Object.create(null);
processedClasses.pending = Object.create(null);
if (#notInCspMode) {
processedClasses.constructorsList = [];
// For every class processed [processedClasses.combinedConstructorFunction]
// will be updated with the corresponding constructor function.
processedClasses.combinedConstructorFunction =
"function \$reflectable(fn){fn.$reflectableField=1;return fn};\\n"+
"var \$desc;\\n";
}
for (var i = 0; i < length; i++) {
var data = reflectionData[i];
// [data] contains these elements:
// 0. The library name (not unique).
// 1. The library URI (unique).
// 2. A function returning the metadata associated with this library.
// 3. The global object to use for this library.
// 4. An object literal listing the members of the library.
// 5. This element is optional and if present it is true and signals that this
// library is the root library (see dart:mirrors IsolateMirror.rootLibrary).
//
// The entries of [data] are built in [assembleProgram] above.
var name = data[0];
var uri = data[1];
var metadata = data[2];
var globalObject = data[3];
var descriptor = data[4];
var isRoot = !!data[5];
var fields = descriptor && descriptor["${namer.classDescriptorProperty}"];
if (fields instanceof Array) fields = fields[0];
var classes = [];
var functions = [];
processStatics(descriptor, processedClasses);
libraries.push([name, uri, classes, functions, metadata, fields, isRoot,
globalObject]);
}
if (#needsClassSupport) finishClasses(processedClasses);
}''', {'libraries': librariesAccess,
'mangledNames': mangledNamesAccess,
'mangledGlobalNames': mangledGlobalNamesAccess,
'statics': staticsAccess,
'typeInformation': typeInformationAccess,
'globalFunctions': globalFunctionsAccess,
'enabledInvokeOn': compiler.enabledInvokeOn,
'interceptedNames': interceptedNamesAccess,
'interceptedNamesSet': interceptedNamesSet,
'notInCspMode': !compiler.useContentSecurityPolicy,
'needsClassSupport': oldEmitter.needsClassSupport});
jsAst.Expression allClassesAccess =
emitter.generateEmbeddedGlobalAccess(embeddedNames.ALL_CLASSES);
// Class descriptions are collected in a JS object.
// 'finishClasses' takes all collected descriptions and sets up
// the prototype.
// Once set up, the constructors prototype field satisfy:
// - it contains all (local) members.
// - its internal prototype (__proto__) points to the superclass'
// prototype field.
// - the prototype's constructor field points to the JavaScript
// constructor.
// For engines where we have access to the '__proto__' we can manipulate
// the object literal directly. For other engines we have to create a new
// object and copy over the members.
jsAst.Statement finishClasses = js.statement('''{
function finishClasses(processedClasses) {
if (#debugFastObjects)
print("Number of classes: " +
Object.getOwnPropertyNames(processedClasses.collected).length);
var allClasses = #allClasses;
if (#inCspMode) {
var constructors = #precompiled(processedClasses.collected);
}
if (#notInCspMode) {
processedClasses.combinedConstructorFunction +=
"return [\\n" + processedClasses.constructorsList.join(",\\n ") +
"\\n]";
var constructors =
new Function("\$collectedClasses",
processedClasses.combinedConstructorFunction)
(processedClasses.collected);
processedClasses.combinedConstructorFunction = null;
}
for (var i = 0; i < constructors.length; i++) {
var constructor = constructors[i];
var cls = constructor.name;
var desc = processedClasses.collected[cls];
var globalObject = \$;
if (desc instanceof Array) {
globalObject = desc[0] || \$;
desc = desc[1];
}
if (#isTreeShakingDisabled)
constructor["${namer.metadataField}"] = desc;
allClasses[cls] = constructor;
globalObject[cls] = constructor;
}
constructors = null;
#finishClassFunction;
#trivialNsmHandlers;
var properties = Object.keys(processedClasses.pending);
for (var i = 0; i < properties.length; i++) finishClass(properties[i]);
}
}''', {'allClasses': allClassesAccess,
'debugFastObjects': DEBUG_FAST_OBJECTS,
'isTreeShakingDisabled': backend.isTreeShakingDisabled,
'finishClassFunction': oldEmitter.buildFinishClass(needsNativeSupport),
'trivialNsmHandlers': oldEmitter.buildTrivialNsmHandlers(),
'inCspMode': compiler.useContentSecurityPolicy,
'notInCspMode': !compiler.useContentSecurityPolicy,
'precompiled': oldEmitter
.generateEmbeddedGlobalAccess(embeddedNames.PRECOMPILED)});
List<jsAst.Statement> incrementalSupport = <jsAst.Statement>[];
if (compiler.hasIncrementalSupport) {
incrementalSupport.add(
js.statement(
'#.addStubs = addStubs;', [namer.accessIncrementalHelper]));
}
return js('''
function $parseReflectionDataName(reflectionData) {
"use strict";
if (#needsClassSupport) {
#defineClass;
#inheritFrom;
#finishClasses;
#processClassData;
}
#processStatics;
if (#needsStructuredMemberInfo) {
#addStubs;
#tearOffCode;
}
#incrementalSupport;
#init;
}''', {
'defineClass': oldEmitter.defineClassFunction,
'inheritFrom': oldEmitter.buildInheritFrom(),
'processClassData': processClassData,
'processStatics': processStatics,
'incrementalSupport': incrementalSupport,
'addStubs': addStubs,
'tearOffCode': tearOffCode,
'init': init,
'finishClasses': finishClasses,
'needsClassSupport': oldEmitter.needsClassSupport,
'needsStructuredMemberInfo': oldEmitter.needsStructuredMemberInfo});
}
String readString(String array, String index) {
return readChecked(
array, index, 'result != null && typeof result != "string"', 'string');
}
String readInt(String array, String index) {
return readChecked(
array, index,
'result != null && (typeof result != "number" || (result|0) !== result)',
'int');
}
String readFunctionType(String array, String index) {
return readChecked(
array, index,
'result != null && '
'(typeof result != "number" || (result|0) !== result) && '
'typeof result != "function"',
'function or int');
}
String readChecked(String array, String index, String check, String type) {
if (!VALIDATE_DATA) return '$array[$index]';
return '''
(function() {
var result = $array[$index];
if ($check) {
throw new Error(
name + ": expected value of type \'$type\' at index " + ($index) +
" but got " + (typeof result));
}
return result;
})()''';
}