UnwindAction checkHandlers(const EHEnt* eh, const ActRec* const fp, PC& pc, Fault& fault) { auto const func = fp->m_func; FTRACE(1, "checkHandlers: func {} ({})\n", func->fullName()->data(), func->unit()->filepath()->data()); // Always blindly propagate on fatal exception since those are // unrecoverable anyway. if (fault.m_faultType == Fault::Type::CppException) { return UnwindAction::Propagate; } for (int i = 0;; ++i) { // Skip the initial m_handledCount - 1 handlers that were // considered before. if (fault.m_handledCount <= i) { fault.m_handledCount++; switch (eh->m_type) { case EHEnt::Type::Fault: FTRACE(1, "checkHandlers: entering fault at {}: save {}\n", eh->m_fault, func->unit()->offsetOf(pc)); pc = func->unit()->entry() + eh->m_fault; DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook()); return UnwindAction::ResumeVM; case EHEnt::Type::Catch: // Note: we skip catch clauses if we have a pending C++ exception // as part of our efforts to avoid running more PHP code in the // face of such exceptions. if (fault.m_faultType == Fault::Type::UserException && ThreadInfo::s_threadInfo->m_pendingException == nullptr) { auto const obj = fault.m_userException; for (auto& idOff : eh->m_catches) { FTRACE(1, "checkHandlers: catch candidate {}\n", idOff.second); auto handler = func->unit()->at(idOff.second); auto const cls = Unit::lookupClass( func->unit()->lookupNamedEntityId(idOff.first) ); if (!cls || !obj->instanceof(cls)) continue; FTRACE(1, "checkHandlers: entering catch at {}\n", idOff.second); pc = handler; DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook()); return UnwindAction::ResumeVM; } } break; } } if (eh->m_parentIndex != -1) { eh = &func->ehtab()[eh->m_parentIndex]; } else { break; } } return UnwindAction::Propagate; }
void throw_exception(const Object& e) { if (!e.instanceof(SystemLib::s_ExceptionClass)) { raise_error("Exceptions must be valid objects derived from the " "Exception base class"); } DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionThrownHook(e.get())); throw e; }
void EventHook::onFunctionExit(const ActRec* ar, const TypedValue* retval, const Fault* fault, ssize_t flags) { // Xenon if (flags & XenonSignalFlag) { Xenon::getInstance().log(Xenon::ExitSample); } // Run IntervalTimer callbacks only if it's safe to do so, i.e., not when // there's a pending exception or we're unwinding from a C++ exception. if (flags & IntervalTimerFlag && ThreadInfo::s_threadInfo->m_pendingException == nullptr && (!fault || fault->m_faultType == Fault::Type::UserException)) { IntervalTimer::RunCallbacks(IntervalTimer::ExitSample); } // Inlined calls normally skip the function enter and exit events. If we // side exit in an inlined callee, we short-circuit here in order to skip // exit events that could unbalance the call stack. if (RuntimeOption::EvalJit && ((jit::TCA) ar->m_savedRip == jit::mcg->tx().uniqueStubs.retInlHelper)) { return; } // User profiler if (flags & EventHookFlag) { auto profiler = ThreadInfo::s_threadInfo->m_profiler; if (profiler != nullptr && !(profiler->shouldSkipBuiltins() && ar->func()->isBuiltin())) { // NB: we don't have a function type flag to match what we got in // onFunctionEnter. That's okay, though... we tolerate this in // TraceProfiler. end_profiler_frame(profiler, retval, GetFunctionNameForProfiler(ar->func(), NormalFunc)); } if (shouldRunUserProfiler(ar->func())) { if (ThreadInfo::s_threadInfo->m_pendingException != nullptr) { // Avoid running PHP code when exception from destructor is pending. // TODO(#2329497) will not happen once CheckSurprise is used } else if (!fault) { runUserProfilerOnFunctionExit(ar, retval, nullptr); } else if (fault->m_faultType == Fault::Type::UserException) { runUserProfilerOnFunctionExit(ar, retval, fault->m_userException); } else { // Avoid running PHP code when unwinding C++ exception. } } } // Debugger hook if (flags & DebuggerHookFlag) { DEBUGGER_ATTACHED_ONLY(phpDebuggerFuncExitHook(ar)); } }
UnwindAction checkHandlers(const EHEnt* eh, const ActRec* const fp, PC& pc, Fault& fault) { auto const func = fp->m_func; ITRACE(1, "checkHandlers: func {} ({})\n", func->fullName()->data(), func->unit()->filepath()->data()); for (int i = 0;; ++i) { // Skip the initial m_handledCount - 1 handlers that were // considered before. if (fault.m_handledCount <= i) { fault.m_handledCount++; switch (eh->m_type) { case EHEnt::Type::Fault: ITRACE(1, "checkHandlers: entering fault at {}: save {}\n", eh->m_handler, func->unit()->offsetOf(pc)); pc = func->unit()->at(eh->m_handler); DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook()); return UnwindAction::ResumeVM; case EHEnt::Type::Catch: ITRACE(1, "checkHandlers: entering catch at {}\n", eh->m_handler); pc = func->unit()->at(eh->m_handler); DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook()); return UnwindAction::ResumeVM; } } if (eh->m_parentIndex != -1) { eh = &func->ehtab()[eh->m_parentIndex]; } else { break; } } return UnwindAction::Propagate; }
Unit* lookupUnit(StringData* path, const char* currentDir, bool* initial_opt) { bool init; bool& initial = initial_opt ? *initial_opt : init; initial = true; /* * NB: the m_evaledFiles map is only for the debugger, and could be omitted * in RepoAuthoritative mode, but currently isn't. */ struct stat s; auto const spath = resolveVmInclude(path, currentDir, &s); if (spath.isNull()) return nullptr; auto const eContext = g_context.getNoCheck(); // Check if this file has already been included. auto it = eContext->m_evaledFiles.find(spath.get()); if (it != end(eContext->m_evaledFiles)) { initial = false; return it->second; } // This file hasn't been included yet, so we need to parse the file auto const cunit = checkoutFile(spath.get(), s); if (cunit.unit && initial_opt) { // if initial_opt is not set, this shouldn't be recorded as a // per request fetch of the file. if (rds::testAndSetBit(cunit.rdsBitId)) { initial = false; } // if parsing was successful, update the mappings for spath and // rpath (if it exists). eContext->m_evaledFilesOrder.push_back(cunit.unit->filepath()); eContext->m_evaledFiles[spath.get()] = cunit.unit; spath.get()->incRefCount(); if (!cunit.unit->filepath()->same(spath.get())) { eContext->m_evaledFiles[cunit.unit->filepath()] = cunit.unit; } if (g_system_profiler) { g_system_profiler->fileLoadCallBack(path->toCppString()); } DEBUGGER_ATTACHED_ONLY(phpDebuggerFileLoadHook(cunit.unit)); } return cunit.unit; }
void EventHook::onFunctionExit(const ActRec* ar, const TypedValue* retval, const Fault* fault, ssize_t flags) { // Xenon if (flags & RequestInjectionData::XenonSignalFlag) { Xenon::getInstance().log(Xenon::ExitSample); } // Inlined calls normally skip the function enter and exit events. If we // side exit in an inlined callee, we short-circuit here in order to skip // exit events that could unbalance the call stack. if ((JIT::TCA) ar->m_savedRip == JIT::mcg->tx().uniqueStubs.retInlHelper) { return; } // User profiler if (flags & RequestInjectionData::EventHookFlag) { Profiler* profiler = ThreadInfo::s_threadInfo->m_profiler; if (profiler != nullptr) { // NB: we don't have a function type flag to match what we got in // onFunctionEnter. That's okay, though... we tolerate this in // TraceProfiler. end_profiler_frame(profiler, retval, GetFunctionNameForProfiler(ar->func(), NormalFunc)); } if (shouldRunUserProfiler(ar->func())) { if (ThreadInfo::s_threadInfo->m_pendingException != nullptr) { // Avoid running PHP code when exception from destructor is pending. // TODO(#2329497) will not happen once CheckSurprise is used } else if (!fault) { runUserProfilerOnFunctionExit(ar, retval, nullptr); } else if (fault->m_faultType == Fault::Type::UserException) { runUserProfilerOnFunctionExit(ar, retval, fault->m_userException); } else { // Avoid running PHP code when unwinding C++ exception. } } } // Debugger hook if (flags & RequestInjectionData::DebuggerHookFlag) { DEBUGGER_ATTACHED_ONLY(phpDebuggerFuncExitHook(ar)); } }
void EventHook::onFunctionEnter(const ActRec* ar, int funcType, ssize_t flags) { // User profiler if (flags & EventHookFlag) { if (shouldRunUserProfiler(ar->func())) { runUserProfilerOnFunctionEnter(ar); } auto profiler = ThreadInfo::s_threadInfo->m_profiler; if (profiler != nullptr && !(profiler->shouldSkipBuiltins() && ar->func()->isBuiltin())) { begin_profiler_frame(profiler, GetFunctionNameForProfiler(ar->func(), funcType)); } } // Debugger hook if (flags & DebuggerHookFlag) { DEBUGGER_ATTACHED_ONLY(phpDebuggerFuncEntryHook(ar)); } }
void EventHook::onFunctionEnter(const ActRec* ar, int funcType, ssize_t flags) { // Xenon if (flags & RequestInjectionData::XenonSignalFlag) { Xenon::getInstance().log(Xenon::EnterSample); } // User profiler if (flags & RequestInjectionData::EventHookFlag) { if (shouldRunUserProfiler(ar->func())) { runUserProfilerOnFunctionEnter(ar); } Profiler* profiler = ThreadInfo::s_threadInfo->m_profiler; if (profiler != nullptr) { begin_profiler_frame(profiler, GetFunctionNameForProfiler(ar->func(), funcType)); } } // Debugger hook if (flags & RequestInjectionData::DebuggerHookFlag) { DEBUGGER_ATTACHED_ONLY(phpDebuggerFuncEntryHook(ar)); } }
Class* Unit::defClass(PreClass* preClass, bool failIsFatal /* = true */) { Class* const* clsList = preClass->namedEntity()->clsList(); Class* top = *clsList; if (top) { Class *cls = top->getCached(); if (cls) { // Raise a fatal unless the existing class definition is identical to the // one this invocation would create. if (cls->preClass() != preClass) { if (failIsFatal) { raise_error("Class already declared: %s", preClass->name()->data()); } return NULL; } return cls; } } // Get a compatible Class, and add it to the list of defined classes. Class* parent = NULL; for (;;) { // Search for a compatible extant class. Searching from most to least // recently created may have better locality than alternative search orders. // In addition, its the only simple way to make this work lock free... for (Class* class_ = top; class_ != NULL; class_ = class_->m_nextClass) { if (class_->preClass() != preClass) continue; Class::Avail avail = class_->avail(parent, failIsFatal /*tryAutoload*/); if (LIKELY(avail == Class::AvailTrue)) { class_->setCached(); DEBUGGER_ATTACHED_ONLY(phpDefClassHook(class_)); return class_; } if (avail == Class::AvailFail) { if (failIsFatal) { raise_error("unknown class %s", parent->name()->data()); } return NULL; } ASSERT(avail == Class::AvailFalse); } // Create a new class. if (!parent && preClass->parent()->size() != 0) { parent = Unit::getClass(preClass->parent(), failIsFatal); if (parent == NULL) { if (failIsFatal) { raise_error("unknown class %s", preClass->parent()->data()); } return NULL; } } VMExecutionContext* ec = g_vmContext; ActRec* fp = ec->getFP(); PC pc = ec->getPC(); bool needsFrame = ec->m_stack.top() && (!fp || fp->m_func->unit() != preClass->unit()); if (needsFrame) { /* we can be called from Unit::merge, which hasnt yet setup the frame (because often it doesnt need to). Set up a fake frame here, in case of errors. But note that mergeUnit is called for systemlib etc before the stack has been setup. So dont do anything if m_stack.top() is NULL */ ActRec &tmp = *ec->m_stack.allocA(); tmp.m_savedRbp = (uint64_t)fp; tmp.m_savedRip = 0; tmp.m_func = preClass->unit()->getMain(); tmp.m_soff = preClass->getOffset() - tmp.m_func->base(); tmp.setThis(NULL); tmp.m_varEnv = 0; tmp.initNumArgs(0); ec->m_fp = &tmp; ec->m_pc = preClass->unit()->at(preClass->getOffset()); ec->pushLocalsAndIterators(tmp.m_func); } ClassPtr newClass(Class::newClass(preClass, parent)); if (needsFrame) { ec->m_stack.top() = (Cell*)(ec->m_fp+1); ec->m_fp = fp; ec->m_pc = pc; } Lock l(Unit::s_classesMutex); /* We could re-enter via Unit::getClass() or class_->avail(), so no need for *clsList to be volatile */ if (UNLIKELY(top != *clsList)) { top = *clsList; continue; } if (top) { newClass->m_cachedOffset = top->m_cachedOffset; } else { newClass->m_cachedOffset = Transl::TargetCache::allocKnownClass(preClass->name()); } newClass->m_nextClass = top; Util::compiler_membar(); *const_cast<Class**>(clsList) = newClass.get(); newClass.get()->incAtomicCount(); newClass.get()->setCached(); DEBUGGER_ATTACHED_ONLY(phpDefClassHook(newClass.get())); return newClass.get(); } }
UnwindAction checkHandlers(const EHEnt* eh, const ActRec* const fp, PC& pc, Fault& fault) { auto const func = fp->m_func; FTRACE(1, "checkHandlers: func {} ({})\n", func->fullName()->data(), func->unit()->filepath()->data()); /* * This code is repeatedly called with the same offset when an * exception is raised and rethrown by fault handlers. The * `faultNest' iterator is here to skip the EHEnt handlers that have * already been run for this in-flight exception. */ int faultNest = 0; for (;;) { assert(faultNest <= fault.m_handledCount); if (faultNest == fault.m_handledCount) { ++fault.m_handledCount; switch (eh->m_type) { case EHEnt::Type::Fault: FTRACE(1, "checkHandlers: entering fault at {}: save {}\n", eh->m_fault, func->unit()->offsetOf(pc)); fault.m_savedRaiseOffset = func->unit()->offsetOf(pc); pc = func->unit()->entry() + eh->m_fault; DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook()); return UnwindAction::ResumeVM; case EHEnt::Type::Catch: // Note: we skip catch clauses if we have a pending C++ exception // as part of our efforts to avoid running more PHP code in the // face of such exceptions. if (fault.m_faultType == Fault::Type::UserException && ThreadInfo::s_threadInfo->m_pendingException == nullptr) { auto const obj = fault.m_userException; for (auto& idOff : eh->m_catches) { FTRACE(1, "checkHandlers: catch candidate {}\n", idOff.second); auto handler = func->unit()->at(idOff.second); auto const cls = Unit::lookupClass( func->unit()->lookupNamedEntityId(idOff.first) ); if (!cls || !obj->instanceof(cls)) continue; FTRACE(1, "checkHandlers: entering catch at {}\n", idOff.second); pc = handler; DEBUGGER_ATTACHED_ONLY(phpDebuggerExceptionHandlerHook()); return UnwindAction::ResumeVM; } } break; } } if (eh->m_parentIndex != -1) { eh = &func->ehtab()[eh->m_parentIndex]; } else { break; } ++faultNest; } return UnwindAction::Propagate; }
Unit* lookupUnit(StringData* path, const char* currentDir, bool* initial_opt) { bool init; bool& initial = initial_opt ? *initial_opt : init; initial = true; /* * NB: the m_evaledFiles map is only for the debugger, and could be omitted * in RepoAuthoritative mode, but currently isn't. */ struct stat s; auto const spath = resolveVmInclude(path, currentDir, &s); if (spath.isNull()) return nullptr; auto const eContext = g_context.getNoCheck(); // Check if this file has already been included. auto it = eContext->m_evaledFiles.find(spath.get()); if (it != end(eContext->m_evaledFiles)) { initial = false; return it->second; } // We didn't find it, so try the realpath. auto const alreadyResolved = RuntimeOption::RepoAuthoritative || (!RuntimeOption::CheckSymLink && (spath[0] == '/')); bool hasRealpath = false; String rpath; if (!alreadyResolved) { std::string rp = StatCache::realpath(spath.data()); if (rp.size() != 0) { rpath = StringData::Make(rp.data(), rp.size(), CopyString); if (!rpath.same(spath)) { hasRealpath = true; it = eContext->m_evaledFiles.find(rpath.get()); if (it != eContext->m_evaledFiles.end()) { // We found it! Update the mapping for spath and return the // unit. auto const unit = it->second; spath.get()->incRefCount(); eContext->m_evaledFiles[spath.get()] = unit; initial = false; return unit; } } } } // This file hasn't been included yet, so we need to parse the file auto const cunit = checkoutFile(hasRealpath ? rpath.get() : spath.get(), s); if (cunit.unit && initial_opt) { // if initial_opt is not set, this shouldn't be recorded as a // per request fetch of the file. if (RDS::testAndSetBit(cunit.rdsBitId)) { initial = false; } // if parsing was successful, update the mappings for spath and // rpath (if it exists). eContext->m_evaledFilesOrder.push_back(cunit.unit->filepath()); eContext->m_evaledFiles[spath.get()] = cunit.unit; spath.get()->incRefCount(); if (hasRealpath) { eContext->m_evaledFiles[rpath.get()] = cunit.unit; rpath.get()->incRefCount(); } DEBUGGER_ATTACHED_ONLY(phpDebuggerFileLoadHook(cunit.unit)); } return cunit.unit; }