HHVM Debugger - Fix segfault when evaluating expression that produces an empty statem...
[hiphop-php.git] / hphp / runtime / ext / vsdebug / evaluate_command.cpp
blobb7cd70894a88de53cabea1e5c698c44cf1b44ea8
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2017-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/compiler/analysis/analysis_result.h"
18 #include "hphp/compiler/parser/parser.h"
19 #include "hphp/compiler/statement/statement_list.h"
20 #include "hphp/runtime/base/backtrace.h"
21 #include "hphp/runtime/base/execution-context.h"
22 #include "hphp/runtime/base/tv-variant.h"
23 #include "hphp/runtime/ext/vsdebug/command.h"
24 #include "hphp/runtime/ext/vsdebug/debugger.h"
25 #include "hphp/runtime/vm/runtime.h"
27 namespace HPHP {
28 namespace VSDEBUG {
30 EvaluateCommand::EvaluateCommand(
31 Debugger* debugger,
32 folly::dynamic message
33 ) : VSCommand(debugger, message),
34 m_frameId{0} {
36 const folly::dynamic& args = tryGetObject(message, "arguments", s_emptyArgs);
37 const int frameId = tryGetInt(args, "frameId", -1);
38 m_frameId = frameId;
41 EvaluateCommand::~EvaluateCommand() {
44 FrameObject* EvaluateCommand::getFrameObject(DebuggerSession* session) {
45 if (m_frameObj != nullptr) {
46 return m_frameObj;
49 m_frameObj = session->getFrameObject(m_frameId);
50 return m_frameObj;
53 request_id_t EvaluateCommand::targetThreadId(DebuggerSession* session) {
54 FrameObject* frame = getFrameObject(session);
55 if (frame == nullptr) {
56 // Execute the eval in the dummy context.
57 return Debugger::kDummyTheadId;
60 return frame->m_requestId;
63 ExecutionContext::EvaluationResult evaluate(
64 Debugger* debugger,
65 RequestInfo* ri,
66 HPHP::Unit* unit,
67 int frameDepth,
68 bool silent
69 ) {
70 ExecutionContext::EvaluationResult result;
72 if (silent) {
73 SilentEvaluationContext silentContext(debugger, ri);
74 result = g_context->evalPHPDebugger(unit, frameDepth);
75 } else {
76 result = g_context->evalPHPDebugger(unit, frameDepth);
79 return result;
82 bool EvaluateCommand::executeImpl(
83 DebuggerSession* session,
84 folly::dynamic* responseMsg
85 ) {
86 folly::dynamic& message = getMessage();
87 const folly::dynamic& args = tryGetObject(message, "arguments", s_emptyArgs);
88 const std::string& expression = tryGetString(args, "expression", "");
89 const auto threadId = targetThreadId(session);
91 std::string evalExpression = expression;
92 preparseEvalExpression(&evalExpression);
93 if (evalExpression.empty()) {
94 throw DebuggerCommandException("No expression provided to evaluate.");
97 // Enable bypassCheck, which allows eval statements from the debugger to
98 // violate visibility checks on object properties.
99 g_context->debuggerSettings.bypassCheck = true;
101 // Set the error reporting level to 0 so non-fatal errors in the expression
102 // are swallowed.
103 RequestInjectionData& rid = RID();
104 const int previousErrorLevel = rid.getErrorReportingLevel();
105 rid.setErrorReportingLevel(0);
107 RequestInfo* ri = m_debugger->getRequestInfo();
108 assert(ri->m_evaluateCommandDepth >= 0);
109 ri->m_evaluateCommandDepth++;
111 // Track if the evaluation command caused any opcode stepping to occur
112 // so we know if we need to re-send a stop event after the evaluation.
113 int previousPauseCount = ri->m_totalPauseCount;
114 bool isDummy = m_debugger->isDummyRequest();
116 // Put everything back on scope exit.
117 SCOPE_EXIT {
118 g_context->debuggerSettings.bypassCheck = false;
119 rid.setErrorReportingLevel(previousErrorLevel);
121 ri->m_evaluateCommandDepth--;
122 assert(ri->m_evaluateCommandDepth >= 0);
124 if (ri->m_evaluateCommandDepth == 0 && isDummy) {
125 // The dummy request only appears in the client UX while it is
126 // stopped at a breakpoint during an evaluation (because the user
127 // needs to see a call stack and scopes at that point). Otherwise,
128 // existance of the dummy is hiden from the user. If the dummy is
129 // no longer executing any evaluation, send a thread exited event
130 // to remove it from the front-end UX.
131 m_debugger->sendThreadEventMessage(
133 Debugger::ThreadEventType::ThreadExited
136 g_context->exitDebuggerDummyEnv();
140 Unit* unit = compile_string(evalExpression.c_str(), evalExpression.size());
141 if (unit == nullptr) {
142 // The compiler will already have printed more detailed error messages
143 // to stderr, which is redirected to the debugger client's console.
144 throw DebuggerCommandException("Error compiling expression.");
147 FrameObject* frameObj = getFrameObject(session);
148 int frameDepth = frameObj == nullptr ? 0 : frameObj->m_frameDepth;
150 if (ri->m_evaluateCommandDepth == 1 && isDummy) {
151 // Set up the dummy evaluation environment unless we have recursively
152 // re-entered eval on the dummy thread, in which case it's already set.
153 g_context->enterDebuggerDummyEnv();
155 // Show the dummy thread while it is doing an evaluation so it can
156 // present a call stack if it hits a breakpoint during the eval.
157 m_debugger->sendThreadEventMessage(
159 Debugger::ThreadEventType::ThreadStarted
163 // We must drop the lock before calling evalPHPDebugger because the eval
164 // is permitted to hit breakpoints, which can call back into Debugger and
165 // enter a command queue. Threads must never enter the command queue while
166 // holding the debugger lock, because we would be unable to processes more
167 // commands from the client: there'd be no way to resume the blocked request.
168 ExecutionContext::EvaluationResult result;
170 // If the client indicates this evaluation is for a watch expression, or
171 // hover evaluation, silence all errors.
172 const std::string evalContext = tryGetString(args, "context", "");
173 bool evalSilent = evalContext == "watch" || evalContext == "hover";
174 m_debugger->executeWithoutLock(
175 [&]() {
176 result = evaluate(m_debugger, ri, unit, frameDepth, evalSilent);
179 if (previousPauseCount != ri->m_totalPauseCount &&
180 ri->m_pauseRecurseCount > 0) {
182 m_debugger->sendStoppedEvent(
183 "Evaluation returned",
184 threadId
188 if (result.failed) {
189 m_debugger->sendUserMessage(
190 result.error.c_str(),
191 DebugTransport::OutputLevelError
193 throw DebuggerCommandException("Failed to evaluate expression.");
196 folly::dynamic serializedResult =
197 VariablesCommand::serializeVariable(
198 session,
199 threadId,
201 result.result
204 (*responseMsg)["body"] = folly::dynamic::object;
205 folly::dynamic& body = (*responseMsg)["body"];
206 body["result"] = serializedResult["value"];
207 body["type"] = serializedResult["type"];
209 int variableReference = tryGetInt(serializedResult, "variablesReference", -1);
210 if (variableReference > 0) {
211 body["variablesReference"] = serializedResult["variablesReference"];
214 int namedVariables = tryGetInt(serializedResult, "namedVariables", -1);
215 if (namedVariables > 0) {
216 body["namedVariables"] = serializedResult["namedVariables"];
219 int indexedVariables = tryGetInt(serializedResult, "indexedVariables", -1);
220 if (indexedVariables > 0) {
221 body["indexedVariables"] = serializedResult["indexedVariables"];
224 try {
225 const auto& presentationHint = serializedResult["presentationHint"];
226 body["presentationHint"] = presentationHint;
227 } catch (std::out_of_range e) {
230 return false;
233 void EvaluateCommand::preparseEvalExpression(
234 std::string* expr
236 // First, trim any leading and trailing white space.
237 std::string& expression = *expr;
238 expression = trimString(expression);
240 // HPHPD users are used to having to prefix variable requests with a leading
241 // = character. We don't require that, but tolorate that syntax to maintain
242 // compatibility for those users.
243 if (expression[0] == '=') {
244 expression = expression.substr(1);
247 // If the user supplied an expression that looks like a well formed script,
248 // meaning it begins with <?php or <?hh, do not do any further transformations
249 // on it - we'll try to just evaluate it directly as the user intended, and
250 // this will honor running as PHP vs Hack. Otherwise we are going to try
251 // to interpret as Hack, and we need to turn this into a valid script snippet.
252 std::string interpretExpr;
253 bool runWithoutModifying;
254 if (expression.find("<?php", 0, 5) == 0 ||
255 expression.find("<?hh", 0, 4) == 0) {
257 runWithoutModifying = true;
258 interpretExpr = expression;
259 } else {
260 // In case the user entered just a bare variable name ("$x") to see a value,
261 // append a ; to make the statement syntactically well formed.
262 // NOTE: It is safe to do this even if the expression ends in a ; because
263 // $x;;
264 // is still well-formed PHP. The parser removes the empty second statement.
265 interpretExpr = "<?hh " + expression + ";";
266 runWithoutModifying = false;
269 if (interpretExpr.empty()) {
270 throw DebuggerCommandException("No expression provided to evaluate.");
273 String input(interpretExpr);
274 AnalysisResultPtr ar(new AnalysisResult());
275 StatementListPtr statements = Compiler::Parser::ParseString(input, ar);
276 if (statements == nullptr) {
277 throw DebuggerCommandException(
278 "HHVM failed to parse the specified expression."
282 if (statements->getCount() > 1) {
283 if (!runWithoutModifying) {
284 expression = interpretExpr;
286 return;
289 if (statements->getCount() == 0) {
290 throw DebuggerCommandException("No expression provided to evaluate.");
293 // In the case of a single statement, if it is an expression, we need to
294 // prepend "return" to it so that we get back the expression value the
295 // user is likely expecting. Otherwise, we'll evaluate the expression,
296 // and any side effects but end up returning void.
297 StatementPtr statement = (*statements)[0];
299 // Statement list says we have a single statement, expect one.
300 assert(statement != nullptr);
302 if (statement->getKindOf() == Construct::KindOfExpStatement) {
303 interpretExpr = "<?hh ";
304 interpretExpr += "return ";
305 interpretExpr += expression;
306 interpretExpr += ";";
309 if (!runWithoutModifying) {
310 expression = interpretExpr;