Give `NowTask` some teeth
[hiphop-php.git] / hphp / runtime / vm / event-hook.cpp
blobdd23487ea057cfa9001071432204ec6019e4d5f6
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
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"
53 namespace HPHP {
54 ///////////////////////////////////////////////////////////////////////////////
56 const StaticString
57 s_args("args"),
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"),
62 s_file("file"),
63 s_line("line"),
64 s_enter("enter"),
65 s_exit("exit"),
66 s_suspend("suspend"),
67 s_resume("resume"),
68 s_exception("exception"),
69 s_return("return"),
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
86 callback) {
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()) {
135 VMRegAnchor _;
136 try {
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());
145 namespace {
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)) {
155 return false;
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())) {
162 return false;
164 return true;
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());
175 assertx(
176 func->localVarName(func->numParams()) == s_reified_generics_var.get()
178 assertx(isArrayLikeType(type(loc)));
180 int idx = 0;
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());
186 idx++;
187 continue;
189 auto const ts = generics->at(idx++);
190 assertx(isArrayLikeType(type(ts)));
192 auto const kind = get_ts_kind(val(ts).parr);
194 switch (kind) {
195 case K::T_class:
196 case K::T_interface:
197 case K::T_trait:
198 case K::T_enum:
199 clist.append(
200 String{const_cast<StringData*>(get_ts_classname(val(ts).parr))}
202 break;
204 case K::T_xhp:
205 clist.append(String{xhpNameFromTS(ArrNR{val(ts).parr})});
206 break;
208 case K::T_union:
209 case K::T_recursiveUnion:
210 case K::T_void:
211 case K::T_int:
212 case K::T_bool:
213 case K::T_float:
214 case K::T_string:
215 case K::T_resource:
216 case K::T_num:
217 case K::T_arraykey:
218 case K::T_noreturn:
219 case K::T_mixed:
220 case K::T_tuple:
221 case K::T_fun:
222 case K::T_typevar:
223 case K::T_shape:
224 case K::T_dict:
225 case K::T_vec:
226 case K::T_keyset:
227 case K::T_vec_or_dict:
228 case K::T_nonnull:
229 case K::T_darray:
230 case K::T_varray:
231 case K::T_varray_or_darray:
232 case K::T_any_array:
233 case K::T_null:
234 case K::T_nothing:
235 case K::T_dynamic:
236 case K::T_class_ptr:
237 case K::T_class_or_classname:
238 clist.append(init_null());
239 break;
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();
252 ALWAYS_INLINE
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) {
259 return;
261 if (frameinfo.isNull()) {
262 frameinfo = Array::CreateDict();
265 if (isCall) {
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) {
285 Offset offset;
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) {
307 return;
310 VMRegAnchor _;
311 ExecutingSetprofileCallbackGuard guard;
313 CallCtx ctx;
314 vm_decode_function(g_context->m_setprofileCallback, ctx);
315 auto const func = ctx.func;
316 if (!func) return;
318 Array frameinfo;
320 frameinfo = Array::attach(ArrayData::CreateDict());
321 if (!isResume) {
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);
328 if (!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()},
340 frameinfo
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) {
350 return;
353 VMRegAnchor _;
354 ExecutingSetprofileCallbackGuard guard;
356 CallCtx ctx;
357 vm_decode_function(g_context->m_setprofileCallback, ctx);
358 auto const func = ctx.func;
359 if (!func) return;
361 Array frameinfo;
363 if (retval) {
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()},
374 frameinfo
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,
385 Array& args,
386 ActRec* ar
388 CallCtx callCtx;
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);
396 }();
398 if (!okay) {
399 raise_error(
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);
407 VecInit par{3u};
408 par.append(called);
409 par.append(called_on);
410 par.append(args);
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();
419 return arr[0];
422 const StaticString
423 s_callback("callback"),
424 s_prepend_this("prepend_this");
426 static Variant call_intercept_handler_callback(
427 ActRec* ar,
428 const Variant& function,
429 bool prepend_this,
430 bool readonly_return
432 CallCtx callCtx;
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());
450 if (prepend_this) {
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());
455 }();
456 args.append(thiz);
458 IterateV(curArgs.get(), [&](TypedValue v) { args.append(v); });
459 return args.toArray();
460 }();
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);
467 }();
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))
476 } // namespace
478 bool EventHook::RunInterceptHandler(ActRec* ar) {
479 assertx(!isResumed(ar));
481 const Func* func = ar->func();
482 if (LIKELY(!Cfg::Eval::FastMethodIntercept && !func->maybeIntercepted())) {
483 return true;
485 assertx(func->isInterceptable() && func->maybeIntercepted());
487 Variant* h = get_intercept_handler(func);
488 if (!h) return true;
490 VMRegAnchor _;
492 SCOPE_FAIL {
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
498 // invoked.
499 ar->setLocalsDecRefd();
500 frame_free_locals_inl_no_hook(ar, ar->func()->numFuncEntryInputs());
503 PC savePc = vmpc();
505 auto done = true;
506 Variant called_on = [&] {
507 if (func->cls()) {
508 if (ar->hasThis()) {
509 return Variant(ar->getThis());
511 // For static methods, give handler the name of called class
512 return Variant{ar->getClass()->name(), Variant::PersistentStrInit{}};
514 return init_null();
515 }();
517 Array args;
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(
536 retArr[s_callback],
537 prepend_this,
538 readonly_return
540 } else {
541 // neither value or callback are present, call the original function
542 done = false;
545 if (done) {
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
558 vmfp() = nullptr;
559 vmpc() = nullptr;
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);
572 tvIncRefGen(v);
573 *start++ = v;
576 uint32_t param = 0;
577 IterateKV(args.get(), [&] (TypedValue, TypedValue v) {
578 if (param >= func->numParams() || !func->isInOut(param++)) {
579 return;
581 push(v);
584 while (start < end) push(make_tv<KindOfNull>());
585 } else {
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.
593 vmfp() = sfp;
594 vmpc() = LIKELY(sfp != nullptr)
595 ? skipCall(sfp->func()->entry() + callOff)
596 : nullptr;
597 return false;
599 vmfp() = ar;
600 vmpc() = savePc;
602 return true;
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);
630 // User profiler
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);
647 // Debugger hook
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()) {
661 return;
664 // Xenon
665 if (flags & XenonSignalFlag) {
666 if (Strobelight::active()) {
667 Strobelight::getInstance().log(Xenon::ExitSample);
668 } else {
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()) {
686 // Memory Threhsold
687 if (flags & MemThresholdFlag) {
688 DoMemoryThresholdCallback();
690 // Time Thresholds
691 if (flags & TimedOutFlag) {
692 RID().invokeUserTimeoutCallback();
695 // Interval timer
696 if (flags & IntervalTimerFlag) {
697 IntervalTimer::RunCallbacks(IntervalTimer::ExitSample);
701 // User profiler
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);
711 } else {
712 // Avoid running PHP code when unwinding C++ exception.
715 if (!unwind) {
716 runInternalEventHook(ar, isSuspend
717 ? ExecutionContext::InternalEventHook::Suspend
718 : ExecutionContext::InternalEventHook::Return);
719 } else {
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);
730 // Debugger hook
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.
740 return 0;
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)) {
753 return
754 reinterpret_cast<uintptr_t>(jit::tc::ustubs().resumeHelperFromInterp);
756 return savedRip;
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());
769 return false;
773 // Xenon
774 if (flags & XenonSignalFlag) {
775 if (Strobelight::active()) {
776 Strobelight::getInstance().log(Xenon::EnterSample);
777 } else {
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()) {
786 // Memory Threhsold
787 if (flags & MemThresholdFlag) {
788 DoMemoryThresholdCallback();
790 // Time Thresholds
791 if (flags & TimedOutFlag) {
792 RID().invokeUserTimeoutCallback();
795 if (flags & IntervalTimerFlag) {
796 IntervalTimer::RunCallbacks(IntervalTimer::EnterSample);
800 onFunctionEnter(ar, funcType, flags, false);
801 return true;
804 void EventHook::onFunctionResumeAwait(const ActRec* ar,
805 EventHook::Source sourceType) {
806 auto const flags = handle_request_surprise();
808 // Xenon
809 if (flags & XenonSignalFlag) {
810 if (Strobelight::active()) {
811 Strobelight::getInstance().log(Xenon::ResumeAwaitSample);
812 } else {
813 Xenon::getInstance().log(Xenon::ResumeAwaitSample, sourceType);
817 // Memory Threhsold
818 if (flags & MemThresholdFlag) {
819 DoMemoryThresholdCallback();
821 // Time Thresholds
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();
837 // Xenon
838 if (flags & XenonSignalFlag) {
839 if (Strobelight::active()) {
840 Strobelight::getInstance().log(Xenon::EnterSample);
841 } else {
842 Xenon::getInstance().log(Xenon::EnterSample, sourceType);
846 // Memory Threhsold
847 if (flags & MemThresholdFlag) {
848 DoMemoryThresholdCallback();
850 // Time Thresholds
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(
865 suspending,
866 resumableAR,
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();
883 try {
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());
894 } catch (...) {
895 decRefObj(frame_afwh(resumableAR));
896 throw;
900 void EventHook::onFunctionSuspendAwaitEGJit(ActRec* suspending) {
901 EventHook::onFunctionSuspendAwaitEG(suspending, EventHook::Source::Jit);
904 // Eagerly executed async generator that was resumed at Yield suspending
905 // at Await.
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)); };
916 try {
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());
926 } catch (...) {
927 decRefObj(wh.get());
928 throw;
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,
940 ObjectData* child,
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)
959 } else {
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(
978 suspending,
979 resumableAR,
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();
996 try {
997 auto const flags = handle_request_surprise();
998 onFunctionExit(resumableAR, nullptr, false, nullptr, flags, true, sourceType);
999 } catch (...) {
1000 auto const resumableObj = [&]() -> ObjectData* {
1001 return !resumableAR->func()->isAsync()
1002 ? frame_generator(resumableAR)->toObject()
1003 : frame_async_generator(resumableAR)->toObject();
1004 }();
1005 decRefObj(resumableObj);
1006 throw;
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,
1039 TypedValue retval,
1040 EventHook::Source sourceType) {
1041 // The locals are already gone. Tell everyone.
1042 ar->setLocalsDecRefd();
1043 ar->trashThis();
1045 try {
1046 auto const flags = handle_request_surprise();
1047 onFunctionExit(ar, &retval, false, nullptr, flags, false, sourceType);
1049 // Async profiler
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));
1056 } else {
1057 auto ag = frame_async_generator(ar);
1058 if (!ag->isEagerlyExecuted()) {
1059 session->onResumableSuccess(ag->getWaitHandle(), init_null());
1064 } catch (...) {
1066 * We're responsible for freeing the return value if we exit with an
1067 * exception. See irgen-ret.
1069 tvDecRefGen(retval);
1070 throw;
1074 void EventHook::onFunctionUnwind(ActRec* ar,
1075 ObjectData* phpException) {
1076 // The locals are already gone. Tell everyone.
1077 ar->setLocalsDecRefd();
1078 ar->trashThis();
1080 // TODO(#2329497) can't handle_request_surprise() yet, unwinder unable to
1081 // replace fault
1082 auto const flags = stackLimitAndSurprise().load() & kSurpriseFlagMask;
1083 onFunctionExit(ar, nullptr, true, phpException, flags, false, EventHook::Source::Unwinder);
1086 } // namespace HPHP