2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present 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 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/event-hook.h"
19 #include "hphp/runtime/base/array-init.h"
20 #include "hphp/runtime/base/array-iterator.h"
21 #include "hphp/runtime/base/container-functions.h"
22 #include "hphp/runtime/base/backtrace.h"
23 #include "hphp/runtime/base/implicit-context.h"
24 #include "hphp/runtime/base/intercept.h"
25 #include "hphp/runtime/base/surprise-flags.h"
26 #include "hphp/runtime/base/tv-refcount.h"
27 #include "hphp/runtime/base/type-structure-helpers-defs.h"
28 #include "hphp/runtime/base/variable-serializer.h"
30 #include "hphp/runtime/ext/asio/asio-session.h"
31 #include "hphp/runtime/ext/intervaltimer/ext_intervaltimer.h"
32 #include "hphp/runtime/ext/std/ext_std_function.h"
33 #include "hphp/runtime/ext/std/ext_std_variable.h"
34 #include "hphp/runtime/ext/strobelight/ext_strobelight.h"
35 #include "hphp/runtime/ext/xenon/ext_xenon.h"
37 #include "hphp/runtime/server/server-stats.h"
39 #include "hphp/runtime/vm/jit/tc.h"
40 #include "hphp/runtime/vm/jit/translator-inline.h"
41 #include "hphp/runtime/vm/func.h"
42 #include "hphp/runtime/vm/resumable.h"
43 #include "hphp/runtime/vm/runtime.h"
44 #include "hphp/runtime/vm/interp-helpers.h"
45 #include "hphp/runtime/vm/unit-util.h"
46 #include "hphp/runtime/vm/vm-regs.h"
48 #include "hphp/util/configs/eval.h"
49 #include "hphp/util/configs/jit.h"
50 #include "hphp/util/configs/setprofile.h"
51 #include "hphp/util/struct-log.h"
54 ///////////////////////////////////////////////////////////////////////////////
58 s_frame_ptr("frame_ptr"),
59 s_parent_frame_ptr("parent_frame_ptr"),
60 s_this_ptr("this_ptr"),
61 s_this_obj("this_obj"),
68 s_exception("exception"),
70 s_reified_classes("reified_classes"),
71 s_reified_generics_var("0ReifiedGenerics");
73 RDS_LOCAL_NO_CHECK(uint64_t, rl_func_sequence_id
){0};
75 void EventHook::Enable() {
76 setSurpriseFlag(EventHookFlag
);
79 void EventHook::Disable() {
80 if (g_context
->m_internalEventHookCallback
== nullptr) {
81 clearSurpriseFlag(EventHookFlag
);
85 void EventHook::EnableInternal(ExecutionContext::InternalEventHookCallbackType
87 g_context
->m_internalEventHookCallback
= callback
;
88 setSurpriseFlag(EventHookFlag
);
91 void EventHook::DisableInternal() {
92 if (g_context
->m_setprofileCallback
.isNull()) {
93 clearSurpriseFlag(EventHookFlag
);
95 g_context
->m_internalEventHookCallback
= nullptr;
98 void EventHook::EnableAsync() {
99 setSurpriseFlag(AsyncEventHookFlag
);
102 void EventHook::DisableAsync() {
103 clearSurpriseFlag(AsyncEventHookFlag
);
106 void EventHook::EnableDebug() {
107 setSurpriseFlag(DebuggerHookFlag
);
110 void EventHook::DisableDebug() {
111 clearSurpriseFlag(DebuggerHookFlag
);
114 void EventHook::EnableIntercept() {
115 setSurpriseFlag(InterceptFlag
);
118 void EventHook::DisableIntercept() {
119 clearSurpriseFlag(InterceptFlag
);
122 struct ExecutingSetprofileCallbackGuard
{
123 ExecutingSetprofileCallbackGuard() {
124 g_context
->m_executingSetprofileCallback
= true;
127 ~ExecutingSetprofileCallbackGuard() {
128 g_context
->m_executingSetprofileCallback
= false;
132 void EventHook::DoMemoryThresholdCallback() {
133 clearSurpriseFlag(MemThresholdFlag
);
134 if (!g_context
->m_memThresholdCallback
.isNull()) {
137 vm_call_user_func(g_context
->m_memThresholdCallback
, empty_vec_array());
138 } catch (Object
& ex
) {
139 raise_error("Uncaught exception escaping mem Threshold callback: %s",
140 throwable_to_string(ex
.get()).data());
147 bool shouldRunUserProfiler(const Func
* func
) {
148 // Don't do anything if we are running the profiling function itself
149 // or if we haven't set up a profiler.
150 if (g_context
->m_executingSetprofileCallback
||
151 g_context
->m_setprofileCallback
.isNull() ||
152 (!g_context
->m_setprofileFunctions
.empty() &&
153 g_context
->m_setprofileFunctions
.count(
154 StrNR(func
->fullName())) == 0)) {
157 // Don't profile 86ctor, since its an implementation detail,
158 // and we dont guarantee to call it
159 if ((g_context
->m_setprofileFlags
& EventHook::ProfileConstructors
) == 0 &&
160 func
->cls() && func
== func
->cls()->getCtor() &&
161 Func::isSpecial(func
->name())) {
167 Array
getReifiedClasses(const ActRec
* ar
) {
168 using K
= TypeStructure::Kind
;
170 auto const func
= ar
->func();
171 auto const& tparams
= func
->getReifiedGenericsInfo();
172 VecInit clist
{tparams
.m_typeParamInfo
.size()};
174 auto const loc
= frame_local(ar
, ar
->func()->numParams());
176 func
->localVarName(func
->numParams()) == s_reified_generics_var
.get()
178 assertx(isArrayLikeType(type(loc
)));
181 auto const generics
= val(loc
).parr
;
183 for (auto const& pinfo
: tparams
.m_typeParamInfo
) {
184 if (!pinfo
.m_isReified
) {
185 clist
.append(init_null());
189 auto const ts
= generics
->at(idx
++);
190 assertx(isArrayLikeType(type(ts
)));
192 auto const kind
= get_ts_kind(val(ts
).parr
);
200 String
{const_cast<StringData
*>(get_ts_classname(val(ts
).parr
))}
205 clist
.append(String
{xhpNameFromTS(ArrNR
{val(ts
).parr
})});
209 case K::T_recursiveUnion
:
227 case K::T_vec_or_dict
:
231 case K::T_varray_or_darray
:
237 case K::T_class_or_classname
:
238 clist
.append(init_null());
241 case K::T_unresolved
:
242 case K::T_typeaccess
:
243 case K::T_reifiedtype
:
244 // These type structures should always be resolved
245 always_assert(false);
249 return clist
.toArray();
253 ActRec
* getParentFrame(const ActRec
* ar
, Offset
* prevPc
= nullptr) {
254 return g_context
->getPrevVMStateSkipFrame(ar
, prevPc
);
257 void addFramePointers(const ActRec
* ar
, Array
& frameinfo
, bool isCall
) {
258 if ((g_context
->m_setprofileFlags
& EventHook::ProfileFramePointers
) == 0) {
261 if (frameinfo
.isNull()) {
262 frameinfo
= Array::CreateDict();
266 auto this_
= ar
->func()->cls() && ar
->hasThis() ?
267 ar
->getThis() : nullptr;
268 frameinfo
.set(s_this_ptr
, Variant(intptr_t(this_
)));
270 if ((g_context
->m_setprofileFlags
& EventHook::ProfileThisObject
) != 0
271 && !Cfg::SetProfile::NullThisObject
&& this_
) {
272 frameinfo
.set(s_this_obj
, Variant(this_
));
276 frameinfo
.set(s_frame_ptr
, Variant(intptr_t(ar
)));
277 ActRec
* parent_ar
= getParentFrame(ar
);
278 if (parent_ar
!= nullptr) {
279 frameinfo
.set(s_parent_frame_ptr
, Variant(intptr_t(parent_ar
)));
283 void addFileLine(const ActRec
* ar
, Array
& frameinfo
) {
284 if ((g_context
->m_setprofileFlags
& EventHook::ProfileFileLine
) != 0) {
286 ActRec
* parent_ar
= getParentFrame(ar
, &offset
);
287 if (parent_ar
!= nullptr) {
288 frameinfo
.set(s_file
, Variant(const_cast<StringData
*>(parent_ar
->func()->filename())));
289 frameinfo
.set(s_line
, Variant(parent_ar
->func()->getLineNumber(offset
)));
294 inline bool isResumeAware() {
295 return (g_context
->m_setprofileFlags
& EventHook::ProfileResumeAware
) != 0;
298 void runInternalEventHook(const ActRec
* ar
,
299 ExecutionContext::InternalEventHook event_type
) {
300 if (g_context
->m_internalEventHookCallback
!= nullptr) {
301 g_context
->m_internalEventHookCallback(ar
, event_type
);
305 void runUserProfilerOnFunctionEnter(const ActRec
* ar
, bool isResume
) {
306 if ((g_context
->m_setprofileFlags
& EventHook::ProfileEnters
) == 0) {
311 ExecutingSetprofileCallbackGuard guard
;
314 vm_decode_function(g_context
->m_setprofileCallback
, ctx
);
315 auto const func
= ctx
.func
;
320 frameinfo
= Array::attach(ArrayData::CreateDict());
322 // Add arguments only if this is a function call.
323 frameinfo
.set(s_args
, hhvm_get_frame_args(ar
));
327 addFramePointers(ar
, frameinfo
, !isResume
);
329 addFileLine(ar
, frameinfo
);
332 if (!isResume
&& ar
->func()->hasReifiedGenerics()) {
333 // Add reified generics only if this is a function call.
334 frameinfo
.set(s_reified_classes
, getReifiedClasses(ar
));
337 const auto params
= make_vec_array(
338 (isResume
&& isResumeAware()) ? s_resume
: s_enter
,
339 StrNR
{ar
->func()->fullNameWithClosureName()},
343 g_context
->invokeFunc(func
, params
, ctx
.this_
, ctx
.cls
,
344 RuntimeCoeffects::defaults(), ctx
.dynamic
);
347 void runUserProfilerOnFunctionExit(const ActRec
* ar
, const TypedValue
* retval
,
348 ObjectData
* exception
, bool isSuspend
) {
349 if ((g_context
->m_setprofileFlags
& EventHook::ProfileExits
) == 0) {
354 ExecutingSetprofileCallbackGuard guard
;
357 vm_decode_function(g_context
->m_setprofileCallback
, ctx
);
358 auto const func
= ctx
.func
;
364 frameinfo
= make_dict_array(s_return
, tvAsCVarRef(retval
));
365 } else if (exception
) {
366 frameinfo
= make_dict_array(s_exception
, Variant
{exception
});
369 addFramePointers(ar
, frameinfo
, false);
371 const auto params
= make_vec_array(
372 (isSuspend
&& isResumeAware()) ? s_suspend
: s_exit
,
373 StrNR
{ar
->func()->fullNameWithClosureName()},
377 g_context
->invokeFunc(func
, params
, ctx
.this_
, ctx
.cls
,
378 RuntimeCoeffects::defaults(), ctx
.dynamic
);
381 static Variant
call_intercept_handler(
382 const Variant
& function
,
383 const Variant
& called
,
384 const Variant
& called_on
,
389 vm_decode_function(function
, callCtx
);
390 auto f
= callCtx
.func
;
391 if (!f
) return uninit_null();
393 auto const okay
= [&] {
394 if (f
->numInOutParams() != 1) return false;
395 return f
->params().size() >= 3 && f
->isInOut(2);
400 "fb_intercept2 used with an inout handler with a bad signature "
401 "(expected parameter three to be inout)"
405 args
= hhvm_get_frame_args(ar
);
409 par
.append(called_on
);
412 auto ret
= Variant::attach(
413 g_context
->invokeFunc(f
, par
.toVariant(), callCtx
.this_
, callCtx
.cls
,
414 RuntimeCoeffects::defaults(), callCtx
.dynamic
)
417 auto& arr
= ret
.asCArrRef();
418 if (arr
[1].isArray()) args
= arr
[1].toArray();
423 s_callback("callback"),
424 s_prepend_this("prepend_this");
426 static Variant
call_intercept_handler_callback(
428 const Variant
& function
,
433 auto const origCallee
= ar
->func();
434 vm_decode_function(function
, callCtx
, DecodeFlags::Warn
, true);
435 auto f
= callCtx
.func
;
436 if (!f
) return uninit_null();
437 if (origCallee
->hasReifiedGenerics() ^ f
->hasReifiedGenerics()) {
438 SystemLib::throwRuntimeExceptionObject(
439 Variant("Mismatch between reifiedness of callee and callback"));
441 if (f
->takesInOutParams()) {
442 //TODO(T52751806): Support inout arguments on fb_intercept2 callback
443 SystemLib::throwRuntimeExceptionObject(
444 Variant("The callback for fb_intercept2 cannot have inout parameters"));
447 auto const args
= [&]{
448 auto const curArgs
= hhvm_get_frame_args(ar
);
449 VecInit
args(prepend_this
+ curArgs
.size());
451 auto const thiz
= [&] {
452 if (!origCallee
->cls()) return make_tv
<KindOfNull
>();
453 if (ar
->hasThis()) return make_tv
<KindOfObject
>(ar
->getThis());
454 return make_tv
<KindOfClass
>(ar
->getClass());
458 IterateV(curArgs
.get(), [&](TypedValue v
) { args
.append(v
); });
459 return args
.toArray();
461 auto reifiedGenerics
= [&] {
462 if (!origCallee
->hasReifiedGenerics()) return Array();
463 // Reified generics is the first non param local
464 auto const generics
= frame_local(ar
, origCallee
->numParams());
465 assertx(tvIsVec(generics
));
466 return Array(val(generics
).parr
);
468 return Variant::attach(
469 g_context
->invokeFunc(f
, args
, callCtx
.this_
, callCtx
.cls
,
470 RuntimeCoeffects::defaults(),
471 callCtx
.dynamic
, false, false, readonly_return
,
472 std::move(reifiedGenerics
))
478 bool EventHook::RunInterceptHandler(ActRec
* ar
) {
479 assertx(!isResumed(ar
));
481 const Func
* func
= ar
->func();
482 if (LIKELY(!Cfg::Eval::FastMethodIntercept
&& !func
->maybeIntercepted())) {
485 assertx(func
->isInterceptable() && func
->maybeIntercepted());
487 Variant
* h
= get_intercept_handler(func
);
493 // Callee frame already decrefd.
494 if (vmfp() != ar
) return;
496 // Decref the locals of the callee frame, signalling to the unwinder that
497 // the frame is already being exited and exception handlers should not be
499 ar
->setLocalsDecRefd();
500 frame_free_locals_inl_no_hook(ar
, ar
->func()->numFuncEntryInputs());
506 Variant called_on
= [&] {
509 return Variant(ar
->getThis());
511 // For static methods, give handler the name of called class
512 return Variant
{ar
->getClass()->name(), Variant::PersistentStrInit
{}};
518 VarNR
called(ar
->func()->fullName());
520 Variant ret
= call_intercept_handler(*h
, called
, called_on
, args
, ar
);
522 if (!ret
.isArray()) {
523 SystemLib::throwRuntimeExceptionObject(
524 Variant("fb_intercept2 requires a darray to be returned"));
526 assertx(ret
.isArray());
527 auto const retArr
= ret
.toDict();
528 if (retArr
.exists(s_value
)) {
529 ret
= retArr
[s_value
];
530 } else if (retArr
.exists(s_callback
)) {
531 bool prepend_this
= retArr
.exists(s_prepend_this
) ?
532 retArr
[s_prepend_this
].toBoolean() : false;
533 bool readonly_return
= func
->attrs() & AttrReadonlyReturn
;
534 ret
= call_intercept_handler_callback(
541 // neither value or callback are present, call the original function
546 auto const sfp
= ar
->sfp();
547 auto const callOff
= ar
->callOffset();
549 Stack
& stack
= vmStack();
550 auto const trim
= [&] {
551 ar
->setLocalsDecRefd();
552 frame_free_locals_inl_no_hook(ar
, ar
->func()->numFuncEntryInputs());
554 // Tear down the callee frame, then push the return value.
555 stack
.trim((TypedValue
*)(ar
+ 1));
557 // Until the caller frame is loaded, remove references to the dead ActRec
562 if (UNLIKELY(func
->takesInOutParams())) {
563 assertx(!args
.isNull());
564 uint32_t count
= func
->numInOutParams();
566 trim(); // discard the callee frame before pushing inout values
567 auto start
= stack
.topTV();
568 auto const end
= start
+ count
;
570 auto push
= [&] (TypedValue v
) {
571 assertx(start
< end
);
577 IterateKV(args
.get(), [&] (TypedValue
, TypedValue v
) {
578 if (param
>= func
->numParams() || !func
->isInOut(param
++)) {
584 while (start
< end
) push(make_tv
<KindOfNull
>());
586 trim(); // nothing to do throw away the frame
589 tvDup(*ret
.asTypedValue(), *stack
.allocTV());
590 stack
.topTV()->m_aux
.u_asyncEagerReturnFlag
= 0;
592 // Return to the caller or exit VM.
594 vmpc() = LIKELY(sfp
!= nullptr)
595 ? skipCall(sfp
->func()->entry() + callOff
)
605 static bool shouldLog(const Func
* /*func*/) {
606 return RID().logFunctionCalls();
609 void logCommon(StructuredLogEntry sample
, const ActRec
* ar
, ssize_t flags
) {
610 sample
.setInt("flags", flags
);
611 sample
.setInt("func_id", ar
->func()->getFuncId().toInt());
612 sample
.setStr("thread_mode",
613 ServerStats::ThreadModeString(ServerStats::GetThreadMode()));
614 sample
.setStr("func_name", ar
->func()->name()->data());
615 sample
.setStr("func_full_name", ar
->func()->fullName()->data());
616 sample
.setStr("func_filename", ar
->func()->filename()->data());
617 addBacktraceToStructLog(
618 createBacktrace(BacktraceArgs()), sample
620 sample
.setInt("sequence_id", (*rl_func_sequence_id
)++);
621 StructuredLog::log("hhvm_function_calls2", sample
);
624 void EventHook::onFunctionEnter(const ActRec
* ar
, int funcType
,
625 ssize_t flags
, bool isResume
) {
626 if (flags
& HeapSamplingFlag
) {
627 gather_alloc_stack(/* skipTop */ true);
631 if (flags
& EventHookFlag
) {
632 if (shouldRunUserProfiler(ar
->func())) {
633 runUserProfilerOnFunctionEnter(ar
, isResume
);
635 runInternalEventHook(ar
, isResume
636 ? ExecutionContext::InternalEventHook::Resume
637 : ExecutionContext::InternalEventHook::Call
);
638 if (shouldLog(ar
->func())) {
639 StructuredLogEntry sample
;
640 sample
.setStr("event_name", "function_enter");
641 sample
.setInt("func_type", funcType
);
642 sample
.setInt("is_resume", isResume
);
643 logCommon(sample
, ar
, flags
);
648 if (flags
& DebuggerHookFlag
) {
649 DEBUGGER_ATTACHED_ONLY(phpDebuggerFuncEntryHook(ar
, isResume
));
653 void EventHook::onFunctionExit(const ActRec
* ar
, const TypedValue
* retval
,
654 bool unwind
, ObjectData
* phpException
,
655 size_t flags
, bool isSuspend
,
656 EventHook::Source sourceType
) {
657 // Inlined calls normally skip the function enter and exit events. If we
658 // side exit in an inlined callee, we short-circuit here in order to skip
659 // exit events that could unbalance the call stack.
660 if (Cfg::Jit::Enabled
&& ar
->isInlined()) {
665 if (flags
& XenonSignalFlag
) {
666 if (Strobelight::active()) {
667 Strobelight::getInstance().log(Xenon::ExitSample
);
669 Xenon::getInstance().log(Xenon::ExitSample
, sourceType
);
673 if (flags
& HeapSamplingFlag
) {
674 gather_alloc_stack();
677 // Run callbacks only if it's safe to do so, i.e., not when
678 // there's a pending exception or we're unwinding from a C++ exception.
679 // It's not safe to run callbacks when we are in the middle of resolving a
680 // constant since we maintain a static array with sentinel bits to detect
681 // recursively defined constants which could be modified by a callback.
682 if (RequestInfo::s_requestInfo
->m_pendingException
== nullptr
683 && (!unwind
|| phpException
)
684 && ar
->func()->name() != s_86cinit
.get()) {
687 if (flags
& MemThresholdFlag
) {
688 DoMemoryThresholdCallback();
691 if (flags
& TimedOutFlag
) {
692 RID().invokeUserTimeoutCallback();
696 if (flags
& IntervalTimerFlag
) {
697 IntervalTimer::RunCallbacks(IntervalTimer::ExitSample
);
702 if (flags
& EventHookFlag
) {
703 if (shouldRunUserProfiler(ar
->func())) {
704 if (RequestInfo::s_requestInfo
->m_pendingException
!= nullptr) {
705 // Avoid running PHP code when exception from destructor is pending.
706 // TODO(#2329497) will not happen once CheckSurprise is used
707 } else if (!unwind
) {
708 runUserProfilerOnFunctionExit(ar
, retval
, nullptr, isSuspend
);
709 } else if (phpException
) {
710 runUserProfilerOnFunctionExit(ar
, retval
, phpException
, isSuspend
);
712 // Avoid running PHP code when unwinding C++ exception.
716 runInternalEventHook(ar
, isSuspend
717 ? ExecutionContext::InternalEventHook::Suspend
718 : ExecutionContext::InternalEventHook::Return
);
720 runInternalEventHook(ar
, ExecutionContext::InternalEventHook::Unwind
);
722 if (shouldLog(ar
->func())) {
723 StructuredLogEntry sample
;
724 sample
.setStr("event_name", "function_exit");
725 sample
.setInt("is_suspend", isSuspend
);
726 logCommon(sample
, ar
, flags
);
731 if (flags
& DebuggerHookFlag
) {
732 DEBUGGER_ATTACHED_ONLY(phpDebuggerFuncExitHook(ar
, isSuspend
));
736 uint64_t EventHook::onFunctionCallJit(const ActRec
* ar
, int funcType
) {
737 auto const savedRip
= ar
->m_savedRip
;
738 if (EventHook::onFunctionCall(ar
, funcType
, EventHook::Source::Jit
)) {
739 // Not intercepted, no return address.
743 // We may have entered with the CLEAN_VERIFY state set by the JIT, but
744 // the functionEnterHelper is going to return to the parent frame, bypassing
745 // the JIT logic that resets this state. Set the state to DIRTY so it won't
746 // leak to other C++ calls.
747 regState() = VMRegState::DIRTY
;
749 // If we entered this frame from the interpreter, use the resumeHelper,
750 // as the retHelper logic has been already performed and the frame has
751 // been overwritten by the return value.
752 if (isReturnHelper(savedRip
)) {
754 reinterpret_cast<uintptr_t>(jit::tc::ustubs().resumeHelperFromInterp
);
759 bool EventHook::onFunctionCall(const ActRec
* ar
, int funcType
,
760 EventHook::Source sourceType
) {
761 assertx(!isResumed(ar
));
762 const Func
* func
= ar
->func();
763 auto const flags
= handle_request_surprise();
765 if ((Cfg::Eval::FastMethodIntercept
&& func
->maybeIntercepted() && is_intercepted(func
))
766 || (!Cfg::Eval::FastMethodIntercept
&& (flags
& InterceptFlag
))) {
767 if (!RunInterceptHandler(const_cast<ActRec
*>(ar
))) {
768 assertx(func
->isInterceptable());
774 if (flags
& XenonSignalFlag
) {
775 if (Strobelight::active()) {
776 Strobelight::getInstance().log(Xenon::EnterSample
);
778 Xenon::getInstance().log(Xenon::EnterSample
, sourceType
);
782 // It's not safe to run callbacks when we are in the middle of resolving a
783 // constant since we maintain a static array with sentinel bits to detect
784 // recursively defined constants which could be modified by a callback.
785 if (func
->name() != s_86cinit
.get()) {
787 if (flags
& MemThresholdFlag
) {
788 DoMemoryThresholdCallback();
791 if (flags
& TimedOutFlag
) {
792 RID().invokeUserTimeoutCallback();
795 if (flags
& IntervalTimerFlag
) {
796 IntervalTimer::RunCallbacks(IntervalTimer::EnterSample
);
800 onFunctionEnter(ar
, funcType
, flags
, false);
804 void EventHook::onFunctionResumeAwait(const ActRec
* ar
,
805 EventHook::Source sourceType
) {
806 auto const flags
= handle_request_surprise();
809 if (flags
& XenonSignalFlag
) {
810 if (Strobelight::active()) {
811 Strobelight::getInstance().log(Xenon::ResumeAwaitSample
);
813 Xenon::getInstance().log(Xenon::ResumeAwaitSample
, sourceType
);
818 if (flags
& MemThresholdFlag
) {
819 DoMemoryThresholdCallback();
822 if (flags
& TimedOutFlag
) {
823 RID().invokeUserTimeoutCallback();
826 if (flags
& IntervalTimerFlag
) {
827 IntervalTimer::RunCallbacks(IntervalTimer::ResumeAwaitSample
);
830 onFunctionEnter(ar
, EventHook::NormalFunc
, flags
, true);
833 void EventHook::onFunctionResumeYield(const ActRec
* ar
,
834 EventHook::Source sourceType
) {
835 auto const flags
= handle_request_surprise();
838 if (flags
& XenonSignalFlag
) {
839 if (Strobelight::active()) {
840 Strobelight::getInstance().log(Xenon::EnterSample
);
842 Xenon::getInstance().log(Xenon::EnterSample
, sourceType
);
847 if (flags
& MemThresholdFlag
) {
848 DoMemoryThresholdCallback();
851 if (flags
& TimedOutFlag
) {
852 RID().invokeUserTimeoutCallback();
855 if (flags
& IntervalTimerFlag
) {
856 IntervalTimer::RunCallbacks(IntervalTimer::EnterSample
);
859 onFunctionEnter(ar
, EventHook::NormalFunc
, flags
, true);
862 void EventHook::onFunctionSuspendAwaitEFJit(ActRec
* suspending
,
863 const ActRec
* resumableAR
) {
864 EventHook::onFunctionSuspendAwaitEF(
867 EventHook::Source::Jit
871 // Eagerly executed async function initially suspending at Await.
872 void EventHook::onFunctionSuspendAwaitEF(ActRec
* suspending
,
873 const ActRec
* resumableAR
,
874 EventHook::Source sourceType
) {
875 assertx(suspending
->func()->isAsyncFunction());
876 assertx(suspending
->func() == resumableAR
->func());
877 assertx(isResumed(resumableAR
));
879 // The locals were already teleported from suspending to resumableAR.
880 suspending
->setLocalsDecRefd();
881 suspending
->trashThis();
884 auto const flags
= handle_request_surprise();
885 onFunctionExit(resumableAR
, nullptr, false, nullptr, flags
, true, sourceType
);
887 if (flags
& AsyncEventHookFlag
) {
888 auto const afwh
= frame_afwh(resumableAR
);
889 auto const session
= AsioSession::Get();
890 if (session
->hasOnResumableCreate()) {
891 session
->onResumableCreate(afwh
, afwh
->getChild());
895 decRefObj(frame_afwh(resumableAR
));
900 void EventHook::onFunctionSuspendAwaitEGJit(ActRec
* suspending
) {
901 EventHook::onFunctionSuspendAwaitEG(suspending
, EventHook::Source::Jit
);
904 // Eagerly executed async generator that was resumed at Yield suspending
906 void EventHook::onFunctionSuspendAwaitEG(ActRec
* suspending
,
907 EventHook::Source sourceType
) {
908 assertx(suspending
->func()->isAsyncGenerator());
909 assertx(isResumed(suspending
));
911 // The generator is still being executed eagerly, make it look like that.
912 auto const gen
= frame_async_generator(suspending
);
913 auto wh
= gen
->detachWaitHandle();
914 SCOPE_EXIT
{ gen
->attachWaitHandle(std::move(wh
)); };
917 auto const flags
= handle_request_surprise();
918 onFunctionExit(suspending
, nullptr, false, nullptr, flags
, true, sourceType
);
920 if (flags
& AsyncEventHookFlag
) {
921 auto const session
= AsioSession::Get();
922 if (session
->hasOnResumableCreate()) {
923 session
->onResumableCreate(wh
.get(), wh
->getChild());
932 void EventHook::onFunctionSuspendAwaitRJit(ActRec
* suspending
, ObjectData
* child
) {
933 EventHook::onFunctionSuspendAwaitR(suspending
, child
, EventHook::Source::Jit
);
936 // Async function or async generator that was resumed at Await suspending
937 // again at Await. The suspending frame has an associated AFWH/AGWH. Child
938 // is the WH we are going to block on.
939 void EventHook::onFunctionSuspendAwaitR(ActRec
* suspending
,
941 EventHook::Source sourceType
) {
942 assertx(suspending
->func()->isAsync());
943 assertx(isResumed(suspending
));
944 assertx(child
->isWaitHandle());
945 assertx(!static_cast<c_Awaitable
*>(child
)->isFinished());
947 auto const flags
= handle_request_surprise();
948 onFunctionExit(suspending
, nullptr, false, nullptr, flags
, true, sourceType
);
950 if (flags
& AsyncEventHookFlag
) {
951 assertx(child
->instanceof(c_WaitableWaitHandle::classof()));
952 auto const session
= AsioSession::Get();
953 if (session
->hasOnResumableAwait()) {
954 if (!suspending
->func()->isGenerator()) {
955 session
->onResumableAwait(
956 frame_afwh(suspending
),
957 static_cast<c_WaitableWaitHandle
*>(child
)
960 session
->onResumableAwait(
961 frame_async_generator(suspending
)->getWaitHandle(),
962 static_cast<c_WaitableWaitHandle
*>(child
)
968 if (UNLIKELY(static_cast<c_Awaitable
*>(child
)->isFinished())) {
969 SystemLib::throwInvalidOperationExceptionObject(
970 "The suspend await event hook entered a new asio scheduler context and "
971 "awaited the same child, revoking the reason for suspension.");
975 void EventHook::onFunctionSuspendCreateContJit(ActRec
* suspending
,
976 const ActRec
* resumableAR
) {
977 EventHook::onFunctionSuspendCreateCont(
980 EventHook::Source::Jit
984 // Generator or async generator suspending initially at CreateCont.
985 void EventHook::onFunctionSuspendCreateCont(ActRec
* suspending
,
986 const ActRec
* resumableAR
,
987 EventHook::Source sourceType
) {
988 assertx(suspending
->func()->isGenerator());
989 assertx(suspending
->func() == resumableAR
->func());
990 assertx(isResumed(resumableAR
));
992 // The locals were already teleported from suspending to resumableAR.
993 suspending
->setLocalsDecRefd();
994 suspending
->trashThis();
997 auto const flags
= handle_request_surprise();
998 onFunctionExit(resumableAR
, nullptr, false, nullptr, flags
, true, sourceType
);
1000 auto const resumableObj
= [&]() -> ObjectData
* {
1001 return !resumableAR
->func()->isAsync()
1002 ? frame_generator(resumableAR
)->toObject()
1003 : frame_async_generator(resumableAR
)->toObject();
1005 decRefObj(resumableObj
);
1010 void EventHook::onFunctionSuspendYieldJit(ActRec
* suspending
) {
1011 EventHook::onFunctionSuspendYield(suspending
, EventHook::Source::Jit
);
1014 // Generator or async generator suspending at Yield.
1015 void EventHook::onFunctionSuspendYield(ActRec
* suspending
, EventHook::Source sourceType
) {
1016 assertx(suspending
->func()->isGenerator());
1017 assertx(isResumed(suspending
));
1019 auto const flags
= handle_request_surprise();
1020 onFunctionExit(suspending
, nullptr, false, nullptr, flags
, true, sourceType
);
1022 if ((flags
& AsyncEventHookFlag
) && suspending
->func()->isAsync()) {
1023 auto const ag
= frame_async_generator(suspending
);
1024 if (!ag
->isEagerlyExecuted()) {
1025 auto const wh
= ag
->getWaitHandle();
1026 auto const session
= AsioSession::Get();
1027 if (session
->hasOnResumableSuccess()) {
1028 session
->onResumableSuccess(wh
, init_null());
1034 void EventHook::onFunctionReturnJit(ActRec
* ar
, TypedValue retval
) {
1035 EventHook::onFunctionReturn(ar
, retval
, EventHook::Source::Jit
);
1038 void EventHook::onFunctionReturn(ActRec
* ar
,
1040 EventHook::Source sourceType
) {
1041 // The locals are already gone. Tell everyone.
1042 ar
->setLocalsDecRefd();
1046 auto const flags
= handle_request_surprise();
1047 onFunctionExit(ar
, &retval
, false, nullptr, flags
, false, sourceType
);
1050 if ((flags
& AsyncEventHookFlag
) && isResumed(ar
) &&
1051 ar
->func()->isAsync()) {
1052 auto session
= AsioSession::Get();
1053 if (session
->hasOnResumableSuccess()) {
1054 if (!ar
->func()->isGenerator()) {
1055 session
->onResumableSuccess(frame_afwh(ar
), tvAsCVarRef(retval
));
1057 auto ag
= frame_async_generator(ar
);
1058 if (!ag
->isEagerlyExecuted()) {
1059 session
->onResumableSuccess(ag
->getWaitHandle(), init_null());
1066 * We're responsible for freeing the return value if we exit with an
1067 * exception. See irgen-ret.
1069 tvDecRefGen(retval
);
1074 void EventHook::onFunctionUnwind(ActRec
* ar
,
1075 ObjectData
* phpException
) {
1076 // The locals are already gone. Tell everyone.
1077 ar
->setLocalsDecRefd();
1080 // TODO(#2329497) can't handle_request_surprise() yet, unwinder unable to
1082 auto const flags
= stackLimitAndSurprise().load() & kSurpriseFlagMask
;
1083 onFunctionExit(ar
, nullptr, true, phpException
, flags
, false, EventHook::Source::Unwinder
);