2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
6 | Copyright (c) 1997-2010 The PHP Group |
7 +----------------------------------------------------------------------+
8 | This source file is subject to version 3.01 of the PHP license, |
9 | that is bundled with this package in the file LICENSE, and is |
10 | available through the world-wide-web at the following url: |
11 | http://www.php.net/license/3_01.txt |
12 | If you did not receive a copy of the PHP license and are unable to |
13 | obtain it through the world-wide-web, please send a note to |
14 | license@php.net so we can mail you a copy immediately. |
15 +----------------------------------------------------------------------+
18 #include "hphp/runtime/ext/xdebug/xdebug_command.h"
19 #include "hphp/runtime/ext/xdebug/hook.h"
20 #include "hphp/runtime/ext/xdebug/xdebug_utils.h"
21 #include "hphp/runtime/ext/xdebug/php5_xdebug/xdebug_var.h"
23 #include "hphp/compiler/builtin_symbols.h"
24 #include "hphp/runtime/base/file.h"
25 #include "hphp/runtime/base/static-string-table.h"
26 #include "hphp/runtime/base/string-util.h"
27 #include "hphp/runtime/base/php-globals.h"
28 #include "hphp/runtime/ext/std/ext_std_file.h"
29 #include "hphp/runtime/ext/std/ext_std_misc.h"
30 #include "hphp/runtime/ext/url/ext_url.h"
31 #include "hphp/runtime/vm/runtime.h"
32 #include "hphp/runtime/vm/vm-regs.h"
36 ////////////////////////////////////////////////////////////////////////////////
39 // COMMAND(NAME, CLASS)
40 // NAME is the command name
41 // CLASS is the corresponding class
43 COMMAND("status", StatusCmd) \
44 COMMAND("feature_get", FeatureGetCmd) \
45 COMMAND("feature_set", FeatureSetCmd) \
46 COMMAND("run", RunCmd) \
47 COMMAND("step_into", StepIntoCmd) \
48 COMMAND("step_out", StepOutCmd) \
49 COMMAND("step_over", StepOverCmd) \
50 COMMAND("stop", StopCmd) \
51 COMMAND("detach", DetachCmd) \
52 COMMAND("break", BreakCmd) \
53 COMMAND("breakpoint_set", BreakpointSetCmd) \
54 COMMAND("breakpoint_get", BreakpointGetCmd) \
55 COMMAND("breakpoint_list", BreakpointListCmd) \
56 COMMAND("breakpoint_update", BreakpointUpdateCmd) \
57 COMMAND("breakpoint_remove", BreakpointRemoveCmd) \
58 COMMAND("stack_depth", StackDepthCmd) \
59 COMMAND("stack_get", StackGetCmd) \
60 COMMAND("context_names", ContextNamesCmd) \
61 COMMAND("context_get", ContextGetCmd) \
62 COMMAND("typemap_get", TypemapGetCmd) \
63 COMMAND("property_get", PropertyGetCmd) \
64 COMMAND("property_set", PropertySetCmd) \
65 COMMAND("property_value", PropertyGetCmd) \
66 COMMAND("source", SourceCmd) \
67 COMMAND("stdout", StdoutCmd) \
68 COMMAND("stderr", StderrCmd) \
69 COMMAND("eval", EvalCmd) \
70 COMMAND("xcmd_profiler_name_get", ProfilerNameGetCmd) \
72 ////////////////////////////////////////////////////////////////////////////////
74 // See http://xdebug.org/docs-dbgp.php#feature-names for a complete list
76 // FEATURE(NAME, SUPPORTED, VALUE, FREE)
77 // NAME is the feature name
78 // SUPPORTED is whether or not the feature is supported
79 // VALUE is the value to add to the xml node
80 // FREE is whether or not the value should be freed
82 FEATURE("breakpoint_languages", "0", nullptr, false) \
83 FEATURE("breakpoint_types", "1", \
84 "line conditional call return exception", false) \
85 FEATURE("data_encoding", "0", nullptr, false) \
86 FEATURE("encoding", "1", "iso-8859-1", false) \
87 FEATURE("language_name", "1", "PHP", false) \
88 FEATURE("language_supports_threads", "1", "0", false) \
89 FEATURE("language_version", "1", xdstrdup(HHVM_VERSION), true) \
90 FEATURE("max_children", "1", \
91 xdebug_sprintf("%d", m_server.m_maxChildren), true) \
92 FEATURE("max_data", "1", xdebug_sprintf("%d", m_server.m_maxData), true) \
93 FEATURE("max_depth", "1", xdebug_sprintf("%d", m_server.m_maxDepth), true) \
94 FEATURE("protocol_version", "1", DBGP_VERSION, false) \
95 FEATURE("supported_encodings", "1", "iso-8859-1", false) \
96 FEATURE("supports_async", "1", "0", false) \
97 FEATURE("supports_postmortem", "1", "1", false) \
98 FEATURE("show_hidden", "1", \
99 xdebug_sprintf("%d", m_server.m_showHidden), true) \
101 ////////////////////////////////////////////////////////////////////////////////
104 // These are used a lot, prevent unnecessary verbosity.
105 using Error
= XDebugError
;
106 using Status
= XDebugStatus
;
107 using Reason
= XDebugReason
;
109 // Compiles the given evaluation string and returns its unit. Throws
110 // XDebugError::EvaluatingCode on failure.
111 static Unit
* compile(const String
& evalStr
) {
112 auto unit
= compile_string(evalStr
.data(), evalStr
.size());
113 if (unit
== nullptr) {
114 throw_exn(Error::EvaluatingCode
);
116 unit
->setInterpretOnly();
120 // Compiles the given expression so that when evaluated the expression result
121 // is returned. Returns the corresponding unit.
122 static Unit
* compile_expression(const String
& expr
) {
124 buf
.printf("<?php return %s;", expr
.data());
125 return compile(buf
.detach());
128 // Evaluates the given unit at the given depth and returns the result or throws
129 // and error on failure.
130 static Variant
do_eval(Unit
* evalUnit
, int depth
) {
131 // Set the error reporting level to 0 to ensure non-fatal errors are hidden
132 auto& req_data
= ThreadInfo::s_threadInfo
->m_reqInjectionData
;
133 auto const old_level
= req_data
.getErrorReportingLevel();
134 req_data
.setErrorReportingLevel(0);
138 bool failure
= g_context
->evalPHPDebugger((TypedValue
*)&result
,
141 // Restore the error reporting level and then either return or throw
142 req_data
.setErrorReportingLevel(old_level
);
144 throw_exn(Error::EvaluatingCode
);
149 // Same as do_eval(const Unit*, int) except that this evaluates a string
150 static Variant
do_eval(const String
& evalStr
, int depth
) {
151 return do_eval(compile(evalStr
), depth
);
154 // Helper for the breakpoint commands that returns an xml node containing
155 // breakpoint information
156 static xdebug_xml_node
* breakpoint_xml_node(int id
,
157 const XDebugBreakpoint
& bp
) {
158 // Initialize the xml node
159 auto xml
= xdebug_xml_node_init("breakpoint");
160 xdebug_xml_add_attribute(xml
, "id", id
);
162 // It looks like php5 xdebug used to consider "temporary" as a state. It's
163 // changed everywhere except here in xdebug's code. An obvious improvement
164 // would to output an extra "temporary" attribute, but for now this logic
165 // just follows xdebug
167 xdebug_xml_add_attribute(xml
, "state", "temporary");
168 } else if (bp
.enabled
) {
169 xdebug_xml_add_attribute(xml
, "state", "enabled");
171 xdebug_xml_add_attribute(xml
, "state", "disabled");
174 // Add the hit condition and count
175 switch (bp
.hitCondition
) {
176 case XDebugBreakpoint::HitCondition::GREATER_OR_EQUAL
:
177 xdebug_xml_add_attribute(xml
, "hit_condition", ">=");
179 case XDebugBreakpoint::HitCondition::EQUAL
:
180 xdebug_xml_add_attribute(xml
, "hit_condition", "==");
182 case XDebugBreakpoint::HitCondition::MULTIPLE
:
183 xdebug_xml_add_attribute(xml
, "hit_condition", "%");
186 xdebug_xml_add_attribute(xml
, "hit_count", bp
.hitCount
);
188 // Add type specific info. Note that since we don't know the lifetime of the
189 // returned node all added breakpoint info is duplicated.
191 // Line breakpoints add a file, line, and possibly a condition
192 case XDebugBreakpoint::Type::LINE
:
193 xdebug_xml_add_attribute(xml
, "type", "line");
194 xdebug_xml_add_attribute_dup(xml
, "filename",
195 XDebugUtils::pathToUrl(bp
.fileName
).data());
196 xdebug_xml_add_attribute(xml
, "lineno", bp
.line
);
198 // Add the condition. cast is due to xml api
199 if (bp
.conditionUnit
!= nullptr) {
200 xdebug_xml_node
* expr_xml
= xdebug_xml_node_init("expression");
201 xdebug_xml_add_text(expr_xml
, xdstrdup(bp
.condition
.data()));
202 xdebug_xml_add_child(xml
, expr_xml
);
205 // Exception breakpoints just add the type
206 case XDebugBreakpoint::Type::EXCEPTION
:
207 xdebug_xml_add_attribute(xml
, "type", "exception");
209 // Call breakpoints add function + class (optionally)
210 case XDebugBreakpoint::Type::CALL
:
211 xdebug_xml_add_attribute(xml
, "type", "call");
212 xdebug_xml_add_attribute_dup(xml
, "function", bp
.funcName
.data());
213 if (!bp
.className
.isNull()) {
214 xdebug_xml_add_attribute_dup(xml
, "class",
215 bp
.className
.toString().data());
218 // Return breakpoints add function + class (optionally)
219 case XDebugBreakpoint::Type::RETURN
:
220 xdebug_xml_add_attribute(xml
, "type", "return");
221 xdebug_xml_add_attribute_dup(xml
, "function", bp
.funcName
.data());
222 if (!bp
.className
.isNull()) {
223 xdebug_xml_add_attribute_dup(xml
, "class",
224 bp
.className
.toString().data());
231 // Given a symbol name and a depth, returns the symbol's value. Throws an error
232 // if the symbol is not found
233 static Variant
find_symbol(const String
& name
, int depth
) {
234 // Retrieve the symbol by treating it as an expression.
235 // NOTE: This does not match php5 xdebug. php5 xdebug allows 'special'
236 // semantics to select the symbol. However, there is no evidence so far of an
237 // IDE using these, plus they are not documented anywhere. Thus, this
238 // implementation just accepts php expressions.
239 auto eval_unit
= compile_expression(name
);
241 // If the result is unitialized, the property must be undefined
242 auto result
= do_eval(eval_unit
, depth
);
243 if (!result
.isInitialized()) {
244 throw_exn(Error::PropertyNonExistent
);
250 const StaticString
s_GLOBALS("GLOBALS");
252 // Returns true if the given variable name is a superglobal. This matches
253 // BuiltinSymbols::IsSuperGlobal with the addition of $GLOBALS
254 bool is_superglobal(const String
& name
) {
255 return name
== s_GLOBALS
|| BuiltinSymbols::IsSuperGlobal(name
.toCppString());
258 ////////////////////////////////////////////////////////////////////////////////
260 // Returns the status of the server
262 struct StatusCmd
: XDebugCommand
{
263 StatusCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
264 : XDebugCommand(server
, cmd
, args
) {}
267 bool isValidInStatus(Status status
) const override
{
271 void handleImpl(xdebug_xml_node
& xml
) override
{
272 m_server
.addStatus(xml
);
276 ////////////////////////////////////////////////////////////////////////////////
277 // feature_get -i # -n NAME
279 struct FeatureGetCmd
: XDebugCommand
{
280 FeatureGetCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
281 : XDebugCommand(server
, cmd
, args
) {
282 // Feature name is required
283 if (args
['n'].isNull()) {
284 throw_exn(Error::InvalidArgs
);
286 m_feature
= args
['n'].toString().toCppString();
291 bool isValidInStatus(Status status
) const override
{ return true; }
293 void handleImpl(xdebug_xml_node
& xml
) override
{
294 // Set to true once we have a match. Const cast is needed due to xdebug
297 xdebug_xml_add_attribute(&xml
, "feature_name", m_feature
.c_str());
299 // Check against the defined features
300 #define FEATURE(name, supported, val, free) \
301 if (!match && m_feature == name) { \
302 if (val != nullptr) { \
303 xdebug_xml_add_text(&xml, val, free); \
305 xdebug_xml_add_attribute(&xml, "supported", supported); \
311 // Check against the commands
312 #define COMMAND(name, className) \
313 if (!match && m_feature == name) { \
314 xdebug_xml_add_text(&xml, "1", 0); \
315 xdebug_xml_add_attribute(&xml, "supported", "1"); \
321 // Unknown feature name
323 xdebug_xml_add_text(&xml
, "0", 0);
324 xdebug_xml_add_attribute(&xml
, "supported", "0");
329 std::string m_feature
;
332 ////////////////////////////////////////////////////////////////////////////////
333 // feature_set -i # -n NAME -v VALUE
335 struct FeatureSetCmd
: XDebugCommand
{
336 FeatureSetCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
337 : XDebugCommand(server
, cmd
, args
) {
338 // Feature name is required
339 if (args
['n'].isNull()) {
340 throw_exn(Error::InvalidArgs
);
342 m_feature
= args
['n'].toString().toCppString();
345 if (args
['v'].isNull()) {
346 throw_exn(Error::InvalidArgs
);
348 m_value
= args
['v'].toString().toCppString();
353 void handleImpl(xdebug_xml_node
& xml
) override
{
354 const char* value_str
= m_value
.c_str();
356 // These could be thrown into a macro, but there aren't very many cases
357 if (m_feature
== "max_children") {
358 m_server
.m_maxChildren
= strtol(value_str
, nullptr, 10);
359 } else if (m_feature
== "max_data") {
360 m_server
.m_maxData
= strtol(value_str
, nullptr, 10);
361 } else if (m_feature
== "max_depth") {
362 m_server
.m_maxDepth
= strtol(value_str
, nullptr, 10);
363 } else if (m_feature
== "show_hidden") {
364 m_server
.m_showHidden
= strtol(value_str
, nullptr, 10);
365 } else if (m_feature
== "multiple_sessions") {
366 // php5 xdebug doesn't do anything here with this value, but it is doesn't
367 // throw an error, either
368 } else if (m_feature
== "encoding") {
369 if (m_value
!= "iso-8859-1") {
370 throw_exn(Error::EncodingNotSupported
);
373 throw_exn(Error::InvalidArgs
);
376 // Const cast is needed due to xdebug xml api.
377 xdebug_xml_add_attribute(&xml
, "feature", m_feature
.c_str());
378 xdebug_xml_add_attribute(&xml
, "success", "1");
382 std::string m_feature
;
386 ////////////////////////////////////////////////////////////////////////////////
388 // Runs the program until a breakpoint is hit or the script is finished
390 struct RunCmd
: XDebugCommand
{
391 RunCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
392 : XDebugCommand(server
, cmd
, args
) {}
395 // run never responds immediately
396 bool shouldRespond() const override
{ return false; }
397 bool shouldContinue() const override
{ return true; }
399 bool isValidInStatus(Status status
) const override
{
401 status
== Status::Starting
||
402 status
== Status::Stopping
||
403 status
== Status::Break
;
406 void handleImpl(xdebug_xml_node
& xml
) override
{
407 // Get the server status
410 m_server
.getStatus(status
, reason
);
414 case XDebugStatus::Starting
:
415 case XDebugStatus::Break
:
416 m_server
.setStatus(Status::Running
, Reason::Ok
);
418 case XDebugStatus::Stopping
:
419 m_server
.setStatus(Status::Detached
, Reason::Ok
);
422 throw Exception("Command 'run' invalid in this server state.");
425 // Call the debugger hook and continue
426 phpDebuggerContinue();
430 ////////////////////////////////////////////////////////////////////////////////
432 // steps to the next statement, if there is a function call involved it will
433 // break on the first statement in that function
435 struct StepIntoCmd
: XDebugCommand
{
436 StepIntoCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
437 : XDebugCommand(server
, cmd
, args
) {}
440 // Respond on step break
441 bool shouldRespond() const override
{ return false; }
442 bool shouldContinue() const override
{ return true; }
444 bool isValidInStatus(Status status
) const override
{
446 status
== Status::Starting
||
447 status
== Status::Stopping
||
448 status
== Status::Break
;
451 void handleImpl(xdebug_xml_node
& xml
) override
{
456 ////////////////////////////////////////////////////////////////////////////////
458 // steps out of the current scope and breaks on the statement after returning
459 // from the current function.
461 struct StepOutCmd
: XDebugCommand
{
462 StepOutCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
463 : XDebugCommand(server
, cmd
, args
) {}
466 // Respond on step out break
467 bool shouldRespond() const override
{ return false; }
468 bool shouldContinue() const override
{ return true; }
470 bool isValidInStatus(Status status
) const override
{
472 status
== Status::Starting
||
473 status
== Status::Stopping
||
474 status
== Status::Break
;
477 void handleImpl(xdebug_xml_node
& xml
) override
{
478 phpDebuggerStepOut();
482 ////////////////////////////////////////////////////////////////////////////////
484 // steps to the next line. Steps over function calls.
486 struct StepOverCmd
: XDebugCommand
{
487 StepOverCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
488 : XDebugCommand(server
, cmd
, args
) {}
491 // Respond on next break
492 bool shouldRespond() const override
{ return false; }
493 bool shouldContinue() const override
{ return true; }
495 bool isValidInStatus(Status status
) const override
{
497 status
== Status::Starting
||
498 status
== Status::Stopping
||
499 status
== Status::Break
;
502 void handleImpl(xdebug_xml_node
& xml
) override
{
507 ////////////////////////////////////////////////////////////////////////////////
509 // Stops execution of the script by exiting
511 struct StopCmd
: XDebugCommand
{
512 StopCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
513 : XDebugCommand(server
, cmd
, args
) {}
516 bool isValidInStatus(Status status
) const override
{ return true; }
518 void handleImpl(xdebug_xml_node
& xml
) override
{
519 m_server
.setStatus(Status::Stopped
, Reason::Ok
);
521 // We need to throw an exception, so this needs to be sent manually
522 m_server
.addStatus(xml
);
523 m_server
.sendMessage(xml
);
524 throw XDebugExitExn();
528 ////////////////////////////////////////////////////////////////////////////////
530 // Detaches the xdebug server. In php5 xdebug this just means the user cannot
533 struct DetachCmd
: XDebugCommand
{
534 DetachCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
535 : XDebugCommand(server
, cmd
, args
) {}
538 void handleImpl(xdebug_xml_node
& xml
) override
{
539 m_server
.setStatus(Status::Detached
, Reason::Ok
);
540 m_server
.addStatus(xml
);
544 ////////////////////////////////////////////////////////////////////////////////
546 // Pauses a running request "as soon as possible". This is the synchronous
547 // implementation of the command, the asynchronous part is managed by
548 // XDebugServer::pollSocketLoop.
550 struct BreakCmd
: XDebugCommand
{
551 explicit BreakCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
552 : XDebugCommand(server
, cmd
, args
)
556 void handleImpl(xdebug_xml_node
& xml
) override
{
557 // If we got here, then we were already paused.
558 m_server
.addStatus(xml
);
562 ////////////////////////////////////////////////////////////////////////////////
563 // breakpoint_set -i # [-t TYPE] [-s STATE] [-f FILENAME] [-n LINENO]
564 // [-m FUNCTION] [-x EXCEPTION] [-h HIT_VALUE]
565 // [-o HIT_CONDITION] [-r 0|1] [-- EXPRESSION]
568 // Valid breakpoint strings
569 static const StaticString
571 s_CONDITIONAL("conditional"),
574 s_EXCEPTION("exception"),
576 s_ENABLED("enabled"),
577 s_DISABLED("disabled"),
578 s_GREATER_OR_EQUAL(">="),
582 struct BreakpointSetCmd
: XDebugCommand
{
583 BreakpointSetCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
584 : XDebugCommand(server
, cmd
, args
) {
585 auto& bp
= m_breakpoint
;
588 if (args
['t'].isNull()) {
589 throw_exn(Error::InvalidArgs
);
592 // Type: line|call|return|exception|conditional
593 auto typeName
= args
['t'].toString();
594 if (typeName
== s_LINE
|| typeName
== s_CONDITIONAL
) {
595 // Despite spec, line and conditional are the same in php5 xdebug
596 bp
.type
= XDebugBreakpoint::Type::LINE
;
597 } else if (typeName
== s_CALL
) {
598 bp
.type
= XDebugBreakpoint::Type::CALL
;
599 } else if (typeName
== s_RETURN
) {
600 bp
.type
= XDebugBreakpoint::Type::RETURN
;
601 } else if (typeName
== s_EXCEPTION
) {
602 bp
.type
= XDebugBreakpoint::Type::EXCEPTION
;
603 } else if (typeName
== s_WATCH
) {
604 throw_exn(Error::BreakpointTypeNotSupported
);
606 throw_exn(Error::InvalidArgs
);
609 // State: enabled|disabled
610 if (!args
['s'].isNull()) {
611 auto state
= args
['s'].toString();
612 if (state
== s_ENABLED
) {
614 } else if (state
== s_DISABLED
) {
617 throw_exn(Error::InvalidArgs
);
621 // Hit condition and value. php5 xdebug does not throw an error if only
622 // one of the two are provided
623 if (!args
['h'].isNull() && !args
['o'].isNull()) {
624 auto condition
= args
['o'].toString();
625 auto val
= args
['h'].toString();
626 if (condition
== s_GREATER_OR_EQUAL
) {
627 bp
.hitCondition
= XDebugBreakpoint::HitCondition::GREATER_OR_EQUAL
;
628 } else if (condition
== s_EQUAL
) {
629 bp
.hitCondition
= XDebugBreakpoint::HitCondition::EQUAL
;
630 } else if (condition
== s_MOD
) {
631 bp
.hitCondition
= XDebugBreakpoint::HitCondition::MULTIPLE
;
633 throw_exn(Error::InvalidArgs
);
635 bp
.hitValue
= strtol(val
.data(), nullptr, 10);
638 // Temporary: 0|1 -- xdebug actually just throws the passed in data into
640 if (!args
['r'].isNull()) {
641 auto temp
= args
['r'].toString();
642 m_breakpoint
.temporary
= (bool) strtol(temp
.data(), nullptr, 10);
645 // Initialize line breakpoint
646 if (bp
.type
== XDebugBreakpoint::Type::LINE
) {
648 if (args
['n'].isNull()) {
649 throw_exn(Error::InvalidArgs
);
651 bp
.line
= strtol(args
['n'].toString().data(), nullptr, 10);
653 // Grab the file, use the current if none provided
654 if (args
['f'].isNull()) {
655 auto filename
= g_context
->getContainingFileName();
656 if (filename
== staticEmptyString()) {
657 throw_exn(Error::StackDepthInvalid
);
659 bp
.fileName
= String(filename
);
661 bp
.fileName
= XDebugUtils::pathFromUrl(args
['f'].toString());
664 // Ensure consistency between filenames
665 bp
.fileName
= File::TranslatePath(bp
.fileName
);
667 // Create the condition unit if a condition string was supplied
668 if (!args
['-'].isNull()) {
669 auto condition
= StringUtil::Base64Decode(args
['-'].toString());
670 bp
.condition
= condition
;
671 bp
.conditionUnit
= compile_expression(condition
);
675 // Call and return type
676 if (bp
.type
== XDebugBreakpoint::Type::CALL
||
677 bp
.type
== XDebugBreakpoint::Type::RETURN
) {
678 if (args
['m'].isNull()) {
679 throw_exn(Error::InvalidArgs
);
681 bp
.funcName
= args
['m'].toString();
683 // This is in php5 xdebug, but not in the spec. If 'a' is passed, the
684 // value is expected to be a class name that will be prepended to the
685 // passed method name.
686 if (!args
['a'].isNull()) {
687 bp
.className
= args
['a'];
690 // Precompute full function name
691 bp
.fullFuncName
= bp
.className
.isNull() ?
692 bp
.funcName
: (bp
.className
.toString() + "::" + bp
.funcName
);
696 if (bp
.type
== XDebugBreakpoint::Type::EXCEPTION
) {
697 if (args
['x'].isNull()) {
698 throw_exn(Error::InvalidArgs
);
700 bp
.exceptionName
= args
['x'].toString();
705 ~BreakpointSetCmd() {}
707 void handleImpl(xdebug_xml_node
& xml
) override
{
708 // Add the breakpoint, write out the id
709 auto const id
= XDEBUG_ADD_BREAKPOINT(m_breakpoint
);
710 xdebug_xml_add_attribute(&xml
, "id", id
);
712 // Add the breakpoint state
713 if (m_breakpoint
.enabled
) {
714 xdebug_xml_add_attribute(&xml
, "state", "enabled");
716 xdebug_xml_add_attribute(&xml
, "state", "disabled");
721 XDebugBreakpoint m_breakpoint
;
724 ////////////////////////////////////////////////////////////////////////////////
725 // breakpoint_get -i # -d ID
726 // Returns information about the breakpoint with the given id
728 struct BreakpointGetCmd
: XDebugCommand
{
729 BreakpointGetCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
730 : XDebugCommand(server
, cmd
, args
) {
731 // Breakpoint id must be provided
732 if (args
['d'].isNull()) {
733 throw_exn(Error::InvalidArgs
);
735 m_id
= strtol(args
['d'].toString().data(), nullptr, 10);
738 ~BreakpointGetCmd() {}
740 void handleImpl(xdebug_xml_node
& xml
) override
{
741 const XDebugBreakpoint
* bp
= XDEBUG_GET_BREAKPOINT(m_id
);
743 throw_exn(Error::NoSuchBreakpoint
);
745 xdebug_xml_add_child(&xml
, breakpoint_xml_node(m_id
, *bp
));
752 ////////////////////////////////////////////////////////////////////////////////
753 // breakpoint_list -i #
754 // Returns all the registered breakpoints
756 struct BreakpointListCmd
: XDebugCommand
{
757 BreakpointListCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
758 : XDebugCommand(server
, cmd
, args
) {}
759 ~BreakpointListCmd() {}
761 bool isValidInStatus(Status status
) const override
{ return true; }
763 void handleImpl(xdebug_xml_node
& xml
) override
{
764 for (auto iter
= XDEBUG_BREAKPOINTS
.begin();
765 iter
!= XDEBUG_BREAKPOINTS
.end(); ++iter
) {
766 int id
= iter
->first
;
767 const XDebugBreakpoint
& bp
= iter
->second
;
768 xdebug_xml_add_child(&xml
, breakpoint_xml_node(id
, bp
));
773 ////////////////////////////////////////////////////////////////////////////////
774 // breakpoint_update -i # -d id [-s STATE] [-n LINE] [-h HIT_VALUE]
775 // [-o HIT_CONDITION]
776 // Updates the breakpoint with the given id using the given arguments
778 struct BreakpointUpdateCmd
: XDebugCommand
{
779 BreakpointUpdateCmd(XDebugServer
& server
,
782 : XDebugCommand(server
, cmd
, args
) {
783 // Breakpoint id must be provided
784 if (args
['d'].isNull()) {
785 throw_exn(Error::InvalidArgs
);
787 m_id
= strtol(args
['d'].toString().data(), nullptr, 10);
789 // Grab the new state if it was passed
790 if (!args
['s'].isNull()) {
791 auto state
= args
['s'].toString();
792 if (state
== s_ENABLED
|| state
== s_DISABLED
) {
793 m_enabled
= state
== s_ENABLED
;
795 throw_exn(Error::InvalidArgs
);
800 // Grab the new line if it was passed
801 if (!args
['n'].isNull()) {
803 m_line
= strtol(args
['n'].toString().data(), nullptr, 10);
806 // Grab the new hit value if it was passed
807 if (!args
['h'].isNull()) {
808 m_hasHitValue
= true;
809 m_hitValue
= strtol(args
['h'].toString().data(), nullptr, 10);
812 // Grab the hit condition if it was passed
813 if (!args
['o'].isNull()) {
814 auto condition
= args
['o'].toString();
815 if (condition
== s_GREATER_OR_EQUAL
) {
816 m_hitCondition
= XDebugBreakpoint::HitCondition::GREATER_OR_EQUAL
;
817 } else if (condition
== s_EQUAL
) {
818 m_hitCondition
= XDebugBreakpoint::HitCondition::EQUAL
;
819 } else if (condition
== s_MOD
) {
820 m_hitCondition
= XDebugBreakpoint::HitCondition::MULTIPLE
;
822 throw_exn(Error::InvalidArgs
);
824 m_hasHitCondition
= true;
828 ~BreakpointUpdateCmd() {}
830 void handleImpl(xdebug_xml_node
& xml
) override
{
831 // Need to grab the breakpoint to send back the breakpoint info
832 auto const bp
= XDEBUG_GET_BREAKPOINT(m_id
);
834 throw_exn(Error::NoSuchBreakpoint
);
837 // If any of the updates fails an error is thrown
839 !s_xdebug_breakpoints
->updateBreakpointState(m_id
, m_enabled
)) {
840 throw_exn(Error::BreakpointInvalid
);
842 if (m_hasHitCondition
&&
843 !s_xdebug_breakpoints
->updateBreakpointHitCondition(m_id
,
845 throw_exn(Error::BreakpointInvalid
);
848 !s_xdebug_breakpoints
->updateBreakpointHitValue(m_id
, m_hitValue
)) {
849 throw_exn(Error::BreakpointInvalid
);
852 !s_xdebug_breakpoints
->updateBreakpointLine(m_id
, m_line
)) {
853 throw_exn(Error::BreakpointInvalid
);
856 // Add the breakpoint info. php5 xdebug does this, the spec does
858 xdebug_xml_node
* node
= breakpoint_xml_node(m_id
, *bp
);
859 xdebug_xml_add_child(&xml
, node
);
865 // Options that can be passed
866 bool m_hasEnabled
= false;
867 bool m_hasLine
= false;
868 bool m_hasHitValue
= false;
869 bool m_hasHitCondition
= false;
871 // Valid if the corresponding boolean is true
875 XDebugBreakpoint::HitCondition m_hitCondition
;
878 ////////////////////////////////////////////////////////////////////////////////
879 // breakpoint_remove -i # -d ID
880 // Removes the breakpoint with the given id
882 struct BreakpointRemoveCmd
: XDebugCommand
{
883 BreakpointRemoveCmd(XDebugServer
& server
,
886 : XDebugCommand(server
, cmd
, args
) {
887 // Breakpoint id must be provided
888 if (args
['d'].isNull()) {
889 throw_exn(Error::InvalidArgs
);
891 m_id
= strtol(args
['d'].toString().data(), nullptr, 10);
894 ~BreakpointRemoveCmd() {}
896 void handleImpl(xdebug_xml_node
& xml
) override
{
897 auto const bp
= XDEBUG_GET_BREAKPOINT(m_id
);
899 throw_exn(Error::NoSuchBreakpoint
);
902 // spec doesn't specify this, but php5 xdebug sends back breakpoint info
903 auto node
= breakpoint_xml_node(m_id
, *bp
);
904 xdebug_xml_add_child(&xml
, node
);
906 // The breakpoint is deleted, so this has to be last
907 XDEBUG_REMOVE_BREAKPOINT(m_id
);
914 ////////////////////////////////////////////////////////////////////////////////
916 // Returns the current stack depth
918 struct StackDepthCmd
: XDebugCommand
{
919 StackDepthCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
920 : XDebugCommand(server
, cmd
, args
) {}
923 void handleImpl(xdebug_xml_node
& xml
) override
{
924 auto const depth
= XDebugUtils::stackDepth();
925 xdebug_xml_add_attribute(&xml
, "depth", depth
);
929 ////////////////////////////////////////////////////////////////////////////////
930 // stack_get -i # [-d DEPTH]
931 // Returns the stack at the given depth, or the entire stack if no depth is
934 const StaticString
s_FILE("file");
936 struct StackGetCmd
: XDebugCommand
{
937 StackGetCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
938 : XDebugCommand(server
, cmd
, args
) {
939 // Grab the optional depth argument
940 if (!args
['d'].isNull()) {
941 m_clientDepth
= strtol(args
['d'].toString().data(), nullptr, 10);
942 if (m_clientDepth
< 0 || m_clientDepth
> XDebugUtils::stackDepth()) {
943 throw_exn(Error::StackDepthInvalid
);
950 bool isValidInStatus(Status status
) const override
{ return true; }
952 void handleImpl(xdebug_xml_node
& xml
) override
{
953 // Iterate up the stack. We need to keep track of both the frame actrec and
954 // our current depth in case the client passed us a depth
957 for (const ActRec
* fp
= g_context
->getStackFrame();
958 fp
!= nullptr && (m_clientDepth
== -1 || depth
<= m_clientDepth
);
959 fp
= g_context
->getPrevVMState(fp
, &offset
), depth
++) {
960 // If a depth was provided, we're only interested in that depth
961 if (m_clientDepth
< 0 || depth
== m_clientDepth
) {
962 auto frame
= getFrame(fp
, offset
, depth
);
963 xdebug_xml_add_child(&xml
, frame
);
969 // Returns the xml node for the given stack frame. If level is non-zero,
970 // offset is the current offset within the frame
971 xdebug_xml_node
* getFrame(const ActRec
* fp
, Offset offset
, int level
) {
972 const auto func
= fp
->func();
973 const auto unit
= fp
->unit();
975 // Compute the function name. php5 xdebug includes names for each type of
976 // include, we don't have access to that
977 auto const func_name
=
978 func
->isPseudoMain() ?
979 (g_context
->getPrevVMState(fp
) == nullptr ? "{main}" : "include") :
980 func
->fullName()->data();
982 // Create the frame node
983 auto node
= xdebug_xml_node_init("stack");
984 xdebug_xml_add_attribute(node
, "where", func_name
);
985 xdebug_xml_add_attribute(node
, "level", level
);
986 xdebug_xml_add_attribute(node
, "type", "file");
988 // Grab the file/line for the frame. For level 0, this is the current
989 // file/line, for all other frames this is the stored file/line #
991 XDebugUtils::pathToUrl(String(const_cast<StringData
*>(unit
->filepath())));
992 auto line
= level
== 0 ? g_context
->getLine() : unit
->getLineNumber(offset
);
994 // Add the call file/line. Duplication is necessary due to xml api
995 xdebug_xml_add_attribute_dup(node
, "filename", file
.data());
996 xdebug_xml_add_attribute(node
, "lineno", line
);
1001 int m_clientDepth
= -1; // If >= 0, depth of the stack frame the client wants
1004 ////////////////////////////////////////////////////////////////////////////////
1005 // context_names -i # [-d DEPTH]
1006 // Returns the names of the currently available contexts at the given depth
1008 // These match php5 xdebug. Adding contexts such as class would make sense
1009 enum class XDebugContext
: int {
1010 LOCAL
= 0, // need to start from 0 for komodo
1015 struct ContextNamesCmd
: XDebugCommand
{
1016 ContextNamesCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
1017 : XDebugCommand(server
, cmd
, args
) {}
1018 ~ContextNamesCmd() {}
1020 bool isValidInStatus(Status status
) const override
{ return true; }
1022 void handleImpl(xdebug_xml_node
& xml
) override
{
1023 auto child
= xdebug_xml_node_init("context");
1024 xdebug_xml_add_attribute(child
, "name", "Locals");
1025 xdebug_xml_add_attribute(child
, "id",
1026 static_cast<int>(XDebugContext::LOCAL
));
1027 xdebug_xml_add_child(&xml
, child
);
1029 child
= xdebug_xml_node_init("context");
1030 xdebug_xml_add_attribute(child
, "name", "Superglobals");
1031 xdebug_xml_add_attribute(child
, "id",
1032 static_cast<int>(XDebugContext::SUPERGLOBALS
));
1033 xdebug_xml_add_child(&xml
, child
);
1035 child
= xdebug_xml_node_init("context");
1036 xdebug_xml_add_attribute(child
, "name", "User defined constants");
1037 xdebug_xml_add_attribute(child
, "id",
1038 static_cast<int>(XDebugContext::USER_CONSTANTS
));
1039 xdebug_xml_add_child(&xml
, child
);
1043 ////////////////////////////////////////////////////////////////////////////////
1044 // context_get -i # [-d DEPTH] [-c CONTEXT]
1045 // Returns the variables in scope within the passed context
1047 // Needed to look up user constants
1048 const StaticString
s_USER("user");
1050 struct ContextGetCmd
: XDebugCommand
{
1051 ContextGetCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
1052 : XDebugCommand(server
, cmd
, args
) {
1053 // Grab the context if it was passed
1054 if (!args
['c'].isNull()) {
1055 auto context
= args
['c'].toInt32();
1057 case static_cast<int>(XDebugContext::LOCAL
):
1058 case static_cast<int>(XDebugContext::SUPERGLOBALS
):
1059 case static_cast<int>(XDebugContext::USER_CONSTANTS
):
1060 m_context
= static_cast<XDebugContext
>(context
);
1063 throw_exn(Error::InvalidArgs
);
1067 // Grab the depth if it was provided
1068 if (!args
['d'].isNull()) {
1069 m_depth
= strtol(args
['d'].toString().data(), nullptr, 10);
1077 void handleImpl(xdebug_xml_node
& xml
) override
{
1078 // Setup the variable exporter
1079 XDebugExporter exporter
;
1080 exporter
.max_depth
= m_server
.m_maxDepth
;
1081 exporter
.max_children
= m_server
.m_maxChildren
;
1082 exporter
.max_data
= m_server
.m_maxData
;
1085 // Grab from the requested context
1086 switch (m_context
) {
1087 case XDebugContext::SUPERGLOBALS
: {
1088 // Iterate through the globals, filtering out non-superglobals
1089 Array globals
= php_globals_as_array();
1090 for (ArrayIter
iter(globals
); iter
; ++iter
) {
1091 auto const name
= iter
.first().toString();
1092 if (!is_superglobal(name
)) {
1097 xdebug_get_value_xml_node(iter
.first().toString().data(),
1098 iter
.second(), XDebugVarType::Normal
,
1100 xdebug_xml_add_child(&xml
, node
);
1104 case XDebugContext::USER_CONSTANTS
: {
1105 Array constants
= lookupDefinedConstants(true)[s_USER
].toArray();
1106 for (ArrayIter
iter(constants
); iter
; ++iter
) {
1108 xdebug_get_value_xml_node(iter
.first().toString().data(),
1109 iter
.second(), XDebugVarType::Normal
,
1111 xdebug_xml_add_child(&xml
, node
);
1115 case XDebugContext::LOCAL
: {
1116 VMRegAnchor regAnchor
;
1117 auto const fp
= g_context
->getFrameAtDepth(m_depth
);
1118 auto const vars
= getDefinedVariables(fp
);
1120 // Add each variable, filtering out superglobals
1121 for (ArrayIter
iter(vars
); iter
; ++iter
) {
1122 String name
= iter
.first().toString();
1123 if (is_superglobal(name
)) {
1128 xdebug_get_value_xml_node(name
.data(), iter
.second(),
1129 XDebugVarType::Normal
, exporter
);
1130 xdebug_xml_add_child(&xml
, node
);
1138 XDebugContext m_context
= XDebugContext::LOCAL
;
1142 ////////////////////////////////////////////////////////////////////////////////
1144 // Returns the types supported as well their corresponding xml schema
1146 // Taken from php5 xdebug
1147 // Fields are common name, lang name, and schema
1148 #define XDEBUG_TYPES_COUNT 8
1149 static const char* s_TYPEMAP
[XDEBUG_TYPES_COUNT
][3] = {
1150 {"bool", "bool", "xsd:boolean"},
1151 {"int", "int", "xsd:decimal"},
1152 {"float", "float", "xsd:double"},
1153 {"string", "string", "xsd:string"},
1154 {"null", "null", nullptr},
1155 {"hash", "array", nullptr},
1156 {"object", "object", nullptr},
1157 {"resource", "resource", nullptr}
1160 struct TypemapGetCmd
: XDebugCommand
{
1161 TypemapGetCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
1162 : XDebugCommand(server
, cmd
, args
) {}
1165 bool isValidInStatus(Status status
) const override
{ return true; }
1167 void handleImpl(xdebug_xml_node
& xml
) override
{
1169 xdebug_xml_add_attribute(&xml
, "xmlns:xsi",
1170 "http://www.w3.org/2001/XMLSchema-instance");
1171 xdebug_xml_add_attribute(&xml
, "xmlns:xsd",
1172 "http://www.w3.org/2001/XMLSchema");
1174 // Add the types. Casts are necessary due to xml api
1175 for (int i
= 0; i
< XDEBUG_TYPES_COUNT
; i
++) {
1176 auto type
= xdebug_xml_node_init("map");
1177 xdebug_xml_add_attribute(type
, "name", s_TYPEMAP
[i
][1]);
1178 xdebug_xml_add_attribute(type
, "type", s_TYPEMAP
[i
][0]);
1179 if (s_TYPEMAP
[i
][2]) {
1180 xdebug_xml_add_attribute(type
, "xsi:type", s_TYPEMAP
[i
][2]);
1182 xdebug_xml_add_child(&xml
, type
);
1187 ////////////////////////////////////////////////////////////////////////////////
1188 // property_get -i # -n LONGNAME [-d DEPTH] [-c CONTEXT] [-m MAX_DATA] [-p PAGE]
1189 // Note that the spec mentioned a 'k' and 'a', php5 xdebug does not support it
1190 // Gets the specified property value
1193 // The dbgp spec specifies property_value as property_get, except that the
1194 // entire value is always returned. But in php5 xdebug, property_value is
1195 // exactly the same as property_get, without support for the constant context.
1196 // Presumably because that context was added later, as the constant context
1197 // is left out in other arbitrary places as well. So in this implementation,
1198 // property_value is implemented as property_get
1200 struct PropertyGetCmd
: XDebugCommand
{
1201 PropertyGetCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
1202 : XDebugCommand(server
, cmd
, args
) {
1203 // A name is required
1204 if (args
['n'].isNull()) {
1205 throw_exn(Error::InvalidArgs
);
1207 m_name
= args
['n'].toString();
1209 // Grab the context if it provided
1210 if (!args
['c'].isNull()) {
1211 int context
= strtol(args
['c'].toString().data(), nullptr, 10);
1213 case static_cast<int>(XDebugContext::LOCAL
):
1214 case static_cast<int>(XDebugContext::SUPERGLOBALS
):
1215 case static_cast<int>(XDebugContext::USER_CONSTANTS
):
1216 m_context
= static_cast<XDebugContext
>(context
);
1219 throw_exn(Error::InvalidArgs
);
1223 // Grab the depth if it is provided
1224 if (!args
['d'].isNull()) {
1225 m_depth
= strtol(args
['d'].toString().data(), nullptr, 10);
1228 // Grab the page if it was provided
1229 if (!args
['p'].isNull()) {
1230 m_page
= strtol(args
['p'].toString().data(), nullptr, 10);
1233 // Grab the max data if it was provided
1234 if (!args
['m'].isNull()) {
1235 m_maxData
= strtol(args
['m'].toString().data(), nullptr, 10);
1239 ~PropertyGetCmd() {}
1241 void handleImpl(xdebug_xml_node
& xml
) override
{
1242 // Get the correct stack frame
1243 auto fp
= g_context
->getStackFrame();
1244 for (int depth
= 0; fp
!= nullptr && depth
< m_depth
;
1245 depth
++, fp
= g_context
->getPrevVMState(fp
)) {}
1247 // If we don't have an actrec, the stack depth was invalid
1248 if (fp
== nullptr) {
1249 throw_exn(Error::StackDepthInvalid
);
1252 // Setup the variable exporter
1253 XDebugExporter exporter
;
1254 exporter
.max_depth
= m_maxDepth
;
1255 exporter
.max_children
= m_maxChildren
;
1256 exporter
.max_data
= m_maxData
;
1257 exporter
.page
= m_page
;
1259 switch (m_context
) {
1260 // Globals and superglobals can be fetched by finding the symbol
1261 case XDebugContext::LOCAL
:
1262 case XDebugContext::SUPERGLOBALS
: {
1263 Variant val
= find_symbol(m_name
, m_depth
);
1264 auto node
= xdebug_get_value_xml_node(m_name
.data(), val
,
1265 XDebugVarType::Normal
,
1267 xdebug_xml_add_child(&xml
, node
);
1270 // Grab the the constant, f_constant throws a warning on failure so
1271 // we ensure it's defined before grabbing it
1272 case XDebugContext::USER_CONSTANTS
: {
1273 if (!f_defined(m_name
)) {
1274 throw_exn(Error::PropertyNonExistent
);
1277 // php5 xdebug adds "constant" facet, but this is not in the spec
1279 xdebug_get_value_xml_node(m_name
.data(), f_constant(m_name
),
1280 XDebugVarType::Constant
, exporter
);
1281 xdebug_xml_add_attribute(node
, "facet", "constant");
1282 xdebug_xml_add_child(&xml
, node
);
1289 req::root
<String
> m_name
;
1290 XDebugContext m_context
= XDebugContext::LOCAL
;
1291 int m_depth
= 0; // desired stack depth
1292 int m_maxDepth
= m_server
.m_maxDepth
; // max property depth
1293 int m_maxData
= m_server
.m_maxData
;
1294 int m_maxChildren
= m_server
.m_maxChildren
;
1298 ////////////////////////////////////////////////////////////////////////////////
1299 // property_set -i # -n LONGNAME [-d DEPTH] [-t TYPE] -- DATA
1300 // Sets the given property to the given value. In php5 xdebug, the semantics of
1301 // this command relating to whether or a not a datatype (-t) is passed are very
1302 // strange, but in this implementation the IDE can assume DATA can be any
1303 // expression and LONGNAME must be some (possibly new) variable.
1305 // Allowed datatypes for property_set
1312 struct PropertySetCmd
: XDebugCommand
{
1313 PropertySetCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
1314 : XDebugCommand(server
, cmd
, args
) {
1315 // A name is required
1316 if (args
['n'].isNull()) {
1317 throw_exn(Error::InvalidArgs
);
1319 m_symbol
= args
['n'].toString();
1321 // Data must be provided
1322 if (args
['-'].isNull()) {
1323 throw_exn(Error::InvalidArgs
);
1325 m_newValue
= StringUtil::Base64Decode(args
['-'].toString());
1327 // A datatype and depth can be provided
1329 if (!args
['d'].isNull()) {
1330 m_depth
= strtol(args
['d'].toString().data(), nullptr, 10);
1331 if (m_depth
< 0 || m_depth
> XDebugUtils::stackDepth()) {
1332 throw_exn(Error::StackDepthInvalid
);
1337 ~PropertySetCmd() {}
1339 void handleImpl(xdebug_xml_node
& xml
) override
{
1340 // Create the evaluation string buffer and store the symbol name
1342 buf
.printf("<?php %s = ", m_symbol
.data());
1344 // If a datatype was passed, add the appropriate type cast
1345 if (!m_type
.isNull()) {
1346 auto type
= m_type
.toString();
1347 if (type
== s_BOOL
) {
1348 buf
.printf("(bool) ");
1349 } else if (type
== s_INT
) {
1350 buf
.printf("(int) ");
1351 } else if (type
== s_FLOAT
) {
1352 buf
.printf("(float) ");
1353 } else if (type
== s_STRING
) {
1354 buf
.printf("(string) ");
1358 // Add the value and create the evaluation string
1359 buf
.printf("(%s);", m_newValue
.data());
1360 auto eval_str
= buf
.detach();
1362 // Perform the evaluation at the given depth. Though this is inconsistent
1363 // with errors in property_get and eval, php5 xdebug sends back success = 0
1364 // on failure, not an error.
1366 do_eval(eval_str
, m_depth
);
1367 xdebug_xml_add_attribute(&xml
, "success", "1");
1369 xdebug_xml_add_attribute(&xml
, "success", "0");
1374 req::root
<String
> m_symbol
;
1375 req::root
<String
> m_newValue
;
1376 req::root
<Variant
> m_type
; // datatype name
1377 int m_depth
= 0; // desired stack depth
1380 ////////////////////////////////////////////////////////////////////////////////
1381 // source -i # [-f FILE] [-b BEGIN] [-e END]
1382 // Grabs the given source file starting at the optionally given begin and end
1385 struct SourceCmd
: XDebugCommand
{
1386 SourceCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
1387 : XDebugCommand(server
, cmd
, args
) {
1388 // Either grab the passed filename or get the current one
1389 if (args
['f'].isNull()) {
1390 auto filename_data
= g_context
->getContainingFileName();
1391 if (filename_data
== staticEmptyString()) {
1392 throw_exn(Error::StackDepthInvalid
);
1394 m_filename
= String(filename_data
);
1396 m_filename
= XDebugUtils::pathFromUrl(args
['f'].toString());
1398 m_filename
= File::TranslatePath(m_filename
); // canonicolize path
1401 // Grab and 0-index the begin line
1402 if (!args
['b'].isNull()) {
1403 m_beginLine
= strtol(args
['b'].toString().data(), nullptr, 10) - 1;
1406 // Grab and 0-index the end line
1407 if (!args
['e'].isNull()) {
1408 m_endLine
= strtol(args
['e'].toString().data(), nullptr, 10) - 1;
1414 void handleImpl(xdebug_xml_node
& xml
) override
{
1415 // Grab the file as an array
1416 Variant file
= HHVM_FN(file
)(m_filename
);
1417 if (!file
.isArray()) {
1418 throw_exn(Error::CantOpenFile
);
1420 auto source
= file
.toArray();
1422 // Compute the begin/end line
1423 if (m_beginLine
< 0) {
1426 if (m_endLine
< 0) {
1427 m_endLine
= source
.size() - 1;
1430 // Compute the source string. The initial size is arbitrary, we just guess
1431 // 80 characters per line
1432 StringBuffer
buf((m_endLine
- m_beginLine
) * 80);
1433 ArrayIter
iter(source
); iter
.setPos(m_beginLine
);
1434 for (int i
= m_beginLine
; i
<= m_endLine
&& iter
; i
++, ++iter
) {
1435 buf
.append(iter
.second());
1437 m_source
= buf
.detach(); // To keep alive as long as command is alive
1439 // Attach the source, const cast is due to xml interface
1440 xdebug_xml_add_text_ex(&xml
, const_cast<char*>(m_source
.data()),
1441 m_source
.size(), 0, 1);
1445 req::root
<String
> m_filename
;
1446 req::root
<String
> m_source
;
1447 int m_beginLine
= 0;
1451 ////////////////////////////////////////////////////////////////////////////////
1452 // stdout -i # -c 0|1|2
1453 // Redirect or copy stdout to the client
1455 // Helper called on stdout write when we have redirected it with the stdout
1456 // command. Once installed, this continues until it is either explicitly
1457 // uninstalled, or if there is no longer an xdebug server.
1458 static void onStdoutWrite(const char* bytes
, int len
, void* copy
) {
1459 if (XDEBUG_GLOBAL(Server
) == nullptr) {
1460 g_context
->setStdout(nullptr, nullptr);
1462 XDEBUG_GLOBAL(Server
)->sendStream("stdout", bytes
, len
);
1464 // If copy is true, we also copy the data to stdout
1466 write(fileno(stdout
), bytes
, len
);
1470 struct StdoutCmd
: XDebugCommand
{
1471 StdoutCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
1472 : XDebugCommand(server
, cmd
, args
) {
1473 // "c" must be provided
1474 if (args
['c'].isNull()) {
1475 throw_exn(Error::InvalidArgs
);
1478 // Only several types of modes are allowed
1479 auto mode
= strtol(args
['c'].toString().data(), nullptr, 10);
1484 m_mode
= static_cast<Mode
>(mode
);
1487 throw_exn(Error::InvalidArgs
);
1493 void handleImpl(xdebug_xml_node
& xml
) override
{
1496 g_context
->setStdout(nullptr, nullptr);
1499 g_context
->setStdout(onStdoutWrite
, (void*) true);
1502 g_context
->setStdout(onStdoutWrite
, (void*) false);
1505 throw Exception("Invalid mode type");
1508 xdebug_xml_add_attribute(&xml
, "success", "1");
1520 ////////////////////////////////////////////////////////////////////////////////
1522 // This "required" dbgp-core feature is not implemented by php5 xdebug :)
1524 struct StderrCmd
: XDebugCommand
{
1525 StderrCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
1526 : XDebugCommand(server
, cmd
, args
) {}
1529 void handleImpl(xdebug_xml_node
& xml
) override
{
1530 xdebug_xml_add_attribute(&xml
, "success", "0");
1534 ////////////////////////////////////////////////////////////////////////////////
1535 // eval -i # [-p PAGE] -- DATA
1536 // Evaluate an expression within the given exeuction context. Note that php5
1537 // xdebug claims non-expressions are allowed in their eval code, but the
1538 // implementation calls zend_eval_string which wraps EXPR in "return EXPR;"
1540 struct EvalCmd
: XDebugCommand
{
1541 EvalCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
1542 : XDebugCommand(server
, cmd
, args
) {
1543 // An evaluation string must be provided
1544 if (args
['-'].isNull()) {
1545 throw_exn(Error::InvalidArgs
);
1547 auto encoded_expr
= args
['-'].toString();
1548 m_evalUnit
= compile_expression(StringUtil::Base64Decode(encoded_expr
));
1550 // A page can optionally be provided
1551 if (!args
['p'].isNull()) {
1552 m_page
= strtol(args
['p'].toString().data(), nullptr, 10);
1558 void handleImpl(xdebug_xml_node
& xml
) override
{
1559 auto result
= do_eval(m_evalUnit
, 0);
1561 // Construct the exporter
1562 XDebugExporter exporter
;
1563 exporter
.max_depth
= m_server
.m_maxDepth
;
1564 exporter
.max_children
= m_server
.m_maxChildren
;
1565 exporter
.max_data
= m_server
.m_maxData
;
1566 exporter
.page
= m_page
;
1568 // Create the xml node
1569 auto node
= xdebug_get_value_xml_node(nullptr, result
,
1570 XDebugVarType::Normal
,
1572 xdebug_xml_add_child(&xml
, node
);
1580 ////////////////////////////////////////////////////////////////////////////////
1581 // xcmd_profiler_name_get -i #
1582 // Returns the profiler filename if profiling has started
1584 struct ProfilerNameGetCmd
: XDebugCommand
{
1585 ProfilerNameGetCmd(XDebugServer
& server
, const String
& cmd
, const Array
& args
)
1586 : XDebugCommand(server
, cmd
, args
) {}
1587 ~ProfilerNameGetCmd() {}
1589 void handleImpl(xdebug_xml_node
& xml
) override
{
1590 Variant filename
= HHVM_FN(xdebug_get_profiler_filename
)();
1591 if (!filename
.isString()) {
1592 throw_exn(Error::ProfilingNotStarted
);
1594 xdebug_xml_add_text(&xml
, xdstrdup(filename
.toString().data()));
1598 ////////////////////////////////////////////////////////////////////////////////
1599 // XDebugCommand implementation
1601 XDebugCommand::XDebugCommand(XDebugServer
& server
,
1604 : m_server(server
), m_commandStr(cmd
.data(), cmd
.size()) {
1605 // A transaction id must be provided
1606 if (args
['i'].isNull()) {
1607 throw_exn(Error::InvalidArgs
);
1609 m_transactionId
= args
['i'].toString().toCppString();
1612 bool XDebugCommand::handle(xdebug_xml_node
& response
) {
1613 m_server
.addCommand(response
, *this);
1614 handleImpl(response
);
1615 return shouldContinue();
1618 XDebugCommand
* XDebugCommand::fromString(XDebugServer
& server
,
1619 const String
& cmdStr
,
1620 const Array
& args
) {
1621 // Match will be set true once there is a match.
1623 auto cmd_cpp
= cmdStr
.toCppString();
1625 // Check each command
1627 #define COMMAND(name, className) \
1628 if (!match && cmd_cpp == name) { \
1629 cmd = new className(server, cmdStr, args); \
1635 // php5 xdebug throws an unimplemented error when no valid match is found
1637 throw_exn(Error::Unimplemented
);
1640 // Ensure this command is valid in the given server status
1641 XDebugStatus status
;
1642 XDebugReason reason
;
1643 server
.getStatus(status
, reason
);
1644 if (!cmd
->isValidInStatus(status
)) {
1646 throw_exn(Error::CommandUnavailable
);
1651 ///////////////////////////////////////////////////////////////////////////////