Implement all of VerifyParamType in hhir

TranslatorX64 interps cases that it expects to fail but I
didn't like that, so the hhir version never punts or interps. The fast
path code reuses the stuff Jordan added for InstanceOfD and should be
the same as tx64's, modulo some branch fusing that doesn't appear to
matter in perflab.
Esse commit está contido em:
bsimmers
2013-03-28 09:16:05 -07:00
commit de Sara Golemon
commit 7d7edb8be2
18 arquivos alterados com 302 adições e 141 exclusões
+25 -6
Ver Arquivo
@@ -647,13 +647,13 @@ D:T = LdConst<T>
D:ConstT = DefConst<T>
D:Cls = LdCls S0:Str S1:Cls
D:Cls = LdCls S0:Str S1:ConstCls
Loads the class named S0 in the context of the class S1. Invokes
autoload and may raise an error if the class is not defined. The
explicit context parameter allows the compiler to optimize this
instruction by simplifying it to LdClsCached, which uses the target
cache.
Loads the class named S0 in the context of the class S1. Invokes autoload and
may raise an error if the class is not defined. The explicit context
parameter allows the compiler to simplify this instruction to a DefConst in
some cases. If S0 is constant, this instruction may be simplified to a
LdClsCached.
D:Cls = LdClsCached S0:ConstStr
@@ -1029,6 +1029,25 @@ Nop
14. Runtime helpers
VerifyParamCls S0:Cls S1:Cls S2:ConstInt
Verify parameter type for classes or traits. If S0 does not extend
(if S1 is a class) or implement (if S1 is an interface) S1, this
instruction will raise a recoverable fatal error describing the type
mismatch.
VerifyParamCallable S0:Cell S1:ConstInt
If S0 is not callable, as defined by the php function is_callable,
this instruction will raise a recoverable fatal error describing the
type mismatch.
VerifyParamFail S0:ConstInt
Assumes that parameter number S0 in the current function has failed
its typehint and raises a recoverable fatal error describing the
type mismatch.
RaiseUninitLoc<localId>
Raise a notice for an uninitialized local variable.
+38 -16
Ver Arquivo
@@ -316,6 +316,8 @@ CALL_OPCODE(DbgAssertPtr)
CALL_OPCODE(LdSwitchDblIndex);
CALL_OPCODE(LdSwitchStrIndex);
CALL_OPCODE(LdSwitchObjIndex);
CALL_OPCODE(VerifyParamCallable);
CALL_OPCODE(VerifyParamFail);
CALL_OPCODE(RaiseUninitLoc)
CALL_OPCODE(WarnNonObjProp)
CALL_OPCODE(ThrowNonObjProp)
@@ -4768,6 +4770,24 @@ void traceCallback(ActRec* fp, Cell* sp, int64_t pcOff, void* rip) {
checkFrame(fp, sp, /*checkLocals*/true);
}
void CodeGenerator::cgVerifyParamCls(IRInstruction* inst) {
SSATmp* objClass = inst->getSrc(0);
assert(!objClass->isConst());
auto objClassReg = objClass->getReg();
SSATmp* constraint = inst->getSrc(1);
if (constraint->isConst()) {
m_as. cmpq(constraint->getValClass(), objClassReg);
} else {
m_as. cmpq(constraint->getReg(), objClassReg);
}
// The native call for this instruction is the slow path that does
// proper subtype checking. The comparison above is just to
// short-circuit the overhead when the Classes are an exact match.
ifThen(m_as, CC_NE, [&]{ cgCallNative(inst); });
}
void CodeGenerator::emitTraceCall(CodeGenerator::Asm& as, int64_t pcOff,
Transl::TranslatorX64* tx64) {
// call to a trace function
@@ -4840,26 +4860,28 @@ void cgTrace(Trace* trace, Asm& amain, Asm& astubs, Transl::TranslatorX64* tx64,
TCA astubsStart = astubs.code.frontier;
patchJumps(*as, state, block);
// If the block ends with a Jmp_ to the next block we're translating into
// the same assembler, it doesn't need to actually emit a jmp.
state.noTerminalJmp_ = false;
IRInstruction* last = block->back();
if (last->getOpcode() == Jmp_) {
for (auto next = it; next != end; ++next) {
if (chooseAs(*next) == as) {
state.noTerminalJmp_ = last->getTaken() == *next;
break;
}
// Grab the next block that will go into this assembler
Block* nextThisAs = nullptr;
for (auto next = it; next != end; ++next) {
if (chooseAs(*next) == as) {
nextThisAs = *next;
break;
}
}
// If the block ends with a Jmp_ to the next block for this
// assembler, it doesn't need to actually emit a jmp.
IRInstruction* last = block->back();
state.noTerminalJmp_ =
last->getOpcode() == Jmp_ && last->getTaken() == nextThisAs;
CodeGenerator cg(trace, *as, astubs, tx64, state);
cg.cgBlock(block, bcMap);
if (Block* next = block->getNext()) {
// if there's a fallthrough block and it's not the next thing going
// into this assembler, then emit a jump to it.
if (it == end || next != *it || as != chooseAs(next)) {
CodeGenerator::emitFwdJmp(*as, next, state);
}
Block* next = block->getNext();
if (next && next != nextThisAs) {
// if there's a fallthrough block and it's not the next thing
// going into this assembler, then emit a jump to it.
CodeGenerator::emitFwdJmp(*as, next, state);
}
if (state.asmInfo) {
state.asmInfo->asmRanges[block] = TcaRange(asmStart, as->code.frontier);
+72 -37
Ver Arquivo
@@ -1858,48 +1858,86 @@ Trace* HhbcTranslator::guardRefs(int64_t entryArDelta,
return exitTrace;
}
void HhbcTranslator::emitVerifyParamType(int32_t paramId,
const StringData* constraintClsName) {
/* It's currently better to interpOne all of these, since the slow path punts
* for the entire tracelet */
emitInterpOneOrPunt(Type::None);
return;
assert(!constraintClsName || constraintClsName->isStatic());
void HhbcTranslator::emitVerifyParamType(int32_t paramId) {
const Func* func = getCurFunc();
const Class* constraint = (constraintClsName ?
Unit::lookupUniqueClass(constraintClsName) :
nullptr);
const TypeConstraint& tc = func->params()[paramId].typeConstraint();
const StringData* tcTypeName = tc.typeName();
SSATmp* locVal = m_tb->genLdLoc(paramId);
Type locType = locVal->getType().unbox();
assert(locType.isKnownDataType());
// VerifyParamType does not remove the parameter from the stack
TRACE(3, "%u: VerifyParamType %s\n", m_bcOff, tcTypeName->data());
Trace* exitTrace2 = getExitSlowTrace();
// XXX Should verify param type unbox?
SSATmp* param = m_tb->genLdAssertedLoc(paramId, Type::Gen);
if (param->getType() != Type::Obj) {
PUNT(VerifyParamType_nonobj);
if (tc.nullable() && locType.isNull()) {
return;
}
if (tc.isCallable()) {
spillStack();
locVal = m_tb->gen(Unbox, getExitTrace(), locVal);
m_tb->gen(VerifyParamCallable, locVal, cns(paramId));
return;
}
// For non-object guards, or object guards with non-object runtime
// types, we rely on what we know from the tracelet guards and never
// have to do runtime checks.
if ((!tc.isObject() && !tc.check(locType.toDataType())) ||
(tc.isObject() && !locType.subtypeOf(Type::Obj))) {
spillStack();
m_tb->gen(VerifyParamFail, cns(paramId));
return;
}
if (!tc.isObject()) {
return;
}
SSATmp* objClass = m_tb->gen(LdObjClass, param);
if (tc.isObject()) {
if (!(param->getType() == Type::Obj ||
(param->getType().isNull() && tc.nullable()))) {
emitInterpOne(Type::None);
return;
}
const StringData* clsName;
const Class* knownConstraint = nullptr;
if (!tc.isSelf() && !tc.isParent()) {
clsName = tc.typeName();
knownConstraint = Unit::lookupClass(clsName);
} else {
if (!tc.check(frame_local(curFrame() /* FIXME */, paramId), getCurFunc())) {
emitInterpOne(Type::None);
if (tc.isSelf()) {
tc.selfToClass(getCurFunc(), &knownConstraint);
} else if (tc.isParent()) {
tc.parentToClass(getCurFunc(), &knownConstraint);
}
if (knownConstraint) {
clsName = knownConstraint->preClass()->name();
} else {
// The hint was self or parent and there's no corresponding
// class for the current func. This typehint will always fail.
spillStack();
m_tb->gen(VerifyParamFail, cns(paramId));
return;
}
}
assert(clsName);
// We can only burn in the Class* if it's unique or in the
// inheritance hierarchy of our context. It's ok if the class isn't
// defined yet - all paths below are tolerant of a null constraint.
if (!classIsUniqueOrCtxParent(knownConstraint)) knownConstraint = nullptr;
m_tb->genVerifyParamType(objClass,
m_tb->genDefConst(tcTypeName),
constraint,
exitTrace2);
Class::initInstanceBits();
bool haveBit = Class::haveInstanceBit(clsName);
SSATmp* constraint = knownConstraint ? cns(knownConstraint)
: m_tb->gen(LdCachedClass, cns(clsName));
locVal = m_tb->gen(Unbox, getExitTrace(), locVal);
SSATmp* objClass = m_tb->gen(LdObjClass, locVal);
if (haveBit || classIsUniqueNormalClass(knownConstraint)) {
SSATmp* isInstance = haveBit ?
m_tb->gen(InstanceOfBitmask, objClass, cns(clsName))
: m_tb->gen(ExtendsClass, objClass, constraint);
m_tb->ifThen(getCurFunc(),
[&](Block* taken) {
m_tb->gen(JmpZero, taken, isInstance);
},
[&] { // taken: the param type does not match
m_tb->hint(Block::Unlikely);
spillStack();
m_tb->gen(VerifyParamFail, cns(paramId));
}
);
} else {
spillStack();
m_tb->gen(VerifyParamCls, objClass, constraint, cns(paramId));
}
}
void HhbcTranslator::emitInstanceOfD(int classNameStrId) {
@@ -1929,11 +1967,8 @@ void HhbcTranslator::emitInstanceOfD(int classNameStrId) {
const bool haveBit = Class::haveInstanceBit(className);
Class* const maybeCls = Unit::lookupUniqueClass(className);
const bool isNormalClass = maybeCls &&
!(maybeCls->attrs() &
(AttrTrait | AttrInterface));
const bool isUnique = RuntimeOption::RepoAuthoritative &&
maybeCls && (maybeCls->attrs() & AttrUnique);
const bool isNormalClass = classIsUniqueNormalClass(maybeCls);
const bool isUnique = classIsUnique(maybeCls);
/*
* If the class is unique or a parent of the current context, we
@@ -271,8 +271,7 @@ struct HhbcTranslator {
void emitIsBoolC();
void emitIsDoubleL(int id);
void emitIsDoubleC();
void emitVerifyParamType(int32_t paramId,
const StringData* constraintClsName);
void emitVerifyParamType(int32_t paramId);
void emitInstanceOfD(int classNameStrId);
void emitNop() {}
void emitCastBool();
+3
Ver Arquivo
@@ -332,6 +332,9 @@ O(DefLabel, DMulti, SUnk, E) \
O(Marker, ND, NA, E) \
O(DefFP, D(StkPtr), NA, E) \
O(DefSP, D(StkPtr), S(StkPtr) C(Int), E) \
O(VerifyParamCls, ND, S(Cls) S(Cls) C(Int),E|N|Mem|Refs|Er) \
O(VerifyParamCallable, ND, S(Cell) C(Int), E|N|Mem|Refs|Er) \
O(VerifyParamFail, ND, C(Int), E|N|Mem|Refs|Er) \
O(RaiseUninitLoc, ND, S(Str), E|N|Mem|Refs|Er) \
O(WarnNonObjProp, ND, NA, E|N|Refs|Er|Mem) \
O(ThrowNonObjProp, ND, NA, T|E|N|Refs|Er|Mem) \
+2 -28
Ver Arquivo
@@ -1150,35 +1150,9 @@ TranslatorX64::irTranslateStaticLocInit(const Tracelet& t,
// check class hierarchy and fail if no match
void
TranslatorX64::irTranslateVerifyParamType(const Tracelet& t,
const NormalizedInstruction& i) {
const NormalizedInstruction& i) {
int param = i.imm[0].u_IVA;
const TypeConstraint& tc = curFunc()->params()[param].typeConstraint();
// not quite a nop. The guards should have verified that the m_type field
// is compatible, but for objects we need to go one step further and
// ensure that we're dealing with the right class.
// NULL inputs only get traced when constraint is nullable.
assert(i.inputs.size() == 1);
if (!i.inputs[0]->isObject()) {
HHIR_UNIMPLEMENTED_WHEN(i.m_txFlags == Interp, VerifyParamType);
return; // nop.
}
bool isSelf = tc.isSelf();
bool isParent = tc.isParent();
const Class *constraint = nullptr;
UNUSED TargetCache::CacheHandle ch = 0;
if (isSelf) {
tc.selfToClass(curFunc(), &constraint);
} else if (isParent) {
tc.parentToClass(curFunc(), &constraint);
} else {
const StringData* clsName = tc.typeName();
ch = TargetCache::allocKnownClass(clsName);
}
HHIR_EMIT(VerifyParamType, param, (constraint ? constraint->name() : nullptr));
HHIR_EMIT(VerifyParamType, param);
}
void
@@ -76,6 +76,11 @@ static CallMap s_callMap({
{PrintStr, (TCA)print_string, DNone, SNone, {{SSA, 0}}},
{PrintInt, (TCA)print_int, DNone, SNone, {{SSA, 0}}},
{PrintBool, (TCA)print_boolean, DNone, SNone, {{SSA, 0}}},
{VerifyParamCls, (TCA)VerifyParamTypeSlow, DNone, SSync,
{{SSA, 0}, {SSA, 1}, {SSA, 2}}},
{VerifyParamCallable, (TCA)VerifyParamTypeCallable, DNone, SSync,
{{TV, 0}, {SSA, 1}}},
{VerifyParamFail, (TCA)VerifyParamTypeFail, DNone, SSync, {{SSA, 0}}},
{RaiseUninitLoc, (TCA)raiseUndefVariable, DNone, SSync, {{SSA, 0}}},
{WarnNonObjProp, (TCA)raisePropertyOnNonObject, DNone, SSync, {}},
{ThrowNonObjProp, (TCA)throw_null_object_prop, DNone, SSync, {}},
+1 -1
Ver Arquivo
@@ -337,7 +337,7 @@ SSATmp* Simplifier::simplifyGetCtxFwdCall(IRInstruction* inst) {
SSATmp* Simplifier::simplifyLdCls(IRInstruction* inst) {
SSATmp* clsName = inst->getSrc(0);
if (clsName->isConst()) {
const Class* cls = Unit::lookupUniqueClass(clsName->getValStr());
const Class* cls = Unit::lookupClass(clsName->getValStr());
if (cls) {
if (RuntimeOption::RepoAuthoritative && (cls->attrs() & AttrUnique)) {
// the class is unique
+2 -13
Ver Arquivo
@@ -420,7 +420,7 @@ SSATmp* TraceBuilder::genJmpCond(SSATmp* boolSrc, Trace* target, bool negate) {
void TraceBuilder::genJmp(Block* target, SSATmp* src) {
EdgeData edge;
gen(Jmp_, target, &edge, src)->getInstruction();
gen(Jmp_, target, &edge, src);
}
void TraceBuilder::genExitWhenSurprised(Trace* targetTrace) {
@@ -515,18 +515,6 @@ SSATmp* TraceBuilder::genLdClsMethodCache(SSATmp* className,
baseClass);
}
// TODO(#2058871): move this to hhbctranslator
void TraceBuilder::genVerifyParamType(SSATmp* objClass,
SSATmp* className,
const Class* constraintClass,
Trace* exitTrace) {
// do NOT use genLdCls() since don't want to load class if it isn't loaded
SSATmp* constraint =
constraintClass ? genDefConst<const Class*>(constraintClass)
: gen(LdCachedClass, className);
gen(JmpNSame, getFirstBlock(exitTrace), objClass, constraint);
}
SSATmp* TraceBuilder::genBoxLoc(uint32_t id) {
SSATmp* prevValue = genLdLoc(id);
Type prevType = prevValue->getType();
@@ -584,6 +572,7 @@ SSATmp* TraceBuilder::genLdLoc(uint32_t id) {
SSATmp* TraceBuilder::genLdLocAsCell(uint32_t id, Trace* exitTrace) {
SSATmp* tmp = genLdLoc(id);
Type type = tmp->getType();
assert(type.isBoxed() || type.notBoxed());
if (!type.isBoxed()) {
return tmp;
}
+52 -5
Ver Arquivo
@@ -214,8 +214,6 @@ public:
SSATmp* genLdStackAddr(int64_t offset) {
return genLdStackAddr(m_spValue, offset);
}
void genVerifyParamType(SSATmp* objClass, SSATmp* className,
const Class* constraint, Trace* exitTrace);
void genNativeImpl();
@@ -297,6 +295,22 @@ public:
m_trace->back()->setHint(h);
}
struct DisableCseGuard {
DisableCseGuard(TraceBuilder& tb)
: m_tb(tb)
, m_oldEnable(tb.m_enableCse)
{
m_tb.m_enableCse = false;
}
~DisableCseGuard() {
m_tb.m_enableCse = m_oldEnable;
}
private:
TraceBuilder& m_tb;
bool m_oldEnable;
};
/*
* cond() generates if-then-else blocks within a trace. The caller
* supplies lambdas to create the branch, next-body, and taken-body.
@@ -307,9 +321,7 @@ public:
SSATmp* cond(const Func* func, Branch branch, Next next, Taken taken) {
Block* taken_block = m_irFactory.defBlock(func);
Block* done_block = m_irFactory.defBlock(func, 1);
bool oldEnableCse = m_enableCse;
m_enableCse = false;
SCOPE_EXIT { m_enableCse = oldEnableCse; };
DisableCseGuard guard(*this);
branch(taken_block);
SSATmp* v1 = next();
genJmp(done_block, v1);
@@ -322,6 +334,41 @@ public:
return result;
}
/*
* ifThen generates if-then blocks within a trace that do not
* produce values. Code emitted in the taken lambda will be executed
* iff the branch emitted in the branch lambda is taken.
*/
template <class Branch, class Taken>
void ifThen(const Func* func, Branch branch, Taken taken) {
Block* taken_block = m_irFactory.defBlock(func);
Block* done_block = m_irFactory.defBlock(func);
DisableCseGuard guard(*this);
branch(taken_block);
assert(!m_trace->back()->getNext());
m_trace->back()->setNext(done_block);
appendBlock(taken_block);
taken();
taken_block->setNext(done_block);
appendBlock(done_block);
}
/*
* ifElse generates if-then-else blocks with an empty 'then' block
* that do not produce values. Code emitted in the next lambda will
* be executed iff the branch emitted in the branch lambda is not
* taken.
*/
template <class Branch, class Next>
void ifElse(const Func* func, Branch branch, Next next) {
Block* done_block = m_irFactory.defBlock(func);
DisableCseGuard guard(*this);
branch(done_block);
next();
m_trace->back()->setNext(done_block);
appendBlock(done_block);
}
private:
static void appendInstruction(IRInstruction* inst, Block* block);
void appendInstruction(IRInstruction* inst);
@@ -16,7 +16,9 @@
#include "runtime/vm/translator/translator-runtime.h"
#include "runtime/ext/ext_function.h"
#include "runtime/vm/member_operations.h"
#include "runtime/vm/type_constraint.h"
namespace HPHP { namespace VM { namespace Transl {
@@ -59,4 +61,27 @@ void raiseUndefProp(ObjectData* base, const StringData* name) {
static_cast<Instance*>(base)->raiseUndefProp(name);
}
void VerifyParamTypeFail(int paramNum) {
VMRegAnchor _;
const ActRec* ar = curFrame();
const Func* func = ar->m_func;
const TypeConstraint& tc = func->params()[paramNum].typeConstraint();
TypedValue* tv = frame_local(ar, paramNum);
assert(!tc.check(tv, func));
tc.verifyFail(func, paramNum, tv);
}
void VerifyParamTypeCallable(TypedValue value, int param) {
if (UNLIKELY(!f_is_callable(tvAsCVarRef(&value)))) {
VerifyParamTypeFail(param);
}
}
HOT_FUNC_VM
void VerifyParamTypeSlow(const Class* cls, const Class* constraint, int param) {
if (UNLIKELY(!(constraint && cls->classof(constraint)))) {
VerifyParamTypeFail(param);
}
}
} } }
@@ -59,6 +59,10 @@ void bindNewElemIR(TypedValue* base, RefData* val, MInstrState* mis);
RefData* box_value(TypedValue tv);
void raisePropertyOnNonObject();
void raiseUndefProp(ObjectData* base, const StringData* name);
void VerifyParamTypeFail(int param);
void VerifyParamTypeCallable(TypedValue value, int param);
void VerifyParamTypeSlow(const Class* cls, const Class* constraint, int param);
int64_t switchDoubleHelper(int64_t val, int64_t base, int64_t nTargets);
int64_t switchStringHelper(StringData* s, int64_t base, int64_t nTargets);
+3 -30
Ver Arquivo
@@ -311,14 +311,14 @@ classIsPersistent(const Class* cls) {
TargetCache::isPersistentHandle(cls->m_cachedOffset);
}
inline static bool
bool
classIsUnique(const Class* cls) {
return RuntimeOption::RepoAuthoritative &&
cls &&
(cls->attrs() & AttrUnique);
}
inline static bool
bool
classIsUniqueOrCtxParent(const Class* cls) {
if (!cls) return false;
if (classIsUnique(cls)) return true;
@@ -327,7 +327,7 @@ classIsUniqueOrCtxParent(const Class* cls) {
return ctx->classof(cls);
}
inline static bool
bool
classIsUniqueNormalClass(const Class* cls) {
return classIsUnique(cls) &&
!(cls->attrs() & (AttrInterface | AttrTrait));
@@ -10323,32 +10323,6 @@ emitClassToReg(X64Assembler& a, const StringData* name, PhysReg r) {
}
}
static void
VerifyParamTypeFail(int paramNum) {
VMRegAnchor _;
const ActRec* ar = curFrame();
const Func* func = ar->m_func;
const TypeConstraint& tc = func->params()[paramNum].typeConstraint();
assert(tc.isObject());
TypedValue* tv = frame_local(ar, paramNum);
TRACE(3, "%s Obj %s, needs type %s\n",
__func__,
tv->m_data.pobj->getVMClass()->name()->data(),
tc.typeName()->data());
tc.verifyFail(func, paramNum, tv);
}
// check class hierarchy and fail if no match
static void
VerifyParamTypeSlow(const Class* cls, const Class* constraint, int param) {
Stats::inc(Stats::Tx64_VerifyParamTypeSlow);
Stats::inc(Stats::Tx64_VerifyParamTypeSlowShortcut, -1);
if (UNLIKELY(!(constraint && cls->classof(constraint)))) {
VerifyParamTypeFail(param);
}
}
static void
VerifyParamCallable(ObjectData* obj, int param) {
TypedValue tv;
@@ -10428,7 +10402,6 @@ TranslatorX64::translateVerifyParamType(const Tracelet& t,
IMM(param));
}
}
}
void
@@ -1151,6 +1151,10 @@ TXFlags planInstrAdd_Int(const NormalizedInstruction& i);
TXFlags planInstrAdd_Array(const NormalizedInstruction& i);
void dumpTranslationInfo(const Tracelet& t, TCA postGuards);
bool classIsUnique(const Class* cls);
bool classIsUniqueOrCtxParent(const Class* cls);
bool classIsUniqueNormalClass(const Class* cls);
// SpaceRecorder is used in translator-x64.cpp and in hopt/irtranslator.cpp
// RAII logger for TC space consumption.
struct SpaceRecorder {
+10 -2
Ver Arquivo
@@ -79,8 +79,8 @@ TypeConstraint::TypeConstraint(const StringData* typeName /* = NULL */,
m_namedEntity = Unit::GetNamedEntity(typeName);
return;
}
if (RuntimeOption::EnableHipHopSyntax || dtype.m_dt == KindOfArray ||
dtype.isParent() || dtype.isSelf()) {
if (RuntimeOption::EnableHipHopSyntax || dtype.m_dt == KindOfArray ||
dtype.isParent() || dtype.isSelf()) {
m_type = dtype;
} else {
m_type = { KindOfObject, Precise };
@@ -131,6 +131,14 @@ TypeConstraint::check(const TypedValue* tv, const Func* func) const {
return equivDataTypes(m_type.m_dt, tv->m_type);
}
bool
TypeConstraint::check(DataType dt) const {
assert(m_type.m_dt != KindOfObject);
assert(dt != KindOfRef);
if (m_nullable && IS_NULL_TYPE(dt)) return true;
return equivDataTypes(m_type.m_dt, dt);
}
void TypeConstraint::verifyFail(const Func* func, int paramNum,
const TypedValue* tv) const {
Transl::VMRegAnchor _;
+2 -1
Ver Arquivo
@@ -78,7 +78,7 @@ public:
bool isParent() const {
return m_type.isParent();
}
bool isCallable() const {
return m_type.isCallable();
}
@@ -112,6 +112,7 @@ public:
}
bool check(const TypedValue* tv, const Func* func) const;
bool check(DataType dt) const;
// NB: will throw if the check fails.
void verify(const TypedValue* tv,
+15
Ver Arquivo
@@ -0,0 +1,15 @@
<?php
// Copyright 2004-present Facebook. All Rights Reserved.
// Make sure we're tolerant of code that swallows typehint failures
function error_handler() {
var_dump(func_get_args());
return true;
}
function main(integer $foo) {
echo "in main\n";
}
set_error_handler('error_handler');
main(0.0);
echo "done with main\n";
+38
Ver Arquivo
@@ -0,0 +1,38 @@
array(6) {
[0]=>
int(4096)
[1]=>
string(72) "Argument 1 passed to main() must be an instance of integer, double given"
[2]=>
"hphp/test/vm/verify-param-type.php"
[3]=>
int(12)
[4]=>
string(0) ""
[5]=>
array(2) {
[0]=>
array(2) {
["file"]=>
"hphp/test/vm/verify-param-type.php"
["line"]=>
int(12)
}
[1]=>
array(4) {
["file"]=>
"hphp/test/vm/verify-param-type.php"
["line"]=>
int(14)
["function"]=>
string(4) "main"
["args"]=>
array(1) {
[0]=>
float(0)
}
}
}
}
in main
done with main