diff --git a/js/src/ctypes/CTypes.cpp b/js/src/ctypes/CTypes.cpp index 3658e4521c..562b3861fd 100644 --- a/js/src/ctypes/CTypes.cpp +++ b/js/src/ctypes/CTypes.cpp @@ -2715,7 +2715,8 @@ IntegerToString(IntegerType i, int radix, mozilla::Vector& resu *--cp = '-'; MOZ_ASSERT(cp >= buffer); - result.append(cp, end); + if (!result.append(cp, end)) + return; } template @@ -3766,12 +3767,10 @@ BuildDataSource(JSContext* cx, double fp = *static_cast(data); \ ToCStringBuf cbuf; \ char* str = NumberToCString(cx, &cbuf, fp); \ - if (!str) { \ + if (!str || !result.append(str, strlen(str))) { \ JS_ReportOutOfMemory(cx); \ return false; \ } \ - \ - result.append(str, strlen(str)); \ break; \ } CTYPES_FOR_EACH_FLOAT_TYPE(FLOAT_CASE) diff --git a/js/src/ctypes/CTypes.h b/js/src/ctypes/CTypes.h index f92b467875..3e98015f78 100644 --- a/js/src/ctypes/CTypes.h +++ b/js/src/ctypes/CTypes.h @@ -77,7 +77,8 @@ template void AppendString(mozilla::Vector& v, mozilla::Vector& w) { - v.append(w.begin(), w.length()); + if (!v.append(w.begin(), w.length())) + return; } template @@ -89,10 +90,13 @@ AppendString(mozilla::Vector& v, JSString* str) if (!linear) return; JS::AutoCheckCannotGC nogc; - if (linear->hasLatin1Chars()) - v.append(linear->latin1Chars(nogc), linear->length()); - else - v.append(linear->twoByteChars(nogc), linear->length()); + if (linear->hasLatin1Chars()) { + if (!v.append(linear->latin1Chars(nogc), linear->length())) + return; + } else { + if (!v.append(linear->twoByteChars(nogc), linear->length())) + return; + } } template diff --git a/js/src/devtools/rootAnalysis/CFG.js b/js/src/devtools/rootAnalysis/CFG.js index 4d2912114a..062e534325 100644 --- a/js/src/devtools/rootAnalysis/CFG.js +++ b/js/src/devtools/rootAnalysis/CFG.js @@ -37,7 +37,7 @@ function isMatchingDestructor(constructor, edge) return false; var variable = callee.Variable; assert(variable.Kind == "Func"); - if (!/::~/.test(variable.Name[0])) + if (variable.Name[1].charAt(0) != '~') return false; var constructExp = constructor.PEdgeCallInstance.Exp; @@ -50,8 +50,11 @@ function isMatchingDestructor(constructor, edge) return sameVariable(constructExp.Variable, destructExp.Variable); } -// Return all calls within the RAII scope of the constructor matched by -// isConstructor() +// Return all calls within the RAII scope of any constructor matched by +// isConstructor(). (Note that this would be insufficient if you needed to +// treat each instance separately, such as when different regions of a function +// body were guarded by these constructors and you needed to do something +// different with each.) function allRAIIGuardedCallPoints(body, isConstructor) { if (!("PEdge" in body)) @@ -67,7 +70,9 @@ function allRAIIGuardedCallPoints(body, isConstructor) continue; var variable = callee.Variable; assert(variable.Kind == "Func"); - if (!isConstructor(variable.Name[0])) + if (!isConstructor(variable.Name)) + continue; + if (!("PEdgeCallInstance" in edge)) continue; if (edge.PEdgeCallInstance.Exp.Kind != "Var") continue; diff --git a/js/src/devtools/rootAnalysis/analyzeRoots.js b/js/src/devtools/rootAnalysis/analyzeRoots.js index d76b120a09..8255707441 100644 --- a/js/src/devtools/rootAnalysis/analyzeRoots.js +++ b/js/src/devtools/rootAnalysis/analyzeRoots.js @@ -140,7 +140,7 @@ function isReturningImmobileValue(edge, variable) // function edgeUsesVariable(edge, variable, body) { - if (ignoreEdgeUse(edge, variable)) + if (ignoreEdgeUse(edge, variable, body)) return 0; if (variable.Kind == "Return" && body.Index[1] == edge.Index[1] && body.BlockId.Kind == "Function") @@ -192,9 +192,9 @@ function expressionIsVariableAddress(exp, variable) return exp.Kind == "Var" && sameVariable(exp.Variable, variable); } -function edgeTakesVariableAddress(edge, variable) +function edgeTakesVariableAddress(edge, variable, body) { - if (ignoreEdgeUse(edge, variable)) + if (ignoreEdgeUse(edge, variable, body)) return false; if (ignoreEdgeAddressTaken(edge)) return false; @@ -492,7 +492,7 @@ function unsafeVariableAddressTaken(suppressed, variable) if (!("PEdge" in body)) continue; for (var edge of body.PEdge) { - if (edgeTakesVariableAddress(edge, variable)) { + if (edgeTakesVariableAddress(edge, variable, body)) { if (edge.Kind == "Assign" || (!suppressed && edgeCanGC(edge))) return {body:body, ppoint:edge.Index[0]}; } @@ -721,6 +721,11 @@ for (var nameIndex = start; nameIndex <= end; nameIndex++) { var data = xdb.read_entry(name); xdb.free_string(name); var json = data.readString(); - process(functionName, json); + try { + process(functionName, json); + } catch (e) { + printErr("Exception caught while handling " + functionName); + throw(e); + } xdb.free_string(data); } diff --git a/js/src/devtools/rootAnalysis/annotations.js b/js/src/devtools/rootAnalysis/annotations.js index c603f9828c..34bf0ff3f6 100644 --- a/js/src/devtools/rootAnalysis/annotations.js +++ b/js/src/devtools/rootAnalysis/annotations.js @@ -91,8 +91,27 @@ function fieldCallCannotGC(csu, fullfield) return false; } -function ignoreEdgeUse(edge, variable) +function ignoreEdgeUse(edge, variable, body) { + // Horrible special case for ignoring a false positive in xptcstubs: there + // is a local variable 'paramBuffer' holding an array of nsXPTCMiniVariant + // on the stack, which appears to be live across a GC call because its + // constructor is called when the array is initialized, even though the + // constructor is a no-op. So we'll do a very narrow exclusion for the use + // that incorrectly started the live range, which was basically "__temp_1 = + // paramBuffer". + // + // By scoping it so narrowly, we can detect most hazards that would be + // caused by modifications in the PrepareAndDispatch code. It just barely + // avoids having a hazard already. + if (('Name' in variable) && (variable.Name[0] == 'paramBuffer')) { + if (body.BlockId.Kind == 'Function' && body.BlockId.Variable.Name[0] == 'PrepareAndDispatch') + if (edge.Kind == 'Assign' && edge.Type.Kind == 'Pointer') + if (edge.Exp[0].Kind == 'Var' && edge.Exp[1].Kind == 'Var') + if (edge.Exp[1].Variable.Kind == 'Local' && edge.Exp[1].Variable.Name[0] == 'paramBuffer') + return true; + } + // Functions which should not be treated as using variable. if (edge.Kind == "Call") { var callee = edge.Exp[0]; @@ -292,13 +311,16 @@ function isUnsafeStorage(typeName) return typeName.startsWith('UniquePtr<'); } -function isSuppressConstructor(name) +function isSuppressConstructor(varName) { - return name.indexOf("::AutoSuppressGC") != -1 - || name.indexOf("::AutoAssertGCCallback") != -1 - || name.indexOf("::AutoEnterAnalysis") != -1 - || name.indexOf("::AutoSuppressGCAnalysis") != -1 - || name.indexOf("::AutoIgnoreRootingHazards") != -1; + // varName[1] contains the unqualified name + return [ + "AutoSuppressGC", + "AutoAssertGCCallback", + "AutoEnterAnalysis", + "AutoSuppressGCAnalysis", + "AutoIgnoreRootingHazards" + ].indexOf(varName[1]) != -1; } // nsISupports subclasses' methods may be scriptable (or overridden diff --git a/js/src/devtools/rootAnalysis/computeCallgraph.js b/js/src/devtools/rootAnalysis/computeCallgraph.js index 0e97e95f57..7ab5856f4d 100644 --- a/js/src/devtools/rootAnalysis/computeCallgraph.js +++ b/js/src/devtools/rootAnalysis/computeCallgraph.js @@ -303,6 +303,43 @@ for (var nameIndex = minStream; nameIndex <= maxStream; nameIndex++) { } } + // Further note: from http://mentorembedded.github.io/cxx-abi/abi.html the + // different kinds of constructors/destructors are: + // C1 # complete object constructor + // C2 # base object constructor + // C3 # complete object allocating constructor + // D0 # deleting destructor + // D1 # complete object destructor + // D2 # base object destructor + // + // In actual practice, I have observed a C4 constructor generated by gcc + // 4.9.3 (but not 4.7.3). The gcc source code says: + // + // /* This is the old-style "[unified]" constructor. + // In some cases, we may emit this function and call + // it from the clones in order to share code and save space. */ + // + // Unfortunately, that "call... from the clones" does not seem to appear in + // the CFG we get from GCC. So if we see a C4 constructor, inject an edge + // to it from C1, C2, and C3. (Note that C3 isn't even used in current GCC, + // but add the edge anyway just in case.) + if (functionName.indexOf("C4E") != -1) { + var [ mangled, unmangled ] = splitFunction(functionName); + // E terminates the method name (and precedes the method parameters). + if (mangled.indexOf("C4E") != -1) { + // If "C4E" shows up in the mangled name for another reason, this + // will create bogus edges in the callgraph. But that shouldn't + // matter too much, and is somewhat difficult to avoid, so we will + // live with it. + var C1 = mangled.replace("C4E", "C1E"); + var C2 = mangled.replace("C4E", "C2E"); + var C3 = mangled.replace("C4E", "C3E"); + print("D " + memo(C1) + " " + memo(mangled)); + print("D " + memo(C2) + " " + memo(mangled)); + print("D " + memo(C3) + " " + memo(mangled)); + } + } + xdb.free_string(name); xdb.free_string(data); } diff --git a/js/src/doc/README.md b/js/src/doc/README.md index 1239acbfb3..989fa40db7 100644 --- a/js/src/doc/README.md +++ b/js/src/doc/README.md @@ -155,6 +155,6 @@ invoke the following commands: This ought to be integrated with mach ------------------------------------- -Indeed. It should somehow be unified with 'mach build-docs', which seems to +Indeed. It should somehow be unified with 'mach doc', which seems to format in-tree docs of a different kind, and use a different markup language (ReStructuredText) and a different formatter (Sphinx). diff --git a/js/src/ds/Fifo.h b/js/src/ds/Fifo.h index 80e519b00e..bc9eb7a24d 100644 --- a/js/src/ds/Fifo.h +++ b/js/src/ds/Fifo.h @@ -36,7 +36,7 @@ class Fifo // An element A is "younger" than an element B if B was inserted into the // |Fifo| before A was. // - // Invariant 1: Every element within |front_| is younger than every element + // Invariant 1: Every element within |front_| is older than every element // within |rear_|. // Invariant 2: Entries within |front_| are sorted from younger to older. // Invariant 3: Entries within |rear_| are sorted from older to younger. diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 6289e03ab0..c1ebcfb8d0 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -355,38 +355,22 @@ BytecodeEmitter::emitDupAt(unsigned slotFromTop) return true; } -/* XXX too many "... statement" L10N gaffes below -- fix via js.msg! */ -const char js_with_statement_str[] = "with statement"; -const char js_finally_block_str[] = "finally block"; - -static const char * const statementName[] = { - "label statement", /* LABEL */ - "if statement", /* IF */ - "else statement", /* ELSE */ - "destructuring body", /* BODY */ - "switch statement", /* SWITCH */ - "block", /* BLOCK */ - js_with_statement_str, /* WITH */ - "catch block", /* CATCH */ - "try block", /* TRY */ - js_finally_block_str, /* FINALLY */ - js_finally_block_str, /* SUBROUTINE */ - "do loop", /* DO_LOOP */ - "for loop", /* FOR_LOOP */ - "for/in loop", /* FOR_IN_LOOP */ - "for/of loop", /* FOR_OF_LOOP */ - "while loop", /* WHILE_LOOP */ - "spread", /* SPREAD */ -}; - -static_assert(MOZ_ARRAY_LENGTH(statementName) == uint16_t(StmtType::LIMIT), - "statementName array and StmtType enum must be consistent"); - static const char* StatementName(StmtInfoBCE* stmt) { if (!stmt) return js_script_str; + + /* XXX too many "... statement" L10N gaffes -- fix via js.msg! */ + static const char* const statementName[] = { + #define STATEMENT_TYPE_NAME(name, desc) desc, + FOR_EACH_STATEMENT_TYPE(STATEMENT_TYPE_NAME) + #undef STATEMENT_TYPE_NAME + }; + + static_assert(MOZ_ARRAY_LENGTH(statementName) == uint16_t(StmtType::LIMIT), + "statementName array and StmtType enum must be consistent"); + return statementName[uint16_t(stmt->type)]; } @@ -3784,7 +3768,6 @@ BytecodeEmitter::emitDestructuringDeclsWithEmitter(JSOp prologueOp, ParseNode* p continue; ParseNode* target = element; if (element->isKind(PNK_SPREAD)) { - MOZ_ASSERT(element->pn_kid->isKind(PNK_NAME)); target = element->pn_kid; } if (target->isKind(PNK_ASSIGN)) @@ -5400,15 +5383,17 @@ BytecodeEmitter::emitHoistedFunctionsInList(ParseNode* list) MOZ_ASSERT(list->pn_xflags & PNX_FUNCDEFS); for (ParseNode* pn = list->pn_head; pn; pn = pn->pn_next) { + ParseNode* maybeFun = pn; + if (!sc->strict()) { - while (pn->isKind(PNK_LABEL)) - pn = pn->as().statement(); + while (maybeFun->isKind(PNK_LABEL)) + maybeFun = maybeFun->as().statement(); } - if (pn->isKind(PNK_ANNEXB_FUNCTION) || - (pn->isKind(PNK_FUNCTION) && pn->functionIsHoisted())) + if (maybeFun->isKind(PNK_ANNEXB_FUNCTION) || + (maybeFun->isKind(PNK_FUNCTION) && maybeFun->functionIsHoisted())) { - if (!emitTree(pn)) + if (!emitTree(maybeFun)) return false; } } diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 9c4b72d9d5..1e24abd581 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -1691,6 +1691,7 @@ Parser::newFunction(HandleAtom atom, FunctionSyntaxKind kind, allocKind = gc::AllocKind::FUNCTION_EXTENDED; break; default: + MOZ_ASSERT(kind == Statement); #ifdef DEBUG if (options().selfHostingMode && !pc->sc->isFunctionBox()) { isGlobalSelfHostedBuiltin = true; @@ -4347,11 +4348,6 @@ Parser::checkDestructuringArray(BindData* da return false; } target = element->pn_kid; - - if (handler.isUnparenthesizedDestructuringPattern(target)) { - report(ParseError, false, target, JSMSG_BAD_DESTRUCT_TARGET); - return false; - } } else if (handler.isUnparenthesizedAssignment(element)) { target = element->pn_left; } else { diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index 6a0674932e..0101266d44 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -474,34 +474,33 @@ SharedContext::allLocalsAliased() return bindingsAccessedDynamically() || (isFunctionBox() && asFunctionBox()->isGenerator()); } +// NOTE: If you add a new type of statement that is a scope, add it between +// WITH and CATCH, or you'll break StmtInfoBase::linksScope. If you add +// a non-looping statement type, add it before DO_LOOP or you'll break +// StmtInfoBase::isLoop(). +#define FOR_EACH_STATEMENT_TYPE(macro) \ + macro(LABEL, "label statement") \ + macro(IF, "if statement") \ + macro(ELSE, "else statement") \ + macro(SEQ, "destructuring body") \ + macro(BLOCK, "block") \ + macro(SWITCH, "switch statement") \ + macro(WITH, "with statement") \ + macro(CATCH, "catch block") \ + macro(TRY, "try block") \ + macro(FINALLY, "finally block") \ + macro(SUBROUTINE, "finally block") \ + macro(DO_LOOP, "do loop") \ + macro(FOR_LOOP, "for loop") \ + macro(FOR_IN_LOOP, "for/in loop") \ + macro(FOR_OF_LOOP, "for/of loop") \ + macro(WHILE_LOOP, "while loop") \ + macro(SPREAD, "spread") -/* - * NB: If you add a new type of statement that is a scope, add it between - * STMT_WITH and STMT_CATCH, or you will break StmtInfoBase::linksScope. If you - * add a non-looping statement type, add it before STMT_DO_LOOP or you will - * break StmtInfoBase::isLoop(). - * - * Also remember to keep the statementName array in BytecodeEmitter.cpp in - * sync. - */ enum class StmtType : uint16_t { - LABEL, /* labeled statement: L: s */ - IF, /* if (then) statement */ - ELSE, /* else clause of if statement */ - SEQ, /* synthetic sequence of statements */ - BLOCK, /* compound statement: { s1[;... sN] } */ - SWITCH, /* switch statement */ - WITH, /* with statement */ - CATCH, /* catch block */ - TRY, /* try block */ - FINALLY, /* finally block */ - SUBROUTINE, /* gosub-target subroutine body */ - DO_LOOP, /* do/while loop statement */ - FOR_LOOP, /* for loop statement */ - FOR_IN_LOOP, /* for/in loop statement */ - FOR_OF_LOOP, /* for/of loop statement */ - WHILE_LOOP, /* while loop statement */ - SPREAD, /* spread operator (pseudo for/of) */ +#define DECLARE_STMTTYPE_ENUM(name, desc) name, + FOR_EACH_STATEMENT_TYPE(DECLARE_STMTTYPE_ENUM) +#undef DECLARE_STMTTYPE_ENUM LIMIT }; diff --git a/js/src/gc/Marking.h b/js/src/gc/Marking.h index d6c9e4b871..ea8d32982f 100644 --- a/js/src/gc/Marking.h +++ b/js/src/gc/Marking.h @@ -413,27 +413,6 @@ IsNullTaggedPointer(void* p) return uintptr_t(p) <= LargestTaggedNullCellPointer; } -// HashKeyRef represents a reference to a HashMap key. This should normally -// be used through the HashTableWriteBarrierPost function. -template -class HashKeyRef : public BufferableRef -{ - Map* map; - Key key; - - public: - HashKeyRef(Map* m, const Key& k) : map(m), key(k) {} - - void trace(JSTracer* trc) override { - Key prior = key; - typename Map::Ptr p = map->lookup(key); - if (!p) - return; - TraceManuallyBarrieredEdge(trc, &key, "HashKeyRef"); - map->rekeyIfMoved(prior, key); - } -}; - // Wrap a GC thing pointer into a new Value or jsid. The type system enforces // that the thing pointer is a wrappable type. template diff --git a/js/src/gc/Nursery.cpp b/js/src/gc/Nursery.cpp index 1ea7646d7c..768e0628c2 100644 --- a/js/src/gc/Nursery.cpp +++ b/js/src/gc/Nursery.cpp @@ -82,10 +82,10 @@ js::Nursery::init(uint32_t maxNurseryBytes) heapStart_ = uintptr_t(heap); heapEnd_ = heapStart_ + nurserySize(); currentStart_ = start(); - numActiveChunks_ = 1; + numActiveChunks_ = numNurseryChunks_; JS_POISON(heap, JS_FRESH_NURSERY_PATTERN, nurserySize()); + updateNumActiveChunks(1); setCurrentChunk(0); - updateDecommittedRegion(); char* env = getenv("JS_GC_PROFILE_NURSERY"); if (env) { @@ -110,24 +110,6 @@ js::Nursery::~Nursery() js_delete(freeMallocedBuffersTask); } -void -js::Nursery::updateDecommittedRegion() -{ -#ifndef JS_GC_ZEAL - if (numActiveChunks_ < numNurseryChunks_) { - // Bug 994054: madvise on MacOS is too slow to make this - // optimization worthwhile. -# ifndef XP_DARWIN - uintptr_t decommitStart = chunk(numActiveChunks_).start(); - uintptr_t decommitSize = heapEnd() - decommitStart; - MOZ_ASSERT(decommitStart == AlignBytes(decommitStart, Alignment)); - MOZ_ASSERT(decommitSize == AlignBytes(decommitStart, Alignment)); - MarkPagesUnused((void*)decommitStart, decommitSize); -# endif - } -#endif -} - void js::Nursery::enable() { @@ -135,7 +117,7 @@ js::Nursery::enable() MOZ_ASSERT(!runtime()->gc.isVerifyPreBarriersEnabled()); if (isEnabled()) return; - numActiveChunks_ = 1; + updateNumActiveChunks(1); setCurrentChunk(0); currentStart_ = position(); #ifdef JS_GC_ZEAL @@ -150,9 +132,8 @@ js::Nursery::disable() MOZ_ASSERT(isEmpty()); if (!isEnabled()) return; - numActiveChunks_ = 0; + updateNumActiveChunks(0); currentEnd_ = 0; - updateDecommittedRegion(); } bool @@ -703,7 +684,7 @@ js::Nursery::growAllocableSpace() MOZ_ASSERT_IF(runtime()->hasZealMode(ZealMode::GenerationalGC), numActiveChunks_ == numNurseryChunks_); #endif - numActiveChunks_ = Min(numActiveChunks_ * 2, numNurseryChunks_); + updateNumActiveChunks(Min(numActiveChunks_ * 2, numNurseryChunks_)); } void @@ -713,6 +694,28 @@ js::Nursery::shrinkAllocableSpace() if (runtime()->hasZealMode(ZealMode::GenerationalGC)) return; #endif - numActiveChunks_ = Max(numActiveChunks_ - 1, 1); - updateDecommittedRegion(); + updateNumActiveChunks(Max(numActiveChunks_ - 1, 1)); +} + +void +js::Nursery::updateNumActiveChunks(int newCount) +{ +#ifndef JS_GC_ZEAL + int priorChunks = numActiveChunks_; +#endif + numActiveChunks_ = newCount; + + // In zeal mode, we want to keep the unused memory poisoned so that we + // will crash sooner. Avoid decommit in that case to avoid having the + // system zero the pages. +#ifndef JS_GC_ZEAL + if (numActiveChunks_ < priorChunks) { + uintptr_t decommitStart = chunk(numActiveChunks_).start(); + uintptr_t decommitSize = chunk(priorChunks - 1).start() + ChunkSize - decommitStart; + MOZ_ASSERT(decommitSize != 0); + MOZ_ASSERT(decommitStart == AlignBytes(decommitStart, Alignment)); + MOZ_ASSERT(decommitSize == AlignBytes(decommitSize, Alignment)); + MarkPagesUnused((void*)decommitStart, decommitSize); + } +#endif // !defined(JS_GC_ZEAL) } diff --git a/js/src/gc/Nursery.h b/js/src/gc/Nursery.h index 22ab99ab6e..ee7741ec32 100644 --- a/js/src/gc/Nursery.h +++ b/js/src/gc/Nursery.h @@ -303,8 +303,8 @@ class Nursery struct NurseryChunkLayout { char data[NurseryChunkUsableSize]; gc::ChunkTrailer trailer; - uintptr_t start() { return uintptr_t(&data); } - uintptr_t end() { return uintptr_t(&trailer); } + uintptr_t start() const { return uintptr_t(&data); } + uintptr_t end() const { return uintptr_t(&trailer); } }; static_assert(sizeof(NurseryChunkLayout) == gc::ChunkSize, "Nursery chunk size must match gc::Chunk size."); @@ -328,7 +328,7 @@ class Nursery initChunk(chunkno); } - void updateDecommittedRegion(); + void updateNumActiveChunks(int newCount); MOZ_ALWAYS_INLINE uintptr_t allocationEnd() const { MOZ_ASSERT(numActiveChunks_ > 0); diff --git a/js/src/gc/StoreBuffer.h b/js/src/gc/StoreBuffer.h index c343d6b376..c9e7e15e8c 100644 --- a/js/src/gc/StoreBuffer.h +++ b/js/src/gc/StoreBuffer.h @@ -11,6 +11,8 @@ #include "mozilla/DebugOnly.h" #include "mozilla/ReentrancyGuard.h" +#include + #include "jsalloc.h" #include "ds/LifoAlloc.h" @@ -288,6 +290,35 @@ class StoreBuffer return !(*this == other); } + // True if this SlotsEdge range overlaps with the other SlotsEdge range, + // false if they do not overlap. + bool overlaps(const SlotsEdge& other) const { + if (objectAndKind_ != other.objectAndKind_) + return false; + + // Widen our range by one on each side so that we consider + // adjacent-but-not-actually-overlapping ranges as overlapping. This + // is particularly useful for coalescing a series of increasing or + // decreasing single index writes 0, 1, 2, ..., N into a SlotsEdge + // range of elements [0, N]. + auto end = start_ + count_ + 1; + auto start = start_ - 1; + + auto otherEnd = other.start_ + other.count_; + return (start <= other.start_ && other.start_ <= end) || + (start <= otherEnd && otherEnd <= end); + } + + // Destructively make this SlotsEdge range the union of the other + // SlotsEdge range and this one. A precondition is that the ranges must + // overlap. + void merge(const SlotsEdge& other) { + MOZ_ASSERT(overlaps(other)); + auto end = std::max(start_ + count_, other.start_ + other.count_); + start_ = std::min(start_, other.start_); + count_ = end - start_; + } + bool maybeInRememberedSet(const Nursery& n) const { return !IsInsideNursery(reinterpret_cast(object())); } @@ -404,7 +435,11 @@ class StoreBuffer void putCell(Cell** cellp) { put(bufferCell, CellPtrEdge(cellp)); } void unputCell(Cell** cellp) { unput(bufferCell, CellPtrEdge(cellp)); } void putSlot(NativeObject* obj, int kind, int32_t start, int32_t count) { - put(bufferSlot, SlotsEdge(obj, kind, start, count)); + SlotsEdge edge(obj, kind, start, count); + if (bufferSlot.last_.overlaps(edge)) + bufferSlot.last_.merge(edge); + else + put(bufferSlot, edge); } void putWholeCell(Cell* cell) { MOZ_ASSERT(cell->isTenured()); diff --git a/js/src/jit-test/tests/basic/destructuring-iterator.js b/js/src/jit-test/tests/basic/destructuring-iterator.js index a9403ed387..f9e68f9299 100644 --- a/js/src/jit-test/tests/basic/destructuring-iterator.js +++ b/js/src/jit-test/tests/basic/destructuring-iterator.js @@ -49,6 +49,17 @@ assertIterable([5,5,4,4], it => { var [,,...rest] = it; return rest; }, [3,4]); +// the iterator should be exhausted before any error is thrown +assertIterable([5,5,4,4], + it => { + assertThrowsInstanceOf(function () { + "use strict"; + [...{0: "".x}] = it; + }, TypeError); + return []; + }, + []); + var arraycalls = 0; var ArrayIterator = Array.prototype[Symbol.iterator]; Array.prototype[Symbol.iterator] = function () { @@ -58,7 +69,11 @@ Array.prototype[Symbol.iterator] = function () { // [...rest] should not call Array#@@iterator for the LHS var [...rest] = iterable; assertEq(arraycalls, 0, 'calls to Array#@@iterator'); - +// [...[...rest]] should do so, since it creates an implicit array for the +// first rest pattern, then destructures that again using @@iterator() for the +// second rest pattern. +var [...[...rest]] = iterable; +assertEq(arraycalls, 1, 'calls to Array#@@iterator'); // loop `fn` a few times, to get it JIT-compiled function loop(fn) { @@ -67,7 +82,7 @@ function loop(fn) { } loop(() => { doneafter = 4; var [a] = iterable; return a; }); -loop(() => { doneafter = 4; var [a,b,...rest] = iterable; return rest; }); +loop(() => { doneafter = 4; var [a,b,...[...rest]] = iterable; return rest; }); // destructuring assignment should always use iterators and not optimize diff --git a/js/src/jit-test/tests/basic/destructuring-rest.js b/js/src/jit-test/tests/basic/destructuring-rest.js index 30ac1b002d..4b86bd2d01 100644 --- a/js/src/jit-test/tests/basic/destructuring-rest.js +++ b/js/src/jit-test/tests/basic/destructuring-rest.js @@ -4,8 +4,6 @@ load(libdir + 'eqArrayHelper.js'); assertThrowsInstanceOf(() => new Function('[...a, ,] = []'), SyntaxError, 'trailing elision'); assertThrowsInstanceOf(() => new Function('[a, ...b, c] = []'), SyntaxError, 'trailing param'); -assertThrowsInstanceOf(() => new Function('[...[a]] = []'), SyntaxError, 'nested arraypattern'); -assertThrowsInstanceOf(() => new Function('[...{a}] = []'), SyntaxError, 'nested objectpattern'); assertThrowsInstanceOf(() => new Function('[...a=b] = []'), SyntaxError, 'assignment expression'); assertThrowsInstanceOf(() => new Function('[...a()] = []'), SyntaxError, 'call expression'); assertThrowsInstanceOf(() => new Function('[...(a,b)] = []'), SyntaxError, 'comma expression'); @@ -22,6 +20,14 @@ assertThrowsInstanceOf(() => assertThrowsInstanceOf(() => new Function('[...b,] = []'), SyntaxError) , Error); +assertThrowsInstanceOf(() => { + try { + eval('let [...[...x]] = (() => { throw "foo"; } )();'); + } catch(e) { + assertEq(e, "foo"); + } + x; +}, ReferenceError); var inputArray = [1, 2, 3]; var inputDeep = [1, inputArray]; @@ -45,6 +51,11 @@ function testAll(fn) { assertEqArray(fn('[, ...(o.prop)]', inputArray, 'o.prop'), expected); o.prop = null; assertEqArray(fn('[, ...(o.call().prop)]', inputArray, 'o.prop'), expected); + + o.prop = null; + assertEqArray(fn('[, ...[...(o.prop)]]', inputArray, 'o.prop'), expected); + o.prop = null; + assertEqArray(fn('[, ...[...(o.call().prop)]]', inputArray, 'o.prop'), expected); } function testDeclaration(fn) { testStr(fn); @@ -53,10 +64,24 @@ function testDeclaration(fn) { assertEqArray(fn('[, ...rest]', inputGenerator()), expected); assertEqArray(fn('[, [, ...rest]]', inputDeep), expected); assertEqArray(fn('{a: [, ...rest]}', inputObject), expected); + + assertEqArray(fn('[, ...[...rest]]', inputArray), expected); + assertEqArray(fn('[, ...[...rest]]', inputGenerator()), expected); + assertEqArray(fn('[, [, ...[...rest]]]', inputDeep), expected); + assertEqArray(fn('{a: [, ...[...rest]]}', inputObject), expected); + + assertEqArray(fn('[, ...{0: a, 1: b}]', inputArray, '[a, b]'), expected); + assertEqArray(fn('[, ...{0: a, 1: b}]', inputGenerator(), '[a, b]'), expected); + assertEqArray(fn('[, [, ...{0: a, 1: b}]]', inputDeep, '[a, b]'), expected); + assertEqArray(fn('{a: [, ...{0: a, 1: b}]}', inputObject, '[a, b]'), expected); } function testStr(fn) { assertEqArray(fn('[, ...rest]', inputStr), expectedStr); + + assertEqArray(fn('[, ...[...rest]]', inputStr), expectedStr); + + assertEqArray(fn('[, ...{0: a, 1: b}]', inputStr, '[a, b]'), expectedStr); } function testForIn(pattern, input, binding) { @@ -88,8 +113,9 @@ testAll(testGlobal); function testClosure(pattern, input, binding) { binding = binding || 'rest'; + const decl = binding.replace('[', '').replace(']', ''); return new Function('input', - 'var ' + binding + '; (function () {' + + 'var ' + decl + '; (function () {' + '(' + pattern + ' = input);' + '})();' + 'return ' + binding diff --git a/js/src/jit-test/tests/ion/bug1233343.js b/js/src/jit-test/tests/ion/bug1233343.js new file mode 100644 index 0000000000..80e96fb106 --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1233343.js @@ -0,0 +1,36 @@ +function addRemove() { + dbg.addDebuggee(g); + f = dbg.getNewestFrame().older; +} +function removeAdd() { + dbg.addDebuggee(g); + var f = dbg.getNewestFrame(); + while (f) { + f = f.older; + } +} +function testInterrupt() { + g = newGlobal(); + dbg = new Debugger; + g.eval("" + function f() { + return g(); + }); + g.eval("" + function g() { + return h(); + }); + g.eval("" + function h() { + for (var i = 0; i < 100; i++) { + interruptIf(5); + } + }); + setInterruptCallback(function() { + toggleSeq(); + return true; + }); + g.f(); +} +toggleSeq = addRemove; +testInterrupt(); +toggleSeq = removeAdd; +testInterrupt(); + diff --git a/js/src/jit/arm/MoveEmitter-arm.cpp b/js/src/jit/arm/MoveEmitter-arm.cpp index 8d8a5b4340..d26cd37993 100644 --- a/js/src/jit/arm/MoveEmitter-arm.cpp +++ b/js/src/jit/arm/MoveEmitter-arm.cpp @@ -170,14 +170,27 @@ MoveEmitterARM::completeCycle(const MoveOperand& from, const MoveOperand& to, Mo // saved value of B, to A. switch (type) { case MoveOp::FLOAT32: - case MoveOp::DOUBLE: + MOZ_ASSERT(!to.isGeneralRegPair()); if (to.isMemory()) { - ScratchDoubleScope scratch(masm); + ScratchFloat32Scope scratch(masm); masm.ma_vldr(cycleSlot(slotId, 0), scratch); masm.ma_vstr(scratch, toAddress(to)); } else if (to.isGeneralReg()) { MOZ_ASSERT(type == MoveOp::FLOAT32); masm.ma_ldr(toAddress(from), to.reg()); + } else { + uint32_t offset = 0; + if ((!from.isMemory()) && from.floatReg().numAlignedAliased() == 1) + offset = sizeof(float); + masm.ma_vldr(cycleSlot(slotId, offset), to.floatReg()); + } + break; + case MoveOp::DOUBLE: + MOZ_ASSERT(!to.isGeneralReg()); + if (to.isMemory()) { + ScratchDoubleScope scratch(masm); + masm.ma_vldr(cycleSlot(slotId, 0), scratch); + masm.ma_vstr(scratch, toAddress(to)); } else if (to.isGeneralRegPair()) { MOZ_ASSERT(type == MoveOp::DOUBLE); ScratchDoubleScope scratch(masm); diff --git a/js/src/jit/arm/SharedICHelpers-arm.h b/js/src/jit/arm/SharedICHelpers-arm.h index fab9128ddc..d72794c656 100644 --- a/js/src/jit/arm/SharedICHelpers-arm.h +++ b/js/src/jit/arm/SharedICHelpers-arm.h @@ -200,7 +200,13 @@ inline void EmitIonEnterStubFrame(MacroAssembler& masm, Register scratch) { MOZ_ASSERT(ICTailCallReg == lr); - masm.Push(ICTailCallReg); + + // In arm the link register contains the return address, + // but in jit frames we expect it to be on the stack. As a result + // push the link register (which is actually part of the previous frame. + // Therefore using push instead of Push). + masm.push(ICTailCallReg); + masm.Push(ICStubReg); } @@ -235,7 +241,7 @@ inline void EmitIonLeaveStubFrame(MacroAssembler& masm) { masm.Pop(ICStubReg); - masm.Pop(ICTailCallReg); + masm.pop(ICTailCallReg); // See EmitIonEnterStubFrame for explanation on pop/Pop. } inline void diff --git a/js/src/jit/mips-shared/SharedICHelpers-mips-shared.h b/js/src/jit/mips-shared/SharedICHelpers-mips-shared.h index db269b7ca4..4fb506ccc2 100644 --- a/js/src/jit/mips-shared/SharedICHelpers-mips-shared.h +++ b/js/src/jit/mips-shared/SharedICHelpers-mips-shared.h @@ -108,7 +108,18 @@ EmitBaselineTailCallVM(JitCode* target, MacroAssembler& masm, uint32_t argSize) inline void EmitIonTailCallVM(JitCode* target, MacroAssembler& masm, uint32_t stackSize) { - MOZ_CRASH("Not implemented yet."); + Register scratch = R2.scratchReg(); + + masm.loadPtr(Address(sp, stackSize), scratch); + masm.rshiftPtr(Imm32(FRAMESIZE_SHIFT), scratch); + masm.addPtr(Imm32(stackSize + JitStubFrameLayout::Size() - sizeof(intptr_t)), scratch); + + // Push frame descriptor and perform the tail call. + MOZ_ASSERT(ICTailCallReg == ra); + masm.makeFrameDescriptor(scratch, JitFrame_IonJS, ExitFrameLayout::Size()); + masm.push(scratch); + masm.push(ICTailCallReg); + masm.branch(target); } inline void @@ -135,7 +146,17 @@ EmitBaselineCallVM(JitCode* target, MacroAssembler& masm) inline void EmitIonCallVM(JitCode* target, size_t stackSlots, MacroAssembler& masm) { - MOZ_CRASH("Not implemented yet."); + uint32_t descriptor = MakeFrameDescriptor(masm.framePushed(), JitFrame_IonStub, + ExitFrameLayout::Size()); + masm.Push(Imm32(descriptor)); + masm.callJit(target); + + // Remove rest of the frame left on the stack. We remove the return address + // which is implicitly popped when returning. + size_t framePop = sizeof(ExitFrameLayout) - sizeof(void*); + + // Pop arguments from framePushed. + masm.implicitPop(stackSlots * sizeof(void*) + framePop); } struct BaselineStubFrame { @@ -184,7 +205,15 @@ EmitBaselineEnterStubFrame(MacroAssembler& masm, Register scratch) inline void EmitIonEnterStubFrame(MacroAssembler& masm, Register scratch) { - MOZ_CRASH("Not implemented yet."); + MOZ_ASSERT(ICTailCallReg == ra); + + // In MIPS the ra register contains the return address, + // but in jit frames we expect it to be on the stack. As a result + // push the link register (which is actually part of the previous frame. + // Therefore using push instead of Push). + masm.push(ICTailCallReg); + + masm.Push(ICStubReg); } inline void @@ -219,7 +248,8 @@ EmitBaselineLeaveStubFrame(MacroAssembler& masm, bool calledIntoIon = false) inline void EmitIonLeaveStubFrame(MacroAssembler& masm) { - MOZ_CRASH("Not implemented yet."); + masm.Pop(ICStubReg); + masm.pop(ICTailCallReg); // See EmitIonEnterStubFrame for explanation on pop/Pop. } inline void diff --git a/js/src/proxy/ScriptedIndirectProxyHandler.cpp b/js/src/proxy/ScriptedIndirectProxyHandler.cpp index c427981198..e2446e06c9 100644 --- a/js/src/proxy/ScriptedIndirectProxyHandler.cpp +++ b/js/src/proxy/ScriptedIndirectProxyHandler.cpp @@ -299,10 +299,42 @@ ScriptedIndirectProxyHandler::get(JSContext* cx, HandleObject proxy, HandleValue if (!GetDerivedTrap(cx, handler, cx->names().get, &fval)) return false; if (!IsCallable(fval)) - return BaseProxyHandler::get(cx, proxy, receiver, id, vp); + return derivedGet(cx, proxy, receiver, id, vp); return Trap(cx, handler, fval, 2, argv.begin(), vp); } +bool +ScriptedIndirectProxyHandler::derivedGet(JSContext* cx, HandleObject proxy, HandleValue receiver, + HandleId id, MutableHandleValue vp) const +{ + // This uses getPropertyDescriptor for backward compatibility reasons. + + Rooted desc(cx); + if (!getPropertyDescriptor(cx, proxy, id, &desc)) + return false; + desc.assertCompleteIfFound(); + + if (!desc.object()) { + vp.setUndefined(); + return true; + } + + if (desc.isDataDescriptor()) { + vp.set(desc.value()); + return true; + } + + MOZ_ASSERT(desc.isAccessorDescriptor()); + RootedObject getter(cx, desc.getterObject()); + + if (!getter) { + vp.setUndefined(); + return true; + } + + return InvokeGetter(cx, receiver, ObjectValue(*getter), vp); +} + bool ScriptedIndirectProxyHandler::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) const diff --git a/js/src/proxy/ScriptedIndirectProxyHandler.h b/js/src/proxy/ScriptedIndirectProxyHandler.h index f7a3436e3a..a8fc6bf85e 100644 --- a/js/src/proxy/ScriptedIndirectProxyHandler.h +++ b/js/src/proxy/ScriptedIndirectProxyHandler.h @@ -55,6 +55,8 @@ class ScriptedIndirectProxyHandler : public BaseProxyHandler static const ScriptedIndirectProxyHandler singleton; private: + bool derivedGet(JSContext* cx, HandleObject proxy, HandleValue receiver, HandleId id, + MutableHandleValue vp) const; bool derivedSet(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, HandleValue receiver, ObjectOpResult& result) const; }; diff --git a/js/src/tests/ecma_6/LexicalEnvironment/block-scoped-functions-deprecated-redecl.js b/js/src/tests/ecma_6/LexicalEnvironment/block-scoped-functions-deprecated-redecl.js index 6f023b454a..af32be5d3d 100644 --- a/js/src/tests/ecma_6/LexicalEnvironment/block-scoped-functions-deprecated-redecl.js +++ b/js/src/tests/ecma_6/LexicalEnvironment/block-scoped-functions-deprecated-redecl.js @@ -9,6 +9,18 @@ // Annex B still works. assertEq(f(), 4); +// The same thing with labels. +{ + assertEq(f(), 4); + function f() { return 3; } + assertEq(f(), 4); + l: function f() { return 4; } + assertEq(f(), 4); +} + +// Annex B still works. +assertEq(f(), 4); + function test() { { assertEq(f(), 2); diff --git a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js index ea8ed61c01..9c00e85049 100644 --- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js +++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js @@ -910,7 +910,7 @@ LoginManagerPrompter.prototype = { _removeLoginNotifications : function () { var popupNote = this._getPopupNote(); if (popupNote) - popupNote = popupNote.getNotification("password-save"); + popupNote = popupNote.getNotification("password"); if (popupNote) popupNote.remove(); diff --git a/toolkit/components/passwordmgr/test/notification_common.js b/toolkit/components/passwordmgr/test/notification_common.js index 2663aa1f1e..8b774aae9b 100644 --- a/toolkit/components/passwordmgr/test/notification_common.js +++ b/toolkit/components/passwordmgr/test/notification_common.js @@ -39,7 +39,11 @@ function getPopupNotifications(aWindow) { */ function getPopup(aPopupNote, aKind) { ok(true, "Looking for " + aKind + " popup notification"); - return aPopupNote.getNotification(aKind); + var notification = aPopupNote.getNotification("password"); + if (notification) { + is(notification.options.passwordNotificationType, aKind); + } + return notification; } diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index 24f1451679..90eea15cfb 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -4206,6 +4206,24 @@ "n_buckets": 20, "description": "Session restore: Number of characters in DOM Storage for a tab. Pages without DOM Storage or with an empty DOM Storage are ignored." }, + "FX_SESSION_RESTORE_AUTO_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS": { + "alert_emails": ["session-restore-telemetry-alerts@mozilla.com"], + "expires_in_version": "default", + "kind": "exponential", + "low": "100", + "high": "100000", + "n_buckets": 20, + "description": "Session restore: If the browser is setup to auto-restore tabs, this probe measures the time elapsed between the instant we start Session Restore and the instant we have finished restoring tabs eagerly. At this stage, the tabs that are restored on demand are not restored yet." + }, + "FX_SESSION_RESTORE_MANUAL_RESTORE_DURATION_UNTIL_EAGER_TABS_RESTORED_MS": { + "alert_emails": ["session-restore-telemetry-alerts@mozilla.com"], + "expires_in_version": "default", + "kind": "exponential", + "low": "100", + "high": "100000", + "n_buckets": 20, + "description": "Session restore: If a session is restored by the user clicking on 'Restore Session', this probe measures the time elapsed between the instant the user has clicked and the instant we have finished restoring tabs eagerly. At this stage, the tabs that are restored on demand are not restored yet." + }, "FX_TABLETMODE_PAGE_LOAD": { "expires_in_version": "47", "kind": "exponential", @@ -5108,12 +5126,6 @@ "keyed": true, "description": "Record the search counts for search engines" }, - "SEARCH_DEFAULT_ENGINE": { - "expires_in_version": "never", - "kind": "flag", - "keyed": true, - "description": "Record the default search engine." - }, "SEARCH_SERVICE_INIT_MS": { "expires_in_version": "never", "kind": "exponential", @@ -7860,26 +7872,26 @@ "description": "Cache hits on the tile-info metadata database" }, "DISPLAY_SCALING_OSX" : { - "expires_in_version": "40", + "expires_in_version": "never", "kind": "linear", - "high": "500", - "n_buckets": "100", + "high": 500, + "n_buckets": 100, "description": "Scaling percentage for the display where the first window is opened (OS X only)", "cpp_guard": "XP_MACOSX" }, "DISPLAY_SCALING_MSWIN" : { - "expires_in_version": "40", + "expires_in_version": "never", "kind": "linear", - "high": "500", - "n_buckets": "100", + "high": 500, + "n_buckets": 100, "description": "Scaling percentage for the display where the first window is opened (MS Windows only)", "cpp_guard": "XP_WIN" }, "DISPLAY_SCALING_LINUX" : { - "expires_in_version": "40", + "expires_in_version": "never", "kind": "linear", - "high": "500", - "n_buckets": "100", + "high": 500, + "n_buckets": 100, "description": "Scaling percentage for the display where the first window is opened (Linux only)", "cpp_guard": "XP_LINUX" }, diff --git a/tools/docs/index.rst b/tools/docs/index.rst index fae8856b2d..ae2a9d5930 100644 --- a/tools/docs/index.rst +++ b/tools/docs/index.rst @@ -21,8 +21,8 @@ Managing Documentation This documentation is generated via the `Sphinx `_ tool from sources in the tree. -To build the documentation, run ``mach build-docs``. Run -``mach help build-docs`` to see configurable options. +To build the documentation, run ``mach doc``. Run +``mach help doc`` to see configurable options. Adding Documentation --------------------