2 +----------------------------------------------------------------------+
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"
30 EvaluateCommand::EvaluateCommand(
32 folly::dynamic message
33 ) : VSCommand(debugger
, message
),
36 const folly::dynamic
& args
= tryGetObject(message
, "arguments", s_emptyArgs
);
37 const int frameId
= tryGetInt(args
, "frameId", -1);
41 EvaluateCommand::~EvaluateCommand() {
44 FrameObject
* EvaluateCommand::getFrameObject(DebuggerSession
* session
) {
45 if (m_frameObj
!= nullptr) {
49 m_frameObj
= session
->getFrameObject(m_frameId
);
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(
70 ExecutionContext::EvaluationResult result
;
73 SilentEvaluationContext
silentContext(debugger
, ri
);
74 result
= g_context
->evalPHPDebugger(unit
, frameDepth
);
76 result
= g_context
->evalPHPDebugger(unit
, frameDepth
);
82 bool EvaluateCommand::executeImpl(
83 DebuggerSession
* session
,
84 folly::dynamic
* responseMsg
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
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.
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(
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",
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(
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"];
225 const auto& presentationHint
= serializedResult
["presentationHint"];
226 body
["presentationHint"] = presentationHint
;
227 } catch (std::out_of_range e
) {
233 void EvaluateCommand::preparseEvalExpression(
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
;
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
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
;
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
;