⚠ Archived content — this site is no longer maintained.   Current WebKit documentation is at docs.webkit.org.

Changeset 264105 in webkit


Ignore:
Timestamp:
Jul 8, 2020, 9:42:54 AM (6 years ago)
Author:
sbarati@apple.com
Message:

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:
Location:
trunk/Source/JavaScriptCore
Files:
19 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/ChangeLog

    r264059 r264105  
     12020-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
    1472020-07-07  Yusuke Suzuki  <ysuzuki@apple.com>
    248
  • trunk/Source/JavaScriptCore/b3/air/AirAllocateRegistersAndStackAndGenerateCode.cpp

    r254788 r264105  
    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               
  • trunk/Source/JavaScriptCore/b3/air/AirCode.cpp

    r256003 r264105  
    106106}
    107107
     108void 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
    108118void Code::setRegsInPriorityOrder(Bank bank, const Vector<Reg>& regs)
    109119{
  • trunk/Source/JavaScriptCore/b3/air/AirCode.h

    r256003 r264105  
    347347
    348348    void emitDefaultPrologue(CCallHelpers&);
     349    void emitEpilogue(CCallHelpers&);
    349350
    350351    std::unique_ptr<GenerateAndAllocateRegisters> m_generateAndAllocateRegisters;
  • trunk/Source/JavaScriptCore/b3/air/AirGenerate.cpp

    r261755 r264105  
    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();
  • trunk/Source/JavaScriptCore/bytecode/BytecodeList.rb

    r263035 r264105  
    13901390op :checkpoint_osr_exit_from_inlined_call_trampoline
    13911391op :checkpoint_osr_exit_trampoline
     1392op :fuzzer_return_early_from_loop_hint
    13921393op :handleUncaughtException
    13931394op :op_call_return_location
  • trunk/Source/JavaScriptCore/bytecode/CodeBlock.cpp

    r264049 r264105  
    769769            break;
    770770        }
     771
     772        case op_loop_hint: {
     773            if (Options::returnEarlyFromInfiniteLoopsForFuzzing())
     774                vm.addLoopHintExecutionCounter(instruction.ptr());
     775            break;
     776        }
    771777       
    772778        default:
    … …  
    811817{
    812818    VM& vm = *m_vm;
     819
     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    }
    813826
    814827#if ENABLE(DFG_JIT)
  • trunk/Source/JavaScriptCore/bytecode/CodeBlock.h

    r263117 r264105  
    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
    925931protected:
    926932    void finalizeLLIntInlineCaches();
  • trunk/Source/JavaScriptCore/dfg/DFGCapabilities.cpp

    r263117 r264105  
    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:
  • trunk/Source/JavaScriptCore/dfg/DFGSpeculativeJIT64.cpp

    r263824 r264105  
    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);
  • trunk/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp

    r263470 r264105  
    16211621            break;
    16221622
     1623        case LoopHint: {
     1624            compileLoopHint();
     1625            break;
     1626        }
     1627
    16231628        case PhantomLocal:
    1624         case LoopHint:
    16251629        case MovHint:
    16261630        case ZombieHint:
    … …  
    1469714701            RELEASE_ASSERT_NOT_REACHED();
    1469814702        }
     14703    }
     14704
     14705    void compileLoopHint()
     14706    {
     14707        if (!Options::returnEarlyFromInfiniteLoopsForFuzzing())
     14708            return;
     14709
     14710        CodeBlock* baselineCodeBlock = m_graph.baselineCodeBlockFor(m_node->origin.semantic);
     14711        if (!baselineCodeBlock->loopHintsAreEligibleForFuzzingEarlyReturn())
     14712            return;
     14713
     14714        BytecodeIndex bytecodeIndex = m_node->origin.semantic.bytecodeIndex();
     14715        const Instruction* instruction = baselineCodeBlock->instructions().at(bytecodeIndex.offset()).ptr();
     14716        uint64_t* ptr = vm().getLoopHintExecutionCounter(instruction);
     14717
     14718        PatchpointValue* patchpoint = m_out.patchpoint(Void);
     14719        patchpoint->effects = Effects::none();
     14720        patchpoint->effects.exitsSideways = true;
     14721        patchpoint->effects.writesLocalState = true;
     14722        patchpoint->setGenerator([ptr] (CCallHelpers& jit, const StackmapGenerationParams& params) {
     14723            auto restore = [&] {
     14724                jit.popToRestore(GPRInfo::regT2);
     14725                jit.popToRestore(GPRInfo::regT1);
     14726                jit.popToRestore(GPRInfo::regT0);
     14727            };
     14728
     14729            jit.pushToSave(GPRInfo::regT0);
     14730            jit.pushToSave(GPRInfo::regT1);
     14731            jit.pushToSave(GPRInfo::regT2);
     14732
     14733            jit.move(CCallHelpers::TrustedImm64(Options::earlyReturnFromInfiniteLoopsLimit()), GPRInfo::regT2);
     14734            jit.move(CCallHelpers::TrustedImmPtr(ptr), GPRInfo::regT0);
     14735            jit.load64(CCallHelpers::Address(GPRInfo::regT0), GPRInfo::regT1);
     14736            auto skipEarlyReturn = jit.branch64(CCallHelpers::Below, GPRInfo::regT1, GPRInfo::regT2);
     14737
     14738            restore();
     14739            jit.move(CCallHelpers::TrustedImm64(JSValue::encode(jsUndefined())), GPRInfo::returnValueGPR);
     14740            params.code().emitEpilogue(jit);
     14741
     14742            skipEarlyReturn.link(&jit);
     14743            jit.add64(CCallHelpers::TrustedImm32(1), GPRInfo::regT1);
     14744            jit.store64(GPRInfo::regT1, CCallHelpers::Address(GPRInfo::regT0));
     14745            restore();
     14746        });
    1469914747    }
    1470014748   
  • trunk/Source/JavaScriptCore/jit/JITOpcodes.cpp

    r263824 r264105  
    11611161#endif // USE(JSVALUE64)
    11621162
    1163 void JIT::emit_op_loop_hint(const Instruction*)
    1164 {
     1163void JIT::emit_op_loop_hint(const Instruction* instruction)
     1164{
     1165#if USE(JSVALUE64)
     1166    if (Options::returnEarlyFromInfiniteLoopsForFuzzing() && m_codeBlock->loopHintsAreEligibleForFuzzingEarlyReturn()) {
     1167        uint64_t* ptr = vm().getLoopHintExecutionCounter(instruction);
     1168        load64(ptr, regT0);
     1169        auto skipEarlyReturn = branch64(Below, regT0, TrustedImm64(Options::earlyReturnFromInfiniteLoopsLimit()));
     1170
     1171        moveValue(jsUndefined(), JSValueRegs { GPRInfo::returnValueGPR });
     1172        checkStackPointerAlignment();
     1173        emitRestoreCalleeSaves();
     1174        emitFunctionEpilogue();
     1175        ret();
     1176
     1177        skipEarlyReturn.link(this);
     1178        add64(TrustedImm32(1), regT0);
     1179        store64(regT0, ptr);
     1180    }
     1181#endif
     1182
    11651183    // Emit the JIT optimization check:
    11661184    if (canBeOptimized()) {
  • trunk/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp

    r263117 r264105  
    463463            *codeBlock, ": Entered loop_osr with executeCounter = ",
    464464            codeBlock->llintExecuteCounter());
     465
     466    if (UNLIKELY(Options::returnEarlyFromInfiniteLoopsForFuzzing() && codeBlock->loopHintsAreEligibleForFuzzingEarlyReturn())) {
     467        uint64_t* ptr = vm.getLoopHintExecutionCounter(pc);
     468        *ptr += codeBlock->llintExecuteCounter().m_activeThreshold;
     469        if (*ptr >= Options::earlyReturnFromInfiniteLoopsLimit())
     470            LLINT_RETURN_TWO(LLInt::getCodePtr<JSEntryPtrTag>(fuzzer_return_early_from_loop_hint).executableAddress(), callFrame->topOfFrame());
     471    }
     472   
    465473   
    466474    auto loopOSREntryBytecodeIndex = BytecodeIndex(codeBlock->bytecodeOffset(pc));
  • trunk/Source/JavaScriptCore/llint/LowLevelInterpreter32_64.asm

    r262613 r264105  
    27032703    dispatch()
    27042704end)
     2705
     2706
     2707op(fuzzer_return_early_from_loop_hint, macro ()
     2708    notSupported()
     2709end)
  • trunk/Source/JavaScriptCore/llint/LowLevelInterpreter64.asm

    r263546 r264105  
    29452945    hasStructurePropertyImpl(size, get, dispatch,  return, _slow_path_in_structure_property)
    29462946end)
     2947
     2948op(fuzzer_return_early_from_loop_hint, macro ()
     2949    move ValueUndefined, r0
     2950    doReturn()
     2951end)
     2952
  • trunk/Source/JavaScriptCore/offlineasm/mips.rb

    r260310 r264105  
    685685            # and we should name it properly.
    686686            # https://bugs.webkit.org/show_bug.cgi?id=208236
    687             if node.name =~ /^.*_return_location(?:_(?:wide16|wide32))?$/ or node.name.start_with?("_checkpoint_osr_exit_from_inlined_call_trampoline")
     687            if node.name =~ /^.*_return_location(?:_(?:wide16|wide32))?$/ or node.name.start_with?("_checkpoint_osr_exit_from_inlined_call_trampoline") or node.name.start_with?("_fuzzer_return_early_from_loop_hint")
    688688                # We need to have a special case for return location labels because they are always
    689689                # reached from a `ret` instruction. In this case, we need to proper reconfigure `$gp`
  • trunk/Source/JavaScriptCore/runtime/OptionsList.h

    r263380 r264105  
    511511    v(Bool, allowUnsupportedTiers, false, Normal, "If true, we will not disable DFG or FTL when an experimental feature is enabled.") \
    512512    v(Bool, usePrivateClassFields, false, Normal, "If true, the parser will understand private data fields inside classes.") \
     513    v(Bool, returnEarlyFromInfiniteLoopsForFuzzing, false, Normal, nullptr) \
     514    v(Size, earlyReturnFromInfiniteLoopsLimit, 1300000000, Normal, "When returnEarlyFromInfiniteLoopsForFuzzing is true, this determines the number of executions a loop can run for before just returning. This is helpful for the fuzzer so it doesn't get stuck in infinite loops.") \
    513515
    514516enum OptionEquivalence {
  • trunk/Source/JavaScriptCore/runtime/VM.cpp

    r263674 r264105  
    15531553}
    15541554
     1555void VM::addLoopHintExecutionCounter(const Instruction* instruction)
     1556{
     1557    auto locker = holdLock(m_loopHintExecutionCountLock);
     1558    auto addResult = m_loopHintExecutionCounts.add(instruction, std::pair<unsigned, std::unique_ptr<uint64_t>>(0, nullptr));
     1559    if (addResult.isNewEntry) {
     1560        auto ptr = WTF::makeUniqueWithoutFastMallocCheck<uint64_t>();
     1561        *ptr = 0;
     1562        addResult.iterator->value.second = WTFMove(ptr);
     1563    }
     1564    ++addResult.iterator->value.first;
     1565}
     1566
     1567uint64_t* VM::getLoopHintExecutionCounter(const Instruction* instruction)
     1568{
     1569    auto locker = holdLock(m_loopHintExecutionCountLock);
     1570    auto iter = m_loopHintExecutionCounts.find(instruction);
     1571    return iter->value.second.get();
     1572}
     1573
     1574void VM::removeLoopHintExecutionCounter(const Instruction* instruction)
     1575{
     1576    auto locker = holdLock(m_loopHintExecutionCountLock);
     1577    auto iter = m_loopHintExecutionCounts.find(instruction);
     1578    RELEASE_ASSERT(!!iter->value.first);
     1579    --iter->value.first;
     1580    if (!iter->value.first)
     1581        m_loopHintExecutionCounts.remove(iter);
     1582}
     1583
    15551584} // namespace JSC
  • trunk/Source/JavaScriptCore/runtime/VM.h

    r263117 r264105  
    11001100    };
    11011101
     1102    void addLoopHintExecutionCounter(const Instruction*);
     1103    uint64_t* getLoopHintExecutionCounter(const Instruction*);
     1104    void removeLoopHintExecutionCounter(const Instruction*);
     1105
    11021106private:
    11031107    friend class LLIntOffsetsExtractor;
    … …  
    12251229    uintptr_t m_currentWeakRefVersion { 0 };
    12261230
     1231    Lock m_loopHintExecutionCountLock;
     1232    HashMap<const Instruction*, std::pair<unsigned, std::unique_ptr<uint64_t>>> m_loopHintExecutionCounts;
     1233
    12271234    VM* m_prev; // Required by DoublyLinkedListNode.
    12281235    VM* m_next; // Required by DoublyLinkedListNode.
Note: See TracChangeset for help on using the changeset viewer.