unrestrict-layout part 5: Eliminate set in place
[hiphop-php.git] / hphp / runtime / ext / vsdebug / set_variable_command.cpp
blob25278ff9786838edd03031880775884740c9efb4
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/runtime/base/backtrace.h"
18 #include "hphp/runtime/base/php-globals.h"
19 #include "hphp/runtime/base/static-string-table.h"
20 #include "hphp/runtime/base/string-util.h"
21 #include "hphp/runtime/base/tv-variant.h"
22 #include "hphp/runtime/ext/vsdebug/command.h"
23 #include "hphp/runtime/ext/vsdebug/debugger.h"
24 #include "hphp/runtime/vm/runtime.h"
25 #include "hphp/runtime/vm/vm-regs.h"
27 namespace HPHP {
28 namespace VSDEBUG {
30 SetVariableCommand::SetVariableCommand(
31 Debugger* debugger,
32 folly::dynamic message
33 ) : VSCommand(debugger, message),
34 m_objectId{0} {
36 const folly::dynamic& args = tryGetObject(message, "arguments", s_emptyArgs);
37 const int variablesReference = tryGetInt(args, "variablesReference", -1);
38 if (variablesReference < 0) {
39 throw DebuggerCommandException("Invalid variablesReference specified.");
42 m_objectId = variablesReference;
45 SetVariableCommand::~SetVariableCommand() {
48 request_id_t SetVariableCommand::targetThreadId(DebuggerSession* session) {
49 ServerObject* obj = session->getServerObject(m_objectId);
50 if (obj == nullptr) {
51 throw DebuggerCommandException("Invalid variablesReference specified.");
54 if (obj->objectType() == ServerObjectType::Scope) {
55 ScopeObject* scope = static_cast<ScopeObject*>(obj);
56 return scope->m_requestId;
57 } else if (obj->objectType() == ServerObjectType::Variable) {
58 VariableObject* variable = static_cast<VariableObject*>(obj);
59 return variable->m_requestId;
62 throw DebuggerCommandException("Unexpected server object type");
65 bool SetVariableCommand::executeImpl(
66 DebuggerSession* session,
67 folly::dynamic* responseMsg
68 ) {
69 // The request thread should not re-enter the debugger while
70 // processing this command.
71 DebuggerNoBreakContext noBreak(m_debugger);
73 folly::dynamic body = folly::dynamic::object;
74 folly::dynamic variables = folly::dynamic::array;
75 auto& args = tryGetObject(getMessage(), "arguments", s_emptyArgs);
77 ServerObject* obj = session->getServerObject(m_objectId);
78 if (obj == nullptr) {
79 throw DebuggerCommandException("Invalid variablesReference specified.");
82 const std::string& suppliedName = tryGetString(args, "name", "");
83 if (suppliedName.empty()) {
84 throw DebuggerCommandException("Invalid variable name specified.");
87 // Remove any prefixes that we would have added to the variable name
88 // before presenting it to the front-end.
89 std::string name = removeVariableNamePrefix(suppliedName);
90 bool success = false;
91 const std::string& strValue = tryGetString(args, "value", "");
93 try {
94 if (obj->objectType() == ServerObjectType::Scope) {
95 ScopeObject* scope = static_cast<ScopeObject*>(obj);
97 switch (scope->m_scopeType) {
98 case ScopeType::Locals:
99 success = setLocalVariable(
100 session,
101 name,
102 strValue,
103 scope,
104 &body
106 break;
108 case ScopeType::ServerConstants:
109 success = setConstant(
110 session,
111 name,
112 strValue,
113 scope,
114 &body
116 break;
118 // Superglobals and core constants are provided by the current execution
119 // context, rather than trying to overwrite them here, defer to the PHP
120 // console, which will let the runtime enforce whatever policies are
121 // appropriate.
122 case ScopeType::Superglobals:
123 m_debugger->sendUserMessage(
124 "Could not directly set value of superglobal variable, you may "
125 "be able to set this value by running a Hack/PHP command in the "
126 " console.",
127 DebugTransport::OutputLevelError
129 break;
131 default:
132 assertx(false);
134 } else if (obj->objectType() == ServerObjectType::Variable) {
135 VariableObject* variable = static_cast<VariableObject*>(obj);
136 Variant& variant = variable->m_variable;
137 if (variant.isArray()) {
138 success = setArrayVariable(session, name, strValue, variable, &body);
139 } else if (variant.isObject()) {
140 success = setObjectVariable(session, name, strValue, variable, &body);
141 } else {
142 throw DebuggerCommandException(
143 "Failed to set variable: Unexpected variable type."
147 } catch (DebuggerCommandException &e) {
148 m_debugger->sendUserMessage(
149 e.what(),
150 DebugTransport::OutputLevelError
152 throw e;
155 if (!success) {
156 throw DebuggerCommandException(
157 "Failed to set variable."
161 (*responseMsg)["body"] = body;
162 return false;
165 bool SetVariableCommand::setLocalVariable(
166 DebuggerSession* session,
167 const std::string& name,
168 const std::string& value,
169 ScopeObject* scope,
170 folly::dynamic* result
172 VMRegAnchor _regAnchor;
174 const auto fp =
175 g_context->getFrameAtDepthForDebuggerUnsafe(scope->m_frameDepth);
176 if (fp == nullptr ||
177 fp->isInlined() ||
178 fp->skipFrame()) {
179 // Can't set variable in a frame with no context.
180 return false;
183 const auto func = fp->func();
184 if (func == nullptr) {
185 return false;
188 const auto localCount = func->numNamedLocals();
190 for (Id id = 0; id < localCount; id++) {
191 TypedValue* frameValue = frame_local(fp, id);
192 const std::string localName = func->localVarName(id)->toCppString();
193 if (localName == name) {
194 setVariableValue(
195 session,
196 name,
197 value,
198 frameValue,
199 scope->m_requestId,
200 result
202 return true;
206 return false;
209 const StaticString s_user("user");
211 bool SetVariableCommand::setConstant(
212 DebuggerSession* session,
213 const std::string& name,
214 const std::string& value,
215 ScopeObject* scope,
216 folly::dynamic* result
218 const auto& constants = lookupDefinedConstants(false);
219 for (ArrayIter iter(constants); iter; ++iter) {
220 const std::string constantName = iter.first().toString().toCppString();
221 if (constantName == name) {
222 TypedValue* constantValue = iter.second().asTypedValue();
223 setVariableValue(
224 session,
225 name,
226 value,
227 constantValue,
228 scope->m_requestId,
229 result
231 return true;
234 return false;
237 bool SetVariableCommand::setArrayVariable(
238 DebuggerSession* session,
239 const std::string& name,
240 const std::string& value,
241 VariableObject* array,
242 folly::dynamic* result
244 VMRegAnchor regAnchor;
246 Variant& var = array->m_variable;
247 assertx(var.isArray());
249 Array arr = var.toArray();
250 for (ArrayIter iter(arr); iter; ++iter) {
251 std::string indexName = iter.first().toString().toCppString();
252 if (indexName == name) {
253 TypedValue* arrayValue = iter.second().asTypedValue();
254 setVariableValue(
255 session,
256 name,
257 value,
258 arrayValue,
259 array->m_requestId,
260 result
263 auto keyVariant = iter.first();
264 if (keyVariant.isString()) {
265 HPHP::String key = keyVariant.toString();
266 arr.set(key, tvToInit(*arrayValue));
267 } else if (keyVariant.isInteger()) {
268 int64_t key = keyVariant.toInt64();
269 arr.set(key, tvToInit(*arrayValue));
270 } else {
271 throw DebuggerCommandException("Unsupported array key type.");
273 return true;
277 return false;
280 bool SetVariableCommand::setObjectVariable(
281 DebuggerSession* session,
282 const std::string& name,
283 const std::string& value,
284 VariableObject* object,
285 folly::dynamic* result
287 Variant& var = object->m_variable;
288 assertx(var.isObject());
290 HPHP::String key(name);
291 ObjectData* obj = var.getObjectData();
292 Variant currentValue = obj->o_get(key, false);
293 if (!currentValue.isInitialized()) {
294 throw DebuggerCommandException(
295 "Failed to set variable: Property not found on object."
299 TypedValue* propValue = currentValue.asTypedValue();
301 setVariableValue(
302 session,
303 name,
304 value,
305 propValue,
306 object->m_requestId,
307 result
309 obj->o_set(key, currentValue);
310 return true;
313 bool SetVariableCommand::getBooleanValue(const std::string& str) {
314 // Trim leading and trailing whitespace.
315 std::string trimmed = trimString(str);
317 // Boolean values in PHP are not case sensitive.
318 for (char& c : trimmed) {
319 c = std::toupper(c);
322 if (trimmed == "TRUE") {
323 return true;
324 } else if (trimmed == "FALSE") {
325 return false;
328 throw DebuggerCommandException(
329 "The specified value was not a valid boolean value."
333 void SetVariableCommand::setVariableValue(
334 DebuggerSession* session,
335 const std::string& name,
336 const std::string& value,
337 TypedValue* typedVariable,
338 request_id_t requestId,
339 folly::dynamic* result
341 switch (typedVariable->m_type) {
342 case KindOfBoolean: {
343 bool boolVal = getBooleanValue(value);
344 typedVariable->m_data.num = boolVal ? 1 : 0;
346 break;
348 case KindOfInt64:
349 try {
350 typedVariable->m_data.num = std::stoi(value, nullptr, 0);
351 } catch (std::exception &e) {
352 throw DebuggerCommandException("Invalid value specified.");
354 break;
356 case KindOfDouble:
357 try {
358 typedVariable->m_data.dbl = std::stod(value);
359 } catch (std::exception &e) {
360 throw DebuggerCommandException("Invalid value specified.");
362 break;
364 case KindOfPersistentString:
365 case KindOfString: {
366 const auto newSd = StringData::Make(
367 value.c_str(),
368 CopyString
371 if (typedVariable->m_type == KindOfString &&
372 typedVariable->m_data.pstr != nullptr) {
374 typedVariable->m_data.pstr->decRefCount();
377 typedVariable->m_data.pstr = newSd;
378 typedVariable->m_type = KindOfString;
379 break;
382 case KindOfUninit:
383 case KindOfNull:
384 // In the case of uninit and null, we don't even know how to interpret
385 // the value from the client, because the object has no known type.
386 // Fallthrough.
387 case KindOfResource:
388 case KindOfPersistentVec:
389 case KindOfVec:
390 case KindOfPersistentDArray:
391 case KindOfDArray:
392 case KindOfPersistentVArray:
393 case KindOfVArray:
394 case KindOfPersistentArray:
395 case KindOfArray:
396 case KindOfPersistentDict:
397 case KindOfDict:
398 case KindOfPersistentKeyset:
399 case KindOfKeyset:
400 case KindOfObject:
401 // For complex types, we need to run PHP code to create a new object
402 // or determine what reference to assign, making direct assignment via
403 // reflection impractical, so we defer to the console REPL, which will use
404 // an evaluation command to run PHP.
405 // NOTE: It would be nice in the future to just run an eval command here
406 // on the user's behalf. At the moment, if we are setting a child prop
407 // on an object or array, we don't know the fully qualified name string
408 // to pass to PHP though, since we only have a reference to the container.
409 throw DebuggerCommandException(
410 "Failed to set object value. Please use the console to set the value "
411 "of complex objects."
414 default:
415 throw DebuggerCommandException("Unexpected variable type");
418 Variant variable = tvAsVariant(typedVariable);
419 *result = VariablesCommand::serializeVariable(
420 session,
421 m_debugger,
422 requestId,
423 name,
424 variable