Пример #1
0
/*
 * Unwinding proceeds as follows:
 *
 *   - Discard all evaluation stack temporaries (including pre-live
 *     activation records).
 *
 *   - Check if the faultOffset that raised the exception is inside a
 *     protected region, if so, if it can handle the Fault resume the
 *     VM at the handler.
 *
 *   - Failing any of the above, pop the frame for the current
 *     function.  If the current function was the last frame in the
 *     current VM nesting level, return UnwindAction::Propagate,
 *     otherwise go to the first step and repeat this process in the
 *     caller's frame.
 *
 * Note: it's important that the unwinder makes a copy of the Fault
 * it's currently operating on, as the underlying faults vector may
 * reallocate due to nested exception handling.
 */
UnwindAction unwind(ActRec*& fp,
                    Stack& stack,
                    PC& pc,
                    Offset faultOffset, // distinct from pc after iopUnwind
                    Fault fault) {
  FTRACE(1, "entering unwinder for fault: {}\n", describeFault(fault));
  SCOPE_EXIT {
    FTRACE(1, "leaving unwinder for fault: {}\n", describeFault(fault));
  };

  for (;;) {
    FTRACE(1, "unwind: func {}, faultOffset {} fp {}\n",
           fp->m_func->name()->data(),
           faultOffset,
           implicit_cast<void*>(fp));

    /*
     * If the handledCount is non-zero, we've already this fault once
     * while unwinding this frema, and popped all eval stack
     * temporaries the first time it was thrown (before entering a
     * fault funclet).  When the Unwind instruction was executed in
     * the funclet, the eval stack must have been left empty again.
     *
     * (We have to skip discardStackTemps in this case because it will
     * look for FPI regions and assume the stack offsets correspond to
     * what the FPI table expects.)
     */
    if (fault.m_handledCount == 0) {
      discardStackTemps(fp, stack, faultOffset);
    }

    if (const EHEnt* eh = fp->m_func->findEH(faultOffset)) {
      switch (checkHandlers(eh, fp, pc, fault)) {
      case UnwindAction::ResumeVM:
        // We've kept our own copy of the Fault, because m_faults may
        // change if we have a reentry during unwinding.  When we're
        // ready to resume, we need to replace the fault to reflect
        // any state changes we've made (handledCount, etc).
        g_vmContext->m_faults.back() = fault;
        return UnwindAction::ResumeVM;
      case UnwindAction::Propagate:
        break;
      }
    }

    // We found no more handlers in this frame, so the nested fault
    // count starts over for the caller frame.
    fault.m_handledCount = 0;

    auto const lastFrameForNesting = fp == fp->arGetSfp();
    tearDownFrame(fp, stack, pc, faultOffset);
    if (lastFrameForNesting) {
      FTRACE(1, "unwind: reached the end of this nesting's ActRec chain\n");
      break;
    }
  }

  return UnwindAction::Propagate;
}
Пример #2
0
/*
 * Unwinding proceeds as follows:
 *
 *   - Discard all evaluation stack temporaries (including pre-live
 *     activation records).
 *
 *   - Check if the faultOffset that raised the exception is inside a
 *     protected region, if so, if it can handle the Fault resume the
 *     VM at the handler.
 *
 *   - Check if we are handling user exception in an eagerly executed
 *     async function. If so, pop its frame, wrap the exception into
 *     failed StaticWaitHandle object, leave it on the stack as
 *     a return value from the async function and resume VM.
 *
 *   - Failing any of the above, pop the frame for the current
 *     function.  If the current function was the last frame in the
 *     current VM nesting level, return UnwindAction::Propagate,
 *     otherwise go to the first step and repeat this process in the
 *     caller's frame.
 *
 * Note: it's important that the unwinder makes a copy of the Fault
 * it's currently operating on, as the underlying faults vector may
 * reallocate due to nested exception handling.
 */
UnwindAction unwind(ActRec*& fp,
                    Stack& stack,
                    PC& pc,
                    Fault fault) {
  FTRACE(1, "entering unwinder for fault: {}\n", describeFault(fault));
  SCOPE_EXIT {
    FTRACE(1, "leaving unwinder for fault: {}\n", describeFault(fault));
  };

  for (;;) {
    bool discard = false;
    if (fault.m_raiseOffset == kInvalidOffset) {
      /*
       * This block executes whenever we want to treat the fault as if
       * it was freshly thrown. Freshly thrown faults either were never
       * previosly seen by the unwinder OR were propagated from the
       * previous frame. In such a case, we fill in the fields with
       * the information from the current frame.
       */
      always_assert(fault.m_raiseNesting == kInvalidNesting);
      // Nesting is set to the current VM nesting.
      fault.m_raiseNesting = g_context->m_nestedVMs.size();
      // Raise frame is set to the current frame
      fault.m_raiseFrame = fp;
      // Raise offset is set to the offset of the current PC.
      fault.m_raiseOffset = fp->m_func->unit()->offsetOf(pc);
      // No handlers were yet examined for this fault.
      fault.m_handledCount = 0;
      // We will be also discarding stack temps.
      discard = true;
    }

    FTRACE(1, "unwind: func {}, raiseOffset {} fp {}\n",
           fp->m_func->name()->data(),
           fault.m_raiseOffset,
           implicit_cast<void*>(fp));

    assert(fault.m_raiseNesting != kInvalidNesting);
    assert(fault.m_raiseFrame != nullptr);
    assert(fault.m_raiseOffset != kInvalidOffset);

    /*
     * If the handledCount is non-zero, we've already seen this fault once
     * while unwinding this frema, and popped all eval stack
     * temporaries the first time it was thrown (before entering a
     * fault funclet).  When the Unwind instruction was executed in
     * the funclet, the eval stack must have been left empty again.
     *
     * (We have to skip discardStackTemps in this case because it will
     * look for FPI regions and assume the stack offsets correspond to
     * what the FPI table expects.)
     */
    if (discard) {
      discardStackTemps(fp, stack, fault.m_raiseOffset);
    }

    do {
      const EHEnt* eh = fp->m_func->findEH(fault.m_raiseOffset);
      if (eh != nullptr) {
        switch (checkHandlers(eh, fp, pc, fault)) {
        case UnwindAction::ResumeVM:
          // We've kept our own copy of the Fault, because m_faults may
          // change if we have a reentry during unwinding.  When we're
          // ready to resume, we need to replace the fault to reflect
          // any state changes we've made (handledCount, etc).
          g_context->m_faults.back() = fault;
          return UnwindAction::ResumeVM;
        case UnwindAction::Propagate:
          break;
        case UnwindAction::Return:
          not_reached();
        }
      }
      // If we came here, it means that no further EHs were found for
      // the current fault offset and handledCount. This means we are
      // allowed to chain the current exception with the previous
      // one (if it exists). This is because the current exception
      // escapes the exception handler where it was thrown.
    } while (chainFaults(fault));

    // If in an eagerly executed async function, wrap the user exception
    // into a failed StaticWaitHandle and return it to the caller.
    if (!fp->resumed() && fp->m_func->isAsyncFunction() &&
        fault.m_faultType == Fault::Type::UserException) {
      tearDownEagerAsyncFrame(fp, stack, pc, fault.m_userException);
      g_context->m_faults.pop_back();
      return pc ? UnwindAction::ResumeVM : UnwindAction::Return;
    }

    // We found no more handlers in this frame, so the nested fault
    // count starts over for the caller frame.
    auto const lastFrameForNesting = !fp->sfp();
    tearDownFrame(fp, stack, pc);

    // Once we are done with EHs for the current frame we restore
    // default values for the fields inside Fault. This makes sure
    // that on another loop pass we will treat the fault just
    // as if it was freshly thrown.
    fault.m_raiseNesting = kInvalidNesting;
    fault.m_raiseFrame = nullptr;
    fault.m_raiseOffset = kInvalidOffset;
    fault.m_handledCount = 0;
    g_context->m_faults.back() = fault;

    if (lastFrameForNesting) {
      FTRACE(1, "unwind: reached the end of this nesting's ActRec chain\n");
      break;
    }
  }

  return UnwindAction::Propagate;
}