PageRenderTime 27ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/hphp/runtime/vm/unwind.cpp

https://gitlab.com/Blueprint-Marketing/hhvm
C++ | 582 lines | 347 code | 59 blank | 176 comment | 72 complexity | 4b02b5fe8c74665cca7546b3827b1953 MD5 | raw file
  1. /*
  2. +----------------------------------------------------------------------+
  3. | HipHop for PHP |
  4. +----------------------------------------------------------------------+
  5. | Copyright (c) 2010-2014 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/vm/unwind.h"
  17. #include <boost/implicit_cast.hpp>
  18. #include <folly/ScopeGuard.h>
  19. #include "hphp/util/trace.h"
  20. #include "hphp/runtime/ext/ext_generator.h"
  21. #include "hphp/runtime/ext/asio/async-function-wait-handle.h"
  22. #include "hphp/runtime/ext/asio/async-generator.h"
  23. #include "hphp/runtime/ext/asio/async-generator-wait-handle.h"
  24. #include "hphp/runtime/ext/asio/static-wait-handle.h"
  25. #include "hphp/runtime/vm/bytecode.h"
  26. #include "hphp/runtime/vm/debugger-hook.h"
  27. #include "hphp/runtime/vm/func.h"
  28. #include "hphp/runtime/vm/runtime.h"
  29. #include "hphp/runtime/vm/unit.h"
  30. #include "hphp/runtime/vm/vm-regs.h"
  31. namespace HPHP {
  32. TRACE_SET_MOD(unwind);
  33. using boost::implicit_cast;
  34. namespace {
  35. //////////////////////////////////////////////////////////////////////
  36. /*
  37. * Enumerates actions that should be taken by the enterVM loop after
  38. * unwinding an exception.
  39. */
  40. enum class UnwindAction {
  41. /*
  42. * The exception was not handled in this nesting of the VM---it
  43. * needs to be rethrown.
  44. */
  45. Propagate,
  46. /*
  47. * The exception was either handled, or a catch or fault handler was
  48. * identified and the VM state has been prepared for entry to it.
  49. */
  50. ResumeVM,
  51. };
  52. #if (defined(DEBUG) || defined(USE_TRACE))
  53. std::string describeFault(const Fault& f) {
  54. return folly::format("[user exception] {}",
  55. implicit_cast<void*>(f.m_userException)).str();
  56. }
  57. #endif
  58. void discardStackTemps(const ActRec* const fp,
  59. Stack& stack,
  60. Offset const bcOffset) {
  61. ITRACE(2, "discardStackTemps with fp {} sp {} pc {}\n",
  62. implicit_cast<const void*>(fp),
  63. implicit_cast<void*>(stack.top()),
  64. bcOffset);
  65. visitStackElems(
  66. fp, stack.top(), bcOffset,
  67. [&] (ActRec* ar) {
  68. assert(ar == reinterpret_cast<ActRec*>(stack.top()));
  69. if (ar->isFromFPushCtor()) {
  70. assert(ar->hasThis());
  71. ar->getThis()->setNoDestruct();
  72. }
  73. ITRACE(2, " unwind pop AR : {}\n",
  74. implicit_cast<void*>(stack.top()));
  75. stack.popAR();
  76. },
  77. [&] (TypedValue* tv) {
  78. assert(tv == stack.top());
  79. ITRACE(2, " unwind pop TV : {}\n",
  80. implicit_cast<void*>(stack.top()));
  81. stack.popTV();
  82. }
  83. );
  84. ITRACE(2, "discardStackTemps ends with sp = {}\n",
  85. implicit_cast<void*>(stack.top()));
  86. }
  87. UnwindAction checkHandlers(const EHEnt* eh,
  88. const ActRec* const fp,
  89. PC& pc,
  90. Fault& fault) {
  91. auto const func = fp->m_func;
  92. ITRACE(1, "checkHandlers: func {} ({})\n",
  93. func->fullName()->data(),
  94. func->unit()->filepath()->data());
  95. for (int i = 0;; ++i) {
  96. // Skip the initial m_handledCount - 1 handlers that were
  97. // considered before.
  98. if (fault.m_handledCount <= i) {
  99. fault.m_handledCount++;
  100. switch (eh->m_type) {
  101. case EHEnt::Type::Fault:
  102. ITRACE(1, "checkHandlers: entering fault at {}: save {}\n",
  103. eh->m_fault,
  104. func->unit()->offsetOf(pc));
  105. pc = func->unit()->entry() + eh->m_fault;
  106. DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
  107. return UnwindAction::ResumeVM;
  108. case EHEnt::Type::Catch:
  109. // Note: we skip catch clauses if we have a pending C++ exception
  110. // as part of our efforts to avoid running more PHP code in the
  111. // face of such exceptions.
  112. if (ThreadInfo::s_threadInfo->m_pendingException == nullptr) {
  113. auto const obj = fault.m_userException;
  114. for (auto& idOff : eh->m_catches) {
  115. ITRACE(1, "checkHandlers: catch candidate {}\n", idOff.second);
  116. auto handler = func->unit()->at(idOff.second);
  117. auto const cls = Unit::lookupClass(
  118. func->unit()->lookupNamedEntityId(idOff.first)
  119. );
  120. if (!cls || !obj->instanceof(cls)) continue;
  121. ITRACE(1, "checkHandlers: entering catch at {}\n", idOff.second);
  122. pc = handler;
  123. DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook());
  124. return UnwindAction::ResumeVM;
  125. }
  126. }
  127. break;
  128. }
  129. }
  130. if (eh->m_parentIndex != -1) {
  131. eh = &func->ehtab()[eh->m_parentIndex];
  132. } else {
  133. break;
  134. }
  135. }
  136. return UnwindAction::Propagate;
  137. }
  138. /**
  139. * Discard the current frame, assuming that a PHP exception given in
  140. * phpException argument, or C++ exception (phpException == nullptr)
  141. * is being thrown. Returns an exception to propagate, or nulltpr
  142. * if the VM execution should be resumed.
  143. */
  144. ObjectData* tearDownFrame(ActRec*& fp, Stack& stack, PC& pc,
  145. ObjectData* phpException) {
  146. auto const func = fp->func();
  147. auto const curOp = *reinterpret_cast<const Op*>(pc);
  148. auto const prevFp = fp->sfp();
  149. auto const soff = fp->m_soff;
  150. ITRACE(1, "tearDownFrame: {} ({})\n",
  151. func->fullName()->data(),
  152. func->unit()->filepath()->data());
  153. ITRACE(1, " fp {} prevFp {}\n",
  154. implicit_cast<void*>(fp),
  155. implicit_cast<void*>(prevFp));
  156. // When throwing from a constructor, we normally want to avoid running the
  157. // destructor on an object that hasn't been fully constructed yet. But if
  158. // we're unwinding through the constructor's RetC, the constructor has
  159. // logically finished and we're unwinding for some internal reason (timeout
  160. // or user profiler, most likely). More importantly, fp->m_this may have
  161. // already been destructed and/or overwritten due to sharing space with
  162. // fp->m_r.
  163. if (fp->isFromFPushCtor() && fp->hasThis() && curOp != OpRetC) {
  164. fp->getThis()->setNoDestruct();
  165. }
  166. auto const decRefLocals = [&] {
  167. /*
  168. * It is possible that locals have already been decref'd.
  169. *
  170. * Here's why:
  171. *
  172. * - If a destructor for any of these things throws a php
  173. * exception, it's swallowed at the dtor boundary and we keep
  174. * running php.
  175. *
  176. * - If the destructor for any of these things throws a fatal,
  177. * it's swallowed, and we set surprise flags to throw a fatal
  178. * from now on.
  179. *
  180. * - If the second case happened and we have to run another
  181. * destructor, its enter hook will throw, but it will be
  182. * swallowed again.
  183. *
  184. * - Finally, the exit hook for the returning function can
  185. * throw, but this happens last so everything is destructed.
  186. *
  187. * - When that happens, exit hook sets localsDecRefd flag.
  188. */
  189. if (!fp->localsDecRefd()) {
  190. try {
  191. // Note that we must convert locals and the $this to
  192. // uninit/zero during unwind. This is because a backtrace
  193. // from another destructing object during this unwind may try
  194. // to read them.
  195. frame_free_locals_unwind(fp, func->numLocals(), phpException);
  196. } catch (...) {}
  197. }
  198. };
  199. if (LIKELY(!fp->resumed())) {
  200. decRefLocals();
  201. if (UNLIKELY(func->isAsyncFunction()) && phpException) {
  202. // If in an eagerly executed async function, wrap the user exception
  203. // into a failed StaticWaitHandle and return it to the caller.
  204. auto const waitHandle = c_StaticWaitHandle::CreateFailed(phpException);
  205. phpException = nullptr;
  206. stack.ndiscard(func->numSlotsInFrame());
  207. stack.ret();
  208. assert(stack.topTV() == &fp->m_r);
  209. cellCopy(make_tv<KindOfObject>(waitHandle), fp->m_r);
  210. } else {
  211. // Free ActRec.
  212. stack.ndiscard(func->numSlotsInFrame());
  213. stack.discardAR();
  214. }
  215. } else if (func->isAsyncFunction()) {
  216. auto const waitHandle = frame_afwh(fp);
  217. if (phpException) {
  218. // Handle exception thrown by async function.
  219. decRefLocals();
  220. waitHandle->fail(phpException);
  221. phpException = nullptr;
  222. } else if (waitHandle->isRunning()) {
  223. // Let the C++ exception propagate. If the current frame represents async
  224. // function that is running, mark it as abruptly interrupted. Some opcodes
  225. // like Await may change state of the async function just before exit hook
  226. // decides to throw C++ exception.
  227. decRefLocals();
  228. waitHandle->failCpp();
  229. }
  230. } else if (func->isAsyncGenerator()) {
  231. auto const gen = frame_async_generator(fp);
  232. if (phpException) {
  233. // Handle exception thrown by async generator.
  234. decRefLocals();
  235. auto eagerResult = gen->fail(phpException);
  236. phpException = nullptr;
  237. if (eagerResult) {
  238. stack.pushObjectNoRc(eagerResult);
  239. }
  240. } else if (gen->isEagerlyExecuted() || gen->getWaitHandle()->isRunning()) {
  241. // Fail the async generator and let the C++ exception propagate.
  242. decRefLocals();
  243. gen->failCpp();
  244. }
  245. } else if (func->isNonAsyncGenerator()) {
  246. // Mark the generator as finished.
  247. decRefLocals();
  248. frame_generator(fp)->fail();
  249. } else {
  250. not_reached();
  251. }
  252. /*
  253. * At the final ActRec in this nesting level.
  254. */
  255. if (UNLIKELY(!prevFp)) {
  256. pc = nullptr;
  257. fp = nullptr;
  258. return phpException;
  259. }
  260. assert(stack.isValidAddress(reinterpret_cast<uintptr_t>(prevFp)) ||
  261. prevFp->resumed());
  262. auto const prevOff = soff + prevFp->func()->base();
  263. pc = prevFp->func()->unit()->at(prevOff);
  264. fp = prevFp;
  265. return phpException;
  266. }
  267. const StaticString s_previous("previous");
  268. void chainFaultObjects(ObjectData* top, ObjectData* prev) {
  269. while (true) {
  270. auto const lookup = top->getProp(
  271. SystemLib::s_ExceptionClass,
  272. s_previous.get()
  273. );
  274. auto const top_tv = lookup.prop;
  275. assert(top_tv != nullptr);
  276. assert(top_tv->m_type != KindOfUninit && lookup.accessible);
  277. if (top_tv->m_type != KindOfObject ||
  278. !top_tv->m_data.pobj->instanceof(SystemLib::s_ExceptionClass)) {
  279. // Since we are overwriting, decref.
  280. tvRefcountedDecRef(top_tv);
  281. // Objects held in m_faults are not refcounted, therefore we need to
  282. // increase the ref count here.
  283. top_tv->m_type = KindOfObject;
  284. top_tv->m_data.pobj = prev;
  285. prev->incRefCount();
  286. break;
  287. }
  288. top = top_tv->m_data.pobj;
  289. }
  290. }
  291. bool chainFaults(Fault& fault) {
  292. always_assert(!g_context->m_faults.empty());
  293. auto& faults = g_context->m_faults;
  294. faults.pop_back();
  295. if (faults.empty()) {
  296. faults.push_back(fault);
  297. return false;
  298. }
  299. auto prev = faults.back();
  300. if (fault.m_raiseNesting == prev.m_raiseNesting &&
  301. fault.m_raiseFrame == prev.m_raiseFrame) {
  302. fault.m_raiseOffset = prev.m_raiseOffset;
  303. fault.m_handledCount = prev.m_handledCount;
  304. chainFaultObjects(fault.m_userException, prev.m_userException);
  305. faults.pop_back();
  306. faults.push_back(fault);
  307. return true;
  308. }
  309. faults.push_back(fault);
  310. return false;
  311. }
  312. const StaticString s_hphpd_break("hphpd_break");
  313. const StaticString s_fb_enable_code_coverage("fb_enable_code_coverage");
  314. const StaticString s_xdebug_start_code_coverage("xdebug_start_code_coverage");
  315. //////////////////////////////////////////////////////////////////////
  316. }
  317. /*
  318. * Unwinding proceeds as follows:
  319. *
  320. * - Discard all evaluation stack temporaries (including pre-live
  321. * activation records).
  322. *
  323. * - Check if the faultOffset that raised the exception is inside a
  324. * protected region, if so, if it can handle the Fault resume the
  325. * VM at the handler.
  326. *
  327. * - Check if we are handling user exception in an eagerly executed
  328. * async function. If so, pop its frame, wrap the exception into
  329. * failed StaticWaitHandle object, leave it on the stack as
  330. * a return value from the async function and resume VM.
  331. *
  332. * - Failing any of the above, pop the frame for the current
  333. * function. If the current function was the last frame in the
  334. * current VM nesting level, rethrow the exception, otherwise go
  335. * to the first step and repeat this process in the caller's
  336. * frame.
  337. *
  338. * Note: it's important that the unwinder makes a copy of the Fault
  339. * it's currently operating on, as the underlying faults vector may
  340. * reallocate due to nested exception handling.
  341. */
  342. void unwindPhp() {
  343. assert(!g_context->m_faults.empty());
  344. auto& fp = vmfp();
  345. auto& stack = vmStack();
  346. auto& pc = vmpc();
  347. auto fault = g_context->m_faults.back();
  348. ITRACE(1, "entering unwinder for fault: {}\n", describeFault(fault));
  349. SCOPE_EXIT {
  350. ITRACE(1, "leaving unwinder for fault: {}\n", describeFault(fault));
  351. };
  352. do {
  353. bool discard = false;
  354. if (fault.m_raiseOffset == kInvalidOffset) {
  355. /*
  356. * This block executes whenever we want to treat the fault as if
  357. * it was freshly thrown. Freshly thrown faults either were never
  358. * previosly seen by the unwinder OR were propagated from the
  359. * previous frame. In such a case, we fill in the fields with
  360. * the information from the current frame.
  361. */
  362. always_assert(fault.m_raiseNesting == kInvalidNesting);
  363. // Nesting is set to the current VM nesting.
  364. fault.m_raiseNesting = g_context->m_nestedVMs.size();
  365. // Raise frame is set to the current frame
  366. fault.m_raiseFrame = fp;
  367. // Raise offset is set to the offset of the current PC.
  368. fault.m_raiseOffset = fp->m_func->unit()->offsetOf(pc);
  369. // No handlers were yet examined for this fault.
  370. fault.m_handledCount = 0;
  371. // We will be also discarding stack temps.
  372. discard = true;
  373. }
  374. ITRACE(1, "unwind: func {}, raiseOffset {} fp {}\n",
  375. fp->m_func->name()->data(),
  376. fault.m_raiseOffset,
  377. implicit_cast<void*>(fp));
  378. assert(fault.m_raiseNesting != kInvalidNesting);
  379. assert(fault.m_raiseFrame != nullptr);
  380. assert(fault.m_raiseOffset != kInvalidOffset);
  381. /*
  382. * If the handledCount is non-zero, we've already seen this fault once
  383. * while unwinding this frema, and popped all eval stack
  384. * temporaries the first time it was thrown (before entering a
  385. * fault funclet). When the Unwind instruction was executed in
  386. * the funclet, the eval stack must have been left empty again.
  387. *
  388. * (We have to skip discardStackTemps in this case because it will
  389. * look for FPI regions and assume the stack offsets correspond to
  390. * what the FPI table expects.)
  391. */
  392. if (discard) {
  393. discardStackTemps(fp, stack, fault.m_raiseOffset);
  394. }
  395. do {
  396. const EHEnt* eh = fp->m_func->findEH(fault.m_raiseOffset);
  397. if (eh != nullptr) {
  398. switch (checkHandlers(eh, fp, pc, fault)) {
  399. case UnwindAction::ResumeVM:
  400. // We've kept our own copy of the Fault, because m_faults may
  401. // change if we have a reentry during unwinding. When we're
  402. // ready to resume, we need to replace the fault to reflect
  403. // any state changes we've made (handledCount, etc).
  404. g_context->m_faults.back() = fault;
  405. return;
  406. case UnwindAction::Propagate:
  407. break;
  408. }
  409. }
  410. // If we came here, it means that no further EHs were found for
  411. // the current fault offset and handledCount. This means we are
  412. // allowed to chain the current exception with the previous
  413. // one (if it exists). This is because the current exception
  414. // escapes the exception handler where it was thrown.
  415. } while (chainFaults(fault));
  416. // We found no more handlers in this frame, so the nested fault
  417. // count starts over for the caller frame.
  418. fault.m_userException = tearDownFrame(fp, stack, pc, fault.m_userException);
  419. if (fault.m_userException == nullptr) {
  420. g_context->m_faults.pop_back();
  421. return;
  422. }
  423. // Once we are done with EHs for the current frame we restore
  424. // default values for the fields inside Fault. This makes sure
  425. // that on another loop pass we will treat the fault just
  426. // as if it was freshly thrown.
  427. fault.m_raiseNesting = kInvalidNesting;
  428. fault.m_raiseFrame = nullptr;
  429. fault.m_raiseOffset = kInvalidOffset;
  430. fault.m_handledCount = 0;
  431. g_context->m_faults.back() = fault;
  432. } while (fp);
  433. ITRACE(1, "unwind: reached the end of this nesting's ActRec chain\n");
  434. g_context->m_faults.pop_back();
  435. Object obj = Object::attach(fault.m_userException);
  436. throw obj;
  437. }
  438. void unwindPhp(ObjectData* phpException) {
  439. Fault fault;
  440. fault.m_userException = phpException;
  441. fault.m_userException->incRefCount();
  442. g_context->m_faults.push_back(fault);
  443. unwindPhp();
  444. }
  445. /*
  446. * Unwinding of C++ exceptions proceeds as follows:
  447. *
  448. * - Discard all PHP exceptions pending for this frame.
  449. *
  450. * - Discard all evaluation stack temporaries (including pre-live
  451. * activation records).
  452. *
  453. * - Pop the frame for the current function. If the current function
  454. * was the last frame in the current VM nesting level, re-throw
  455. * the C++ exception, otherwise go to the first step and repeat
  456. * this process in the caller's frame.
  457. */
  458. void unwindCpp(Exception* exception) {
  459. auto& fp = vmfp();
  460. auto& stack = vmStack();
  461. auto& pc = vmpc();
  462. assert(!g_context->m_unwindingCppException);
  463. g_context->m_unwindingCppException = true;
  464. ITRACE(1, "entering unwinder for C++ exception: {}\n",
  465. implicit_cast<void*>(exception));
  466. SCOPE_EXIT {
  467. assert(g_context->m_unwindingCppException);
  468. g_context->m_unwindingCppException = false;
  469. ITRACE(1, "leaving unwinder for C++ exception: {}\n",
  470. implicit_cast<void*>(exception));
  471. };
  472. do {
  473. auto const offset = fp->func()->unit()->offsetOf(pc);
  474. ITRACE(1, "unwindCpp: func {}, raiseOffset {} fp {}\n",
  475. fp->func()->name()->data(),
  476. offset,
  477. implicit_cast<void*>(fp));
  478. // Discard all PHP exceptions pending for this frame
  479. auto& faults = g_context->m_faults;
  480. while (UNLIKELY(!faults.empty()) &&
  481. faults.back().m_raiseFrame == fp &&
  482. faults.back().m_raiseNesting == g_context->m_nestedVMs.size()) {
  483. decRefObj(faults.back().m_userException);
  484. faults.pop_back();
  485. }
  486. // Discard stack temporaries
  487. discardStackTemps(fp, stack, offset);
  488. // Discard the frame
  489. DEBUG_ONLY auto const phpException = tearDownFrame(fp, stack, pc, nullptr);
  490. assert(phpException == nullptr);
  491. } while (fp);
  492. // Propagate the C++ exception to the outer VM nesting
  493. exception->throwException();
  494. }
  495. void unwindBuiltinFrame() {
  496. auto& stack = vmStack();
  497. auto& fp = vmfp();
  498. assert(fp->m_func->name()->isame(s_hphpd_break.get()) ||
  499. fp->m_func->name()->isame(s_fb_enable_code_coverage.get()) ||
  500. fp->m_func->name()->isame(s_xdebug_start_code_coverage.get()));
  501. // Free any values that may be on the eval stack. We know there
  502. // can't be FPI regions and it can't be a generator body because
  503. // it's a builtin frame.
  504. const int numSlots = fp->m_func->numSlotsInFrame();
  505. auto const evalTop = reinterpret_cast<TypedValue*>(vmfp()) - numSlots;
  506. while (stack.topTV() < evalTop) {
  507. stack.popTV();
  508. }
  509. // Free the locals and VarEnv if there is one
  510. auto rv = make_tv<KindOfNull>();
  511. frame_free_locals_inl(fp, fp->m_func->numLocals(), &rv);
  512. // Tear down the frame
  513. Offset pc = -1;
  514. ActRec* sfp = g_context->getPrevVMState(fp, &pc);
  515. assert(pc != -1);
  516. fp = sfp;
  517. vmpc() = fp->m_func->unit()->at(pc);
  518. stack.ndiscard(numSlots);
  519. stack.discardAR();
  520. stack.pushNull(); // return value
  521. }
  522. //////////////////////////////////////////////////////////////////////
  523. }