2 +----------------------------------------------------------------------+
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"
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(
42 folly::dynamic message
43 ) : VSCommand(debugger
, message
),
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
);
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(
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()) {
92 std::string upperString
= std::string(str
);
93 for (char& c
: upperString
) {
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());
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
++) {
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) {
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
);
165 } else if (obj
->objectType() == ServerObjectType::SubScope
) {
166 VariableSubScope
* subScope
= static_cast<VariableSubScope
*>(obj
);
178 body
["variables"] = variables
;
179 (*responseMsg
)["body"] = body
;
181 // Completion of this command does not resume the target.
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
,
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
: {
207 int count
= addConstants(session
, requestId
, s_user
, nullptr);
210 "User Defined Constants",
211 "[" + std::to_string(count
) + "]",
215 ClassPropsType::UserDefinedConstants
,
221 count
= addConstants(session
, requestId
, s_core
, nullptr);
224 "System Defined Constants",
225 "[" + std::to_string(count
) + "]",
229 ClassPropsType::SystemDefinedConstants
,
237 case ScopeType::Superglobals
:
238 return addSuperglobalVariables(session
, requestId
, scope
, vars
);
247 void VariablesCommand::addSubScopes(
248 VariableSubScope
* subScope
,
249 DebuggerSession
* session
,
250 request_id_t requestId
,
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
);
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
282 subScope
->m_variable
,
288 case ClassPropsType::StaticProps
: {
289 // Add all the static props for the specified class to the
297 subScope
->m_variable
,
303 case ClassPropsType::PrivateBaseProps
: {
304 addClassPrivateProps(
310 subScope
->m_variable
,
317 throw DebuggerCommandException("Unexpected sub scope type");
322 int VariablesCommand::addLocals(
323 DebuggerSession
* session
,
324 request_id_t requestId
,
325 const ScopeObject
* scope
,
328 assertx(scope
->m_scopeType
== ScopeType::Locals
);
330 VMRegAnchor regAnchor
;
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.
338 fp
->func() != nullptr &&
339 fp
->func()->cls() != nullptr &&
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") {
357 if (vars
!= nullptr) {
359 serializeVariable(session
, requestId
, name
, iter
.second())
369 int VariablesCommand::getCachedValue(
370 DebuggerSession
* session
,
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
;
389 int VariablesCommand::addConstants(
390 DebuggerSession
* session
,
391 request_id_t requestId
,
392 const StaticString
& category
,
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) {
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) {
416 serializeVariable(session
, requestId
, name
, iter
.second(), true)
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
427 session
->setCachedVariableObject(cacheKey
, *vars
);
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
,
443 assertx(scope
->m_scopeType
== ScopeType::Superglobals
);
445 int cacheCount
= getCachedValue(
447 DebuggerSession::kCachedVariableKeyServerGlobals
,
451 if (cacheCount
>= 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
)) {
463 if (vars
!= nullptr) {
465 serializeVariable(session
, requestId
, name
, iter
.second())
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
476 session
->setCachedVariableObject(
477 DebuggerSession::kCachedVariableKeyServerGlobals
,
485 const std::string
VariablesCommand::getPHPVarName(const std::string
& name
) {
486 std::string variableName
= (name
[0] != '$' && name
[0] != ':')
487 ? std::string("$") + name
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(
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();
521 var
["indexedVariables"] = variable
.toArray().size();
524 var
["variablesReference"] = id
;
527 if (presentationHint
!= nullptr) {
528 var
["presentationHint"] = *presentationHint
;
534 const char* VariablesCommand::getTypeName(const Variant
& variable
) {
535 switch (variable
.getType()) {
549 case KindOfPersistentString
:
556 case KindOfPersistentVec
:
558 case KindOfPersistentDict
:
560 case KindOfPersistentKeyset
:
562 case KindOfPersistentArray
:
564 if (variable
.isVecArray()) {
568 if (variable
.isDict()) {
572 if (variable
.isKeyset()) {
580 return variable
.toCObjRef()->getClassName().c_str();
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();
599 // For primitive / scalar values, just return a string representation of
600 // the variable's value.
606 return variable
.toBooleanVal() ? "true" : "false";
609 return std::to_string(variable
.toInt64Val());
612 // Convert double to string, but remove any trailing 0s after the
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] == '.') {
622 case KindOfPersistentString
:
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();
636 case KindOfPersistentVec
:
638 case KindOfPersistentArray
:
640 std::string arrayDesc
= "array[";
641 arrayDesc
+= std::to_string(variable
.toArray().size());
646 case KindOfPersistentDict
:
648 std::string dictDisc
= "dict[";
649 dictDisc
+= std::to_string(variable
.toArray().size());
654 case KindOfPersistentKeyset
:
656 std::string keysetDisc
= "keyset[";
657 keysetDisc
+= std::to_string(variable
.toArray().size());
663 // Note: PHP references are not supported in Hack.
667 return variable
.toCObjRef()->getClassName().c_str();
670 return "Unexpected variable type";
674 int VariablesCommand::addComplexChildren(
675 DebuggerSession
* session
,
676 request_id_t requestId
,
679 VariableObject
* variable
,
682 Variant
& var
= variable
->m_variable
;
684 if (var
.isObject()) {
685 int result
= addObjectChildren(
690 variable
->m_variable
,
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(
715 ClassPropsType::Constants
,
721 // Static props defined on this object's class and all static props
722 // inherited from classes up the parent chain.
723 result
+= addClassSubScopes(
725 ClassPropsType::StaticProps
,
732 } else if (var
.isArray()) {
733 return addArrayChildren(session
, requestId
, start
, count
, variable
, vars
);
739 int VariablesCommand::addArrayChildren(
740 DebuggerSession
* session
,
741 request_id_t requestId
,
744 VariableObject
* variable
,
747 Variant
& var
= variable
->m_variable
;
749 assertx(var
.isArray());
754 for (ArrayIter
iter(var
.toArray()); iter
; ++iter
) {
757 if (start
>= 0 && idx
< start
) {
761 std::string name
= iter
.first().toString().toCppString();
762 if (!iter
.first().isInteger()) {
763 name
= "'" + name
+ "'";
766 if (vars
!= nullptr) {
768 serializeVariable(session
, requestId
, name
, iter
.second(), true)
773 if (count
> 0 && added
>= count
) {
781 int VariablesCommand::addClassConstants(
782 DebuggerSession
* session
,
783 request_id_t requestId
,
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() +
805 constant
.name
->toCppString();
807 if (constantNames
.find(name
) == constantNames
.end()) {
808 constantNames
.insert(name
);
809 if (vars
!= nullptr) {
815 tvAsVariant(const_cast<TypedValueAux
*>(&constant
.val
)),
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
);
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
,
854 const std::string className
= cls
->name()->toCppString();
855 const auto staticProperties
= cls
->staticProperties();
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();
891 if (vars
!= nullptr) {
906 if (vars
!= nullptr) {
907 sortVariablesInPlace(*vars
);
909 if (start
> 0 && start
< vars
->size()) {
910 vars
->erase(vars
->begin(), vars
->begin() + start
);
914 if (count
> vars
->size()) {
915 count
= vars
->size();
917 vars
->erase(vars
->begin() + count
, vars
->end());
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
,
932 request_id_t requestId
,
936 int constantScopeId
=
937 session
->generateVariableSubScope(
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
,
971 int subScopeCount
= 0;
972 const auto obj
= var
.toObject().get();
973 if (obj
== nullptr) {
977 auto currentClass
= obj
->getVMClass();
978 if (currentClass
== nullptr) {
983 const char* scopeTitle
= nullptr;
984 std::string className
= currentClass
->name()->toCppString();
987 case ClassPropsType::Constants
: {
988 count
= addClassConstants(
997 scopeTitle
= "Class Constants";
1003 "class " + className
,
1015 case ClassPropsType::StaticProps
: {
1016 className
= currentClass
->name()->toCppString();
1017 count
= addClassStaticProps(
1026 scopeTitle
= "Static Props";
1032 "class " + className
,
1048 return subScopeCount
;
1051 void VariablesCommand::forEachInstanceProp(
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
1063 const auto obj
= var
.toObject().get();
1064 if (obj
== nullptr) {
1068 const auto cls
= obj
->getVMClass();
1069 if (cls
== nullptr) {
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
;
1094 // This is a private property on this object class or one of its base
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
)
1106 folly::dynamic presentationHint
= folly::dynamic::object
;
1107 presentationHint
["visibility"] = visibilityDescription
;
1109 if (propertyVariant
.isObject()) {
1110 presentationHint
["kind"] = "class";
1113 bool continueLooping
= callback(
1118 visibilityDescription
,
1123 if (!continueLooping
) {
1129 void VariablesCommand::addClassPrivateProps(
1130 DebuggerSession
* session
,
1131 request_id_t requestId
,
1134 VariableSubScope
* subScope
,
1136 folly::dynamic
* vars
1140 forEachInstanceProp(
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
) {
1157 // And only private properties on the specified class.
1158 if (propClassName
!= subScope
->m_className
) {
1163 if (start
>= 0 && propCount
< start
) {
1178 if (count
> 0 && propCount
- start
>= count
) {
1186 int VariablesCommand::addObjectChildren(
1187 DebuggerSession
* session
,
1188 request_id_t requestId
,
1192 folly::dynamic
* vars
1194 assertx(var
.isObject());
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(
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.
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);
1240 // Don't add the private property to this scope, add it to a child
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.
1252 properties
.insert(displayName
);
1255 if (start
>= 0 && propCount
< start
) {
1270 if (count
> 0 && propCount
- start
>= count
) {
1277 if (vars
== nullptr) {
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
;
1288 "class " + propClassName
,
1292 ClassPropsType::PrivateBaseProps
,