From 3c3c788b73ebe0d2240ce708ce7ba8f2ea72abe4 Mon Sep 17 00:00:00 2001 From: Michael Saboff Date: Wed, 20 Mar 2024 17:59:11 -0700 Subject: [PATCH 1/3] Scoped Arguments needs to alias between named and unnamed accesses and across nested scopes https://bugs.webkit.org/show_bug.cgi?id=261934 rdar://114925088 rdar://117838992 Reviewed by Yusuke Suzuki. Fixed issue where an access to a named argument and a seperate access via its argument[i] counterpart weren't recognized throughout all JIT tiers as accesses to the same scoped value. The DFG bytecode parser can unknowingly constant fold the read access. Added aliasing via the SymbolTable and its ScopedArgumentsTable for both types of accesses of such values. related objects Added watchpoints for scoped arguments, and shared the watchpoint from the SymbolTableEntry for the named parameter with the ScopedArgument entry for the matching index. Tagged op_put_to_scope bytecodes with a new ScopedArgumentInitialization initialization type in GetPutInfo to signify this shared watchpoint case. Since currently all tiers write to scoped arguments via ScopedArguments::setIndexQuickly(), that is where we fire its watchpoint. Added a new test. * JSTests/stress/arrow-function-captured-arguments-aliased.js: Added. (createOptAll): (createOpt500): (createOpt2000): (createOpt5000): (main): * Source/JavaScriptCore/bytecode/CodeBlock.cpp: (JSC::CodeBlock::finishCreation): * Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp: (JSC::BytecodeGenerator::BytecodeGenerator): * Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp: (JSC::DFG::ByteCodeParser::parseBlock): * Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm: * Source/JavaScriptCore/llint/LowLevelInterpreter64.asm: * Source/JavaScriptCore/runtime/GetPutInfo.h: (JSC::initializationModeName): (JSC::isInitialization): * Source/JavaScriptCore/runtime/ScopedArguments.cpp: (JSC::ScopedArguments::unmapArgument): * Source/JavaScriptCore/runtime/ScopedArguments.h: * Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp: (JSC::ScopedArgumentsTable::tryCreate): (JSC::ScopedArgumentsTable::tryClone): (JSC::ScopedArgumentsTable::trySetLength): (JSC::ScopedArgumentsTable::trySetWatchpointSet): * Source/JavaScriptCore/runtime/ScopedArgumentsTable.h: * Source/JavaScriptCore/runtime/SymbolTable.cpp: (JSC::SymbolTable::cloneScopePart): * Source/JavaScriptCore/runtime/SymbolTable.h: Originally-landed-as: 272448.5@safari-7618-branch (97894699773c). rdar://124557495 Canonical link: https://commits.webkit.org/276437@main --- ...row-function-captured-arguments-aliased.js | 66 +++++++++++++++++++ Source/JavaScriptCore/bytecode/CodeBlock.cpp | 7 +- .../bytecompiler/BytecodeGenerator.cpp | 18 +++-- .../JavaScriptCore/dfg/DFGByteCodeParser.cpp | 3 + .../llint/LowLevelInterpreter32_64.asm | 2 +- .../llint/LowLevelInterpreter64.asm | 2 +- Source/JavaScriptCore/runtime/GetPutInfo.h | 7 +- .../runtime/ScopedArguments.cpp | 1 + .../JavaScriptCore/runtime/ScopedArguments.h | 9 ++- .../runtime/ScopedArgumentsTable.cpp | 17 ++++- .../runtime/ScopedArgumentsTable.h | 8 ++- Source/JavaScriptCore/runtime/SymbolTable.h | 11 ++++ 12 files changed, 136 insertions(+), 15 deletions(-) create mode 100644 JSTests/stress/arrow-function-captured-arguments-aliased.js diff --git a/JSTests/stress/arrow-function-captured-arguments-aliased.js b/JSTests/stress/arrow-function-captured-arguments-aliased.js new file mode 100644 index 0000000000000..e8c4c93276b18 --- /dev/null +++ b/JSTests/stress/arrow-function-captured-arguments-aliased.js @@ -0,0 +1,66 @@ +function createOptAll(count) { + count = 0; + + return () => { + arguments[0]++; + + return count; + }; +} + +function createOpt500(count) { + count = 0; + + return () => { + arguments[0]++; + + if (arguments[0] < 500) + return arguments[0]; + + return count; + }; +} + +function createOpt2000(count) { + count = 0; + + return () => { + arguments[0]++; + + if (arguments[0] < 2000) + return arguments[0]; + + return count; + }; +} + +function createOpt5000(count) { + count = 0; + + return () => { + arguments[0]++; + + if (arguments[0] < 5000) + return arguments[0]; + + return count; + }; +} + +function main() { + const testCount = 10000; + const createOptFuncs = [createOptAll, createOpt500, createOpt2000, createOpt5000]; + for (createOptFunc of createOptFuncs) { + const opt = createOptFunc(0); + for (let i = 0; i < testCount; i++) { + opt(); + } + + const expectedResult = testCount+1; + let actualResult = opt(); + if (actualResult != expectedResult) + print("Expected " + expectedResult + ", got " + actualResult); + } +} + +main(); diff --git a/Source/JavaScriptCore/bytecode/CodeBlock.cpp b/Source/JavaScriptCore/bytecode/CodeBlock.cpp index 0513a514b7e2e..57d3bc032fc8a 100644 --- a/Source/JavaScriptCore/bytecode/CodeBlock.cpp +++ b/Source/JavaScriptCore/bytecode/CodeBlock.cpp @@ -601,7 +601,12 @@ bool CodeBlock::finishCreation(VM& vm, ScriptExecutable* ownerExecutable, Unlink ConcurrentJSLocker locker(symbolTable->m_lock); auto iter = symbolTable->find(locker, ident.impl()); ASSERT(iter != symbolTable->end(locker)); - iter->value.prepareToWatch(); + if (bytecode.m_getPutInfo.initializationMode() == InitializationMode::ScopedArgumentInitialization) { + ASSERT(bytecode.m_value.isArgument()); + unsigned argumentIndex = bytecode.m_value.toArgument() - 1; + symbolTable->prepareToWatchScopedArgument(iter->value, argumentIndex); + } else + iter->value.prepareToWatch(); metadata.m_watchpointSet = iter->value.watchpointSet(); } else metadata.m_watchpointSet = nullptr; diff --git a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp index a895c1770db51..51124e90201f0 100644 --- a/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp +++ b/Source/JavaScriptCore/bytecompiler/BytecodeGenerator.cpp @@ -539,17 +539,23 @@ BytecodeGenerator::BytecodeGenerator(VM& vm, FunctionNode* functionNode, Unlinke m_outOfMemoryDuringConstruction = true; return; } + + unsigned varOrAnonymous = UINT_MAX; + if (UniquedStringImpl* name = visibleNameForParameter(parameters.at(i).first)) { VarOffset varOffset(offset); SymbolTableEntry entry(varOffset); - // Stores to these variables via the ScopedArguments object will not do - // notifyWrite(), since that would be cumbersome. Also, watching formal - // parameters when "arguments" is in play is unlikely to be super profitable. - // So, we just disable it. - entry.disableWatching(m_vm); functionSymbolTable->set(NoLockingNecessary, name, entry); + +IGNORE_GCC_WARNINGS_BEGIN("dangling-reference") + const Identifier& ident = + static_cast(parameters.at(i).first)->boundProperty(); +IGNORE_GCC_WARNINGS_END + + varOrAnonymous = addConstant(ident); } - OpPutToScope::emit(this, m_lexicalEnvironmentRegister, UINT_MAX, virtualRegisterForArgumentIncludingThis(1 + i), GetPutInfo(ThrowIfNotFound, ResolvedClosureVar, InitializationMode::NotInitialization, ecmaMode), SymbolTableOrScopeDepth::symbolTable(VirtualRegister { symbolTableConstantIndex }), offset.offset()); + + OpPutToScope::emit(this, m_lexicalEnvironmentRegister, varOrAnonymous, virtualRegisterForArgumentIncludingThis(1 + i), GetPutInfo(ThrowIfNotFound, ResolvedClosureVar, InitializationMode::ScopedArgumentInitialization, ecmaMode), SymbolTableOrScopeDepth::symbolTable(VirtualRegister { symbolTableConstantIndex }), offset.offset()); } // This creates a scoped arguments object and copies the overflow arguments into the diff --git a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp index 26b0f9f643186..b1275d0d5e5ae 100644 --- a/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp +++ b/Source/JavaScriptCore/dfg/DFGByteCodeParser.cpp @@ -8103,6 +8103,9 @@ void ByteCodeParser::parseBlock(unsigned limit) // Must happen after the store. See comment for GetGlobalVar. addToGraph(NotifyWrite, OpInfo(watchpoints)); } + + // Keep scope alive until after put. + addToGraph(Phantom, scopeNode); break; } diff --git a/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm b/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm index 70401e45be6d0..0c56cde3dccc6 100644 --- a/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm +++ b/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm @@ -2821,7 +2821,7 @@ llintOpWithMetadata(op_put_to_scope, OpPutToScope, macro (size, get, dispatch, m loadi OpPutToScope::Metadata::m_getPutInfo + GetPutInfo::m_operand[t5], t0 andi InitializationModeMask, t0 rshifti InitializationModeShift, t0 - bineq t0, NotInitialization, .noNeedForTDZCheck + bilt t0, NotInitialization, .noNeedForTDZCheck loadp OpPutToScope::Metadata::m_operand[t5], t0 loadi TagOffset[t0], t0 bieq t0, EmptyValueTag, .pDynamic diff --git a/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm b/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm index 52ef9a04efe14..d1027ed199932 100644 --- a/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm +++ b/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm @@ -3007,7 +3007,7 @@ llintOpWithMetadata(op_put_to_scope, OpPutToScope, macro (size, get, dispatch, m loadi OpPutToScope::Metadata::m_getPutInfo + GetPutInfo::m_operand[t5], t0 andi InitializationModeMask, t0 rshifti InitializationModeShift, t0 - bineq t0, NotInitialization, .noNeedForTDZCheck + bilt t0, NotInitialization, .noNeedForTDZCheck loadp OpPutToScope::Metadata::m_operand[t5], t0 loadq [t0], t0 bqeq t0, ValueEmpty, .pDynamic diff --git a/Source/JavaScriptCore/runtime/GetPutInfo.h b/Source/JavaScriptCore/runtime/GetPutInfo.h index e12ba4bb7c986..ae65d57188cc6 100644 --- a/Source/JavaScriptCore/runtime/GetPutInfo.h +++ b/Source/JavaScriptCore/runtime/GetPutInfo.h @@ -83,7 +83,8 @@ enum ResolveType : unsigned { enum class InitializationMode : unsigned { Initialization, // "let x = 20;" ConstInitialization, // "const x = 20;" - NotInitialization // "x = 20;" + NotInitialization, // "x = 20;" + ScopedArgumentInitialization // Assign to scoped argument, which also has NotInitialization semantics. }; ALWAYS_INLINE const char* resolveModeName(ResolveMode resolveMode) @@ -120,7 +121,8 @@ ALWAYS_INLINE const char* initializationModeName(InitializationMode initializati static const char* const names[] = { "Initialization", "ConstInitialization", - "NotInitialization" + "NotInitialization", + "ScopedArgumentInitialization" }; return names[static_cast(initializationMode)]; } @@ -132,6 +134,7 @@ ALWAYS_INLINE bool isInitialization(InitializationMode initializationMode) case InitializationMode::ConstInitialization: return true; case InitializationMode::NotInitialization: + case InitializationMode::ScopedArgumentInitialization: return false; } ASSERT_NOT_REACHED(); diff --git a/Source/JavaScriptCore/runtime/ScopedArguments.cpp b/Source/JavaScriptCore/runtime/ScopedArguments.cpp index d3131e6b93f17..71abecba5fa20 100644 --- a/Source/JavaScriptCore/runtime/ScopedArguments.cpp +++ b/Source/JavaScriptCore/runtime/ScopedArguments.cpp @@ -155,6 +155,7 @@ void ScopedArguments::unmapArgument(JSGlobalObject* globalObject, uint32_t i) return; } m_table.set(vm, this, maybeCloned); + m_table->clearWatchpointSet(i); } else storage()[i - namedLength].clear(); } diff --git a/Source/JavaScriptCore/runtime/ScopedArguments.h b/Source/JavaScriptCore/runtime/ScopedArguments.h index 385fdb67b61b1..6d53328b0204a 100644 --- a/Source/JavaScriptCore/runtime/ScopedArguments.h +++ b/Source/JavaScriptCore/runtime/ScopedArguments.h @@ -27,6 +27,7 @@ #include "GenericArguments.h" #include "JSLexicalEnvironment.h" +#include "Watchpoint.h" namespace JSC { @@ -111,9 +112,13 @@ class ScopedArguments final : public GenericArguments { { ASSERT_WITH_SECURITY_IMPLICATION(isMappedArgument(i)); unsigned namedLength = m_table->length(); - if (i < namedLength) + if (i < namedLength) { m_scope->variableAt(m_table->get(i)).set(vm, m_scope.get(), value); - else + + auto* watchpointSet = m_table->getWatchpointSet(i); + if (watchpointSet) + watchpointSet->touch(vm, "Write to ScopedArgument."); + } else storage()[i - namedLength].set(vm, this, value); } diff --git a/Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp b/Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp index fae2508d5d2d2..688b7d50b4efd 100644 --- a/Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp +++ b/Source/JavaScriptCore/runtime/ScopedArgumentsTable.cpp @@ -70,6 +70,7 @@ ScopedArgumentsTable* ScopedArgumentsTable::tryCreate(VM& vm, uint32_t length) result->m_arguments = ArgumentsPtr::tryCreate(length); if (UNLIKELY(!result->m_arguments)) return nullptr; + result->m_watchpointSets.fill(nullptr, length); return result; } @@ -80,6 +81,7 @@ ScopedArgumentsTable* ScopedArgumentsTable::tryClone(VM& vm) return nullptr; for (unsigned i = m_length; i--;) result->at(i) = this->at(i); + result->m_watchpointSets = this->m_watchpointSets; return result; } @@ -93,14 +95,18 @@ ScopedArgumentsTable* ScopedArgumentsTable::trySetLength(VM& vm, uint32_t newLen newArguments.at(i, newLength) = this->at(i); m_length = newLength; m_arguments = WTFMove(newArguments); + m_watchpointSets.resize(newLength); return this; } ScopedArgumentsTable* result = tryCreate(vm, newLength); if (UNLIKELY(!result)) return nullptr; - for (unsigned i = std::min(m_length, newLength); i--;) + m_watchpointSets.resize(newLength); + for (unsigned i = std::min(m_length, newLength); i--;) { result->at(i) = this->at(i); + result->m_watchpointSets[i] = this->m_watchpointSets[i]; + } return result; } @@ -119,6 +125,15 @@ ScopedArgumentsTable* ScopedArgumentsTable::trySet(VM& vm, uint32_t i, ScopeOffs return result; } +void ScopedArgumentsTable::trySetWatchpointSet(uint32_t i, WatchpointSet* watchpoints) +{ + ASSERT(watchpoints); + if (i >= m_watchpointSets.size()) + return; + + m_watchpointSets[i] = watchpoints; +} + Structure* ScopedArgumentsTable::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) { return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info()); diff --git a/Source/JavaScriptCore/runtime/ScopedArgumentsTable.h b/Source/JavaScriptCore/runtime/ScopedArgumentsTable.h index a1f45a4d689dc..6d42c5da771c8 100644 --- a/Source/JavaScriptCore/runtime/ScopedArgumentsTable.h +++ b/Source/JavaScriptCore/runtime/ScopedArgumentsTable.h @@ -33,6 +33,8 @@ namespace JSC { +class WatchpointSet; + // This class's only job is to hold onto the list of ScopeOffsets for each argument that a // function has. Most of the time, the BytecodeGenerator will create one of these and it will // never be modified subsequently. There is a rare case where a ScopedArguments object is created @@ -67,6 +69,7 @@ class ScopedArgumentsTable final : public JSCell { ScopedArgumentsTable* trySetLength(VM&, uint32_t newLength); ScopeOffset get(uint32_t i) const { return at(i); } + WatchpointSet* getWatchpointSet(uint32_t i) const { return m_watchpointSets.at(i); } void lock() { @@ -74,7 +77,9 @@ class ScopedArgumentsTable final : public JSCell { } ScopedArgumentsTable* trySet(VM&, uint32_t index, ScopeOffset); - + void trySetWatchpointSet(uint32_t index, WatchpointSet* watchpoints); + void clearWatchpointSet(uint32_t index) { m_watchpointSets[index] = nullptr; } + DECLARE_INFO; static Structure* createStructure(VM&, JSGlobalObject*, JSValue prototype); @@ -96,6 +101,7 @@ class ScopedArgumentsTable final : public JSCell { uint32_t m_length; bool m_locked; // Being locked means that there are multiple references to this object and none of them expect to see the others' modifications. This means that modifications need to make a copy first. ArgumentsPtr m_arguments; + Vector m_watchpointSets; }; } // namespace JSC diff --git a/Source/JavaScriptCore/runtime/SymbolTable.h b/Source/JavaScriptCore/runtime/SymbolTable.h index cf0ff330496e4..1271d13233ed1 100644 --- a/Source/JavaScriptCore/runtime/SymbolTable.h +++ b/Source/JavaScriptCore/runtime/SymbolTable.h @@ -668,6 +668,7 @@ class SymbolTable final : public JSCell { return false; m_arguments.set(vm, this, table); } + return true; } @@ -687,6 +688,16 @@ class SymbolTable final : public JSCell { return true; } + void prepareToWatchScopedArgument(SymbolTableEntry& entry, uint32_t i) + { + entry.prepareToWatch(); + if (!m_arguments) + return; + + WatchpointSet* watchpoints = entry.watchpointSet(); + m_arguments->trySetWatchpointSet(i, watchpoints); + } + ScopedArgumentsTable* arguments() const { if (!m_arguments) From aa485ddeff855d4762f214166da258045abc0949 Mon Sep 17 00:00:00 2001 From: Yijia Huang Date: Mon, 24 Apr 2023 13:03:05 -0700 Subject: [PATCH 2/3] Add MemoryExhaustion to enable existing instead of crashing on out of memory https://bugs.webkit.org/show_bug.cgi?id=255881 rdar://108438866 Reviewed by Yusuke Suzuki and Justin Michaud. It's intend to crash when a worker exhausting memory when constructing a VM. Use RELEASE_ASSERT_RESOURCE_AVAILABLE instead for these types of resource exhaustion to unblock fuzzer. * Source/JavaScriptCore/runtime/ResourceExhaustion.h: * Source/JavaScriptCore/runtime/VM.cpp: (JSC::VM::VM): Canonical link: https://commits.webkit.org/263328@main --- Source/JavaScriptCore/runtime/ResourceExhaustion.h | 1 + Source/JavaScriptCore/runtime/VM.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/JavaScriptCore/runtime/ResourceExhaustion.h b/Source/JavaScriptCore/runtime/ResourceExhaustion.h index e4043506b5433..e4e1af664a529 100644 --- a/Source/JavaScriptCore/runtime/ResourceExhaustion.h +++ b/Source/JavaScriptCore/runtime/ResourceExhaustion.h @@ -29,6 +29,7 @@ namespace JSC { enum ResourceExhaustionCode { StructureIDExhaustion = 30, + MemoryExhaustion = 31, }; #define RELEASE_ASSERT_RESOURCE_AVAILABLE(assertion, resourceExhaustionCode, failureMessage) do { \ diff --git a/Source/JavaScriptCore/runtime/VM.cpp b/Source/JavaScriptCore/runtime/VM.cpp index f1f5679eb00fc..92f618f9682b2 100644 --- a/Source/JavaScriptCore/runtime/VM.cpp +++ b/Source/JavaScriptCore/runtime/VM.cpp @@ -92,6 +92,7 @@ #include "PropertyTable.h" #include "RandomizingFuzzerAgent.h" #include "RegExpCache.h" +#include "ResourceExhaustion.h" #include "SamplingProfiler.h" #include "ScopedArguments.h" #include "ShadowChicken.h" @@ -299,7 +300,7 @@ VM::VM(VMType vmType, HeapType heapType, WTF::RunLoop* runLoop, bool* success) if (success) *success = false; else - RELEASE_ASSERT(bigInt); + RELEASE_ASSERT_RESOURCE_AVAILABLE(bigInt, MemoryExhaustion, "Crash intentionally because memory is exhausted."); } } From d0b956bcd804b1e72714eaeb67bc0ccb38d6fc68 Mon Sep 17 00:00:00 2001 From: Michael Saboff Date: Mon, 25 Mar 2024 13:14:26 -0700 Subject: [PATCH 3/3] REGRESSION: JavaScriptCore: JSC::ScopedArguments::setIndexQuickly https://bugs.webkit.org/show_bug.cgi?id=268409 rdar://121748005 Reviewed by Yusuke Suzuki. A code inspection of the symbol table and scoped arguments code revealed that SymbolTable::cloneScopePart() doesn't properly copy the ScopedArgumentsTable from the source. Since ScopedArguments point to the WatchpointSets in the related SymbolTable, we need to create new WatchpointSets in the cloned SymbolTable and have the ScopedArguments point to the related new WatchpointSets. This is a speculative fix. * Source/JavaScriptCore/runtime/ScopedArguments.h: * Source/JavaScriptCore/runtime/SymbolTable.cpp: (JSC::SymbolTable::cloneScopePart): (JSC::SymbolTable::hasScopedWatchpointSet): * Source/JavaScriptCore/runtime/SymbolTable.h: Originally-landed-as: 272448.422@safari-7618-branch (5bc92c9d5253). rdar://124554329 Canonical link: https://commits.webkit.org/276646@main --- .../JavaScriptCore/runtime/ScopedArguments.h | 6 +- Source/JavaScriptCore/runtime/SymbolTable.cpp | 58 ++++++++++++++++--- Source/JavaScriptCore/runtime/SymbolTable.h | 4 ++ 3 files changed, 59 insertions(+), 9 deletions(-) diff --git a/Source/JavaScriptCore/runtime/ScopedArguments.h b/Source/JavaScriptCore/runtime/ScopedArguments.h index 6d53328b0204a..a2f1df7e0164f 100644 --- a/Source/JavaScriptCore/runtime/ScopedArguments.h +++ b/Source/JavaScriptCore/runtime/ScopedArguments.h @@ -116,8 +116,12 @@ class ScopedArguments final : public GenericArguments { m_scope->variableAt(m_table->get(i)).set(vm, m_scope.get(), value); auto* watchpointSet = m_table->getWatchpointSet(i); - if (watchpointSet) + if (watchpointSet) { +#if ASSERT_ENABLED + ASSERT(m_scope->symbolTable()->hasScopedWatchpointSet(watchpointSet)); +#endif watchpointSet->touch(vm, "Write to ScopedArgument."); + } } else storage()[i - namedLength].set(vm, this, value); } diff --git a/Source/JavaScriptCore/runtime/SymbolTable.cpp b/Source/JavaScriptCore/runtime/SymbolTable.cpp index b8bfc72f2780b..35d7603737f40 100644 --- a/Source/JavaScriptCore/runtime/SymbolTable.cpp +++ b/Source/JavaScriptCore/runtime/SymbolTable.cpp @@ -31,6 +31,7 @@ #include "CodeBlock.h" #include "JSCJSValueInlines.h" +#include "ResourceExhaustion.h" #include "TypeProfiler.h" #include @@ -150,19 +151,44 @@ SymbolTable* SymbolTable::cloneScopePart(VM& vm) result->m_nestedLexicalScope = m_nestedLexicalScope; result->m_scopeType = m_scopeType; + HashMap varOffsetToArgIndexMap; + + if (this->arguments()) { + // Copy the arguments, but not the WatchpointSets. We create new WatchpointSets as appropriate when we create the SymbolTableEntry + // copies below and propogate the new watchpointSets to the new ScopedArgumentsTable. + auto length = this->arguments()->length(); + ScopedArgumentsTable* arguments = ScopedArgumentsTable::tryCreate(vm, length); + RELEASE_ASSERT_RESOURCE_AVAILABLE(arguments, MemoryExhaustion, "Crash intentionally because memory is exhausted."); + + for (uint32_t index = 0; index < length; ++index) { + ScopeOffset offset = this->arguments()->get(index); + + arguments->trySet(vm, index, offset); + if (this->arguments()->getWatchpointSet(index)) + varOffsetToArgIndexMap.set(VarOffset(offset), index); + } + + result->m_arguments.set(vm, result, arguments); + } + + bool hasScopedArgumentWatchpoints = !varOffsetToArgIndexMap.isEmpty(); + for (auto iter = m_map.begin(), end = m_map.end(); iter != end; ++iter) { if (!iter->value.varOffset().isScope()) continue; - result->m_map.add( - iter->key, - SymbolTableEntry(iter->value.varOffset(), iter->value.getAttributes())); + SymbolTableEntry entry(iter->value.varOffset(), iter->value.getAttributes()); + + if (hasScopedArgumentWatchpoints) { + auto findIter = varOffsetToArgIndexMap.find(iter->value.varOffset()); + if (findIter != varOffsetToArgIndexMap.end()) + result->prepareToWatchScopedArgument(entry, findIter->value); + } + + result->m_map.add(iter->key, WTFMove(entry)); } - + result->m_maxScopeOffset = m_maxScopeOffset; - - if (ScopedArgumentsTable* arguments = this->arguments()) - result->m_arguments.set(vm, result, arguments); - + if (m_rareData) { result->m_rareData = makeUnique(); @@ -285,6 +311,22 @@ RefPtr SymbolTable::globalTypeSetForVariable(const ConcurrentJSLocker& return iter->value; } +#if ASSERT_ENABLED +bool SymbolTable::hasScopedWatchpointSet(WatchpointSet* watchpointSet) +{ + for (auto iter = m_map.begin(), end = m_map.end(); iter != end; ++iter) { + if (!iter->value.varOffset().isScope()) + continue; + + auto* entryWatchpointSet = iter->value.watchpointSet(); + if (entryWatchpointSet && entryWatchpointSet == watchpointSet) + return true; + } + + return false; +} +#endif + void SymbolTable::dump(PrintStream& out) const { ConcurrentJSLocker locker(m_lock); diff --git a/Source/JavaScriptCore/runtime/SymbolTable.h b/Source/JavaScriptCore/runtime/SymbolTable.h index 1271d13233ed1..0d8ebbd21b891 100644 --- a/Source/JavaScriptCore/runtime/SymbolTable.h +++ b/Source/JavaScriptCore/runtime/SymbolTable.h @@ -748,6 +748,10 @@ class SymbolTable final : public JSCell { DECLARE_EXPORT_INFO; +#if ASSERT_ENABLED + bool hasScopedWatchpointSet(WatchpointSet*); +#endif + void finalizeUnconditionally(VM&); void dump(PrintStream&) const;