Exemple #1
0
void tearDownEagerAsyncFrame(ActRec*& fp, Stack& stack, PC& pc, ObjectData* e) {
  auto const func = fp->func();
  auto const prevFp = fp->sfp();
  auto const soff = fp->m_soff;
  assert(!fp->resumed());
  assert(func->isAsyncFunction());
  assert(*reinterpret_cast<const Op*>(pc) != OpRetC);

  FTRACE(1, "tearDownAsyncFrame: {} ({})\n  fp {} prevFp {}\n",
         func->fullName()->data(),
         func->unit()->filepath()->data(),
         implicit_cast<void*>(fp),
         implicit_cast<void*>(prevFp));

  try {
    frame_free_locals_unwind(fp, func->numLocals());
  } catch (...) {}

  stack.ndiscard(func->numSlotsInFrame());
  stack.ret();
  assert(stack.topTV() == &fp->m_r);
  tvWriteObject(c_StaticWaitHandle::CreateFailed(e), &fp->m_r);
  e->decRefCount();

  if (UNLIKELY(!prevFp)) {
    pc = 0;
    return;
  }

  assert(stack.isValidAddress(reinterpret_cast<uintptr_t>(prevFp)) ||
         prevFp->resumed());
  auto const prevOff = soff + prevFp->func()->base();
  pc = prevFp->func()->unit()->at(prevOff);
  fp = prevFp;
}
Exemple #2
0
void tearDownFrame(ActRec*& fp, Stack& stack, PC& pc, Offset& faultOffset) {
  auto const func = fp->m_func;
  auto const curOp = *reinterpret_cast<const Op*>(pc);
  auto const unwindingGeneratorFrame = func->isGenerator();
  auto const unwindingReturningFrame = curOp == OpRetC || curOp == OpRetV;
  auto const prevFp = fp->arGetSfp();
  auto const soff = fp->m_soff;

  FTRACE(1, "tearDownFrame: {} ({})\n  fp {} prevFp {}\n",
         func->fullName()->data(),
         func->unit()->filepath()->data(),
         implicit_cast<void*>(fp),
         implicit_cast<void*>(prevFp));

  // When throwing from a constructor, we normally want to avoid running the
  // destructor on an object that hasn't been fully constructed yet. But if
  // we're unwinding through the constructor's RetC, the constructor has
  // logically finished and we're unwinding for some internal reason (timeout
  // or user profiler, most likely). More importantly, fp->m_this may have
  // already been destructed and/or overwritten due to sharing space with
  // fp->m_r.
  if (!unwindingReturningFrame && fp->isFromFPushCtor() && fp->hasThis()) {
    fp->getThis()->setNoDestruct();
  }

  // A generator's locals don't live on this stack.
  if (LIKELY(!unwindingGeneratorFrame)) {
    /*
     * If we're unwinding through a frame that's returning, it's only
     * possible that its locals have already been decref'd.
     *
     * Here's why:
     *
     *   - If a destructor for any of these things throws a php
     *     exception, it's swallowed at the dtor boundary and we keep
     *     running php.
     *
     *   - If the destructor for any of these things throws a fatal,
     *     it's swallowed, and we set surprise flags to throw a fatal
     *     from now on.
     *
     *   - If the second case happened and we have to run another
     *     destructor, its enter hook will throw, but it will be
     *     swallowed again.
     *
     *   - Finally, the exit hook for the returning function can
     *     throw, but this happens last so everything is destructed.
     *
     */
    if (!unwindingReturningFrame) {
      try {
        // Note that we must convert locals and the $this to
        // uninit/zero during unwind.  This is because a backtrace
        // from another destructing object during this unwind may try
        // to read them.
        frame_free_locals_unwind(fp, func->numLocals());
      } catch (...) {}
    }
    stack.ndiscard(func->numSlotsInFrame());
    stack.discardAR();
  } else {
    // The generator's locals will be cleaned up when the Continuation
    // object is destroyed. But we are leaving the generator function
    // now, so signal that to anyone who cares.
    try {
      EventHook::FunctionExit(fp);
    } catch (...) {} // As above, don't let new exceptions out of unwind.
  }

  /*
   * At the final ActRec in this nesting level.  We don't need to set
   * pc and fp since we're about to re-throw the exception.  And we
   * don't want to dereference prefFp since we just popped it.
   */
  if (prevFp == fp) return;

  assert(stack.isValidAddress(reinterpret_cast<uintptr_t>(prevFp)) ||
         prevFp->m_func->isGenerator());
  auto const prevOff = soff + prevFp->m_func->base();
  pc = prevFp->m_func->unit()->at(prevOff);
  fp = prevFp;
  faultOffset = prevOff;
}
Exemple #3
0
void tearDownFrame(ActRec*& fp, Stack& stack, PC& pc) {
  auto const func = fp->func();
  auto const curOp = *reinterpret_cast<const Op*>(pc);
  auto const prevFp = fp->sfp();
  auto const soff = fp->m_soff;

  FTRACE(1, "tearDownFrame: {} ({})\n  fp {} prevFp {}\n",
         func->fullName()->data(),
         func->unit()->filepath()->data(),
         implicit_cast<void*>(fp),
         implicit_cast<void*>(prevFp));

  // When throwing from a constructor, we normally want to avoid running the
  // destructor on an object that hasn't been fully constructed yet. But if
  // we're unwinding through the constructor's RetC, the constructor has
  // logically finished and we're unwinding for some internal reason (timeout
  // or user profiler, most likely). More importantly, fp->m_this may have
  // already been destructed and/or overwritten due to sharing space with
  // fp->m_r.
  if (fp->isFromFPushCtor() && fp->hasThis() && curOp != OpRetC) {
    fp->getThis()->setNoDestruct();
  }

  /*
   * It is possible that locals have already been decref'd.
   *
   * Here's why:
   *
   *   - If a destructor for any of these things throws a php
   *     exception, it's swallowed at the dtor boundary and we keep
   *     running php.
   *
   *   - If the destructor for any of these things throws a fatal,
   *     it's swallowed, and we set surprise flags to throw a fatal
   *     from now on.
   *
   *   - If the second case happened and we have to run another
   *     destructor, its enter hook will throw, but it will be
   *     swallowed again.
   *
   *   - Finally, the exit hook for the returning function can
   *     throw, but this happens last so everything is destructed.
   *
   *   - When that happens, exit hook sets localsDecRefd flag.
   */
  if (!fp->localsDecRefd()) {
    try {
      // Note that we must convert locals and the $this to
      // uninit/zero during unwind.  This is because a backtrace
      // from another destructing object during this unwind may try
      // to read them.
      frame_free_locals_unwind(fp, func->numLocals());
    } catch (...) {}
  }

  if (LIKELY(!fp->resumed())) {
    // Free ActRec.
    stack.ndiscard(func->numSlotsInFrame());
    stack.discardAR();
  } else if (fp->func()->isAsyncFunction()) {
    // Do nothing. AsyncFunctionWaitHandle will handle the exception.
  } else if (fp->func()->isAsyncGenerator()) {
    // Do nothing. AsyncGeneratorWaitHandle will handle the exception.
  } else if (fp->func()->isNonAsyncGenerator()) {
    // Mark the generator as finished.
    frame_generator(fp)->finish();
  } else {
    not_reached();
  }

  /*
   * At the final ActRec in this nesting level.  We don't need to set
   * pc and fp since we're about to re-throw the exception.  And we
   * don't want to dereference prefFp since we just popped it.
   */
  if (!prevFp) return;

  assert(stack.isValidAddress(reinterpret_cast<uintptr_t>(prevFp)) ||
         prevFp->resumed());
  auto const prevOff = soff + prevFp->func()->base();
  pc = prevFp->func()->unit()->at(prevOff);
  fp = prevFp;
}
Exemple #4
0
/**
 * Discard the current frame, assuming that a PHP exception given in
 * phpException argument, or C++ exception (phpException == nullptr)
 * is being thrown. Returns an exception to propagate, or nulltpr
 * if the VM execution should be resumed.
 */
ObjectData* tearDownFrame(ActRec*& fp, Stack& stack, PC& pc,
                          ObjectData* phpException) {
  auto const func = fp->func();
  auto const curOp = peek_op(pc);
  auto const prevFp = fp->sfp();
  auto const soff = fp->m_soff;

  ITRACE(1, "tearDownFrame: {} ({})\n",
         func->fullName()->data(),
         func->unit()->filepath()->data());
  ITRACE(1, "  fp {} prevFp {}\n",
         implicit_cast<void*>(fp),
         implicit_cast<void*>(prevFp));

  // When throwing from a constructor, we normally want to avoid running the
  // destructor on an object that hasn't been fully constructed yet. But if
  // we're unwinding through the constructor's RetC, the constructor has
  // logically finished and we're unwinding for some internal reason (timeout
  // or user profiler, most likely). More importantly, fp->m_this may have
  // already been destructed and/or overwritten due to sharing space with
  // fp->m_r.
  if (curOp != OpRetC &&
      fp->hasThis() &&
      fp->getThis()->getVMClass()->getCtor() == func &&
      fp->getThis()->getVMClass()->getDtor()) {
    /*
     * Looks like an FPushCtor call, but it could still have been called
     * directly. Check the fpi region to be sure.
     */
    Offset prevPc;
    auto outer = g_context->getPrevVMState(fp, &prevPc);
    if (outer) {
      auto fe = outer->func()->findPrecedingFPI(prevPc);
      if (fe && isFPushCtor(outer->func()->unit()->getOp(fe->m_fpushOff))) {
        fp->getThis()->setNoDestruct();
      }
    }
  }

  auto const decRefLocals = [&] {
    /*
     * It is possible that locals have already been decref'd.
     *
     * Here's why:
     *
     *   - If a destructor for any of these things throws a php
     *     exception, it's swallowed at the dtor boundary and we keep
     *     running php.
     *
     *   - If the destructor for any of these things throws a fatal,
     *     it's swallowed, and we set surprise flags to throw a fatal
     *     from now on.
     *
     *   - If the second case happened and we have to run another
     *     destructor, its enter hook will throw, but it will be
     *     swallowed again.
     *
     *   - Finally, the exit hook for the returning function can
     *     throw, but this happens last so everything is destructed.
     *
     *   - When that happens, exit hook sets localsDecRefd flag.
     */
    if (!fp->localsDecRefd()) {
      try {
        // Note that we must convert locals and the $this to
        // uninit/zero during unwind.  This is because a backtrace
        // from another destructing object during this unwind may try
        // to read them.
        frame_free_locals_unwind(fp, func->numLocals(), phpException);
      } catch (...) {}
    }
  };

  if (LIKELY(!fp->resumed())) {
    decRefLocals();
    if (UNLIKELY(func->isAsyncFunction()) &&
        phpException &&
        !fp->isFCallAwait()) {
      // If in an eagerly executed async function, wrap the user exception
      // into a failed StaticWaitHandle and return it to the caller.
      auto const waitHandle = c_StaticWaitHandle::CreateFailed(phpException);
      phpException = nullptr;
      stack.ndiscard(func->numSlotsInFrame());
      stack.ret();
      assert(stack.topTV() == &fp->m_r);
      cellCopy(make_tv<KindOfObject>(waitHandle), fp->m_r);
    } else {
      // Free ActRec.
      stack.ndiscard(func->numSlotsInFrame());
      stack.discardAR();
    }
  } else if (func->isAsyncFunction()) {
    auto const waitHandle = frame_afwh(fp);
    if (phpException) {
      // Handle exception thrown by async function.
      decRefLocals();
      waitHandle->fail(phpException);
      phpException = nullptr;
    } else if (waitHandle->isRunning()) {
      // Let the C++ exception propagate. If the current frame represents async
      // function that is running, mark it as abruptly interrupted. Some opcodes
      // like Await may change state of the async function just before exit hook
      // decides to throw C++ exception.
      decRefLocals();
      waitHandle->failCpp();
    }
  } else if (func->isAsyncGenerator()) {
    auto const gen = frame_async_generator(fp);
    if (phpException) {
      // Handle exception thrown by async generator.
      decRefLocals();
      auto eagerResult = gen->fail(phpException);
      phpException = nullptr;
      if (eagerResult) {
        stack.pushObjectNoRc(eagerResult);
      }
    } else if (gen->isEagerlyExecuted() || gen->getWaitHandle()->isRunning()) {
      // Fail the async generator and let the C++ exception propagate.
      decRefLocals();
      gen->failCpp();
    }
  } else if (func->isNonAsyncGenerator()) {
    // Mark the generator as finished.
    decRefLocals();
    frame_generator(fp)->fail();
  } else {
    not_reached();
  }

  /*
   * At the final ActRec in this nesting level.
   */
  if (UNLIKELY(!prevFp)) {
    pc = nullptr;
    fp = nullptr;
    return phpException;
  }

  assert(stack.isValidAddress(reinterpret_cast<uintptr_t>(prevFp)) ||
         prevFp->resumed());
  auto const prevOff = soff + prevFp->func()->base();
  pc = prevFp->func()->unit()->at(prevOff);
  fp = prevFp;
  return phpException;
}
Exemple #5
0
UnwindAction tearDownFrame(ActRec*& fp, Stack& stack, PC& pc,
                           const Fault& fault) {
  auto const func = fp->func();
  auto const curOp = *reinterpret_cast<const Op*>(pc);
  auto const prevFp = fp->sfp();
  auto const soff = fp->m_soff;

  FTRACE(1, "tearDownFrame: {} ({})\n  fp {} prevFp {}\n",
         func->fullName()->data(),
         func->unit()->filepath()->data(),
         implicit_cast<void*>(fp),
         implicit_cast<void*>(prevFp));

  // When throwing from a constructor, we normally want to avoid running the
  // destructor on an object that hasn't been fully constructed yet. But if
  // we're unwinding through the constructor's RetC, the constructor has
  // logically finished and we're unwinding for some internal reason (timeout
  // or user profiler, most likely). More importantly, fp->m_this may have
  // already been destructed and/or overwritten due to sharing space with
  // fp->m_r.
  if (fp->isFromFPushCtor() && fp->hasThis() && curOp != OpRetC) {
    fp->getThis()->setNoDestruct();
  }

  /*
   * It is possible that locals have already been decref'd.
   *
   * Here's why:
   *
   *   - If a destructor for any of these things throws a php
   *     exception, it's swallowed at the dtor boundary and we keep
   *     running php.
   *
   *   - If the destructor for any of these things throws a fatal,
   *     it's swallowed, and we set surprise flags to throw a fatal
   *     from now on.
   *
   *   - If the second case happened and we have to run another
   *     destructor, its enter hook will throw, but it will be
   *     swallowed again.
   *
   *   - Finally, the exit hook for the returning function can
   *     throw, but this happens last so everything is destructed.
   *
   *   - When that happens, exit hook sets localsDecRefd flag.
   */
  if (!fp->localsDecRefd()) {
    try {
      // Note that we must convert locals and the $this to
      // uninit/zero during unwind.  This is because a backtrace
      // from another destructing object during this unwind may try
      // to read them.
      frame_free_locals_unwind(fp, func->numLocals(), fault);
    } catch (...) {}
  }

  auto action = UnwindAction::Propagate;

  if (LIKELY(!fp->resumed())) {
    if (UNLIKELY(func->isAsyncFunction()) &&
        fault.m_faultType == Fault::Type::UserException) {
      // If in an eagerly executed async function, wrap the user exception
      // into a failed StaticWaitHandle and return it to the caller.
      auto const e = fault.m_userException;
      stack.ndiscard(func->numSlotsInFrame());
      stack.ret();
      assert(stack.topTV() == &fp->m_r);
      tvWriteObject(c_StaticWaitHandle::CreateFailed(e), &fp->m_r);
      e->decRefCount();
      action = UnwindAction::ResumeVM;
    } else {
      // Free ActRec.
      stack.ndiscard(func->numSlotsInFrame());
      stack.discardAR();
    }
  } else if (func->isAsyncFunction()) {
    auto const waitHandle = frame_afwh(fp);
    if (fault.m_faultType == Fault::Type::UserException) {
      // Handle exception thrown by async function.
      waitHandle->fail(fault.m_userException);
      action = UnwindAction::ResumeVM;
    } else {
      // Fail the async function and let the C++ exception propagate.
      waitHandle->fail(AsioSession::Get()->getAbruptInterruptException());
    }
  } else if (func->isAsyncGenerator()) {
    // Do nothing. AsyncGeneratorWaitHandle will handle the exception.
  } else if (func->isNonAsyncGenerator()) {
    // Mark the generator as finished.
    frame_generator(fp)->finish();
  } else {
    not_reached();
  }

  /*
   * At the final ActRec in this nesting level.
   */
  if (UNLIKELY(!prevFp)) {
    pc = nullptr;
    fp = nullptr;
    return action;
  }

  assert(stack.isValidAddress(reinterpret_cast<uintptr_t>(prevFp)) ||
         prevFp->resumed());
  auto const prevOff = soff + prevFp->func()->base();
  pc = prevFp->func()->unit()->at(prevOff);
  fp = prevFp;
  return action;
}
void tearDownFrame(ActRec*& fp, Stack& stack, PC& pc, Offset& faultOffset) {
  auto const func = fp->m_func;
  auto const curOp = *reinterpret_cast<const Op*>(pc);
  auto const unwindingGeneratorFrame = func->isGenerator();
  auto const unwindingReturningFrame = curOp == OpRetC || curOp == OpRetV;
  auto const prevFp = fp->arGetSfp();

  FTRACE(1, "tearDownFrame: {} ({})\n  fp {} prevFp {}\n",
         func->fullName()->data(),
         func->unit()->filepath()->data(),
         implicit_cast<void*>(fp),
         implicit_cast<void*>(prevFp));

  if (fp->isFromFPushCtor() && fp->hasThis()) {
    fp->getThis()->setNoDestruct();
  }

  // A generator's locals don't live on this stack.
  if (LIKELY(!unwindingGeneratorFrame)) {
    /*
     * If we're unwinding through a frame that's returning, it's only
     * possible that its locals have already been decref'd.
     *
     * Here's why:
     *
     *   - If a destructor for any of these things throws a php
     *     exception, it's swallowed at the dtor boundary and we keep
     *     running php.
     *
     *   - If the destructor for any of these things throws a fatal,
     *     it's swallowed, and we set surprise flags to throw a fatal
     *     from now on.
     *
     *   - If the second case happened and we have to run another
     *     destructor, its enter hook will throw, but it will be
     *     swallowed again.
     *
     *   - Finally, the exit hook for the returning function can
     *     throw, but this happens last so everything is destructed.
     *
     */
    if (!unwindingReturningFrame) {
      try {
        // Note that we must convert locals and the $this to
        // uninit/zero during unwind.  This is because a backtrace
        // from another destructing object during this unwind may try
        // to read them.
        frame_free_locals_unwind(fp, func->numLocals());
      } catch (...) {}
    }
    stack.ndiscard(func->numSlotsInFrame());
    stack.discardAR();
  }

  assert(stack.isValidAddress(reinterpret_cast<uintptr_t>(prevFp)) ||
         prevFp->m_func->isGenerator());
  auto const prevOff = fp->m_soff + prevFp->m_func->base();
  pc = prevFp->m_func->unit()->at(prevOff);
  fp = prevFp;
  faultOffset = prevOff;
}