NewLiteralStorage.java
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import static org.mozilla.javascript.NativeObject.PROTO_PROPERTY;
import java.util.Arrays;
/** Used to store the support structures for a literal object (or array) being built. */
public abstract class NewLiteralStorage {
protected Object[] keys;
protected int[] getterSetters;
protected Object[] values;
protected int index = 0;
protected NewLiteralStorage(Object[] ids, int length, boolean createKeys) {
int l;
if (ids != null) {
this.keys = ids;
l = ids.length;
} else {
this.keys = createKeys ? new Object[length] : null;
l = length;
}
this.getterSetters = new int[l];
this.values = new Object[l];
}
public void pushValue(Object value) {
values[index] = value;
attemptToInferFunctionName(value);
++index;
}
public void pushGetter(Object value) {
getterSetters[index] = -1;
pushValue(value);
}
public void pushSetter(Object value) {
getterSetters[index] = +1;
pushValue(value);
}
public void pushKey(Object key) {
if (key instanceof Symbol) {
keys[index] = key;
} else {
keys[index] = ScriptRuntime.toString(key);
}
}
public void spread(Context cx, Scriptable scope, Object source) {
// See ECMAScript 13.2.5.5
if (source != null && !Undefined.isUndefined(source)) {
Scriptable src = ScriptRuntime.toObject(cx, scope, source);
Object[] ids;
if (src instanceof ScriptableObject) {
ids = ((ScriptableObject) src).getIds(false, true);
} else {
ids = src.getIds();
}
// Resize all the arrays
int newLen = values.length + ids.length;
keys = Arrays.copyOf(keys, newLen);
getterSetters = Arrays.copyOf(getterSetters, newLen);
values = Arrays.copyOf(values, newLen);
// getIds() can only return a string, int or a symbol
for (Object id : ids) {
Object value;
if (id instanceof String) {
value = ScriptableObject.getProperty(src, (String) id);
} else if (id instanceof Integer) {
value = ScriptableObject.getProperty(src, (int) id);
} else if (ScriptRuntime.isSymbol(id)) {
value = ScriptableObject.getProperty(src, (Symbol) id);
} else {
throw Kit.codeBug();
}
pushKey(id);
pushValue(value);
}
}
}
public Object[] getKeys() {
return keys;
}
public int[] getGetterSetters() {
return getterSetters;
}
public Object[] getValues() {
return values;
}
public static NewLiteralStorage create(Context cx, Object[] ids) {
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
return new NameInference(ids, -1, false);
} else {
return new NoInference(ids, -1, false);
}
}
public static NewLiteralStorage create(Context cx, int length, boolean createKeys) {
if (cx.getLanguageVersion() >= Context.VERSION_ES6) {
return new NameInference(null, length, createKeys);
} else {
return new NoInference(null, length, createKeys);
}
}
// In version < ES6, we don't do name inference for functions. Thus, we have NewLiteralStorage
// as an abstract class, with two subclasses, depending on the language version. In this way, we
// pay the cost of the check only once, rather than for each key.
protected abstract void attemptToInferFunctionName(Object value);
private static final class NoInference extends NewLiteralStorage {
NoInference(Object[] ids, int length, boolean createKeys) {
super(ids, length, createKeys);
}
@Override
protected void attemptToInferFunctionName(Object value) {
// Do nothing
}
}
private static final class NameInference extends NewLiteralStorage {
NameInference(Object[] ids, int length, boolean createKeys) {
super(ids, length, createKeys);
}
@Override
protected void attemptToInferFunctionName(Object value) {
// Try to infer the name if the value is a normal JS function
if (this.keys == null
|| (!(value instanceof NativeFunction) && !(value instanceof ArrowFunction))) {
return;
}
BaseFunction fun = (BaseFunction) value;
if (!"".equals(fun.get("name", fun))) {
return;
}
String prefix = "";
if (getterSetters[index] == -1) {
prefix = "get ";
} else if (getterSetters[index] == +1) {
prefix = "set ";
}
Object propKey = this.keys[index];
if (propKey instanceof Symbol) {
// For symbol keys, valid names are: `[foo]`, `get [foo]`
// However `[]` or `get []` aren't, and become `` and `get `
String symbolName = ((Symbol) propKey).getName();
if (!symbolName.isEmpty()) {
fun.setFunctionName(prefix + "[" + symbolName + "]");
} else if (!prefix.isEmpty()) {
fun.setFunctionName(prefix);
}
} else {
// Key was already converted to a string
if (!propKey.equals(PROTO_PROPERTY)) {
fun.setFunctionName(prefix + propKey);
} else {
// `__proto__` is, as usual, weird and applies only to methods, meaning:
// - { __proto__(){} } infers the name
// - { __proto__: function(){} } does not!
if (fun instanceof NativeFunction && ((NativeFunction) fun).isShorthand()) {
fun.setFunctionName(prefix + propKey);
}
}
}
}
}
}