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/runtime/base/array-iterator.h"
18 #include "hphp/runtime/base/tv-variant.h"
19 #include "hphp/runtime/ext/vsdebug/command.h"
20 #include "hphp/runtime/ext/vsdebug/debugger.h"
21 #include "hphp/runtime/vm/named-entity-defs.h"
22 #include "hphp/runtime/vm/runtime-compiler.h"
23 #include "hphp/runtime/vm/vm-regs.h"
33 FramePointer(Debugger
*debugger
, int frameDepth
)
35 m_exitDummyContext(false) {
38 if (debugger
->isDummyRequest()) {
40 if (m_fp
== nullptr && vmStack().count() == 0) {
41 g_context
->enterDebuggerDummyEnv();
43 m_exitDummyContext
= true;
46 m_fp
= g_context
->getFrameAtDepthForDebuggerUnsafe(frameDepth
);
51 if (m_exitDummyContext
) {
52 g_context
->exitDebuggerDummyEnv();
60 ActRec
* operator ->() {
66 bool m_exitDummyContext
;
70 CompletionsCommand::CompletionsCommand(
72 folly::dynamic message
73 ) : VSCommand(debugger
, message
),
76 const folly::dynamic
& args
= tryGetObject(message
, "arguments", s_emptyArgs
);
77 const int frameId
= tryGetInt(args
, "frameId", -1);
81 CompletionsCommand::~CompletionsCommand() {
84 FrameObject
* CompletionsCommand::getFrameObject(DebuggerSession
* session
) {
85 if (m_frameObj
!= nullptr) {
89 m_frameObj
= session
->getFrameObject(m_frameId
);
93 request_id_t
CompletionsCommand::targetThreadId(DebuggerSession
* session
) {
94 FrameObject
* frame
= getFrameObject(session
);
95 if (frame
== nullptr) {
96 // Execute the completion in the dummy context.
97 return Debugger::kDummyTheadId
;
100 return frame
->m_requestId
;
103 void CompletionsCommand::addCompletionTarget(
104 folly::dynamic
& completions
,
105 const char* completionText
,
106 const char* completionType
,
109 completions
.push_back(folly::dynamic::object
);
110 folly::dynamic
& completion
= completions
[completions
.size() - 1];
111 completion
["label"] = completionText
;
112 completion
["type"] = completionType
;
114 // NOTE: "length" in the protocol is the number of chars to overwrite from
115 // the input text, counting backwards from "column" position in the
116 // CompletionsRequest message. Why this is called "length" is anybody's
118 completion
["length"] = charsToOverwrite
;
121 CompletionsCommand::SuggestionContext
122 CompletionsCommand::parseCompletionExpression(
123 const std::string
& expr
125 std::string text
= expr
;
126 SuggestionContext context
;
127 context
.type
= SuggestionType::None
;
131 bool CompletionsCommand::executeImpl(
132 DebuggerSession
* session
,
133 folly::dynamic
* responseMsg
135 VMRegAnchor regAnchor
;
137 // The request thread should not re-enter the debugger while
138 // processing this command.
139 DebuggerNoBreakContext
noBreak(m_debugger
);
141 folly::dynamic
& message
= getMessage();
142 const folly::dynamic
& args
= tryGetObject(message
, "arguments", s_emptyArgs
);
143 int column
= tryGetInt(args
, "column", -1);
144 std::string text
= tryGetString(args
, "text", "");
147 // If no column specified, use the end of the string.
148 column
= text
.size() - 1;
150 // Completion column is 1-based, but std::string indexes are 0 based.
154 text
= text
.substr(0, column
);
156 folly::dynamic body
= folly::dynamic::object
;
157 body
["targets"] = folly::dynamic::array
;
160 SuggestionContext context
= parseCompletionExpression(text
);
161 auto& targets
= body
["targets"];
163 // If the target is not paused, completions are only valid for the dummy
165 if (!m_debugger
->isPaused() &&
166 targetThreadId(session
) != Debugger::kDummyTheadId
) {
167 throw DebuggerCommandException("Target request is running.");
170 // Chop off any leading $.
171 if (context
.matchPrefix
[0] == '$') {
172 context
.matchPrefix
= context
.matchPrefix
.substr(1);
175 switch (context
.type
) {
176 case SuggestionType::None
:
179 case SuggestionType::Variable
:
180 addVariableCompletions(context
, targets
);
183 case SuggestionType::Member
:
184 addMemberCompletions(context
, targets
);
187 case SuggestionType::ClassStatic
:
188 addClassStaticCompletions(context
, targets
);
191 case SuggestionType::ClassConstant
:
192 addClassConstantCompletions(context
, targets
);
195 case SuggestionType::FuncsAndConsts
:
196 addFuncConstantCompletions(context
, targets
);
203 // Don't actually report any errors for completion requests, we just
204 // return an empty list if something goes wrong.
207 (*responseMsg
)["body"] = body
;
209 // Do not resume the target.
213 void CompletionsCommand::addVariableCompletions(
214 SuggestionContext
& context
,
215 folly::dynamic
& targets
217 int frameDepth
= m_frameObj
? m_frameObj
->m_frameDepth
: 0;
218 auto fp
= FramePointer(m_debugger
, frameDepth
);
224 // If there is a $this, add it.
227 fp
->func() != nullptr &&
228 fp
->func()->cls() != nullptr &&
230 static const std::string
thisName("this");
231 if (context
.matchPrefix
.size() >= thisName
.size() &&
235 context
.matchPrefix
.begin())) {
246 // Add any defined variables that match the specified prefix.
247 const auto allVariables
= getDefinedVariables(fp
);
249 for (ArrayIter
iter(allVariables
); iter
; ++iter
) {
250 const std::string
& name
= iter
.first().toString().toCppString();
251 addIfMatch(name
, context
.matchPrefix
, CompletionTypeVar
, targets
);
255 void CompletionsCommand::addMemberCompletions(
256 SuggestionContext
& context
,
257 folly::dynamic
& targets
259 int frameDepth
= m_frameObj
? m_frameObj
->m_frameDepth
: 0;
260 FramePointer
fp(m_debugger
, frameDepth
);
266 // Don't execute the evaluation if the input string looks like it might
267 // contain a function call. This could have side effects we don't want to
268 // cause during auto complete suggestions and it's better to be safe here.
269 if (context
.matchContext
.find("(") != std::string::npos
||
270 context
.matchContext
.find(")") != std::string::npos
||
271 trimString(context
.matchContext
).size() == 0) {
276 DebuggerRequestInfo
* ri
= m_debugger
->getRequestInfo();
278 // SilentEvaluationContext suppresses all error output during the evaluation,
279 // and re-enables it when it is destroyed.
280 SilentEvaluationContext
silentContext(m_debugger
, ri
);
282 // Figure out what object the context for the completion refers to. Don't
283 // hit any breakpoints during this eval.
284 // NOTE: Prefixing the evaluation with @ to suppress any PHP notices that
285 // might otherwise be generated by evaluating the expression.
286 context
.matchContext
= "<?hh return @(" + context
.matchContext
+ ");";
287 std::unique_ptr
<Unit
> unit(
288 compile_debugger_string(context
.matchContext
.c_str(),
289 context
.matchContext
.size(),
290 g_context
->getRepoOptionsForCurrentFrame()));
292 if (unit
== nullptr) {
293 // Expression failed to compile.
297 Unit
* rawUnit
= unit
.get();
298 ri
->m_evaluationUnits
.push_back(std::move(unit
));
299 const auto& result
= g_context
->evalPHPDebugger(
308 const Variant
& obj
= result
.result
;
309 if (!obj
.isObject()) {
313 const auto object
= obj
.toObject().get();
314 if (object
== nullptr) {
318 // Add any matching instance properties of the object.
319 const Array instProps
= object
->toArray(false, true);
320 for (ArrayIter
iter(instProps
); iter
; ++iter
) {
321 std::string propName
= iter
.first().toString().toCppString();
323 // The object's property name can be encoded with info about the modifier
324 // and class. Decode it. (See HPHP::PreClass::manglePropName).
325 if (propName
[1] == '*') {
326 // This is a protected property.
327 propName
= propName
.substr(3);
328 } else if (propName
[0] == '\0') {
329 // This is a private property on this object class or one of its base
331 const unsigned long classNameEnd
= propName
.find('\0', 1);
332 propName
= propName
.substr(classNameEnd
+ 1);
335 addIfMatch(propName
, context
.matchPrefix
, CompletionTypeProp
, targets
);
338 // Add any instance methods of this object's class, or any of its parent
339 // classes. NB parent methods are automatic, no need to walk the tree.
340 Class
* cls
= object
->getVMClass();
341 int methodCount
= cls
->numMethods();
342 for (Slot i
= 0; i
< methodCount
; ++i
) {
343 const Func
* method
= cls
->getMethod(i
);
344 if (method
!= nullptr && (method
->attrs() & AttrStatic
) == 0) {
345 const std::string
& name
= method
->name()->toCppString();
346 addIfMatch(name
, context
.matchPrefix
, CompletionTypeFn
, targets
);
351 void CompletionsCommand::addClassConstantCompletions(
352 SuggestionContext
& context
,
353 folly::dynamic
& targets
355 HPHP::String
classStr(context
.matchContext
.c_str());
356 Class
* cls
= Class::load(classStr
.get());
357 if (cls
== nullptr) {
361 // Add constants of this class. Note that here and in methods, we get
362 // everything from this class and its ancestors
363 for (Slot i
= 0; i
< cls
->numConstants(); i
++) {
364 auto const &clsConst
= cls
->constants()[i
];
365 // constants() includes type constants and abstract constants, neither of
366 // which are particularly useful for debugger completion
367 if (clsConst
.kind() == ConstModifiers::Kind::Value
368 && !clsConst
.isAbstractAndUninit()) {
369 const std::string
& name
= clsConst
.name
->toCppString();
370 addIfMatch(name
, context
.matchPrefix
, CompletionTypeValue
, targets
);
374 // Add static methods of this class.
375 int methodCount
= cls
->numMethods();
376 for (Slot i
= 0; i
< methodCount
; ++i
) {
377 const Func
* method
= cls
->getMethod(i
);
378 if (method
!= nullptr && method
->attrs() & AttrStatic
) {
379 const std::string
& name
= method
->name()->toCppString();
380 addIfMatch(name
, context
.matchPrefix
, CompletionTypeFn
, targets
);
385 void CompletionsCommand::addClassStaticCompletions(
386 SuggestionContext
& context
,
387 folly::dynamic
& targets
389 HPHP::String
classStr(context
.matchContext
.c_str());
390 Class
* cls
= Class::load(classStr
.get());
392 while (cls
!= nullptr) {
393 // Add static propreties of this class.
394 const auto staticProperties
= cls
->staticProperties();
395 for (Slot i
= 0; i
< cls
->numStaticProperties(); i
++) {
396 const auto prop
= staticProperties
[i
];
397 const std::string
& propName
= prop
.name
.get()->toCppString();
398 addIfMatch(propName
, context
.matchPrefix
, CompletionTypeProp
, targets
);
405 void CompletionsCommand::addIfMatch(
406 const std::string
& name
,
407 const std::string
& matchPrefix
,
409 folly::dynamic
& targets
411 if (matchPrefix
.empty() ||
412 (name
.size() >= matchPrefix
.size() &&
417 [](char c1
, char c2
) {
418 return std::toupper(c1
) == std::toupper(c2
);
431 void CompletionsCommand::addFuncConstantCompletions(
432 SuggestionContext
& context
,
433 folly::dynamic
& targets
435 NamedEntity::foreach_cached_func([&](Func
* func
) {
436 if (func
->isGenerated()) return; //continue
437 auto name
= func
->name()->toCppString();
438 // Unit::getFunctions returns all lowercase names, lowercase here too.
439 std::transform(name
.begin(), name
.end(), name
.begin(), ::tolower
);
440 addIfMatch(name
, context
.matchPrefix
, CompletionTypeFn
, targets
);
443 auto const consts
= lookupDefinedConstants();
444 IterateKV(consts
.get(), [&] (TypedValue k
, TypedValue
) {
445 auto const& name
= String::attach(tvCastToStringData(k
));
454 NamedEntity::foreach_cached_class([&](Class
* c
) {
455 if (!(c
->attrs() & (AttrInterface
| AttrTrait
))) {
456 auto const& name
= c
->name()->toCppString();
457 addIfMatch(name
, context
.matchPrefix
, CompletionTypeClass
, targets
);
462 static const char* suggestionKeywords
[] = {
526 const auto count
= sizeof(suggestionKeywords
) / sizeof(suggestionKeywords
[0]);
528 for (int i
= 0; i
< count
; i
++) {
530 suggestionKeywords
[i
],
532 CompletionTypeKeyword
,