Ignore:
Timestamp:
Mar 1, 2022, 10:42:27 PM (3 years ago)
Author:
Devin Rousso
Message:

Web Inspector: add a switch to control whether breakpoint evaluations (condition, ignore count, actions) are also affected by script blackboxing
https://bugs.webkit.org/show_bug.cgi?id=235274

Reviewed by Patrick Angle.

Source/JavaScriptCore:

Being able to defer breakpoint evaluations until the next actual pause can sometimes be far
more useful than doing the breakpoint evaluations at the breakpoint's original location.

As an example, configuring the All Events breakpoint with a console.trace() action and
auto-continue enabled would not provide much useful information without also blackboxing
breakpoint evaluations as the original pause location of the All Events breakpoint is on the
first line of the event handler, meaning that if the script containing the event handler is
blackboxed then the console.trace() would still only show that location even though the
Sources Tab would show the first line of code outside of that script (due to the blackbox).
Being able to also blackbox breakpoint evaluations would instead cause the console.trace()
action to have the same output as the Sources Tab, since it's evaluation would be deferred
until execution actually paused, which is further inside the event handler.

  • debugger/Debugger.cpp:

(JSC::Debugger::Debugger):
(JSC::Debugger::didHitBreakpoint):
(JSC::Debugger::evaluateBreakpointCondition):
(JSC::Debugger::continueProgram):
(JSC::Debugger::pauseIfNeeded):
(JSC::Debugger::didExecuteProgram):
(JSC::Debugger::setBlackboxBreakpointEvaluations): Added.
Keep track of every Breakpoint wants to pause but is deferred due to blackboxing.
Depending on whether breakpoint evaluations are also blackboxed (based on the flag set via
the piping code/logic below), check if the script attempting to be paused in is blackboxed
before or after handling breakpoint evaluations.
Side effects of this change are:

  • breakpoint conditions are now evaluated right before that breakpoint's actions, instead of evaluating all breakpoint conditions before all breakpoint actions
  • if breakpoint evaluations are blackboxed, more than two breakpoints can now be evaluated

during a single pause attempt depending on how may deferrals (with breakpoints) happened

  • debugger/Debugger.h:

(JSC::Debugger::Observer::didDeferBreakpointPause): Added.
Add a way to notify InspectorDebuggerAgent of deferred breakpoint pauses so that the
correct originalReason will be used after a deferred pause.

  • inspector/protocol/Debugger.json:
  • inspector/agents/InspectorDebuggerAgent.h:
  • inspector/agents/InspectorDebuggerAgent.cpp:

(Inspector::InspectorDebuggerAgent::setBlackboxBreakpointEvaluations): Added.
(Inspector::InspectorDebuggerAgent::didDeferBreakpointPause): Added.
Add a setBlackboxBreakpointEvaluations command that passes directly to Debugger.

Source/WebInspectorUI:

Being able to defer breakpoint evaluations until the next actual pause can sometimes be far
more useful than doing the breakpoint evaluations at the breakpoint's original location.

As an example, configuring the All Events breakpoint with a console.trace() action and
auto-continue enabled would not provide much useful information without also blackboxing
breakpoint evaluations as the original pause location of the All Events breakpoint is on the
first line of the event handler, meaning that if the script containing the event handler is
blackboxed then the console.trace() would still only show that location even though the
Sources Tab would show the first line of code outside of that script (due to the blackbox).
Being able to also blackbox breakpoint evaluations would instead cause the console.trace()
action to have the same output as the Sources Tab, since it's evaluation would be deferred
until execution actually paused, which is further inside the event handler

  • UserInterface/Base/Setting.js:
  • UserInterface/Controllers/DebuggerManager.js:

(WI.DebuggerManager.supportsBlackboxingBreakpointEvaluations): Added.
(WI.DebuggerManager.prototype.async initializeTarget):
(WI.DebuggerManager.prototype._setBlackboxBreakpointEvaluations): Added.
(WI.DebuggerManager.prototype._handleBlackboxBreakpointEvaluationsChange): Added.
Create a global WI.Setting for this feature and handle changes from the code below.

  • UserInterface/Views/BlackboxSettingsView.js:

(WI.BlackboxSettingsView.prototype.initialLayout):

  • UserInterface/Views/BlackboxSettingsView.css:

(.settings-view.blackbox > p > label): Added.
(.settings-view.blackbox > p > label > input): Added.
(.settings-view.blackbox > p > label > span): Added.
Add a checkbox and explanation of this new feature in the Blackbox pane of the Settings Tab.

  • Localizations/en.lproj/localizedStrings.js:

LayoutTests:

  • inspector/debugger/setBlackboxBreakpointEvaluations.html: Added.
  • inspector/debugger/setBlackboxBreakpointEvaluations-expected.txt: Added.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/debugger/Debugger.cpp

    r290575 r290720  
    114114Debugger::Debugger(VM& vm)
    115115    : m_vm(vm)
     116    , m_blackboxBreakpointEvaluations(false)
    116117    , m_pauseAtNextOpportunity(false)
    117118    , m_pastFirstExpressionInStatement(false)
     
    544545}
    545546
    546 RefPtr<Breakpoint> Debugger::didHitBreakpoint(JSGlobalObject* globalObject, SourceID sourceID, const TextPosition& position)
     547RefPtr<Breakpoint> Debugger::didHitBreakpoint(SourceID sourceID, const TextPosition& position)
    547548{
    548549    if (!m_breakpointsActivated)
     
    566567        // Since frontend truncates the indent, the first statement in a line must match the breakpoint (line,0).
    567568        ASSERT(this == m_currentCallFrame->codeBlock()->globalObject()->debugger());
    568         if ((line != m_lastExecutedLine && line == breakLine && !breakColumn) || (line == breakLine && column == breakColumn)) {
    569             if (breakpoint->shouldPause(*this, globalObject))
    570                 return breakpoint.copyRef();
    571             break;
    572         }
     569        if ((line != m_lastExecutedLine && line == breakLine && !breakColumn) || (line == breakLine && column == breakColumn))
     570            return breakpoint.copyRef();
    573571    }
    574572
     
    607605bool Debugger::evaluateBreakpointCondition(Breakpoint& breakpoint, JSGlobalObject* globalObject)
    608606{
     607    ASSERT(m_isPaused);
     608    ASSERT(isAttached(globalObject));
     609
    609610    const String& condition = breakpoint.condition();
    610611    if (condition.isEmpty())
    611612        return true;
    612 
    613     // We cannot stop in the debugger while executing condition code,
    614     // so make it looks like the debugger is already paused.
    615     TemporaryPausedState pausedState(*this);
    616613
    617614    NakedPtr<Exception> exception;
     
    782779{
    783780    clearNextPauseState();
     781    m_deferredBreakpoints.clear();
    784782
    785783    if (!m_isPaused)
     
    884882    DebuggerPausedScope debuggerPausedScope(*this);
    885883
    886     bool pauseNow = m_pauseAtNextOpportunity;
    887     pauseNow |= (m_pauseOnCallFrame == m_currentCallFrame);
    888 
    889     bool didPauseForStep = pauseNow;
     884    bool afterBlackboxedScript = m_afterBlackboxedScript;
     885    bool pauseNow = false;
     886    bool didPauseForStep = false;
     887    if (m_pauseAtNextOpportunity) {
     888        pauseNow = true;
     889        didPauseForStep = !afterBlackboxedScript;
     890    } else if (m_pauseOnStepNext || m_pauseOnStepOut || (m_pauseOnCallFrame == m_currentCallFrame)) {
     891        pauseNow = true;
     892        didPauseForStep = true;
     893    }
    890894
    891895    TextPosition position = DebuggerCallFrame::positionForCallFrame(vm, m_currentCallFrame);
    892896
    893     auto breakpoint = didHitBreakpoint(globalObject, sourceID, position);
    894     if (breakpoint)
     897    if (auto breakpoint = didHitBreakpoint(sourceID, position)) {
    895898        pauseNow = true;
     899        m_deferredBreakpoints.add(breakpoint.releaseNonNull());
     900    }
    896901
    897902    // Special breakpoints are only given one opportunity to pause.
    898     auto specialBreakpoint = WTFMove(m_specialBreakpoint);
    899     if (specialBreakpoint && specialBreakpoint->shouldPause(*this, globalObject))
     903    if (m_specialBreakpoint) {
    900904        pauseNow = true;
     905        m_deferredBreakpoints.add(m_specialBreakpoint.releaseNonNull());
     906    }
    901907
    902908    m_lastExecutedLine = position.m_line.zeroBasedInt();
     
    904910        return;
    905911
    906     bool afterBlackboxedScript = m_afterBlackboxedScript;
    907912    clearNextPauseState();
    908913
     
    911916    TemporaryPausedState pausedState(*this);
    912917
    913     if (breakpoint || specialBreakpoint) {
    914         // Note that the actions can potentially stop the debugger, so we need to check that
    915         // we still have a current call frame when we get back.
    916 
    917         bool autoContinue = false;
    918 
    919         if (breakpoint) {
    920             evaluateBreakpointActions(*breakpoint, globalObject);
    921 
     918    auto shouldDeferPause = [&] () {
     919        if (blackboxTypeIterator == m_blackboxedScripts.end())
     920            return false;
     921
     922        if (blackboxTypeIterator->value != BlackboxType::Deferred)
     923            return false;
     924
     925        m_afterBlackboxedScript = true;
     926
     927        if (m_pausingBreakpointID != noBreakpointID) {
     928            dispatchFunctionToObservers([&] (Observer& observer) {
     929                observer.didDeferBreakpointPause(m_pausingBreakpointID);
     930            });
     931
     932            m_pausingBreakpointID = noBreakpointID;
     933        }
     934
     935        schedulePauseAtNextOpportunity();
     936        return true;
     937    };
     938
     939    if (m_blackboxBreakpointEvaluations && shouldDeferPause())
     940        return;
     941
     942    if (!m_deferredBreakpoints.isEmpty()) {
     943        std::optional<BreakpointID> pausingBreakpointID;
     944        bool hasEvaluatedSpecialBreakpoint = false;
     945        bool shouldContinue = true;
     946
     947        for (auto&& deferredBreakpoint : std::exchange(m_deferredBreakpoints, { })) {
     948            // Note that breakpoint evaluations can potentially stop the debugger, so we need to
     949            // check that we still have a current call frame after evaluating them.
     950
     951            bool shouldPause = deferredBreakpoint->shouldPause(*this, globalObject);
    922952            if (!m_currentCallFrame)
    923953                return;
    924 
    925             if (breakpoint->isAutoContinue())
    926                 autoContinue = true;
    927         }
    928 
    929         if (specialBreakpoint) {
    930             evaluateBreakpointActions(*specialBreakpoint, globalObject);
    931 
     954            if (!shouldPause)
     955                continue;
     956
     957            evaluateBreakpointActions(deferredBreakpoint, globalObject);
    932958            if (!m_currentCallFrame)
    933959                return;
    934960
    935             if (specialBreakpoint->isAutoContinue())
    936                 autoContinue = true;
     961            if (deferredBreakpoint->isAutoContinue())
     962                continue;
     963
     964            shouldContinue = false;
     965
     966            // Only propagate `PausedForBreakpoint` to the `InspectorDebuggerAgent` if the first
     967            // line:column breakpoint hit was before the first special breakpoint, as the latter
     968            // would already have set a unique reason before attempting to pause.
     969            if (!deferredBreakpoint->isLinked())
     970                hasEvaluatedSpecialBreakpoint = true;
     971            else if (!hasEvaluatedSpecialBreakpoint && !pausingBreakpointID)
     972                pausingBreakpointID = deferredBreakpoint->id();
    937973        }
    938974
    939         if (autoContinue) {
     975        if (shouldContinue) {
    940976            if (!didPauseForStep)
    941977                return;
    942 
    943             breakpoint = nullptr;
    944             specialBreakpoint = nullptr;
    945         } else if (breakpoint)
    946             m_pausingBreakpointID = breakpoint->id();
    947     }
    948 
    949     if (blackboxTypeIterator != m_blackboxedScripts.end() && blackboxTypeIterator->value == BlackboxType::Deferred) {
    950         m_afterBlackboxedScript = true;
    951         schedulePauseAtNextOpportunity();
    952         return;
    953     }
     978        } else if (pausingBreakpointID)
     979            m_pausingBreakpointID = *pausingBreakpointID;
     980    }
     981
     982    if (!m_blackboxBreakpointEvaluations && shouldDeferPause())
     983        return;
    954984
    955985    {
     
    957987        if (afterBlackboxedScript)
    958988            reason = PausedAfterBlackboxedScript;
    959         else if (breakpoint)
     989        else if (m_pausingBreakpointID)
    960990            reason = PausedForBreakpoint;
    961991        PauseReasonDeclaration rauseReasonDeclaration(*this, reason);
     
    11551185
    11561186    // Do not continue stepping into an unknown future program.
    1157     if (!m_currentCallFrame)
     1187    if (!m_currentCallFrame) {
    11581188        clearNextPauseState();
     1189        m_deferredBreakpoints.clear();
     1190    }
    11591191}
    11601192
     
    12121244}
    12131245
     1246void Debugger::setBlackboxBreakpointEvaluations(bool blackboxBreakpointEvaluations)
     1247{
     1248    m_blackboxBreakpointEvaluations = blackboxBreakpointEvaluations;
     1249}
     1250
    12141251void Debugger::clearBlackbox()
    12151252{
Note: See TracChangeset for help on using the changeset viewer.