Codemod asserts to assertxs in the runtime
[hiphop-php.git] / hphp / runtime / ext / vsdebug / variables_command.cpp
blob8b12d6418bcb5decd58231d91631638011ab8862
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/compiler/builtin_symbols.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 const StaticString s_user("user");
32 const StaticString s_core("Core");
34 static bool isArrayObjectType(const std::string className) {
35 // HH\Vector and HH\Map are special in that they are objects but their
36 // children look like array indicies.
37 return className == "HH\\Vector" || className == "HH\\Map";
40 VariablesCommand::VariablesCommand(
41 Debugger* debugger,
42 folly::dynamic message
43 ) : VSCommand(debugger, message),
44 m_objectId{0} {
46 const folly::dynamic& args = tryGetObject(message, "arguments", s_emptyArgs);
47 const int variablesReference = tryGetInt(args, "variablesReference", -1);
48 if (variablesReference < 0) {
49 throw DebuggerCommandException("Invalid variablesReference specified.");
52 m_objectId = variablesReference;
55 VariablesCommand::~VariablesCommand() {
58 request_id_t VariablesCommand::targetThreadId(DebuggerSession* session) {
59 ServerObject* obj = session->getServerObject(m_objectId);
60 if (obj == nullptr) {
61 return 0;
64 if (obj->objectType() == ServerObjectType::Scope) {
65 ScopeObject* scope = static_cast<ScopeObject*>(obj);
66 return scope->m_requestId;
67 } else if (obj->objectType() == ServerObjectType::Variable) {
68 VariableObject* variable = static_cast<VariableObject*>(obj);
69 return variable->m_requestId;
70 } else if (obj->objectType() == ServerObjectType::SubScope) {
71 VariableSubScope* subScope = static_cast<VariableSubScope*>(obj);
72 return subScope->m_requestId;
75 throw DebuggerCommandException("Unexpected server object type");
78 const std::string& VariablesCommand::getUcVariableName(
79 folly::dynamic& var,
80 const char* ucKey
81 ) {
82 // Convert name to UC to allow for case-insensitive sort, but
83 // cache the result on the object so we don't do this every
84 // iteration of the sort operation.
85 const std::string& str = var["name"].getString();
86 const std::string& ucStr = tryGetString(var, ucKey, "");
88 if (!ucStr.empty() || str.empty()) {
89 return ucStr;
92 std::string upperString = std::string(str);
93 for (char& c : upperString) {
94 c = std::toupper(c);
97 var[ucKey] = upperString;
98 return tryGetString(var, ucKey, "");
101 void VariablesCommand::sortVariablesInPlace(folly::dynamic& vars) {
102 constexpr char* ucKey = "uc_name";
103 assertx(vars.isArray());
105 std::sort(
106 vars.begin(),
107 vars.end(),
108 [](folly::dynamic& a, folly::dynamic& b) {
109 const std::string& strA = getUcVariableName(a, ucKey);
110 const std::string& strB = getUcVariableName(b, ucKey);
111 return strA.compare(strB) < 0;
115 // Finally, remove the cached uc_names from the objects.
116 for (auto it = vars.begin(); it != vars.end(); it++) {
117 try {
118 it->erase(ucKey);
119 } catch (std::out_of_range e) {
124 bool VariablesCommand::executeImpl(
125 DebuggerSession* session,
126 folly::dynamic* responseMsg
128 folly::dynamic body = folly::dynamic::object;
129 folly::dynamic variables = folly::dynamic::array;
130 auto& args = tryGetObject(getMessage(), "arguments", s_emptyArgs);
132 ServerObject* obj = session->getServerObject(m_objectId);
133 if (obj != nullptr) {
134 int start = tryGetInt(args, "start", 0);
135 int count = tryGetInt(args, "count", -1);
136 auto requestId = targetThreadId(session);
138 if (obj->objectType() == ServerObjectType::Scope) {
139 ScopeObject* scope = static_cast<ScopeObject*>(obj);
140 addScopeVariables(session, targetThreadId(session), scope, &variables);
141 sortVariablesInPlace(variables);
143 // Client can ask for a subsequence of the variables.
144 auto startIt = variables.begin() + start;
145 if (startIt != variables.begin() || count > 0) {
146 if (count <= 0) {
147 count = variables.size();
148 } else if (start + count >= variables.size()) {
149 count = variables.size() - start;
152 auto endIt = variables.begin() + start + count;
153 variables = folly::dynamic::array(startIt, endIt);
155 } else if (obj->objectType() == ServerObjectType::Variable) {
156 VariableObject* variable = static_cast<VariableObject*>(obj);
157 addComplexChildren(
158 session,
159 requestId,
160 start,
161 count,
162 variable,
163 &variables
165 } else if (obj->objectType() == ServerObjectType::SubScope) {
166 VariableSubScope* subScope = static_cast<VariableSubScope*>(obj);
167 addSubScopes(
168 subScope,
169 session,
170 requestId,
171 start,
172 count,
173 &variables
178 body["variables"] = variables;
179 (*responseMsg)["body"] = body;
181 // Completion of this command does not resume the target.
182 return false;
185 int VariablesCommand::countScopeVariables(
186 DebuggerSession* session,
187 const ScopeObject* scope,
188 request_id_t requestId
190 return addScopeVariables(session, requestId, scope, nullptr);
193 int VariablesCommand::addScopeVariables(
194 DebuggerSession* session,
195 request_id_t requestId,
196 const ScopeObject* scope,
197 folly::dynamic* vars
199 assertx(vars == nullptr || vars->isArray());
201 switch (scope->m_scopeType) {
202 case ScopeType::Locals:
203 return addLocals(session, requestId, scope, vars);
205 case ScopeType::ServerConstants: {
206 Variant v;
207 int count = addConstants(session, requestId, s_user, nullptr);
208 addScopeSubSection(
209 session,
210 "User Defined Constants",
211 "[" + std::to_string(count) + "]",
213 nullptr,
214 count,
215 ClassPropsType::UserDefinedConstants,
216 requestId,
218 vars
221 count = addConstants(session, requestId, s_core, nullptr);
222 addScopeSubSection(
223 session,
224 "System Defined Constants",
225 "[" + std::to_string(count) + "]",
227 nullptr,
228 count,
229 ClassPropsType::SystemDefinedConstants,
230 requestId,
232 vars
235 return 2;
237 case ScopeType::Superglobals:
238 return addSuperglobalVariables(session, requestId, scope, vars);
240 default:
241 assertx(false);
244 return 0;
247 void VariablesCommand::addSubScopes(
248 VariableSubScope* subScope,
249 DebuggerSession* session,
250 request_id_t requestId,
251 int start,
252 int count,
253 folly::dynamic* variables
255 if (subScope->m_subScopeType == ClassPropsType::UserDefinedConstants) {
256 addConstants(session, requestId, s_user, variables);
257 sortVariablesInPlace(*variables);
258 } else if (subScope->m_subScopeType ==
259 ClassPropsType::SystemDefinedConstants) {
260 addConstants(session, requestId, s_core, variables);
261 sortVariablesInPlace(*variables);
262 } else {
263 const auto obj = subScope->m_variable.toObject().get();
264 if (obj == nullptr) {
265 throw DebuggerCommandException("Expected a server object.");
268 const auto cls = obj->getVMClass();
269 if (cls == nullptr) {
270 throw DebuggerCommandException("Expected an object class.");
273 switch (subScope->m_subScopeType) {
274 case ClassPropsType::Constants: {
275 // Add all the constants for the specified class to the result array
276 addClassConstants(
277 session,
278 requestId,
279 start,
280 count,
281 cls,
282 subScope->m_variable,
283 variables
285 break;
288 case ClassPropsType::StaticProps: {
289 // Add all the static props for the specified class to the
290 // result array.
291 addClassStaticProps(
292 session,
293 requestId,
294 start,
295 count,
296 cls,
297 subScope->m_variable,
298 variables
300 break;
303 case ClassPropsType::PrivateBaseProps: {
304 addClassPrivateProps(
305 session,
306 requestId,
307 start,
308 count,
309 subScope,
310 subScope->m_variable,
311 variables
313 break;
316 default:
317 throw DebuggerCommandException("Unexpected sub scope type");
322 int VariablesCommand::addLocals(
323 DebuggerSession* session,
324 request_id_t requestId,
325 const ScopeObject* scope,
326 folly::dynamic* vars
328 assertx(scope->m_scopeType == ScopeType::Locals);
330 VMRegAnchor regAnchor;
331 int count = 0;
333 const int frameDepth = scope->m_frameDepth;
334 auto const fp = g_context->getFrameAtDepth(frameDepth);
336 // If the frame at the specified depth has a $this, include it.
337 if (fp != nullptr &&
338 fp->func() != nullptr &&
339 fp->func()->cls() != nullptr &&
340 fp->hasThis()) {
342 count++;
343 if (vars != nullptr) {
344 Variant this_(fp->getThis());
345 vars->push_back(serializeVariable(session, requestId, "this", this_));
349 // Add each variable, filtering out superglobals
350 auto const locals = getDefinedVariables(fp);
351 for (ArrayIter iter(locals); iter; ++iter) {
352 const std::string name = iter.first().toString().toCppString();
353 if (isSuperGlobal(name) || name == "this") {
354 continue;
357 if (vars != nullptr) {
358 vars->push_back(
359 serializeVariable(session, requestId, name, iter.second())
363 count++;
366 return count;
369 int VariablesCommand::getCachedValue(
370 DebuggerSession* session,
371 const int cacheKey,
372 folly::dynamic* vars
374 int count;
376 folly::dynamic* cachedResult = session->getCachedVariableObject(cacheKey);
377 if (cachedResult != nullptr) {
378 // Found a cached copy of this response as a folly::dynamic. Use that.
379 count = cachedResult->size();
380 if (vars != nullptr) {
381 *vars = *cachedResult;
383 return count;
386 return -1;
389 int VariablesCommand::addConstants(
390 DebuggerSession* session,
391 request_id_t requestId,
392 const StaticString& category,
393 folly::dynamic* vars
395 int count = 0;
397 int cacheKey = -1;
398 if (category == s_core) {
399 cacheKey = DebuggerSession::kCachedVariableKeyServerConsts;
400 } else if (category == s_user) {
401 cacheKey = DebuggerSession::kCachedVariableKeyUserConsts;
404 if (cacheKey != -1) {
405 int cacheCount = getCachedValue(session, cacheKey, vars);
406 if (cacheCount >= 0) {
407 return cacheCount;
411 const auto& constants = lookupDefinedConstants(true)[category].toArray();
412 for (ArrayIter iter(constants); iter; ++iter) {
413 const std::string name = iter.first().toString().toCppString();
414 if (vars != nullptr) {
415 vars->push_back(
416 serializeVariable(session, requestId, name, iter.second(), true)
420 count++;
423 if (vars != nullptr && cacheKey != -1) {
424 // Cache the result since the same JSON array is to be requested by the
425 // client for every stack frame for every request for this pause of the
426 // target.
427 session->setCachedVariableObject(cacheKey, *vars);
430 return count;
433 bool VariablesCommand::isSuperGlobal(const std::string& name) {
434 return name == "GLOBALS" || BuiltinSymbols::IsSuperGlobal(name);
437 int VariablesCommand::addSuperglobalVariables(
438 DebuggerSession* session,
439 request_id_t requestId,
440 const ScopeObject* scope,
441 folly::dynamic* vars
443 assertx(scope->m_scopeType == ScopeType::Superglobals);
445 int cacheCount = getCachedValue(
446 session,
447 DebuggerSession::kCachedVariableKeyServerGlobals,
448 vars
451 if (cacheCount >= 0) {
452 return cacheCount;
455 int count = 0;
456 const Array globals = php_globals_as_array();
457 for (ArrayIter iter(globals); iter; ++iter) {
458 const std::string name = iter.first().toString().toCppString();
459 if (!isSuperGlobal(name)) {
460 continue;
463 if (vars != nullptr) {
464 vars->push_back(
465 serializeVariable(session, requestId, name, iter.second())
469 count++;
472 if (vars != nullptr) {
473 // Cache the result since the same JSON array is to be requested by the
474 // client for every stack frame for every request for this pause of the
475 // target.
476 session->setCachedVariableObject(
477 DebuggerSession::kCachedVariableKeyServerGlobals,
478 *vars
482 return count;
485 const std::string VariablesCommand::getPHPVarName(const std::string& name) {
486 std::string variableName = (name[0] != '$' && name[0] != ':')
487 ? std::string("$") + name
488 : name;
489 return variableName;
492 folly::dynamic VariablesCommand::serializeVariable(
493 DebuggerSession* session,
494 request_id_t requestId,
495 const std::string& name,
496 const Variant& variable,
497 bool doNotModifyName, /* = false */
498 folly::dynamic* presentationHint /* = nullptr */
500 folly::dynamic var = folly::dynamic::object;
501 const std::string variableName = doNotModifyName ? name : getPHPVarName(name);
503 var["name"] = variableName;
504 var["value"] = getVariableValue(variable);
505 var["type"] = getTypeName(variable);
507 // If the variable is an array or object, register it as a server object and
508 // indicate how many children it has.
509 if (variable.isArray() || variable.isObject()) {
510 unsigned int id = session->generateVariableId(
511 requestId,
512 const_cast<Variant&>(variable)
515 if (variable.isObject()) {
516 var["namedVariables"] =
517 addObjectChildren(session, requestId, -1, -1, variable, nullptr);
518 } else if (variable.isDict() || variable.isKeyset()) {
519 var["namedVariables"] = variable.toArray().size();
520 } else {
521 var["indexedVariables"] = variable.toArray().size();
524 var["variablesReference"] = id;
527 if (presentationHint != nullptr) {
528 var["presentationHint"] = *presentationHint;
531 return var;
534 const char* VariablesCommand::getTypeName(const Variant& variable) {
535 switch (variable.getType()) {
536 case KindOfUninit:
537 case KindOfNull:
538 return "null";
540 case KindOfBoolean:
541 return "bool";
543 case KindOfInt64:
544 return "int";
546 case KindOfDouble:
547 return "double";
549 case KindOfPersistentString:
550 case KindOfString:
551 return "string";
553 case KindOfResource:
554 return "resource";
556 case KindOfPersistentVec:
557 case KindOfVec:
558 case KindOfPersistentDict:
559 case KindOfDict:
560 case KindOfPersistentKeyset:
561 case KindOfKeyset:
562 case KindOfPersistentArray:
563 case KindOfArray: {
564 if (variable.isVecArray()) {
565 return "vec";
568 if (variable.isDict()) {
569 return "dict";
572 if (variable.isKeyset()) {
573 return "keyset";
576 return "array";
579 case KindOfObject:
580 return variable.toCObjRef()->getClassName().c_str();
582 case KindOfRef:
583 return "reference";
585 default:
586 VSDebugLogger::Log(
587 VSDebugLogger::LogLevelError,
588 "Unknown type %d for variable!",
589 (int)variable.getType()
591 return "UNKNOWN TYPE";
595 const std::string VariablesCommand::getVariableValue(const Variant& variable) {
596 const DataType type = variable.getType();
598 switch (type) {
599 // For primitive / scalar values, just return a string representation of
600 // the variable's value.
601 case KindOfUninit:
602 case KindOfNull:
603 return "null";
605 case KindOfBoolean:
606 return variable.toBooleanVal() ? "true" : "false";
608 case KindOfInt64:
609 return std::to_string(variable.toInt64Val());
611 case KindOfDouble: {
612 // Convert double to string, but remove any trailing 0s after the
613 // tenths position.
614 std::string dblString = std::to_string(variable.toDoubleVal());
615 dblString.erase(dblString.find_last_not_of('0') + 1, std::string::npos);
616 if (dblString[dblString.size() - 1] == '.') {
617 dblString += "0";
619 return dblString;
622 case KindOfPersistentString:
623 case KindOfString:
624 return variable.toCStrRef().toCppString();
626 case KindOfResource: {
627 auto res = variable.toResource();
628 std::string resourceDesc = "resource id='";
629 resourceDesc += std::to_string(res->getId());
630 resourceDesc += "' type='";
631 resourceDesc += res->o_getResourceName().data();
632 resourceDesc += "'";
633 return resourceDesc;
636 case KindOfPersistentVec:
637 case KindOfVec:
638 case KindOfPersistentArray:
639 case KindOfArray: {
640 std::string arrayDesc = "array[";
641 arrayDesc += std::to_string(variable.toArray().size());
642 arrayDesc += "]";
643 return arrayDesc;
646 case KindOfPersistentDict:
647 case KindOfDict: {
648 std::string dictDisc = "dict[";
649 dictDisc += std::to_string(variable.toArray().size());
650 dictDisc += "]";
651 return dictDisc;
654 case KindOfPersistentKeyset:
655 case KindOfKeyset: {
656 std::string keysetDisc = "keyset[";
657 keysetDisc += std::to_string(variable.toArray().size());
658 keysetDisc += "]";
659 return keysetDisc;
662 case KindOfRef:
663 // Note: PHP references are not supported in Hack.
664 return "reference";
666 case KindOfObject:
667 return variable.toCObjRef()->getClassName().c_str();
669 default:
670 return "Unexpected variable type";
674 int VariablesCommand::addComplexChildren(
675 DebuggerSession* session,
676 request_id_t requestId,
677 int start,
678 int count,
679 VariableObject* variable,
680 folly::dynamic* vars
682 Variant& var = variable->m_variable;
684 if (var.isObject()) {
685 int result = addObjectChildren(
686 session,
687 requestId,
688 start,
689 count,
690 variable->m_variable,
691 vars
694 bool isArrayLikeObject = false;
695 const auto obj = var.toObject().get();
696 if (obj != nullptr) {
697 auto currentClass = obj->getVMClass();
698 if (currentClass != nullptr) {
699 const std::string className = currentClass->name()->toCppString();
700 isArrayLikeObject = isArrayObjectType(className);
704 if (vars != nullptr && !isArrayLikeObject) {
705 sortVariablesInPlace(*vars);
708 // NOTE: These are added after the instance variables are added and sorted
709 // so that they appear at the end of the scope block in the UX.
711 // Constants defined on this object's class and all constants inherited
712 // from classes up the parent chain.
713 result += addClassSubScopes(
714 session,
715 ClassPropsType::Constants,
716 requestId,
717 var,
718 vars
721 // Static props defined on this object's class and all static props
722 // inherited from classes up the parent chain.
723 result += addClassSubScopes(
724 session,
725 ClassPropsType::StaticProps,
726 requestId,
727 var,
728 vars
731 return result;
732 } else if (var.isArray()) {
733 return addArrayChildren(session, requestId, start, count, variable, vars);
736 return 0;
739 int VariablesCommand::addArrayChildren(
740 DebuggerSession* session,
741 request_id_t requestId,
742 int start,
743 int count,
744 VariableObject* variable,
745 folly::dynamic* vars
747 Variant& var = variable->m_variable;
749 assertx(var.isArray());
751 int idx = -1;
752 int added = 0;
754 for (ArrayIter iter(var.toArray()); iter; ++iter) {
755 idx++;
757 if (start >= 0 && idx < start) {
758 continue;
761 std::string name = iter.first().toString().toCppString();
762 if (!iter.first().isInteger()) {
763 name = "'" + name + "'";
766 if (vars != nullptr) {
767 vars->push_back(
768 serializeVariable(session, requestId, name, iter.second(), true)
770 added++;
773 if (count > 0 && added >= count) {
774 break;
778 return added;
781 int VariablesCommand::addClassConstants(
782 DebuggerSession* session,
783 request_id_t requestId,
784 int start,
785 int count,
786 Class* cls,
787 const Variant& var,
788 folly::dynamic* vars
790 Class* currentClass = cls;
792 std::unordered_set<std::string> constantNames;
793 while (currentClass != nullptr) {
794 for (Slot i = 0; i < currentClass->numConstants(); i++) {
795 auto& constant = currentClass->constants()[i];
797 folly::dynamic presentationHint = folly::dynamic::object;
798 folly::dynamic attribs = folly::dynamic::array;
799 attribs.push_back("constant");
800 attribs.push_back("readOnly");
801 presentationHint["attributes"] = attribs;
803 std::string name = constant.cls->name()->toCppString() +
804 "::" +
805 constant.name->toCppString();
807 if (constantNames.find(name) == constantNames.end()) {
808 constantNames.insert(name);
809 if (vars != nullptr) {
810 vars->push_back(
811 serializeVariable(
812 session,
813 requestId,
814 name,
815 tvAsVariant(const_cast<TypedValueAux*>(&constant.val)),
816 true,
817 &presentationHint
824 currentClass = currentClass->parent();
827 if (vars != nullptr) {
828 sortVariablesInPlace(*vars);
830 if (start > 0 && start < vars->size()) {
831 vars->erase(vars->begin(), vars->begin() + start);
834 if (count > 0) {
835 if (count > vars->size()) {
836 count = vars->size();
838 vars->erase(vars->begin() + count, vars->end());
842 return constantNames.size();
845 int VariablesCommand::addClassStaticProps(
846 DebuggerSession* session,
847 request_id_t requestId,
848 int start,
849 int count,
850 Class* cls,
851 const Variant& var,
852 folly::dynamic* vars
854 const std::string className = cls->name()->toCppString();
855 const auto staticProperties = cls->staticProperties();
856 int propCount = 0;
858 for (Slot i = 0; i < cls->numStaticProperties(); i++) {
859 const auto& prop = staticProperties[i];
860 auto val = cls->getSPropData(i);
862 if (val != nullptr) {
863 Variant variant = tvAsVariant(val);
865 folly::dynamic presentationHint = folly::dynamic::object;
866 folly::dynamic attribs = folly::dynamic::array;
867 attribs.push_back("static");
869 if (prop.attrs & AttrInterface) {
870 attribs.push_back("interface");
873 if (prop.attrs & AttrPublic) {
874 presentationHint["visibility"] = "public";
875 } else if (prop.attrs & AttrProtected) {
876 presentationHint["visibility"] = "protected";
877 } else if (prop.attrs & AttrPrivate) {
878 presentationHint["visibility"] = "private";
881 if (variant.isObject()) {
882 presentationHint["kind"] = "class";
885 presentationHint["attributes"] = attribs;
886 std::string propName =
887 className + "::$" + prop.name.get()->toCppString();
889 propCount++;
891 if (vars != nullptr) {
892 vars->push_back(
893 serializeVariable(
894 session,
895 requestId,
896 propName,
897 variant,
898 true,
899 &presentationHint
906 if (vars != nullptr) {
907 sortVariablesInPlace(*vars);
909 if (start > 0 && start < vars->size()) {
910 vars->erase(vars->begin(), vars->begin() + start);
913 if (count > 0) {
914 if (count > vars->size()) {
915 count = vars->size();
917 vars->erase(vars->begin() + count, vars->end());
921 return propCount;
924 int VariablesCommand::addScopeSubSection(
925 DebuggerSession* session,
926 const char* displayName,
927 const std::string& displayValue,
928 const std::string& className,
929 const Class* currentClass,
930 int childCount,
931 ClassPropsType type,
932 request_id_t requestId,
933 const Variant& var,
934 folly::dynamic* vars
936 int constantScopeId =
937 session->generateVariableSubScope(
938 requestId,
939 var,
940 currentClass,
941 className,
942 type
945 if (vars != nullptr) {
946 folly::dynamic container = folly::dynamic::object;
947 folly::dynamic presentationHint = folly::dynamic::object;
948 folly::dynamic attribs = folly::dynamic::array;
949 attribs.push_back("constant");
950 attribs.push_back("readOnly");
951 presentationHint["attributes"] = attribs;
953 container["name"] = displayName;
954 container["value"] = displayValue;
955 container["namedVariables"] = childCount;
956 container["presentationHint"] = presentationHint;
957 container["variablesReference"] = constantScopeId;
958 vars->push_back(container);
961 return constantScopeId;
964 int VariablesCommand::addClassSubScopes(
965 DebuggerSession* session,
966 ClassPropsType propType,
967 request_id_t requestId,
968 const Variant& var,
969 folly::dynamic* vars
971 int subScopeCount = 0;
972 const auto obj = var.toObject().get();
973 if (obj == nullptr) {
974 return 0;
977 auto currentClass = obj->getVMClass();
978 if (currentClass == nullptr) {
979 return 0;
982 int count = 0;
983 const char* scopeTitle = nullptr;
984 std::string className = currentClass->name()->toCppString();
986 switch (propType) {
987 case ClassPropsType::Constants: {
988 count = addClassConstants(
989 session,
990 requestId,
993 currentClass,
994 var,
995 nullptr
997 scopeTitle = "Class Constants";
998 if (count > 0) {
999 subScopeCount++;
1000 addScopeSubSection(
1001 session,
1002 scopeTitle,
1003 "class " + className,
1004 className,
1005 currentClass,
1006 count,
1007 propType,
1008 requestId,
1009 var,
1010 vars
1014 break;
1015 case ClassPropsType::StaticProps: {
1016 className = currentClass->name()->toCppString();
1017 count = addClassStaticProps(
1018 session,
1019 requestId,
1022 currentClass,
1023 var,
1024 nullptr
1026 scopeTitle = "Static Props";
1027 if (count > 0) {
1028 subScopeCount++;
1029 addScopeSubSection(
1030 session,
1031 scopeTitle,
1032 "class " + className,
1033 className,
1034 currentClass,
1035 count,
1036 propType,
1037 requestId,
1038 var,
1039 vars
1043 break;
1044 default:
1045 assertx(false);
1048 return subScopeCount;
1051 void VariablesCommand::forEachInstanceProp(
1052 const Variant& var,
1053 std::function<bool(
1054 const std::string& objectClassName,
1055 const std::string& propName,
1056 const std::string& propClassName,
1057 const std::string& displayName,
1058 const char* visibilityDescription,
1059 folly::dynamic& presentationHint,
1060 const Variant& propertyVariant
1061 )> callback
1063 const auto obj = var.toObject().get();
1064 if (obj == nullptr) {
1065 return;
1068 const auto cls = obj->getVMClass();
1069 if (cls == nullptr) {
1070 return;
1073 // Instance properties on this object.
1074 const Array instProps = obj->toArray();
1075 const std::string className = cls->name()->toCppString();
1077 for (ArrayIter iter(instProps); iter; ++iter) {
1078 std::string propName = iter.first().toString().toCppString();
1079 const Variant& propertyVariant = iter.second();
1081 std::string propClassName = className;
1082 const char* visibilityDescription;
1084 // The object's property name can be encoded with info about the modifier
1085 // and class. Decode it. (See HPHP::PreClass::manglePropName).
1086 if (propName.size() < 3 || propName[0] != '\0') {
1087 // This is a public property.
1088 visibilityDescription = VisibilityPublic;
1089 } else if (propName[1] == '*') {
1090 // This is a protected property.
1091 propName = propName.substr(3);
1092 visibilityDescription = VisibilityProtected;
1093 } else {
1094 // This is a private property on this object class or one of its base
1095 // classes.
1096 visibilityDescription = VisibilityPrivate;
1097 const unsigned long classNameEnd = propName.find('\0', 1);
1098 propClassName = propName.substr(1, classNameEnd - 1);
1099 propName = propName.substr(classNameEnd + 1);
1102 const std::string displayName = isArrayObjectType(className)
1103 ? propName
1104 : "$" + propName;
1106 folly::dynamic presentationHint = folly::dynamic::object;
1107 presentationHint["visibility"] = visibilityDescription;
1109 if (propertyVariant.isObject()) {
1110 presentationHint["kind"] = "class";
1113 bool continueLooping = callback(
1114 className,
1115 propName,
1116 propClassName,
1117 displayName,
1118 visibilityDescription,
1119 presentationHint,
1120 propertyVariant
1123 if (!continueLooping) {
1124 break;
1129 void VariablesCommand::addClassPrivateProps(
1130 DebuggerSession* session,
1131 request_id_t requestId,
1132 int start,
1133 int count,
1134 VariableSubScope* subScope,
1135 const Variant& var,
1136 folly::dynamic* vars
1138 int propCount = 0;
1140 forEachInstanceProp(
1141 var,
1142 [&](
1143 const std::string& objectClassName,
1144 const std::string& propName,
1145 const std::string& propClassName,
1146 const std::string& displayName,
1147 const char* visibilityDescription,
1148 folly::dynamic& presentationHint,
1149 const Variant& propertyVariant
1152 // Only looking for private properties.
1153 if (visibilityDescription != VisibilityPrivate) {
1154 return true;
1157 // And only private properties on the specified class.
1158 if (propClassName != subScope->m_className) {
1159 return true;
1162 propCount++;
1163 if (start >= 0 && propCount < start) {
1164 return true;
1167 vars->push_back(
1168 serializeVariable(
1169 session,
1170 requestId,
1171 displayName,
1172 propertyVariant,
1173 true,
1174 &presentationHint
1178 if (count > 0 && propCount - start >= count) {
1179 return false;
1182 return true;
1186 int VariablesCommand::addObjectChildren(
1187 DebuggerSession* session,
1188 request_id_t requestId,
1189 int start,
1190 int count,
1191 const Variant& var,
1192 folly::dynamic* vars
1194 assertx(var.isObject());
1196 int propCount = 0;
1198 // The inheritance rules for public and protected properties are such that
1199 // the first property with a given name encountered when walking up from
1200 // the most-derived class type to the base-most class type, shadows all
1201 // other instances, and is the only one that actually exists on the object,
1202 // from any context.
1203 std::unordered_set<std::string> properties;
1204 std::unordered_map<std::string, int> privPropScopes;
1206 forEachInstanceProp(
1207 var,
1208 [&](
1209 const std::string& objectClassName,
1210 const std::string& propName,
1211 const std::string& propClassName,
1212 const std::string& displayName,
1213 const char* visibilityDescription,
1214 folly::dynamic& presentationHint,
1215 const Variant& propertyVariant
1217 if (vars == nullptr) {
1218 // We are only counting.
1219 propCount++;
1220 return true;
1223 if (propClassName != objectClassName) {
1224 // If this is a private member variable and the variable's declaring
1225 // class name is not the same as the class name of the current object,
1226 // this is a private member declared in a parent class. In this case,
1227 // the private member is not accessible from code in the derived
1228 // classes, but the property is not shadowed on the PHP object: it is
1229 // visible and accessible from methods on the base class.
1230 auto it = privPropScopes.find(propClassName);
1231 if (it == privPropScopes.end()) {
1232 // If this is the first private property encountered for this
1233 // particular parent class, add a scope section for that class's
1234 // private properties.
1235 privPropScopes.emplace(propClassName, 1);
1236 } else {
1237 it->second++;
1240 // Don't add the private property to this scope, add it to a child
1241 // scope below.
1242 return true;
1245 if (properties.find(displayName) != properties.end()) {
1246 // A property with this name has already been added by a derived class,
1247 // since we're walking the class inheritance hierarchy from the object
1248 // upwards, the derived property shadows this property.
1249 return true;
1252 properties.insert(displayName);
1253 propCount++;
1255 if (start >= 0 && propCount < start) {
1256 return true;
1259 vars->push_back(
1260 serializeVariable(
1261 session,
1262 requestId,
1263 displayName,
1264 propertyVariant,
1265 true,
1266 &presentationHint
1270 if (count > 0 && propCount - start >= count) {
1271 return false;
1274 return true;
1277 if (vars == nullptr) {
1278 return propCount;
1281 // Create a scope for each base class that had shadowed private properties.
1282 for (auto it = privPropScopes.begin(); it != privPropScopes.end(); it++) {
1283 const std::string& propClassName = it->first;
1284 int privPropCount = it->second;
1285 addScopeSubSection(
1286 session,
1287 "Private props",
1288 "class " + propClassName,
1289 propClassName,
1290 nullptr,
1291 privPropCount,
1292 ClassPropsType::PrivateBaseProps,
1293 requestId,
1294 var,
1295 vars
1299 return propCount;