mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-04 14:58:37 +09:00
Stage 1-3: Generate better source in Object.prototype.toSource.
Tag #960.
This commit is contained in:
parent
9081722152
commit
f2622bd19f
1 changed files with 193 additions and 87 deletions
|
|
@ -9,11 +9,13 @@
|
|||
#include "mozilla/ArrayUtils.h"
|
||||
|
||||
#include "jscntxt.h"
|
||||
#include "jsstr.h"
|
||||
|
||||
#include "builtin/Eval.h"
|
||||
#include "frontend/BytecodeCompiler.h"
|
||||
#include "jit/InlinableNatives.h"
|
||||
#include "js/UniquePtr.h"
|
||||
#include "vm/AsyncFunction.h"
|
||||
#include "vm/StringBuffer.h"
|
||||
|
||||
#include "jsobjinlines.h"
|
||||
|
|
@ -124,6 +126,27 @@ obj_toSource(JSContext* cx, unsigned argc, Value* vp)
|
|||
return true;
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
static bool
|
||||
Consume(const CharT*& s, const CharT* e, const char *chars)
|
||||
{
|
||||
size_t len = strlen(chars);
|
||||
if (s + len >= e)
|
||||
return false;
|
||||
if (!EqualChars(s, chars, len))
|
||||
return false;
|
||||
s += len;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
static void
|
||||
ConsumeSpaces(const CharT*& s, const CharT* e)
|
||||
{
|
||||
while (*s == ' ' && s < e)
|
||||
s++;
|
||||
}
|
||||
|
||||
/*
|
||||
* Given a function source string, return the offset and length of the part
|
||||
* between '(function $name' and ')'.
|
||||
|
|
@ -133,37 +156,53 @@ static bool
|
|||
ArgsAndBodySubstring(mozilla::Range<const CharT> chars, size_t* outOffset, size_t* outLen)
|
||||
{
|
||||
const CharT* const start = chars.begin().get();
|
||||
const CharT* const end = chars.end().get();
|
||||
const CharT* s = start;
|
||||
const CharT* e = chars.end().get();
|
||||
|
||||
uint8_t parenChomp = 0;
|
||||
if (s[0] == '(') {
|
||||
if (s == e)
|
||||
return false;
|
||||
|
||||
// Remove enclosing parentheses.
|
||||
if (*s == '(' && *(e - 1) == ')') {
|
||||
s++;
|
||||
parenChomp = 1;
|
||||
e--;
|
||||
}
|
||||
|
||||
/* Try to jump "function" keyword. */
|
||||
s = js_strchr_limit(s, ' ', end);
|
||||
if (!s)
|
||||
return false;
|
||||
(void) Consume(s, e, "async");
|
||||
ConsumeSpaces(s, e);
|
||||
(void) (Consume(s, e, "function") || Consume(s, e, "get") || Consume(s, e, "set"));
|
||||
ConsumeSpaces(s, e);
|
||||
(void) Consume(s, e, "*");
|
||||
ConsumeSpaces(s, e);
|
||||
|
||||
/*
|
||||
* Jump over the function's name: it can't be encoded as part
|
||||
* of an ECMA getter or setter.
|
||||
*/
|
||||
s = js_strchr_limit(s, '(', end);
|
||||
if (!s)
|
||||
return false;
|
||||
|
||||
if (*s == ' ')
|
||||
// Jump over the function's name.
|
||||
if (Consume(s, e, "[")) {
|
||||
s = js_strchr_limit(s, ']', e);
|
||||
if (!s)
|
||||
return false;
|
||||
s++;
|
||||
ConsumeSpaces(s, e);
|
||||
if (*s != '(')
|
||||
return false;
|
||||
} else {
|
||||
s = js_strchr_limit(s, '(', e);
|
||||
if (!s)
|
||||
return false;
|
||||
}
|
||||
|
||||
*outOffset = s - start;
|
||||
*outLen = end - s - parenChomp;
|
||||
*outLen = e - s;
|
||||
MOZ_ASSERT(*outOffset + *outLen <= chars.length());
|
||||
return true;
|
||||
}
|
||||
|
||||
enum class PropertyKind {
|
||||
Getter,
|
||||
Setter,
|
||||
Method,
|
||||
Normal
|
||||
};
|
||||
|
||||
JSString*
|
||||
js::ObjectToSource(JSContext* cx, HandleObject obj)
|
||||
{
|
||||
|
|
@ -182,59 +221,28 @@ js::ObjectToSource(JSContext* cx, HandleObject obj)
|
|||
if (!buf.append('{'))
|
||||
return nullptr;
|
||||
|
||||
RootedValue v0(cx), v1(cx);
|
||||
MutableHandleValue val[2] = {&v0, &v1};
|
||||
|
||||
RootedString str0(cx), str1(cx);
|
||||
MutableHandleString gsop[2] = {&str0, &str1};
|
||||
|
||||
AutoIdVector idv(cx);
|
||||
if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &idv))
|
||||
return nullptr;
|
||||
|
||||
bool comma = false;
|
||||
for (size_t i = 0; i < idv.length(); ++i) {
|
||||
RootedId id(cx, idv[i]);
|
||||
Rooted<PropertyDescriptor> desc(cx);
|
||||
if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
|
||||
return nullptr;
|
||||
|
||||
int valcnt = 0;
|
||||
if (desc.object()) {
|
||||
if (desc.isAccessorDescriptor()) {
|
||||
if (desc.hasGetterObject() && desc.getterObject()) {
|
||||
val[valcnt].setObject(*desc.getterObject());
|
||||
gsop[valcnt].set(cx->names().get);
|
||||
valcnt++;
|
||||
}
|
||||
if (desc.hasSetterObject() && desc.setterObject()) {
|
||||
val[valcnt].setObject(*desc.setterObject());
|
||||
gsop[valcnt].set(cx->names().set);
|
||||
valcnt++;
|
||||
}
|
||||
} else {
|
||||
valcnt = 1;
|
||||
val[0].set(desc.value());
|
||||
gsop[0].set(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto AddProperty = [cx, &comma, &buf](HandleId id, HandleValue val, PropertyKind kind) -> bool {
|
||||
/* Convert id to a string. */
|
||||
RootedString idstr(cx);
|
||||
if (JSID_IS_SYMBOL(id)) {
|
||||
RootedValue v(cx, SymbolValue(JSID_TO_SYMBOL(id)));
|
||||
idstr = ValueToSource(cx, v);
|
||||
if (!idstr)
|
||||
return nullptr;
|
||||
return false;
|
||||
} else {
|
||||
RootedValue idv(cx, IdToValue(id));
|
||||
idstr = ToString<CanGC>(cx, idv);
|
||||
if (!idstr)
|
||||
return nullptr;
|
||||
return false;
|
||||
|
||||
/*
|
||||
* If id is a string that's not an identifier, or if it's a negative
|
||||
* integer, then it must be quoted.
|
||||
* If id is a string that's not an identifier, or if it's a
|
||||
* negative integer, then it must be quoted.
|
||||
*/
|
||||
if (JSID_IS_ATOM(id)
|
||||
? !IsIdentifier(JSID_TO_ATOM(id))
|
||||
|
|
@ -242,28 +250,65 @@ js::ObjectToSource(JSContext* cx, HandleObject obj)
|
|||
{
|
||||
idstr = QuoteString(cx, idstr, char16_t('\''));
|
||||
if (!idstr)
|
||||
return nullptr;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (int j = 0; j < valcnt; j++) {
|
||||
/* Convert val[j] to its canonical source form. */
|
||||
JSString* valsource = ValueToSource(cx, val[j]);
|
||||
if (!valsource)
|
||||
return nullptr;
|
||||
RootedString valsource(cx, ValueToSource(cx, val));
|
||||
if (!valsource)
|
||||
return false;
|
||||
|
||||
RootedLinearString valstr(cx, valsource->ensureLinear(cx));
|
||||
if (!valstr)
|
||||
return nullptr;
|
||||
RootedLinearString valstr(cx, valsource->ensureLinear(cx));
|
||||
if (!valstr)
|
||||
return false;
|
||||
|
||||
size_t voffset = 0;
|
||||
size_t vlength = valstr->length();
|
||||
if (comma && !buf.append(", "))
|
||||
return false;
|
||||
comma = true;
|
||||
|
||||
/*
|
||||
* Remove '(function ' from the beginning of valstr and ')' from the
|
||||
* end so that we can put "get" in front of the function definition.
|
||||
*/
|
||||
if (gsop[j] && IsFunctionObject(val[j])) {
|
||||
size_t voffset, vlength;
|
||||
|
||||
// Methods and accessors can return exact syntax of source, that fits
|
||||
// into property without adding property name or "get"/"set" prefix.
|
||||
// Use the exact syntax when the following conditions are met:
|
||||
//
|
||||
// * It's a function object
|
||||
// (exclude proxies)
|
||||
// * Function's kind and property's kind are same
|
||||
// (this can be false for dynamically defined properties)
|
||||
// * Function has explicit name
|
||||
// (this can be false for computed property and dynamically defined
|
||||
// properties)
|
||||
// * Function's name and property's name are same
|
||||
// (this can be false for dynamically defined properties)
|
||||
if (kind == PropertyKind::Getter || kind == PropertyKind::Setter ||
|
||||
kind == PropertyKind::Method)
|
||||
{
|
||||
RootedFunction fun(cx);
|
||||
if (val.toObject().is<JSFunction>()) {
|
||||
fun = &val.toObject().as<JSFunction>();
|
||||
// Method's case should be checked on caller.
|
||||
if (((fun->isGetter() && kind == PropertyKind::Getter) ||
|
||||
(fun->isSetter() && kind == PropertyKind::Setter) ||
|
||||
kind == PropertyKind::Method) &&
|
||||
fun->explicitName())
|
||||
{
|
||||
bool result;
|
||||
if (!EqualStrings(cx, fun->explicitName(), idstr, &result))
|
||||
return false;
|
||||
|
||||
if (result) {
|
||||
if (!buf.append(valstr))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// When falling back try to generate a better string
|
||||
// representation by skipping the prelude, and also removing
|
||||
// the enclosing parentheses.
|
||||
bool success;
|
||||
JS::AutoCheckCannotGC nogc;
|
||||
if (valstr->hasLatin1Chars())
|
||||
|
|
@ -271,29 +316,90 @@ js::ObjectToSource(JSContext* cx, HandleObject obj)
|
|||
else
|
||||
success = ArgsAndBodySubstring(valstr->twoByteRange(nogc), &voffset, &vlength);
|
||||
if (!success)
|
||||
gsop[j].set(nullptr);
|
||||
kind = PropertyKind::Normal;
|
||||
}
|
||||
|
||||
if (comma && !buf.append(", "))
|
||||
return nullptr;
|
||||
comma = true;
|
||||
if (kind == PropertyKind::Getter) {
|
||||
if (!buf.append("get "))
|
||||
return false;
|
||||
} else if (kind == PropertyKind::Setter) {
|
||||
if (!buf.append("set "))
|
||||
return false;
|
||||
} else if (kind == PropertyKind::Method && fun) {
|
||||
if (IsWrappedAsyncFunction(fun)) {
|
||||
if (!buf.append("async "))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gsop[j]) {
|
||||
if (!buf.append(gsop[j]) || !buf.append(' '))
|
||||
if (fun->isStarGenerator()) {
|
||||
if (!buf.append('*'))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool needsBracket = JSID_IS_SYMBOL(id);
|
||||
if (needsBracket && !buf.append('['))
|
||||
return false;
|
||||
if (!buf.append(idstr))
|
||||
return false;
|
||||
if (needsBracket && !buf.append(']'))
|
||||
return false;
|
||||
|
||||
if (kind == PropertyKind::Getter || kind == PropertyKind::Setter ||
|
||||
kind == PropertyKind::Method)
|
||||
{
|
||||
if (!buf.appendSubstring(valstr, voffset, vlength))
|
||||
return false;
|
||||
} else {
|
||||
if (!buf.append(':'))
|
||||
return false;
|
||||
if (!buf.append(valstr))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
RootedId id(cx);
|
||||
Rooted<PropertyDescriptor> desc(cx);
|
||||
RootedValue val(cx);
|
||||
RootedFunction fun(cx);
|
||||
for (size_t i = 0; i < idv.length(); ++i) {
|
||||
id = idv[i];
|
||||
if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
|
||||
return nullptr;
|
||||
|
||||
if (!desc.object())
|
||||
continue;
|
||||
|
||||
if (desc.isAccessorDescriptor()) {
|
||||
if (desc.hasGetterObject() && desc.getterObject()) {
|
||||
val.setObject(*desc.getterObject());
|
||||
if (!AddProperty(id, val, PropertyKind::Getter))
|
||||
return nullptr;
|
||||
}
|
||||
if (JSID_IS_SYMBOL(id) && !buf.append('['))
|
||||
return nullptr;
|
||||
if (!buf.append(idstr))
|
||||
return nullptr;
|
||||
if (JSID_IS_SYMBOL(id) && !buf.append(']'))
|
||||
return nullptr;
|
||||
if (!buf.append(gsop[j] ? ' ' : ':'))
|
||||
return nullptr;
|
||||
|
||||
if (!buf.appendSubstring(valstr, voffset, vlength))
|
||||
return nullptr;
|
||||
if (desc.hasSetterObject() && desc.setterObject()) {
|
||||
val.setObject(*desc.setterObject());
|
||||
if (!AddProperty(id, val, PropertyKind::Setter))
|
||||
return nullptr;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
val.set(desc.value());
|
||||
if (IsFunctionObject(val, fun.address())) {
|
||||
if (IsWrappedAsyncFunction(fun))
|
||||
fun = GetUnwrappedAsyncFunction(fun);
|
||||
|
||||
if (fun->isMethod()) {
|
||||
if (!AddProperty(id, val, PropertyKind::Method))
|
||||
return nullptr;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!AddProperty(id, val, PropertyKind::Normal))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!buf.append('}'))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue