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/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
{
38 //////////////////////////////////////////////////////////////////////
40 const Type
getArg(ISS
& env
, const php::Func
* func
, const FCallArgs
& fca
,
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 //////////////////////////////////////////////////////////////////////
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
;
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
:
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
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
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
)) {
143 auto const dt
= val
->m_data
.pstr
->toNumeric(ival
, dval
);
144 return dt
== KindOfInt64
|| dt
== KindOfDouble
? TTrue
: TFalse
;
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))) {
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
});
175 if (!autoload
.strictSubtypeOf(TBool
)) return NoReduced
{};
176 auto const v
= tv(autoload
);
178 if (fca
.numArgs() == 2) reduce(env
, bc::PopC
{});
182 bc::OODeclExists
{ subop
});
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
{});
211 auto retTy
= TBottom
;
212 if (ty
.couldBe(BNull
)) {
213 retTy
|= sval(staticEmptyString());
215 if (ty
.couldBe(BNum
| BBool
| BRes
)) {
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
);
231 if (ty
.couldBe(BLazyCls
)) {
232 if (is_specialized_lazycls(ty
)) {
233 auto cname
= lazyclsval_of(ty
);
234 retTy
|= sval(cname
);
239 if (ty
.couldBe(BStr
)) {
241 if (ty
.subtypeOf(BSStr
)) {
242 auto const v
= tv(ty
);
245 if (v
->m_data
.pstr
->isStrictlyInteger(i
)) {
256 if (!ty
.couldBe(BObj
| BArrLike
)) {
261 if (retTy
== TBottom
) unreachable(env
);
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
)) {
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
{});
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
)) {
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
:
305 case Type::ArrayCat::Mixed
:
306 case Type::ArrayCat::Struct
:
308 case Type::ArrayCat::None
:
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
)) {
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
)) {
342 if (t
.subtypeOf(BLazyCls
) && is_specialized_lazycls(t
)) {
343 if (RO::EvalRaiseClassConversionWarning
) throws
= TriBool::Maybe
;
344 return lazyclsval_of(t
);
348 if (!clsStr
) return std::nullopt
;
350 auto const typeAlias
= env
.index
.lookup_type_alias(clsStr
);
352 // No type-alias with that name
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
362 return std::make_pair(TBottom
, TriBool::Yes
);
365 return std::make_pair(
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.
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
;
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
;
398 return clsExact(*rcls
);
400 throws
= TriBool::Maybe
;
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
409 auto const fake
= [] {
410 auto array
= make_dict_array(
412 Variant(static_cast<uint8_t>(TypeStructure::Kind::T_class
)),
414 Variant(s_type_structure_non_existant_class
)
416 array
.setEvalScalar();
417 return dict_val(array
.get());
420 switch (lookup
.found
) {
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
;
427 if (lookup
.abstract
== TriBool::Maybe
) throws
= TriBool::Maybe
;
428 lookup
.resolution
.type
= fake();
432 if (!no_throw_on_undefined
|| lookup
.abstract
== TriBool::No
) {
433 throws
= TriBool::Maybe
;
435 if (lookup
.abstract
== TriBool::Maybe
) throws
= TriBool::Maybe
;
436 lookup
.resolution
.type
|= fake();
440 assertx(lookup
.abstract
== TriBool::No
);
444 if (lookup
.resolution
.mightFail
) {
445 if (no_throw_on_undefined
) {
446 lookup
.resolution
.type
|= fake();
447 } else if (throws
!= TriBool::Yes
) {
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
{};
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
{};
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
) {
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
)) {
510 if (!classname
.couldBe(BSStr
)) return NoReduced
{};
512 if (r
->second
== TriBool::No
&& present
) {
516 return intersection_of(classname
, TSStr
);
519 #define SPECIAL_BUILTINS \
523 X(get_class, get_class) \
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);
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
))
547 auto result
= [&]() -> TypeOrReduced
{
548 #define X(x, y) if (name->isame(s_##x.get())) return builtin_##x(env, func, fca);
555 [&] (NoReduced
) { return false; },
557 for (int i
= 0; i
< kNumActRecCells
; ++i
) reduce(env
, bc::PopU2
{});
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
));
569 //////////////////////////////////////////////////////////////////////
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
)) ||
578 func
->params
.size() >= Native::maxFCallBuiltinArgs() ||
580 !RuntimeOption::EvalEnableCallBuiltin
) {
584 // Do not allow for inout arguments, unpack and variadic arguments
585 if (func
->hasInOutArgs
||
586 fca
.enforceInOut() ||
588 (func
->params
.size() && func
->params
.back().isVariadic
)) {
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
,
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
;
628 Class
* cls
= nullptr;
629 auto const func
= [&] () -> HPHP::Func
* {
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;
637 return Func::lookupBuiltin(phpFunc
.name
);
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();
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`
662 auto const retVal
= g_context
->invokeFuncFew(
663 func
, cls
, args
.size(), args
.data(), RuntimeCoeffects::none(),
665 assertx(tvIsPlausible(retVal
));
671 //////////////////////////////////////////////////////////////////////