From 159df826fb27e140daa86fb403316f95b86130f5 Mon Sep 17 00:00:00 2001 From: Shu-yu Guo Date: Mon, 17 Apr 2017 19:51:34 -0700 Subject: [PATCH] Bug 1216630 - Print class source when calling toString on the constructor. (r=Yoric) This is accomplished in the following ways. LazyScripts and JSScripts now have 4 offsets: - Source begin and end for the actual source. This is used for lazy parsing. - toString begin and end for toString. Some kinds of functions, like async, only have a different begin offset. Class constructors have different offsets for both begin and end. For syntactically present (i.e. non-default) constructors, the class source span is remembered directly on the LazyScript or JSScript. The toString implementation then splices out the substring directly. For default constructors, a new SRC_CLASS SrcNote type is added. It's binary and has as its arguments the begin and end offsets of the class expression or statement. MakeDefaultConstructor reads the note and overrides the cloned self-hosted function's source object. This is probably the least intrusive way to accomplish this. --- js/src/frontend/BytecodeCompiler.cpp | 16 ++++-- js/src/frontend/BytecodeEmitter.cpp | 10 +++- js/src/frontend/FullParseHandler.h | 6 +- js/src/frontend/ParseNode.h | 5 +- js/src/frontend/Parser.cpp | 85 ++++++++++++++++++++-------- js/src/frontend/Parser.h | 37 ++++++++++++ js/src/frontend/SharedContext.h | 11 ++++ js/src/frontend/SourceNotes.h | 3 +- js/src/frontend/SyntaxParseHandler.h | 2 +- js/src/jsfun.cpp | 41 +++++++------- js/src/jsscript.cpp | 35 ++++++++++-- js/src/jsscript.h | 39 +++++++++++-- js/src/shell/js.cpp | 8 +++ js/src/vm/Interpreter.cpp | 25 ++++++-- 14 files changed, 248 insertions(+), 75 deletions(-) diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index bec2376b719f..ed363f065934 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -61,7 +61,13 @@ class MOZ_STACK_CLASS BytecodeCompiler bool canLazilyParse(); bool createParser(); bool createSourceAndParser(const Maybe& parameterListEnd = Nothing()); - bool createScript(uint32_t preludeStart = 0); + + // If toString{Start,End} are not explicitly passed, assume the script's + // offsets in the source used to parse it are the same as what should be + // used to compute its Function.prototype.toString() value. + bool createScript(); + bool createScript(uint32_t toStringStart, uint32_t toStringEnd); + bool emplaceEmitter(Maybe& emitter, SharedContext* sharedContext); bool handleParseFailure(const Directives& newDirectives); bool deoptimizeArgumentsInEnclosingScripts(JSContext* cx, HandleObject environment); @@ -292,11 +298,11 @@ BytecodeCompiler::createSourceAndParser(const Maybe& parameterListEnd } bool -BytecodeCompiler::createScript(uint32_t preludeStart /* = 0 */) +BytecodeCompiler::createScript(uint32_t preludeStart /* = 0 */, uint32_t postludeEnd /* = 0 */) { script = JSScript::Create(cx, options, sourceObject, /* sourceStart = */ 0, sourceBuffer.length(), - preludeStart); + preludeStart, postludeEnd); return script != nullptr; } @@ -508,7 +514,7 @@ BytecodeCompiler::compileStandaloneFunction(MutableHandleFunction fun, if (fn->pn_funbox->function()->isInterpreted()) { MOZ_ASSERT(fun == fn->pn_funbox->function()); - if (!createScript(fn->pn_funbox->preludeStart)) + if (!createScript(fn->pn_funbox->preludeStart, fn->pn_funbox->postludeEnd)) return false; Maybe emitter; @@ -729,7 +735,7 @@ frontend::CompileLazyFunction(JSContext* cx, Handle lazy, const cha Rooted script(cx, JSScript::Create(cx, options, sourceObject, lazy->begin(), lazy->end(), - lazy->preludeStart())); + lazy->preludeStart(), lazy->postludeEnd())); if (!script) return false; diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 872c41dc5c6c..995ce7bd7bc2 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -7949,7 +7949,8 @@ BytecodeEmitter::emitFunction(ParseNode* pn, bool needsProto) Rooted sourceObject(cx, script->sourceObject()); Rooted script(cx, JSScript::Create(cx, options, sourceObject, funbox->bufStart, funbox->bufEnd, - funbox->preludeStart)); + funbox->preludeStart, + funbox->postludeEnd)); if (!script) return false; @@ -10469,6 +10470,13 @@ BytecodeEmitter::emitClass(ParseNode* pn) return false; } } else { + // In the case of default class constructors, emit the start and end + // offsets in the source buffer as source notes so that when we + // actually make the constructor during execution, we can give it the + // correct toString output. + if (!newSrcNote3(SRC_CLASS_SPAN, ptrdiff_t(pn->pn_pos.begin), ptrdiff_t(pn->pn_pos.end))) + return false; + JSAtom *name = names ? names->innerBinding()->pn_atom : cx->names().empty; if (heritageExpression) { if (!emitAtomOp(name, JSOP_DERIVEDCONSTRUCTOR)) diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index 01339850cd5d..a1e2ba7b165b 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -323,8 +323,10 @@ class FullParseHandler return literal; } - ParseNode* newClass(ParseNode* name, ParseNode* heritage, ParseNode* methodBlock) { - return new_(name, heritage, methodBlock); + ParseNode* newClass(ParseNode* name, ParseNode* heritage, ParseNode* methodBlock, + const TokenPos& pos) + { + return new_(name, heritage, methodBlock, pos); } ParseNode* newClassMethodList(uint32_t begin) { return new_(PNK_CLASSMETHODLIST, TokenPos(begin, begin + 1)); diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index 3ba3128f0e41..40d16690e8cd 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -1311,8 +1311,9 @@ struct ClassNames : public BinaryNode { }; struct ClassNode : public TernaryNode { - ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock) - : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodsOrBlock) + ClassNode(ParseNode* names, ParseNode* heritage, ParseNode* methodsOrBlock, + const TokenPos& pos) + : TernaryNode(PNK_CLASS, JSOP_NOP, names, heritage, methodsOrBlock, pos) { MOZ_ASSERT_IF(names, names->is()); MOZ_ASSERT(methodsOrBlock->is() || diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index ce5079613b27..512b8658b6a6 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -142,7 +142,8 @@ StatementKindIsBraced(StatementKind kind) kind == StatementKind::Switch || kind == StatementKind::Try || kind == StatementKind::Catch || - kind == StatementKind::Finally; + kind == StatementKind::Finally || + kind == StatementKind::Class; } void @@ -473,6 +474,7 @@ FunctionBox::FunctionBox(JSContext* cx, LifoAlloc& alloc, ObjectBox* traceListHe startLine(1), startColumn(0), preludeStart(preludeStart), + postludeEnd(0), length(0), generatorKindBits_(GeneratorKindAsBits(generatorKind)), asyncKindBits_(AsyncKindAsBits(asyncKind)), @@ -543,10 +545,16 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt allowNewTarget_ = true; allowSuperProperty_ = fun->allowSuperProperty(); - if (kind == DerivedClassConstructor) { - setDerivedClassConstructor(); - allowSuperCall_ = true; - needsThisTDZChecks_ = true; + if (kind == ClassConstructor || kind == DerivedClassConstructor) { + auto stmt = enclosing->findInnermostStatement(); + MOZ_ASSERT(stmt); + stmt->setConstructorBox(this); + + if (kind == DerivedClassConstructor) { + setDerivedClassConstructor(); + allowSuperCall_ = true; + needsThisTDZChecks_ = true; + } } if (isGenexpLambda) @@ -566,6 +574,16 @@ FunctionBox::initWithEnclosingParseContext(ParseContext* enclosing, FunctionSynt } } +void +FunctionBox::resetForAbortedSyntaxParse(ParseContext* enclosing, FunctionSyntaxKind kind) +{ + if (kind == ClassConstructor || kind == DerivedClassConstructor) { + auto stmt = enclosing->findInnermostStatement(); + MOZ_ASSERT(stmt); + stmt->clearConstructorBoxForAbortedSyntaxParse(this); + } +} + void FunctionBox::initWithEnclosingScope(Scope* enclosingScope) { @@ -3434,6 +3452,7 @@ Parser::trySyntaxParseInnerFunction(ParseNode* pn, HandleFunct // correctness. parser->clearAbortedSyntaxParse(); usedNames.rewind(token); + funbox->resetForAbortedSyntaxParse(pc, kind); MOZ_ASSERT_IF(!parser->context->helperThread(), !parser->context->isExceptionPending()); break; @@ -3725,14 +3744,14 @@ Parser::functionFormalParametersAndBody(InHandling inHandling, MUST_MATCH_TOKEN_MOD_WITH_REPORT(TOK_RC, TokenStream::Operand, reportMissingClosing(JSMSG_CURLY_AFTER_BODY, JSMSG_CURLY_OPENED, openedPos)); - funbox->bufEnd = pos().end; + funbox->setEnd(pos().end); } else { #if !JS_HAS_EXPR_CLOSURES MOZ_ASSERT(kind == Arrow); #endif if (tokenStream.hadError()) return false; - funbox->bufEnd = pos().end; + funbox->setEnd(pos().end); if (kind == Statement && !matchOrInsertSemicolonAfterExpression()) return false; } @@ -7023,6 +7042,7 @@ Parser::classDefinition(YieldHandling yieldHandling, { MOZ_ASSERT(tokenStream.isCurrentTokenType(TOK_CLASS)); + uint32_t classStartOffset = pos().begin; bool savedStrictness = setLocalStrictMode(true); TokenKind tt; @@ -7048,16 +7068,20 @@ Parser::classDefinition(YieldHandling yieldHandling, tokenStream.ungetToken(); } + // Push a ParseContext::ClassStatement to keep track of the constructor + // funbox. + ParseContext::ClassStatement classStmt(pc); + RootedAtom propAtom(context); // A named class creates a new lexical scope with a const binding of the - // class name. - Maybe classStmt; - Maybe classScope; + // class name for the "inner name". + Maybe innerScopeStmt; + Maybe innerScope; if (name) { - classStmt.emplace(pc, StatementKind::Block); - classScope.emplace(this); - if (!classScope->init(pc)) + innerScopeStmt.emplace(pc, StatementKind::Block); + innerScope.emplace(this); + if (!innerScope->init(pc)) return null(); } @@ -7084,7 +7108,6 @@ Parser::classDefinition(YieldHandling yieldHandling, if (!classMethods) return null(); - bool seenConstructor = false; for (;;) { TokenKind tt; if (!tokenStream.getToken(&tt)) @@ -7135,16 +7158,17 @@ Parser::classDefinition(YieldHandling yieldHandling, propType = PropertyType::GetterNoExpressionClosure; if (propType == PropertyType::Setter) propType = PropertyType::SetterNoExpressionClosure; - if (!isStatic && propAtom == context->names().constructor) { + + bool isConstructor = !isStatic && propAtom == context->names().constructor; + if (isConstructor) { if (propType != PropertyType::Method) { errorAt(nameOffset, JSMSG_BAD_METHOD_DEF); return null(); } - if (seenConstructor) { + if (classStmt.constructorBox()) { errorAt(nameOffset, JSMSG_DUPLICATE_PROPERTY, "constructor"); return null(); } - seenConstructor = true; propType = hasHeritage ? PropertyType::DerivedConstructor : PropertyType::Constructor; } else if (isStatic && propAtom == context->names().prototype) { errorAt(nameOffset, JSMSG_BAD_METHOD_DEF); @@ -7169,7 +7193,12 @@ Parser::classDefinition(YieldHandling yieldHandling, if (!tokenStream.isCurrentTokenType(TOK_RB)) funName = propAtom; } - Node fn = methodDefinition(nameOffset, propType, funName); + + // Calling toString on constructors need to return the source text for + // the entire class. The end offset is unknown at this point in + // parsing and will be amended when class parsing finishes below. + Node fn = methodDefinition(isConstructor ? classStartOffset : nameOffset, + propType, funName); if (!fn) return null(); @@ -7180,6 +7209,15 @@ Parser::classDefinition(YieldHandling yieldHandling, return null(); } + // Amend the postlude offset for the constructor now that we've finished + // parsing the class. + uint32_t classEndOffset = pos().end; + if (FunctionBox* ctorbox = classStmt.constructorBox()) { + if (ctorbox->function()->isInterpretedLazy()) + ctorbox->function()->lazyScript()->setPostludeEnd(classEndOffset); + ctorbox->postludeEnd = classEndOffset; + } + Node nameNode = null(); Node methodsOrBlock = classMethods; if (name) { @@ -7191,15 +7229,15 @@ Parser::classDefinition(YieldHandling yieldHandling, if (!innerName) return null(); - Node classBlock = finishLexicalScope(*classScope, classMethods); + Node classBlock = finishLexicalScope(*innerScope, classMethods); if (!classBlock) return null(); methodsOrBlock = classBlock; // Pop the inner scope. - classScope.reset(); - classStmt.reset(); + innerScope.reset(); + innerScopeStmt.reset(); Node outerName = null(); if (classContext == ClassStatement) { @@ -7219,7 +7257,8 @@ Parser::classDefinition(YieldHandling yieldHandling, MOZ_ALWAYS_TRUE(setLocalStrictMode(savedStrictness)); - return handler.newClass(nameNode, classHeritage, methodsOrBlock); + return handler.newClass(nameNode, classHeritage, methodsOrBlock, + TokenPos(classStartOffset, classEndOffset)); } template @@ -8484,7 +8523,7 @@ Parser::generatorComprehensionLambda(unsigned begin) uint32_t end = pos().end; handler.setBeginPosition(comp, begin); handler.setEndPosition(comp, end); - genFunbox->bufEnd = end; + genFunbox->setEnd(end); handler.addStatementToList(body, comp); handler.setEndPosition(body, end); handler.setBeginPosition(genfn, begin); diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 16eaa6570ce1..fbd691ca6246 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -87,6 +87,31 @@ class ParseContext : public Nestable } }; + class ClassStatement : public Statement + { + FunctionBox* constructorBox_; + + public: + explicit ClassStatement(ParseContext* pc) + : Statement(pc, StatementKind::Class), + constructorBox_(nullptr) + { } + + void clearConstructorBoxForAbortedSyntaxParse(FunctionBox* funbox) { + MOZ_ASSERT(constructorBox_ == funbox); + constructorBox_ = nullptr; + } + + void setConstructorBox(FunctionBox* funbox) { + MOZ_ASSERT(!constructorBox_); + constructorBox_ = funbox; + } + + FunctionBox* constructorBox() const { + return constructorBox_; + } + }; + // The intra-function scope stack. // // Tracks declared and used names within a scope. @@ -442,6 +467,11 @@ class ParseContext : public Nestable return Statement::findNearest(innermostStatement_, predicate); } + template + T* findInnermostStatement() { + return Statement::findNearest(innermostStatement_); + } + AtomVector& positionalFormalParameterNames() { return *positionalFormalParameterNames_; } @@ -542,6 +572,13 @@ ParseContext::Statement::is() const return kind_ == StatementKind::Label; } +template <> +inline bool +ParseContext::Statement::is() const +{ + return kind_ == StatementKind::Class; +} + template inline T& ParseContext::Statement::as() diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index c150ac902ba3..ab7d378ea976 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -38,6 +38,7 @@ enum class StatementKind : uint8_t ForOfLoop, DoLoop, WhileLoop, + Class, // Used only by BytecodeEmitter. Spread @@ -451,6 +452,7 @@ class FunctionBox : public ObjectBox, public SharedContext uint32_t startLine; uint32_t startColumn; uint32_t preludeStart; + uint32_t postludeEnd; uint16_t length; uint8_t generatorKindBits_; /* The GeneratorKind of this function. */ @@ -501,6 +503,7 @@ class FunctionBox : public ObjectBox, public SharedContext void initFromLazyFunction(); void initStandaloneFunction(Scope* enclosingScope); void initWithEnclosingParseContext(ParseContext* enclosing, FunctionSyntaxKind kind); + void resetForAbortedSyntaxParse(ParseContext* enclosing, FunctionSyntaxKind kind); ObjectBox* toObjectBox() override { return this; } JSFunction* function() const { return &object->as(); } @@ -615,6 +618,14 @@ class FunctionBox : public ObjectBox, public SharedContext tokenStream.srcCoords.lineNumAndColumnIndex(bufStart, &startLine, &startColumn); } + void setEnd(uint32_t end) { + // For all functions except class constructors, the buffer and + // postlude ending positions are the same. Class constructors override + // the postlude ending position with the end of the class definition. + bufEnd = end; + postludeEnd = end; + } + void trace(JSTracer* trc) override; }; diff --git a/js/src/frontend/SourceNotes.h b/js/src/frontend/SourceNotes.h index dd2a95ad1e58..6ae184ae4bed 100644 --- a/js/src/frontend/SourceNotes.h +++ b/js/src/frontend/SourceNotes.h @@ -56,13 +56,14 @@ namespace js { M(SRC_NEXTCASE, "nextcase", 1) /* Distance forward from one CASE in a CONDSWITCH to \ the next. */ \ M(SRC_ASSIGNOP, "assignop", 0) /* += or another assign-op follows. */ \ + M(SRC_CLASS_SPAN, "class", 2) /* The starting and ending offsets for the class, used \ + for toString correctness for default ctors. */ \ M(SRC_TRY, "try", 1) /* JSOP_TRY, offset points to goto at the end of the \ try block. */ \ /* All notes above here are "gettable". See SN_IS_GETTABLE below. */ \ M(SRC_COLSPAN, "colspan", 1) /* Number of columns this opcode spans. */ \ M(SRC_NEWLINE, "newline", 0) /* Bytecode follows a source newline. */ \ M(SRC_SETLINE, "setline", 1) /* A file-absolute source line number note. */ \ - M(SRC_UNUSED20, "unused20", 0) /* Unused. */ \ M(SRC_UNUSED21, "unused21", 0) /* Unused. */ \ M(SRC_UNUSED22, "unused22", 0) /* Unused. */ \ M(SRC_UNUSED23, "unused23", 0) /* Unused. */ \ diff --git a/js/src/frontend/SyntaxParseHandler.h b/js/src/frontend/SyntaxParseHandler.h index 44e313fa9ef2..39a19ce1733b 100644 --- a/js/src/frontend/SyntaxParseHandler.h +++ b/js/src/frontend/SyntaxParseHandler.h @@ -287,7 +287,7 @@ class SyntaxParseHandler Node newObjectLiteral(uint32_t begin) { return NodeUnparenthesizedObject; } Node newClassMethodList(uint32_t begin) { return NodeGeneric; } Node newClassNames(Node outer, Node inner, const TokenPos& pos) { return NodeGeneric; } - Node newClass(Node name, Node heritage, Node methodBlock) { return NodeGeneric; } + Node newClass(Node name, Node heritage, Node methodBlock, const TokenPos& pos) { return NodeGeneric; } Node newNewTarget(Node newHolder, Node targetHolder) { return NodeGeneric; } Node newPosHolder(const TokenPos& pos) { return NodeGeneric; } diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index da8c1fc000ff..bced9dfdc2a2 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -882,7 +882,7 @@ CreateFunctionPrototype(JSContext* cx, JSProtoKey key) sourceObject, begin, ss->length(), - 0)); + 0, 0)); if (!script || !JSScript::initFunctionPrototype(cx, script, functionProto)) return nullptr; @@ -1019,7 +1019,13 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) } bool funIsNonArrowLambda = fun->isLambda() && !fun->isArrow(); - bool haveSource = fun->isInterpreted() && !fun->isSelfHostedBuiltin(); + + // Default class constructors are self-hosted, but have their source + // objects overridden to refer to the span of the class statement or + // expression. Non-default class constructors are never self-hosted. So, + // all class constructors always have source. + bool haveSource = fun->isInterpreted() && (fun->isClassConstructor() || + !fun->isSelfHostedBuiltin()); // If we're not in pretty mode, put parentheses around lambda functions // so that eval returns lambda, not function statement. @@ -1060,7 +1066,7 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) }; if (haveSource) { - Rooted src(cx, JSScript::sourceDataWithPrelude(cx, script)); + Rooted src(cx, JSScript::sourceDataForToString(cx, script)); if (!src) return nullptr; @@ -1080,27 +1086,18 @@ js::FunctionToString(JSContext* cx, HandleFunction fun, bool prettyPrint) return nullptr; } } else { - bool derived = fun->infallibleIsDefaultClassConstructor(cx); - if (derived && fun->isDerivedClassConstructor()) { - if (!AppendPrelude() || - !out.append("(...args) {\n ") || - !out.append("super(...args);\n}")) - { - return nullptr; - } - } else { - if (!AppendPrelude() || - !out.append("() {\n ")) - return nullptr; + // Default class constructors should always haveSource. + MOZ_ASSERT(!fun->infallibleIsDefaultClassConstructor(cx)); - if (!derived) { - if (!out.append("[native code]")) - return nullptr; - } + if (!AppendPrelude() || + !out.append("() {\n ")) + return nullptr; - if (!out.append("\n}")) - return nullptr; - } + if (!out.append("[native code]")) + return nullptr; + + if (!out.append("\n}")) + return nullptr; } return out.finishString(); } diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 919f4897d400..9ce039200095 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -238,6 +238,7 @@ XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, HandleScript scri uint32_t begin = script->sourceStart(); uint32_t end = script->sourceEnd(); uint32_t preludeStart = script->preludeStart(); + uint32_t postludeEnd = script->postludeEnd(); uint32_t lineno = script->lineno(); uint32_t column = script->column(); @@ -246,6 +247,7 @@ XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, HandleScript scri MOZ_ASSERT(begin == lazy->begin()); MOZ_ASSERT(end == lazy->end()); MOZ_ASSERT(preludeStart == lazy->preludeStart()); + MOZ_ASSERT(postludeEnd == lazy->postludeEnd()); MOZ_ASSERT(lineno == lazy->lineno()); MOZ_ASSERT(column == lazy->column()); // We can assert we have no inner functions because we don't @@ -261,6 +263,10 @@ XDRRelazificationInfo(XDRState* xdr, HandleFunction fun, HandleScript scri RootedScriptSource sourceObject(cx, &script->scriptSourceUnwrap()); lazy.set(LazyScript::Create(cx, fun, script, enclosingScope, sourceObject, packedFields, begin, end, preludeStart, lineno, column)); + if (!lazy) + return false; + + lazy->setPostludeEnd(postludeEnd); // As opposed to XDRLazyScript, we need to restore the runtime bits // of the script, as we are trying to match the fact this function @@ -545,7 +551,7 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, } } - script = JSScript::Create(cx, *options, sourceObject, 0, 0, 0); + script = JSScript::Create(cx, *options, sourceObject, 0, 0, 0, 0); if (!script) return false; @@ -636,6 +642,8 @@ js::XDRScript(XDRState* xdr, HandleScope scriptEnclosingScope, return false; if (!xdr->codeUint32(&script->preludeStart_)) return false; + if (!xdr->codeUint32(&script->postludeEnd_)) + return false; if (!xdr->codeUint32(&lineno) || !xdr->codeUint32(&column) || @@ -956,6 +964,7 @@ js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, uint32_t begin; uint32_t end; uint32_t preludeStart; + uint32_t postludeEnd; uint32_t lineno; uint32_t column; uint64_t packedFields; @@ -970,6 +979,7 @@ js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, begin = lazy->begin(); end = lazy->end(); preludeStart = lazy->preludeStart(); + postludeEnd = lazy->postludeEnd(); lineno = lazy->lineno(); column = lazy->column(); packedFields = lazy->packedFields(); @@ -977,6 +987,7 @@ js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, if (!xdr->codeUint32(&begin) || !xdr->codeUint32(&end) || !xdr->codeUint32(&preludeStart) || + !xdr->codeUint32(&postludeEnd) || !xdr->codeUint32(&lineno) || !xdr->codeUint32(&column) || !xdr->codeUint64(&packedFields)) { @@ -988,6 +999,7 @@ js::XDRLazyScript(XDRState* xdr, HandleScope enclosingScope, packedFields, begin, end, preludeStart, lineno, column)); if (!lazy) return false; + lazy->setPostludeEnd(postludeEnd); fun->initLazyScript(lazy); } } @@ -1031,6 +1043,15 @@ JSScript::setSourceObject(JSObject* object) sourceObject_ = object; } +void +JSScript::setDefaultClassConstructorSpan(JSObject* sourceObject, uint32_t start, uint32_t end) +{ + MOZ_ASSERT(isDefaultClassConstructor()); + setSourceObject(sourceObject); + preludeStart_ = start; + postludeEnd_ = end; +} + js::ScriptSourceObject& JSScript::scriptSourceUnwrap() const { return UncheckedUnwrap(sourceObject())->as(); @@ -1459,10 +1480,10 @@ JSScript::sourceData(JSContext* cx, HandleScript script) } /* static */ JSFlatString* -JSScript::sourceDataWithPrelude(JSContext* cx, HandleScript script) +JSScript::sourceDataForToString(JSContext* cx, HandleScript script) { MOZ_ASSERT(script->scriptSource()->hasSourceData()); - return script->scriptSource()->substring(cx, script->preludeStart(), script->sourceEnd()); + return script->scriptSource()->substring(cx, script->preludeStart(), script->postludeEnd()); } UncompressedSourceCache::AutoHoldEntry::AutoHoldEntry() @@ -2549,7 +2570,7 @@ JSScript::initCompartment(JSContext* cx) /* static */ JSScript* JSScript::Create(JSContext* cx, const ReadOnlyCompileOptions& options, HandleObject sourceObject, uint32_t bufStart, uint32_t bufEnd, - uint32_t preludeStart) + uint32_t preludeStart, uint32_t postludeEnd) { MOZ_ASSERT(bufStart <= bufEnd); @@ -2572,6 +2593,7 @@ JSScript::Create(JSContext* cx, const ReadOnlyCompileOptions& options, script->sourceStart_ = bufStart; script->sourceEnd_ = bufEnd; script->preludeStart_ = preludeStart; + script->postludeEnd_ = postludeEnd; #ifdef MOZ_VTUNE script->vtuneMethodId_ = vtune::GenerateUniqueMethodID(); @@ -3521,7 +3543,7 @@ CreateEmptyScriptForClone(JSContext* cx, HandleScript src) .setVersion(src->getVersion()); return JSScript::Create(cx, options, sourceObject, src->sourceStart(), src->sourceEnd(), - src->preludeStart()); + src->preludeStart(), src->postludeEnd()); } JSScript* @@ -4074,7 +4096,7 @@ JSScript::formalLivesInArgumentsObject(unsigned argSlot) LazyScript::LazyScript(JSFunction* fun, void* table, uint64_t packedFields, uint32_t begin, uint32_t end, - uint32_t preludeStart, uint32_t lineno, uint32_t column) + uint32_t preludeStart, uint32_t lineno, uint32_t column) : script_(nullptr), function_(fun), enclosingScope_(nullptr), @@ -4084,6 +4106,7 @@ LazyScript::LazyScript(JSFunction* fun, void* table, uint64_t packedFields, begin_(begin), end_(end), preludeStart_(preludeStart), + postludeEnd_(end), lineno_(lineno), column_(column) { diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 342962ee5d08..ad69f5876deb 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -949,9 +949,19 @@ class JSScript : public js::gc::TenuredCell // | // preludeStart_ // + // And, in the case of class constructors, an additional postlude offset + // is used for use with toString. + // + // class C { constructor() { this.field = 42; } } + // ^ ^ ^ ^ + // | | | `---------` + // | sourceStart_ sourceEnd_ | + // | | + // preludeStart_ postludeEnd_ uint32_t sourceStart_; uint32_t sourceEnd_; uint32_t preludeStart_; + uint32_t postludeEnd_; #ifdef MOZ_VTUNE // Unique Method ID passed to the VTune profiler, or 0 if unset. @@ -1121,7 +1131,7 @@ class JSScript : public js::gc::TenuredCell // instead of private to suppress -Wunused-private-field compiler warnings. protected: #if JS_BITS_PER_WORD == 32 - uint32_t padding; + // Currently no padding is needed. #endif // @@ -1131,8 +1141,9 @@ class JSScript : public js::gc::TenuredCell public: static JSScript* Create(JSContext* cx, const JS::ReadOnlyCompileOptions& options, - js::HandleObject sourceObject, uint32_t sourceStart, - uint32_t sourceEnd, uint32_t preludeStart); + js::HandleObject sourceObject, + uint32_t sourceStart, uint32_t sourceEnd, + uint32_t preludeStart, uint32_t postludeEnd); void initCompartment(JSContext* cx); @@ -1283,10 +1294,14 @@ class JSScript : public js::gc::TenuredCell return sourceEnd_; } - size_t preludeStart() const { + uint32_t preludeStart() const { return preludeStart_; } + uint32_t postludeEnd() const { + return postludeEnd_; + } + bool noScriptRval() const { return noScriptRval_; } @@ -1620,7 +1635,7 @@ class JSScript : public js::gc::TenuredCell bool mayReadFrameArgsDirectly(); static JSFlatString* sourceData(JSContext* cx, JS::HandleScript script); - static JSFlatString* sourceDataWithPrelude(JSContext* cx, JS::HandleScript script); + static JSFlatString* sourceDataForToString(JSContext* cx, JS::HandleScript script); static bool loadSource(JSContext* cx, js::ScriptSource* ss, bool* worked); @@ -1631,6 +1646,9 @@ class JSScript : public js::gc::TenuredCell js::ScriptSourceObject& scriptSourceUnwrap() const; js::ScriptSource* scriptSource() const; js::ScriptSource* maybeForwardedScriptSource() const; + + void setDefaultClassConstructorSpan(JSObject* sourceObject, uint32_t start, uint32_t end); + bool mutedErrors() const { return scriptSource()->mutedErrors(); } const char* filename() const { return scriptSource()->filename(); } const char* maybeForwardedFilename() const { return maybeForwardedScriptSource()->filename(); } @@ -2058,7 +2076,7 @@ class LazyScript : public gc::TenuredCell // instead of private to suppress -Wunused-private-field compiler warnings. protected: #if JS_BITS_PER_WORD == 32 - // Currently no padding is needed. + uint32_t padding; #endif private: @@ -2107,6 +2125,7 @@ class LazyScript : public gc::TenuredCell uint32_t begin_; uint32_t end_; uint32_t preludeStart_; + uint32_t postludeEnd_; // Line and column of |begin_| position, that is the position where we // start parsing. uint32_t lineno_; @@ -2332,6 +2351,9 @@ class LazyScript : public gc::TenuredCell uint32_t preludeStart() const { return preludeStart_; } + uint32_t postludeEnd() const { + return postludeEnd_; + } uint32_t lineno() const { return lineno_; } @@ -2339,6 +2361,11 @@ class LazyScript : public gc::TenuredCell return column_; } + void setPostludeEnd(uint32_t postludeEnd) { + MOZ_ASSERT(postludeEnd_ >= end_); + postludeEnd_ = postludeEnd; + } + bool hasUncompiledEnclosingScript() const; friend class GCMarker; diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index df8c3ecc2e08..93ad89a74a27 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2674,6 +2674,14 @@ SrcNotes(JSContext* cx, HandleScript script, Sprinter* sp) return false; break; + case SRC_CLASS_SPAN: { + unsigned startOffset = GetSrcNoteOffset(sn, 0); + unsigned endOffset = GetSrcNoteOffset(sn, 1); + if (!sp->jsprintf(" %u %u", startOffset, endOffset)) + return false; + break; + } + default: MOZ_ASSERT_UNREACHABLE("unrecognized srcnote"); } diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 75679c6ce358..ee196f8e0552 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -246,11 +246,16 @@ SetPropertyOperation(JSContext* cx, JSOp op, HandleValue lval, HandleId id, Hand } static JSFunction* -MakeDefaultConstructor(JSContext* cx, JSOp op, JSAtom* atom, HandleObject proto) +MakeDefaultConstructor(JSContext* cx, HandleScript script, jsbytecode* pc, HandleObject proto) { + JSOp op = JSOp(*pc); + JSAtom* atom = script->getAtom(pc); bool derived = op == JSOP_DERIVEDCONSTRUCTOR; MOZ_ASSERT(derived == !!proto); + jssrcnote* classNote = GetSrcNote(cx, script, pc); + MOZ_ASSERT(classNote && SN_TYPE(classNote) == SRC_CLASS_SPAN); + PropertyName* lookup = derived ? cx->names().DefaultDerivedClassConstructor : cx->names().DefaultBaseClassConstructor; @@ -267,9 +272,19 @@ MakeDefaultConstructor(JSContext* cx, JSOp op, JSAtom* atom, HandleObject proto) ctor->setIsConstructor(); ctor->setIsClassConstructor(); - MOZ_ASSERT(ctor->infallibleIsDefaultClassConstructor(cx)); + // Create the script now, as the source span needs to be overridden for + // toString. Calling toString on a class constructor must not return the + // source for just the constructor function. + JSScript *ctorScript = JSFunction::getOrCreateScript(cx, ctor); + if (!ctorScript) + return nullptr; + uint32_t classStartOffset = GetSrcNoteOffset(classNote, 0); + uint32_t classEndOffset = GetSrcNoteOffset(classNote, 1); + ctorScript->setDefaultClassConstructorSpan(script->sourceObject(), classStartOffset, + classEndOffset); + return ctor; } @@ -4216,8 +4231,7 @@ CASE(JSOP_DERIVEDCONSTRUCTOR) MOZ_ASSERT(REGS.sp[-1].isObject()); ReservedRooted proto(&rootObject0, ®S.sp[-1].toObject()); - JSFunction* constructor = MakeDefaultConstructor(cx, JSOp(*REGS.pc), script->getAtom(REGS.pc), - proto); + JSFunction* constructor = MakeDefaultConstructor(cx, script, REGS.pc, proto); if (!constructor) goto error; @@ -4227,8 +4241,7 @@ END_CASE(JSOP_DERIVEDCONSTRUCTOR) CASE(JSOP_CLASSCONSTRUCTOR) { - JSFunction* constructor = MakeDefaultConstructor(cx, JSOp(*REGS.pc), script->getAtom(REGS.pc), - nullptr); + JSFunction* constructor = MakeDefaultConstructor(cx, script, REGS.pc, nullptr); if (!constructor) goto error; PUSH_OBJECT(*constructor);