Exemple #1
0
TypedValue* methodWrapper(ActRec* ar) {
  auto func = ar->m_func;
  auto numArgs = func->numParams();
  auto numNonDefault = ar->numArgs();
  bool isStatic = func->isStatic();
  assert(!func->hasVariadicCaptureParam());
  TypedValue* args = ((TypedValue*)ar) - 1;
  TypedValue rv;
  rv.m_type = KindOfNull;

  if (LIKELY(numNonDefault == numArgs) ||
      LIKELY(nativeWrapperCheckArgs(ar))) {
    if (coerceFCallArgs(args, numArgs, numNonDefault, func)) {

      // Prepend a context arg for methods
      // KindOfClass when it's being called statically Foo::bar()
      // KindOfObject when it's being called on an instance $foo->bar()
      TypedValue ctx;
      if (ar->hasThis()) {
        if (isStatic) {
          throw_instance_method_fatal(getInvokeName(ar)->data());
        }
        ctx.m_type = KindOfObject;
        ctx.m_data.pobj = ar->getThis();
      } else {
        if (!isStatic) {
          throw_instance_method_fatal(getInvokeName(ar)->data());
        }
        ctx.m_type = KindOfClass;
        ctx.m_data.pcls = const_cast<Class*>(ar->getClass());
      }

      callFunc(func, &ctx, args, numArgs, rv);
    } else if (func->attrs() & AttrParamCoerceModeFalse) {
      rv.m_type = KindOfBoolean;
      rv.m_data.num = 0;
    }
  }

  assert(rv.m_type != KindOfUninit);
  if (isStatic) {
    frame_free_locals_no_this_inl(ar, func->numLocals(), &rv);
  } else {
    frame_free_locals_inl(ar, func->numLocals(), &rv);
  }
  tvCopy(rv, ar->m_r);
  return &ar->m_r;
}
Exemple #2
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 #3
0
/*
 * Compute the stack and local type postconditions for a
 * single-entry/single-exit tracelet.
 */
std::vector<RegionDesc::TypePred> IRBuilder::getKnownTypes() {
  // This function is only correct when given a single-exit region, as
  // in TransProfile.  Furthermore, its output is only used to guide
  // formation of profile-driven regions.
  assert(tx->mode() == TransProfile);

  // We want the state for the last block on the "main trace".  Figure
  // out which that is.
  Block* mainExit = nullptr;
  for (auto* b : rpoSortCfg(m_unit)) {
    if (isMainExit(b)) {
      assert(mainExit == nullptr);
      mainExit = b;
    }
  }
  assert(mainExit != nullptr);

  // Load state for mainExit.  This feels hacky.
  FTRACE(1, "mainExit: B{}\n", mainExit->id());
  m_state.startBlock(mainExit);

  // Now use the current state to get all the types.
  std::vector<RegionDesc::TypePred> result;
  auto const curFunc  = m_state.func();
  auto const sp       = m_state.sp();
  auto const spOffset = m_state.spOffset();

  for (unsigned i = 0; i < curFunc->maxStackCells(); ++i) {
    auto t = getStackValue(sp, i).knownType;
    if (!t.equals(Type::StackElem)) {
      result.push_back({ RegionDesc::Location::Stack{i, spOffset - i}, t });
    }
  }

  for (unsigned i = 0; i < curFunc->numLocals(); ++i) {
    auto t = m_state.localType(i);
    if (!t.equals(Type::Gen)) {
      FTRACE(1, "Local {}: {}\n", i, t.toString());
      result.push_back({ RegionDesc::Location::Local{i}, t });
    }
  }
  return result;
}
Exemple #4
0
std::vector<RegionDesc::TypePred> IRBuilder::getKnownTypes() const {
  std::vector<RegionDesc::TypePred> result;
  auto const curFunc  = m_state.func();
  auto const sp       = m_state.sp();
  auto const spOffset = m_state.spOffset();

  for (unsigned i = 0; i < curFunc->maxStackCells(); ++i) {
    auto t = getStackValue(sp, i).knownType;
    if (!t.equals(Type::StackElem)) {
      result.push_back({ RegionDesc::Location::Stack{i, spOffset - i}, t });
    }
  }

  for (unsigned i = 0; i < curFunc->numLocals(); ++i) {
    auto t = m_state.localType(i);
    if (!t.equals(Type::Gen)) {
      result.push_back({ RegionDesc::Location::Local{i}, t });
    }
  }
  return result;
}
Exemple #5
0
void Generator::copyVars(const ActRec* srcFp) {
  const auto dstFp = actRec();
  const auto func = dstFp->func();
  assert(srcFp->func() == dstFp->func());

  for (Id i = 0; i < func->numLocals(); ++i) {
    tvDupFlattenVars(frame_local(srcFp, i), frame_local(dstFp, i));
  }

  if (dstFp->hasThis()) {
    dstFp->getThis()->incRefCount();
  }

  if (LIKELY(!(srcFp->func()->attrs() & AttrMayUseVV))) return;
  if (LIKELY(srcFp->m_varEnv == nullptr)) return;

  if (srcFp->hasExtraArgs()) {
    dstFp->setExtraArgs(srcFp->getExtraArgs()->clone(dstFp));
  } else {
    assert(srcFp->hasVarEnv());
    dstFp->setVarEnv(srcFp->getVarEnv()->clone(dstFp));
  }
}
Exemple #6
0
void FrameState::trackDefInlineFP(const IRInstruction* inst) {
  auto const target     = inst->extra<DefInlineFP>()->target;
  auto const savedSPOff = inst->extra<DefInlineFP>()->retSPOff;
  auto const calleeFP   = inst->dst();
  auto const calleeSP   = inst->src(0);
  auto const savedSP    = inst->src(1);

  // Saved IRBuilder state will include the "return" fp/sp.
  // Whatever the current fpValue is is good enough, but we have to be
  // passed in the StkPtr that represents the stack prior to the
  // ActRec being allocated.
  m_spOffset = savedSPOff;
  m_spValue = savedSP;

  auto const stackValues = collectStackValues(m_spValue, m_spOffset);
  for (DEBUG_ONLY auto& val : stackValues) {
    FTRACE(4, "    marking caller stack value available: {}\n",
           val->toString());
  }

  m_inlineSavedStates.emplace_back(createSnapshot());

  /*
   * Set up the callee state.
   *
   * We set m_thisIsAvailable to true on any object method, because we
   * just don't inline calls to object methods with a null $this.
   */
  m_fpValue         = calleeFP;
  m_spValue         = calleeSP;
  m_thisAvailable   = target->cls() != nullptr && !target->isStatic();
  m_curFunc         = target;
  m_frameSpansCall  = false;

  m_locals.clear();
  m_locals.resize(target->numLocals());
}
Exemple #7
0
TypedValue* functionWrapper(ActRec* ar) {
  auto func = ar->m_func;
  auto numArgs = func->numParams();
  auto numNonDefault = ar->numArgs();
  assert(!func->hasVariadicCaptureParam());
  TypedValue* args = ((TypedValue*)ar) - 1;
  TypedValue rv;
  rv.m_type = KindOfNull;

  if (LIKELY(numNonDefault == numArgs) ||
      LIKELY(nativeWrapperCheckArgs(ar))) {
    if (coerceFCallArgs(args, numArgs, numNonDefault, func)) {
      callFunc(func, nullptr, args, numArgs, rv);
    } else if (func->attrs() & AttrParamCoerceModeFalse) {
      rv.m_type = KindOfBoolean;
      rv.m_data.num = 0;
    }
  }

  assert(rv.m_type != KindOfUninit);
  frame_free_locals_no_this_inl(ar, func->numLocals(), &rv);
  tvCopy(rv, ar->m_r);
  return &ar->m_r;
}
Exemple #8
0
void c_Continuation::copyContinuationVars(ActRec* srcFp) {
  const auto dstFp = actRec();
  const auto func = dstFp->func();
  assert(srcFp->func() == dstFp->func());

  for (Id i = 0; i < func->numLocals(); ++i) {
    tvDupFlattenVars(frame_local(srcFp, i), frame_local(dstFp, i));
  }

  if (dstFp->hasThis()) {
    dstFp->getThis()->incRefCount();
  }

  if (LIKELY(srcFp->m_varEnv == nullptr)) {
    return;
  }

  if (srcFp->hasExtraArgs()) {
    dstFp->setExtraArgs(srcFp->getExtraArgs()->clone(dstFp));
  } else {
    assert(srcFp->hasVarEnv());
    dstFp->setVarEnv(srcFp->getVarEnv()->clone(dstFp));
  }
}
Exemple #9
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 #10
0
void cgCall(IRLS& env, const IRInstruction* inst) {
  auto const sp = srcLoc(env, inst, 0).reg();
  auto const fp = srcLoc(env, inst, 1).reg();
  auto const extra = inst->extra<Call>();
  auto const callee = extra->callee;
  auto const argc = extra->numParams;

  auto& v = vmain(env);
  auto& vc = vcold(env);
  auto const catchBlock = label(env, inst->taken());

  auto const calleeSP = sp[cellsToBytes(extra->spOffset.offset)];
  auto const calleeAR = calleeSP + cellsToBytes(argc);

  v << store{fp, calleeAR + AROFF(m_sfp)};
  v << storeli{safe_cast<int32_t>(extra->after), calleeAR + AROFF(m_soff)};

  if (extra->fcallAwait) {
    // This clobbers any flags that might have already been set on the callee
    // AR (e.g., by SpillFrame), but this is okay because there should never be
    // any conflicts; see the documentation in act-rec.h.
    auto const imm = static_cast<int32_t>(
      ActRec::encodeNumArgsAndFlags(argc, ActRec::Flags::IsFCallAwait)
    );
    v << storeli{imm, calleeAR + AROFF(m_numArgsAndFlags)};
  }

  auto const isNativeImplCall = callee &&
                                callee->builtinFuncPtr() &&
                                !callee->nativeFuncPtr() &&
                                argc == callee->numParams();
  if (isNativeImplCall) {
    // The assumption here is that for builtins, the generated func contains
    // only a single opcode (NativeImpl), and there are no non-argument locals.
    if (do_assert) {
      assertx(argc == callee->numLocals());
      assertx(callee->numIterators() == 0);

      auto addr = callee->getEntry();
      while (peek_op(addr) == Op::AssertRATL) {
        addr += instrLen(addr);
      }
      assertx(peek_op(addr) == Op::NativeImpl);
      assertx(addr + instrLen(addr) ==
              callee->unit()->entry() + callee->past());
    }

    v << store{v.cns(mcg->ustubs().retHelper), calleeAR + AROFF(m_savedRip)};
    if (callee->attrs() & AttrMayUseVV) {
      v << storeqi{0, calleeAR + AROFF(m_invName)};
    }
    v << lea{calleeAR, rvmfp()};

    emitCheckSurpriseFlagsEnter(v, vc, fp, Fixup(0, argc), catchBlock);

    auto const builtinFuncPtr = callee->builtinFuncPtr();
    TRACE(2, "Calling builtin preClass %p func %p\n",
          callee->preClass(), builtinFuncPtr);

    // We sometimes call this while curFunc() isn't really the builtin, so make
    // sure to record the sync point as if we are inside the builtin.
    if (FixupMap::eagerRecord(callee)) {
      auto const syncSP = v.makeReg();
      v << lea{calleeSP, syncSP};
      emitEagerSyncPoint(v, callee->getEntry(), rvmtl(), rvmfp(), syncSP);
    }

    // Call the native implementation.  This will free the locals for us in the
    // normal case.  In the case where an exception is thrown, the VM unwinder
    // will handle it for us.
    auto const done = v.makeBlock();
    v << vinvoke{CallSpec::direct(builtinFuncPtr), v.makeVcallArgs({{rvmfp()}}),
                 v.makeTuple({}), {done, catchBlock}, Fixup(0, argc)};
    env.catch_calls[inst->taken()] = CatchCall::CPP;

    v = done;
    // The native implementation already put the return value on the stack for
    // us, and handled cleaning up the arguments.  We have to update the frame
    // pointer and the stack pointer, and load the return value into the return
    // register so the trace we are returning to has it where it expects.
    // TODO(#1273094): We should probably modify the actual builtins to return
    // values via registers using the C ABI and do a reg-to-reg move.
    loadTV(v, inst->dst(), dstLoc(env, inst, 0), rvmfp()[AROFF(m_r)], true);
    v << load{rvmfp()[AROFF(m_sfp)], rvmfp()};
    emitRB(v, Trace::RBTypeFuncExit, callee->fullName()->data());
    return;
  }

  v << lea{calleeAR, rvmfp()};

  if (RuntimeOption::EvalHHIRGenerateAsserts) {
    v << syncvmsp{v.cns(0x42)};

    constexpr uint64_t kUninitializedRIP = 0xba5eba11acc01ade;
    emitImmStoreq(v, kUninitializedRIP, rvmfp()[AROFF(m_savedRip)]);
  }

  // Emit a smashable call that initially calls a recyclable service request
  // stub.  The stub and the eventual targets take rvmfp() as an argument,
  // pointing to the callee ActRec.
  auto const target = callee
    ? mcg->ustubs().immutableBindCallStub
    : mcg->ustubs().bindCallStub;

  auto const done = v.makeBlock();
  v << callphp{target, php_call_regs(), {{done, catchBlock}}};
  env.catch_calls[inst->taken()] = CatchCall::PHP;
  v = done;

  auto const dst = dstLoc(env, inst, 0);
  v << defvmret{dst.reg(0), dst.reg(1)};
}
Exemple #11
0
/*
 * Intended to be called after all optimizations are finished on a
 * single-entry, single-exit tracelet, this collects the types of all stack
 * slots and locals at the end of the main exit.
 */
void IRUnit::collectPostConditions() {
  // This function is only correct when given a single-exit region, as in
  // TransKind::Profile.  Furthermore, its output is only used to guide
  // formation of profile-driven regions.
  assert(mcg->tx().mode() == TransKind::Profile);
  assert(m_postConds.empty());
  Timer _t(Timer::collectPostConditions);

  // We want the state for the last block on the "main trace".  Figure
  // out which that is.
  Block* mainExit = nullptr;
  Block* lastMainBlock = nullptr;

  FrameStateMgr state{*this, entry()->front().marker()};
  // TODO(#5678127): this code is wrong for HHIRBytecodeControlFlow
  state.setLegacyReoptimize();
  ITRACE(2, "collectPostConditions starting\n");
  Trace::Indent _i;

  for (auto* block : rpoSortCfg(*this)) {
    state.startBlock(block, block->front().marker());

    for (auto& inst : *block) {
      state.update(&inst);
    }

    if (isMainBlock(block)) lastMainBlock = block;

    if (isMainExit(block)) {
      mainExit = block;
      break;
    }

    state.finishBlock(block);
  }

  // If we didn't find an obvious exit, then use the last block in the region.
  always_assert(lastMainBlock != nullptr);
  if (mainExit == nullptr) mainExit = lastMainBlock;

  FTRACE(1, "mainExit: B{}\n", mainExit->id());

  // state currently holds the state at the end of mainExit
  auto const curFunc  = state.func();
  auto const sp       = state.sp();
  auto const spOffset = state.spOffset();

  for (unsigned i = 0; i < spOffset; ++i) {
    auto t = getStackValue(sp, i).knownType;
    if (!t.equals(Type::StackElem)) {
      m_postConds.push_back({ RegionDesc::Location::Stack{i, spOffset - i},
                              t });
    }
  }

  for (unsigned i = 0; i < curFunc->numLocals(); ++i) {
    auto t = state.localType(i);
    if (!t.equals(Type::Gen)) {
      FTRACE(1, "Local {}: {}\n", i, t.toString());
      m_postConds.push_back({ RegionDesc::Location::Local{i}, t });
    }
  }
}
Exemple #12
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 #13
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 #14
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;
}
Exemple #15
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();

  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;
}