2 +----------------------------------------------------------------------+
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_variable.h"
19 #include "hphp/runtime/base/array-init.h"
20 #include "hphp/runtime/debugger/cmd/cmd_where.h"
21 #include "hphp/runtime/debugger/debugger_client.h"
22 #include "hphp/runtime/vm/runtime.h"
24 namespace HPHP
{ namespace Eval
{
25 ///////////////////////////////////////////////////////////////////////////////
27 TRACE_SET_MOD(debugger
);
29 void CmdVariable::sendImpl(DebuggerThriftBuffer
&thrift
) {
30 DebuggerCommand::sendImpl(thrift
);
31 thrift
.write(m_frame
);
34 DebuggerWireHelpers::WireSerialize(m_variables
, sdata
);
37 thrift
.write(m_global
);
39 thrift
.write(m_formatMaxLen
);
40 thrift
.write(m_varName
);
41 thrift
.write(m_filter
);
45 void CmdVariable::recvImpl(DebuggerThriftBuffer
&thrift
) {
46 DebuggerCommand::recvImpl(thrift
);
51 auto error
= DebuggerWireHelpers::WireUnserialize(sdata
, m_variables
);
52 if (error
!= DebuggerWireHelpers::NoError
) {
54 if (error
!= DebuggerWireHelpers::HitLimit
|| m_version
== 0) {
55 // Unexpected error. Log it.
56 m_wireError
= sdata
.toCppString();
60 thrift
.read(m_global
);
62 thrift
.read(m_formatMaxLen
);
63 thrift
.read(m_varName
);
64 thrift
.read(m_filter
);
68 void CmdVariable::help(DebuggerClient
&client
) {
69 client
.helpTitle("Variable Command");
71 "[v]ariable", "lists all local variables on stack",
72 "[v]ariable {text}", "full-text search local variables",
76 "This will print names and values of all variables that are currently "
77 "accessible by simple names. Use '[w]here', '[u]p {num}', '[d]own {num}', "
78 "'[f]rame {index}' commands to choose a different frame to view variables "
79 "at different level of the stack.\n"
81 "Specify some free text to print local variables that contain the text "
82 "either in their names or values. The search is case-insensitive and "
88 s_HTTP_RAW_POST_DATA("HTTP_RAW_POST_DATA"),
89 s_omitted("...(omitted)");
91 void CmdVariable::PrintVariable(DebuggerClient
&client
, const String
& varName
) {
93 auto charCount
= client
.getDebuggerClientShortPrintCharCount();
94 cmd
.m_frame
= client
.getFrame();
95 auto rcmd
= client
.xend
<CmdVariable
>(&cmd
);
97 always_assert(rcmd
->m_version
== 2);
99 // Using the new protocol. rcmd contains a list of variables only. Fetch
100 // value of varName only, so that we can recover nicely when its value is too
101 // large to serialize.
102 cmd
.m_varName
= varName
;
103 cmd
.m_variables
.reset();
104 cmd
.m_formatMaxLen
= charCount
;
106 rcmd
= client
.xend
<CmdVariable
>(&cmd
);
108 if (rcmd
->m_variables
.empty()) {
109 // Perhaps the value is too large? See recvImpl. Retry the command with
110 // version 1, in which case values are omitted.
112 rcmd
= client
.xend
<CmdVariable
>(&cmd
);
113 if (!rcmd
->m_variables
.empty()) {
114 // It's there without values, and gone with values, so it is too large.
115 client
.output(s_omitted
);
120 auto const get_var
= [varName
] (const CmdVariable
& cmd
) {
121 assertx(cmd
.m_variables
.size() == 1);
122 assertx(cmd
.m_variables
.exists(varName
, true /* isKey */));
123 assertx(cmd
.m_variables
[varName
].isString());
124 return cmd
.m_variables
[varName
].toString();
127 auto const value
= get_var(*rcmd
);
128 if (charCount
<= 0 || value
.size() <= charCount
) {
129 client
.output(value
);
133 // Don't show the "omitted" suffix.
134 auto piece
= folly::StringPiece
{value
.data(), static_cast<size_t>(charCount
)};
135 client
.output(piece
);
136 if (client
.ask("There are more characters. Continue? [y/N]") == 'y') {
137 // Now we get the full value, and show the rest.
138 cmd
.m_variables
.reset();
139 cmd
.m_formatMaxLen
= -1;
140 rcmd
= client
.xend
<CmdVariable
>(&cmd
);
142 auto value
= get_var(*rcmd
);
143 auto rest
= folly::StringPiece
{
144 value
.data() + charCount
,
145 static_cast<size_t>(value
.size() - charCount
)
148 client
.tutorial("You can use 'set cc n' to increase the character"
149 " limit. 'set cc -1' will remove the limit.");
153 void CmdVariable::PrintVariables(DebuggerClient
&client
, const Array
& variables
,
154 int frame
, const String
& text
, int version
) {
155 bool global
= frame
== -1; // I.e. we were called from CmdGlobal, or the
156 //client's current frame is the global frame, according to OnServer
161 always_assert(version
== 2);
163 for (ArrayIter
iter(variables
); iter
; ++iter
) {
164 auto const name
= iter
.first().toString();
167 // Using the new protocol, so variables contain only names. Fetch the value
171 cmd
.m_variables
.reset();
172 cmd
.m_varName
= name
;
174 cmd
.m_formatMaxLen
= 200;
176 auto rcmd
= client
.xend
<CmdVariable
>(&cmd
);
177 if (!rcmd
->m_variables
.empty()) {
178 assertx(rcmd
->m_variables
[name
].isString());
179 value
= rcmd
->m_variables
[name
].toString();
181 } else if (text
.empty()) {
182 // Not missing because filtered out, assume the value is too large.
185 } else if (name
.find(text
, 0, false) >= 0) {
186 // Server should have matched it. Assume missing because value is too
191 // The variable was filtered out on the server, using text. Or it was
192 // just too large. Either way we skip over it.
196 if (global
&& system
) {
197 client
.print("$%s = %s", name
.data(), value
.data());
199 client
.output("$%s = %s", name
.data(), value
.data());
202 // We know s_HTTP_RAW_POST_DATA is the last system global.
203 if (global
&& name
== s_HTTP_RAW_POST_DATA
) {
204 client
.output("%s", "");
209 if (i
% DebuggerClient::ScrollBlockSize
== 0 &&
210 client
.ask("There are %zd more variables. Continue? [Y/n]",
211 variables
.size() - i
) == 'n') {
216 if (!text
.empty() && !found
) {
217 client
.info("(unable to find specified text in any variables)");
221 void CmdVariable::onClient(DebuggerClient
&client
) {
222 if (DebuggerCommand::displayedHelp(client
)) return;
225 if (client
.argCount() == 1) {
226 text
= client
.argValue(1);
227 } else if (client
.argCount() != 0) {
232 m_frame
= client
.getFrame();
234 auto cmd
= client
.xend
<CmdVariable
>(this);
235 if (cmd
->m_variables
.empty()) {
236 client
.info("(no variable was defined)");
238 PrintVariables(client
, cmd
->m_variables
, cmd
->m_global
? -1 : m_frame
,
239 text
, cmd
->m_version
);
243 const StaticString
s_GLOBALS("GLOBALS");
244 const StaticString
s_this("this");
246 Array
CmdVariable::GetGlobalVariables() {
247 Array ret
= g_context
->m_globalVarEnv
->getDefinedVariables();
248 ret
.remove(s_GLOBALS
);
252 bool CmdVariable::onServer(DebuggerProxy
&proxy
) {
254 m_variables
= g_context
->m_globalVarEnv
->getDefinedVariables();
257 m_variables
= g_context
->getLocalDefinedVariablesDebugger(m_frame
);
258 const auto fp
= g_context
->getFrameAtDepthForDebuggerUnsafe(m_frame
);
259 m_global
= g_context
->getVarEnv(fp
) == g_context
->m_globalVarEnv
;
260 auto oThis
= g_context
->getThis();
261 if (nullptr != oThis
) {
262 auto tvThis
= make_tv
<KindOfObject
>(oThis
);
263 Variant
thisName(s_this
);
264 m_variables
.set(thisName
, tvAsVariant(&tvThis
));
269 m_variables
.remove(s_GLOBALS
);
272 // Deprecated IDE uses this, so keep it around for now. It expects all
273 // variable values to be fully serialized across the wire.
274 if (m_version
== 0) {
275 return proxy
.sendToClient(this);
278 // Version 1 of this command means we want the names of all variables, but we
279 // don't care about their values just yet.
280 if (m_version
== 1) {
281 DArrayInit
ret(m_variables
->size());
283 for (ArrayIter
iter(m_variables
); iter
; ++iter
) {
284 assertx(iter
.first().isString());
285 ret
.add(iter
.first().toString(), v
);
287 m_variables
= ret
.toArray();
289 return proxy
.sendToClient(this);
292 // Version 2 of this command means we're trying to get the value of a single
294 always_assert(m_version
== 2);
295 always_assert(!m_varName
.empty());
297 // Variable name might not exist.
298 if (!m_variables
.exists(m_varName
, true /* isKey */)) {
299 m_variables
= Array::CreateDArray();
300 return proxy
.sendToClient(this);
303 auto const value
= m_variables
[m_varName
];
304 auto const result
= m_formatMaxLen
< 0
305 ? DebuggerClient::FormatVariable(value
)
306 : DebuggerClient::FormatVariableWithLimit(value
, m_formatMaxLen
);
307 m_variables
= make_darray(m_varName
, result
);
309 // Remove the entry if its name or context does not match the filter.
310 if (!m_filter
.empty() && m_varName
.find(m_filter
, 0, false) < 0) {
311 auto const fullvalue
= DebuggerClient::FormatVariable(value
);
312 if (fullvalue
.find(m_filter
, 0, false) < 0) {
313 m_variables
= Array::CreateDArray();
317 return proxy
.sendToClient(this);
320 ///////////////////////////////////////////////////////////////////////////////