Codemod asserts to assertxs in the runtime
[hiphop-php.git] / hphp / runtime / vm / async-flow-stepper.cpp
blob9746fc4004b84b302f8e0f12a9f886247b89bc9c
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/async-flow-stepper.h"
19 #include "hphp/runtime/base/tv-type.h"
20 #include "hphp/runtime/ext/asio/ext_asio.h"
21 #include "hphp/runtime/ext/asio/ext_async-function-wait-handle.h"
22 #include "hphp/runtime/vm/debugger-hook.h"
23 #include "hphp/runtime/vm/vm-regs.h"
25 namespace HPHP {
27 TRACE_SET_MOD(debuggerflow);
29 c_WaitableWaitHandle *objToWaitableWaitHandle(const Object& o) {
30 assertx(o->instanceof(c_WaitableWaitHandle::classof()));
31 return static_cast<c_WaitableWaitHandle*>(o.get());
34 bool AsyncFlowStepper::isActRecOnAsyncStack(const ActRec* target) {
35 auto currentWaitHandle = HHVM_FN(asio_get_running)();
36 if (currentWaitHandle.isNull()) {
37 return false;
39 auto const depStack =
40 objToWaitableWaitHandle(currentWaitHandle)->getDependencyStack();
41 if (depStack.empty()) {
42 return false;
44 ArrayIter iter(depStack);
45 ++iter; // Skip the top frame.
46 for (; iter; ++iter) {
47 auto const rval = iter.secondRval().unboxed();
48 if (isNullType(rval.type())) {
49 return false;
51 auto wh = objToWaitableWaitHandle(tvCastToObject(rval.tv()));
52 if (wh->getKind() == c_Awaitable::Kind::AsyncFunction &&
53 target == wh->asAsyncFunction()->actRec()) {
54 return true;
57 return false;
60 // Setup async stepping if needed.
61 void AsyncFlowStepper::setup() {
62 auto const func = vmfp()->func();
63 auto const unit = func != nullptr ? func->unit() : nullptr;
64 if (unit == nullptr) {
65 return;
67 if (!func->isAsyncFunction()) {
68 // Only perform async stepping inside async function.
69 return;
72 auto const pc = vmpc();
73 SourceLoc source_loc;
74 if (!unit->getSourceLoc(unit->offsetOf(pc), source_loc)) {
75 TRACE(5, "Could not grab the current line number\n");
76 return;
78 auto const line = source_loc.line1;
80 OffsetRangeVec curLineRanges;
81 if (!unit->getOffsetRanges(line, curLineRanges)) {
82 curLineRanges.clear();
85 auto const awaitOpcodeFilter = [](Op op) { return op == OpAwait; };
86 m_stepRangeFlowFilter.addRanges(
87 unit,
88 curLineRanges
90 m_awaitOpcodeBreakpointFilter.addRanges(
91 unit,
92 curLineRanges,
93 awaitOpcodeFilter
95 // So that we can get notified when hitting await instruction.
96 auto bpFilter = getBreakPointFilter();
97 bpFilter->addRanges(
98 unit,
99 curLineRanges,
100 awaitOpcodeFilter
102 updateStepStartStackDepth();
103 m_stage = AsyncStepperStage::StepOver;
106 AsyncStepHandleOpcodeResult AsyncFlowStepper::handleOpcode(PC pc) {
107 auto ret = AsyncStepHandleOpcodeResult::Unhandled;
108 switch (m_stage) {
109 case AsyncStepperStage::Disabled:
110 break;
112 case AsyncStepperStage::StepOver:
114 // Check if we are executing "await" instruction.
115 if (m_awaitOpcodeBreakpointFilter.checkPC(pc)) {
116 auto wh = c_Awaitable::fromCell(*vmsp());
117 // Is "await" blocked?
118 if (wh && !wh->isFinished()) {
119 handleBlockedAwaitOpcode(pc);
121 // else { no special action for non-blocked await. }
122 ret = AsyncStepHandleOpcodeResult::Handled;
123 } else if (isCompleted(pc)) {
124 reset();
125 ret = AsyncStepHandleOpcodeResult::Completed;
127 // else { let normal step-over logic to handle. }
129 break;
131 case AsyncStepperStage::StepOverAwait:
133 captureResumeIdAfterAwait();
134 // We have set internal breakpoint in resume block
135 // and captured the resumeId,
136 // do not need interrupt anymore.
137 RID().setDebuggerIntr(false);
138 m_stage = AsyncStepperStage::WaitResume;
139 ret = AsyncStepHandleOpcodeResult::Handled;
141 break;
143 case AsyncStepperStage::WaitResume:
145 // We are waiting for resume internal breakpoint to
146 // trigger do not let other steppers handle.
147 ret = AsyncStepHandleOpcodeResult::Handled;
148 if (didResumeBreakpointTrigger(pc)) {
149 // Finished await resume, start normal step-over again.
150 updateStepStartStackDepth();
151 m_stage = AsyncStepperStage::StepOver;
154 break;
156 default:
157 not_reached();
158 break;
160 return ret;
163 void AsyncFlowStepper::handleExceptionThrown() {
164 m_isCurrentAsyncStepException = isActRecOnAsyncStack(m_asyncResumableId);
167 bool AsyncFlowStepper::handleExceptionHandler() {
168 // Check if the throwing exception will break out of
169 // current async stepping or not; if true we need
170 // finish the async stepping in the exception handler.
171 if (m_stage == AsyncStepperStage::WaitResume &&
172 m_isCurrentAsyncStepException &&
173 !isActRecOnAsyncStack(m_asyncResumableId)) {
174 reset();
175 return true;
177 return false;
180 // Called when hitting a blocked "await" opcode.
181 // Async stepping resume internal breakpoint needs
182 // two parts to check triggering:
183 // 1. m_asyncResumableId matches.
184 // 2. breakpoint pc matches.
185 // #1 is required because this async function may be called multiple times
186 // and we need m_asyncResumableId to distinguish if the triggered resume
187 // breakpoint is the one we are stepping.
188 // m_asyncResumableId uses ActRec address as id so for eager execution mode
189 // we need step over "await" opcode to get the final ActRec address on heap.
190 void AsyncFlowStepper::handleBlockedAwaitOpcode(PC pc) {
191 TRACE(2, "AsyncFlowStepper: encountered blocking await\n");
192 setResumeInternalBreakpoint(pc);
193 auto fp = vmfp();
194 if (fp->resumed()) {
195 // Already in resumed execution mode.
196 m_asyncResumableId = fp;
197 m_stage = AsyncStepperStage::WaitResume;
198 } else {
199 // In eager execution non-resumed mode we need to step over "await" opcode
200 // then grab async function's migrated ActRec on heap as m_asyncResumableId.
201 stepOverAwaitOpcode();
202 m_stage = AsyncStepperStage::StepOverAwait;
206 // Used by eager execution mode to step over "await" opcode
207 // so that the async frame's ActRec is migrated to heap.
208 // This is required because async frame ActRec's address
209 // is used for m_asyncResumableId.
210 void AsyncFlowStepper::stepOverAwaitOpcode() {
211 assertx(vmfp()->func()->isAsyncFunction());
212 m_stage = AsyncStepperStage::StepOverAwait;
214 // Request interrupt opcode callback after "await" instruction
215 // so that we can increase to next stage.
216 RID().setDebuggerIntr(true);
219 // Should only be called immediately after "await" opcode
220 // to capture m_asyncResumableId.
221 void AsyncFlowStepper::captureResumeIdAfterAwait() {
222 auto topObj = vmsp()->m_data.pobj;
223 auto wh = static_cast<c_AsyncFunctionWaitHandle*>(topObj);
224 m_asyncResumableId = wh->actRec();
227 // Set guard internal breakpoint at async operation
228 // resume point to continue stepping.
229 void AsyncFlowStepper::setResumeInternalBreakpoint(PC pc) {
230 assertx(decode_op(pc) == Op::Await);
232 auto resumeInstPc = pc + instrLen(pc);
233 assertx(vmfp()->func()->unit()->offsetOf(resumeInstPc)
234 != InvalidAbsoluteOffset);
236 TRACE(2, "Setup internal breakpoint after await at '%s' offset %d\n",
237 vmfp()->func()->fullName()->data(),
238 vmfp()->func()->unit()->offsetOf(resumeInstPc));
239 m_resumeBreakpointFilter.addPC(resumeInstPc);
241 auto bpFilter = getBreakPointFilter();
242 bpFilter->addPC(resumeInstPc);
244 m_stage = AsyncStepperStage::WaitResume;
247 bool AsyncFlowStepper::didResumeBreakpointTrigger(PC pc) {
248 if (!UNLIKELY(m_resumeBreakpointFilter.checkPC(pc))) {
249 return false;
251 return m_asyncResumableId == getAsyncResumableId(vmfp());
254 const ActRec* AsyncFlowStepper::getAsyncResumableId(const ActRec* fp) {
255 assertx(fp->resumed());
256 assertx(fp->func()->isResumable());
257 return fp;
260 void AsyncFlowStepper::updateStepStartStackDepth() {
261 auto& req_data = RID();
262 int stackDepth = req_data.getDebuggerStackDepth();
263 if (stackDepth == 0) {
264 return;
266 m_stepStartStackDepth = stackDepth;
269 // Return 'true' if stepping has completed.
270 bool AsyncFlowStepper::isCompleted(PC pc) {
271 const auto stackDisp = getStackDisposition(m_stepStartStackDepth);
272 // Step completes when pc is outside of step range and
273 // is not in deeper frames(e.g. step-into).
274 return stackDisp != StackDepthDisposition::Deeper &&
275 !m_stepRangeFlowFilter.checkPC(pc);
278 void AsyncFlowStepper::reset() {
279 m_stage = AsyncStepperStage::Disabled;
280 m_asyncResumableId = nullptr;
281 m_stepRangeFlowFilter.clear();
282 m_awaitOpcodeBreakpointFilter.clear();
283 m_resumeBreakpointFilter.clear();
286 } // namespace HPHP