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 +----------------------------------------------------------------------+
16 #include "hphp/runtime/base/execution-context.h"
18 #define __STDC_LIMIT_MACROS
25 #include <folly/MapUtil.h>
26 #include <folly/Format.h>
27 #include <folly/Likely.h>
29 #include "hphp/util/logger.h"
30 #include "hphp/util/process.h"
31 #include "hphp/util/text-color.h"
32 #include "hphp/util/service-data.h"
33 #include "hphp/runtime/base/request-event-handler.h"
34 #include "hphp/runtime/base/array-init.h"
35 #include "hphp/runtime/base/debuggable.h"
36 #include "hphp/runtime/base/array-iterator.h"
37 #include "hphp/runtime/base/memory-manager.h"
38 #include "hphp/runtime/base/sweepable.h"
39 #include "hphp/runtime/base/backtrace.h"
40 #include "hphp/runtime/base/builtin-functions.h"
41 #include "hphp/runtime/base/comparisons.h"
42 #include "hphp/runtime/base/program-functions.h"
43 #include "hphp/runtime/base/runtime-option.h"
44 #include "hphp/runtime/base/tv-refcount.h"
45 #include "hphp/runtime/base/tv-type.h"
46 #include "hphp/runtime/base/type-variant.h"
47 #include "hphp/runtime/base/unit-cache.h"
48 #include "hphp/runtime/base/system-profiler.h"
49 #include "hphp/runtime/base/container-functions.h"
50 #include "hphp/runtime/base/hhprof.h"
51 #include "hphp/runtime/base/apc-stats.h"
52 #include "hphp/runtime/base/apc-typed-value.h"
53 #include "hphp/runtime/base/extended-logger.h"
54 #include "hphp/runtime/base/implicit-context.h"
55 #include "hphp/runtime/debugger/debugger.h"
56 #include "hphp/runtime/ext/std/ext_std_output.h"
57 #include "hphp/runtime/ext/string/ext_string.h"
58 #include "hphp/runtime/ext/reflection/ext_reflection.h"
59 #include "hphp/runtime/ext/apc/ext_apc.h"
60 #include "hphp/runtime/server/cli-server.h"
61 #include "hphp/runtime/server/server-stats.h"
62 #include "hphp/runtime/vm/debug/debug.h"
63 #include "hphp/runtime/vm/jit/enter-tc.h"
64 #include "hphp/runtime/vm/jit/tc.h"
65 #include "hphp/runtime/vm/jit/translator-inline.h"
66 #include "hphp/runtime/vm/jit/translator.h"
67 #include "hphp/runtime/vm/jit/unwind-itanium.h"
68 #include "hphp/runtime/vm/act-rec-defs.h"
69 #include "hphp/runtime/vm/debugger-hook.h"
70 #include "hphp/runtime/vm/event-hook.h"
71 #include "hphp/runtime/vm/hh-utils.h"
72 #include "hphp/runtime/vm/interp-helpers.h"
73 #include "hphp/runtime/vm/resumable.h"
74 #include "hphp/runtime/vm/runtime.h"
75 #include "hphp/runtime/vm/runtime-compiler.h"
76 #include "hphp/runtime/vm/treadmill.h"
77 #include "hphp/runtime/vm/unwind.h"
78 #include "hphp/runtime/base/php-globals.h"
79 #include "hphp/runtime/base/exceptions.h"
80 #include "hphp/zend/zend-math.h"
83 ///////////////////////////////////////////////////////////////////////////////
84 TRACE_SET_MOD(bcinterp
);
86 rds::local::AliasedRDSLocal
<ExecutionContext
,
87 rds::local::Initialize::Explicitly
,
88 &rds::local::detail::HotRDSLocals::g_context
91 ExecutionContext::ExecutionContext()
92 : m_transport(nullptr)
94 , m_implicitFlush(false)
96 , m_stdoutBytesWritten(0)
97 , m_errorState(ExecutionContext::ErrorState::NoError
)
99 , m_deferredErrors(nullptr)
100 , m_throwAllErrors(false)
101 , m_pageletTasksStarted(0)
103 , m_globalVarEnv(nullptr)
106 , m_dbgNoBreak(false)
107 , m_lastErrorPath(staticEmptyString())
109 , m_executingSetprofileCallback(false)
110 , m_logger_hook(*this)
112 ARRPROV_USE_RUNTIME_LOCATION();
113 m_deferredErrors
= staticEmptyVec();
114 resetCoverageCounters();
115 // We don't want a new execution context to cause any request-heap
116 // allocations (because it will cause us to hold a slab, even while idle).
117 static auto s_cwd
= makeStaticString(Process::CurrentWorkingDirectory
);
119 RID().setMemoryLimit(std::to_string(RuntimeOption::RequestMemoryMaxBytes
));
120 RID().setErrorReportingLevel(RuntimeOption::RuntimeErrorReportingLevel
);
122 VariableSerializer::serializationSizeLimit
->value
=
123 RuntimeOption::SerializationSizeLimit
;
124 tvWriteUninit(m_headerCallback
);
126 m_shutdowns
[ShutdownType::ShutDown
] = empty_vec_array();
127 m_shutdowns
[ShutdownType::PostSend
] = empty_vec_array();
128 m_shutdownsBackup
[ShutdownType::ShutDown
] = empty_vec_array();
129 m_shutdownsBackup
[ShutdownType::PostSend
] = empty_vec_array();
132 namespace rds
{ namespace local
{
133 // See header for why this is required.
137 void rds::local::RDSLocal
<ExecutionContext
,
138 rds::local::Initialize::Explicitly
>::destroy() {
140 getNoCheck()->sweep();
147 void ExecutionContext::cleanup() {
149 auto dead
= std::move(m_stdoutHooks
);
152 void ExecutionContext::sweep() {
156 ExecutionContext::~ExecutionContext() {
160 void ExecutionContext::backupSession() {
161 m_shutdownsBackup
= m_shutdowns
;
162 m_userErrorHandlersBackup
= m_userErrorHandlers
;
163 m_userExceptionHandlersBackup
= m_userExceptionHandlers
;
166 void ExecutionContext::restoreSession() {
167 m_shutdowns
= m_shutdownsBackup
;
168 m_userErrorHandlers
= m_userErrorHandlersBackup
;
169 m_userExceptionHandlers
= m_userExceptionHandlersBackup
;
172 ///////////////////////////////////////////////////////////////////////////////
175 String
ExecutionContext::getMimeType() const {
178 mimetype
= m_transport
->getMimeType();
181 if (strncasecmp(mimetype
.data(), "text/", 5) == 0) {
182 int pos
= mimetype
.find(';');
183 if (pos
!= String::npos
) {
184 mimetype
= mimetype
.substr(0, pos
);
186 } else if (m_transport
&& m_transport
->getUseDefaultContentType()) {
187 mimetype
= RID().getDefaultMimeType();
192 std::string
ExecutionContext::getRequestUrl(size_t szLimit
) {
193 Transport
* t
= getTransport();
194 std::string ret
= t
? t
->getUrl() : "";
195 if (szLimit
!= std::string::npos
) {
196 ret
= ret
.substr(0, szLimit
);
201 void ExecutionContext::setContentType(const String
& mimetype
,
202 const String
& charset
) {
204 String contentType
= mimetype
;
206 contentType
+= "charset=";
207 contentType
+= charset
;
208 m_transport
->addHeader("Content-Type", contentType
.c_str());
209 m_transport
->setUseDefaultContentType(false);
213 ///////////////////////////////////////////////////////////////////////////////
216 void ExecutionContext::write(const String
& s
) {
217 write(s
.data(), s
.size());
220 void ExecutionContext::addStdoutHook(StdoutHook
* hook
) {
221 if (hook
!= nullptr) {
222 m_stdoutHooks
.insert(hook
);
226 bool ExecutionContext::removeStdoutHook(StdoutHook
* hook
) {
227 if (hook
== nullptr) {
231 return m_stdoutHooks
.erase(hook
) != 0;
234 static void safe_stdout(const void *ptr
, size_t size
) {
235 write(fileno(stdout
), ptr
, size
);
238 void ExecutionContext::writeStdout(const char *s
, int len
) {
240 if (m_stdoutHooks
.empty()) {
241 if (s_stdout_color
) {
242 safe_stdout(s_stdout_color
, strlen(s_stdout_color
));
244 safe_stdout(ANSI_COLOR_END
, strlen(ANSI_COLOR_END
));
248 m_stdoutBytesWritten
+= len
;
250 for (auto const hook
: m_stdoutHooks
) {
251 assertx(hook
!= nullptr);
257 void ExecutionContext::writeTransport(const char *s
, int len
) {
259 m_transport
->sendRaw(s
, len
, 200, false, true);
265 size_t ExecutionContext::getStdoutBytesWritten() const {
266 return m_stdoutBytesWritten
;
269 void ExecutionContext::write(const char *s
, int len
) {
271 m_sb
->append(s
, len
);
272 if (m_out
&& m_out
->chunk_size
> 0) {
273 if (m_sb
->size() >= m_out
->chunk_size
) {
277 if (m_implicitFlush
) flush();
279 writeTransport(s
, len
);
283 ///////////////////////////////////////////////////////////////////////////////
286 void ExecutionContext::obProtect(bool on
) {
287 m_protectedLevel
= on
? m_buffers
.size() : 0;
290 void ExecutionContext::obStart(const Variant
& handler
/* = null */,
291 int chunk_size
/* = 0 */,
292 OBFlags flags
/* = OBFlags::Default */) {
293 if (m_insideOBHandler
) {
294 raise_error("ob_start(): Cannot use output buffering "
295 "in output buffering display handlers");
297 m_buffers
.emplace_back(Variant(handler
), chunk_size
, flags
);
298 resetCurrentBuffer();
301 String
ExecutionContext::obCopyContents() {
302 if (!m_buffers
.empty()) {
303 StringBuffer
&oss
= m_buffers
.back().oss
;
308 return empty_string();
311 String
ExecutionContext::obDetachContents() {
312 if (!m_buffers
.empty()) {
313 StringBuffer
&oss
= m_buffers
.back().oss
;
318 return empty_string();
321 int ExecutionContext::obGetContentLength() {
322 if (m_buffers
.empty()) {
325 return m_buffers
.back().oss
.size();
328 void ExecutionContext::obClean(int handler_flag
) {
329 if (!m_buffers
.empty()) {
330 OutputBuffer
*last
= &m_buffers
.back();
331 if (!last
->handler
.isNull()) {
332 m_insideOBHandler
= true;
333 SCOPE_EXIT
{ m_insideOBHandler
= false; };
334 vm_call_user_func(last
->handler
,
335 make_vec_array(last
->oss
.detach(), handler_flag
));
341 bool ExecutionContext::obFlush(bool force
/*= false*/) {
342 assertx(m_protectedLevel
>= 0);
344 if ((int)m_buffers
.size() <= m_protectedLevel
) {
348 auto iter
= m_buffers
.end();
349 OutputBuffer
& last
= *(--iter
);
350 if (!force
&& !(last
.flags
& OBFlags::Flushable
)) {
353 if (any(last
.flags
& OBFlags::OutputDisabled
)) {
357 const int flag
= k_PHP_OUTPUT_HANDLER_START
| k_PHP_OUTPUT_HANDLER_END
;
359 if (iter
!= m_buffers
.begin()) {
360 OutputBuffer
& prev
= *(--iter
);
361 if (last
.handler
.isNull()) {
362 prev
.oss
.absorb(last
.oss
);
364 auto str
= last
.oss
.detach();
368 ARRPROV_USE_RUNTIME_LOCATION();
369 m_insideOBHandler
= true;
370 SCOPE_EXIT
{ m_insideOBHandler
= false; };
371 tout
= vm_call_user_func(
372 last
.handler
, make_vec_array(str
, flag
)
375 prev
.oss
.append(tout
.toString());
377 prev
.oss
.append(str
);
384 auto str
= last
.oss
.detach();
385 if (!last
.handler
.isNull()) {
389 ARRPROV_USE_RUNTIME_LOCATION();
390 m_insideOBHandler
= true;
391 SCOPE_EXIT
{ m_insideOBHandler
= false; };
392 tout
= vm_call_user_func(
393 last
.handler
, make_vec_array(str
, flag
)
396 str
= tout
.toString();
398 writeTransport(str
.data(), str
.size());
403 writeTransport(str
.data(), str
.size());
407 void ExecutionContext::obFlushAll() {
413 bool ExecutionContext::obEnd() {
414 assertx(m_protectedLevel
>= 0);
415 if ((int)m_buffers
.size() > m_protectedLevel
) {
416 m_buffers
.pop_back();
417 resetCurrentBuffer();
418 if (m_implicitFlush
) flush();
421 if (m_implicitFlush
) flush();
425 void ExecutionContext::obEndAll() {
429 int ExecutionContext::obGetLevel() {
430 assertx((int)m_buffers
.size() >= m_protectedLevel
);
431 return m_buffers
.size() - m_protectedLevel
;
440 s_chunk_size("chunk_size"),
441 s_buffer_used("buffer_used"),
442 s_default_output_handler("default output handler");
444 Array
ExecutionContext::obGetStatus(bool full
) {
445 Array ret
= empty_varray();
447 for (auto& buffer
: m_buffers
) {
448 Array status
= empty_darray();
449 if (level
< m_protectedLevel
|| buffer
.handler
.isNull()) {
450 status
.set(s_name
, s_default_output_handler
);
451 status
.set(s_type
, 0);
453 status
.set(s_name
, buffer
.handler
);
454 status
.set(s_type
, 1);
458 if (any(buffer
.flags
& OBFlags::Cleanable
)) {
459 flags
|= k_PHP_OUTPUT_HANDLER_CLEANABLE
;
461 if (any(buffer
.flags
& OBFlags::Flushable
)) {
462 flags
|= k_PHP_OUTPUT_HANDLER_FLUSHABLE
;
464 if (any(buffer
.flags
& OBFlags::Removable
)) {
465 flags
|= k_PHP_OUTPUT_HANDLER_REMOVABLE
;
467 status
.set(s_flags
, flags
);
469 status
.set(s_level
, level
);
470 status
.set(s_chunk_size
, buffer
.chunk_size
);
471 status
.set(s_buffer_used
, static_cast<uint64_t>(buffer
.oss
.size()));
476 ret
= std::move(status
);
483 String
ExecutionContext::obGetBufferName() {
484 if (m_buffers
.empty()) {
486 } else if (m_buffers
.size() <= m_protectedLevel
) {
487 return s_default_output_handler
;
489 auto iter
= m_buffers
.end();
490 OutputBuffer
& buffer
= *(--iter
);
491 if (buffer
.handler
.isNull()) {
492 return s_default_output_handler
;
494 return buffer
.handler
.toString();
499 void ExecutionContext::obSetImplicitFlush(bool on
) {
500 m_implicitFlush
= on
;
503 Array
ExecutionContext::obGetHandlers() {
504 Array ret
= empty_varray();
505 for (auto& ob
: m_buffers
) {
506 auto& handler
= ob
.handler
;
507 ret
.append(handler
.isNull() ? s_default_output_handler
: handler
);
512 void ExecutionContext::flush() {
513 if (!m_buffers
.empty() &&
514 RuntimeOption::EnableEarlyFlush
&& m_protectedLevel
&&
515 !(m_buffers
.front().flags
& OBFlags::OutputDisabled
)) {
516 OutputBuffer
&buffer
= m_buffers
.front();
517 StringBuffer
&oss
= buffer
.oss
;
519 if (any(buffer
.flags
& OBFlags::WriteToStdout
)) {
520 writeStdout(oss
.data(), oss
.size());
522 writeTransport(oss
.data(), oss
.size());
529 void ExecutionContext::resetCurrentBuffer() {
530 if (m_buffers
.empty()) {
534 m_sb
= &m_buffers
.back().oss
;
535 m_out
= &m_buffers
.back();
539 ///////////////////////////////////////////////////////////////////////////////
540 // program executions
542 void ExecutionContext::registerShutdownFunction(const Variant
& function
,
544 auto& funcs
= m_shutdowns
[type
];
545 assertx(funcs
.isVec());
546 funcs
.append(function
);
549 Variant
ExecutionContext::pushUserErrorHandler(const Variant
& function
,
552 if (!m_userErrorHandlers
.empty()) {
553 ret
= m_userErrorHandlers
.back().first
;
555 m_userErrorHandlers
.push_back(std::pair
<Variant
,int>(function
, error_types
));
559 Variant
ExecutionContext::pushUserExceptionHandler(const Variant
& function
) {
561 if (!m_userExceptionHandlers
.empty()) {
562 ret
= m_userExceptionHandlers
.back();
564 m_userExceptionHandlers
.push_back(function
);
568 void ExecutionContext::popUserErrorHandler() {
569 if (!m_userErrorHandlers
.empty()) {
570 m_userErrorHandlers
.pop_back();
574 void ExecutionContext::clearUserErrorHandlers() {
575 while (!m_userErrorHandlers
.empty()) m_userErrorHandlers
.pop_back();
578 void ExecutionContext::popUserExceptionHandler() {
579 if (!m_userExceptionHandlers
.empty()) {
580 m_userExceptionHandlers
.pop_back();
584 void ExecutionContext::acceptRequestEventHandlers(bool enable
) {
585 m_acceptRequestEventHandlers
= enable
;
588 std::size_t ExecutionContext::registerRequestEventHandler(
589 RequestEventHandler
*handler
) {
590 assertx(handler
&& handler
->getInited());
591 assertx(m_acceptRequestEventHandlers
);
592 m_requestEventHandlers
.push_back(handler
);
593 return m_requestEventHandlers
.size()-1;
596 void ExecutionContext::unregisterRequestEventHandler(
597 RequestEventHandler
* handler
,
599 assertx(index
< m_requestEventHandlers
.size() &&
600 m_requestEventHandlers
[index
] == handler
);
601 assertx(!handler
->getInited());
602 if (index
== m_requestEventHandlers
.size()-1) {
603 m_requestEventHandlers
.pop_back();
605 m_requestEventHandlers
[index
] = nullptr;
609 static bool requestEventHandlerPriorityComp(RequestEventHandler
*a
,
610 RequestEventHandler
*b
) {
612 else if (!b
) return false;
613 else return a
->priority() < b
->priority();
616 void ExecutionContext::onRequestShutdown() {
617 while (!m_requestEventHandlers
.empty()) {
618 // handlers could cause other handlers to be registered,
619 // so need to repeat until done
620 decltype(m_requestEventHandlers
) tmp
;
621 tmp
.swap(m_requestEventHandlers
);
623 // Sort handlers by priority so that lower priority values get shutdown
625 sort(tmp
.begin(), tmp
.end(),
626 requestEventHandlerPriorityComp
);
627 for (auto* handler
: tmp
) {
628 if (!handler
) continue;
629 assertx(handler
->getInited());
630 handler
->requestShutdown();
631 handler
->setInited(false);
636 void ExecutionContext::executeFunctions(ShutdownType type
) {
637 ARRPROV_USE_RUNTIME_LOCATION();
639 RuntimeOption::PspTimeoutSeconds
,
640 RuntimeOption::PspCpuTimeoutSeconds
643 if (RO::EvalEnableImplicitContext
) {
644 // If the main request terminated with a C++ exception, we would not
645 // have cleared the implicit context since that logic is
646 // done in a PHP try-finally. Let's clear the implicit context here.
647 *ImplicitContext::activeCtx
= nullptr;
650 // We mustn't destroy any callbacks until we're done with all
651 // of them. So hold them in tmp.
652 // XXX still true in a world without destructors?
653 auto tmp
= empty_vec_array();
655 Array funcs
= m_shutdowns
[type
];
656 if (funcs
.empty()) break;
657 m_shutdowns
[type
] = empty_vec_array();
661 vm_call_user_func(VarNR
{v
}, init_null_variant
);
662 // Implicit context should not have leaked between each call
663 assertx(!RO::EvalEnableImplicitContext
||
664 !(*ImplicitContext::activeCtx
));
671 void ExecutionContext::onShutdownPreSend() {
672 // in case obStart was called without obFlush
674 try { obFlushAll(); } catch (...) {}
677 // When host is OOMing, abort abruptly.
678 if (RID().shouldOOMAbort()) return;
680 tracing::Block _
{"shutdown-pre-send"};
682 tl_heap
->resetCouldOOM(isStandardRequest());
683 executeFunctions(ShutDown
);
686 void ExecutionContext::debuggerExecutePsps() {
688 executeFunctions(PostSend
);
689 } catch (const ExitException
& e
) {
691 } catch (const Exception
& e
) {
693 } catch (const Object
& e
) {
694 onUnhandledException(e
);
699 void ExecutionContext::onShutdownPostSend() {
700 // When host is OOMing, abort abruptly.
701 if (RID().shouldOOMAbort()) return;
703 tracing::Block _
{"shutdown-post-send"};
705 ServerStats::SetThreadMode(ServerStats::ThreadMode::PostProcessing
);
706 tl_heap
->resetCouldOOM(isStandardRequest());
709 ServerStatsHelper
ssh("psp", ServerStatsHelper::TRACK_HWINST
);
710 executeFunctions(PostSend
);
713 bump_counter_and_rethrow(true /* isPsp */);
714 } catch (const ExitException
& e
) {
716 } catch (const Exception
& e
) {
718 } catch (const Object
& e
) {
719 onUnhandledException(e
);
723 Logger::Error("unknown exception was thrown from psp");
726 ServerStats::SetThreadMode(ServerStats::ThreadMode::Idling
);
729 ///////////////////////////////////////////////////////////////////////////////
732 bool ExecutionContext::errorNeedsHandling(int errnum
,
733 bool callUserHandler
,
734 ErrorThrowMode mode
) {
735 if (UNLIKELY(m_throwAllErrors
)) {
736 throw Exception(folly::sformat("throwAllErrors: {}", errnum
));
738 if (mode
!= ErrorThrowMode::Never
|| errorNeedsLogging(errnum
)) {
741 if (callUserHandler
) {
742 if (!m_userErrorHandlers
.empty() &&
743 (m_userErrorHandlers
.back().second
& errnum
) != 0) {
750 bool ExecutionContext::errorNeedsLogging(int errnum
) {
752 RID().getErrorReportingLevel() |
753 RuntimeOption::ForceErrorReportingLevel
;
754 return RuntimeOption::NoSilencer
|| (level
& errnum
) != 0;
757 struct ErrorStateHelper
{
758 ErrorStateHelper(ExecutionContext
*context
,
759 ExecutionContext::ErrorState state
) {
761 m_originalState
= m_context
->getErrorState();
762 m_context
->setErrorState(state
);
764 ~ErrorStateHelper() {
765 m_context
->setErrorState(m_originalState
);
768 ExecutionContext
*m_context
;
769 ExecutionContext::ErrorState m_originalState
;
775 s_function("function"),
777 s_error_num("error-num"),
778 s_error_string("error-string"),
779 s_error_file("error-file"),
780 s_error_line("error-line"),
781 s_error_backtrace("error-backtrace"),
782 s_overflow("overflow");
784 void ExecutionContext::handleError(const std::string
& msg
,
786 bool callUserHandler
,
788 const std::string
& prefix
,
789 bool skipFrame
/* = false */) {
790 SYNC_VM_REGS_SCOPED();
792 auto newErrorState
= ErrorState::ErrorRaised
;
793 switch (getErrorState()) {
794 case ErrorState::ErrorRaised
:
795 case ErrorState::ErrorRaisedByUserHandler
:
797 case ErrorState::ExecutingUserHandler
:
798 newErrorState
= ErrorState::ErrorRaisedByUserHandler
;
804 // Potentially upgrade the error to E_USER_ERROR
805 if (errnum
& RuntimeOption::ErrorUpgradeLevel
&
806 static_cast<int>(ErrorMode::UPGRADEABLE_ERROR
)) {
807 errnum
= static_cast<int>(ErrorMode::USER_ERROR
);
808 mode
= ErrorThrowMode::IfUnhandled
;
811 auto const ee
= skipFrame
?
812 ExtendedException(ExtendedException::SkipFrame
{}, msg
) :
813 ExtendedException(msg
);
814 bool handled
= false;
816 ErrorStateHelper
esh(this, newErrorState
);
817 if (callUserHandler
) {
818 handled
= callUserErrorHandler(ee
, errnum
, false);
822 recordLastError(ee
, errnum
);
825 if (g_system_profiler
) {
826 g_system_profiler
->errorCallBack(ee
, errnum
, msg
);
830 if (mode
== ErrorThrowMode::Always
||
831 (mode
== ErrorThrowMode::IfUnhandled
&& !handled
)) {
832 DEBUGGER_ATTACHED_ONLY(phpDebuggerErrorHook(ee
, errnum
, msg
));
834 errnum
== static_cast<int>(ErrorMode::RECOVERABLE_ERROR
);
835 raise_fatal_error(msg
.c_str(), ee
.getBacktrace(), isRecoverable
,
836 !errorNeedsLogging(errnum
) /* silent */);
843 // If we're inside an error handler already, queue it up on the deferred
845 if (getErrorState() == ErrorState::ExecutingUserHandler
) {
846 auto& deferred
= m_deferredErrors
;
847 if (deferred
.size() < RuntimeOption::EvalMaxDeferredErrors
) {
848 auto fileAndLine
= ee
.getFileAndLine();
853 s_error_file
, std::move(fileAndLine
.first
),
854 s_error_line
, fileAndLine
.second
,
855 s_error_backtrace
, ee
.getBacktrace()
858 } else if (!deferred
.empty()) {
859 auto const last
= deferred
.lval(int64_t{deferred
.size() - 1});
860 if (isDictType(type(last
))) {
861 asArrRef(last
).set(s_overflow
, true);
866 if (errorNeedsLogging(errnum
)) {
867 DEBUGGER_ATTACHED_ONLY(phpDebuggerErrorHook(ee
, errnum
, ee
.getMessage()));
868 auto fileAndLine
= ee
.getFileAndLine();
869 Logger::Log(Logger::LogError
, prefix
.c_str(), ee
,
870 fileAndLine
.first
.c_str(), fileAndLine
.second
);
875 bool ExecutionContext::callUserErrorHandler(const Exception
& e
, int errnum
,
876 bool swallowExceptions
) {
877 switch (getErrorState()) {
878 case ErrorState::ExecutingUserHandler
:
879 case ErrorState::ErrorRaisedByUserHandler
:
884 if (!m_userErrorHandlers
.empty() &&
885 (m_userErrorHandlers
.back().second
& errnum
) != 0) {
886 auto fileAndLine
= std::make_pair(empty_string(), 0);
888 if (auto const ee
= dynamic_cast<const ExtendedException
*>(&e
)) {
889 fileAndLine
= ee
->getFileAndLine();
890 backtrace
= ee
->getBacktrace();
893 ARRPROV_USE_RUNTIME_LOCATION();
894 ErrorStateHelper
esh(this, ErrorState::ExecutingUserHandler
);
895 m_deferredErrors
= empty_vec_array();
896 SCOPE_EXIT
{ m_deferredErrors
= empty_vec_array(); };
897 if (!same(vm_call_user_func
898 (m_userErrorHandlers
.back().first
,
899 make_vec_array(errnum
, String(e
.getMessage()),
900 fileAndLine
.first
, fileAndLine
.second
, empty_darray(),
905 } catch (const RequestTimeoutException
&) {
906 static auto requestErrorHandlerTimeoutCounter
=
907 ServiceData::createTimeSeries("requests_timed_out_error_handler",
908 {ServiceData::StatsType::COUNT
});
909 requestErrorHandlerTimeoutCounter
->addValue(1);
910 ServerStats::Log("request.timed_out.error_handler", 1);
912 if (!swallowExceptions
) throw;
913 } catch (const RequestCPUTimeoutException
&) {
914 static auto requestErrorHandlerCPUTimeoutCounter
=
915 ServiceData::createTimeSeries("requests_cpu_timed_out_error_handler",
916 {ServiceData::StatsType::COUNT
});
917 requestErrorHandlerCPUTimeoutCounter
->addValue(1);
918 ServerStats::Log("request.cpu_timed_out.error_handler", 1);
920 if (!swallowExceptions
) throw;
921 } catch (const RequestMemoryExceededException
&) {
922 static auto requestErrorHandlerMemoryExceededCounter
=
923 ServiceData::createTimeSeries(
924 "requests_memory_exceeded_error_handler",
925 {ServiceData::StatsType::COUNT
});
926 requestErrorHandlerMemoryExceededCounter
->addValue(1);
927 ServerStats::Log("request.memory_exceeded.error_handler", 1);
929 if (!swallowExceptions
) throw;
931 static auto requestErrorHandlerOtherExceptionCounter
=
932 ServiceData::createTimeSeries(
933 "requests_other_exception_error_handler",
934 {ServiceData::StatsType::COUNT
});
935 requestErrorHandlerOtherExceptionCounter
->addValue(1);
936 ServerStats::Log("request.other_exception.error_handler", 1);
938 if (!swallowExceptions
) throw;
944 bool ExecutionContext::onFatalError(const Exception
& e
) {
945 tl_heap
->resetCouldOOM(isStandardRequest());
947 // need to restore the error reporting level, because the fault
948 // handler for silencers won't be run on fatals, and we might be
949 // about to run a user error handler (and psp/shutdown code).
950 RID().setErrorReportingLevel(RuntimeOption::RuntimeErrorReportingLevel
);
952 auto prefix
= "\nFatal error: ";
953 auto errnum
= static_cast<int>(ErrorMode::FATAL_ERROR
);
954 auto const fatal
= dynamic_cast<const FatalErrorException
*>(&e
);
955 if (fatal
&& fatal
->isRecoverable()) {
956 prefix
= "\nCatchable fatal error: ";
957 errnum
= static_cast<int>(ErrorMode::RECOVERABLE_ERROR
);
960 recordLastError(e
, errnum
);
962 bool silenced
= false;
963 auto fileAndLine
= std::make_pair(empty_string(), 0);
964 if (auto const ee
= dynamic_cast<const ExtendedException
*>(&e
)) {
965 silenced
= ee
->isSilent();
966 fileAndLine
= ee
->getFileAndLine();
968 // need to silence even with the AlwaysLogUnhandledExceptions flag set
969 if (!silenced
&& RuntimeOption::AlwaysLogUnhandledExceptions
) {
970 Logger::Log(Logger::LogError
, prefix
, e
, fileAndLine
.first
.c_str(),
973 bool handled
= false;
974 if (RuntimeOption::CallUserHandlerOnFatals
) {
975 handled
= callUserErrorHandler(e
, errnum
, true);
977 if (!handled
&& !silenced
&& !RuntimeOption::AlwaysLogUnhandledExceptions
) {
978 Logger::Log(Logger::LogError
, prefix
, e
, fileAndLine
.first
.c_str(),
984 bool ExecutionContext::onUnhandledException(Object e
) {
985 String err
= throwable_to_string(e
.get());
986 if (RuntimeOption::AlwaysLogUnhandledExceptions
) {
987 Logger::Error("\nFatal error: Uncaught %s", err
.data());
990 if (e
.instanceof(SystemLib::s_ThrowableClass
)) {
991 ARRPROV_USE_RUNTIME_LOCATION();
992 // user thrown exception
993 if (!m_userExceptionHandlers
.empty()) {
994 if (!same(vm_call_user_func
995 (m_userExceptionHandlers
.back(),
1006 if (!RuntimeOption::AlwaysLogUnhandledExceptions
) {
1007 Logger::Error("\nFatal error: Uncaught %s", err
.data());
1012 ///////////////////////////////////////////////////////////////////////////////
1014 void ExecutionContext::debuggerInfo(
1015 std::vector
<std::pair
<const char*,std::string
>>& info
) {
1016 int64_t newInt
= convert_bytes_to_long(IniSetting::Get("memory_limit"));
1018 newInt
= std::numeric_limits
<int64_t>::max();
1020 if (newInt
== std::numeric_limits
<int64_t>::max()) {
1021 info
.emplace_back("Max Memory", "(unlimited)");
1023 info
.emplace_back("Max Memory", IDebuggable::FormatSize(newInt
));
1025 info
.emplace_back("Max Time",
1026 IDebuggable::FormatTime(RID().getTimeout() * 1000));
1029 void ExecutionContext::setenv(const String
& name
, const String
& value
) {
1030 m_envs
.set(m_envs
.convertKey
<IntishCast::Cast
>(name
),
1031 make_tv
<KindOfString
>(value
.get()));
1034 void ExecutionContext::unsetenv(const String
& name
) {
1035 m_envs
.remove(name
);
1038 String
ExecutionContext::getenv(const String
& name
) const {
1039 if (m_envs
.exists(name
)) {
1040 return m_envs
[name
].toString();
1042 if (is_cli_server_mode()) {
1043 auto envs
= cli_env();
1044 if (envs
.exists(name
)) return envs
[name
].toString();
1047 if (auto value
= ::getenv(name
.data())) {
1048 return String(value
, CopyString
);
1050 if (RuntimeOption::EnvVariables
.find(name
.c_str()) != RuntimeOption::EnvVariables
.end()) {
1051 return String(RuntimeOption::EnvVariables
[name
.c_str()].data(), CopyString
);
1056 TypedValue
ExecutionContext::lookupClsCns(const NamedEntity
* ne
,
1057 const StringData
* cls
,
1058 const StringData
* cns
) {
1059 Class
* class_
= nullptr;
1061 class_
= Unit::loadClass(ne
, cls
);
1062 } catch (Object
& ex
) {
1063 // For compatibility with php, throwing through a constant lookup has
1064 // different behavior inside a property initializer (86pinit/86sinit).
1065 auto ar
= getStackFrame();
1066 if (ar
&& ar
->func() && Func::isSpecial(ar
->func()->name())) {
1067 raise_warning("Uncaught %s", throwable_to_string(ex
.get()).data());
1068 raise_error("Couldn't find constant %s::%s", cls
->data(), cns
->data());
1072 if (class_
== nullptr) {
1073 raise_error(Strings::UNKNOWN_CLASS
, cls
->data());
1075 TypedValue clsCns
= class_
->clsCnsGet(cns
);
1076 if (clsCns
.m_type
== KindOfUninit
) {
1077 raise_error("Couldn't find constant %s::%s", cls
->data(), cns
->data());
1082 static Class
* loadClass(StringData
* clsName
) {
1083 Class
* class_
= Unit::loadClass(clsName
);
1084 if (class_
== nullptr) {
1085 raise_error(Strings::UNKNOWN_CLASS
, clsName
->data());
1090 ObjectData
* ExecutionContext::createObject(StringData
* clsName
,
1091 const Variant
& params
,
1092 bool init
/* = true */) {
1093 return createObject(loadClass(clsName
), params
, init
);
1096 ObjectData
* ExecutionContext::createObject(const Class
* class_
,
1097 const Variant
& params
,
1099 callerDynamicConstructChecks(class_
);
1100 auto o
= Object::attach(ObjectData::newInstance(const_cast<Class
*>(class_
)));
1102 initObject(class_
, params
, o
.get());
1108 ObjectData
* ExecutionContext::createObjectOnly(StringData
* clsName
) {
1109 return createObject(clsName
, init_null_variant
, false);
1112 ObjectData
* ExecutionContext::initObject(StringData
* clsName
,
1113 const Variant
& params
,
1115 return initObject(loadClass(clsName
), params
, o
);
1118 ObjectData
* ExecutionContext::initObject(const Class
* class_
,
1119 const Variant
& params
,
1121 auto ctor
= class_
->getCtor();
1122 if (!(ctor
->attrs() & AttrPublic
)) {
1123 std::string msg
= "Access to non-public constructor of class ";
1124 msg
+= class_
->name()->data();
1125 Reflection::ThrowReflectionExceptionObject(msg
);
1128 if (!isContainerOrNull(params
)) {
1129 throw_param_is_not_container();
1131 tvDecRefGen(invokeFunc(ctor
, params
, o
, nullptr, true, false, true));
1135 ActRec
* ExecutionContext::getStackFrame() {
1140 ObjectData
* ExecutionContext::getThis() {
1142 ActRec
* fp
= vmfp();
1143 if (fp
->skipFrame()) fp
= getPrevVMStateSkipFrame(fp
);
1144 if (fp
&& fp
->func()->cls() && fp
->hasThis()) {
1145 return fp
->getThis();
1150 const RepoOptions
& ExecutionContext::getRepoOptionsForCurrentFrame() const {
1153 if (auto const ar
= vmfp()) {
1154 auto const path
= ar
->func()->unit()->filepath();
1155 return RepoOptions::forFile(path
->data());
1157 return RepoOptions::defaults();
1160 void ExecutionContext::onLoadWithOptions(
1161 const char* f
, const RepoOptions
& opts
1163 if (!RuntimeOption::EvalFatalOnParserOptionMismatch
) return;
1164 if (!m_requestOptions
) {
1165 m_requestOptions
.emplace(opts
);
1168 if (m_requestOptions
!= opts
) {
1169 // The data buffer has to stay alive for the call to raise_error.
1170 auto const path_str
= opts
.path();
1171 auto const path
= path_str
.empty() ? "{default options}" : path_str
.data();
1173 "Attempting to load file %s with incompatible parser settings from %s, "
1174 "this request is using parser settings from %s",
1175 f
, path
, m_requestOptions
->path().data()
1180 StringData
* ExecutionContext::getContainingFileName() {
1182 ActRec
* ar
= vmfp();
1183 if (ar
== nullptr) return staticEmptyString();
1184 if (ar
->skipFrame()) ar
= getPrevVMStateSkipFrame(ar
);
1185 if (ar
== nullptr) return staticEmptyString();
1186 Unit
* unit
= ar
->m_func
->unit();
1187 auto const path
= ar
->m_func
->originalFilename() ?
1188 ar
->m_func
->originalFilename() : unit
->filepath();
1189 return const_cast<StringData
*>(path
);
1192 int ExecutionContext::getLine() {
1194 ActRec
* ar
= vmfp();
1195 Unit
* unit
= ar
? ar
->m_func
->unit() : nullptr;
1196 Offset pc
= unit
? pcOff() : 0;
1197 if (ar
== nullptr) return -1;
1198 if (ar
->skipFrame()) ar
= getPrevVMStateSkipFrame(ar
, &pc
);
1199 if (ar
== nullptr || (unit
= ar
->m_func
->unit()) == nullptr) return -1;
1200 return unit
->getLineNumber(pc
);
1203 ActRec
* ExecutionContext::getFrameAtDepthForDebuggerUnsafe(int frameDepth
) {
1204 ActRec
* ret
= nullptr;
1205 walkStack([&] (ActRec
* fp
, Offset
) {
1206 if (frameDepth
== 0) {
1207 if (fp
&& !fp
->localsDecRefd()) {
1219 void ExecutionContext::setVar(StringData
* name
, tv_rval v
) {
1221 ActRec
*fp
= vmfp();
1223 if (fp
->skipFrame()) fp
= getPrevVMStateSkipFrame(fp
);
1224 if (fp
) fp
->getVarEnv()->set(name
, v
);
1227 Array
ExecutionContext::getLocalDefinedVariablesDebugger(int frame
) {
1228 const auto fp
= getFrameAtDepthForDebuggerUnsafe(frame
);
1229 return getDefinedVariables(fp
);
1232 bool ExecutionContext::setHeaderCallback(const Variant
& callback
) {
1233 if (tvAsVariant(g_context
->m_headerCallback
).toBoolean()) {
1234 // return false if a callback has already been set.
1237 tvAsVariant(g_context
->m_headerCallback
) = callback
;
1241 const static StaticString
1242 s_enter_async_entry_point("__SystemLib\\enter_async_entry_point");
1244 TypedValue
ExecutionContext::invokeUnit(const Unit
* unit
,
1245 bool callByHPHPInvoke
) {
1246 checkHHConfig(unit
);
1248 if (!unit
->isHHFile()) {
1249 throw PhpNotSupportedException(unit
->filepath()->data());
1252 const_cast<Unit
*>(unit
)->merge();
1254 auto it
= unit
->getCachedEntryPoint();
1255 if (callByHPHPInvoke
&& it
!= nullptr) {
1256 if (it
->isAsync()) {
1258 Unit::lookupFunc(s_enter_async_entry_point
.get()),
1259 make_vec_array_tagged(ARRPROV_HERE(), Variant
{it
}),
1260 nullptr, nullptr, false
1263 invokeFunc(it
, init_null_variant
, nullptr, nullptr, false);
1266 return make_tv
<KindOfInt64
>(1);
1269 void ExecutionContext::syncGdbState() {
1270 if (RuntimeOption::EvalJit
&& !RuntimeOption::EvalJitNoGdb
) {
1271 Debug::DebugInfo::Get()->debugSync();
1275 void ExecutionContext::pushVMState(TypedValue
* savedSP
) {
1276 if (UNLIKELY(!vmfp())) {
1278 assertx(m_nestedVMs
.size() == 0);
1282 TRACE(3, "savedVM: %p %p %p %p\n", vmpc(), vmfp(), vmFirstAR(), savedSP
);
1284 auto& savedVM
= m_nestedVMs
.emplace_back(
1293 jit::g_unwind_rds
->exn
,
1294 jit::g_unwind_rds
->sideEnter
1297 jit::g_unwind_rds
->exn
= nullptr;
1298 jit::g_unwind_rds
->sideEnter
= false;
1302 if (debug
&& savedVM
.fp
&&
1303 savedVM
.fp
->m_func
&&
1304 savedVM
.fp
->m_func
->unit()) {
1305 // Some asserts and tracing.
1306 const Func
* func
= savedVM
.fp
->m_func
;
1307 /* bound-check asserts in offsetOf */
1308 func
->unit()->offsetOf(savedVM
.pc
);
1309 TRACE(3, "pushVMState: saving frame %s pc %p off %d fp %p\n",
1310 func
->name()->data(),
1312 func
->unit()->offsetOf(savedVM
.pc
),
1317 void ExecutionContext::popVMState() {
1318 if (UNLIKELY(m_nestedVMs
.empty())) {
1322 vmFirstAR() = nullptr;
1326 assertx(m_nestedVMs
.size() >= 1);
1328 VMState
&savedVM
= m_nestedVMs
.back();
1329 vmpc() = savedVM
.pc
;
1330 vmfp() = savedVM
.fp
;
1331 vmFirstAR() = savedVM
.firstAR
;
1332 vmStack().top() = savedVM
.sp
;
1333 vmMInstrState() = savedVM
.mInstrState
;
1334 vmJitCalledFrame() = savedVM
.jitCalledFrame
;
1335 vmJitReturnAddr() = savedVM
.jitReturnAddr
;
1336 jit::g_unwind_rds
->exn
= savedVM
.exn
;
1337 jit::g_unwind_rds
->sideEnter
= savedVM
.unwinderSideEnter
;
1341 savedVM
.fp
->m_func
&&
1342 savedVM
.fp
->m_func
->unit()) {
1343 const Func
* func
= savedVM
.fp
->m_func
;
1344 (void) /* bound-check asserts in offsetOf */
1345 func
->unit()->offsetOf(savedVM
.pc
);
1346 TRACE(3, "popVMState: restoring frame %s pc %p off %d fp %p\n",
1347 func
->name()->data(),
1349 func
->unit()->offsetOf(savedVM
.pc
),
1354 m_nestedVMs
.pop_back();
1357 TRACE(1, "Reentry: exit fp %p pc %p\n", vmfp(), vmpc());
1360 void ExecutionContext::ExcLoggerHook::operator()(
1361 const char* header
, const char* msg
, const char* ending
1372 s_namespace("namespace "),
1373 s_curly_start(" { "),
1375 s_function_start("<<__DynamicallyCallable>> function "),
1376 s_evaluate_default_argument("evaluate_default_argument"),
1377 s_function_middle("() { return "),
1379 s_stdclass("stdclass");
1381 void ExecutionContext::requestInit() {
1382 assertx(SystemLib::s_unit
);
1385 VarEnv::createGlobal();
1386 vmStack().requestInit();
1387 ResourceHdr::resetMaxId();
1388 jit::tc::requestInit();
1390 if (RuntimeOption::EvalJitEnableRenameFunction
) {
1391 assertx(SystemLib::s_anyNonPersistentBuiltins
);
1395 * The normal case for production mode is that all builtins are
1396 * persistent, and every systemlib unit is accordingly going to be
1399 * However, if we have rename_function generally enabled, or if any
1400 * builtin functions were specified as interceptable at
1401 * repo-generation time, we'll actually need to merge systemlib on
1402 * every request because some of the builtins will not be marked
1405 if (UNLIKELY(SystemLib::s_anyNonPersistentBuiltins
)) {
1406 SystemLib::s_unit
->merge();
1407 SystemLib::mergePersistentUnits();
1408 if (SystemLib::s_hhas_unit
) SystemLib::s_hhas_unit
->merge();
1410 // System units are merge only, and everything is persistent.
1411 assertx(SystemLib::s_unit
->isEmpty());
1412 assertx(!SystemLib::s_hhas_unit
|| SystemLib::s_hhas_unit
->isEmpty());
1415 if (RO::EvalEnableImplicitContext
) *ImplicitContext::activeCtx
= nullptr;
1417 profileRequestStart();
1419 HHProf::Request::StartProfiling();
1422 Class
* cls
= NamedEntity::get(s_stdclass
.get())->clsList();
1424 assertx(cls
== SystemLib::s_stdclassClass
);
1427 if (Logger::UseRequestLog
) Logger::SetThreadHook(&m_logger_hook
);
1429 // Needs to be last (or nearly last): might cause unit merging to call an
1430 // extension function in the VM; this is bad if systemlib itself hasn't been
1432 autoTypecheckRequestInit();
1435 void ExecutionContext::requestExit() {
1436 apcExtension::purgeDeferred(std::move(m_apcDeferredExpire
));
1438 autoTypecheckRequestExit();
1439 HHProf::Request::FinishProfiling();
1443 vmStack().requestExit();
1444 profileRequestEnd();
1445 EventHook::Disable();
1449 if (m_globalVarEnv
) {
1450 req::destroy_raw(m_globalVarEnv
);
1451 m_globalVarEnv
= nullptr;
1454 if (!m_lastError
.isNull()) {
1459 ARRPROV_USE_RUNTIME_LOCATION();
1460 m_deferredErrors
= empty_vec_array();
1463 if (Logger::UseRequestLog
) Logger::SetThreadHook(nullptr);
1464 if (m_requestTrace
) record_trace(std::move(*m_requestTrace
));
1467 template<class Action
>
1468 static inline void enterVM(ActRec
* ar
, Action action
) {
1469 enterVMCustomHandler(ar
, [&] { exception_handler(action
); });
1473 * Shared implementation for invokeFunc{,Few}().
1475 * The `doEnterVM' callback take an ActRec* argument corresponding to
1476 * the reentry frame.
1478 template<class FEnterVM
>
1480 TypedValue
ExecutionContext::invokeFuncImpl(const Func
* f
,
1481 ObjectData
* thiz
, Class
* cls
,
1482 uint32_t numArgsInclUnpack
,
1483 FEnterVM doEnterVM
) {
1485 // If `f' is a regular function, `thiz' and `cls' must be null.
1486 assertx(IMPLIES(!f
->implCls(), (!thiz
&& !cls
)));
1487 // If `f' is a method, either `thiz' or `cls' must be non-null.
1488 assertx(IMPLIES(f
->preClass(), thiz
|| cls
));
1489 // If `f' is a static method, thiz must be null.
1490 assertx(IMPLIES(f
->isStaticInPrologue(), !thiz
));
1492 if (thiz
!= nullptr) thiz
->incRefCount();
1494 ActRec
* ar
= vmStack().indA(numArgsInclUnpack
);
1495 ar
->setReturnVMExit();
1504 ar
->setNumArgs(numArgsInclUnpack
);
1508 if (vmfp() == nullptr) {
1509 TRACE(1, "Reentry: enter %s(%p) from top-level\n",
1510 f
->name()->data(), ar
);
1512 TRACE(1, "Reentry: enter %s(pc %p ar %p) from %s(%p)\n",
1513 f
->name()->data(), vmpc(), ar
,
1514 vmfp()->m_func
? vmfp()->m_func
->name()->data()
1520 auto const reentrySP
=
1521 vmStack().top() + numArgsInclUnpack
+ kNumActRecCells
+ f
->numInOutParams();
1522 pushVMState(reentrySP
);
1525 vmStack().top() == reentrySP
,
1526 "vmsp() mismatch around reentry: before @ {}, after @ {}",
1527 reentrySP
, vmStack().top()
1532 enterVM(ar
, [&] { doEnterVM(ar
); });
1534 if (UNLIKELY(f
->takesInOutParams())) {
1535 // This is OK (albeit ugly) since the return value should only be readable
1536 // from user code via e.g. call_user_func and we will tagTV the result from
1537 // the native wrapper and getting the correct frame pointer here (or from
1538 // the normal arrprov::tagFromPC) is awkward.
1539 ARRPROV_USE_RUNTIME_LOCATION();
1541 VArrayInit
varr(f
->numInOutParams() + 1);
1542 for (uint32_t i
= 0; i
< f
->numInOutParams() + 1; ++i
) {
1543 varr
.append(*vmStack().topTV());
1546 auto arr
= varr
.toArray();
1547 return make_array_like_tv(arr
.detach());
1549 auto const retval
= *vmStack().topTV();
1550 vmStack().discard();
1556 * Enter VM by calling action(), which invokes a function or resumes
1557 * an async function. The 'ar' argument points to an ActRec of the
1558 * invoked/resumed function.
1560 template<class Action
>
1561 static inline void enterVMCustomHandler(ActRec
* ar
, Action action
) {
1563 assertx(!ar
->sfp());
1564 assertx(isReturnHelper(reinterpret_cast<void*>(ar
->m_savedRip
)));
1565 assertx(ar
->callOffset() == 0);
1568 vmJitCalledFrame() = nullptr;
1569 vmJitReturnAddr() = 0;
1574 exception_handler(enterVMAtCurPC
);
1578 TypedValue
ExecutionContext::invokeFunc(const Func
* f
,
1579 const Variant
& args_
,
1580 ObjectData
* thiz
/* = NULL */,
1581 Class
* cls
/* = NULL */,
1582 bool dynamic
/* = true */,
1583 bool checkRefAnnot
/* = false */,
1584 bool allowDynCallNoPointer
1586 Array
&& reifiedGenerics
1590 // We must do a stack overflow check for leaf functions on re-entry,
1591 // because we won't have checked that the stack is deep enough for a
1592 // leaf function /after/ re-entry, and the prologue for the leaf
1593 // function will not make a check.
1594 if (f
->isPhpLeafFn() ||
1595 !(f
->numParams() <= kStackCheckReenterPadding
- kNumActRecCells
)) {
1596 // Check both the native stack and VM stack for overflow, numParams
1597 // is already included in f->maxStackCells().
1598 checkStack(vmStack(), f
, f
->numInOutParams() + kNumActRecCells
);
1600 // invokeFunc() must always check the native stack for overflow no
1605 // Reserve space for inout outputs and ActRec.
1606 for (auto i
= f
->numInOutParams(); i
> 0; --i
) vmStack().pushUninit();
1607 for (auto i
= kNumActRecCells
; i
> 0; --i
) vmStack().pushUninit();
1610 auto const& args
= *args_
.asTypedValue();
1611 assertx(isContainerOrNull(args
));
1612 auto numArgs
= tvIsNull(args
) ? 0 : getContainerSize(args
);
1614 assertx(isContainer(args
));
1615 tvDup(args
, *vmStack().allocC());
1616 numArgs
= prepareUnpackArgs(f
, 0, checkRefAnnot
);
1620 if (dynamic
) callerDynamicCallChecks(f
, allowDynCallNoPointer
);
1622 auto const doEnterVM
= [&] (ActRec
* ar
) {
1623 enterVMAtFunc(ar
, std::move(reifiedGenerics
), f
->takesInOutParams(),
1624 dynamic
, allowDynCallNoPointer
);
1627 return invokeFuncImpl(f
, thiz
, cls
, numArgs
, doEnterVM
);
1630 TypedValue
ExecutionContext::invokeFuncFew(
1632 ExecutionContext::ThisOrClass thisOrCls
,
1634 const TypedValue
* argv
,
1635 bool dynamic
/* = true */,
1636 bool allowDynCallNoPointer
1640 auto& stack
= vmStack();
1642 // See comments in invokeFunc().
1643 if (f
->isPhpLeafFn() ||
1644 !(numArgs
<= kStackCheckReenterPadding
- kNumActRecCells
)) {
1645 checkStack(stack
, f
, f
->numInOutParams() + kNumActRecCells
);
1650 // Reserve space for inout outputs and ActRec.
1651 for (auto i
= f
->numInOutParams(); i
> 0; --i
) stack
.pushUninit();
1652 for (auto i
= kNumActRecCells
; i
> 0; --i
) stack
.pushUninit();
1654 // Push non-variadic arguments.
1655 auto const numParams
= f
->numNonVariadicParams();
1656 for (auto i
= 0; i
< numArgs
&& i
< numParams
; ++i
) {
1657 const TypedValue
*from
= &argv
[i
];
1658 TypedValue
* to
= stack
.allocTV();
1662 // Push variadic arguments.
1663 if (UNLIKELY(numParams
< numArgs
)) {
1664 VArrayInit ai
{numArgs
- numParams
};
1665 for (auto i
= numParams
; i
< numArgs
; ++i
) ai
.append(argv
[i
]);
1666 if (RuntimeOption::EvalHackArrDVArrs
) {
1667 stack
.pushVecNoRc(ai
.create());
1669 stack
.pushArrayNoRc(ai
.create());
1671 numArgs
= numParams
+ 1;
1675 if (dynamic
) callerDynamicCallChecks(f
, allowDynCallNoPointer
);
1677 auto const doEnterVM
= [&] (ActRec
* ar
) {
1678 enterVMAtFunc(ar
, Array(), f
->takesInOutParams(), dynamic
, false);
1681 return invokeFuncImpl(f
,
1684 numArgs
, doEnterVM
);
1687 static void prepareAsyncFuncEntry(ActRec
* enterFnAr
,
1688 Resumable
* resumable
,
1691 assertx(enterFnAr
->func()->isAsync());
1692 assertx(isResumed(enterFnAr
));
1696 // If we're going to unwind, we need to resume at the offset we
1697 // suspended at, so we look up the correct catch trace (not the one
1698 // for the next op).
1699 vmpc() = enterFnAr
->func()->unit()->at(
1701 ? resumable
->suspendOffset()
1702 : resumable
->resumeFromAwaitOffset()
1704 EventHook::FunctionResumeAwait(enterFnAr
);
1707 void ExecutionContext::resumeAsyncFunc(Resumable
* resumable
,
1708 ObjectData
* freeObj
,
1709 const TypedValue awaitResult
) {
1710 assertx(tl_regState
== VMRegState::CLEAN
);
1711 SCOPE_EXIT
{ assertx(tl_regState
== VMRegState::CLEAN
); };
1713 auto fp
= resumable
->actRec();
1715 if (RO::EvalEnableImplicitContext
) {
1716 *ImplicitContext::activeCtx
= [&] {
1717 if (!fp
->func()->isGenerator()) return frame_afwh(fp
)->m_implicitContext
;
1718 auto gen
= frame_async_generator(fp
);
1719 return gen
->getWaitHandle()->m_implicitContext
;
1723 // We don't need to check for space for the ActRec (unlike generally
1724 // in normal re-entry), because the ActRec isn't on the stack.
1725 checkStack(vmStack(), fp
->func(), 0);
1727 TypedValue
* savedSP
= vmStack().top();
1728 tvDup(awaitResult
, *vmStack().allocC());
1730 // decref after awaitResult is on the stack
1733 pushVMState(savedSP
);
1734 SCOPE_EXIT
{ popVMState(); };
1737 prepareAsyncFuncEntry(fp
, resumable
, false);
1739 const bool useJit
= RID().getJit();
1740 if (LIKELY(useJit
&& resumable
->resumeAddr())) {
1741 Stats::inc(Stats::VMEnter
);
1742 jit::enterTC(resumable
->resumeAddr());
1749 void ExecutionContext::resumeAsyncFuncThrow(Resumable
* resumable
,
1750 ObjectData
* freeObj
,
1751 ObjectData
* exception
) {
1753 assertx(exception
->instanceof(SystemLib::s_ThrowableClass
));
1754 assertx(tl_regState
== VMRegState::CLEAN
);
1755 SCOPE_EXIT
{ assertx(tl_regState
== VMRegState::CLEAN
); };
1757 auto fp
= resumable
->actRec();
1759 if (RO::EvalEnableImplicitContext
) {
1760 *ImplicitContext::activeCtx
= [&] {
1761 if (!fp
->func()->isGenerator()) return frame_afwh(fp
)->m_implicitContext
;
1762 auto gen
= frame_async_generator(fp
);
1763 return gen
->getWaitHandle()->m_implicitContext
;
1767 checkStack(vmStack(), fp
->func(), 0);
1769 // decref after we hold reference to the exception
1770 Object
e(exception
);
1773 pushVMState(vmStack().top());
1774 SCOPE_EXIT
{ popVMState(); };
1776 enterVMCustomHandler(fp
, [&] {
1777 prepareAsyncFuncEntry(fp
, resumable
, true);
1778 unwindVM(exception
);
1783 ActRec
* ExecutionContext::getPrevVMState(const ActRec
* fp
,
1784 Offset
* prevPc
/* = NULL */,
1785 TypedValue
** prevSp
/* = NULL */,
1786 bool* fromVMEntry
/* = NULL */,
1787 uint64_t* jitReturnAddr
/* = NULL */) {
1788 if (fp
== nullptr) {
1791 ActRec
* prevFp
= fp
->sfp();
1792 if (LIKELY(prevFp
!= nullptr)) {
1794 if (UNLIKELY(isResumed(fp
))) {
1795 assertx(fp
->func()->isGenerator());
1796 *prevSp
= (TypedValue
*)prevFp
- prevFp
->func()->numSlotsInFrame();
1798 *prevSp
= (TypedValue
*)(fp
+ 1);
1801 if (prevPc
) *prevPc
= prevFp
->func()->base() + fp
->callOffset();
1802 if (fromVMEntry
) *fromVMEntry
= false;
1805 // Linear search from end of m_nestedVMs. In practice, we're probably
1806 // looking for something recently pushed.
1807 int i
= m_nestedVMs
.size() - 1;
1808 ActRec
* firstAR
= vmFirstAR();
1809 while (i
>= 0 && firstAR
!= fp
) {
1810 firstAR
= m_nestedVMs
[i
--].firstAR
;
1812 if (i
== -1) return nullptr;
1813 const VMState
& vmstate
= m_nestedVMs
[i
];
1814 prevFp
= vmstate
.fp
;
1816 assertx(prevFp
->func()->unit());
1817 if (prevSp
) *prevSp
= vmstate
.sp
;
1819 *prevPc
= prevFp
->func()->unit()->offsetOf(vmstate
.pc
);
1821 if (fromVMEntry
) *fromVMEntry
= true;
1822 if (jitReturnAddr
) *jitReturnAddr
= (uint64_t)vmstate
.jitReturnAddr
;
1826 Variant
ExecutionContext::getEvaledArg(const StringData
* val
,
1827 const String
& namespacedName
,
1828 const Unit
* funcUnit
) {
1829 auto key
= StrNR(val
);
1831 if (m_evaledArgs
.get()) {
1832 auto const arg
= m_evaledArgs
.get()->get(key
);
1833 if (arg
.is_init()) return Variant::wrap(arg
);
1838 int pos
= namespacedName
.rfind('\\');
1840 auto ns
= namespacedName
.substr(0, pos
);
1841 code
= (funcUnit
->isHHFile() ? s_hh
: s_php
) +
1842 s_namespace
+ ns
+ s_curly_start
+ s_function_start
+
1843 s_evaluate_default_argument
+ s_function_middle
+ key
+
1844 s_semicolon
+ s_curly_end
+ s_curly_end
;
1845 funcName
= ns
+ "\\" + s_evaluate_default_argument
;
1847 code
= (funcUnit
->isHHFile() ? s_hh
: s_php
) +
1848 s_function_start
+ s_evaluate_default_argument
+ s_function_middle
+
1849 key
+ s_semicolon
+ s_curly_end
;
1850 funcName
= s_evaluate_default_argument
;
1853 Unit
* unit
= compileEvalString(code
.get());
1854 assertx(unit
!= nullptr);
1855 unit
->setInterpretOnly();
1857 // The evaluate_default_argument function should be the last one
1858 auto func
= *(unit
->funcs().end() - 1);
1859 assertx(func
->name()->equal(funcName
.get()) &&
1860 "We expecting the evaluate_default_argument func");
1862 // Default arg values are not currently allowed to depend on class context.
1863 auto v
= Variant::attach(
1864 g_context
->invokeFuncFew(func
, nullptr, 0, nullptr, true, true)
1866 auto const lv
= m_evaledArgs
.lvalForce(key
, AccessFlags::Key
);
1867 tvSet(*v
.asTypedValue(), lv
);
1868 return Variant::wrap(lv
.tv());
1871 void ExecutionContext::recordLastError(const Exception
& e
, int errnum
) {
1872 m_lastError
= String(e
.getMessage());
1873 m_lastErrorNum
= errnum
;
1874 m_lastErrorPath
= String::attach(getContainingFileName());
1875 m_lastErrorLine
= getLine();
1876 if (auto const ee
= dynamic_cast<const ExtendedException
*>(&e
)) {
1877 m_lastErrorPath
= ee
->getFileAndLine().first
;
1878 m_lastErrorLine
= ee
->getFileAndLine().second
;
1882 void ExecutionContext::clearLastError() {
1883 m_lastError
= String();
1885 m_lastErrorPath
= staticEmptyString();
1886 m_lastErrorLine
= 0;
1889 void ExecutionContext::enqueueAPCDeferredExpire(const String
& key
) {
1890 auto keyStr
= key
.get();
1891 keyStr
->incRefCount();
1892 m_apcDeferredExpire
.push_back(keyStr
);
1895 void ExecutionContext::enqueueAPCHandle(APCHandle
* handle
, size_t size
) {
1896 assertx(handle
->isUncounted());
1897 m_apcHandles
.push_back(handle
);
1898 m_apcMemSize
+= size
;
1901 // Treadmill solution for the SharedVariant memory management
1903 struct FreedAPCHandle
{
1904 explicit FreedAPCHandle(std::vector
<APCHandle
*>&& shandles
, size_t size
)
1905 : m_memSize(size
), m_apcHandles(std::move(shandles
))
1908 for (auto handle
: m_apcHandles
) {
1909 APCTypedValue::fromHandle(handle
)->deleteUncounted();
1911 APCStats::getAPCStats().removePendingDelete(m_memSize
);
1915 std::vector
<APCHandle
*> m_apcHandles
;
1919 void ExecutionContext::manageAPCHandle() {
1920 assertx(apcExtension::UseUncounted
|| m_apcHandles
.size() == 0);
1921 if (m_apcHandles
.size() > 0) {
1923 FreedAPCHandle(std::move(m_apcHandles
), m_apcMemSize
)
1925 APCStats::getAPCStats().addPendingDelete(m_apcMemSize
);
1929 // Evaled units have a footprint in the TC and translation metadata. The
1930 // applications we care about tend to have few, short, stereotyped evals,
1931 // where the same code keeps getting eval'ed over and over again; so we
1932 // keep around units for each eval'ed string, so that the TC space isn't
1933 // wasted on each eval.
1934 typedef RankedCHM
<StringData
*, HPHP::Unit
*,
1935 StringDataHashCompare
,
1936 RankEvaledUnits
> EvaledUnitsMap
;
1937 static EvaledUnitsMap s_evaledUnits
;
1938 Unit
* ExecutionContext::compileEvalString(
1940 const char* evalFilename
/* = nullptr */) {
1941 EvaledUnitsMap::accessor acc
;
1942 // Promote this to a static string; otherwise it may get swept
1944 code
= makeStaticString(code
);
1945 if (s_evaledUnits
.insert(acc
, code
)) {
1946 acc
->second
= compile_string(
1950 Native::s_noNativeFuncs
,
1951 getRepoOptionsForCurrentFrame()
1957 ExecutionContext::EvaluationResult
1958 ExecutionContext::evalPHPDebugger(StringData
* code
, int frame
) {
1959 // The code has "<?php" prepended already
1960 auto unit
= compile_debugger_string(code
->data(), code
->size(),
1961 getRepoOptionsForCurrentFrame());
1962 if (unit
== nullptr) {
1963 raise_error("Syntax error");
1964 return {true, init_null_variant
, "Syntax error"};
1967 return evalPHPDebugger(unit
, frame
);
1971 s_DebuggerMainAttr("__DebuggerMain"),
1972 s_uninitClsName("__uninitSentinel");
1974 ExecutionContext::EvaluationResult
1975 ExecutionContext::evalPHPDebugger(Unit
* unit
, int frame
) {
1976 always_assert(!RuntimeOption::RepoAuthoritative
);
1978 // Do not JIT this unit, we are using it exactly once.
1979 unit
->setInterpretOnly();
1983 auto fp
= getFrameAtDepthForDebuggerUnsafe(frame
);
1985 // Continue walking up the stack until we find a frame that can have
1986 // a variable environment context attached to it, or we run out out frames.
1987 while (fp
&& (fp
->skipFrame() || fp
->isInlined())) {
1988 fp
= getPrevVMStateSkipFrame(fp
);
1991 if (fp
) phpDebuggerEvalHook(fp
->m_func
);
1993 const static StaticString
s_cppException("Hit an exception");
1994 const static StaticString
s_phpException("Hit a php exception");
1995 const static StaticString
s_exit("Hit exit");
1996 const static StaticString
s_fatal("Hit fatal");
1997 std::ostringstream errorString
;
2000 // Find a suitable PC to use when switching to the target frame. If the target
2001 // is the current frame, this is just vmpc(). For other cases, this will
2002 // generally be the address of a call from that frame's function. If we can't
2003 // find the target frame (because it lies deeper in the stack), then just use
2004 // the target frame's func's entry point.
2005 auto const findSuitablePC
= [this](const ActRec
* target
){
2006 if (auto fp
= vmfp()) {
2007 if (fp
== target
) return vmpc();
2009 auto prevFp
= getPrevVMState(fp
);
2011 if (prevFp
== target
) {
2012 return prevFp
->func()->getEntry() + fp
->callOffset();
2017 return target
->func()->getEntry();
2021 // We also need to change vmpc() to match, since we assert in a few places
2022 // that the vmpc() lies within vmfp()'s code.
2023 auto savedFP
= vmfp();
2024 auto savedPC
= vmpc();
2026 vmpc() = findSuitablePC(fp
);
2029 SCOPE_EXIT
{ vmpc() = savedPC
; vmfp() = savedFP
; };
2032 enum VarAction
{ StoreFrame
, StoreVV
, StoreEnv
};
2034 auto const uninit_cls
= Unit::loadClass(s_uninitClsName
.get());
2036 auto globals
= Array();
2037 auto const global
= fp
&& fp
->m_varEnv
&& fp
->m_varEnv
->isGlobalScope();
2038 auto& env
= m_debuggerEnv
;
2040 auto const ctx
= fp
? fp
->func()->cls() : nullptr;
2041 auto const f
= [&] () -> Func
* {
2042 for (auto orig_f
: unit
->funcs()) {
2043 if (orig_f
->userAttributes().count(s_DebuggerMainAttr
.get())) {
2044 auto const f
= ctx
? orig_f
->clone(ctx
) : orig_f
;
2048 if (fp
&& fp
->hasThis()) {
2049 f
->setAttrs(Attr(f
->attrs() & ~AttrStatic
));
2050 } else if (fp
&& fp
->hasClass()) {
2051 f
->setAttrs(Attr(f
->attrs() | AttrStatic
));
2054 assertx(f
->numParams() >= 1 && f
->numParams() == f
->numInOutParams());
2062 VArrayInit args
{f
->numParams()};
2063 std::vector
<VarAction
> actions
{f
->numParams(), StoreEnv
};
2064 std::vector
<Id
> frameIds
;
2065 frameIds
.resize(f
->numParams(), 0);
2066 auto const appendUninit
= [&] {
2067 args
.append(make_tv
<KindOfObject
>(Object
{uninit_cls
}.detach()));
2069 for (Id id
= 0; id
< f
->numParams() - 1; id
++) {
2070 assertx(id
< f
->numNamedLocals());
2071 assertx(f
->params()[id
].isInOut());
2073 auto const idx
= fp
->func()->lookupVarId(f
->localVarName(id
));
2074 if (idx
!= kInvalidId
) {
2075 Variant var
{tvAsCVarRef(*frame_local(fp
, idx
))};
2076 if (var
.isInitialized()) args
.append(var
);
2077 else appendUninit();
2078 actions
[id
] = StoreFrame
;
2082 if (fp
->hasVarEnv()) {
2083 auto const tv
= fp
->getVarEnv()->lookup(f
->localVarName(id
));
2085 if (type(tv
) != KindOfUninit
) args
.append(tvAsCVarRef(*tv
));
2086 else appendUninit();
2087 actions
[id
] = StoreVV
;
2092 auto const val
= [&]{
2093 if (!global
) return env
.lookup(StrNR
{f
->localVarName(id
)});
2094 auto const rval
= fp
->m_varEnv
->lookup(f
->localVarName(id
));
2095 return rval
? *rval
: make_tv
<KindOfUninit
>();
2097 if (val
.is_init()) args
.append(val
);
2098 else appendUninit();
2100 args
.append(make_tv
<KindOfNull
>()); // $__debugger_exn$output
2102 auto const obj
= ctx
&& fp
->hasThis() ? fp
->getThis() : nullptr;
2103 auto const cls
= ctx
&& fp
->hasClass() ? fp
->getClass() : nullptr;
2104 auto const arr_tv
= invokeFunc(f
, args
.toArray(), obj
, cls
, false);
2105 assertx(isArrayLikeType(type(arr_tv
)));
2106 assertx(val(arr_tv
).parr
->size() == f
->numParams() + 1);
2107 Array arr
= Array::attach(val(arr_tv
).parr
);
2108 for (Id id
= 0; id
< f
->numParams() - 1; id
++) {
2109 auto const tv
= arr
.lookup(id
+ 1);
2110 if (isObjectType(type(tv
)) &&
2111 val(tv
).pobj
->instanceof(uninit_cls
)) {
2112 switch (actions
[id
]) {
2114 variant_ref
{frame_local(fp
, frameIds
[id
])}.unset();
2117 fp
->m_varEnv
->unset(f
->localVarName(id
));
2121 fp
->m_varEnv
->unset(f
->localVarName(id
));
2123 env
.remove(StrNR
{f
->localVarName(id
)});
2129 switch (actions
[id
]) {
2131 variant_ref
{frame_local(fp
, frameIds
[id
])} = arr
[id
+ 1];
2134 fp
->m_varEnv
->set(f
->localVarName(id
), &tv
);
2138 fp
->m_varEnv
->set(f
->localVarName(id
), &tv
);
2140 env
.set(StrNR
{f
->localVarName(id
)}, tv
);
2145 auto const exn
= arr
[int64_t(f
->numParams())];
2146 if (exn
.isObject()) {
2147 assertx(exn
.toObject().instanceof("Throwable"));
2148 throw_object(exn
.toObject());
2150 assertx(exn
.isNull());
2152 return {false, arr
[0], ""};
2153 } catch (FatalErrorException
& e
) {
2154 errorString
<< s_fatal
.data();
2155 errorString
<< " : ";
2156 errorString
<< e
.getMessage().c_str();
2157 errorString
<< "\n";
2158 stack
= ExtendedLogger::StringOfStackTrace(e
.getBacktrace());
2159 } catch (ExitException
& e
) {
2160 errorString
<< s_exit
.data();
2161 errorString
<< " : ";
2162 errorString
<< *rl_exit_code
;
2163 } catch (Eval::DebuggerException
& e
) {
2164 } catch (Exception
& e
) {
2165 errorString
<< s_cppException
.data();
2166 errorString
<< " : ";
2167 errorString
<< e
.getMessage().c_str();
2168 ExtendedException
* ee
= dynamic_cast<ExtendedException
*>(&e
);
2170 errorString
<< "\n";
2171 stack
= ExtendedLogger::StringOfStackTrace(ee
->getBacktrace());
2173 } catch (Object
&e
) {
2174 errorString
<< s_phpException
.data();
2175 errorString
<< " : ";
2177 errorString
<< throwable_to_string(e
.get()).data();
2179 errorString
<< e
->getVMClass()->name()->data();
2182 errorString
<< s_cppException
.data();
2185 auto errorStr
= errorString
.str();
2186 g_context
->write(errorStr
);
2187 if (!stack
.empty()) {
2188 g_context
->write(stack
.c_str());
2191 return {true, init_null_variant
, errorStr
};
2194 const StaticString
s_include("include");
2196 void ExecutionContext::enterDebuggerDummyEnv() {
2197 static Unit
* s_debuggerDummy
= compile_debugger_string(
2198 "<?hh", 4, RepoOptions::defaults()
2200 // Ensure that the VM stack is completely empty (vmfp() should be null)
2201 // and that we're not in a nested VM (reentrancy)
2202 assertx(vmfp() == nullptr);
2203 assertx(m_nestedVMs
.size() == 0);
2204 assertx(m_nesting
== 0);
2205 assertx(vmStack().count() == 0);
2206 ActRec
* ar
= vmStack().allocA();
2207 ar
->m_func
= s_debuggerDummy
->lookupFuncId(1);
2208 assertx(ar
->m_func
&& ar
->m_func
->name()->equal(s_include
.get()));
2210 for (int i
= 0; i
< ar
->m_func
->numLocals(); ++i
) vmStack().pushInt(1);
2212 ar
->setReturnVMExit();
2214 vmpc() = s_debuggerDummy
->entry();
2216 vmfp()->setVarEnv(m_globalVarEnv
);
2217 m_globalVarEnv
->enterFP(nullptr, vmfp());
2220 void ExecutionContext::exitDebuggerDummyEnv() {
2221 assertx(m_globalVarEnv
);
2222 // Ensure that vmfp() is valid
2223 assertx(vmfp() != nullptr);
2224 // Ensure that vmfp() points to the only frame on the call stack.
2225 // In other words, make sure there are no VM frames directly below
2226 // this one and that we are not in a nested VM (reentrancy)
2227 assertx(!vmfp()->sfp());
2228 assertx(m_nestedVMs
.size() == 0);
2229 assertx(m_nesting
== 0);
2230 // Teardown the frame we erected by enterDebuggerDummyEnv()
2231 const Func
* func
= vmfp()->m_func
;
2233 vmfp()->setLocalsDecRefd();
2234 frame_free_locals_no_hook(vmfp());
2236 vmStack().ndiscard(func
->numSlotsInFrame());
2237 vmStack().discardAR();
2238 // After tearing down this frame, the VM stack should be completely empty
2239 assertx(vmStack().count() == 0);
2244 ThrowAllErrorsSetter::ThrowAllErrorsSetter() {
2245 m_throwAllErrors
= g_context
->getThrowAllErrors();
2246 g_context
->setThrowAllErrors(true);
2249 ThrowAllErrorsSetter::~ThrowAllErrorsSetter() {
2250 g_context
->setThrowAllErrors(m_throwAllErrors
);