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 +----------------------------------------------------------------------+
19 #include "hphp/runtime/base/builtin-functions.h"
20 #include "hphp/runtime/base/coeffects-config.h"
21 #include "hphp/runtime/base/exceptions.h"
22 #include "hphp/runtime/base/implicit-context.h"
23 #include "hphp/runtime/base/runtime-error.h"
24 #include "hphp/runtime/base/runtime-option.h"
25 #include "hphp/runtime/base/request-info.h"
26 #include "hphp/runtime/vm/act-rec.h"
27 #include "hphp/runtime/vm/bytecode.h"
28 #include "hphp/runtime/vm/coeffects.h"
29 #include "hphp/runtime/vm/func.h"
30 #include "hphp/runtime/vm/hhbc.h"
31 #include "hphp/runtime/vm/reified-generics.h"
32 #include "hphp/runtime/vm/runtime.h"
33 #include "hphp/util/text-util.h"
34 #include "hphp/util/trace.h"
36 #include <folly/Random.h>
41 * RAII wrapper for popping/pushing generics from/to the VM stack.
43 struct GenericsSaver
{
44 explicit GenericsSaver(bool hasGenerics
) : m_generics(pop(hasGenerics
)) {}
45 ~GenericsSaver() { push(std::move(m_generics
)); }
47 static Array
pop(bool hasGenerics
) {
48 if (LIKELY(!hasGenerics
)) return Array();
49 assertx(tvIsHAMSafeVArray(vmStack().topC()));
50 auto const generics
= vmStack().topC()->m_data
.parr
;
52 return Array::attach(generics
);
54 static void push(Array
&& generics
) {
55 if (LIKELY(generics
.isNull())) return;
56 vmStack().pushArrayLikeNoRc(generics
.detach());
64 * RAII wrapper for popping/pushing coeffects from/to the VM stack.
66 struct CoeffectsSaver
{
67 explicit CoeffectsSaver(bool hasCoeffects
) : m_coeffects(pop(hasCoeffects
)) {}
68 ~CoeffectsSaver() { push(m_coeffects
); }
70 static folly::Optional
<RuntimeCoeffects
> pop(bool hasCoeffects
) {
71 if (LIKELY(!hasCoeffects
)) return folly::none
;
72 assertx(tvIsInt(vmStack().topC()));
73 auto const coeffects
= vmStack().topC()->m_data
.num
;
75 return RuntimeCoeffects::fromValue(coeffects
);
77 static void push(folly::Optional
<RuntimeCoeffects
> coeffects
) {
78 if (LIKELY(!coeffects
)) return;
79 vmStack().pushInt(coeffects
->value());
83 folly::Optional
<RuntimeCoeffects
> m_coeffects
;
86 inline void callerInOutChecks(const Func
* func
, const FCallArgs
& fca
) {
87 for (auto i
= 0; i
< fca
.numArgs
; ++i
) {
88 auto const inout
= func
->isInOut(i
);
89 if (inout
!= fca
.isInOut(i
)) {
90 SystemLib::throwInvalidArgumentExceptionObject(
91 formatParamInOutMismatch(func
->fullName()->data(), i
, inout
));
96 inline void callerDynamicCallChecks(const Func
* func
,
97 bool allowDynCallNoPointer
= false) {
98 auto dynCallable
= func
->isDynamicallyCallable();
100 if (allowDynCallNoPointer
) return;
101 if (!RO::EvalForbidDynamicCallsWithAttr
) return;
103 auto level
= func
->isMethod()
105 ? RO::EvalForbidDynamicCallsToClsMeth
106 : RO::EvalForbidDynamicCallsToInstMeth
)
107 : RO::EvalForbidDynamicCallsToFunc
;
108 if (level
<= 0) return;
109 if (dynCallable
&& level
< 2) return;
111 if (auto const rate
= func
->dynCallSampleRate()) {
112 if (folly::Random::rand32(*rate
) != 0) return;
116 auto error_msg
= dynCallable
?
117 Strings::FUNCTION_CALLED_DYNAMICALLY_WITH_ATTRIBUTE
:
118 Strings::FUNCTION_CALLED_DYNAMICALLY_WITHOUT_ATTRIBUTE
;
119 if (level
>= 3 || (level
>= 2 && !dynCallable
)) {
121 string_printf(msg
, error_msg
, func
->fullName()->data());
122 throw_invalid_operation_exception(makeStaticString(msg
));
124 raise_notice(error_msg
, func
->fullName()->data());
127 inline void callerDynamicConstructChecks(const Class
* cls
) {
128 auto level
= RO::EvalForbidDynamicConstructs
;
129 if (level
<= 0 || cls
->isDynamicallyConstructible()) return;
131 if (auto const rate
= cls
->dynConstructSampleRate()) {
132 if (folly::Random::rand32(*rate
) != 0) return;
140 Strings::CLASS_CONSTRUCTED_DYNAMICALLY
,
143 throw_invalid_operation_exception(makeStaticString(msg
));
146 Strings::CLASS_CONSTRUCTED_DYNAMICALLY
,
152 inline void calleeDynamicCallChecks(const Func
* func
, bool dynamicCall
,
153 bool allowDynCallNoPointer
= false) {
154 if (!dynamicCall
) return;
155 if (func
->isDynamicallyCallable() && allowDynCallNoPointer
) return;
157 auto error_msg
= func
->isDynamicallyCallable() ?
158 Strings::FUNCTION_CALLED_DYNAMICALLY_WITH_ATTRIBUTE
:
159 Strings::FUNCTION_CALLED_DYNAMICALLY_WITHOUT_ATTRIBUTE
;
161 if (RuntimeOption::EvalNoticeOnBuiltinDynamicCalls
&& func
->isBuiltin()) {
164 func
->fullName()->data()
170 * Check if the `callee` satisfies the coeffect constraints on the call flags.
171 * Returns true if yes, otherwise raise a warning and return false or raise
174 inline bool calleeCoeffectChecks(const Func
* callee
,
175 RuntimeCoeffects providedCoeffects
,
176 uint32_t numArgsInclUnpack
,
178 if (!CoeffectsConfig::enabled()) {
179 if (callee
->hasCoeffectRules()) {
180 vmStack().pushInt(RuntimeCoeffects::none().value());
184 auto const requiredCoeffects
= [&] {
185 auto required
= callee
->staticCoeffects().toRequired();
186 if (!callee
->hasCoeffectRules()) return required
;
187 auto rules
= RuntimeCoeffects::full();
188 for (auto const& rule
: callee
->getCoeffectRules()) {
189 rules
&= rule
.emit(callee
, numArgsInclUnpack
, prologueCtx
);
192 auto ambient
= callee
->staticCoeffects().toAmbient();
194 vmStack().pushInt(ambient
.value());
197 if (LIKELY(providedCoeffects
.canCall(requiredCoeffects
))) return true;
198 raiseCoeffectsCallViolation(callee
, providedCoeffects
, requiredCoeffects
);
203 * Check for presence, count and wildcard match of generics.
205 inline void calleeGenericsChecks(const Func
* callee
, bool hasGenerics
) {
206 if (LIKELY(!callee
->hasReifiedGenerics())) {
207 if (UNLIKELY(hasGenerics
)) vmStack().popC();
212 if (!areAllGenericsSoft(callee
->getReifiedGenericsInfo())) {
213 throw_call_reified_func_without_generics(callee
);
216 raise_warning_for_soft_reified(0, true, callee
->fullName());
218 // Push an empty array, as the remainder of the call setup assumes generics
220 ARRPROV_USE_RUNTIME_LOCATION();
221 auto const ad
= ArrayData::CreateVArray();
222 vmStack().pushArrayLikeNoRc(ad
);
226 auto const generics
= vmStack().topC();
227 assertx(tvIsHAMSafeVArray(generics
));
228 checkFunReifiedGenericMismatch(callee
, val(generics
).parr
);
232 * Check for too few or too many arguments and trim extra args.
234 inline void calleeArgumentArityChecks(const Func
* callee
,
235 uint32_t numArgsInclUnpack
) {
236 if (numArgsInclUnpack
< callee
->numRequiredParams()) {
237 throwMissingArgument(callee
, numArgsInclUnpack
);
240 if (numArgsInclUnpack
> callee
->numParams()) {
241 assertx(!callee
->hasVariadicCaptureParam());
242 assertx(numArgsInclUnpack
== callee
->numNonVariadicParams() + 1);
244 GenericsSaver gs
{callee
->hasReifiedGenerics()};
246 assertx(tvIsHAMSafeVArray(vmStack().topC()));
247 auto const numUnpackArgs
= vmStack().topC()->m_data
.parr
->size();
250 if (numUnpackArgs
!= 0) {
251 raiseTooManyArguments(callee
, numArgsInclUnpack
+ numUnpackArgs
- 1);
256 inline void calleeImplicitContextChecks(const Func
* callee
) {
257 if (!RO::EvalEnableImplicitContext
||
258 !callee
->hasNoContextAttr() ||
259 *ImplicitContext::activeCtx
== nullptr) {
262 throw_implicit_context_exception(folly::to
<std::string
>(
263 "Function ", callee
->fullName()->data(), " has implicit context "
264 "but is marked with __NoContext"));
267 inline void initFuncInputs(const Func
* callee
, uint32_t numArgsInclUnpack
) {
268 assertx(numArgsInclUnpack
<= callee
->numNonVariadicParams() + 1);
270 // All arguments already initialized. Extra arguments already popped
271 // by calleeArgumentArityChecks().
272 if (LIKELY(numArgsInclUnpack
>= callee
->numParams())) return;
274 CoeffectsSaver cs
{callee
->hasCoeffectRules()};
275 GenericsSaver gs
{callee
->hasReifiedGenerics()};
276 auto const numParams
= callee
->numNonVariadicParams();
277 while (numArgsInclUnpack
< numParams
) {
278 vmStack().pushUninit();
282 if (callee
->hasVariadicCaptureParam()) {
283 arrprov::TagOverride
_(RO::EvalArrayProvenance
284 ? arrprov::Tag::Param(callee
, numParams
)
286 auto const ad
= ArrayData::CreateVArray();
287 vmStack().pushArrayLikeNoRc(ad
);
291 assertx(numArgsInclUnpack
== callee
->numParams());
295 * This helper only does a stack overflow check for the native stack.
296 * Both native and VM stack overflows are independently possible.
298 inline void checkNativeStack() {
299 // Check whether we're going out of bounds of our native stack.
300 if (LIKELY(stack_in_bounds())) return;
301 TRACE_MOD(Trace::gc
, 1, "Maximum stack depth exceeded.\n");
302 throw_stack_overflow();
306 * This helper does a stack overflow check on *both* the native stack
309 * In some cases for re-entry, we're checking for space other than
310 * just the callee, and `extraCells' may need to be passed with a
311 * non-zero value. (We over-check in these situations, but it's fine.)
314 void checkStack(Stack
& stk
, const Func
* f
, int32_t extraCells
) {
316 * Check whether func's maximum stack usage would overflow the stack.
317 * Both native and VM stack overflows are independently possible.
319 * All stack checks are inflated by kStackCheckPadding to ensure
320 * there is space both for calling leaf functions /and/ for
321 * re-entry. (See kStackCheckReenterPadding and
322 * kStackCheckLeafPadding.)
324 auto limit
= f
->maxStackCells() + kStackCheckPadding
+ extraCells
;
325 if (LIKELY(stack_in_bounds() && !stk
.wouldOverflow(limit
))) return;
326 TRACE_MOD(Trace::gc
, 1, "Maximum stack depth exceeded.\n");
327 throw_stack_overflow();