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/vm/jit/irgen-types.h"
18 #include "hphp/runtime/base/type-structure.h"
19 #include "hphp/runtime/base/type-structure-helpers.h"
20 #include "hphp/runtime/base/type-structure-helpers-defs.h"
22 #include "hphp/runtime/vm/repo-global-data.h"
23 #include "hphp/runtime/vm/runtime.h"
25 #include "hphp/runtime/vm/jit/is-type-struct-profile.h"
26 #include "hphp/runtime/vm/jit/guard-constraint.h"
27 #include "hphp/runtime/vm/jit/target-profile.h"
28 #include "hphp/runtime/vm/jit/type.h"
30 #include "hphp/runtime/vm/jit/ir-opcode.h"
31 #include "hphp/runtime/vm/jit/irgen-exit.h"
32 #include "hphp/runtime/vm/jit/irgen-interpone.h"
33 #include "hphp/runtime/vm/jit/irgen-builtin.h"
35 #include "hphp/runtime/vm/jit/irgen-internal.h"
37 namespace HPHP
{ namespace jit
{ namespace irgen
{
41 //////////////////////////////////////////////////////////////////////
44 s_Stringish("Stringish"),
45 s_Awaitable("HH\\Awaitable");
47 //////////////////////////////////////////////////////////////////////
50 * Returns a {Cls|Nullptr} suitable for use in instance checks. If knownCls is
51 * not null and is safe to use, that will be returned. Otherwise, className
52 * will be used to look up a class.
54 SSATmp
* ldClassSafe(IRGS
& env
, const StringData
* className
,
55 const Class
* knownCls
= nullptr) {
57 knownCls
= lookupUniqueClass(env
, className
);
61 return cns(env
, knownCls
);
67 return gen(env
, LdClsCachedSafe
, taken
, cns(env
, className
));
69 [&] (SSATmp
* cls
) { // next
73 hint(env
, Block::Hint::Unlikely
);
74 return cns(env
, nullptr);
79 SSATmp
* ldRecDescSafe(IRGS
& env
, const StringData
* recName
) {
83 return gen(env
, LdRecDescCachedSafe
, RecNameData
{recName
}, taken
);
85 [&] (SSATmp
* rec
) { // next
89 return cns(env
, nullptr);
95 * Returns a Bool value indicating if src (which must be <= TObj) is an
96 * instance of the class given in className, or nullptr if we don't have an
97 * efficient translation of the required check. checkCls must be the TCls for
98 * className (but it doesn't have to be constant).
100 SSATmp
* implInstanceCheck(IRGS
& env
, SSATmp
* src
, const StringData
* className
,
102 assertx(src
->isA(TObj
));
103 if (s_Awaitable
.get()->isame(className
)) {
104 return gen(env
, IsWaitHandle
, src
);
106 if (s_Stringish
.get()->isame(className
)) {
107 return gen(env
, HasToString
, src
);
110 auto knownCls
= checkCls
->hasConstVal(TCls
) ? checkCls
->clsVal() : nullptr;
111 assertx(IMPLIES(knownCls
, classIsUniqueOrCtxParent(env
, knownCls
)));
112 assertx(IMPLIES(knownCls
, knownCls
->name()->isame(className
)));
114 auto const srcType
= src
->type();
117 * If the value is a specialized object type and we don't have to constrain a
118 * guard to get it, we can avoid emitting runtime checks if we know the
119 * result is true. If we don't know, we still have to emit a runtime check
120 * because src might be a subtype of the specialized type.
122 if (srcType
< TObj
&& srcType
.clsSpec()) {
123 auto const cls
= srcType
.clsSpec().cls();
124 if (!env
.irb
->constrainValue(src
, GuardConstraint(cls
).setWeak()) &&
125 ((knownCls
&& cls
->classof(knownCls
)) ||
126 cls
->name()->isame(className
))) {
127 return cns(env
, true);
131 // Every case after this point requires knowing things about knownCls.
132 if (knownCls
== nullptr) return nullptr;
134 auto const ssaClassName
= cns(env
, className
);
135 auto const objClass
= gen(env
, LdObjClass
, src
);
137 if (env
.context
.kind
== TransKind::Profile
&& !InstanceBits::initted()) {
138 gen(env
, ProfileInstanceCheck
, cns(env
, className
));
139 } else if (env
.context
.kind
== TransKind::Optimize
||
140 InstanceBits::initted()) {
141 InstanceBits::init();
142 if (InstanceBits::lookup(className
) != 0) {
143 return gen(env
, InstanceOfBitmask
, objClass
, ssaClassName
);
147 // If the class is an interface, we can just hit the class's vtable or
148 // interface map and call it a day.
149 if (isInterface(knownCls
)) {
150 auto const slot
= knownCls
->preClass()->ifaceVtableSlot();
151 if (slot
!= kInvalidSlot
&& RuntimeOption::RepoAuthoritative
) {
153 InstanceOfIfaceVtable
,
154 InstanceOfIfaceVtableData
{knownCls
, true},
158 return gen(env
, InstanceOfIface
, objClass
, ssaClassName
);
161 // If knownCls isn't a normal class, our caller may want to do something
163 return isNormalClass(knownCls
) ?
164 gen(env
, ExtendsClass
, ExtendsClassData
{ knownCls
}, objClass
) : nullptr;
168 * Emit a type-check for the given type-constraint. Since the details can vary
169 * quite a bit depending on what the type-constraint represents, this function
170 * is heavily templatized.
172 * The lambda parameters are as follows:
174 * - GetVal: Return the SSATmp of the value to test
175 * - FuncToStr: Emit code to deal with any func to string conversions.
176 * - ClsMethToVec: Emit code to deal with any ClsMeth to array conversions
177 * - Fail: Emit code to deal with the type check failing.
178 * - Callable: Emit code to verify that the given value is callable.
179 * - VerifyCls: Emit code to verify that the given value is an instance of the
181 * - VerifyRecordDesc: Emit code to verify that the given value is an instance
182 * of the given record.
183 * - Giveup: Called when the type check cannot be resolved statically. Either
184 * PUNT or call a runtime helper to do the check.
186 * `propCls' should only be non-null for property type-hints, and represents the
187 * runtime class of the object the property belongs to.
189 template <typename GetVal
,
192 typename ClsMethToVec
,
196 typename VerifyRecordDesc
,
198 void verifyTypeImpl(IRGS
& env
,
199 const TypeConstraint
& tc
,
200 bool onlyCheckNullability
,
204 ClassToStr classToStr
,
205 ClsMethToVec clsMethToVec
,
209 VerifyRecordDesc verifyRecDesc
,
212 if (!tc
.isCheckable()) return;
213 assertx(!tc
.isUpperBound() || RuntimeOption::EvalEnforceGenericsUB
!= 0);
216 assertx(val
->type() <= TCell
);
218 auto const valType
= val
->type();
220 if (!valType
.isKnownDataType()) return giveup();
222 if (tc
.isNullable() && valType
<= TInitNull
) return;
224 auto const genFail
= [&] {
225 // If we know there are no mock classes for the current class, it is
226 // okay to fail hard. Otherwise, mock objects may still pass, and we
227 // have to be ready for execution to resume.
228 auto const thisFailsHard
= !tc
.couldSeeMockObject();
230 auto const failHard
= RuntimeOption::RepoAuthoritative
232 && (!tc
.isThis() || thisFailsHard
)
233 && (!tc
.isUpperBound() || RuntimeOption::EvalEnforceGenericsUB
>= 2);
234 return fail(valType
, failHard
);
238 annotCompat(valType
.toDataType(), tc
.type(), tc
.typeName());
240 case AnnotAction::Pass
: return;
241 case AnnotAction::Fail
: return genFail();
242 case AnnotAction::CallableCheck
:
243 return callable(val
);
244 case AnnotAction::ObjectCheck
:
247 case AnnotAction::WarnClass
:
248 assertx(valType
<= TCls
);
249 if (!classToStr(val
)) return genFail();
255 makeStaticString(Strings::CLASS_TO_STRING_IMPLICIT
)
260 case AnnotAction::ConvertClass
:
261 assertx(valType
<= TCls
);
262 if (!classToStr(val
)) return genFail();
264 case AnnotAction::ClsMethCheck
:
265 assertx(valType
<= TClsMeth
);
266 if (!clsMethToVec(val
)) return genFail();
268 case AnnotAction::RecordCheck
:
269 assertx(valType
<= TRecord
);
270 auto const rec
= Unit::lookupUniqueRecDesc(tc
.typeName());
271 auto const isPersistent
= recordHasPersistentRDS(rec
);
272 auto const checkRecDesc
= isPersistent
?
273 cns(env
, rec
) : ldRecDescSafe(env
, tc
.typeName());
274 verifyRecDesc(gen(env
, LdRecDesc
, val
), checkRecDesc
, val
);
277 assertx(result
== AnnotAction::ObjectCheck
);
278 if (onlyCheckNullability
) return;
280 if (!(valType
<= TObj
)) {
281 if (tc
.isResolved()) return genFail();
282 // For RepoAuthoritative mode, if tc is a type alias we can optimize in
284 if (tc
.isObject() && RuntimeOption::RepoAuthoritative
) {
285 auto const td
= tc
.namedEntity()->getCachedTypeAlias();
286 if (tc
.namedEntity()->isPersistentTypeAlias() && td
&&
287 ((td
->nullable
&& valType
<= TNull
) ||
288 annotCompat(valType
.toDataType(), td
->type
,
291 (td
->rec
? td
->rec
->name() : nullptr)) == AnnotAction::Pass
)) {
292 env
.irb
->constrainValue(val
, DataTypeSpecific
);
295 auto const cachedClass
= tc
.namedEntity()->getCachedClass();
296 if (cachedClass
&& classHasPersistentRDS(cachedClass
) &&
297 cachedClass
->enumBaseTy() &&
298 annotCompat(valType
.toDataType(),
299 enumDataTypeToAnnotType(*cachedClass
->enumBaseTy()),
300 nullptr) == AnnotAction::Pass
) {
301 env
.irb
->constrainValue(val
, DataTypeSpecific
);
308 // At this point we know valType is Obj.
310 // For this type checks, the class needs to be an exact match.
311 auto const ctxCls
= propCls
? propCls
: ldCtxCls(env
);
312 auto const objClass
= gen(env
, LdObjClass
, val
);
316 gen(env
, JmpZero
, taken
, gen(env
, EqCls
, ctxCls
, objClass
));
319 hint(env
, Block::Hint::Unlikely
);
326 // If we reach here then valType is Obj and tc is Object, Self, or Parent
327 const StringData
* clsName
;
328 const Class
* knownConstraint
= nullptr;
330 auto const td
= tc
.namedEntity()->getCachedTypeAlias();
331 if (RuntimeOption::RepoAuthoritative
&& td
&&
332 tc
.namedEntity()->isPersistentTypeAlias() &&
334 assertx(classHasPersistentRDS(td
->klass
));
335 clsName
= td
->klass
->name();
336 knownConstraint
= td
->klass
;
338 clsName
= tc
.typeName();
343 knownConstraint
= curFunc(env
)->cls();
345 assertx(tc
.isParent());
346 if (auto cls
= curFunc(env
)->cls()) knownConstraint
= cls
->parent();
348 if (!knownConstraint
) {
349 // The hint was self or parent and there's no corresponding
350 // class for the current func. This typehint will always fail.
353 clsName
= knownConstraint
->preClass()->name();
356 // For "self" and "parent", knownConstraint should always be
357 // non-null at this point
358 assertx(IMPLIES(tc
.isSelf() || tc
.isParent(), knownConstraint
!= nullptr));
359 assertx(IMPLIES(tc
.isSelf() || tc
.isParent(), clsName
!= nullptr));
360 assertx(IMPLIES(tc
.isSelf() || tc
.isParent(), !propCls
));
362 auto const checkCls
= ldClassSafe(env
, clsName
, knownConstraint
);
363 auto const fastIsInstance
= implInstanceCheck(env
, val
, clsName
, checkCls
);
364 if (fastIsInstance
) {
368 gen(env
, JmpZero
, taken
, fastIsInstance
);
370 [&] { // taken: the param type does not match
371 hint(env
, Block::Hint::Unlikely
);
378 verifyCls(val
, gen(env
, LdObjClass
, val
), checkCls
);
381 Type
typeOpToType(IsTypeOp op
) {
383 case IsTypeOp::Null
: return TInitNull
;
384 case IsTypeOp::Int
: return TInt
;
385 case IsTypeOp::Dbl
: return TDbl
;
386 case IsTypeOp::Bool
: return TBool
;
387 case IsTypeOp::Str
: return TStr
;
388 case IsTypeOp::Keyset
: return TKeyset
;
389 case IsTypeOp::Obj
: return TObj
;
390 case IsTypeOp::Res
: return TRes
;
391 case IsTypeOp::ClsMeth
: return TClsMeth
;
392 case IsTypeOp::Func
: return TFunc
;
393 case IsTypeOp::Class
: return TCls
;
396 case IsTypeOp::VArray
:
397 case IsTypeOp::DArray
:
398 case IsTypeOp::ArrLike
:
400 case IsTypeOp::PHPArr
:
401 case IsTypeOp::Scalar
: not_reached();
406 SSATmp
* isScalarImpl(IRGS
& env
, SSATmp
* val
) {
407 // The simplifier works fine when val has a known DataType, but do some
408 // checks first in case val has a type like {Int|Str}.
409 auto const scalar
= TBool
| TInt
| TDbl
| TStr
;
410 if (val
->isA(scalar
)) return cns(env
, true);
411 if (!val
->type().maybe(scalar
)) return cns(env
, false);
413 SSATmp
* result
= nullptr;
414 for (auto t
: {TBool
, TInt
, TDbl
, TStr
}) {
415 auto const is_t
= gen(env
, ConvBoolToInt
, gen(env
, IsType
, t
, val
));
416 result
= result
? gen(env
, OrInt
, result
, is_t
) : is_t
;
418 return gen(env
, ConvIntToBool
, result
);
421 const StaticString
s_FUNC_CONVERSION(Strings::FUNC_TO_STRING
);
422 const StaticString
s_FUNC_IS_STRING("Func used in is_string");
423 const StaticString
s_CLASS_CONVERSION(Strings::CLASS_TO_STRING
);
424 const StaticString
s_CLASS_IS_STRING("Class used in is_string");
426 SSATmp
* isStrImpl(IRGS
& env
, SSATmp
* src
) {
429 mc
.ifTypeThen(src
, TStr
, [&](SSATmp
*) { return cns(env
, true); });
431 mc
.ifTypeThen(src
, TCls
, [&](SSATmp
*) {
432 if (RuntimeOption::EvalClassIsStringNotices
) {
433 gen(env
, RaiseNotice
, cns(env
, s_CLASS_IS_STRING
.get()));
435 return cns(env
, true);
438 return mc
.elseDo([&]{ return cns(env
, false); });
441 // Checks if the `arr` has provenance, implementing the same logic as in the
442 // runtume helper arrprov::arrayWantsTag. If so, logs a serialization notice.
443 void maybeLogSerialization(IRGS
& env
, SSATmp
* arr
, SerializationSite site
) {
444 assertx(arr
->isA(TArr
));
445 if (!RO::EvalArrayProvenance
) return;
447 ifThen(env
, [&](Block
* taken
) {
448 arr
= gen(env
, CheckType
, TVArr
|TDArr
, taken
, arr
);
449 gen(env
, RaiseArraySerializeNotice
, cns(env
, site
), arr
);
453 SSATmp
* isArrayImpl(IRGS
& env
, SSATmp
* src
, bool log_on_hack_arrays
) {
456 mc
.ifTypeThen(src
, TArr
, [&](SSATmp
* src
) {
457 maybeLogSerialization(env
, src
, SerializationSite::IsArray
);
458 return cns(env
, true);
461 if (!RO::EvalHackArrDVArrs
&& RO::EvalIsCompatibleClsMethType
) {
462 mc
.ifTypeThen(src
, TClsMeth
, [&](SSATmp
*) {
463 if (RO::EvalIsVecNotices
) {
464 auto const msg
= makeStaticString(Strings::CLSMETH_COMPAT_IS_ARR
);
465 gen(env
, RaiseNotice
, cns(env
, msg
));
467 return cns(env
, true);
471 auto const hacLogging
= [&](const char* msg
) {
472 if (!RO::EvalHackArrCompatIsArrayNotices
) return;
473 gen(env
, RaiseHackArrCompatNotice
, cns(env
, makeStaticString(msg
)));
476 if (!curFunc(env
)->isBuiltin() &&
477 RO::EvalHackArrCompatIsArrayNotices
&&
478 log_on_hack_arrays
) {
479 mc
.ifTypeThen(src
, TVec
, [&](SSATmp
* src
) {
480 hacLogging(Strings::HACKARR_COMPAT_VEC_IS_ARR
);
481 return cns(env
, false);
483 mc
.ifTypeThen(src
, TDict
, [&](SSATmp
* src
) {
484 hacLogging(Strings::HACKARR_COMPAT_DICT_IS_ARR
);
485 return cns(env
, false);
487 mc
.ifTypeThen(src
, TKeyset
, [&](SSATmp
* src
) {
488 hacLogging(Strings::HACKARR_COMPAT_KEYSET_IS_ARR
);
489 return cns(env
, false);
493 return mc
.elseDo([&]{ return cns(env
, false); });
496 SSATmp
* isVecImpl(IRGS
& env
, SSATmp
* src
) {
499 mc
.ifTypeThen(src
, TVec
, [&](SSATmp
* src
) {
500 return cns(env
, true);
503 auto const hacLogging
= [&](const char* msg
) {
504 if (!RO::EvalHackArrCompatIsVecDictNotices
) return;
505 gen(env
, RaiseHackArrCompatNotice
, cns(env
, makeStaticString(msg
)));
508 if (RO::EvalIsCompatibleClsMethType
) {
509 if (RO::EvalHackArrDVArrs
) {
510 mc
.ifTypeThen(src
, TClsMeth
, [&](SSATmp
* src
) {
511 if (RO::EvalIsVecNotices
) {
512 gen(env
, RaiseNotice
,
513 cns(env
, makeStaticString(Strings::CLSMETH_COMPAT_IS_VEC
)));
515 return cns(env
, true);
518 mc
.ifTypeThen(src
, TClsMeth
, [&](SSATmp
* src
) {
519 hacLogging(Strings::HACKARR_COMPAT_VARR_IS_VEC
);
520 return cns(env
, false);
525 if (RO::EvalHackArrCompatIsVecDictNotices
|| RO::EvalArrayProvenance
) {
526 mc
.ifTypeThen(src
, TVArr
, [&](SSATmp
* src
) {
527 hacLogging(Strings::HACKARR_COMPAT_VARR_IS_VEC
);
528 maybeLogSerialization(env
, src
, SerializationSite::IsVec
);
529 return cns(env
, false);
533 return mc
.elseDo([&]{ return cns(env
, false); });
536 SSATmp
* isDictImpl(IRGS
& env
, SSATmp
* src
) {
539 mc
.ifTypeThen(src
, TDict
, [&](SSATmp
* src
) {
540 return cns(env
, true);
543 auto const hacLogging
= [&](const char* msg
) {
544 if (!RO::EvalHackArrCompatIsVecDictNotices
) return;
545 gen(env
, RaiseHackArrCompatNotice
, cns(env
, makeStaticString(msg
)));
548 if (RO::EvalHackArrCompatIsVecDictNotices
|| RO::EvalArrayProvenance
) {
549 mc
.ifTypeThen(src
, TDArr
, [&](SSATmp
* src
) {
550 hacLogging(Strings::HACKARR_COMPAT_DARR_IS_DICT
);
551 maybeLogSerialization(env
, src
, SerializationSite::IsDict
);
552 return cns(env
, false);
556 return mc
.elseDo([&]{ return cns(env
, false); });
559 SSATmp
* isDVArrayImpl(IRGS
& env
, SSATmp
* src
, IsTypeOp subop
) {
562 assertx(subop
== IsTypeOp::VArray
|| subop
== IsTypeOp::DArray
);
563 auto const varray
= subop
== IsTypeOp::VArray
;
565 mc
.ifTypeThen(src
, varray
? TVArr
: TDArr
, [&](SSATmp
* src
) {
566 return cns(env
, true);
569 if (varray
&& RO::EvalIsCompatibleClsMethType
) {
570 mc
.ifTypeThen(src
, TClsMeth
, [&](SSATmp
*) {
571 if (RO::EvalIsVecNotices
) {
572 auto const msg
= makeStaticString(Strings::CLSMETH_COMPAT_IS_VARR
);
573 gen(env
, RaiseNotice
, cns(env
, msg
));
575 return cns(env
, true);
579 return mc
.elseDo([&]{ return cns(env
, false); });
582 SSATmp
* isArrLikeImpl(IRGS
& env
, SSATmp
* src
, bool log_on_hack_arrays
) {
585 // We eventually want ClsMeth to be its own DataType, so we must log.
586 if (RO::EvalIsCompatibleClsMethType
) {
587 mc
.ifTypeThen(src
, TClsMeth
, [&](SSATmp
* src
) {
588 if (RO::EvalIsVecNotices
) {
589 auto const msg
= makeStaticString(Strings::CLSMETH_COMPAT_IS_ANY_ARR
);
590 gen(env
, RaiseNotice
, cns(env
, msg
));
592 return cns(env
, true);
596 if (log_on_hack_arrays
&& RO::EvalWidenIsArrayLogs
) {
597 auto const widenIsArrayLogging
= [&](Type t
, const char* msg
) {
598 mc
.ifTypeThen(src
, t
, [&](SSATmp
* src
) {
599 gen(env
, RaiseHackArrCompatNotice
, cns(env
, makeStaticString(msg
)));
600 return cns(env
, true);
603 widenIsArrayLogging(TVec
, Strings::HACKARR_COMPAT_VEC_IS_ARR
);
604 widenIsArrayLogging(TDict
, Strings::HACKARR_COMPAT_DICT_IS_ARR
);
605 widenIsArrayLogging(TKeyset
, Strings::HACKARR_COMPAT_KEYSET_IS_ARR
);
608 return mc
.elseDo([&]{ return gen(env
, IsType
, TArrLike
, src
); });
611 //////////////////////////////////////////////////////////////////////
615 SSATmp
* implInstanceOfD(IRGS
& env
, SSATmp
* src
, const StringData
* className
) {
617 * InstanceOfD is always false if it's not an object.
619 * We're prepared to generate translations for known non-object types, but if
620 * it's Gen/Cell we're going to PUNT because it's natural to translate that
621 * case with control flow TODO(#16781576)
623 if (TObj
< src
->type()) {
624 PUNT(InstanceOfD_MaybeObj
);
626 if (!src
->isA(TObj
)) {
627 if (src
->isA(TCls
)) {
628 if (!interface_supports_string(className
)) return cns(env
, false);
629 if (RuntimeOption::EvalClassIsStringNotices
&& src
->isA(TCls
)) {
633 cns(env
, s_CLASS_IS_STRING
.get())
636 return cns(env
, true);
639 if (src
->isA(TClsMeth
)) {
640 if (!interface_supports_arrlike(className
)) {
641 return cns(env
, false);
644 if (RO::EvalIsVecNotices
) {
650 makeStaticString(folly::sformat(
651 "Implicit clsmeth to {} conversion", className
->data()
657 return cns(env
, true);
661 (src
->isA(TArrLike
) && interface_supports_arrlike(className
)) ||
662 (src
->isA(TStr
) && interface_supports_string(className
)) ||
663 (src
->isA(TInt
) && interface_supports_int(className
)) ||
664 (src
->isA(TDbl
) && interface_supports_double(className
));
665 return cns(env
, res
);
668 auto const checkCls
= ldClassSafe(env
, className
);
669 if (auto isInstance
= implInstanceCheck(env
, src
, className
, checkCls
)) {
673 return gen(env
, InstanceOf
, gen(env
, LdObjClass
, src
), checkCls
);
676 //////////////////////////////////////////////////////////////////////
678 void emitInstanceOfD(IRGS
& env
, const StringData
* className
) {
679 auto const src
= popC(env
);
680 push(env
, implInstanceOfD(env
, src
, className
));
684 void emitInstanceOf(IRGS
& env
) {
685 auto const t1
= popC(env
);
686 auto const t2
= popC(env
); // t2 instanceof t1
688 if (t1
->isA(TObj
) && t2
->isA(TObj
)) {
689 auto const c2
= gen(env
, LdObjClass
, t2
);
690 auto const c1
= gen(env
, LdObjClass
, t1
);
691 push(env
, gen(env
, InstanceOf
, c2
, c1
));
697 if (!t1
->isA(TStr
)) PUNT(InstanceOf
-NotStr
);
700 auto const c1
= gen(env
, LookupClsRDS
, t1
);
701 auto const c2
= gen(env
, LdObjClass
, t2
);
702 push(env
, gen(env
, InstanceOf
, c2
, c1
));
708 auto const res
= [&]() -> SSATmp
* {
709 if (t2
->isA(TArr
)) return gen(env
, InterfaceSupportsArr
, t1
);
710 if (t2
->isA(TVec
)) return gen(env
, InterfaceSupportsVec
, t1
);
711 if (t2
->isA(TDict
)) return gen(env
, InterfaceSupportsDict
, t1
);
712 if (t2
->isA(TKeyset
)) return gen(env
, InterfaceSupportsKeyset
, t1
);
713 if (t2
->isA(TInt
)) return gen(env
, InterfaceSupportsInt
, t1
);
714 if (t2
->isA(TStr
)) return gen(env
, InterfaceSupportsStr
, t1
);
715 if (t2
->isA(TDbl
)) return gen(env
, InterfaceSupportsDbl
, t1
);
717 if (!RO::EvalRaiseClassConversionWarning
) {
718 return gen(env
, InterfaceSupportsStr
, t1
);
723 gen(env
, JmpZero
, taken
, gen(env
, InterfaceSupportsStr
, t1
));
726 gen(env
, RaiseNotice
, cns(env
, s_CLASS_CONVERSION
.get()));
727 return cns(env
, true);
729 [&] { return cns(env
, false); }
732 if (!t2
->type().maybe(TObj
|TArr
|TVec
|TDict
|TKeyset
|
733 TInt
|TStr
|TDbl
)) return cns(env
, false);
737 if (!res
) PUNT(InstanceOf
-Unknown
);
744 void emitIsLateBoundCls(IRGS
& env
) {
745 auto const cls
= curClass(env
);
746 if (!cls
) PUNT(IsLateBoundCls
-NoClassContext
);
747 if (isTrait(cls
)) PUNT(IsLateBoundCls
-Trait
);
748 auto const obj
= popC(env
);
749 if (obj
->isA(TObj
)) {
750 auto const rhs
= ldCtxCls(env
);
751 auto const lhs
= gen(env
, LdObjClass
, obj
);
752 push(env
, gen(env
, InstanceOf
, lhs
, rhs
));
753 } else if (!obj
->type().maybe(TObj
)) {
754 push(env
, cns(env
, false));
756 PUNT(IsLateBoundCls
-MaybeObject
);
764 SSATmp
* resolveTypeStructureAndCacheInRDS(
767 bool typeStructureCouldBeNonStatic
769 if (typeStructureCouldBeNonStatic
) return resolveTypeStruct();
770 auto const handle
= RDSHandleData
{ rds::alloc
<ArrayData
*>().handle() };
771 auto const type
= RO::EvalHackArrDVArrs
? TPtrToOtherDict
: TPtrToOtherDArr
;
772 auto const addr
= gen(env
, LdRDSAddr
, handle
, type
);
776 gen(env
, CheckRDSInitialized
, taken
, handle
);
779 hint(env
, Block::Hint::Unlikely
);
780 gen(env
, StMem
, addr
, resolveTypeStruct());
781 gen(env
, MarkRDSInitialized
, handle
);
784 return gen(env
, LdMem
, RO::EvalHackArrDVArrs
? TDict
: TDArr
, addr
);
787 SSATmp
* resolveTypeStructImpl(
789 bool typeStructureCouldBeNonStatic
,
794 auto const declaringCls
= curFunc(env
) ? curClass(env
) : nullptr;
795 auto const calledCls
=
796 declaringCls
&& typeStructureCouldBeNonStatic
799 auto const result
= resolveTypeStructureAndCacheInRDS(
805 ResolveTypeStructData
{
808 spOffBCFromIRSP(env
),
809 static_cast<uint32_t>(n
),
816 typeStructureCouldBeNonStatic
823 const ArrayData
* staticallyResolveTypeStructure(
829 auto const declaringCls
= curFunc(env
) ? curClass(env
) : nullptr;
830 bool persistent
= false;
831 // This shouldn't do a difference, but does on GCC 8.3 on Ubuntu 19.04;
832 // if we take the catch then return `ts`, it's a bogus value and we
833 // segfault... sometimes...
834 const ArrayData
* ts_copy
= ts
;
836 auto newTS
= TypeStructure::resolvePartial(
837 ArrNR(ts
), nullptr, declaringCls
, persistent
, partial
, invalidType
);
838 if (persistent
) return ArrayData::GetScalarArray(std::move(newTS
));
839 } catch (Exception
& e
) {}
840 // We are here because either we threw in the resolution or it wasn't
841 // persistent resolution which means we didn't really resolve it
846 SSATmp
* check_nullable(IRGS
& env
, SSATmp
* res
, SSATmp
* var
) {
849 [&] (Block
* taken
) { gen(env
, JmpNZero
, taken
, res
); },
850 [&] { return gen(env
, IsType
, TNull
, var
); },
851 [&] { return cns(env
, true); }
855 void chain_is_type(IRGS
& env
, SSATmp
* c
, bool nullable
, Type ty
) {
856 always_assert(false);
859 template<typename
... Types
>
860 void chain_is_type(IRGS
& env
, SSATmp
* c
, bool nullable
,
861 Type ty1
, Type ty2
, Types
&&... rest
) {
865 auto const res
= gen(env
, IsType
, ty1
, c
);
866 gen(env
, JmpNZero
, taken
, res
);
869 if (sizeof...(rest
) == 0) {
870 auto const res
= gen(env
, IsType
, ty2
, c
);
871 push(env
, nullable
? check_nullable(env
, res
, c
) : res
);
873 chain_is_type(env
, c
, nullable
, ty2
, rest
...);
877 push(env
, cns(env
, true));
883 * This function tries to emit is type struct operations without resolving
884 * the type structure when that's possible.
885 * When it returns true, it has popped two values from the stack, namely the
886 * type structure and the cell, and pushed one value back to stack, namely
887 * true/false if it is an is-operation or the cell if it is an as operation.
888 * This function does not modify the reference counts of these stack values,
889 * leaving that responsibility to the caller.
890 * When it returns false, it does not modify anything.
892 bool emitIsTypeStructWithoutResolvingIfPossible(
896 // Top of the stack is the type structure, so the thing we are checking is
898 auto const t
= topC(env
, BCSPRelOffset
{ 1 });
899 auto const is_nullable_ts
= is_ts_nullable(ts
);
901 auto const cnsResult
= [&] (bool value
) {
902 popC(env
); // pop the ts that's on the stack
903 popC(env
); // pop the cell
904 push(env
, cns(env
, value
));
908 auto const success
= [&] { return cnsResult(true); };
909 auto const fail
= [&] { return cnsResult(false); };
911 auto const primitive
= [&] (Type ty
, bool should_negate
= false) {
912 auto const nty
= is_nullable_ts
? ty
|TNull
: ty
;
913 if (t
->isA(nty
)) return should_negate
? fail() : success();
914 if (!t
->type().maybe(nty
)) return should_negate
? success() : fail();
915 popC(env
); // pop the ts that's on the stack
916 auto const c
= popC(env
);
917 auto const res
= gen(env
, should_negate
? IsNType
: IsType
, ty
, c
);
918 push(env
, is_nullable_ts
? check_nullable(env
, res
, c
) : res
);
922 // We explicitly bind is_nullable_ts because failing to do so causes a
923 // spurious compiler error on some g++ versions.
924 auto const unionOf
= [&,is_nullable_ts
] (Type ty1
, Type ty2
,
926 auto const ty
= Type::unionAll(ty1
, ty2
, rest
...) |
927 (is_nullable_ts
? TNull
: TBottom
);
928 if (t
->isA(ty
)) return success();
929 if (!t
->type().maybe(ty
)) return fail();
931 popC(env
); // pop the ts that's on the stack
932 auto const c
= popC(env
);
933 chain_is_type(env
, c
, is_nullable_ts
, ty1
, ty2
, rest
...);
937 if (t
->isA(TNull
) && is_nullable_ts
) return success();
939 auto kind
= get_ts_kind(ts
);
941 case TypeStructure::Kind::T_int
: return primitive(TInt
);
942 case TypeStructure::Kind::T_bool
: return primitive(TBool
);
943 case TypeStructure::Kind::T_float
: return primitive(TDbl
);
944 case TypeStructure::Kind::T_string
: {
945 if (t
->type().maybe(TCls
) &&
946 RuntimeOption::EvalRaiseClassConversionWarning
) {
949 gen(env
, CheckType
, TCls
, taken
, t
);
952 gen(env
, RaiseWarning
, cns(env
, s_CLASS_IS_STRING
.get()));
956 return unionOf(TStr
, TCls
);
958 case TypeStructure::Kind::T_null
: return primitive(TNull
);
959 case TypeStructure::Kind::T_void
: return primitive(TNull
);
960 case TypeStructure::Kind::T_keyset
: return primitive(TKeyset
);
961 case TypeStructure::Kind::T_nonnull
: return primitive(TNull
, true);
962 case TypeStructure::Kind::T_mixed
:
963 case TypeStructure::Kind::T_dynamic
:
965 case TypeStructure::Kind::T_num
: return unionOf(TInt
, TDbl
);
966 case TypeStructure::Kind::T_arraykey
: return unionOf(TInt
, TStr
);
967 case TypeStructure::Kind::T_arraylike
:
968 if (t
->type().maybe(TClsMeth
)) {
969 if (t
->isA(TClsMeth
)) {
970 if (RuntimeOption::EvalIsVecNotices
) {
971 gen(env
, RaiseNotice
,
972 cns(env
, makeStaticString(Strings::CLSMETH_COMPAT_IS_ANY_ARR
)));
976 PUNT(TypeStructC
-MaybeClsMeth
);
979 return unionOf(TArr
, TVec
, TDict
, TKeyset
);
980 case TypeStructure::Kind::T_vec_or_dict
:
981 if (t
->type().maybe(TClsMeth
)) {
982 if (t
->isA(TClsMeth
)) {
983 if (RuntimeOption::EvalHackArrDVArrs
) {
984 if (RuntimeOption::EvalIsVecNotices
) {
985 gen(env
, RaiseNotice
,
986 cns(env
, makeStaticString(Strings::CLSMETH_COMPAT_IS_VEC
)));
993 PUNT(TypeStructC
-MaybeClsMeth
);
997 case TypeStructure::Kind::T_dict
:
998 case TypeStructure::Kind::T_vec
: {
999 popC(env
); // pop the ts that's on the stack
1000 auto const c
= popC(env
);
1001 auto const res
= [&]{
1002 if (kind
== TypeStructure::Kind::T_dict
) {
1003 return isDictImpl(env
, c
);
1004 } else if (kind
== TypeStructure::Kind::T_vec
) {
1005 return isVecImpl(env
, c
);
1006 } else if (kind
== TypeStructure::Kind::T_vec_or_dict
) {
1010 auto vec
= isVecImpl(env
, c
);
1011 gen(env
, JmpZero
, taken
, vec
);
1014 return cns(env
, true);
1017 return isDictImpl(env
, c
);
1024 push(env
, is_nullable_ts
? check_nullable(env
, res
, c
) : res
);
1027 case TypeStructure::Kind::T_class
:
1028 case TypeStructure::Kind::T_interface
:
1029 case TypeStructure::Kind::T_xhp
: {
1030 auto const clsname
= get_ts_classname(ts
);
1031 auto cls
= lookupUniqueClass(env
, clsname
);
1032 if (ts
->exists(s_generic_types
) &&
1033 ((classIsPersistentOrCtxParent(env
, cls
) &&
1034 cls
->hasReifiedGenerics()) ||
1035 !isTSAllWildcards(ts
))) {
1036 // If it is a reified class or has non wildcard generics,
1040 popC(env
); // pop the ts that's on the stack
1041 auto const c
= popC(env
);
1042 auto const res
= implInstanceOfD(env
, c
, clsname
);
1043 push(env
, is_nullable_ts
? check_nullable(env
, res
, c
) : res
);
1046 case TypeStructure::Kind::T_nothing
:
1047 case TypeStructure::Kind::T_noreturn
:
1049 case TypeStructure::Kind::T_typevar
:
1050 case TypeStructure::Kind::T_fun
:
1051 case TypeStructure::Kind::T_trait
:
1052 case TypeStructure::Kind::T_array
:
1053 case TypeStructure::Kind::T_darray
:
1054 case TypeStructure::Kind::T_varray
:
1055 case TypeStructure::Kind::T_varray_or_darray
:
1056 // Not supported, will throw an error on these at the resolution phase
1058 case TypeStructure::Kind::T_enum
:
1059 case TypeStructure::Kind::T_tuple
:
1060 case TypeStructure::Kind::T_shape
:
1061 case TypeStructure::Kind::T_typeaccess
:
1062 case TypeStructure::Kind::T_unresolved
:
1063 case TypeStructure::Kind::T_resource
:
1064 case TypeStructure::Kind::T_reifiedtype
:
1065 // TODO(T28423611): Implement these
1072 * shouldDefRef is set iff the resulting SSATmp is a newly allocated type
1074 * This function does not modify the reference count of its inputs, leaving that
1077 SSATmp
* handleIsResolutionAndCommonOpts(
1079 TypeStructResolveOp op
,
1084 auto const a
= topC(env
);
1085 auto const required_ts_type
= RO::EvalHackArrDVArrs
? TDict
: TDArr
;
1086 if (!a
->isA(required_ts_type
)) PUNT(IsTypeStructC
-NotArrayTypeStruct
);
1087 if (!a
->hasConstVal(required_ts_type
)) {
1088 if (op
== TypeStructResolveOp::Resolve
) {
1089 return resolveTypeStructImpl(env
, true, true, 1, true);
1091 shouldDecRef
= false;
1095 auto const ts
= a
->arrLikeVal();
1096 auto maybe_resolved
= ts
;
1097 bool partial
= true;
1098 bool invalidType
= true;
1099 if (op
== TypeStructResolveOp::Resolve
) {
1101 staticallyResolveTypeStructure(env
, ts
, partial
, invalidType
);
1102 shouldDecRef
= maybe_resolved
!= ts
;
1104 if (emitIsTypeStructWithoutResolvingIfPossible(env
, maybe_resolved
)) {
1108 if (op
== TypeStructResolveOp::Resolve
&& (partial
|| invalidType
)) {
1109 shouldDecRef
= true;
1110 return resolveTypeStructImpl(
1111 env
, typeStructureCouldBeNonStatic(ts
), true, 1, true);
1114 if (op
== TypeStructResolveOp::DontResolve
) checkValid
= true;
1115 return cns(env
, maybe_resolved
);
1120 void emitIsTypeStructC(IRGS
& env
, TypeStructResolveOp op
) {
1121 auto const a
= topC(env
);
1122 auto const c
= topC(env
, BCSPRelOffset
{ 1 });
1123 bool done
= false, shouldDecRef
= true, checkValid
= false;
1125 handleIsResolutionAndCommonOpts(env
, op
, done
, shouldDecRef
, checkValid
);
1132 auto block
= opcodeMayRaise(IsTypeStruct
) && shouldDecRef
1133 ? create_catch_block(env
, [&]{ decRef(env
, tc
); })
1135 auto const data
= RDSHandleData
{ rds::bindTSCache(curFunc(env
)).handle() };
1137 static const StaticString s_IsTypeStruct
{"IsTypeStruct"};
1138 auto const profile
= TargetProfile
<IsTypeStructProfile
> {
1140 env
.irb
->curMarker(),
1141 s_IsTypeStruct
.get()
1144 auto const generic
= [&] {
1145 if (checkValid
) gen(env
, RaiseErrorOnInvalidIsAsExpressionType
, tc
);
1146 return gen(env
, IsTypeStruct
, block
, data
, tc
, c
);
1149 auto const finish
= [&] (SSATmp
* result
) {
1155 if (profile
.profiling()) {
1156 gen(env
, ProfileIsTypeStruct
, RDSHandleData
{ profile
.handle() }, a
);
1161 if (!profile
.optimizing() || !profile
.data().shouldOptimize()) {
1168 [&] (Block
* taken
) {
1169 return gen(env
, IsTypeStructCached
, taken
, a
, c
);
1171 [&] (SSATmp
* result
) { // next
1175 hint(env
, Block::Hint::Unlikely
);
1181 void emitThrowAsTypeStructException(IRGS
& env
) {
1182 auto const arr
= topC(env
);
1183 auto const c
= topC(env
, BCSPRelOffset
{ 1 });
1184 auto const tsAndBlock
= [&]() -> std::pair
<SSATmp
*, Block
*> {
1185 if (arr
->hasConstVal(RO::EvalHackArrDVArrs
? TDict
: TDArr
)) {
1186 auto const ts
= arr
->arrLikeVal();
1187 auto maybe_resolved
= ts
;
1188 bool partial
= true, invalidType
= true;
1190 staticallyResolveTypeStructure(env
, ts
, partial
, invalidType
);
1191 if (!ts
->same(maybe_resolved
)) {
1192 auto const inputTS
= cns(env
, maybe_resolved
);
1193 return {inputTS
, create_catch_block(env
, [&]{ decRef(env
, inputTS
); })};
1196 auto const ts
= resolveTypeStructImpl(env
, true, false, 1, true);
1197 return {ts
, nullptr};
1199 // No need to decref inputs as this instruction will throw
1200 gen(env
, ThrowAsTypeStructException
, tsAndBlock
.second
, tsAndBlock
.first
, c
);
1203 void emitRecordReifiedGeneric(IRGS
& env
) {
1204 auto const ts
= popC(env
);
1205 if (!ts
->isA(RO::EvalHackArrDVArrs
? TVec
: TVArr
)) {
1206 PUNT(RecordReifiedGeneric
-InvalidTS
);
1208 // RecordReifiedGenericsAndGetTSList decrefs the ts
1209 auto const result
= gen(env
, RecordReifiedGenericsAndGetTSList
, ts
);
1213 void emitCombineAndResolveTypeStruct(IRGS
& env
, uint32_t n
) {
1214 push(env
, resolveTypeStructImpl(env
, true, false, n
, false));
1217 void raiseClsmethCompatTypeHint(
1218 IRGS
& env
, int32_t id
, const Func
* func
, const TypeConstraint
& tc
) {
1219 auto name
= tc
.displayName(func
->cls());
1220 if (id
== TypeConstraint::ReturnId
) {
1221 gen(env
, RaiseNotice
, cns(env
, makeStaticString(
1222 folly::sformat("class_meth Compat: Value returned from function {}() "
1223 "must be of type {}, clsmeth given",
1224 func
->fullName(), name
))));
1226 gen(env
, RaiseNotice
, cns(env
, makeStaticString(
1227 folly::sformat("class_meth Compat: Argument {} passed to {}() "
1228 "must be of type {}, clsmeth given",
1229 id
+ 1, func
->fullName(), name
))));
1235 void verifyRetTypeImpl(IRGS
& env
, int32_t id
, int32_t ind
,
1236 bool onlyCheckNullability
) {
1237 auto const func
= curFunc(env
);
1238 auto const verifyFunc
= [&] (const TypeConstraint
& tc
) {
1242 onlyCheckNullability
,
1244 [&] { // Get value to test
1245 return topC(env
, BCSPRelOffset
{ ind
});
1247 [&] (SSATmp
* val
) { // func to string conversions
1248 auto const str
= gen(env
, LdFuncName
, val
);
1249 auto const offset
= offsetFromIRSP(env
, BCSPRelOffset
{ ind
});
1250 gen(env
, StStk
, IRSPRelOffsetData
{offset
}, sp(env
), str
);
1251 env
.irb
->exceptionStackBoundary();
1254 [&] (SSATmp
* val
) { // class to string conversions
1255 auto const str
= gen(env
, LdClsName
, val
);
1256 auto const offset
= offsetFromIRSP(env
, BCSPRelOffset
{ ind
});
1257 gen(env
, StStk
, IRSPRelOffsetData
{offset
}, sp(env
), str
);
1258 env
.irb
->exceptionStackBoundary();
1261 [&] (SSATmp
* val
) { // clsmeth to varray/vec conversions
1262 if (RuntimeOption::EvalVecHintNotices
) {
1263 raiseClsmethCompatTypeHint(env
, id
, func
, tc
);
1265 auto clsMethArr
= convertClsMethToVec(env
, val
);
1267 push(env
, clsMethArr
);
1271 [&] (Type
, bool hard
) { // Check failure
1273 env
.irb
->exceptionStackBoundary();
1276 hard
? VerifyRetFailHard
: VerifyRetFail
,
1277 ParamWithTCData
{ id
, &tc
},
1278 ldStkAddr(env
, BCSPRelOffset
{ ind
})
1281 [&] (SSATmp
* val
) { // Callable check
1289 [&] (SSATmp
* val
, SSATmp
* objClass
, SSATmp
* checkCls
) {
1290 // Class/type-alias check
1297 cns(env
, uintptr_t(&tc
)),
1301 [&] (SSATmp
* valRecDesc
, SSATmp
* checkRec
, SSATmp
* val
) {
1302 // Record/type-alias check
1309 cns(env
, uintptr_t(&tc
)),
1314 PUNT(VerifyReturnType
);
1318 auto const& tc
= (id
== TypeConstraint::ReturnId
)
1319 ? func
->returnTypeConstraint()
1320 : func
->params()[id
].typeConstraint
;
1323 if (id
== TypeConstraint::ReturnId
&& func
->hasReturnWithMultiUBs()) {
1324 auto& ubs
= const_cast<Func::UpperBoundVec
&>(func
->returnUBs());
1325 for (auto& ub
: ubs
) {
1326 applyFlagsToUB(ub
, tc
);
1329 } else if (func
->hasParamsWithMultiUBs()) {
1330 auto& ubs
= const_cast<Func::ParamUBMap
&>(func
->paramUBs());
1331 auto it
= ubs
.find(id
);
1332 if (it
!= ubs
.end()) {
1333 for (auto& ub
: it
->second
) {
1334 applyFlagsToUB(ub
, tc
);
1341 void verifyParamTypeImpl(IRGS
& env
, int32_t id
) {
1342 auto const func
= curFunc(env
);
1343 auto const verifyFunc
= [&](const TypeConstraint
& tc
) {
1349 [&] { // Get value to test
1350 return ldLoc(env
, id
, nullptr, DataTypeSpecific
);
1352 [&] (SSATmp
* val
) { // func to string conversions
1353 auto const str
= gen(env
, LdFuncName
, val
);
1354 stLocRaw(env
, id
, fp(env
), str
);
1357 [&] (SSATmp
* val
) { // class to string conversions
1358 auto const str
= gen(env
, LdClsName
, val
);
1359 stLocRaw(env
, id
, fp(env
), str
);
1362 [&] (SSATmp
* val
) { // clsmeth to varray/vec conversions
1363 if (RuntimeOption::EvalVecHintNotices
) {
1364 raiseClsmethCompatTypeHint(env
, id
, func
, tc
);
1366 auto clsMethArr
= convertClsMethToVec(env
, val
);
1367 stLocRaw(env
, id
, fp(env
), clsMethArr
);
1371 [&] (Type valType
, bool hard
) { // Check failure
1372 auto const failHard
= hard
&&
1373 !(tc
.isArray() && valType
.maybe(TObj
));
1376 failHard
? VerifyParamFailHard
: VerifyParamFail
,
1377 ParamWithTCData
{ id
, &tc
}
1380 [&] (SSATmp
* val
) { // Callable check
1383 VerifyParamCallable
,
1388 [&] (SSATmp
*, SSATmp
* objClass
, SSATmp
* checkCls
) {
1389 // Class/type-alias check
1395 cns(env
, uintptr_t(&tc
)),
1399 [&] (SSATmp
* valRecDesc
, SSATmp
* checkRec
, SSATmp
*) {
1400 // Record/type-alias check
1406 cns(env
, uintptr_t(&tc
)),
1411 PUNT(VerifyParamType
);
1415 auto const& tc
= func
->params()[id
].typeConstraint
;
1417 if (func
->hasParamsWithMultiUBs()) {
1418 auto& ubs
= const_cast<Func::ParamUBMap
&>(func
->paramUBs());
1419 auto it
= ubs
.find(id
);
1420 if (it
!= ubs
.end()) {
1421 for (auto& ub
: it
->second
) {
1422 applyFlagsToUB(ub
, tc
);
1431 void verifyPropType(IRGS
& env
,
1433 const HPHP::TypeConstraint
* tc
,
1434 const Class::UpperBoundVec
* ubs
,
1439 SSATmp
** coerce
/* = nullptr */) {
1440 assertx(cls
->isA(TCls
));
1441 assertx(val
->isA(TCell
));
1443 if (coerce
) *coerce
= val
;
1444 if (RuntimeOption::EvalCheckPropTypeHints
<= 0) return;
1446 auto const verifyFunc
= [&](const TypeConstraint
* tc
) {
1447 if (!tc
|| !tc
->isCheckable()) return;
1448 assertx(tc
->validForProp());
1455 [&] { // Get value to check
1456 env
.irb
->constrainValue(val
, DataTypeSpecific
);
1459 [&] (SSATmp
*) { return false; }, // No func to string automatic conversions
1460 [&] (SSATmp
*) { // class to string automatic conversions
1461 if (!coerce
) return false;
1462 if (RO::EvalCheckPropTypeHints
< 3) return false;
1463 *coerce
= gen(env
, LdClsName
, val
);
1467 if (!coerce
) return false;
1468 // If we're not hard enforcing property type mismatches don't coerce
1469 if (RO::EvalCheckPropTypeHints
< 3) return false;
1470 if (tc
->isUpperBound() && RO::EvalEnforceGenericsUB
< 2) return false;
1471 if (RuntimeOption::EvalVecHintNotices
) {
1472 if (cls
->hasConstVal(TCls
) && name
->hasConstVal(TStr
)) {
1473 auto const msg
= makeStaticString(folly::sformat(
1474 "class_meth Compat: {} '{}::{}' declared as type {}, clsmeth "
1476 isSProp
? "Static property" : "Property",
1477 cls
->clsVal()->name()->data(),
1478 name
->strVal()->data(),
1479 tc
->displayName().c_str()
1481 gen(env
, RaiseNotice
, cns(env
, msg
));
1485 RaiseClsMethPropConvertNotice
,
1486 RaiseClsMethPropConvertNoticeData
{tc
, isSProp
},
1492 *coerce
= convertClsMethToVec(env
, val
);
1495 [&] (Type
, bool hard
) { // Check failure
1496 auto const failHard
=
1497 hard
&& RuntimeOption::EvalCheckPropTypeHints
>= 3 &&
1498 (!tc
->isUpperBound() || RuntimeOption::EvalEnforceGenericsUB
>= 2);
1501 failHard
? VerifyPropFailHard
: VerifyPropFail
,
1502 TypeConstraintData
{ tc
},
1509 // We don't allow callable as a property type-hint, so we should never need
1510 // to check callability.
1511 [&] (SSATmp
*) { always_assert(false); },
1512 [&] (SSATmp
* v
, SSATmp
*, SSATmp
* checkCls
) { // Class/type-alias check
1516 TypeConstraintData
{ tc
},
1524 [&] (SSATmp
*, SSATmp
* checkRec
, SSATmp
* val
) { // Record/type-alias check
1528 TypeConstraintData
{ tc
},
1537 // Unlike the other type-hint checks, we don't punt here. We instead do
1538 // the check using a runtime helper. This gives us the freedom to call
1539 // verifyPropType without us worrying about it punting the entire
1541 if (coerce
&& (tc
->isArray() || (tc
->isObject() && !tc
->isResolved()))) {
1545 TypeConstraintData
{ tc
},
1555 TypeConstraintData
{ tc
},
1566 if (RuntimeOption::EvalEnforceGenericsUB
> 0) {
1567 for (auto const& ub
: *ubs
) {
1573 void emitVerifyRetTypeC(IRGS
& env
) {
1574 verifyRetTypeImpl(env
, TypeConstraint::ReturnId
, 0, false);
1577 void emitVerifyRetTypeTS(IRGS
& env
) {
1578 verifyRetTypeImpl(env
, TypeConstraint::ReturnId
, 1, false);
1579 auto const ts
= popC(env
);
1580 auto const cell
= topC(env
);
1581 auto const reified
= tcCouldBeReified(curFunc(env
), TypeConstraint::ReturnId
);
1582 if (reified
|| cell
->isA(TObj
)) {
1583 gen(env
, VerifyReifiedReturnType
, cell
, ts
);
1584 } else if (cell
->type().maybe(TObj
) && !reified
) {
1585 // Meaning we did not not guard on the stack input correctly
1586 PUNT(VerifyRetTypeTS
-UnguardedObj
);
1590 void emitVerifyRetNonNullC(IRGS
& env
) {
1591 auto const func
= curFunc(env
);
1592 auto const& tc
= func
->returnTypeConstraint();
1593 always_assert(!tc
.isNullable());
1594 verifyRetTypeImpl(env
, TypeConstraint::ReturnId
, 0, true);
1597 void emitVerifyOutType(IRGS
& env
, uint32_t paramId
) {
1598 verifyRetTypeImpl(env
, paramId
, 0, false);
1601 void emitVerifyParamType(IRGS
& env
, int32_t paramId
) {
1602 verifyParamTypeImpl(env
, paramId
);
1605 void emitVerifyParamTypeTS(IRGS
& env
, int32_t paramId
) {
1606 verifyParamTypeImpl(env
, paramId
);
1607 auto const ts
= popC(env
);
1608 auto const cell
= ldLoc(env
, paramId
, nullptr, DataTypeSpecific
);
1609 auto const reified
= tcCouldBeReified(curFunc(env
), paramId
);
1610 if (cell
->isA(TObj
) || reified
) {
1611 gen(env
, VerifyReifiedLocalType
, ParamData
{ paramId
}, ts
);
1612 } else if (cell
->type().maybe(TObj
)) {
1613 // Meaning we did not not guard on the stack input correctly
1614 PUNT(VerifyReifiedLocalType
-UnguardedObj
);
1618 void emitOODeclExists(IRGS
& env
, OODeclExistsOp subop
) {
1619 auto const tAutoload
= topC(env
);
1620 auto const tCls
= topC(env
, BCSPRelOffset
{1});
1622 if (!tCls
->isA(TStr
) || !tAutoload
->isA(TBool
)){ // result of Cast
1623 PUNT(OODeclExists
-BadTypes
);
1628 case OODeclExistsOp::Class
: kind
= ClassKind::Class
; break;
1629 case OODeclExistsOp::Trait
: kind
= ClassKind::Trait
; break;
1630 case OODeclExistsOp::Interface
: kind
= ClassKind::Interface
; break;
1633 auto const val
= gen(
1636 ClassKindData
{ kind
},
1645 void emitIssetL(IRGS
& env
, int32_t id
) {
1646 auto const ld
= ldLoc(env
, id
, nullptr, DataTypeSpecific
);
1647 if (ld
->isA(TClsMeth
)) {
1648 PUNT(IssetL_is_ClsMeth
);
1650 push(env
, gen(env
, IsNType
, TNull
, ld
));
1653 void emitIsUnsetL(IRGS
& env
, int32_t id
) {
1654 auto const ld
= ldLoc(env
, id
, nullptr, DataTypeSpecific
);
1655 push(env
, gen(env
, IsType
, TUninit
, ld
));
1658 SSATmp
* isTypeHelper(IRGS
& env
, IsTypeOp subop
, SSATmp
* val
) {
1660 case IsTypeOp::VArray
: /* intentional fallthrough */
1661 case IsTypeOp::DArray
: return isDVArrayImpl(env
, val
, subop
);
1662 case IsTypeOp::PHPArr
:
1663 return isArrayImpl(env
, val
, /*log_on_hack_arrays=*/false);
1665 return RO::EvalWidenIsArray
1666 ? isArrLikeImpl(env
, val
, /* log_on_hack_arrays = */ true)
1667 : isArrayImpl(env
, val
, /*log_on_hack_arrays=*/true);
1668 case IsTypeOp::Vec
: return isVecImpl(env
, val
);
1669 case IsTypeOp::Dict
: return isDictImpl(env
, val
);
1670 case IsTypeOp::Scalar
: return isScalarImpl(env
, val
);
1671 case IsTypeOp::Str
: return isStrImpl(env
, val
);
1672 case IsTypeOp::ArrLike
:
1673 return isArrLikeImpl(env
, val
, /*log_on_hack_arrays = */ false);
1677 auto const t
= typeOpToType(subop
);
1678 return t
<= TObj
? optimizedCallIsObject(env
, val
) : gen(env
, IsType
, t
, val
);
1681 void emitIsTypeC(IRGS
& env
, IsTypeOp subop
) {
1682 auto const val
= popC(env
, DataTypeSpecific
);
1683 push(env
, isTypeHelper(env
, subop
, val
));
1687 void emitIsTypeL(IRGS
& env
, NamedLocal loc
, IsTypeOp subop
) {
1688 auto const val
= ldLocWarn(env
, loc
, nullptr, DataTypeSpecific
);
1689 push(env
, isTypeHelper(env
, subop
, val
));
1692 //////////////////////////////////////////////////////////////////////
1694 void emitAssertRATL(IRGS
& env
, int32_t loc
, RepoAuthType rat
) {
1695 assertTypeLocal(env
, loc
, typeFromRAT(rat
, curClass(env
)));
1698 void emitAssertRATStk(IRGS
& env
, uint32_t offset
, RepoAuthType rat
) {
1701 BCSPRelOffset
{safe_cast
<int32_t>(offset
)},
1702 typeFromRAT(rat
, curClass(env
))
1706 //////////////////////////////////////////////////////////////////////
1708 SSATmp
* doDVArrChecks(IRGS
& env
, SSATmp
* arr
, Block
* taken
,
1709 const TypeConstraint
& tc
) {
1710 assertx(tc
.isArray());
1711 auto const type
= [&]{
1712 if (tc
.isVArray()) return TVArr
;
1713 if (tc
.isDArray()) return TDArr
;
1714 assertx(tc
.isVArrayOrDArray());
1717 return gen(env
, CheckType
, type
, taken
, arr
);
1720 //////////////////////////////////////////////////////////////////////