convert funcs for getting frame locals, varenv, etc to darray
[hiphop-php.git] / hphp / runtime / debugger / cmd / cmd_variable.cpp
blob8ac01e0c0619b6c5d1687577f217d2b5488e4641
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_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);
33 String sdata;
34 DebuggerWireHelpers::WireSerialize(m_variables, sdata);
35 thrift.write(sdata);
37 thrift.write(m_global);
38 if (m_version == 2) {
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);
47 thrift.read(m_frame);
49 String sdata;
50 thrift.read(sdata);
51 auto error = DebuggerWireHelpers::WireUnserialize(sdata, m_variables);
52 if (error != DebuggerWireHelpers::NoError) {
53 m_variables.reset();
54 if (error != DebuggerWireHelpers::HitLimit || m_version == 0) {
55 // Unexpected error. Log it.
56 m_wireError = sdata.toCppString();
60 thrift.read(m_global);
61 if (m_version == 2) {
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");
70 client.helpCmds(
71 "[v]ariable", "lists all local variables on stack",
72 "[v]ariable {text}", "full-text search local variables",
73 nullptr
75 client.helpBody(
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"
80 "\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 "
83 "string-based."
87 const StaticString
88 s_HTTP_RAW_POST_DATA("HTTP_RAW_POST_DATA"),
89 s_omitted("...(omitted)");
91 void CmdVariable::PrintVariable(DebuggerClient &client, const String& varName) {
92 CmdVariable cmd;
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;
105 cmd.m_version = 2;
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.
111 cmd.m_version = 1;
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);
117 return;
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);
130 return;
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)
147 client.output(rest);
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
157 bool system = true;
158 int i = 0;
159 bool found = false;
161 always_assert(version == 2);
163 for (ArrayIter iter(variables); iter; ++iter) {
164 auto const name = iter.first().toString();
165 String value;
167 // Using the new protocol, so variables contain only names. Fetch the value
168 // separately.
169 CmdVariable cmd;
170 cmd.m_frame = frame;
171 cmd.m_variables.reset();
172 cmd.m_varName = name;
173 cmd.m_filter = text;
174 cmd.m_formatMaxLen = 200;
175 cmd.m_version = 2;
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();
180 found = true;
181 } else if (text.empty()) {
182 // Not missing because filtered out, assume the value is too large.
183 value = s_omitted;
184 found = true;
185 } else if (name.find(text, 0, false) >= 0) {
186 // Server should have matched it. Assume missing because value is too
187 // large.
188 value = s_omitted;
189 found = true;
190 } else {
191 // The variable was filtered out on the server, using text. Or it was
192 // just too large. Either way we skip over it.
193 continue;
196 if (global && system) {
197 client.print("$%s = %s", name.data(), value.data());
198 } else {
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", "");
205 system = false;
208 ++i;
209 if (i % DebuggerClient::ScrollBlockSize == 0 &&
210 client.ask("There are %zd more variables. Continue? [Y/n]",
211 variables.size() - i) == 'n') {
212 break;
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;
224 String text;
225 if (client.argCount() == 1) {
226 text = client.argValue(1);
227 } else if (client.argCount() != 0) {
228 help(client);
229 return;
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)");
237 } else {
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);
249 return ret;
252 bool CmdVariable::onServer(DebuggerProxy &proxy) {
253 if (m_frame < 0) {
254 m_variables = g_context->m_globalVarEnv->getDefinedVariables();
255 m_global = true;
256 } else {
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));
268 if (m_global) {
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());
282 Variant v;
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();
288 m_version = 2;
289 return proxy.sendToClient(this);
292 // Version 2 of this command means we're trying to get the value of a single
293 // variable.
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 ///////////////////////////////////////////////////////////////////////////////