From 90f6b2704658a554a12ed3c37545cb6474fbcf51 Mon Sep 17 00:00:00 2001 From: wolfbeast Date: Sat, 2 Feb 2019 14:29:54 +0100 Subject: Stage 1-3: Generate better source in Object.prototype.toSource. Tag #960. --- js/src/builtin/Object.cpp | 280 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 193 insertions(+), 87 deletions(-) (limited to 'js') diff --git a/js/src/builtin/Object.cpp b/js/src/builtin/Object.cpp index cd4ac122c..389bb57db 100644 --- a/js/src/builtin/Object.cpp +++ b/js/src/builtin/Object.cpp @@ -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 +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 +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 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] == '(') { - s++; - parenChomp = 1; - } - - /* Try to jump "function" keyword. */ - s = js_strchr_limit(s, ' ', end); - if (!s) + if (s == e) return false; - /* - * 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; + // Remove enclosing parentheses. + if (*s == '(' && *(e - 1) == ')') { + s++; + e--; + } - if (*s == ' ') + (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. + 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 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(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; + + if (comma && !buf.append(", ")) + return false; + comma = true; + + 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()) { + fun = &val.toObject().as(); + // 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; - size_t voffset = 0; - size_t vlength = valstr->length(); + if (result) { + if (!buf.append(valstr)) + return false; + return 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])) { + { + // 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(' ')) - return nullptr; + if (fun->isStarGenerator()) { + if (!buf.append('*')) + return false; + } } - 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; + } + 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 nullptr; + return false; + } else { + if (!buf.append(':')) + return false; + if (!buf.append(valstr)) + return false; + } + return true; + }; + + RootedId id(cx); + Rooted 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 (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('}')) -- cgit v1.2.3