PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/hphp/runtime/debugger/break_point.cpp

http://github.com/facebook/hiphop-php
C++ | 1122 lines | 987 code | 64 blank | 71 comment | 243 complexity | 4d76a521a4be87ad69b3fcf4b4dfe528 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-2-Clause, BSD-3-Clause, MPL-2.0-no-copyleft-exception, MIT, LGPL-2.0, Apache-2.0
  1. /*
  2. +----------------------------------------------------------------------+
  3. | HipHop for PHP |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
  6. +----------------------------------------------------------------------+
  7. | This source file is subject to version 3.01 of the PHP license, |
  8. | that is bundled with this package in the file LICENSE, and is |
  9. | available through the world-wide-web at the following url: |
  10. | http://www.php.net/license/3_01.txt |
  11. | If you did not receive a copy of the PHP license and are unable to |
  12. | obtain it through the world-wide-web, please send a note to |
  13. | license@php.net so we can mail you a copy immediately. |
  14. +----------------------------------------------------------------------+
  15. */
  16. #include "hphp/runtime/debugger/break_point.h"
  17. #include <vector>
  18. #include <folly/Conv.h>
  19. #include "hphp/runtime/debugger/debugger.h"
  20. #include "hphp/runtime/debugger/debugger_proxy.h"
  21. #include "hphp/runtime/debugger/debugger_thrift_buffer.h"
  22. #include "hphp/runtime/base/preg.h"
  23. #include "hphp/runtime/base/execution-context.h"
  24. #include "hphp/runtime/base/stat-cache.h"
  25. #include "hphp/runtime/vm/jit/translator-inline.h"
  26. #include "hphp/runtime/base/comparisons.h"
  27. #include "hphp/runtime/ext/generator/ext_generator.h"
  28. namespace HPHP { namespace Eval {
  29. ///////////////////////////////////////////////////////////////////////////////
  30. TRACE_SET_MOD(debugger);
  31. int InterruptSite::getFileLen() const {
  32. TRACE(2, "InterruptSite::getFileLen\n");
  33. if (m_file.empty()) {
  34. getFile();
  35. }
  36. return m_file.size();
  37. }
  38. std::string InterruptSite::desc() const {
  39. TRACE(2, "InterruptSite::desc\n");
  40. std::string ret;
  41. if (m_error.isNull()) {
  42. ret = "Break";
  43. } else if (m_error.isObject()) {
  44. ret = "Exception thrown";
  45. } else {
  46. ret = "Error occurred";
  47. }
  48. const char *cls = getClass();
  49. const char *func = getFunction();
  50. if (func && *func) {
  51. ret += " at ";
  52. if (cls && *cls) {
  53. ret += cls;
  54. ret += "::";
  55. }
  56. ret += func;
  57. ret += "()";
  58. }
  59. std::string file = getFile();
  60. int line0 = getLine0();
  61. if (line0) {
  62. ret += " on line " + folly::to<std::string>(line0);
  63. if (!file.empty()) {
  64. ret += " of " + file;
  65. }
  66. } else if (!file.empty()) {
  67. ret += " in " + file;
  68. }
  69. return ret;
  70. }
  71. InterruptSite::InterruptSite(bool hardBreakPoint, const Variant& error)
  72. : m_error(error), m_activationRecord(nullptr),
  73. m_callingSite(nullptr), m_class(nullptr),
  74. m_file((StringData*)nullptr),
  75. m_line0(0), m_char0(0), m_line1(0), m_char1(0),
  76. m_offset(kInvalidOffset), m_unit(nullptr), m_valid(false),
  77. m_funcEntry(false) {
  78. TRACE(2, "InterruptSite::InterruptSite\n");
  79. #define bail_on(c) if (c) { return; }
  80. auto const context = g_context.getNoCheck();
  81. ActRec *fp = vmfp();
  82. bail_on(!fp);
  83. if (hardBreakPoint && fp->skipFrame()) {
  84. // for hard breakpoint, the fp is for an extension function,
  85. // so we need to construct the site on the caller
  86. Offset offset;
  87. fp = context->getPrevVMStateSkipFrame(fp, &offset);
  88. m_offset = fp->unit()->offsetOf(skipCall(fp->unit()->at(offset)));
  89. } else {
  90. auto const *pc = vmpc();
  91. auto f = fp->m_func;
  92. bail_on(!f);
  93. m_unit = f->unit();
  94. bail_on(!m_unit);
  95. m_offset = m_unit->offsetOf(pc);
  96. auto base = f->isGenerator()
  97. ? BaseGenerator::userBase(f)
  98. : f->base();
  99. if (m_offset == base) {
  100. m_funcEntry = true;
  101. }
  102. }
  103. #undef bail_on
  104. this->Initialize(fp);
  105. }
  106. // Only used to look for callers by function name. No need to
  107. // to retrieve source line information for this kind of site.
  108. InterruptSite::InterruptSite(ActRec *fp, Offset offset, const Variant& error)
  109. : m_error(error), m_activationRecord(nullptr),
  110. m_callingSite(nullptr), m_class(nullptr),
  111. m_file((StringData*)nullptr),
  112. m_line0(0), m_char0(0), m_line1(0), m_char1(0),
  113. m_offset(offset), m_unit(nullptr), m_valid(false),
  114. m_funcEntry(false), m_builtin(false) {
  115. TRACE(2, "InterruptSite::InterruptSite(fp)\n");
  116. this->Initialize(fp);
  117. }
  118. void InterruptSite::Initialize(ActRec *fp) {
  119. TRACE(2, "InterruptSite::Initialize\n");
  120. #define bail_on(c) if (c) { return; }
  121. assertx(fp);
  122. m_activationRecord = fp;
  123. bail_on(!fp->m_func);
  124. m_unit = fp->m_func->unit();
  125. bail_on(!m_unit);
  126. m_file = String(StrNR{m_unit->filepath()});
  127. if (m_unit->getSourceLoc(m_offset, m_sourceLoc)) {
  128. m_line0 = m_sourceLoc.line0;
  129. m_char0 = m_sourceLoc.char0;
  130. m_line1 = m_sourceLoc.line1;
  131. m_char1 = m_sourceLoc.char1;
  132. }
  133. m_function = fp->m_func->name()->data();
  134. if (fp->m_func->preClass()) {
  135. m_class = fp->m_func->preClass()->name()->data();
  136. } else {
  137. m_class = "";
  138. }
  139. m_builtin = fp->m_func->isBuiltin();
  140. #undef bail_on
  141. m_valid = true;
  142. }
  143. // Returns an Interrupt site for the function that called the
  144. // function that contains this site. This site retains ownership
  145. // of the returned site and it will be deleted when this site
  146. // is destructed, so do not hold on to the returned object for
  147. // longer than there is a guarantee that this site will be alive.
  148. const InterruptSite *InterruptSite::getCallingSite() const {
  149. if (m_callingSite) return m_callingSite.get();
  150. auto const context = g_context.getNoCheck();
  151. Offset parentOffset;
  152. auto parentFp = context->getPrevVMState(m_activationRecord, &parentOffset);
  153. if (parentFp == nullptr) return nullptr;
  154. m_callingSite = req::make_unique<InterruptSite>(parentFp, parentOffset,
  155. m_error);
  156. return m_callingSite.get();
  157. }
  158. ///////////////////////////////////////////////////////////////////////////////
  159. const char *BreakPointInfo::ErrorClassName = "@";
  160. const char *BreakPointInfo::GetInterruptName(InterruptType interrupt) {
  161. TRACE(2, "BreakPointInfo::GetInterruptName\n");
  162. switch (interrupt) {
  163. case RequestStarted: return "start of request";
  164. case RequestEnded: return "end of request or start of psp";
  165. case PSPEnded: return "end of psp";
  166. default:
  167. assertx(false);
  168. break;
  169. }
  170. return nullptr;
  171. }
  172. BreakPointInfo::BreakPointInfo(bool regex, State state,
  173. const std::string &file, int line)
  174. : m_index(0), m_state(state), m_valid(true),
  175. m_interruptType(BreakPointReached),
  176. m_file(file), m_line1(line), m_line2(line),
  177. m_regex(regex), m_check(false) {
  178. TRACE(2, "BreakPointInfo::BreakPointInfo..const std::string &file, int)\n");
  179. createIndex();
  180. }
  181. BreakPointInfo::BreakPointInfo(bool regex, State state,
  182. InterruptType interrupt,
  183. const std::string &url)
  184. : m_index(0), m_state(state), m_valid(true), m_interruptType(interrupt),
  185. m_line1(0), m_line2(0), m_url(url),
  186. m_regex(regex), m_check(false) {
  187. TRACE(2, "BreakPointInfo::BreakPointInfo..const std::string &url)\n");
  188. createIndex();
  189. }
  190. BreakPointInfo::BreakPointInfo(bool regex, State state,
  191. InterruptType interrupt,
  192. const std::string &exp,
  193. const std::string &file)
  194. : m_index(0), m_state(state), m_valid(true), m_interruptType(interrupt),
  195. m_line1(0), m_line2(0),
  196. m_regex(regex), m_check(false) {
  197. TRACE(2, "BreakPointInfo::BreakPointInfo..const std::string &file)\n");
  198. assertx(m_interruptType != ExceptionHandler); // Server-side only.
  199. if (m_interruptType == ExceptionThrown) {
  200. parseExceptionThrown(exp);
  201. } else {
  202. parseBreakPointReached(exp, file);
  203. }
  204. createIndex();
  205. }
  206. static int s_max_breakpoint_index = 0;
  207. void BreakPointInfo::createIndex() {
  208. TRACE(2, "BreakPointInfo::createIndex\n");
  209. m_index = ++s_max_breakpoint_index;
  210. }
  211. BreakPointInfo::~BreakPointInfo() {
  212. TRACE(2, "BreakPointInfo::~BreakPointInfo\n");
  213. if (m_index && m_index == s_max_breakpoint_index) {
  214. --s_max_breakpoint_index;
  215. }
  216. }
  217. void BreakPointInfo::sendImpl(int version, DebuggerThriftBuffer &thrift) {
  218. TRACE(2, "BreakPointInfo::sendImpl\n");
  219. thrift.write((int8_t)m_state);
  220. if (version >= 1) thrift.write((int8_t)m_bindState);
  221. thrift.write((int8_t)m_interruptType);
  222. thrift.write(m_file);
  223. thrift.write(m_line1);
  224. thrift.write(m_line2);
  225. thrift.write(m_namespace);
  226. thrift.write(m_class);
  227. thrift.write(m_funcs);
  228. thrift.write(m_url);
  229. thrift.write(m_regex);
  230. thrift.write(m_check);
  231. thrift.write(m_clause);
  232. thrift.write(m_output);
  233. thrift.write(m_exceptionClass);
  234. thrift.write(m_exceptionObject);
  235. }
  236. void BreakPointInfo::recvImpl(int version, DebuggerThriftBuffer &thrift) {
  237. TRACE(2, "BreakPointInfo::recvImpl\n");
  238. int8_t tmp;
  239. thrift.read(tmp); m_state = (State)tmp;
  240. if (version >= 1) {
  241. thrift.read(tmp); m_bindState = (BindState)tmp;
  242. }
  243. thrift.read(tmp); m_interruptType = (InterruptType)tmp;
  244. thrift.read(m_file);
  245. thrift.read(m_line1);
  246. thrift.read(m_line2);
  247. thrift.read(m_namespace);
  248. thrift.read(m_class);
  249. thrift.read(m_funcs);
  250. thrift.read(m_url);
  251. thrift.read(m_regex);
  252. thrift.read(m_check);
  253. thrift.read(m_clause);
  254. thrift.read(m_output);
  255. thrift.read(m_exceptionClass);
  256. thrift.read(m_exceptionObject);
  257. }
  258. void BreakPointInfo::setClause(const std::string &clause, bool check) {
  259. TRACE(2, "BreakPointInfo::setClause\n");
  260. m_clause = clause;
  261. m_check = check;
  262. }
  263. void BreakPointInfo::transferStack(BreakPointInfoPtr bpi) {
  264. if (bpi->m_stack.empty()) return;
  265. m_stack.splice(m_stack.begin(), bpi->m_stack);
  266. }
  267. // Disables the breakpoint at the given stack level.
  268. // Following this call, BreakPointInfo::breakable will return false until
  269. // a subsequent call to BreakPointInfo::setBreakable with a lower or equal
  270. // stack level.
  271. void BreakPointInfo::unsetBreakable(int stackDepth, Offset offset) {
  272. TRACE(2, "BreakPointInfo::unsetBreakable\n");
  273. if (m_stack.empty() || m_stack.back().first < stackDepth) {
  274. m_stack.push_back(std::make_pair(stackDepth, offset));
  275. }
  276. }
  277. // Enables the breakpoint at the given stack level.
  278. // Following this call, BreakPointInfo::breakable will return true until
  279. // a subsequent call to BreakPointInfo::unsetBreakable with the same or
  280. // higher stack level.
  281. void BreakPointInfo::setBreakable(int stackDepth) {
  282. TRACE(2, "BreakPointInfo::setBreakable\n");
  283. while (!m_stack.empty() && m_stack.back().first >= stackDepth) {
  284. m_stack.pop_back();
  285. }
  286. }
  287. // Returns true if this breakpoint is enabled at the given stack level.
  288. bool BreakPointInfo::breakable(int stackDepth, Offset offset) const {
  289. TRACE(2, "BreakPointInfo::breakable\n");
  290. if (!m_stack.empty() && m_stack.back().first >= stackDepth) {
  291. if (m_stack.back().first == stackDepth && m_stack.back().second >= offset) {
  292. // We assume that the only way to ask this question for the same
  293. // stack level and offset, is for the execution to have come back
  294. // here after executing the operation at offset, but without
  295. // executing any other operations in the interpreter.
  296. return true;
  297. }
  298. return false;
  299. } else {
  300. return true;
  301. }
  302. }
  303. void BreakPointInfo::toggle() {
  304. TRACE(2, "BreakPointInfo::toggle\n");
  305. switch (m_state) {
  306. case Always: setState(Once); break;
  307. case Once: setState(Disabled); break;
  308. case Disabled: setState(Always); break;
  309. default:
  310. assertx(false);
  311. break;
  312. }
  313. }
  314. bool BreakPointInfo::valid() {
  315. TRACE(2, "BreakPointInfo::valid\n");
  316. if (m_valid) {
  317. switch (m_interruptType) {
  318. case BreakPointReached:
  319. if (!getFuncName().empty()) {
  320. if (!m_file.empty() || m_line1 != 0) {
  321. return false;
  322. }
  323. } else {
  324. if (m_file.empty() || m_line1 == 0 || m_line2 != m_line1) {
  325. return false;
  326. }
  327. }
  328. if (m_regex) {
  329. return false;
  330. }
  331. return (m_line1 && m_line2) || !m_file.empty() || !m_funcs.empty();
  332. case ExceptionThrown:
  333. return !m_class.empty();
  334. case RequestStarted:
  335. case RequestEnded:
  336. case PSPEnded:
  337. return true;
  338. default:
  339. break;
  340. }
  341. }
  342. return false;
  343. }
  344. bool BreakPointInfo::same(BreakPointInfoPtr bpi) {
  345. TRACE(2, "BreakPointInfo::same\n");
  346. return desc() == bpi->desc();
  347. }
  348. // Checks if the interrupt type and site matches this breakpoint.
  349. // Does not run any code.
  350. bool BreakPointInfo::match(DebuggerProxy &proxy, InterruptType interrupt,
  351. InterruptSite &site) {
  352. return match(proxy, interrupt, site, false);
  353. }
  354. // Checks if the interrupt type and site matches this breakpoint.
  355. // Evaluates the breakpoint's conditional clause if present.
  356. // This can cause side effects.
  357. bool BreakPointInfo::cmatch(DebuggerProxy &proxy, InterruptType interrupt,
  358. InterruptSite &site) {
  359. return match(proxy, interrupt, site, true);
  360. }
  361. bool BreakPointInfo::match(DebuggerProxy &proxy, InterruptType interrupt,
  362. InterruptSite &site, bool evalClause) {
  363. TRACE(2, "BreakPointInfo::match\n");
  364. if (m_interruptType == interrupt) {
  365. switch (interrupt) {
  366. case RequestStarted:
  367. case RequestEnded:
  368. case PSPEnded:
  369. return
  370. checkUrl(site.url());
  371. case ExceptionThrown:
  372. return
  373. checkExceptionOrError(site.getError()) &&
  374. checkUrl(site.url()) && (!evalClause || checkClause(proxy));
  375. case BreakPointReached:
  376. {
  377. bool match =
  378. Match(site.getFile(), site.getFileLen(), m_file, m_regex, false) &&
  379. checkLines(site.getLine0()) && checkStack(site) &&
  380. checkUrl(site.url()) && (!evalClause || checkClause(proxy));
  381. if (!getFuncName().empty()) {
  382. // function entry breakpoint
  383. match = match && site.funcEntry();
  384. }
  385. return match;
  386. }
  387. default:
  388. break;
  389. }
  390. }
  391. return false;
  392. }
  393. std::string BreakPointInfo::state(bool padding) const {
  394. TRACE(2, "BreakPointInfo::state\n");
  395. switch (m_state) {
  396. case Always: return padding ? "ALWAYS " : "ALWAYS" ;
  397. case Once: return padding ? "ONCE " : "ONCE" ;
  398. case Disabled: return padding ? "DISABLED" : "DISABLED";
  399. default:
  400. assertx(false);
  401. break;
  402. }
  403. return "";
  404. }
  405. std::string BreakPointInfo::regex(const std::string &name) const {
  406. TRACE(7, "BreakPointInfo::regex\n");
  407. if (m_regex) {
  408. return "regex{" + name + "}";
  409. }
  410. return name;
  411. }
  412. std::string BreakPointInfo::getNamespace() const {
  413. TRACE(7, "BreakPointInfo::getNamespace\n");
  414. if (!m_funcs.empty()) {
  415. return m_funcs[0]->m_namespace;
  416. }
  417. return "";
  418. }
  419. std::string BreakPointInfo::getClass() const {
  420. TRACE(7, "BreakPointInfo::getClass\n");
  421. if (!m_funcs.empty()) {
  422. return m_funcs[0]->m_class;
  423. }
  424. return "";
  425. }
  426. std::string BreakPointInfo::getFunction() const {
  427. TRACE(7, "BreakPointInfo::getFunction\n");
  428. if (!m_funcs.empty()) {
  429. return m_funcs[0]->m_function;
  430. }
  431. return "";
  432. }
  433. std::string BreakPointInfo::getFuncName() const {
  434. TRACE(7, "BreakPointInfo::getFuncName\n");
  435. if (!m_funcs.empty()) {
  436. return m_funcs[0]->getName();
  437. }
  438. return "";
  439. }
  440. std::string BreakPointInfo::site() const {
  441. TRACE(7, "BreakPointInfo::site\n");
  442. std::string ret;
  443. std::string preposition = "at ";
  444. if (!m_funcs.empty()) {
  445. ret = m_funcs[0]->site(preposition);
  446. for (unsigned i = 1; i < m_funcs.size(); i++) {
  447. ret += " called by ";
  448. std::string tmp;
  449. ret += m_funcs[i]->site(tmp);
  450. }
  451. }
  452. if (!m_file.empty() || m_line1) {
  453. if (!ret.empty()) {
  454. ret += " ";
  455. } else {
  456. preposition = "";
  457. }
  458. if (m_line1) {
  459. ret += "on line " + folly::to<std::string>(m_line1);
  460. if (!m_file.empty()) {
  461. ret += " of " + m_file;
  462. }
  463. } else {
  464. ret += "in " + m_file;
  465. }
  466. }
  467. return preposition + ret;
  468. }
  469. std::string BreakPointInfo::descBreakPointReached() const {
  470. TRACE(2, "BreakPointInfo::descBreakPointReached\n");
  471. std::string ret;
  472. for (unsigned i = 0; i < m_funcs.size(); i++) {
  473. ret += (i == 0 ? "upon entering " : " called by ");
  474. ret += m_funcs[i]->desc(this);
  475. }
  476. if (!m_file.empty() || m_line1 || m_line2) {
  477. if (!ret.empty()) {
  478. ret += " ";
  479. }
  480. if (m_line1 || m_line2) {
  481. if (m_line1 == m_line2) {
  482. ret += "on line " + folly::to<std::string>(m_line1);
  483. } else if (m_line2 == -1) {
  484. ret += "between line " + folly::to<std::string>(m_line1) + " and end";
  485. } else {
  486. ret += "between line " + folly::to<std::string>(m_line1) +
  487. " and line " + folly::to<std::string>(m_line2);
  488. }
  489. if (!m_file.empty()) {
  490. ret += " of " + regex(m_file);
  491. } else {
  492. ret += " of any file";
  493. }
  494. } else {
  495. ret += "on any lines in " + regex(m_file);
  496. }
  497. }
  498. return ret;
  499. }
  500. std::string BreakPointInfo::descExceptionThrown() const {
  501. TRACE(2, "BreakPointInfo::descExceptionThrown\n");
  502. std::string ret;
  503. if (!m_namespace.empty() || !m_class.empty()) {
  504. if (m_class == ErrorClassName) {
  505. ret = "right after an error";
  506. } else {
  507. ret = "right before throwing ";
  508. if (!m_class.empty()) {
  509. if (!m_namespace.empty()) {
  510. ret += regex(m_namespace) + "::";
  511. }
  512. ret += regex(m_class);
  513. } else {
  514. ret += "any exceptions in namespace " + regex(m_namespace);
  515. }
  516. }
  517. }
  518. return ret;
  519. }
  520. std::string BreakPointInfo::desc() const {
  521. TRACE(2, "BreakPointInfo::desc\n");
  522. std::string ret;
  523. switch (m_interruptType) {
  524. case BreakPointReached:
  525. ret = descBreakPointReached();
  526. break;
  527. case ExceptionThrown:
  528. ret = descExceptionThrown();
  529. break;
  530. default:
  531. ret = GetInterruptName((InterruptType)m_interruptType);
  532. break;
  533. }
  534. if (!m_url.empty()) {
  535. ret += " when request is " + regex(m_url);
  536. }
  537. if (!m_clause.empty()) {
  538. if (m_check) {
  539. ret += " if " + m_clause;
  540. } else {
  541. ret += " && " + m_clause;
  542. }
  543. }
  544. return ret;
  545. }
  546. void mangleXhpName(const std::string &source, std::string &target) {
  547. auto oldLen = source.length();
  548. size_t newLen = 0;
  549. size_t index = 0;
  550. for (; index < oldLen; index++) {
  551. auto ch = source[index];
  552. if (ch != ':' && ch != '-') continue;
  553. newLen = 4+index;
  554. break;
  555. }
  556. if (newLen == 0) {
  557. target = source;
  558. return;
  559. }
  560. for (; index < oldLen; index++) {
  561. if (source[index] == ':') newLen += 2; else newLen +=1;
  562. }
  563. target.clear();
  564. target.reserve(newLen);
  565. target.append("xhp_");
  566. for (index = 0; index < oldLen; index++) {
  567. auto ch = source[index];
  568. if (ch == '-') {
  569. target.push_back('_');
  570. } else if (ch == ':') {
  571. if (index > 0) {
  572. target.push_back('_');
  573. target.push_back('_');
  574. }
  575. } else {
  576. target.push_back(ch);
  577. }
  578. }
  579. }
  580. int32_t scanName(const std::string &str, int32_t offset) {
  581. auto len = str.length();
  582. assertx(0 <= offset && offset <= len);
  583. while (offset < len) {
  584. char ch = str[offset];
  585. if (ch == ':' || ch == '\\' || ch == ',' || ch == '(' || ch == '=' ||
  586. ch == '@') {
  587. if (offset+1 >= len) return offset;
  588. char ch1 = str[offset+1];
  589. if (ch == ':') {
  590. if (ch1 == ':' || ('0' <= ch1 && ch1 <= '9')) return offset;
  591. } else if (ch == '(') {
  592. if (ch1 == ')') return offset;
  593. } else if (ch == '=') {
  594. if (ch1 == '>') return offset;
  595. } else {
  596. assert (ch == '\\' || ch == ',' || ch == '@');
  597. return offset;
  598. }
  599. }
  600. offset++;
  601. }
  602. return offset;
  603. }
  604. int32_t scanNumber(const std::string &str, int32_t offset, int32_t& value) {
  605. value = 0;
  606. auto len = str.length();
  607. assertx(0 <= offset && offset < len);
  608. while (offset < len) {
  609. char ch = str[offset];
  610. if (ch < '0' || ch > '9') return offset;
  611. value = value*10 + (ch - '0');
  612. offset++;
  613. }
  614. return offset;
  615. }
  616. int32_t BreakPointInfo::parseFileLocation(const std::string &str,
  617. int32_t offset) {
  618. auto len = str.length();
  619. assertx(0 <= offset && offset < len);
  620. auto offset1 = scanNumber(str, offset, m_line1);
  621. if (offset1 == offset) return offset; //Did not find a number
  622. m_line2 = m_line1; //so that we always have a range
  623. if (offset1 >= len) return len; //Nothing follows the number
  624. auto ch = str[offset1];
  625. if (ch == '-') {
  626. if (offset1+1 >= len) return offset; //Invalid file location
  627. auto offset2 = scanNumber(str, offset1+1, m_line2);
  628. if (offset1+1 == offset2) return offset; //Invalid file location
  629. return offset2;
  630. }
  631. return offset1;
  632. }
  633. //( letter | underscore ) #( letter | digit | underscore | extended_ascii ),
  634. //extended_ascii::=char_nbr(127)..char_nbr(255),
  635. static bool isValidIdentifier(const std::string &str) {
  636. auto len = str.length();
  637. for (int32_t index = 0; index < len; index++) {
  638. char ch = str[index];
  639. if (('A' <= ch && ch <= 'Z') || ('a' <= ch && ch <= 'z') || ch == '_') {
  640. continue;
  641. }
  642. if (index == 0) return false;
  643. if (('0' <= ch && ch <= '9') || ch >= 127) {
  644. continue;
  645. }
  646. return false;
  647. }
  648. return true;
  649. }
  650. /* The parser accepts the following syntax, which harks back to pre VM days
  651. (all components are optional, as long as there is at least one component):
  652. {file location},{call}=>{call}()@{url}
  653. {call}=>{call}(),{file location}@{url}
  654. file location: {file}:{line1}-{line2}
  655. call: \{namespace}\{cls}::{func}
  656. Currently semantic checks will disallow expressions that specify
  657. both file locations and calls.
  658. */
  659. void BreakPointInfo::parseBreakPointReached(const std::string &exp,
  660. const std::string &file) {
  661. TRACE(2, "BreakPointInfo::parseBreakPointReached\n");
  662. std::string name;
  663. auto len = exp.length();
  664. auto offset0 = 0;
  665. //Look for leading number by itself
  666. auto offset1 = scanNumber(exp, offset0, m_line1);
  667. if (offset1 == len) {
  668. m_line2 = m_line1;
  669. m_file = file;
  670. return;
  671. }
  672. // Skip over a leading backslash
  673. if (len > 0 && exp[0] == '\\') offset0++;
  674. offset1 = scanName(exp, offset0);
  675. // check that exp starts with a file or method name
  676. if (offset1 == offset0) goto returnInvalid;
  677. name = exp.substr(offset0, offset1-offset0);
  678. if (offset0 == 0) {
  679. // parse {file location} if appropriate
  680. if (offset1 < len && exp[offset1] == ',') {
  681. m_file = name;
  682. name.clear();
  683. offset1 += 1;
  684. } else if (offset1 < len-1 && exp[offset1] == ':' &&
  685. exp[offset1+1] != ':') {
  686. m_file = name;
  687. name.clear();
  688. offset1 += 1;
  689. auto offset2 = parseFileLocation(exp, offset1);
  690. // check for {file}:{something that is not a number}
  691. if (offset2 == offset1) goto returnInvalid;
  692. offset1 = offset2;
  693. if (offset1 >= len) return; // file location without anything else
  694. if (exp[offset1] == '@') goto parseUrl; // file location followed by url
  695. // check for {file}{ something other than @ or :}
  696. if (exp[offset1] != ',') goto returnInvalid;
  697. offset1 += 1;
  698. }
  699. }
  700. // parse {func}() or {func}=>{func}() or {func}=>{func}=>{func}() and so on
  701. while (true) {
  702. if (name.empty()) {
  703. if (len > offset1 && exp[offset1] == '\\') offset1++;
  704. auto offset2 = scanName(exp, offset1);
  705. // check for {something other than a name}
  706. if (offset2 == offset1) goto returnInvalid;
  707. name = exp.substr(offset1, offset2-offset1);
  708. offset1 = offset2;
  709. }
  710. while (offset1 < len && exp[offset1] == '\\') {
  711. if (!m_namespace.empty()) m_namespace += "\\";
  712. if (!isValidIdentifier(name)) goto returnInvalid;
  713. m_namespace += name;
  714. offset1 += 1;
  715. auto offset2 = scanName(exp, offset1);
  716. // check for {namespace}\{something that is not a name}
  717. if (offset2 == offset1) goto returnInvalid;
  718. name = exp.substr(offset1, offset2-offset1);
  719. offset1 = offset2;
  720. }
  721. if (offset1 < len-1 && exp[offset1] == ':' && exp[offset1+1] == ':') {
  722. m_class = name;
  723. offset1 += 2;
  724. auto offset2 = scanName(exp, offset1);
  725. // check for {namespace}\{class}::{something that is not a name}
  726. if (offset2 == offset1) goto returnInvalid;
  727. name = exp.substr(offset1, offset2-offset1);
  728. offset1 = offset2;
  729. }
  730. // Now we have a namespace, class and func name.
  731. // The namespace only or the namespace and class might be empty.
  732. auto pfunc = std::make_shared<DFunctionInfo>();
  733. if (m_class.empty()) {
  734. if (!isValidIdentifier(name)) goto returnInvalid;
  735. if (m_namespace.empty()) {
  736. pfunc->m_function = name;
  737. } else {
  738. // Yes this does seem beyond strange, but that is what the PHP parser
  739. // does when it sees a function declared inside a namespace, so we
  740. // too have to pretend there is no namespace here. At some point
  741. // the parser hack may have to go away. At that stage, this code
  742. // will have to change, as well as other parts of the debugger.
  743. pfunc->m_function = m_namespace + "\\" + name;
  744. }
  745. } else {
  746. mangleXhpName(m_class, pfunc->m_class);
  747. if (!isValidIdentifier(pfunc->m_class)) goto returnInvalid;
  748. if (!m_namespace.empty()) {
  749. // Emulate parser hack. See longer comment above.
  750. pfunc->m_class = m_namespace + "\\" + pfunc->m_class;
  751. }
  752. if (!isValidIdentifier(name)) goto returnInvalid;
  753. pfunc->m_function = name;
  754. }
  755. m_funcs.insert(m_funcs.begin(), pfunc);
  756. m_namespace.clear();
  757. m_class.clear();
  758. name.clear();
  759. // If we are now at () we skip over it and terminate the loop
  760. if (offset1 < len && exp[offset1] == '(') {
  761. // check for {func}{(}{not )}
  762. if (offset1+1 >= len || exp[offset1+1] != ')') goto returnInvalid;
  763. offset1 += 2;
  764. break; // parsed the last (perhaps only) call in a function call chain
  765. }
  766. // If we are now at => we need to carry on with the loop
  767. if (offset1 < len-1 && exp[offset1] == '=' && exp[offset1+1] == '>') {
  768. offset1 += 2;
  769. continue;
  770. }
  771. goto returnInvalid; // {func calls}{not () or =>}
  772. }
  773. if (m_file.empty()) {
  774. if (offset1 < len && exp[offset1] == ',') {
  775. auto offset2 = scanName(exp, ++offset1);
  776. // check for {func calls}:{not a filename}
  777. if (offset2 == offset1) goto returnInvalid;
  778. m_file = exp.substr(offset1, offset2-offset1);
  779. offset1 = offset2;
  780. if (offset1 < len && exp[offset1] == ':') {
  781. offset2 = parseFileLocation(exp, offset1+1);
  782. // check for {file}:{something that is not a number}
  783. if (offset2 == offset2+1) goto returnInvalid;
  784. offset1 = offset2;
  785. }
  786. }
  787. }
  788. parseUrl:
  789. if (offset1 < len-2 && exp[offset1] == '@') {
  790. offset1++;
  791. m_url = exp.substr(offset1, len-offset1);
  792. } else {
  793. // check for unparsed characters at end of exp
  794. if (offset1 != len) goto returnInvalid;
  795. }
  796. return;
  797. returnInvalid:
  798. m_valid = false;
  799. }
  800. void BreakPointInfo::parseExceptionThrown(const std::string &exp) {
  801. TRACE(2, "BreakPointInfo::parseExceptionThrown\n");
  802. std::string name;
  803. auto len = exp.length();
  804. auto offset0 = 0;
  805. // Skip over a leading backslash
  806. if (len > 0 && exp[0] == '\\') offset0++;
  807. auto offset1 = scanName(exp, offset0);
  808. // check that exp starts with a name
  809. if (offset1 == offset0) goto returnInvalid;
  810. name = exp.substr(offset0, offset1-offset0);
  811. if (name.empty()) {
  812. if (len > offset1 && exp[offset1] == '\\') offset1++;
  813. auto offset2 = scanName(exp, offset1);
  814. // check for {something other than a name}
  815. if (offset2 == offset1) goto returnInvalid;
  816. name = exp.substr(offset1, offset2-offset1);
  817. }
  818. while (offset1 < len && exp[offset1] == '\\') {
  819. if (!m_namespace.empty()) m_namespace += "\\";
  820. m_namespace += name;
  821. offset1 += 1;
  822. auto offset2 = scanName(exp, offset1);
  823. // check for {namespace}\{something that is not a name}
  824. if (offset2 == offset1) goto returnInvalid;
  825. name = exp.substr(offset1, offset2-offset1);
  826. offset1 = offset2;
  827. }
  828. m_class = name;
  829. // Now we have a namespace and class name.
  830. // The namespace might be empty.
  831. mangleXhpName(m_class, m_class);
  832. if (m_class == "error") m_class = ErrorClassName;
  833. if (!m_namespace.empty()) {
  834. m_class = m_namespace + "\\" + m_class;
  835. m_namespace.clear();
  836. }
  837. if (offset1 < len-2 && exp[offset1] == '@') {
  838. offset1++;
  839. m_url = exp.substr(offset1, len-offset1);
  840. } else {
  841. // check for unparsed characters at end of exp
  842. if (offset1 != len) goto returnInvalid;
  843. }
  844. return;
  845. returnInvalid:
  846. m_valid = false;
  847. }
  848. bool BreakPointInfo::MatchFile(const char *haystack, int haystack_len,
  849. const std::string &needle) {
  850. TRACE(2, "BreakPointInfo::MatchFile(const char *haystack\n");
  851. int pos = haystack_len - needle.size();
  852. if ((pos == 0 || haystack[pos - 1] == '/') &&
  853. strcasecmp(haystack + pos, needle.c_str()) == 0) {
  854. return true;
  855. }
  856. if (strcasecmp(StatCache::realpath(needle.c_str()).c_str(), haystack)
  857. == 0) {
  858. return true;
  859. }
  860. return false;
  861. }
  862. // Returns true if file is a suffix path of fullPath
  863. bool BreakPointInfo::MatchFile(const std::string& file,
  864. const std::string& fullPath) {
  865. TRACE(7, "BreakPointInfo::MatchFile(const std::string&\n");
  866. if (file == fullPath) {
  867. return true;
  868. }
  869. if (file.size() > 0 && file[0] != '/') {
  870. auto pos = fullPath.rfind(file);
  871. // check for match
  872. if (pos == std::string::npos) return false;
  873. // check if match is a suffix
  874. if (pos + file.size() > fullPath.size()) return false;
  875. // check if suffix is a sub path
  876. if (pos == 0 || fullPath[pos-1] != '/') return false;
  877. return true;
  878. }
  879. // Perhaps file or fullPath is a symlink.
  880. auto realFile = StatCache::realpath(file.c_str());
  881. auto realFullPath = StatCache::realpath(fullPath.c_str());
  882. if (realFile != file || realFullPath != fullPath) {
  883. return MatchFile(realFile, realFullPath);
  884. }
  885. return false;
  886. }
  887. bool BreakPointInfo::MatchClass(const char *fcls, const std::string &bcls,
  888. bool regex, const char *func) {
  889. TRACE(2, "BreakPointInfo::MatchClass\n");
  890. if (bcls.empty()) return true;
  891. if (!fcls || !*fcls) return false;
  892. if (regex || !func || !*func) {
  893. return Match(fcls, 0, bcls, true, true);
  894. }
  895. String sdBClsName(bcls);
  896. Class* clsB = Unit::lookupClass(sdBClsName.get());
  897. if (!clsB) return false;
  898. String sdFClsName(fcls, CopyString);
  899. Class* clsF = Unit::lookupClass(sdFClsName.get());
  900. if (clsB == clsF) return true;
  901. String sdFuncName(func, CopyString);
  902. Func* f = clsB->lookupMethod(sdFuncName.get());
  903. return f && f->baseCls() == clsF;
  904. }
  905. bool BreakPointInfo::Match(const char *haystack, int haystack_len,
  906. const std::string &needle, bool regex, bool exact) {
  907. TRACE(2, "BreakPointInfo::Match\n");
  908. if (needle.empty()) {
  909. return true;
  910. }
  911. if (!haystack || !*haystack) {
  912. return false;
  913. }
  914. if (!regex) {
  915. if (exact) {
  916. return strcasecmp(haystack, needle.c_str()) == 0;
  917. }
  918. return MatchFile(haystack, haystack_len, needle);
  919. }
  920. Variant matches;
  921. Variant r = preg_match(String(needle.c_str(), needle.size(),
  922. CopyString),
  923. String(haystack, haystack_len, CopyString),
  924. &matches);
  925. return HPHP::same(r, static_cast<int64_t>(1));
  926. }
  927. bool BreakPointInfo::checkExceptionOrError(const Variant& e) {
  928. TRACE(2, "BreakPointInfo::checkException\n");
  929. assertx(!e.isNull());
  930. if (e.isObject()) {
  931. if (m_regex) {
  932. return Match(m_class.c_str(), m_class.size(),
  933. e.toObject()->getClassName().data(), true, false);
  934. }
  935. return e.getObjectData()->instanceof(m_class);
  936. }
  937. return Match(m_class.c_str(), m_class.size(), ErrorClassName, m_regex,
  938. false);
  939. }
  940. bool BreakPointInfo::checkUrl(std::string &url) {
  941. TRACE(2, "BreakPointInfo::checkUrl\n");
  942. if (!m_url.empty()) {
  943. if (url.empty()) {
  944. url = "/";
  945. Transport *transport = g_context->getTransport();
  946. if (transport) {
  947. url += transport->getCommand();
  948. }
  949. }
  950. return Match(url.c_str(), url.size(), m_url, m_regex, false);
  951. }
  952. return true;
  953. }
  954. bool BreakPointInfo::checkLines(int line) {
  955. TRACE(2, "BreakPointInfo::checkLines\n");
  956. if (m_line1) {
  957. assertx(m_line2 == -1 || m_line2 >= m_line1);
  958. return line >= m_line1 && (m_line2 == -1 || line <= m_line2);
  959. }
  960. return true;
  961. }
  962. // Checks if m_funcs[0] matches the top of the execution stack and
  963. // if m_funcs[1] (if not null) matches an earlier stack frame, and so on.
  964. // I.e. m_funcs[1] need only be caller, not a direct caller, of m_funcs[0].
  965. bool BreakPointInfo::checkStack(InterruptSite &site) {
  966. TRACE(2, "BreakPointInfo::checkStack\n");
  967. const InterruptSite* s = &site;
  968. for (int i = 0; i < m_funcs.size(); ) {
  969. if (!Match(s->getNamespace(), 0, m_funcs[i]->m_namespace, m_regex, true) ||
  970. !Match(s->getFunction(), 0, m_funcs[i]->m_function, m_regex, true) ||
  971. !MatchClass(s->getClass(), m_funcs[i]->m_class, m_regex,
  972. s->getFunction())) {
  973. if (i == 0) return false; //m_funcs[0] must match the very first frame.
  974. // there is a mismatch for this frame, but the calling frame may match
  975. // so carry on.
  976. } else {
  977. i++; // matched m_funcs[i], proceed to match m_funcs[i+1]
  978. }
  979. s = s->getCallingSite();
  980. if (s == nullptr) return false;
  981. }
  982. return true;
  983. }
  984. bool BreakPointInfo::checkClause(DebuggerProxy &proxy) {
  985. TRACE(2, "BreakPointInfo::checkClause\n");
  986. if (!m_clause.empty()) {
  987. if (m_php.empty()) {
  988. if (m_check) {
  989. m_php = DebuggerProxy::MakePHPReturn(m_clause);
  990. } else {
  991. m_php = DebuggerProxy::MakePHP(m_clause);
  992. }
  993. }
  994. String output;
  995. {
  996. // Don't hit more breakpoints while attempting to decide if we should stop
  997. // at this breakpoint.
  998. EvalBreakControl eval(true);
  999. auto const ret = proxy.ExecutePHP(m_php, output, 0,
  1000. DebuggerProxy::ExecutePHPFlagsNone);
  1001. if (m_check) {
  1002. return ret.second.toBoolean();
  1003. }
  1004. }
  1005. m_output = std::string(output.data(), output.size());
  1006. return true;
  1007. }
  1008. return true;
  1009. }
  1010. ///////////////////////////////////////////////////////////////////////////////
  1011. void BreakPointInfo::SendImpl(int version,
  1012. const std::vector<BreakPointInfoPtr> &bps,
  1013. DebuggerThriftBuffer &thrift) {
  1014. TRACE(2, "BreakPointInfo::SendImpl\n");
  1015. int16_t size = bps.size();
  1016. thrift.write(size);
  1017. for (int i = 0; i < size; i++) {
  1018. bps[i]->sendImpl(version, thrift);
  1019. }
  1020. }
  1021. void BreakPointInfo::RecvImpl(int version,
  1022. std::vector<BreakPointInfoPtr> &bps,
  1023. DebuggerThriftBuffer &thrift) {
  1024. TRACE(2, "BreakPointInfo::RecvImpl\n");
  1025. int16_t size;
  1026. thrift.read(size);
  1027. bps.resize(size);
  1028. for (int i = 0; i < size; i++) {
  1029. BreakPointInfoPtr bpi(new BreakPointInfo());
  1030. bpi->recvImpl(version, thrift);
  1031. bps[i] = bpi;
  1032. }
  1033. }
  1034. ///////////////////////////////////////////////////////////////////////////////
  1035. }}