diff --git a/.clang-format b/.clang-format index 4edbaa6..96df5d5 100644 --- a/.clang-format +++ b/.clang-format @@ -1,7 +1,7 @@ --- Language: Cpp # BasedOnStyle: Google -AccessModifierOffset: -2 +AccessModifierOffset: -3 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false diff --git a/config.m4 b/config.m4 index fe020fd..07afe0d 100644 --- a/config.m4 +++ b/config.m4 @@ -81,6 +81,7 @@ if test "$PHP_YASD" != "no"; then src/context.cc \ src/global.cc \ src/source_reader.cc \ + src/dbgp.cc \ src/debuger_mode_base.cc \ src/cmder_debugger.cc \ src/remote_debugger.cc \ diff --git a/include/cmder_debugger.h b/include/cmder_debugger.h index 3167843..c486930 100644 --- a/include/cmder_debugger.h +++ b/include/cmder_debugger.h @@ -62,6 +62,7 @@ class CmderDebugger: public DebuggerModeBase { void show_welcome_info(); void show_breakpoint_hit_info(); + void reload_cache_breakpoint(); int get_listsize() { return listsize; diff --git a/include/dbgp.h b/include/dbgp.h new file mode 100644 index 0000000..049646f --- /dev/null +++ b/include/dbgp.h @@ -0,0 +1,178 @@ +/* + +----------------------------------------------------------------------+ + | Yasd | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.0 of the Apache license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.apache.org/licenses/LICENSE-2.0.html | + | If you did not receive a copy of the Apache2.0 license and are unable| + | to obtain it through the world-wide-web, please send a note to | + | license@swoole.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: codinghuang | + +----------------------------------------------------------------------+ +*/ +#pragma once + +#include + +#include "thirdparty/tinyxml2/tinyxml2.h" + +namespace yasd { + +class DbgpInitElement { + public: + std::string debugger_name; + std::string debugger_version; + std::string fileuri; + std::string language; + std::string language_version; + std::string appid; + std::string idekey; + + std::string author; + std::string url; + std::string copyright; + + DbgpInitElement &set_debugger_name(std::string _debugger_name) { + debugger_name = _debugger_name; + return *this; + } + + DbgpInitElement &set_debugger_version(std::string _debugger_version) { + debugger_version = _debugger_version; + return *this; + } + + DbgpInitElement &set_fileuri(std::string _fileuri) { + fileuri = _fileuri; + return *this; + } + + DbgpInitElement &set_language(std::string _language) { + language = _language; + return *this; + } + + DbgpInitElement &set_language_version(std::string _language_version) { + language_version = _language_version; + return *this; + } + + DbgpInitElement &set_idekey(std::string _idekey) { + idekey = _idekey; + return *this; + } + + DbgpInitElement &set_appid(std::string _appid) { + appid = _appid; + return *this; + } + + DbgpInitElement &set_author(std::string _author) { + author = _author; + return *this; + } + + DbgpInitElement &set_url(std::string _url) { + url = _url; + return *this; + } + + DbgpInitElement &set_copyright(std::string _copyright) { + copyright = _copyright; + return *this; + } +}; + +class ResponseElement { + public: + std::string cmd; + int transaction_id; + + ResponseElement &set_cmd(std::string _cmd) { + cmd = _cmd; + return *this; + } + + ResponseElement &set_transaction_id(int _transaction_id) { + transaction_id = _transaction_id; + return *this; + } +}; + +class MessageElement { + public: + std::string filename; + int lineno; + + MessageElement &set_filename(std::string _filename) { + filename = _filename; + return *this; + } + + MessageElement &set_lineno(int _lineno) { + lineno = _lineno; + return *this; + } +}; + +class PropertyElement { + public: + std::string type; + std::string name; + std::string fullname; + zval *value; + int level = 0; + bool encoding = false; + + PropertyElement &set_type(std::string _type) { + type = _type; + return *this; + } + + PropertyElement &set_name(std::string _name) { + name = _name; + return *this; + } + + PropertyElement &set_fullname(std::string _fullname) { + fullname = _fullname; + return *this; + } + + PropertyElement &set_value(zval *_value) { + value = _value; + return *this; + } + + PropertyElement &set_level(int _level) { + level = _level; + return *this; + } + + PropertyElement &set_encoding(bool _encoding) { + encoding = _encoding; + return *this; + } +}; + +class Dbgp { + public: + Dbgp() {} + ~Dbgp() {} + + static std::string make_message(tinyxml2::XMLDocument *doc); + static void get_zend_string_property_doc(tinyxml2::XMLElement *root, const PropertyElement &property_element); + static void get_zend_array_child_property_doc(tinyxml2::XMLElement *child, const PropertyElement &property_element); + static void get_zend_object_child_property_doc(tinyxml2::XMLElement *child, + const PropertyElement &property_element); + static void get_init_event_doc(tinyxml2::XMLDocument *doc, const DbgpInitElement &init_element); + static void get_response_doc(tinyxml2::XMLElement *root, const ResponseElement &response_element); + static void get_property_doc(tinyxml2::XMLElement *root, const PropertyElement &property_element); + static void get_message_doc(tinyxml2::XMLDocument *doc, + const ResponseElement &response_element, + const MessageElement &message_element); +}; +} // namespace yasd diff --git a/include/remote_debugger.h b/include/remote_debugger.h index 3509315..df00faa 100644 --- a/include/remote_debugger.h +++ b/include/remote_debugger.h @@ -43,26 +43,16 @@ class RemoteDebugger : public DebuggerModeBase { std::string get_next_cmd(); int execute_cmd(); - void init_response_xml_root_node(tinyxml2::XMLElement *root, std::string cmd); - void init_xml_property_node( - tinyxml2::XMLElement *child, std::string name, zval *value, int level = 0, bool encoding = false); void init_local_variables_xml_child_node(tinyxml2::XMLElement *root); void init_superglobal_variables_xml_child_node(tinyxml2::XMLElement *root); void init_user_defined_constant_variables_xml_child_node(tinyxml2::XMLElement *root); - void init_zend_array_element_xml_property_node( - tinyxml2::XMLElement *child, std::string name, zval *value, int level = 0, bool encoding = false); - - void init_zend_object_property_xml_property_node( - tinyxml2::XMLElement *child, std::string name, zval *value, int level = 0, bool encoding = false); - public: RemoteDebugger() {} ~RemoteDebugger() {} void init(); void handle_request(const char *filename, int lineno); - std::string make_message(tinyxml2::XMLDocument *doc); ssize_t send_doc(tinyxml2::XMLDocument *doc); ssize_t send_init_event_message(); diff --git a/include/util.h b/include/util.h index 4107521..31dfd5c 100644 --- a/include/util.h +++ b/include/util.h @@ -27,8 +27,6 @@ namespace yasd { class Util { public: - static std::vector explode(const std::string &str, const std::string &delimiter); - static HashTable *get_defined_vars(); static zval *find_variable(std::string var_name); static zval *find_variable(zend_array *symbol_table, std::string var_name); @@ -52,7 +50,6 @@ class Util { static int get_prev_executed_file_lineno(); static bool is_match(std::string sub_str, std::string target_str); - static void reload_cache_breakpoint(); static void clear_breakpoint_cache_file(); static std::string get_breakpoint_cache_filename(); static void cache_breakpoint(std::string filename, int lineno); diff --git a/php_yasd.h b/php_yasd.h index b35ca8d..15a0b84 100644 --- a/php_yasd.h +++ b/php_yasd.h @@ -39,20 +39,4 @@ extern ZEND_DECLARE_MODULE_GLOBALS(yasd); #define php_yasd_fatal_error(level, fmt_str, ...) \ php_error_docref(NULL, level, (const char *) (fmt_str), ##__VA_ARGS__) -namespace yasd { namespace function { -class ReturnValue { - public: - zval value; - ReturnValue() { - value = {}; - } - ~ReturnValue() { - zval_dtor(&value); - } -}; - -ReturnValue call(const std::string &func_name, int argc, zval *argv); -} -} - #endif /* PHP_YASD_H_ */ diff --git a/src/cmder_debugger.cc b/src/cmder_debugger.cc index 61b408d..c95fc02 100644 --- a/src/cmder_debugger.cc +++ b/src/cmder_debugger.cc @@ -34,7 +34,7 @@ void CmderDebugger::init() { show_welcome_info(); - yasd::Util::reload_cache_breakpoint(); + reload_cache_breakpoint(); register_cmd_handler(); do { @@ -51,8 +51,9 @@ void CmderDebugger::handle_request(const char *filename, int lineno) { int status; std::string cmd; yasd::SourceReader reader(filename); + std::vector exploded_cmd; - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); if (get_full_name(exploded_cmd[0]) == "run" || get_full_name(exploded_cmd[0]) == "continue") { yasd::Util::printf_info(yasd::Color::YASD_ECHO_MAGENTA, "stop because of breakpoint "); @@ -101,8 +102,9 @@ int CmderDebugger::parse_run_cmd() { int CmderDebugger::parse_breakpoint_cmd() { int lineno; std::string filename; + std::vector exploded_cmd; - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); // breakpoint in current file with lineno if (exploded_cmd.size() == 2) { @@ -139,8 +141,9 @@ int CmderDebugger::parse_breakpoint_cmd() { int CmderDebugger::parse_delete_breakpoint_cmd() { int lineno; std::string filename; + std::vector exploded_cmd; - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); if (exploded_cmd.size() == 2) { filename = yasd::Util::get_executed_filename(); @@ -232,8 +235,9 @@ int CmderDebugger::parse_list_cmd() { int lineno = last_list_lineno; const char *filename = yasd::Util::get_executed_filename(); yasd::SourceReader reader(filename); + std::vector exploded_cmd; - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); // list lineno or list - if (exploded_cmd.size() == 2) { @@ -255,7 +259,9 @@ int CmderDebugger::parse_list_cmd() { } int CmderDebugger::parse_set_cmd() { - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + std::vector exploded_cmd; + + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); if (exploded_cmd[1] == "listsize") { listsize = atoi(exploded_cmd[2].c_str()); @@ -267,8 +273,9 @@ int CmderDebugger::parse_watch_cmd() { zend_function *func = EG(current_execute_data)->func; std::string var_name; yasd::WatchPointElement element; + std::vector exploded_cmd; - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); if (exploded_cmd.size() < 2) { yasd::Util::printfln_info(yasd::Color::YASD_ECHO_RED, "you should set watch point"); return RECV_CMD_AGAIN; @@ -314,8 +321,9 @@ int CmderDebugger::parse_watch_cmd() { int CmderDebugger::parse_unwatch_cmd() { zend_function *func = EG(current_execute_data)->func; + std::vector exploded_cmd; - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); if (exploded_cmd.size() < 2) { yasd::Util::printfln_info(yasd::Color::YASD_ECHO_RED, "you should set watch point"); return RECV_CMD_AGAIN; @@ -364,6 +372,37 @@ void CmderDebugger::show_welcome_info() { yasd::Util::printfln_info(YASD_ECHO_GREEN, "[You can set breakpoint now]"); } +void CmderDebugger::reload_cache_breakpoint() { + std::string content; + std::fstream file(yasd::Util::get_breakpoint_cache_filename()); + std::string filename; + int lineno; + + while (getline(file, content)) { + std::vector exploded_content; + + boost::split(exploded_content, content, boost::is_any_of(":"), boost::token_compress_on); + if (exploded_content.size() != 2) { + continue; + } + + filename = exploded_content[0]; + lineno = atoi(exploded_content[1].c_str()); + + auto iter = global->breakpoints->find(filename); + + yasd::Util::printfln_info(yasd::Color::YASD_ECHO_GREEN, "reload breakpoint at %s:%d", filename.c_str(), lineno); + + if (iter != global->breakpoints->end()) { + iter->second.insert(lineno); + } else { + std::set lineno_set; + lineno_set.insert(lineno); + global->breakpoints->insert(std::make_pair(filename, lineno_set)); + } + } +} + int CmderDebugger::execute_cmd() { std::vector exploded_cmd; diff --git a/src/dbgp.cc b/src/dbgp.cc new file mode 100644 index 0000000..dab6de3 --- /dev/null +++ b/src/dbgp.cc @@ -0,0 +1,263 @@ +/* + +----------------------------------------------------------------------+ + | Yasd | + +----------------------------------------------------------------------+ + | This source file is subject to version 2.0 of the Apache license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.apache.org/licenses/LICENSE-2.0.html | + | If you did not receive a copy of the Apache2.0 license and are unable| + | to obtain it through the world-wide-web, please send a note to | + | license@swoole.com so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: codinghuang | + +----------------------------------------------------------------------+ +*/ + +#include "./php_yasd.h" +#include "include/dbgp.h" +#include "include/base64.h" +#include "include/common.h" +#include "include/util.h" +#include "include/zend_property_info.h" + +#include + +namespace yasd { +void Dbgp::get_init_event_doc(tinyxml2::XMLDocument *doc, const DbgpInitElement &init_element) { + // https://xdebug.org/docs/dbgp#connection-initialization + + tinyxml2::XMLElement *root; + tinyxml2::XMLElement *child; + + root = doc->NewElement("init"); + doc->LinkEndChild(root); + root->SetAttribute("xmlns", "urn:debugger_protocol_v1"); + root->SetAttribute("xmlns:xdebug", "https://xdebug.org/dbgp/xdebug"); + + child = doc->NewElement("engine"); + root->InsertEndChild(child); + child->SetAttribute("version", init_element.debugger_version.c_str()); + child->InsertNewText(init_element.debugger_name.c_str())->SetCData(true); + + child = doc->NewElement("author"); + root->InsertEndChild(child); + child->InsertNewText(init_element.author.c_str())->SetCData(true); + + child = doc->NewElement("url"); + root->InsertEndChild(child); + child->InsertNewText(init_element.url.c_str())->SetCData(true); + + child = doc->NewElement("copyright"); + root->InsertEndChild(child); + child->InsertNewText(init_element.copyright.c_str())->SetCData(true); + + root->SetAttribute("fileuri", init_element.fileuri.c_str()); + root->SetAttribute("language", init_element.language.c_str()); + root->SetAttribute("xdebug:language_version", init_element.language_version.c_str()); + root->SetAttribute("protocol_version", "1.0"); + root->SetAttribute("appid", init_element.appid.c_str()); + root->SetAttribute("idekey", init_element.idekey.c_str()); +} + +void Dbgp::get_message_doc(tinyxml2::XMLDocument *doc, + const ResponseElement &response_element, + const MessageElement &message_element) { + tinyxml2::XMLElement *root; + tinyxml2::XMLElement *child; + + root = doc->NewElement("response"); + doc->LinkEndChild(root); + yasd::Dbgp::get_response_doc(root, response_element); + root->SetAttribute("status", "break"); + root->SetAttribute("reason", "ok"); + + child = root->InsertNewChildElement("xdebug:message"); + std::string filename_ = "file://" + message_element.filename; + child->SetAttribute("filename", filename_.c_str()); + child->SetAttribute("lineno", message_element.lineno); +} + +std::string Dbgp::make_message(tinyxml2::XMLDocument *doc) { + // https://xdebug.org/docs/dbgp#response + + std::string message = ""; + tinyxml2::XMLPrinter printer; + + doc->Print(&printer); + + int size = printer.CStrSize() - 1 + sizeof("\n") - 1; + message = message + std::to_string(size); + message += '\0'; + message += "\n"; + message += printer.CStr(); + message += '\0'; + + return message; +} + +void Dbgp::get_response_doc(tinyxml2::XMLElement *root, const ResponseElement &response_element) { + root->SetAttribute("xmlns", "urn:debugger_protocol_v1"); + root->SetAttribute("xmlns:xdebug", "https://xdebug.org/dbgp/xdebug"); + root->SetAttribute("command", response_element.cmd.c_str()); + root->SetAttribute("transaction_id", response_element.transaction_id); +} + +void Dbgp::get_property_doc(tinyxml2::XMLElement *root, const PropertyElement &property_element) { + if (property_element.level > YASD_G(depth)) { + return; + } + + root->SetAttribute("type", property_element.type.c_str()); + root->SetAttribute("name", property_element.name.c_str()); + root->SetAttribute("fullname", property_element.fullname.c_str()); + + switch (Z_TYPE_P(property_element.value)) { + case IS_TRUE: + root->InsertNewText("1")->SetCData(true); + break; + case IS_FALSE: + root->InsertNewText("0")->SetCData(true); + break; + case IS_NULL: + break; + case IS_LONG: + root->InsertNewText(std::to_string(Z_LVAL_P(property_element.value)).c_str())->SetCData(true); + break; + case IS_DOUBLE: + root->InsertNewText(std::to_string(Z_DVAL_P(property_element.value)).c_str())->SetCData(true); + break; + case IS_STRING: + get_zend_string_property_doc(root, property_element); + break; + case IS_ARRAY: + get_zend_array_child_property_doc(root, property_element); + break; + case IS_OBJECT: { + get_zend_object_child_property_doc(root, property_element); + break; + } + case IS_UNDEF: + root->SetAttribute("type", "uninitialized"); + break; + default: + break; + } +} + +void Dbgp::get_zend_string_property_doc(tinyxml2::XMLElement *root, const PropertyElement &property_element) { + if (property_element.encoding) { + root->SetAttribute("size", (uint64_t) Z_STRLEN_P(property_element.value)); + root->SetAttribute("encoding", "base64"); + root->InsertNewText( + base64_encode((unsigned char *) Z_STRVAL_P(property_element.value), Z_STRLEN_P(property_element.value)) + .c_str()) + ->SetCData(true); + } else { + root->InsertNewText(Z_STRVAL_P(property_element.value))->SetCData(true); + } +} + +void Dbgp::get_zend_array_child_property_doc(tinyxml2::XMLElement *child, const PropertyElement &property_element) { + zend_ulong num; + zend_string *key; + zval *val; + zend_array *ht = Z_ARRVAL_P(property_element.value); + int level = property_element.level; + + child->SetAttribute("children", ht->nNumOfElements > 0 ? "1" : "0"); + child->SetAttribute("numchildren", ht->nNumOfElements); + if (yasd_zend_hash_is_recursive(ht)) { + child->SetAttribute("recursive", 1); + } else { + ZEND_HASH_FOREACH_KEY_VAL_IND(ht, num, key, val) { + tinyxml2::XMLElement *property = child->InsertNewChildElement("property"); + std::string child_name; + std::string child_fullname; + + if (key == nullptr) { // num key + child_name = std::to_string(num); + child_fullname = property_element.fullname + "[" + child_name + "]"; + } else { // string key + child_name = ZSTR_VAL(key); + child_fullname = property_element.fullname + "[" + child_name + "]"; + } + + level++; + + yasd::PropertyElement property_element; + property_element.set_type(zend_zval_type_name(val)) + .set_name(child_name) + .set_fullname(child_fullname) + .set_value(val) + .set_level(level) + .set_encoding(true); + get_property_doc(property, property_element); + + if (level > YASD_G(depth)) { + child->DeleteChild(property); + } + level--; + } + ZEND_HASH_FOREACH_END(); + } +} + +void Dbgp::get_zend_object_child_property_doc(tinyxml2::XMLElement *child, const PropertyElement &property_element) { + zend_string *class_name; + zend_array *properties; + class_name = Z_OBJCE_P(property_element.value)->name; + zend_ulong num; + zend_string *key; + zval *val; + properties = yasd::Util::get_properties(property_element.value); + int level = property_element.level; + + child->SetAttribute("type", "object"); + child->SetAttribute("classname", ZSTR_VAL(class_name)); + child->SetAttribute("children", properties->nNumOfElements > 0 ? "1" : "0"); + child->SetAttribute("numchildren", properties->nNumOfElements); + + std::vector summary_properties_info; + + // TODO(codinghuang): may we have a better way to get private properties + void *property_info; + ZEND_HASH_FOREACH_STR_KEY_PTR(&Z_OBJCE_P(property_element.value)->properties_info, key, property_info) { + ZendPropertyInfo info; + info.property_name = key; + summary_properties_info.emplace_back(info); + } + ZEND_HASH_FOREACH_END(); + + int i = 0; + ZEND_HASH_FOREACH_KEY_VAL_IND(properties, num, key, val) { + tinyxml2::XMLElement *property = child->InsertNewChildElement("property"); + std::string child_fullname; + std::string child_name; + + ZendPropertyInfo info = summary_properties_info[i]; + key = info.property_name; + + child_name = ZSTR_VAL(key); + child_fullname = property_element.fullname + "->" + child_name; + + level++; + + yasd::PropertyElement property_element; + property_element.set_type(zend_zval_type_name(val)) + .set_name(child_name) + .set_fullname(child_fullname) + .set_value(val) + .set_level(level) + .set_encoding(true); + get_property_doc(property, property_element); + + if (level > YASD_G(depth)) { + child->DeleteChild(property); + } + level--; + i++; + } + ZEND_HASH_FOREACH_END(); +} +} // namespace yasd diff --git a/src/remote_debugger.cc b/src/remote_debugger.cc index c8294b0..1004d7c 100644 --- a/src/remote_debugger.cc +++ b/src/remote_debugger.cc @@ -17,14 +17,16 @@ #include #include +#include "./php_yasd.h" #include "include/global.h" #include "include/util.h" #include "include/common.h" #include "include/base64.h" #include "include/remote_debugger.h" +#include "include/dbgp.h" #include "include/zend_property_info.h" -#include "./php_yasd.h" +#include "thirdparty/boost/algorithm/include/boost/algorithm/string.hpp" namespace yasd { @@ -83,7 +85,9 @@ std::string RemoteDebugger::get_next_cmd() { } int RemoteDebugger::execute_cmd() { - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + std::vector exploded_cmd; + + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); transaction_id = atoi(exploded_cmd[2].c_str()); @@ -100,30 +104,19 @@ int RemoteDebugger::execute_cmd() { } void RemoteDebugger::handle_request(const char *filename, int lineno) { - // 316 - // - int status; std::unique_ptr doc(new tinyxml2::XMLDocument()); - tinyxml2::XMLElement *root; - tinyxml2::XMLElement *child; - - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + yasd::ResponseElement response_element; + yasd::MessageElement message_element; + std::vector exploded_cmd; - root = doc->NewElement("response"); - doc->LinkEndChild(root); - init_response_xml_root_node(root, exploded_cmd[0]); - root->SetAttribute("status", "break"); - root->SetAttribute("reason", "ok"); + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); - child = root->InsertNewChildElement("xdebug:message"); - std::string filename_ = "file://" + std::string(filename); - child->SetAttribute("filename", filename_.c_str()); - child->SetAttribute("lineno", lineno); + response_element.set_cmd(exploded_cmd[0]).set_transaction_id(transaction_id); + message_element.set_filename("file://" + std::string(filename)).set_lineno(lineno); + yasd::Dbgp::get_message_doc(doc.get(), response_element, message_element); send_doc(doc.get()); do { @@ -141,45 +134,27 @@ ssize_t RemoteDebugger::send_init_event_message() { std::unique_ptr doc(new tinyxml2::XMLDocument()); - tinyxml2::XMLElement *root; - tinyxml2::XMLElement *child; + yasd::DbgpInitElement init_element; - root = doc->NewElement("init"); - doc->LinkEndChild(root); - root->SetAttribute("xmlns", "urn:debugger_protocol_v1"); - root->SetAttribute("xmlns:xdebug", "https://xdebug.org/dbgp/xdebug"); - - child = doc->NewElement("engine"); - root->InsertEndChild(child); - child->SetAttribute("version", "0.1.0"); - child->InsertNewText("Yasd")->SetCData(true); - - child = doc->NewElement("author"); - root->InsertEndChild(child); - child->InsertNewText("Codinghuang")->SetCData(true); - - child = doc->NewElement("url"); - root->InsertEndChild(child); - child->InsertNewText("https://github.com/swoole/yasd")->SetCData(true); - - child = doc->NewElement("copyright"); - root->InsertEndChild(child); - child->InsertNewText("Copyright (c) 2020-2021 by Codinghuang")->SetCData(true); - - std::string fileuri = "file://" + std::string(global->entry_file); - root->SetAttribute("fileuri", fileuri.c_str()); - root->SetAttribute("language", "PHP"); - root->SetAttribute("xdebug:language_version", PHP_VERSION); - root->SetAttribute("protocol_version", "1.0"); - root->SetAttribute("appid", std::to_string(getpid()).c_str()); - root->SetAttribute("idekey", "hantaohuang"); + init_element.set_appid(std::to_string(getpid())) + .set_author("Codinghuang") + .set_copyright("Copyright (c) 2020-2021 by Codinghuang") + .set_debugger_name("Yasd") + .set_debugger_version("0.1.0") + .set_fileuri("file://" + std::string(global->entry_file)) + .set_idekey("hantaohuang") + .set_language("PHP") + .set_language_version(PHP_VERSION) + .set_url("https://github.com/swoole/yasd"); + + yasd::Dbgp::get_init_event_doc(doc.get(), init_element); return send_doc(doc.get()); } ssize_t RemoteDebugger::send_doc(tinyxml2::XMLDocument *doc) { ssize_t ret; - std::string message = make_message(doc); + std::string message = yasd::Dbgp::make_message(doc); // std::cout << message << std::endl; @@ -189,31 +164,6 @@ ssize_t RemoteDebugger::send_doc(tinyxml2::XMLDocument *doc) { return ret; } -std::string RemoteDebugger::make_message(tinyxml2::XMLDocument *doc) { - // https://xdebug.org/docs/dbgp#response - - std::string message = ""; - tinyxml2::XMLPrinter printer; - - doc->Print(&printer); - - int size = printer.CStrSize() - 1 + sizeof("\n") - 1; - message = message + std::to_string(size); - message += '\0'; - message += "\n"; - message += printer.CStr(); - message += '\0'; - - return message; -} - -void RemoteDebugger::init_response_xml_root_node(tinyxml2::XMLElement *root, std::string cmd) { - root->SetAttribute("xmlns", "urn:debugger_protocol_v1"); - root->SetAttribute("xmlns:xdebug", "https://xdebug.org/dbgp/xdebug"); - root->SetAttribute("command", cmd.c_str()); - root->SetAttribute("transaction_id", transaction_id); -} - void RemoteDebugger::init_local_variables_xml_child_node(tinyxml2::XMLElement *root) { // https://xdebug.org/docs/dbgp#properties-variables-and-values @@ -227,26 +177,29 @@ void RemoteDebugger::init_local_variables_xml_child_node(tinyxml2::XMLElement *r child = root->InsertNewChildElement("property"); zend_string *var_name = op_array->vars[i]; std::string name = "$" + std::string(ZSTR_VAL(var_name)); - std::string fullname = "$" + std::string(ZSTR_VAL(var_name)); - child->SetAttribute("name", name.c_str()); - child->SetAttribute("fullname", fullname.c_str()); + std::string fullname = std::string(ZSTR_VAL(var_name)); zval *var = yasd::Util::find_variable(ZSTR_VAL(var_name)); - if (!var) { - child->SetAttribute("type", "uninitialized"); - } else { - init_xml_property_node(child, ZSTR_VAL(var_name), var); - } + yasd::PropertyElement property_element; + property_element.set_type(zend_zval_type_name(var)) + .set_name(name) + .set_fullname(ZSTR_VAL(var_name)) + .set_value(var); + yasd::Dbgp::get_property_doc(child, property_element); i++; } if (Z_TYPE(EG(current_execute_data)->This) == IS_OBJECT) { child = root->InsertNewChildElement("property"); - child->SetAttribute("name", "$this"); - child->SetAttribute("fullname", "this"); - init_xml_property_node(child, "this", &EG(current_execute_data)->This); + + yasd::PropertyElement property_element; + property_element.set_type(zend_zval_type_name(&EG(current_execute_data)->This)) + .set_name("$this") + .set_fullname("this") + .set_value(&EG(current_execute_data)->This); + yasd::Dbgp::get_property_doc(child, property_element); } } @@ -276,172 +229,34 @@ void RemoteDebugger::init_user_defined_constant_variables_xml_child_node(tinyxml } child = root->InsertNewChildElement("property"); - child->SetAttribute("name", ZSTR_VAL(val->name)); - child->SetAttribute("fullname", ZSTR_VAL(val->name)); child->SetAttribute("facet", "constant"); - init_xml_property_node(child, ZSTR_VAL(val->name), zval_value); - } - ZEND_HASH_FOREACH_END(); - return; -} - -void RemoteDebugger::init_xml_property_node( - tinyxml2::XMLElement *child, std::string name, zval *value, int level, bool encoding) { - if (level > YASD_G(depth)) { - return; - } - switch (Z_TYPE_P(value)) { - case IS_TRUE: - child->SetAttribute("type", "bool"); - child->InsertNewText("1")->SetCData(true); - break; - case IS_FALSE: - child->SetAttribute("type", "bool"); - child->InsertNewText("0")->SetCData(true); - break; - case IS_NULL: - child->SetAttribute("type", "null"); - break; - case IS_LONG: - child->SetAttribute("type", "int"); - child->InsertNewText(std::to_string(Z_LVAL_P(value)).c_str())->SetCData(true); - break; - case IS_DOUBLE: - child->SetAttribute("type", "float"); - child->InsertNewText(std::to_string(Z_DVAL_P(value)).c_str())->SetCData(true); - break; - case IS_STRING: - child->SetAttribute("type", "string"); - if (encoding) { - child->SetAttribute("size", (uint64_t) Z_STRLEN_P(value)); - child->SetAttribute("encoding", "base64"); - child->InsertNewText(base64_encode((unsigned char *) Z_STRVAL_P(value), Z_STRLEN_P(value)).c_str()) - ->SetCData(true); - } else { - child->InsertNewText(Z_STRVAL_P(value))->SetCData(true); - } - break; - case IS_ARRAY: - init_zend_array_element_xml_property_node(child, name, value, level, encoding); - break; - case IS_OBJECT: { - init_zend_object_property_xml_property_node(child, name, value, level, encoding); - break; - } - case IS_UNDEF: - child->SetAttribute("type", "uninitialized"); - break; - default: - break; - } -} - -void RemoteDebugger::init_zend_array_element_xml_property_node( - tinyxml2::XMLElement *child, std::string name, zval *value, int level, bool encoding) { - zend_ulong num; - zend_string *key; - zval *val; - zend_array *ht = Z_ARRVAL_P(value); - - child->SetAttribute("type", "array"); - child->SetAttribute("children", ht->nNumOfElements > 0 ? "1" : "0"); - child->SetAttribute("numchildren", ht->nNumOfElements); - if (yasd_zend_hash_is_recursive(ht)) { - child->SetAttribute("recursive", 1); - } else { - ZEND_HASH_FOREACH_KEY_VAL_IND(ht, num, key, val) { - tinyxml2::XMLElement *property = child->InsertNewChildElement("property"); - std::string fullname; - std::string key_str; - - if (key == nullptr) { // num key - key_str = std::to_string(num); - property->SetAttribute("name", num); - fullname = name + "[" + std::to_string(num) + "]"; - } else { // string key - key_str = ZSTR_VAL(key); - property->SetAttribute("name", ZSTR_VAL(key)); - fullname = name + "[" + ZSTR_VAL(key) + "]"; - } - - property->SetAttribute("type", zend_zval_type_name(val)); - property->SetAttribute("fullname", fullname.c_str()); - level++; - init_xml_property_node(property, key_str, val, level, true); - if (level > YASD_G(depth)) { - child->DeleteChild(property); - } - level--; - } - ZEND_HASH_FOREACH_END(); - } -} - -void RemoteDebugger::init_zend_object_property_xml_property_node( - tinyxml2::XMLElement *child, std::string name, zval *value, int level, bool encoding) { - zend_string *class_name; - zend_array *properties; - class_name = Z_OBJCE_P(value)->name; - zend_ulong num; - zend_string *key; - zval *val; - properties = yasd::Util::get_properties(value); - - child->SetAttribute("type", "object"); - child->SetAttribute("classname", ZSTR_VAL(class_name)); - child->SetAttribute("children", properties->nNumOfElements > 0 ? "1" : "0"); - child->SetAttribute("numchildren", properties->nNumOfElements); - - std::vector summary_properties_info; - - // TODO(codinghuang): may we have a better way to get private properties - void *property_info; - ZEND_HASH_FOREACH_STR_KEY_PTR(&Z_OBJCE_P(value)->properties_info, key, property_info) { - ZendPropertyInfo info; - info.property_name = key; - summary_properties_info.emplace_back(info); - } - ZEND_HASH_FOREACH_END(); - - int i = 0; - ZEND_HASH_FOREACH_KEY_VAL_IND(properties, num, key, val) { - tinyxml2::XMLElement *property = child->InsertNewChildElement("property"); - std::string fullname; - std::string key_str; - - ZendPropertyInfo info = summary_properties_info[i]; - key = info.property_name; - - key_str = ZSTR_VAL(key); - property->SetAttribute("name", key_str.c_str()); - fullname = name + "->" + key_str; - - property->SetAttribute("fullname", fullname.c_str()); - property->SetAttribute("type", zend_zval_type_name(val)); - level++; - init_xml_property_node(property, key_str, val, level, true); - if (level > YASD_G(depth)) { - child->DeleteChild(property); - } - level--; - i++; + yasd::PropertyElement property_element; + property_element.set_type(zend_zval_type_name(zval_value)) + .set_name(ZSTR_VAL(val->name)) + .set_fullname(ZSTR_VAL(val->name)) + .set_value(zval_value); + yasd::Dbgp::get_property_doc(child, property_element); } ZEND_HASH_FOREACH_END(); + return; } int RemoteDebugger::parse_feature_set_cmd() { // https://xdebug.org/docs/dbgp#feature-set + std::vector exploded_cmd; - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); std::unique_ptr doc(new tinyxml2::XMLDocument()); - tinyxml2::XMLElement *root; + yasd::ResponseElement response_element; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "feature_set"); + + response_element.set_cmd("feature_set").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); root->SetAttribute("feature", exploded_cmd[4].c_str()); root->SetAttribute("success", 0); @@ -454,12 +269,14 @@ int RemoteDebugger::parse_stdout_cmd() { // https://xdebug.org/docs/dbgp#stdout-stderr std::unique_ptr doc(new tinyxml2::XMLDocument()); - tinyxml2::XMLElement *root; + yasd::ResponseElement response_element; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "stdout"); + + response_element.set_cmd("stdout").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); root->SetAttribute("success", 0); send_doc(doc.get()); @@ -471,12 +288,14 @@ int RemoteDebugger::parse_status_cmd() { // https://xdebug.org/docs/dbgp#status std::unique_ptr doc(new tinyxml2::XMLDocument()); - tinyxml2::XMLElement *root; + yasd::ResponseElement response_element; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "status"); + + response_element.set_cmd("status").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); root->SetAttribute("status", "starting"); root->SetAttribute("reason", "ok"); @@ -489,7 +308,9 @@ int RemoteDebugger::parse_eval_cmd() { // https://xdebug.org/docs/dbgp#eval zval ret_zval; - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + std::vector exploded_cmd; + + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); if (exploded_cmd[3] != "--") { return yasd::DebuggerModeBase::FAILED; @@ -502,16 +323,20 @@ int RemoteDebugger::parse_eval_cmd() { yasd::Util::eval(const_cast(eval_str.c_str()), &ret_zval, const_cast("yasd://debug-eval")); std::unique_ptr doc(new tinyxml2::XMLDocument()); - tinyxml2::XMLElement *root; tinyxml2::XMLElement *child; + yasd::ResponseElement response_element; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "eval"); + response_element.set_cmd("eval").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); child = root->InsertNewChildElement("property"); - init_xml_property_node(child, "", &ret_zval); + + yasd::PropertyElement property_element; + property_element.set_type(zend_zval_type_name(&ret_zval)).set_name("").set_fullname("").set_value(&ret_zval); + yasd::Dbgp::get_property_doc(child, property_element); send_doc(doc.get()); @@ -521,7 +346,9 @@ int RemoteDebugger::parse_eval_cmd() { int RemoteDebugger::parse_breakpoint_list_cmd() { // https://xdebug.org/docs/dbgp#id7 - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + std::vector exploded_cmd; + + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); if (exploded_cmd[0] != "breakpoint_list") { return yasd::DebuggerModeBase::FAILED; } @@ -530,12 +357,13 @@ int RemoteDebugger::parse_breakpoint_list_cmd() { } std::unique_ptr doc(new tinyxml2::XMLDocument()); - tinyxml2::XMLElement *root; + yasd::ResponseElement response_element; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "breakpoint_list"); + response_element.set_cmd("breakpoint_list").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); send_doc(doc.get()); @@ -545,7 +373,9 @@ int RemoteDebugger::parse_breakpoint_list_cmd() { int RemoteDebugger::parse_breakpoint_set_cmd() { // https://xdebug.org/docs/dbgp#id3 - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + std::vector exploded_cmd; + + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); if (exploded_cmd[0] != "breakpoint_set") { return yasd::DebuggerModeBase::FAILED; } @@ -580,10 +410,13 @@ int RemoteDebugger::parse_breakpoint_set_cmd() { std::unique_ptr doc(new tinyxml2::XMLDocument()); tinyxml2::XMLElement *root; + yasd::ResponseElement response_element; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "breakpoint_set"); + + response_element.set_cmd("breakpoint_set").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); root->SetAttribute("id", breakpoint_admin_add()); send_doc(doc.get()); @@ -596,10 +429,13 @@ int RemoteDebugger::parse_breakpoint_set_exception_cmd() { std::unique_ptr doc(new tinyxml2::XMLDocument()); tinyxml2::XMLElement *root; + yasd::ResponseElement response_element; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "breakpoint_set"); + + response_element.set_cmd("breakpoint_set").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); root->SetAttribute("id", breakpoint_admin_add()); send_doc(doc.get()); @@ -621,10 +457,13 @@ int RemoteDebugger::parse_stack_get_cmd() { std::unique_ptr doc(new tinyxml2::XMLDocument()); tinyxml2::XMLElement *root; tinyxml2::XMLElement *child; + yasd::ResponseElement response_element; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "stack_get"); + + response_element.set_cmd("stack_get").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); root->SetAttribute("id", breakpoint_admin_add()); child = root->InsertNewChildElement("stack"); @@ -660,11 +499,14 @@ int RemoteDebugger::parse_context_names_cmd() { std::unique_ptr doc(new tinyxml2::XMLDocument()); tinyxml2::XMLElement *root; tinyxml2::XMLElement *child; + yasd::ResponseElement response_element; + int id = 0; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "context_names"); + response_element.set_cmd("context_names").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); root->SetAttribute("id", breakpoint_admin_add()); child = root->InsertNewChildElement("context"); @@ -687,7 +529,9 @@ int RemoteDebugger::parse_context_names_cmd() { int RemoteDebugger::parse_context_get_cmd() { // https://xdebug.org/docs/dbgp#context-get - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); + std::vector exploded_cmd; + + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); int context_id; if (exploded_cmd[0] != "context_get") { return yasd::DebuggerModeBase::FAILED; @@ -696,10 +540,12 @@ int RemoteDebugger::parse_context_get_cmd() { std::unique_ptr doc(new tinyxml2::XMLDocument()); tinyxml2::XMLElement *root; + yasd::ResponseElement response_element; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "context_get"); + response_element.set_cmd("context_get").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); root->SetAttribute("context", 0); if (context_id == LOCALS) { @@ -721,8 +567,10 @@ int RemoteDebugger::parse_context_get_cmd() { int RemoteDebugger::parse_property_get_cmd() { // https://xdebug.org/docs/dbgp#property-get-property-set-property-value - auto exploded_cmd = yasd::Util::explode(last_cmd, " "); - std::string name; + std::vector exploded_cmd; + + boost::split(exploded_cmd, last_cmd, boost::is_any_of(" "), boost::token_compress_on); + std::string fullname; zval *property; if (exploded_cmd[0] != "property_get") { @@ -732,39 +580,35 @@ int RemoteDebugger::parse_property_get_cmd() { std::unique_ptr doc(new tinyxml2::XMLDocument()); tinyxml2::XMLElement *root; tinyxml2::XMLElement *child; + yasd::ResponseElement response_element; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "property_get"); - name = yasd::Util::get_option_value(exploded_cmd, "-n"); + response_element.set_cmd("property_get").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); + + fullname = yasd::Util::get_option_value(exploded_cmd, "-n"); // vscode has double quotes, but PhpStorm does not - if (name.front() == '"') { - name.erase(0, 1); + if (fullname.front() == '"') { + fullname.erase(0, 1); } - if (name.back() == '"') { - name.pop_back(); + if (fullname.back() == '"') { + fullname.pop_back(); } - // auto exploded_name = yasd::Util::explode(name, "->"); - - // if (Z_TYPE(EG(current_execute_data)->This) == IS_OBJECT) { - // zobj = &EG(current_execute_data)->This; - // } else { - // zobj = yasd::Util::find_variable(exploded_name[0]); - // } - - property = yasd::Util::fetch_zval_by_fullname(name); - - // for (auto iter = exploded_name.begin() + 1; iter != exploded_name.end(); iter++) { - // property = yasd_zend_read_property(Z_OBJCE_P(zobj), zobj, iter->c_str(), iter->length(), 1); - // zobj = property; - // } + property = yasd::Util::fetch_zval_by_fullname(fullname); child = root->InsertNewChildElement("property"); - init_xml_property_node(child, name, property); + + yasd::PropertyElement property_element; + property_element.set_type(zend_zval_type_name(property)) + .set_name(fullname) + .set_fullname(fullname) + .set_value(property); + yasd::Dbgp::get_property_doc(child, property_element); send_doc(doc.get()); @@ -778,10 +622,12 @@ int RemoteDebugger::parse_stop_cmd() { std::unique_ptr doc(new tinyxml2::XMLDocument()); tinyxml2::XMLElement *root; + yasd::ResponseElement response_element; root = doc->NewElement("response"); doc->LinkEndChild(root); - init_response_xml_root_node(root, "stop"); + response_element.set_cmd("stop").set_transaction_id(transaction_id); + yasd::Dbgp::get_response_doc(root, response_element); root->SetAttribute("status", "stopped"); root->SetAttribute("reason", "ok"); diff --git a/src/util.cc b/src/util.cc index 0833b67..57ea499 100644 --- a/src/util.cc +++ b/src/util.cc @@ -28,38 +28,6 @@ YASD_EXTERN_C_END namespace yasd { -std::vector Util::explode(const std::string &target, const std::string &delimiter) { - std::vector arr; - - int str_len = target.length(); - int del_len = delimiter.length(); - - if (del_len == 0) { - return arr; - } - - int i = 0; - int k = 0; - - while (i < str_len) { - int j = 0; - while (i + j < str_len && j < del_len && target[i + j] == delimiter[j]) { - j++; - } - - if (j == del_len) { - arr.emplace_back(target.substr(k, i - k)); - i += del_len; - k = i; - } else { - i++; - } - } - - arr.emplace_back(target.substr(k, i - k)); - return arr; -} - HashTable *Util::get_defined_vars() { HashTable *symbol_table; @@ -69,28 +37,11 @@ HashTable *Util::get_defined_vars() { } zval *Util::find_variable(std::string var_name) { - zval *var; HashTable *defined_vars; defined_vars = get_defined_vars(); - var = zend_hash_str_find(defined_vars, var_name.c_str(), var_name.length()); - - // not define variable - if (!var) { - return nullptr; - } - - while (Z_TYPE_P(var) == IS_INDIRECT) { - var = Z_INDIRECT_P(var); - } - - // the statement that defines the variable has not yet been executed - if (Z_TYPE_P(var) == IS_UNDEF) { - return nullptr; - } - - return var; + return find_variable(defined_vars, var_name); } zval *Util::find_variable(zend_array *symbol_table, std::string var_name) { @@ -111,11 +62,6 @@ zval *Util::find_variable(zend_array *symbol_table, std::string var_name) { var = Z_INDIRECT_P(var); } - // the statement that defines the variable has not yet been executed - if (Z_TYPE_P(var) == IS_UNDEF) { - return nullptr; - } - return var; } @@ -280,35 +226,6 @@ bool Util::is_match(std::string sub_str, std::string target_str) { return true; } -void Util::reload_cache_breakpoint() { - std::string content; - std::fstream file(yasd::Util::get_breakpoint_cache_filename()); - std::string filename; - int lineno; - - while (getline(file, content)) { - auto exploded_content = explode(content, ":"); - if (exploded_content.size() != 2) { - continue; - } - - filename = exploded_content[0]; - lineno = atoi(exploded_content[1].c_str()); - - auto iter = global->breakpoints->find(filename); - - yasd::Util::printfln_info(yasd::Color::YASD_ECHO_GREEN, "reload breakpoint at %s:%d", filename.c_str(), lineno); - - if (iter != global->breakpoints->end()) { - iter->second.insert(lineno); - } else { - std::set lineno_set; - lineno_set.insert(lineno); - global->breakpoints->insert(std::make_pair(filename, lineno_set)); - } - } -} - void Util::clear_breakpoint_cache_file() { // create file and clear file std::ofstream file(yasd::Util::get_breakpoint_cache_filename()); diff --git a/test.php b/test.php index 51b1d5a..aaad0f6 100644 --- a/test.php +++ b/test.php @@ -63,7 +63,8 @@ public function __construct($a) $this->foo2 = new Foo2(); } } - +$b = true; +$b = false; $i = 0; $j = 1; $j = 2;