67bf707768
Replace NameDef with a new struct of runtime-resolved typedef information. This needs to include more than Class* or Typedef*, because we might have nullable type aliases, or a non-nullable alias to a nullable typedef, or vice versa. Switch to the new TypeAnnotation stuff in TypedefStatement instead of just strings so support for this isn't weird (shapes are outside of this for now though---see the hack in parser.cpp). Also fixes support for type aliases to mixed.
353 linhas
11 KiB
C++
353 linhas
11 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| HipHop for PHP |
|
|
+----------------------------------------------------------------------+
|
|
| Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) |
|
|
+----------------------------------------------------------------------+
|
|
| This source file is subject to version 3.01 of the PHP license, |
|
|
| that is bundled with this package in the file LICENSE, and is |
|
|
| available through the world-wide-web at the following url: |
|
|
| http://www.php.net/license/3_01.txt |
|
|
| If you did not receive a copy of the PHP license and are unable to |
|
|
| obtain it through the world-wide-web, please send a note to |
|
|
| license@php.net so we can mail you a copy immediately. |
|
|
+----------------------------------------------------------------------+
|
|
*/
|
|
|
|
#include "hphp/runtime/vm/jit/translator-runtime.h"
|
|
|
|
#include "hphp/runtime/ext/ext_function.h"
|
|
#include "hphp/runtime/vm/member_operations.h"
|
|
#include "hphp/runtime/vm/type_constraint.h"
|
|
|
|
namespace HPHP { namespace Transl {
|
|
|
|
ArrayData* addElemIntKeyHelper(ArrayData* ad,
|
|
int64_t key,
|
|
TypedValue value) {
|
|
// this does not re-enter
|
|
// set will decRef any old value that may have been overwritten
|
|
// if appropriate
|
|
ArrayData* retval = ad->set(key, tvAsCVarRef(&value),
|
|
ad->getCount() > 1);
|
|
// TODO Task #1970153: It would be great if there were set()
|
|
// methods that didn't bump up the refcount so that we didn't
|
|
// have to decrement it here
|
|
tvRefcountedDecRef(&value);
|
|
return arrayRefShuffle<false>(ad, retval, nullptr);
|
|
}
|
|
|
|
ArrayData* addElemStringKeyHelper(ArrayData* ad,
|
|
StringData* key,
|
|
TypedValue value) {
|
|
// this does not re-enter
|
|
bool copy = ad->getCount() > 1;
|
|
// set will decRef any old value that may have been overwritten
|
|
// if appropriate
|
|
int64_t intkey;
|
|
ArrayData* retval = UNLIKELY(key->isStrictlyInteger(intkey)) ?
|
|
ad->set(intkey, tvAsCVarRef(&value), copy) :
|
|
ad->set(key, tvAsCVarRef(&value), copy);
|
|
// TODO Task #1970153: It would be great if there were set()
|
|
// methods that didn't bump up the refcount so that we didn't
|
|
// have to decrement it here
|
|
tvRefcountedDecRef(&value);
|
|
return arrayRefShuffle<false>(ad, retval, nullptr);
|
|
}
|
|
|
|
ArrayData* array_add(ArrayData* a1, ArrayData* a2) {
|
|
if (!a2->empty()) {
|
|
if (a1->empty()) {
|
|
decRefArr(a1);
|
|
return a2;
|
|
}
|
|
if (a1 != a2) {
|
|
ArrayData *escalated = a1->plus(a2, a1->getCount() > 1);
|
|
if (escalated != a1) {
|
|
escalated->incRefCount();
|
|
decRefArr(a2);
|
|
decRefArr(a1);
|
|
return escalated;
|
|
}
|
|
}
|
|
}
|
|
decRefArr(a2);
|
|
return a1;
|
|
}
|
|
|
|
HOT_FUNC_VM void setNewElem(TypedValue* base, Cell val) {
|
|
SetNewElem<false>(base, &val);
|
|
}
|
|
|
|
void bindNewElemIR(TypedValue* base, RefData* val, MInstrState* mis) {
|
|
base = NewElem(mis->tvScratch, mis->tvRef, base);
|
|
if (!(base == &mis->tvScratch && base->m_type == KindOfUninit)) {
|
|
tvBindRef(val, base);
|
|
}
|
|
}
|
|
|
|
// TODO: Kill this #2031980
|
|
HOT_FUNC_VM RefData* box_value(TypedValue tv) {
|
|
return tvBoxHelper(tv.m_type, tv.m_data.num);
|
|
}
|
|
|
|
ArrayData* convCellToArrHelper(TypedValue tv) {
|
|
// Note: the call sites of this function all assume that
|
|
// no user code will run and no recoverable exceptions will
|
|
// occur while running this code. This seems trivially true
|
|
// in all cases but converting objects to arrays. It also
|
|
// seems true for that case as well, since the resulting array
|
|
// is essentially metadata for the object. If that is not true,
|
|
// you might end up looking at this code in a debugger and now
|
|
// you know why.
|
|
tvCastToArrayInPlace(&tv); // consumes a ref on counted values
|
|
return tv.m_data.parr;
|
|
}
|
|
|
|
int64_t convArrToBoolHelper(const ArrayData* a) {
|
|
return a->size() != 0;
|
|
}
|
|
|
|
int64_t convStrToBoolHelper(const StringData* s) {
|
|
return s->toBoolean();
|
|
}
|
|
|
|
int64_t convCellToBoolHelper(TypedValue tv) {
|
|
// Cannot call tvCastToBooleanInPlace here because some of the
|
|
// call sites will not be increasing the ref count on tv before
|
|
// calling, the ref count must be left alone.
|
|
|
|
switch (tv.m_type) {
|
|
case KindOfUninit:
|
|
case KindOfNull: return false;
|
|
case KindOfBoolean: return tv.m_data.num;
|
|
case KindOfInt64: return tv.m_data.num != 0;
|
|
case KindOfDouble: return tv.m_data.dbl != 0;
|
|
case KindOfStaticString:
|
|
case KindOfString: return tv.m_data.pstr->toBoolean();
|
|
case KindOfArray: return !tv.m_data.parr->empty();
|
|
case KindOfObject: return tv.m_data.pobj != nullptr;
|
|
default: not_reached();
|
|
}
|
|
}
|
|
|
|
int64_t convArrToDblHelper(ArrayData* a) {
|
|
return reinterpretDblAsInt(a->empty() ? 0 : 1);
|
|
}
|
|
|
|
int64_t convStrToDblHelper(const StringData* s) {
|
|
return reinterpretDblAsInt(s->toDouble());
|
|
}
|
|
|
|
int64_t convCellToDblHelper(TypedValue tv) {
|
|
try {
|
|
tvCastToDoubleInPlace(&tv); // consumes a ref on counted values
|
|
// but not if an exception happens. (REVIEW)
|
|
return tv.m_data.num;
|
|
} catch (...) {
|
|
// spill tv back to stack. unwinder
|
|
// will take care of decreffing it.
|
|
VMRegAnchor _;
|
|
TypedValue* spillSlot = (TypedValue *)vmsp();
|
|
spillSlot->m_data.num = tv.m_data.num;
|
|
spillSlot->m_type = tv.m_type;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
int64_t convArrToIntHelper(ArrayData* a) {
|
|
return a->empty() ? 0 : 1;
|
|
}
|
|
|
|
int64_t convDblToIntHelper(int64_t i) {
|
|
double d = reinterpretIntAsDbl(i);
|
|
return (d >= 0 ? d > std::numeric_limits<uint64_t>::max() ? 0u :
|
|
(uint64_t)d : (int64_t)d);
|
|
}
|
|
|
|
int64_t convStrToIntHelper(const StringData* s) {
|
|
return s->toInt64(10);
|
|
}
|
|
|
|
int64_t convCellToIntHelper(TypedValue tv) {
|
|
try {
|
|
tvCastToInt64InPlace(&tv); // consumes a ref on counted values
|
|
// but not if an exception happens. (REVIEW)
|
|
return tv.m_data.num;
|
|
} catch (...) {
|
|
// spill tv back to stack. unwinder
|
|
// will take care of decreffing it.
|
|
VMRegAnchor _;
|
|
TypedValue* spillSlot = (TypedValue *)vmsp();
|
|
spillSlot->m_data.num = tv.m_data.num;
|
|
spillSlot->m_type = tv.m_type;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
ObjectData* convCellToObjHelper(TypedValue tv) {
|
|
// Note: the call sites of this function all assume that
|
|
// no user code will run and no recoverable exceptions will
|
|
// occur while running this code. This seems trivially true
|
|
// in all cases but converting arrays to objects. It also
|
|
// seems true for that case as well, since the source array
|
|
// is essentially metadata for the object. If that is not true,
|
|
// you might end up looking at this code in a debugger and now
|
|
// you know why.
|
|
tvCastToObjectInPlace(&tv); // consumes a ref on counted values
|
|
return tv.m_data.pobj;
|
|
}
|
|
|
|
StringData* convDblToStrHelper(int64_t i) {
|
|
double d = reinterpretIntAsDbl(i);
|
|
auto r = buildStringData(d);
|
|
r->incRefCount();
|
|
return r;
|
|
}
|
|
|
|
StringData* convIntToStrHelper(int64_t i) {
|
|
auto r = buildStringData(i);
|
|
r->incRefCount();
|
|
return r;
|
|
}
|
|
|
|
StringData* convObjToStrHelper(ObjectData* o) {
|
|
try {
|
|
auto s = o->t___tostring();
|
|
auto r = s.get();
|
|
decRefObj(o);
|
|
if (!r->isStatic()) r->incRefCount();
|
|
return r;
|
|
} catch (...) {
|
|
// spill object back to stack. unwinder
|
|
// will take care of decreffing it.
|
|
VMRegAnchor _;
|
|
TypedValue* spillSlot = (TypedValue *)vmsp();
|
|
spillSlot->m_data.pobj = o;
|
|
spillSlot->m_type = KindOfObject;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
StringData* convCellToStrHelper(TypedValue tv) {
|
|
switch (tv.m_type) {
|
|
case KindOfUninit:
|
|
case KindOfNull: return buildStringData("");
|
|
case KindOfBoolean: return buildStringData(tv.m_data.num ? "1" : "");
|
|
case KindOfInt64: return convIntToStrHelper(tv.m_data.num);
|
|
case KindOfDouble: return convDblToStrHelper(tv.m_data.num);
|
|
case KindOfStaticString:
|
|
case KindOfString: return tv.m_data.pstr;
|
|
case KindOfArray: tvDecRefArr(&tv); return buildStringData("Array");
|
|
case KindOfObject: return convObjToStrHelper(tv.m_data.pobj);
|
|
default: not_reached();
|
|
}
|
|
}
|
|
|
|
void raisePropertyOnNonObject() {
|
|
raise_warning("Cannot access property on non-object");
|
|
}
|
|
|
|
void raiseUndefProp(ObjectData* base, const StringData* name) {
|
|
static_cast<Instance*>(base)->raiseUndefProp(name);
|
|
}
|
|
|
|
void raise_error_sd(const StringData *msg) {
|
|
raise_error("%s", msg->data());
|
|
}
|
|
|
|
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,
|
|
const TypeConstraint* expected) {
|
|
if (LIKELY(constraint && cls->classof(constraint))) {
|
|
return;
|
|
}
|
|
|
|
// Check a typedef for a class. We interp'd if the param wasn't an
|
|
// object, so if it's a typedef for something non-objecty we're
|
|
// failing anyway.
|
|
if (auto namedEntity = expected->namedEntity()) {
|
|
auto def = namedEntity->getCachedTypedef();
|
|
if (UNLIKELY(!def)) {
|
|
VMRegAnchor _;
|
|
String nameStr(const_cast<StringData*>(expected->typeName()));
|
|
if (AutoloadHandler::s_instance->autoloadType(nameStr)) {
|
|
def = namedEntity->getCachedTypedef();
|
|
}
|
|
}
|
|
if (def) {
|
|
// There's no need to handle nullable typedefs specially here:
|
|
// we already know we're checking a non-null object with the
|
|
// class `cls'.
|
|
if (def->kind == KindOfObject) {
|
|
constraint = def->klass;
|
|
if (constraint && cls->classof(constraint)) return;
|
|
}
|
|
}
|
|
}
|
|
|
|
VerifyParamTypeFail(param);
|
|
}
|
|
|
|
template<bool useTargetCache>
|
|
RefData* staticLocInitImpl(StringData* name, ActRec* fp, TypedValue val,
|
|
TargetCache::CacheHandle ch) {
|
|
assert(useTargetCache == (bool)ch);
|
|
HphpArray* map;
|
|
if (useTargetCache) {
|
|
// If we have a cache handle, we know the current func isn't a
|
|
// closure or generator closure so we can directly grab its static
|
|
// locals map.
|
|
const Func* func = fp->m_func;
|
|
assert(!(func->isClosureBody() || func->isGeneratorFromClosure()));
|
|
map = func->getStaticLocals();
|
|
} else {
|
|
map = get_static_locals(fp);
|
|
}
|
|
|
|
TypedValue *mapVal = map->nvGet(name);
|
|
if (!mapVal) {
|
|
map->set(name, tvAsCVarRef(&val), false);
|
|
mapVal = map->nvGet(name);
|
|
}
|
|
if (mapVal->m_type != KindOfRef) {
|
|
tvBox(mapVal);
|
|
}
|
|
assert(mapVal->m_type == KindOfRef);
|
|
RefData* ret = mapVal->m_data.pref;
|
|
if (useTargetCache) {
|
|
*TargetCache::handleToPtr<RefData*>(ch) = ret;
|
|
}
|
|
ret->incRefCount();
|
|
return ret;
|
|
}
|
|
|
|
RefData* staticLocInit(StringData* name, ActRec* fp, TypedValue val) {
|
|
return staticLocInitImpl<false>(name, fp, val, 0);
|
|
}
|
|
|
|
RefData* staticLocInitCached(StringData* name, ActRec* fp, TypedValue val,
|
|
TargetCache::CacheHandle ch) {
|
|
return staticLocInitImpl<true>(name, fp, val, ch);
|
|
}
|
|
|
|
} }
|