Don't log an error when xdebug stops the request
[hiphop-php.git] / hphp / runtime / ext / xdebug / xdebug_command.cpp
blob641b70947b5144c0cd51d3b34afae2e905869f93
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
34 namespace HPHP {
36 ////////////////////////////////////////////////////////////////////////////////
37 // Commands
39 // COMMAND(NAME, CLASS)
40 // NAME is the command name
41 // CLASS is the corresponding class
42 #define COMMANDS \
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 ////////////////////////////////////////////////////////////////////////////////
73 // Features
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
81 #define FEATURES \
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 ////////////////////////////////////////////////////////////////////////////////
102 // Helpers
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();
117 return unit;
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) {
123 StringBuffer buf;
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);
136 // Do the eval
137 Variant result;
138 bool failure = g_context->evalPHPDebugger((TypedValue*)&result,
139 evalUnit, depth);
141 // Restore the error reporting level and then either return or throw
142 req_data.setErrorReportingLevel(old_level);
143 if (failure) {
144 throw_exn(Error::EvaluatingCode);
146 return result;
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
166 if (bp.temporary) {
167 xdebug_xml_add_attribute(xml, "state", "temporary");
168 } else if (bp.enabled) {
169 xdebug_xml_add_attribute(xml, "state", "enabled");
170 } else {
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", ">=");
178 break;
179 case XDebugBreakpoint::HitCondition::EQUAL:
180 xdebug_xml_add_attribute(xml, "hit_condition", "==");
181 break;
182 case XDebugBreakpoint::HitCondition::MULTIPLE:
183 xdebug_xml_add_attribute(xml, "hit_condition", "%");
184 break;
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.
190 switch (bp.type) {
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);
204 break;
205 // Exception breakpoints just add the type
206 case XDebugBreakpoint::Type::EXCEPTION:
207 xdebug_xml_add_attribute(xml, "type", "exception");
208 break;
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());
217 break;
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());
226 break;
228 return xml;
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);
246 return result;
249 // $GLOBALS variable
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 ////////////////////////////////////////////////////////////////////////////////
259 // status -i #
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) {}
265 ~StatusCmd() {}
267 bool isValidInStatus(Status status) const override {
268 return true;
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();
289 ~FeatureGetCmd() {}
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
295 // xml api.
296 bool match = false;
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); \
306 match = true; \
308 FEATURES
309 #undef FEATURE
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"); \
316 match = true; \
318 COMMANDS
319 #undef COMMAND
321 // Unknown feature name
322 if (!match) {
323 xdebug_xml_add_text(&xml, "0", 0);
324 xdebug_xml_add_attribute(&xml, "supported", "0");
328 private:
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();
344 // Value is required
345 if (args['v'].isNull()) {
346 throw_exn(Error::InvalidArgs);
348 m_value = args['v'].toString().toCppString();
351 ~FeatureSetCmd() {}
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);
372 } else {
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");
381 private:
382 std::string m_feature;
383 std::string m_value;
386 ////////////////////////////////////////////////////////////////////////////////
387 // run -i #
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) {}
393 ~RunCmd() {}
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 {
400 return
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
408 XDebugStatus status;
409 XDebugReason reason;
410 m_server.getStatus(status, reason);
412 // Modify the status
413 switch (status) {
414 case XDebugStatus::Starting:
415 case XDebugStatus::Break:
416 m_server.setStatus(Status::Running, Reason::Ok);
417 break;
418 case XDebugStatus::Stopping:
419 m_server.setStatus(Status::Detached, Reason::Ok);
420 break;
421 default:
422 throw Exception("Command 'run' invalid in this server state.");
425 // Call the debugger hook and continue
426 phpDebuggerContinue();
430 ////////////////////////////////////////////////////////////////////////////////
431 // step_into -i #
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) {}
438 ~StepIntoCmd() {}
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 {
445 return
446 status == Status::Starting ||
447 status == Status::Stopping ||
448 status == Status::Break;
451 void handleImpl(xdebug_xml_node& xml) override {
452 phpDebuggerStepIn();
456 ////////////////////////////////////////////////////////////////////////////////
457 // step_out -i #
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) {}
464 ~StepOutCmd() {}
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 {
471 return
472 status == Status::Starting ||
473 status == Status::Stopping ||
474 status == Status::Break;
477 void handleImpl(xdebug_xml_node& xml) override {
478 phpDebuggerStepOut();
482 ////////////////////////////////////////////////////////////////////////////////
483 // step_over -i #
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) {}
489 ~StepOverCmd() {}
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 {
496 return
497 status == Status::Starting ||
498 status == Status::Stopping ||
499 status == Status::Break;
502 void handleImpl(xdebug_xml_node& xml) override {
503 phpDebuggerNext();
507 ////////////////////////////////////////////////////////////////////////////////
508 // stop -i #
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) {}
514 ~StopCmd() {}
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 ////////////////////////////////////////////////////////////////////////////////
529 // detach -i #
530 // Detaches the xdebug server. In php5 xdebug this just means the user cannot
531 // input commands.
533 struct DetachCmd : XDebugCommand {
534 DetachCmd(XDebugServer& server, const String& cmd, const Array& args)
535 : XDebugCommand(server, cmd, args) {}
536 ~DetachCmd() {}
538 void handleImpl(xdebug_xml_node& xml) override {
539 m_server.setStatus(Status::Detached, Reason::Ok);
540 m_server.addStatus(xml);
544 ////////////////////////////////////////////////////////////////////////////////
545 // break -i #
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)
554 ~BreakCmd() {}
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]
566 // Adds a breakpoint
568 // Valid breakpoint strings
569 static const StaticString
570 s_LINE("line"),
571 s_CONDITIONAL("conditional"),
572 s_CALL("call"),
573 s_RETURN("return"),
574 s_EXCEPTION("exception"),
575 s_WATCH("watch"),
576 s_ENABLED("enabled"),
577 s_DISABLED("disabled"),
578 s_GREATER_OR_EQUAL(">="),
579 s_EQUAL("=="),
580 s_MOD("%");
582 struct BreakpointSetCmd : XDebugCommand {
583 BreakpointSetCmd(XDebugServer& server, const String& cmd, const Array& args)
584 : XDebugCommand(server, cmd, args) {
585 auto& bp = m_breakpoint;
587 // Type is required
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);
605 } else {
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) {
613 bp.enabled = true;
614 } else if (state == s_DISABLED) {
615 bp.enabled = false;
616 } else {
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;
632 } else {
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
639 // strtol.
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) {
647 // Grab the 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);
660 } else {
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);
695 // Exception type
696 if (bp.type == XDebugBreakpoint::Type::EXCEPTION) {
697 if (args['x'].isNull()) {
698 throw_exn(Error::InvalidArgs);
700 bp.exceptionName = args['x'].toString();
701 return;
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");
715 } else {
716 xdebug_xml_add_attribute(&xml, "state", "disabled");
720 private:
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);
742 if (bp == nullptr) {
743 throw_exn(Error::NoSuchBreakpoint);
745 xdebug_xml_add_child(&xml, breakpoint_xml_node(m_id, *bp));
748 private:
749 int m_id;
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,
780 const String& cmd,
781 const Array& args)
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;
794 } else {
795 throw_exn(Error::InvalidArgs);
797 m_hasEnabled = true;
800 // Grab the new line if it was passed
801 if (!args['n'].isNull()) {
802 m_hasLine = true;
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;
821 } else {
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);
833 if (bp == nullptr) {
834 throw_exn(Error::NoSuchBreakpoint);
837 // If any of the updates fails an error is thrown
838 if (m_hasEnabled &&
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,
844 m_hitCondition)) {
845 throw_exn(Error::BreakpointInvalid);
847 if (m_hasHitValue &&
848 !s_xdebug_breakpoints->updateBreakpointHitValue(m_id, m_hitValue)) {
849 throw_exn(Error::BreakpointInvalid);
851 if (m_hasLine &&
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
857 // not specify this.
858 xdebug_xml_node* node = breakpoint_xml_node(m_id, *bp);
859 xdebug_xml_add_child(&xml, node);
862 private:
863 int m_id;
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
872 bool m_enabled;
873 int m_line;
874 int m_hitValue;
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,
884 const String& cmd,
885 const Array& args)
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);
898 if (bp == nullptr) {
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);
910 private:
911 int m_id;
914 ////////////////////////////////////////////////////////////////////////////////
915 // stack_depth -i #
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) {}
921 ~StackDepthCmd() {}
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
932 // provided
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);
948 ~StackGetCmd() {}
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
955 Offset offset;
956 int depth = 0;
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);
968 private:
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 #
990 auto file =
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);
997 return node;
1000 private:
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
1011 SUPERGLOBALS = 1,
1012 USER_CONSTANTS = 2
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();
1056 switch (context) {
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);
1061 break;
1062 default:
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);
1070 } else {
1071 m_depth = 0;
1075 ~ContextGetCmd() {}
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;
1083 exporter.page = 0;
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)) {
1093 continue;
1096 auto node =
1097 xdebug_get_value_xml_node(iter.first().toString().data(),
1098 iter.second(), XDebugVarType::Normal,
1099 exporter);
1100 xdebug_xml_add_child(&xml, node);
1102 break;
1104 case XDebugContext::USER_CONSTANTS: {
1105 Array constants = lookupDefinedConstants(true)[s_USER].toArray();
1106 for (ArrayIter iter(constants); iter; ++iter) {
1107 auto node =
1108 xdebug_get_value_xml_node(iter.first().toString().data(),
1109 iter.second(), XDebugVarType::Normal,
1110 exporter);
1111 xdebug_xml_add_child(&xml, node);
1113 break;
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)) {
1124 continue;
1127 auto node =
1128 xdebug_get_value_xml_node(name.data(), iter.second(),
1129 XDebugVarType::Normal, exporter);
1130 xdebug_xml_add_child(&xml, node);
1132 break;
1137 private:
1138 XDebugContext m_context = XDebugContext::LOCAL;
1139 int m_depth = 0;
1142 ////////////////////////////////////////////////////////////////////////////////
1143 // typemap_get -i #
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) {}
1163 ~TypemapGetCmd() {}
1165 bool isValidInStatus(Status status) const override { return true; }
1167 void handleImpl(xdebug_xml_node& xml) override {
1168 // Add the schema
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
1192 // 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);
1212 switch (context) {
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);
1217 break;
1218 default:
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,
1266 exporter);
1267 xdebug_xml_add_child(&xml, node);
1268 break;
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
1278 auto node =
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);
1283 break;
1288 private:
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;
1295 int m_page = 0;
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
1306 const StaticString
1307 s_BOOL("bool"),
1308 s_INT("int"),
1309 s_FLOAT("float"),
1310 s_STRING("string");
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
1328 m_type = args['t'];
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
1341 StringBuffer buf;
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.
1365 try {
1366 do_eval(eval_str, m_depth);
1367 xdebug_xml_add_attribute(&xml, "success", "1");
1368 } catch (...) {
1369 xdebug_xml_add_attribute(&xml, "success", "0");
1373 private:
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
1383 // lines.
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);
1395 } else {
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;
1412 ~SourceCmd() {}
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) {
1424 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);
1444 private:
1445 req::root<String> m_filename;
1446 req::root<String> m_source;
1447 int m_beginLine = 0;
1448 int m_endLine = -1;
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
1465 if ((bool) copy) {
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);
1480 switch (mode) {
1481 case MODE_DISABLE:
1482 case MODE_COPY:
1483 case MODE_REDIRECT:
1484 m_mode = static_cast<Mode>(mode);
1485 break;
1486 default:
1487 throw_exn(Error::InvalidArgs);
1491 ~StdoutCmd() {}
1493 void handleImpl(xdebug_xml_node& xml) override {
1494 switch (m_mode) {
1495 case MODE_DISABLE:
1496 g_context->setStdout(nullptr, nullptr);
1497 break;
1498 case MODE_COPY:
1499 g_context->setStdout(onStdoutWrite, (void*) true);
1500 break;
1501 case MODE_REDIRECT:
1502 g_context->setStdout(onStdoutWrite, (void*) false);
1503 break;
1504 default:
1505 throw Exception("Invalid mode type");
1508 xdebug_xml_add_attribute(&xml, "success", "1");
1511 private:
1512 enum Mode {
1513 MODE_DISABLE = 0,
1514 MODE_COPY = 1,
1515 MODE_REDIRECT = 2
1517 Mode m_mode;
1520 ////////////////////////////////////////////////////////////////////////////////
1521 // stderr -i #
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) {}
1527 ~StderrCmd() {}
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);
1556 ~EvalCmd() {}
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,
1571 exporter);
1572 xdebug_xml_add_child(&xml, node);
1575 private:
1576 Unit* m_evalUnit;
1577 int m_page = 0;
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,
1602 const String& cmd,
1603 const Array& args)
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.
1622 auto match = false;
1623 auto cmd_cpp = cmdStr.toCppString();
1625 // Check each command
1626 XDebugCommand* cmd;
1627 #define COMMAND(name, className) \
1628 if (!match && cmd_cpp == name) { \
1629 cmd = new className(server, cmdStr, args); \
1630 match = true; \
1632 COMMANDS
1633 #undef COMMAND
1635 // php5 xdebug throws an unimplemented error when no valid match is found
1636 if (!match) {
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)) {
1645 delete cmd;
1646 throw_exn(Error::CommandUnavailable);
1648 return cmd;
1651 ///////////////////////////////////////////////////////////////////////////////