Add support for enum bounds
[hiphop-php.git] / hphp / runtime / ext / vsdebug / set_variable_command.cpp
blob22efc0cf6cebae998cb1c52393dfecd1d391e7ff
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/array-iterator.h"
18 #include "hphp/runtime/base/backtrace.h"
19 #include "hphp/runtime/base/php-globals.h"
20 #include "hphp/runtime/base/static-string-table.h"
21 #include "hphp/runtime/base/string-util.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"
26 #include "hphp/runtime/vm/vm-regs.h"
28 namespace HPHP {
29 namespace VSDEBUG {
31 SetVariableCommand::SetVariableCommand(
32 Debugger* debugger,
33 folly::dynamic message
34 ) : VSCommand(debugger, message),
35 m_objectId{0} {
37 const folly::dynamic& args = tryGetObject(message, "arguments", s_emptyArgs);
38 const int variablesReference = tryGetInt(args, "variablesReference", -1);
39 if (variablesReference < 0) {
40 throw DebuggerCommandException("Invalid variablesReference specified.");
43 m_objectId = variablesReference;
46 SetVariableCommand::~SetVariableCommand() {
49 request_id_t SetVariableCommand::targetThreadId(DebuggerSession* session) {
50 ServerObject* obj = session->getServerObject(m_objectId);
51 if (obj == nullptr) {
52 throw DebuggerCommandException("Invalid variablesReference specified.");
55 if (obj->objectType() == ServerObjectType::Scope) {
56 ScopeObject* scope = static_cast<ScopeObject*>(obj);
57 return scope->m_requestId;
58 } else if (obj->objectType() == ServerObjectType::Variable) {
59 VariableObject* variable = static_cast<VariableObject*>(obj);
60 return variable->m_requestId;
63 throw DebuggerCommandException("Unexpected server object type");
66 bool SetVariableCommand::executeImpl(
67 DebuggerSession* session,
68 folly::dynamic* responseMsg
69 ) {
70 // The request thread should not re-enter the debugger while
71 // processing this command.
72 DebuggerNoBreakContext noBreak(m_debugger);
74 folly::dynamic body = folly::dynamic::object;
75 folly::dynamic variables = folly::dynamic::array;
76 auto& args = tryGetObject(getMessage(), "arguments", s_emptyArgs);
78 ServerObject* obj = session->getServerObject(m_objectId);
79 if (obj == nullptr) {
80 throw DebuggerCommandException("Invalid variablesReference specified.");
83 const std::string& suppliedName = tryGetString(args, "name", "");
84 if (suppliedName.empty()) {
85 throw DebuggerCommandException("Invalid variable name specified.");
88 // Remove any prefixes that we would have added to the variable name
89 // before presenting it to the front-end.
90 std::string name = removeVariableNamePrefix(suppliedName);
91 bool success = false;
92 const std::string& strValue = tryGetString(args, "value", "");
94 try {
95 if (obj->objectType() == ServerObjectType::Scope) {
96 ScopeObject* scope = static_cast<ScopeObject*>(obj);
98 switch (scope->m_scopeType) {
99 case ScopeType::Locals:
100 success = setLocalVariable(
101 session,
102 name,
103 strValue,
104 scope,
105 &body
107 break;
109 case ScopeType::ServerConstants:
110 success = setConstant(
111 session,
112 name,
113 strValue,
114 scope,
115 &body
117 break;
119 // Superglobals and core constants are provided by the current execution
120 // context, rather than trying to overwrite them here, defer to the PHP
121 // console, which will let the runtime enforce whatever policies are
122 // appropriate.
123 case ScopeType::Superglobals:
124 m_debugger->sendUserMessage(
125 "Could not directly set value of superglobal variable, you may "
126 "be able to set this value by running a Hack/PHP command in the "
127 " console.",
128 DebugTransport::OutputLevelError
130 break;
132 default:
133 assertx(false);
135 } else if (obj->objectType() == ServerObjectType::Variable) {
136 VariableObject* variable = static_cast<VariableObject*>(obj);
137 Variant& variant = variable->m_variable;
138 if (variant.isArray()) {
139 success = setArrayVariable(session, name, strValue, variable, &body);
140 } else if (variant.isObject()) {
141 success = setObjectVariable(session, name, strValue, variable, &body);
142 } else {
143 throw DebuggerCommandException(
144 "Failed to set variable: Unexpected variable type."
148 } catch (DebuggerCommandException &e) {
149 m_debugger->sendUserMessage(
150 e.what(),
151 DebugTransport::OutputLevelError
153 throw e;
156 if (!success) {
157 throw DebuggerCommandException(
158 "Failed to set variable."
162 (*responseMsg)["body"] = body;
163 return false;
166 bool SetVariableCommand::setLocalVariable(
167 DebuggerSession* session,
168 const std::string& name,
169 const std::string& value,
170 ScopeObject* scope,
171 folly::dynamic* result
173 VMRegAnchor _regAnchor;
175 const auto fp =
176 g_context->getFrameAtDepthForDebuggerUnsafe(scope->m_frameDepth);
177 if (fp == nullptr ||
178 fp->isInlined() ||
179 fp->skipFrame()) {
180 // Can't set variable in a frame with no context.
181 return false;
184 const auto func = fp->func();
185 if (func == nullptr) {
186 return false;
189 const auto localCount = func->numNamedLocals();
191 for (Id id = 0; id < localCount; id++) {
192 // Check for unnamed local.
193 auto const localNameSd = func->localVarName(id);
194 if (!localNameSd) continue;
196 auto const frameValue = frame_local(fp, id);
197 const std::string localName = localNameSd->toCppString();
198 if (localName == name) {
199 setVariableValue(
200 session,
201 name,
202 value,
203 frameValue,
204 scope->m_requestId,
205 result
207 return true;
211 return false;
214 const StaticString s_user("user");
216 bool SetVariableCommand::setConstant(
217 DebuggerSession* session,
218 const std::string& name,
219 const std::string& value,
220 ScopeObject* scope,
221 folly::dynamic* result
223 const auto& constants = lookupDefinedConstants(false);
224 for (ArrayIter iter(constants); iter; ++iter) {
225 const std::string constantName = iter.first().toString().toCppString();
226 if (constantName == name) {
227 TypedValue* constantValue = iter.second().asTypedValue();
228 setVariableValue(
229 session,
230 name,
231 value,
232 constantValue,
233 scope->m_requestId,
234 result
236 return true;
239 return false;
242 bool SetVariableCommand::setArrayVariable(
243 DebuggerSession* session,
244 const std::string& name,
245 const std::string& value,
246 VariableObject* array,
247 folly::dynamic* result
249 VMRegAnchor regAnchor;
251 Variant& var = array->m_variable;
252 assertx(var.isArray());
254 Array arr = var.toArray();
255 for (ArrayIter iter(arr); iter; ++iter) {
256 std::string indexName = iter.first().toString().toCppString();
257 if (indexName == name) {
258 TypedValue* arrayValue = iter.second().asTypedValue();
259 setVariableValue(
260 session,
261 name,
262 value,
263 arrayValue,
264 array->m_requestId,
265 result
268 auto keyVariant = iter.first();
269 if (keyVariant.isString()) {
270 HPHP::String key = keyVariant.toString();
271 arr.set(key, tvToInit(*arrayValue));
272 } else if (keyVariant.isInteger()) {
273 int64_t key = keyVariant.toInt64();
274 arr.set(key, tvToInit(*arrayValue));
275 } else {
276 throw DebuggerCommandException("Unsupported array key type.");
278 return true;
282 return false;
285 bool SetVariableCommand::setObjectVariable(
286 DebuggerSession* session,
287 const std::string& name,
288 const std::string& value,
289 VariableObject* object,
290 folly::dynamic* result
292 Variant& var = object->m_variable;
293 assertx(var.isObject());
295 HPHP::String key(name);
296 ObjectData* obj = var.getObjectData();
297 Variant currentValue = obj->o_get(key, false);
298 if (!currentValue.isInitialized()) {
299 throw DebuggerCommandException(
300 "Failed to set variable: Property not found on object."
304 TypedValue* propValue = currentValue.asTypedValue();
306 setVariableValue(
307 session,
308 name,
309 value,
310 propValue,
311 object->m_requestId,
312 result
314 obj->o_set(key, currentValue);
315 return true;
318 bool SetVariableCommand::getBooleanValue(const std::string& str) {
319 // Trim leading and trailing whitespace.
320 std::string trimmed = trimString(str);
322 // Boolean values in PHP are not case sensitive.
323 for (char& c : trimmed) {
324 c = std::toupper(c);
327 if (trimmed == "TRUE") {
328 return true;
329 } else if (trimmed == "FALSE") {
330 return false;
333 throw DebuggerCommandException(
334 "The specified value was not a valid boolean value."
338 void SetVariableCommand::setVariableValue(
339 DebuggerSession* session,
340 const std::string& name,
341 const std::string& value,
342 tv_lval typedVariable,
343 request_id_t requestId,
344 folly::dynamic* result
346 switch (type(typedVariable)) {
347 case KindOfBoolean: {
348 bool boolVal = getBooleanValue(value);
349 val(typedVariable).num = boolVal ? 1 : 0;
351 break;
353 case KindOfInt64:
354 try {
355 val(typedVariable).num = std::stoi(value, nullptr, 0);
356 } catch (std::exception &) {
357 throw DebuggerCommandException("Invalid value specified.");
359 break;
361 case KindOfDouble:
362 try {
363 val(typedVariable).dbl = std::stod(value);
364 } catch (std::exception &) {
365 throw DebuggerCommandException("Invalid value specified.");
367 break;
369 case KindOfPersistentString:
370 case KindOfString: {
371 const auto newSd = StringData::Make(
372 value.c_str(),
373 CopyString
376 if (type(typedVariable) == KindOfString &&
377 val(typedVariable).pstr != nullptr) {
378 val(typedVariable).pstr->decRefCount();
381 val(typedVariable).pstr = newSd;
382 type(typedVariable) = KindOfString;
383 break;
386 case KindOfUninit:
387 case KindOfNull:
388 // In the case of uninit and null, we don't even know how to interpret
389 // the value from the client, because the object has no known type.
390 // Fallthrough.
391 case KindOfResource:
392 case KindOfPersistentVec:
393 case KindOfVec:
394 case KindOfPersistentDict:
395 case KindOfDict:
396 case KindOfPersistentKeyset:
397 case KindOfKeyset:
398 case KindOfObject:
399 // For complex types, we need to run PHP code to create a new object
400 // or determine what reference to assign, making direct assignment via
401 // reflection impractical, so we defer to the console REPL, which will use
402 // an evaluation command to run PHP.
403 // NOTE: It would be nice in the future to just run an eval command here
404 // on the user's behalf. At the moment, if we are setting a child prop
405 // on an object or array, we don't know the fully qualified name string
406 // to pass to PHP though, since we only have a reference to the container.
407 throw DebuggerCommandException(
408 "Failed to set object value. Please use the console to set the value "
409 "of complex objects."
412 default:
413 throw DebuggerCommandException("Unexpected variable type");
416 Variant variable{variant_ref{typedVariable}};
417 *result = VariablesCommand::serializeVariable(
418 session,
419 m_debugger,
420 requestId,
421 name,
422 variable