Clear the container before decoding for inplace adapter
[hiphop-php.git] / hphp / hhbbc / interp-builtin.cpp
blob756f3ce6bbeaa9bc15a1636c10bd57940e2ba046
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 +----------------------------------------------------------------------+
16 #include "hphp/hhbbc/interp.h"
18 #include "hphp/util/match.h"
19 #include "hphp/util/trace.h"
21 #include "hphp/runtime/base/array-init.h"
22 #include "hphp/runtime/base/array-iterator.h"
23 #include "hphp/runtime/base/execution-context.h"
24 #include "hphp/runtime/base/type-structure-helpers-defs.h"
26 #include "hphp/hhbbc/eval-cell.h"
27 #include "hphp/hhbbc/interp-internal.h"
28 #include "hphp/hhbbc/optimize.h"
29 #include "hphp/hhbbc/type-builtins.h"
30 #include "hphp/hhbbc/type-structure.h"
31 #include "hphp/hhbbc/type-system.h"
32 #include "hphp/hhbbc/unit-util.h"
34 namespace HPHP::HHBBC {
36 namespace {
38 //////////////////////////////////////////////////////////////////////
40 const Type getArg(ISS& env, const php::Func* func, const FCallArgs& fca,
41 uint32_t idx) {
42 assertx(idx < func->params.size());
43 if (idx < fca.numArgs()) return topC(env, fca.numArgs() - idx - 1);
44 auto const& pi = func->params[idx];
45 assertx(pi.defaultValue.m_type != KindOfUninit);
46 return from_cell(pi.defaultValue);
49 //////////////////////////////////////////////////////////////////////
51 struct Reduced {};
52 struct NoReduced {};
53 using TypeOrReduced = boost::variant<Type, Reduced, NoReduced>;
55 //////////////////////////////////////////////////////////////////////
57 TypeOrReduced builtin_get_class(ISS& env, const php::Func* func,
58 const FCallArgs& fca) {
59 assertx(fca.numArgs() >= 0 && fca.numArgs() <= 1);
60 auto const ty = getArg(env, func, fca, 0);
62 if (!ty.subtypeOf(BObj)) return NoReduced{};
64 if (!is_specialized_obj(ty)) return TStr;
65 auto const& d = dobj_of(ty);
66 if (!d.isExact()) return TStr;
67 constprop(env);
68 return sval(d.cls().name());
71 TypeOrReduced builtin_abs(ISS& env, const php::Func* func,
72 const FCallArgs& fca) {
73 assertx(fca.numArgs() == 1);
74 auto const ty = getArg(env, func, fca, 0);
75 return ty.subtypeOf(BInt) ? TInt :
76 ty.subtypeOf(BDbl) ? TDbl :
77 TInitUnc;
80 /**
81 * if the input to these functions is known to be integer or double,
82 * the result will be a double. Otherwise, the result is conditional
83 * on a successful conversion and an accurate number of arguments.
85 TypeOrReduced floatIfNumeric(ISS& env, const php::Func* func,
86 const FCallArgs& fca) {
87 assertx(fca.numArgs() == 1);
88 auto const ty = getArg(env, func, fca, 0);
89 return ty.subtypeOf(BNum) ? TDbl : TInitUnc;
91 TypeOrReduced builtin_ceil(ISS& env, const php::Func* func,
92 const FCallArgs& fca) {
93 return floatIfNumeric(env, func, fca);
95 TypeOrReduced builtin_floor(ISS& env, const php::Func* func,
96 const FCallArgs& fca) {
97 return floatIfNumeric(env, func, fca);
101 * The compiler specializes the two-arg version of min() and max()
102 * into an HNI provided helper. If both arguments are an integer
103 * or both arguments are a double, we know the exact type of the
104 * return value. If they're both numeric, the result is at least
105 * numeric.
107 TypeOrReduced minmax2(ISS& env, const php::Func* func, const FCallArgs& fca) {
108 assertx(fca.numArgs() == 2);
109 auto const t0 = getArg(env, func, fca, 0);
110 auto const t1 = getArg(env, func, fca, 1);
111 if (!t0.subtypeOf(BNum) || !t1.subtypeOf(BNum)) return NoReduced{};
112 return t0 == t1 ? t0 : TNum;
114 TypeOrReduced builtin_max2(ISS& env, const php::Func* func,
115 const FCallArgs& fca) {
116 return minmax2(env, func, fca);
118 TypeOrReduced builtin_min2(ISS& env, const php::Func* func,
119 const FCallArgs& fca) {
120 return minmax2(env, func, fca);
123 TypeOrReduced builtin_strlen(ISS& env, const php::Func* func,
124 const FCallArgs& fca) {
125 assertx(fca.numArgs() == 1);
126 auto const ty = getArg(env, func, fca, 0);
127 // Returns null and raises a warning when input is an array, resource, or
128 // object.
129 if (ty.subtypeOf(BPrim | BStr)) effect_free(env);
130 return ty.subtypeOf(BPrim | BStr) ? TInt : TOptInt;
133 TypeOrReduced builtin_is_numeric(ISS& env, const php::Func* func,
134 const FCallArgs& fca) {
135 assertx(fca.numArgs() == 1);
136 auto const ty = getArg(env, func, fca, 0);
137 if (!ty.couldBe(BInt | BDbl | BStr)) return TFalse;
138 if (ty.subtypeOf(BInt | BDbl)) return TTrue;
139 if (ty.subtypeOf(BStr)) {
140 if (auto const val = tv(ty)) {
141 int64_t ival;
142 double dval;
143 auto const dt = val->m_data.pstr->toNumeric(ival, dval);
144 return dt == KindOfInt64 || dt == KindOfDouble ? TTrue : TFalse;
147 return TBool;
150 TypeOrReduced builtin_function_exists(ISS& env, const php::Func* func,
151 const FCallArgs& fca) {
152 assertx(fca.numArgs() >= 1 && fca.numArgs() <= 2);
153 if (!handle_function_exists(env, getArg(env, func, fca, 0))) {
154 return NoReduced{};
156 constprop(env);
157 return TTrue;
160 TypeOrReduced handle_oodecl_exists(ISS& env,
161 const php::Func* func, const FCallArgs& fca,
162 OODeclExistsOp subop) {
163 assertx(fca.numArgs() >= 1 && fca.numArgs() <= 2);
164 auto const name = getArg(env, func, fca, 0);
165 auto const autoload = getArg(env, func, fca, 1);
166 if (name.subtypeOf(BStr)) {
167 if (fca.numArgs() == 1) {
168 reduce(env, bc::True {});
169 } else if (!autoload.subtypeOf(BBool)) {
170 reduce(env, bc::CastBool {});
172 reduce(env, bc::OODeclExists { subop });
173 return Reduced{};
175 if (!autoload.strictSubtypeOf(TBool)) return NoReduced{};
176 auto const v = tv(autoload);
177 assertx(v);
178 if (fca.numArgs() == 2) reduce(env, bc::PopC {});
179 reduce(env,
180 bc::CastString {},
181 gen_constant(*v),
182 bc::OODeclExists { subop });
183 return Reduced{};
186 TypeOrReduced builtin_class_exists(ISS& env, const php::Func* func,
187 const FCallArgs& fca) {
188 return handle_oodecl_exists(env, func, fca, OODeclExistsOp::Class);
191 TypeOrReduced builtin_interface_exists(ISS& env, const php::Func* func,
192 const FCallArgs& fca) {
193 return handle_oodecl_exists(env, func, fca, OODeclExistsOp::Interface);
196 TypeOrReduced builtin_trait_exists(ISS& env, const php::Func* func,
197 const FCallArgs& fca) {
198 return handle_oodecl_exists(env, func, fca, OODeclExistsOp::Trait);
201 TypeOrReduced builtin_array_key_cast(ISS& env, const php::Func* func,
202 const FCallArgs& fca) {
203 assertx(fca.numArgs() == 1);
204 auto const ty = getArg(env, func, fca, 0);
206 if (ty.subtypeOf(BNum) || ty.subtypeOf(BBool) || ty.subtypeOf(BRes)) {
207 reduce(env, bc::CastInt {});
208 return Reduced{};
211 auto retTy = TBottom;
212 if (ty.couldBe(BNull)) {
213 retTy |= sval(staticEmptyString());
215 if (ty.couldBe(BNum | BBool | BRes)) {
216 retTy |= TInt;
218 if (ty.couldBe(BCls)) {
219 if (is_specialized_cls(ty)) {
220 auto const& dcls = dcls_of(ty);
221 if (dcls.isExact()) {
222 auto cname = dcls.cls().name();
223 retTy |= sval(cname);
224 } else {
225 retTy |= TSStr;
227 } else {
228 retTy |= TSStr;
231 if (ty.couldBe(BLazyCls)) {
232 if (is_specialized_lazycls(ty)) {
233 auto cname = lazyclsval_of(ty);
234 retTy |= sval(cname);
235 } else {
236 retTy |= TSStr;
239 if (ty.couldBe(BStr)) {
240 retTy |= [&] {
241 if (ty.subtypeOf(BSStr)) {
242 auto const v = tv(ty);
243 if (v) {
244 int64_t i;
245 if (v->m_data.pstr->isStrictlyInteger(i)) {
246 return ival(i);
248 return ty;
250 return TUncArrKey;
252 return TArrKey;
253 }();
256 if (!ty.couldBe(BObj | BArrLike)) {
257 constprop(env);
258 effect_free(env);
261 if (retTy == TBottom) unreachable(env);
262 return retTy;
265 TypeOrReduced builtin_is_callable(ISS& env, const php::Func* func,
266 const FCallArgs& fca) {
267 assertx(fca.numArgs() >= 1 && fca.numArgs() <= 2);
268 // Do not handle syntax-only checks or name output.
269 if (getArg(env, func, fca, 1) != TFalse) return NoReduced{};
271 auto const ty = getArg(env, func, fca, 0);
272 if (ty == TInitCell) return NoReduced{};
273 auto const res = [&]() -> Optional<bool> {
274 if (ty.subtypeOf(BClsMeth | BFunc)) return true;
275 if (ty.subtypeOf(BArrLikeE | BKeyset) ||
276 !ty.couldBe(BClsMeth | BFunc | BVec | BDict | BObj | BStr)) {
277 return false;
279 return {};
280 }();
281 if (!res) return NoReduced{};
282 if (fca.numArgs() == 2) reduce(env, bc::PopC {});
283 reduce(env, bc::PopC {});
284 *res ? reduce(env, bc::True {}) : reduce(env, bc::False {});
285 return Reduced{};
288 TypeOrReduced builtin_is_list_like(ISS& env, const php::Func* func,
289 const FCallArgs& fca) {
290 assertx(fca.numArgs() == 1);
291 auto const ty = getArg(env, func, fca, 0);
293 if (!ty.couldBe(BClsMeth)) {
294 constprop(env);
295 effect_free(env);
298 if (!ty.couldBe(BArrLike | BClsMeth)) return TFalse;
299 if (ty.subtypeOf(BVec | BClsMeth)) return TTrue;
301 switch (categorize_array(ty).cat) {
302 case Type::ArrayCat::Empty:
303 case Type::ArrayCat::Packed:
304 return TTrue;
305 case Type::ArrayCat::Mixed:
306 case Type::ArrayCat::Struct:
307 return TFalse;
308 case Type::ArrayCat::None:
309 return NoReduced{};
311 always_assert(false);
315 * Optimize type_structure, type_structure_nothrow, and
316 * type_structure_classname flavors. Returns the best known result
317 * they produce, and whether they might throw. Returns std::nullopt if
318 * no optimization should be done.
320 Optional<std::pair<Type, TriBool>>
321 impl_builtin_type_structure(ISS& env, const php::Func* func,
322 const FCallArgs& fca,
323 bool no_throw_on_undefined) {
324 assertx(fca.numArgs() >= 1 && fca.numArgs() <= 2);
326 auto const name = getArg(env, func, fca, 1);
328 if (name.subtypeOf(BInitNull)) {
329 // Type-alias case:
330 auto throws = TriBool::No;
331 auto const clsStr = [&] () -> SString {
332 auto const t = getArg(env, func, fca, 0);
333 if (t.subtypeOf(BCls) && is_specialized_cls(t)) {
334 auto const& dcls = dcls_of(t);
335 if (!dcls.isExact()) return nullptr;
336 if (RO::EvalRaiseClassConversionWarning) throws = TriBool::Maybe;
337 return dcls.cls().name();
339 if (t.subtypeOf(BStr) && is_specialized_string(t)) {
340 return sval_of(t);
342 if (t.subtypeOf(BLazyCls) && is_specialized_lazycls(t)) {
343 if (RO::EvalRaiseClassConversionWarning) throws = TriBool::Maybe;
344 return lazyclsval_of(t);
346 return nullptr;
347 }();
348 if (!clsStr) return std::nullopt;
350 auto const typeAlias = env.index.lookup_type_alias(clsStr);
351 if (!typeAlias) {
352 // No type-alias with that name
353 unreachable(env);
354 return std::make_pair(TBottom, TriBool::Yes);
357 // Found a type-alias, resolve it's type-structure.
358 auto const r = resolve_type_structure(env.index, *typeAlias);
359 if (r.type.is(BBottom)) {
360 // Resolution will always fail
361 unreachable(env);
362 return std::make_pair(TBottom, TriBool::Yes);
365 return std::make_pair(
366 r.type,
367 noOrMaybe(throws == TriBool::No && !r.mightFail)
369 } else if (!name.subtypeOf(BStr)) {
370 // Don't know whether it's a class or type-alias.
371 return std::nullopt;
374 // Class constant case:
376 auto throws = TriBool::No;
378 auto const cls = [&] {
379 auto const t = getArg(env, func, fca, 0);
380 if (t.subtypeOf(BCls)) return t;
381 if (t.subtypeOf(BObj)) return objcls(t);
382 if (t.subtypeOf(BStr) && is_specialized_string(t)) {
383 auto const str = sval_of(t);
384 auto const rcls = env.index.resolve_class(env.ctx, str);
385 if (!rcls || !rcls->resolved()) {
386 throws = TriBool::Maybe;
387 return TCls;
389 return clsExact(*rcls);
391 if (t.subtypeOf(BLazyCls) && is_specialized_lazycls(t)) {
392 auto const str = lazyclsval_of(t);
393 auto const rcls = env.index.resolve_class(env.ctx, str);
394 if (!rcls || !rcls->resolved()) {
395 throws = TriBool::Maybe;
396 return TCls;
398 return clsExact(*rcls);
400 throws = TriBool::Maybe;
401 return TCls;
402 }();
404 auto lookup = env.index.lookup_class_type_constant(cls, name);
405 assertx(lookup.resolution.type.subtypeOf(BSDictN));
407 // Match behavior of runtime and return "fake" resolution for
408 // nothrow failure.
409 auto const fake = [] {
410 auto array = make_dict_array(
411 s_kind,
412 Variant(static_cast<uint8_t>(TypeStructure::Kind::T_class)),
413 s_classname,
414 Variant(s_type_structure_non_existant_class)
416 array.setEvalScalar();
417 return dict_val(array.get());
420 switch (lookup.found) {
421 case TriBool::No:
422 assertx(!lookup.resolution.mightFail);
423 assertx(lookup.resolution.type.is(BBottom));
424 if (!no_throw_on_undefined || lookup.abstract == TriBool::No) {
425 throws = TriBool::Yes;
426 } else {
427 if (lookup.abstract == TriBool::Maybe) throws = TriBool::Maybe;
428 lookup.resolution.type = fake();
430 break;
431 case TriBool::Maybe:
432 if (!no_throw_on_undefined || lookup.abstract == TriBool::No) {
433 throws = TriBool::Maybe;
434 } else {
435 if (lookup.abstract == TriBool::Maybe) throws = TriBool::Maybe;
436 lookup.resolution.type |= fake();
438 break;
439 case TriBool::Yes:
440 assertx(lookup.abstract == TriBool::No);
441 break;
444 if (lookup.resolution.mightFail) {
445 if (no_throw_on_undefined) {
446 lookup.resolution.type |= fake();
447 } else if (throws != TriBool::Yes) {
448 throws =
449 lookup.resolution.type.is(BBottom) ? TriBool::Yes : TriBool::Maybe;
453 return std::make_pair(lookup.resolution.type, throws);
456 TypeOrReduced builtin_type_structure(ISS& env, const php::Func* func,
457 const FCallArgs& fca) {
458 auto const r = impl_builtin_type_structure(env, func, fca, false);
459 if (!r) return NoReduced{};
460 switch (r->second) {
461 case TriBool::Yes:
462 unreachable(env);
463 return TBottom;
464 case TriBool::No:
465 effect_free(env);
466 constprop(env);
467 return r->first;
468 case TriBool::Maybe:
469 return r->first;
471 always_assert(false);
474 TypeOrReduced builtin_type_structure_no_throw(ISS& env, const php::Func* func,
475 const FCallArgs& fca) {
476 auto const r = impl_builtin_type_structure(env, func, fca, true);
477 if (!r) return NoReduced{};
478 switch (r->second) {
479 case TriBool::Yes:
480 unreachable(env);
481 return TBottom;
482 case TriBool::No:
483 effect_free(env);
484 constprop(env);
485 return r->first;
486 case TriBool::Maybe:
487 return r->first;
489 always_assert(false);
492 const StaticString s_classname("classname");
494 TypeOrReduced builtin_type_structure_classname(ISS& env, const php::Func* func,
495 const FCallArgs& fca) {
496 auto const r = impl_builtin_type_structure(env, func, fca, false);
497 if (!r) return NoReduced{};
498 if (r->second == TriBool::Yes) {
499 unreachable(env);
500 return TBottom;
502 assertx(r->first.subtypeOf(BDictN));
504 auto const [classname, present] =
505 array_like_elem(r->first, sval(s_classname.get()));
506 if (classname.is(BBottom)) {
507 unreachable(env);
508 return TBottom;
510 if (!classname.couldBe(BSStr)) return NoReduced{};
512 if (r->second == TriBool::No && present) {
513 effect_free(env);
514 constprop(env);
516 return intersection_of(classname, TSStr);
519 #define SPECIAL_BUILTINS \
520 X(abs, abs) \
521 X(ceil, ceil) \
522 X(floor, floor) \
523 X(get_class, get_class) \
524 X(max2, max2) \
525 X(min2, min2) \
526 X(strlen, strlen) \
527 X(is_numeric, is_numeric) \
528 X(function_exists, function_exists) \
529 X(class_exists, class_exists) \
530 X(interface_exists, interface_exists) \
531 X(trait_exists, trait_exists) \
532 X(array_key_cast, HH\\array_key_cast) \
533 X(is_callable, is_callable) \
534 X(is_list_like, HH\\is_list_like) \
535 X(type_structure, HH\\type_structure) \
536 X(type_structure_no_throw, HH\\type_structure_no_throw) \
537 X(type_structure_classname, HH\\type_structure_classname) \
539 #define X(x, y) const StaticString s_##x(#y);
540 SPECIAL_BUILTINS
541 #undef X
543 bool handle_builtin(ISS& env, const php::Func* func, const FCallArgs& fca) {
544 auto const name = func->cls
545 ? makeStaticString(folly::sformat("{}::{}", func->cls->name, func->name))
546 : func->name;
547 auto result = [&]() -> TypeOrReduced {
548 #define X(x, y) if (name->isame(s_##x.get())) return builtin_##x(env, func, fca);
549 SPECIAL_BUILTINS
550 return NoReduced{};
551 #undef X
552 }();
553 return match<bool>(
554 result,
555 [&] (NoReduced) { return false; },
556 [&] (Reduced) {
557 for (int i = 0; i < kNumActRecCells; ++i) reduce(env, bc::PopU2 {});
558 return true;
560 [&] (Type retType) {
561 for (int i = 0; i < fca.numArgs(); ++i) popT(env);
562 for (int i = 0; i < kNumActRecCells; ++i) popU(env);
563 push(env, std::move(retType));
564 return true;
569 //////////////////////////////////////////////////////////////////////
571 } // namespace
573 bool optimize_builtin(ISS& env, const php::Func* func, const FCallArgs& fca) {
574 if (any(env.collect.opts & CollectionOpts::Speculating) ||
575 func->attrs & AttrNoFCallBuiltin ||
576 (func->cls && !(func->attrs & AttrStatic)) ||
577 !func->isNative ||
578 func->params.size() >= Native::maxFCallBuiltinArgs() ||
579 fca.hasGenerics() ||
580 !RuntimeOption::EvalEnableCallBuiltin) {
581 return false;
584 // Do not allow for inout arguments, unpack and variadic arguments
585 if (func->hasInOutArgs ||
586 fca.enforceInOut() ||
587 fca.hasUnpack() ||
588 (func->params.size() && func->params.back().isVariadic)) {
589 return false;
592 // Argument not allowed to overrun the signature
593 if (fca.numArgs() > func->params.size()) return false;
595 // Check for missing non-optional arguments
596 for (int i = fca.numArgs(); i < func->params.size(); i++) {
597 auto const& pi = func->params[i];
598 assertx(!pi.isVariadic);
599 if (pi.defaultValue.m_type == KindOfUninit) return false;
602 return handle_builtin(env, func, fca);
605 bool handle_function_exists(ISS& env, const Type& name) {
606 if (!name.strictSubtypeOf(BStr)) return false;
607 auto const v = tv(name);
608 if (!v) return false;
609 auto const rfunc = env.index.resolve_func(env.ctx, v->m_data.pstr);
610 return rfunc.exactFunc();
613 Optional<Type> const_fold(ISS& env,
614 uint32_t nArgs,
615 uint32_t numExtraInputs,
616 const php::Func& phpFunc,
617 bool variadicsPacked) {
618 assertx(phpFunc.attrs & AttrIsFoldable);
620 std::vector<TypedValue> args(nArgs);
621 auto const firstArgPos = numExtraInputs + nArgs - 1;
622 for (auto i = uint32_t{0}; i < nArgs; ++i) {
623 auto const val = tv(topT(env, firstArgPos - i));
624 if (!val || val->m_type == KindOfUninit) return std::nullopt;
625 args[i] = *val;
628 Class* cls = nullptr;
629 auto const func = [&] () -> HPHP::Func* {
630 if (phpFunc.cls) {
631 cls = Class::lookup(phpFunc.cls->name);
632 if (!cls || !(cls->attrs() & AttrBuiltin)) return nullptr;
633 auto const f = cls->lookupMethod(phpFunc.name);
634 if (!f->isStatic()) return nullptr;
635 return f;
637 return Func::lookupBuiltin(phpFunc.name);
638 }();
640 if (!func) return std::nullopt;
642 // If the function is variadic and all the variadic parameters are already
643 // packed into an array as the last parameter, we need to unpack them, as
644 // invokeFuncFew expects them to be unpacked.
645 if (func->hasVariadicCaptureParam() && variadicsPacked) {
646 if (args.empty()) return std::nullopt;
647 if (!isVecType(args.back().m_type)) return std::nullopt;
648 auto const variadic = args.back();
649 args.pop_back();
650 IterateV(
651 variadic.m_data.parr,
652 [&](TypedValue v) { args.emplace_back(v); }
656 FTRACE(1, "invoking: {}\n", func->fullName()->data());
658 assertx(!RuntimeOption::EvalJit);
659 // NB: Coeffects are already checked prior to here by `shouldAttemptToFold`
660 return eval_cell(
661 [&] {
662 auto const retVal = g_context->invokeFuncFew(
663 func, cls, args.size(), args.data(), RuntimeCoeffects::none(),
664 false, false);
665 assertx(tvIsPlausible(retVal));
666 return retVal;
671 //////////////////////////////////////////////////////////////////////