Skip to content

Commit 6385302

Browse files
committed
Add a way to return early from detected infinite loops to aid the fuzzer
https://bugs.webkit.org/show_bug.cgi?id=214067 Reviewed by Yusuke Suzuki. It's useful for the fuzzer to not get stuck in infinite loops so its test cases can make forward progress trying to find bugs. This patch adds a new mechanism where we can early return if we've exceeded a total execution count for a static loop in bytecode. Note: this is not on a per-frame basis, but it's a way to implement this in a non-invasive way which is also practical for the fuzzer to use. * b3/air/AirAllocateRegistersAndStackAndGenerateCode.cpp: (JSC::B3::Air::GenerateAndAllocateRegisters::generate): * b3/air/AirCode.cpp: (JSC::B3::Air::Code::emitEpilogue): * b3/air/AirCode.h: * b3/air/AirGenerate.cpp: (JSC::B3::Air::generateWithAlreadyAllocatedRegisters): * bytecode/BytecodeList.rb: * bytecode/CodeBlock.cpp: (JSC::CodeBlock::finishCreation): (JSC::CodeBlock::~CodeBlock): * dfg/DFGCapabilities.cpp: (JSC::DFG::capabilityLevel): * dfg/DFGSpeculativeJIT64.cpp: (JSC::DFG::SpeculativeJIT::compile): * ftl/FTLLowerDFGToB3.cpp: (JSC::FTL::DFG::LowerDFGToB3::compileNode): (JSC::FTL::DFG::LowerDFGToB3::compileLoopHint): * jit/JITOpcodes.cpp: (JSC::JIT::emit_op_loop_hint): * llint/LLIntSlowPaths.cpp: (JSC::LLInt::LLINT_SLOW_PATH_DECL): * llint/LowLevelInterpreter32_64.asm: * llint/LowLevelInterpreter64.asm: * offlineasm/mips.rb: * runtime/OptionsList.h: * runtime/VM.cpp: (JSC::VM::addLoopHintExecutionCounter): (JSC::VM::getLoopHintExecutionCounter): (JSC::VM::removeLoopHintExecutionCounter): * runtime/VM.h: Canonical link: https://commits.webkit.org/226887@main git-svn-id: https://svn.webkit.org/repository/webkit/trunk@264105 268f45cc-cd09-0410-ab3c-d52691b4dbfc
1 parent 54cc485 commit 6385302

19 files changed

Lines changed: 234 additions & 15 deletions

‎Source/JavaScriptCore/ChangeLog‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,49 @@
1+
2020-07-08 Saam Barati <sbarati@apple.com>
2+
3+
Add a way to return early from detected infinite loops to aid the fuzzer
4+
https://bugs.webkit.org/show_bug.cgi?id=214067
5+
6+
Reviewed by Yusuke Suzuki.
7+
8+
It's useful for the fuzzer to not get stuck in infinite loops so its
9+
test cases can make forward progress trying to find bugs. This patch
10+
adds a new mechanism where we can early return if we've exceeded a total
11+
execution count for a static loop in bytecode. Note: this is not on a
12+
per-frame basis, but it's a way to implement this in a non-invasive way
13+
which is also practical for the fuzzer to use.
14+
15+
* b3/air/AirAllocateRegistersAndStackAndGenerateCode.cpp:
16+
(JSC::B3::Air::GenerateAndAllocateRegisters::generate):
17+
* b3/air/AirCode.cpp:
18+
(JSC::B3::Air::Code::emitEpilogue):
19+
* b3/air/AirCode.h:
20+
* b3/air/AirGenerate.cpp:
21+
(JSC::B3::Air::generateWithAlreadyAllocatedRegisters):
22+
* bytecode/BytecodeList.rb:
23+
* bytecode/CodeBlock.cpp:
24+
(JSC::CodeBlock::finishCreation):
25+
(JSC::CodeBlock::~CodeBlock):
26+
* dfg/DFGCapabilities.cpp:
27+
(JSC::DFG::capabilityLevel):
28+
* dfg/DFGSpeculativeJIT64.cpp:
29+
(JSC::DFG::SpeculativeJIT::compile):
30+
* ftl/FTLLowerDFGToB3.cpp:
31+
(JSC::FTL::DFG::LowerDFGToB3::compileNode):
32+
(JSC::FTL::DFG::LowerDFGToB3::compileLoopHint):
33+
* jit/JITOpcodes.cpp:
34+
(JSC::JIT::emit_op_loop_hint):
35+
* llint/LLIntSlowPaths.cpp:
36+
(JSC::LLInt::LLINT_SLOW_PATH_DECL):
37+
* llint/LowLevelInterpreter32_64.asm:
38+
* llint/LowLevelInterpreter64.asm:
39+
* offlineasm/mips.rb:
40+
* runtime/OptionsList.h:
41+
* runtime/VM.cpp:
42+
(JSC::VM::addLoopHintExecutionCounter):
43+
(JSC::VM::getLoopHintExecutionCounter):
44+
(JSC::VM::removeLoopHintExecutionCounter):
45+
* runtime/VM.h:
46+
147
2020-07-07 Yusuke Suzuki <ysuzuki@apple.com>
248

349
[JSC] BytecodeGenerator should be robust against failed constant generation

‎Source/JavaScriptCore/b3/air/AirAllocateRegistersAndStackAndGenerateCode.cpp‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -787,12 +787,7 @@ void GenerateAndAllocateRegisters::generate(CCallHelpers& jit)
787787

788788
// We currently don't represent the full epilogue in Air, so we need to
789789
// have this override.
790-
if (m_code.frameSize()) {
791-
m_jit->emitRestore(m_code.calleeSaveRegisterAtOffsetList());
792-
m_jit->emitFunctionEpilogue();
793-
} else
794-
m_jit->emitFunctionEpilogueWithEmptyFrame();
795-
m_jit->ret();
790+
m_code.emitEpilogue(*m_jit);
796791
}
797792

798793
if (needsToGenerate) {

‎Source/JavaScriptCore/b3/air/AirCode.cpp‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,16 @@ void Code::emitDefaultPrologue(CCallHelpers& jit)
105105
defaultPrologueGenerator(jit, *this);
106106
}
107107

108+
void Code::emitEpilogue(CCallHelpers& jit)
109+
{
110+
if (frameSize()) {
111+
jit.emitRestore(calleeSaveRegisterAtOffsetList());
112+
jit.emitFunctionEpilogue();
113+
} else
114+
jit.emitFunctionEpilogueWithEmptyFrame();
115+
jit.ret();
116+
}
117+
108118
void Code::setRegsInPriorityOrder(Bank bank, const Vector<Reg>& regs)
109119
{
110120
regsInPriorityOrderImpl(bank) = regs;

‎Source/JavaScriptCore/b3/air/AirCode.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ class Code {
346346
WeakRandom& weakRandom() { return m_weakRandom; }
347347

348348
void emitDefaultPrologue(CCallHelpers&);
349+
void emitEpilogue(CCallHelpers&);
349350

350351
std::unique_ptr<GenerateAndAllocateRegisters> m_generateAndAllocateRegisters;
351352

‎Source/JavaScriptCore/b3/air/AirGenerate.cpp‎

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -270,12 +270,7 @@ static void generateWithAlreadyAllocatedRegisters(Code& code, CCallHelpers& jit)
270270
// We currently don't represent the full prologue/epilogue in Air, so we need to
271271
// have this override.
272272
auto start = jit.labelIgnoringWatchpoints();
273-
if (code.frameSize()) {
274-
jit.emitRestore(code.calleeSaveRegisterAtOffsetList());
275-
jit.emitFunctionEpilogue();
276-
} else
277-
jit.emitFunctionEpilogueWithEmptyFrame();
278-
jit.ret();
273+
code.emitEpilogue(jit);
279274
addItem(block->last());
280275
auto end = jit.labelIgnoringWatchpoints();
281276
if (disassembler)

‎Source/JavaScriptCore/bytecode/BytecodeList.rb‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,6 +1389,7 @@
13891389
op :llint_internal_function_construct_trampoline
13901390
op :checkpoint_osr_exit_from_inlined_call_trampoline
13911391
op :checkpoint_osr_exit_trampoline
1392+
op :fuzzer_return_early_from_loop_hint
13921393
op :handleUncaughtException
13931394
op :op_call_return_location
13941395
op :op_construct_return_location

‎Source/JavaScriptCore/bytecode/CodeBlock.cpp‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -768,6 +768,12 @@ bool CodeBlock::finishCreation(VM& vm, ScriptExecutable* ownerExecutable, Unlink
768768
m_numberOfArgumentsToSkip = numberOfArgumentsToSkip;
769769
break;
770770
}
771+
772+
case op_loop_hint: {
773+
if (Options::returnEarlyFromInfiniteLoopsForFuzzing())
774+
vm.addLoopHintExecutionCounter(instruction.ptr());
775+
break;
776+
}
771777

772778
default:
773779
break;
@@ -811,6 +817,13 @@ CodeBlock::~CodeBlock()
811817
{
812818
VM& vm = *m_vm;
813819

820+
if (Options::returnEarlyFromInfiniteLoopsForFuzzing() && JITCode::isBaselineCode(jitType())) {
821+
for (const auto& instruction : instructions()) {
822+
if (instruction->is<OpLoopHint>())
823+
vm.removeLoopHintExecutionCounter(instruction.ptr());
824+
}
825+
}
826+
814827
#if ENABLE(DFG_JIT)
815828
// The JITCode (and its corresponding DFG::CommonData) may outlive the CodeBlock by
816829
// a short amount of time after the CodeBlock is destructed. For example, the

‎Source/JavaScriptCore/bytecode/CodeBlock.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -922,6 +922,12 @@ class CodeBlock : public JSCell {
922922
MetadataTable* metadataTable() { return m_metadata.get(); }
923923
const void* instructionsRawPointer() { return m_instructionsRawPointer; }
924924

925+
bool loopHintsAreEligibleForFuzzingEarlyReturn()
926+
{
927+
// Some builtins are required to always complete the loops they run.
928+
return !m_unlinkedCode->isBuiltinFunction();
929+
}
930+
925931
protected:
926932
void finalizeLLIntInlineCaches();
927933
#if ENABLE(JIT)

‎Source/JavaScriptCore/dfg/DFGCapabilities.cpp‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ CapabilityLevel capabilityLevel(OpcodeID opcodeID, CodeBlock* codeBlock, const I
317317
case checkpoint_osr_exit_from_inlined_call_trampoline:
318318
case checkpoint_osr_exit_trampoline:
319319
case handleUncaughtException:
320+
case fuzzer_return_early_from_loop_hint:
320321
case op_iterator_open_return_location:
321322
case op_iterator_next_return_location:
322323
case op_call_return_location:

‎Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4963,7 +4963,35 @@ void SpeculativeJIT::compile(Node* node)
49634963
break;
49644964

49654965
case PhantomLocal:
4966+
// This is a no-op.
4967+
noResult(node);
4968+
break;
4969+
49664970
case LoopHint:
4971+
if (Options::returnEarlyFromInfiniteLoopsForFuzzing()) {
4972+
CodeBlock* baselineCodeBlock = m_jit.graph().baselineCodeBlockFor(node->origin.semantic);
4973+
if (baselineCodeBlock->loopHintsAreEligibleForFuzzingEarlyReturn()) {
4974+
BytecodeIndex bytecodeIndex = node->origin.semantic.bytecodeIndex();
4975+
const Instruction* instruction = baselineCodeBlock->instructions().at(bytecodeIndex.offset()).ptr();
4976+
4977+
uint64_t* ptr = vm().getLoopHintExecutionCounter(instruction);
4978+
m_jit.pushToSave(GPRInfo::regT0);
4979+
m_jit.load64(ptr, GPRInfo::regT0);
4980+
auto skipEarlyReturn = m_jit.branch64(CCallHelpers::Below, GPRInfo::regT0, CCallHelpers::TrustedImm64(Options::earlyReturnFromInfiniteLoopsLimit()));
4981+
4982+
m_jit.popToRestore(GPRInfo::regT0);
4983+
m_jit.move(CCallHelpers::TrustedImm64(JSValue::encode(jsUndefined())), GPRInfo::returnValueGPR);
4984+
m_jit.emitRestoreCalleeSaves();
4985+
m_jit.emitFunctionEpilogue();
4986+
m_jit.ret();
4987+
4988+
skipEarlyReturn.link(&m_jit);
4989+
m_jit.add64(CCallHelpers::TrustedImm32(1), GPRInfo::regT0);
4990+
m_jit.store64(GPRInfo::regT0, ptr);
4991+
m_jit.popToRestore(GPRInfo::regT0);
4992+
}
4993+
}
4994+
49674995
// This is a no-op.
49684996
noResult(node);
49694997
break;

0 commit comments

Comments
 (0)