Codemod asserts to assertxs in the runtime
[hiphop-php.git] / hphp / runtime / debugger / cmd / cmd_flow_control.cpp
blobd951eda444dac4e27626eca780170e6ab2a26964
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/debugger/cmd/cmd_flow_control.h"
19 #include <algorithm>
21 #include "hphp/runtime/debugger/debugger_client.h"
22 #include "hphp/runtime/vm/debugger-hook.h"
23 #include "hphp/runtime/vm/hhbc-codec.h"
24 #include "hphp/runtime/vm/vm-regs.h"
26 namespace HPHP { namespace Eval {
27 ///////////////////////////////////////////////////////////////////////////////
29 TRACE_SET_MOD(debuggerflow);
31 CmdFlowControl::~CmdFlowControl() {
32 // Remove any location filter that may have been setup.
33 removeLocationFilter();
36 void CmdFlowControl::sendImpl(DebuggerThriftBuffer &thrift) {
37 DebuggerCommand::sendImpl(thrift);
38 thrift.write(m_count);
39 thrift.write(m_smallStep);
42 void CmdFlowControl::recvImpl(DebuggerThriftBuffer &thrift) {
43 DebuggerCommand::recvImpl(thrift);
44 thrift.read(m_count);
45 thrift.read(m_smallStep);
48 void CmdFlowControl::onClient(DebuggerClient &client) {
49 if (DebuggerCommand::displayedHelp(client)) return;
51 client.setFrame(0);
53 if (client.argCount() > 1) {
54 help(client);
55 return;
58 if (client.argCount() == 1) {
59 std::string snum = client.argValue(1);
60 if (!DebuggerClient::IsValidNumber(snum)) {
61 client.error("Count needs to be a number.");
62 return;
65 m_count = atoi(snum.c_str());
66 if (m_count < 1) {
67 client.error("Count needs to be a positive number.");
68 return;
71 m_smallStep = client.getDebuggerClientSmallStep();
72 client.sendToServer(this);
73 throw DebuggerConsoleExitException();
76 bool CmdFlowControl::onServer(DebuggerProxy& /*proxy*/) {
77 // Flow control cmds do their work in onSetup() and onBeginInterrupt(), so
78 // there is no real work to do in here.
79 return true;
82 // Setup the flow filter on the VM context for all offsets covered by
83 // the current source line. This will short-circuit the work done in
84 // phpDebuggerOpcodeHook() and ensure we don't interrupt on this source line.
85 // We exclude continuation opcodes which transfer control out of the function,
86 // which allows cmds to get a chance to alter their behavior when those opcodes
87 // are encountered.
88 void CmdFlowControl::installLocationFilterForLine(InterruptSite *site) {
89 // We may be stopped at a place with no source info.
90 if (!site || !site->valid()) return;
91 RequestInjectionData &rid = ThreadInfo::s_threadInfo->m_reqInjectionData;
92 rid.m_flowFilter.clear();
93 TRACE(3, "Prepare location filter for %s:%d, unit %p:\n",
94 site->getFile(), site->getLine0(), site->getUnit());
95 OffsetRangeVec ranges;
96 const auto unit = site->getUnit();
97 if (m_smallStep) {
98 // Get offset range for the pc only.
99 OffsetRange range;
100 if (unit->getOffsetRange(site->getCurOffset(), range)) {
101 ranges.push_back(range);
103 } else {
104 // Get offset ranges for the whole line.
105 // We use line1 here because it seems to be working better than line0
106 // in a handful of cases for our bytecode-source mapping.
107 if (!unit->getOffsetRanges(site->getLine1(), ranges)) {
108 ranges.clear();
111 auto func = unit->getFunc(site->getCurOffset());
112 if (func->isResumable()) {
113 auto excludeResumableReturns = [] (Op op) {
114 return (op != OpYield) &&
115 (op != OpYieldK) &&
116 (op != OpAwait) &&
117 (op != OpRetC);
119 rid.m_flowFilter.addRanges(unit, ranges,
120 excludeResumableReturns);
121 } else {
122 rid.m_flowFilter.addRanges(unit, ranges);
126 void CmdFlowControl::removeLocationFilter() {
127 ThreadInfo::s_threadInfo->m_reqInjectionData.m_flowFilter.clear();
130 bool CmdFlowControl::hasStepOuts() {
131 return m_stepOut1.valid() || m_stepOut2.valid();
134 bool CmdFlowControl::atStepOutOffset(Unit* unit, Offset o) {
135 return m_stepOut1.at(unit, o) || m_stepOut2.at(unit, o);
138 // Place internal breakpoints to get out of the current function. This may place
139 // multiple internal breakpoints, and it may place them more than one frame up.
140 // Some instructions can cause PHP to be invoked without an explicit call. A set
141 // which causes a destructor to run, a iteration init which causes an object's
142 // next() method to run, a RetC which causes destructors to run, etc. This
143 // recgonizes such cases and ensures we have internal breakpoints to cover the
144 // destination(s) of such instructions.
145 void CmdFlowControl::setupStepOuts() {
146 // Existing step outs should be cleaned up before making new ones.
147 assertx(!hasStepOuts());
148 auto fp = vmfp();
149 if (!fp) return; // No place to step out to!
150 Offset returnOffset;
151 bool fromVMEntry;
152 while (!hasStepOuts()) {
153 fp = g_context->getPrevVMState(fp, &returnOffset, nullptr, &fromVMEntry);
154 // If we've run off the top of the stack, just return having setup no
155 // step outs. This will cause cmds like Next and Out to just let the program
156 // run, which is appropriate.
157 if (!fp) break;
158 Unit* returnUnit = fp->m_func->unit();
159 PC returnPC = returnUnit->at(returnOffset);
160 TRACE(2, "CmdFlowControl::setupStepOuts: at '%s' offset %d opcode %s\n",
161 fp->m_func->fullName()->data(), returnOffset,
162 opcodeToName(peek_op(returnPC)));
163 // Don't step out to generated or builtin functions, keep looking.
164 if (fp->m_func->line1() == 0) continue;
165 if (fp->m_func->isBuiltin()) continue;
166 if (fromVMEntry) {
167 TRACE(2, "CmdFlowControl::setupStepOuts: VM entry\n");
168 // We only execute this for opcodes which invoke more PHP, and that does
169 // not include switches. Thus, we'll have at most two destinations.
170 auto const retOp = peek_op(returnPC);
171 assertx(!isSwitch(retOp) && numSuccs(returnPC) <= 2);
172 // Set an internal breakpoint after the instruction if it can fall thru.
173 if (instrAllowsFallThru(retOp)) {
174 Offset nextOffset = returnOffset + instrLen(returnPC);
175 TRACE(2, "CmdFlowControl: step out to '%s' offset %d (fall-thru)\n",
176 fp->m_func->fullName()->data(), nextOffset);
177 m_stepOut1 = StepDestination(returnUnit, nextOffset);
179 // Set an internal breakpoint at the target of a control flow instruction.
180 // A good example of a control flow op that invokes PHP is IterNext.
181 if (instrIsControlFlow(retOp)) {
182 Offset target = instrJumpTarget(returnPC, 0);
183 if (target != InvalidAbsoluteOffset) {
184 Offset targetOffset = returnOffset + target;
185 TRACE(2, "CmdFlowControl: step out to '%s' offset %d (jump target)\n",
186 fp->m_func->fullName()->data(), targetOffset);
187 m_stepOut2 = StepDestination(returnUnit, targetOffset);
190 // If we have no place to step out to, then unwind another frame and try
191 // again. The most common case that leads here is Ret*, which does not
192 // fall-thru and has no encoded target.
193 } else {
194 TRACE(2, "CmdFlowControl: step out to '%s' offset %d\n",
195 fp->m_func->fullName()->data(), returnOffset);
196 m_stepOut1 = StepDestination(returnUnit, returnOffset);
201 void CmdFlowControl::cleanupStepOuts() {
202 if (m_stepOut1.valid()) {
203 m_stepOut1 = StepDestination();
205 if (m_stepOut2.valid()) {
206 m_stepOut2 = StepDestination();
210 ///////////////////////////////////////////////////////////////////////////////
211 // StepDestination
213 // NB: a StepDestination also manages an internal breakpoint at the
214 // given location. If there is already an internal breakpoint set when
215 // a StepDestination is constructed then it will not remove the
216 // breakpoint when it is destructed. The move assignment operator
217 // handles the transfer of ownership, and we delete the copy
218 // constructor/assignment operators explicitly to ensure no two
219 // StepDestinations believe they can remove the same internal
220 // breakpoint.
222 // We can manage internal breakpoints without a true refcount like
223 // this because no other operation can manipulate the breakpoint
224 // filter while a single flow control command is active, and because
225 // the lifetime of a StepDestination is scoped by the lifetime of its
226 // flow control command.
228 CmdFlowControl::StepDestination::StepDestination() :
229 m_unit(nullptr), m_offset(InvalidAbsoluteOffset),
230 m_ownsInternalBreakpoint(false)
234 CmdFlowControl::StepDestination::StepDestination(const Unit* unit,
235 Offset offset) :
236 m_unit(unit), m_offset(offset)
238 m_ownsInternalBreakpoint = !phpHasBreakpoint(m_unit, m_offset);
239 if (m_ownsInternalBreakpoint) phpAddBreakPoint(m_unit, m_offset);
242 CmdFlowControl::StepDestination::StepDestination(StepDestination&& other) {
243 *this = std::move(other);
246 CmdFlowControl::StepDestination&
247 CmdFlowControl::StepDestination::operator=(StepDestination&& other) {
248 if (this != &other) {
249 if (m_ownsInternalBreakpoint) phpRemoveBreakPoint(m_unit, m_offset);
250 m_unit = other.m_unit;
251 m_offset = other.m_offset;
252 m_ownsInternalBreakpoint = other.m_ownsInternalBreakpoint;
253 other.m_unit = nullptr;
254 other.m_offset = InvalidAbsoluteOffset;
255 other.m_ownsInternalBreakpoint = false;
257 return *this;
260 CmdFlowControl::StepDestination::~StepDestination() {
261 if (m_ownsInternalBreakpoint) phpRemoveBreakPoint(m_unit, m_offset);
264 ///////////////////////////////////////////////////////////////////////////////