Compute ambient coeffects and write to coeffects local
[hiphop-php.git] / hphp / runtime / vm / interp-helpers.h
blob381204a2f17259061cfc7796c78d62cf2a13266d
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 #pragma once
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>
38 namespace HPHP {
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;
51 vmStack().discard();
52 return Array::attach(generics);
54 static void push(Array&& generics) {
55 if (LIKELY(generics.isNull())) return;
56 vmStack().pushArrayLikeNoRc(generics.detach());
59 private:
60 Array m_generics;
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;
74 vmStack().discard();
75 return RuntimeCoeffects::fromValue(coeffects);
77 static void push(folly::Optional<RuntimeCoeffects> coeffects) {
78 if (LIKELY(!coeffects)) return;
79 vmStack().pushInt(coeffects->value());
82 private:
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();
99 if (dynCallable) {
100 if (allowDynCallNoPointer) return;
101 if (!RO::EvalForbidDynamicCallsWithAttr) return;
103 auto level = func->isMethod()
104 ? (func->isStatic()
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;
113 level = 1;
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)) {
120 std::string msg;
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;
133 level = 1;
136 if (level >= 2) {
137 std::string msg;
138 string_printf(
139 msg,
140 Strings::CLASS_CONSTRUCTED_DYNAMICALLY,
141 cls->name()->data()
143 throw_invalid_operation_exception(makeStaticString(msg));
144 } else {
145 raise_notice(
146 Strings::CLASS_CONSTRUCTED_DYNAMICALLY,
147 cls->name()->data()
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()) {
162 raise_notice(
163 error_msg,
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
172 * an exception.
174 inline bool calleeCoeffectChecks(const Func* callee,
175 RuntimeCoeffects providedCoeffects,
176 uint32_t numArgsInclUnpack,
177 void* prologueCtx) {
178 if (!CoeffectsConfig::enabled()) {
179 if (callee->hasCoeffectRules()) {
180 vmStack().pushInt(RuntimeCoeffects::none().value());
182 return true;
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);
191 required &= rules;
192 auto ambient = callee->staticCoeffects().toAmbient();
193 ambient &= rules;
194 vmStack().pushInt(ambient.value());
195 return required;
196 }();
197 if (LIKELY(providedCoeffects.canCall(requiredCoeffects))) return true;
198 raiseCoeffectsCallViolation(callee, providedCoeffects, requiredCoeffects);
199 return false;
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();
208 return;
211 if (!hasGenerics) {
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
219 // are on the stack.
220 ARRPROV_USE_RUNTIME_LOCATION();
221 auto const ad = ArrayData::CreateVArray();
222 vmStack().pushArrayLikeNoRc(ad);
223 return;
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();
248 vmStack().popC();
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) {
260 return;
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();
279 ++numArgsInclUnpack;
282 if (callee->hasVariadicCaptureParam()) {
283 arrprov::TagOverride _(RO::EvalArrayProvenance
284 ? arrprov::Tag::Param(callee, numParams)
285 : arrprov::Tag{});
286 auto const ad = ArrayData::CreateVArray();
287 vmStack().pushArrayLikeNoRc(ad);
288 ++numArgsInclUnpack;
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
307 * and the VM 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.)
313 ALWAYS_INLINE
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();