5371743d9d
If we're not going to mutate the Cell, it might make sense to pass it by value rather than pointer to const. Do folks like this better? I can see a couple arguments various ways. But it does seem like even if we want to pass it by pointer at the hardware level we would ideally passing by const reference at the language level, so this choice would be transparent at callsite code. This diff doesn't change anything in tv_helpers.h for now.
549 linhas
15 KiB
C++
549 linhas
15 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/base/tv_comparisons.h"
|
|
|
|
#include <type_traits>
|
|
|
|
#include "hphp/runtime/base/tv_conversions.h"
|
|
#include "hphp/runtime/base/comparisons.h"
|
|
#include "hphp/runtime/base/type_conversions.h"
|
|
|
|
namespace HPHP {
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
extern bool collectionEquals(ObjectData*, ObjectData*);
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
namespace {
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* Family of relative op functions.
|
|
*
|
|
* These are used to implement the common parts of the php operators
|
|
* ==, <, and >. They handle some of the php behavior with regard to
|
|
* numeric-ish strings, and delegate to the 'op' functor to perform
|
|
* the actual comparison on primitive types, and between complex php
|
|
* types of the same type.
|
|
*
|
|
* See below for the implementations of the Op template parameter.
|
|
*/
|
|
|
|
template<class Op>
|
|
bool cellRelOp(Op op, Cell cell, bool val) {
|
|
return op(cellToBool(cell), val);
|
|
}
|
|
|
|
template<class Op>
|
|
bool cellRelOp(Op op, Cell cell, int64_t val) {
|
|
assert(cellIsPlausible(&cell));
|
|
|
|
switch (cell.m_type) {
|
|
case KindOfUninit:
|
|
case KindOfNull: return op(false, !!val);
|
|
case KindOfBoolean: return op(!!cell.m_data.num, val != 0);
|
|
case KindOfInt64: return op(cell.m_data.num, val);
|
|
case KindOfDouble: return op(cell.m_data.dbl, val);
|
|
case KindOfArray: return op(true, false);
|
|
|
|
case KindOfObject:
|
|
return cell.m_data.pobj->isCollection()
|
|
? op.collectionVsNonObj()
|
|
: op(cell.m_data.pobj->o_toInt64(), val);
|
|
|
|
case KindOfStaticString:
|
|
case KindOfString:
|
|
{
|
|
auto const sdata = cell.m_data.pstr;
|
|
int64_t ival;
|
|
double dval;
|
|
auto const dt = sdata->isNumericWithVal(ival, dval,
|
|
/* allow_error */ true);
|
|
return dt == KindOfInt64 ? op(ival, val) :
|
|
dt == KindOfDouble ? op(dval, val) :
|
|
op(0, val);
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
not_reached();
|
|
}
|
|
|
|
template<class Op>
|
|
bool cellRelOp(Op op, Cell cell, double val) {
|
|
assert(cellIsPlausible(&cell));
|
|
|
|
switch (cell.m_type) {
|
|
case KindOfUninit:
|
|
case KindOfNull: return op(false, val != 0);
|
|
case KindOfBoolean: return op(!!cell.m_data.num, val != 0);
|
|
case KindOfInt64: return op(cell.m_data.num, val);
|
|
case KindOfDouble: return op(cell.m_data.dbl, val);
|
|
case KindOfArray: return op(true, false);
|
|
|
|
case KindOfObject:
|
|
return cell.m_data.pobj->isCollection()
|
|
? op.collectionVsNonObj()
|
|
: op(cell.m_data.pobj->o_toDouble(), val);
|
|
|
|
case KindOfStaticString:
|
|
case KindOfString:
|
|
return op(toDouble(cell.m_data.pstr), val);
|
|
|
|
default:
|
|
break;
|
|
}
|
|
not_reached();
|
|
}
|
|
|
|
template<class Op>
|
|
bool cellRelOp(Op op, Cell cell, const StringData* val) {
|
|
assert(cellIsPlausible(&cell));
|
|
|
|
switch (cell.m_type) {
|
|
case KindOfUninit:
|
|
case KindOfNull: return op(empty_string.get(), val);
|
|
case KindOfBoolean: return op(!!cell.m_data.num, toBoolean(val));
|
|
case KindOfDouble: return op(cell.m_data.dbl, val->toDouble());
|
|
case KindOfArray: return op(true, false);
|
|
case KindOfString:
|
|
case KindOfStaticString: return op(cell.m_data.pstr, val);
|
|
|
|
case KindOfInt64:
|
|
{
|
|
int64_t ival;
|
|
double dval;
|
|
auto const dt = val->isNumericWithVal(ival, dval,
|
|
/* allow_error */ true);
|
|
return dt == KindOfInt64 ? op(cell.m_data.num, ival) :
|
|
dt == KindOfDouble ? op(cell.m_data.num, dval) :
|
|
op(cell.m_data.num, 0);
|
|
}
|
|
|
|
case KindOfObject:
|
|
{
|
|
auto const od = cell.m_data.pobj;
|
|
if (od->isResource()) return op(od->o_toDouble(), val->toDouble());
|
|
if (od->isCollection()) return op.collectionVsNonObj();
|
|
try {
|
|
String str(const_cast<ObjectData*>(od)->t___tostring());
|
|
return op(str.get(), val);
|
|
} catch (BadTypeConversionException&) {
|
|
return op(true, false);
|
|
}
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
not_reached();
|
|
}
|
|
|
|
template<class Op>
|
|
bool cellRelOp(Op op, Cell cell, const ArrayData* ad) {
|
|
assert(cellIsPlausible(&cell));
|
|
|
|
switch (cell.m_type) {
|
|
case KindOfUninit:
|
|
case KindOfNull: return op(false, !ad->empty());
|
|
case KindOfBoolean: return op(cell.m_data.num, !ad->empty());
|
|
case KindOfInt64: return op(false, true);
|
|
case KindOfDouble: return op(false, true);
|
|
case KindOfArray: return op(cell.m_data.parr, ad);
|
|
case KindOfStaticString:
|
|
case KindOfString: return op(false, true);
|
|
case KindOfObject:
|
|
{
|
|
auto const od = cell.m_data.pobj;
|
|
if (od->isResource()) return op(false, true);
|
|
return od->isCollection()
|
|
? op.collectionVsNonObj()
|
|
: op(true, false);
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
not_reached();
|
|
}
|
|
|
|
template<class Op>
|
|
bool cellRelOp(Op op, Cell cell, const ObjectData* od) {
|
|
assert(cellIsPlausible(&cell));
|
|
|
|
switch (cell.m_type) {
|
|
case KindOfUninit:
|
|
case KindOfNull: // TODO: should use o_toBoolean
|
|
return op(false, true);
|
|
case KindOfBoolean: return op(!!cell.m_data.num, true);
|
|
case KindOfInt64:
|
|
return od->isCollection() ? op.collectionVsNonObj()
|
|
: op(cell.m_data.num, od->o_toInt64());
|
|
case KindOfDouble:
|
|
return od->isCollection() ? op.collectionVsNonObj()
|
|
: op(cell.m_data.dbl, od->o_toDouble());
|
|
case KindOfArray:
|
|
if (od->isResource()) return op(true, false);
|
|
return od->isCollection() ? op.collectionVsNonObj() : op(false, true);
|
|
case KindOfString:
|
|
case KindOfStaticString:
|
|
if (od->isResource()) return op(cell.m_data.pstr->toDouble(),
|
|
od->o_toDouble());
|
|
if (od->isCollection()) return op.collectionVsNonObj();
|
|
try {
|
|
String str(const_cast<ObjectData*>(od)->t___tostring());
|
|
return op(cell.m_data.pstr, str.get());
|
|
} catch (BadTypeConversionException&) {
|
|
return op(false, true);
|
|
}
|
|
case KindOfObject:
|
|
return op(cell.m_data.pobj, od);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
not_reached();
|
|
}
|
|
|
|
template<class Op>
|
|
bool cellRelOp(Op op, Cell c1, Cell c2) {
|
|
assert(cellIsPlausible(&c1));
|
|
assert(cellIsPlausible(&c2));
|
|
|
|
switch (c2.m_type) {
|
|
case KindOfUninit:
|
|
case KindOfNull:
|
|
return IS_STRING_TYPE(c1.m_type)
|
|
? op(c1.m_data.pstr, empty_string.get())
|
|
: cellRelOp(op, c1, false);
|
|
case KindOfInt64: return cellRelOp(op, c1, c2.m_data.num);
|
|
case KindOfBoolean: return cellRelOp(op, c1, !!c2.m_data.num);
|
|
case KindOfDouble: return cellRelOp(op, c1, c2.m_data.dbl);
|
|
case KindOfStaticString:
|
|
case KindOfString: return cellRelOp(op, c1, c2.m_data.pstr);
|
|
case KindOfArray: return cellRelOp(op, c1, c2.m_data.parr);
|
|
case KindOfObject: return cellRelOp(op, c1, c2.m_data.pobj);
|
|
default:
|
|
break;
|
|
}
|
|
not_reached();
|
|
}
|
|
|
|
template<class Op>
|
|
bool tvRelOp(Op op, TypedValue tv1, TypedValue tv2) {
|
|
assert(tvIsPlausible(&tv1));
|
|
assert(tvIsPlausible(&tv2));
|
|
return cellRelOp(op, *tvToCell(&tv1), *tvToCell(&tv2));
|
|
}
|
|
|
|
/*
|
|
* These relative ops helper function objects define operator() for
|
|
* each primitive type, and for the case of a complex type being
|
|
* compared with itself (that is obj with obj, string with string,
|
|
* array with array).
|
|
*
|
|
* They must also define a function called collectionVsNonObj() which
|
|
* is used when comparing collections with non-object types. (The obj
|
|
* vs obj function should handle the collection vs collection and
|
|
* collection vs non-collection object cases.) This is just to handle
|
|
* that php operator == returns false in these cases, while the Lt/Gt
|
|
* operators throw and exception.
|
|
*/
|
|
|
|
struct Eq {
|
|
template<class T, class U>
|
|
typename std::enable_if<
|
|
!std::is_pointer<T>::value &&
|
|
!std::is_pointer<U>::value,
|
|
bool
|
|
>::type operator()(T t, U u) const { return t == u; }
|
|
|
|
bool operator()(const StringData* sd1, const StringData* sd2) const {
|
|
return sd1->equal(sd2);
|
|
}
|
|
|
|
bool operator()(const ArrayData* ad1, const ArrayData* ad2) const {
|
|
return ad1->equal(ad2, false);
|
|
}
|
|
|
|
bool operator()(const ObjectData* od1, const ObjectData* od2) const {
|
|
if (od1 == od2) return true;
|
|
if (od1->isResource() || od2->isResource()) return false;
|
|
if (od1->getVMClass() != od2->getVMClass()) return false;
|
|
if (od1->isCollection()) {
|
|
// TODO constness
|
|
return collectionEquals(const_cast<ObjectData*>(od1),
|
|
const_cast<ObjectData*>(od2));
|
|
}
|
|
Array ar1(od1->o_toArray());
|
|
Array ar2(od2->o_toArray());
|
|
return ar1->equal(ar2.get(), false);
|
|
}
|
|
|
|
bool collectionVsNonObj() const { return false; }
|
|
};
|
|
|
|
struct Lt {
|
|
template<class T, class U>
|
|
typename std::enable_if<
|
|
!std::is_pointer<T>::value &&
|
|
!std::is_pointer<U>::value,
|
|
bool
|
|
>::type operator()(T t, U u) const { return t < u; }
|
|
|
|
bool operator()(const StringData* sd1, const StringData* sd2) const {
|
|
return sd1->compare(sd2) < 0;
|
|
}
|
|
|
|
bool operator()(const ArrayData* ad1, const ArrayData* ad2) const {
|
|
return ad1->compare(ad2) < 0;
|
|
}
|
|
|
|
bool operator()(const ObjectData* od1, const ObjectData* od2) const {
|
|
if (od1->isCollection() || od2->isCollection()) {
|
|
throw_collection_compare_exception();
|
|
}
|
|
if (od1 == od2) return false;
|
|
Array ar1(od1->o_toArray());
|
|
Array ar2(od2->o_toArray());
|
|
return (*this)(ar1.get(), ar2.get());
|
|
}
|
|
|
|
bool collectionVsNonObj() const {
|
|
throw_collection_compare_exception();
|
|
not_reached();
|
|
}
|
|
};
|
|
|
|
struct Gt {
|
|
template<class T, class U>
|
|
typename std::enable_if<
|
|
!std::is_pointer<T>::value &&
|
|
!std::is_pointer<U>::value,
|
|
bool
|
|
>::type operator()(T t, U u) const { return t > u; }
|
|
|
|
bool operator()(const StringData* sd1, const StringData* sd2) const {
|
|
return sd1->compare(sd2) > 0;
|
|
}
|
|
|
|
bool operator()(const ArrayData* ad1, const ArrayData* ad2) const {
|
|
return 0 > ad2->compare(ad1); // Not symmetric; order matters here.
|
|
}
|
|
|
|
bool operator()(const ObjectData* od1, const ObjectData* od2) const {
|
|
if (od1->isCollection() || od2->isCollection()) {
|
|
throw_collection_compare_exception();
|
|
}
|
|
if (od1 == od2) return false;
|
|
Array ar1(od1->o_toArray());
|
|
Array ar2(od2->o_toArray());
|
|
return (*this)(ar1.get(), ar2.get());
|
|
}
|
|
|
|
bool collectionVsNonObj() const {
|
|
throw_collection_compare_exception();
|
|
not_reached();
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
}
|
|
|
|
bool cellSame(Cell c1, Cell c2) {
|
|
assert(cellIsPlausible(&c1));
|
|
assert(cellIsPlausible(&c2));
|
|
|
|
bool const null1 = IS_NULL_TYPE(c1.m_type);
|
|
bool const null2 = IS_NULL_TYPE(c2.m_type);
|
|
if (null1 && null2) return true;
|
|
if (null1 || null2) return false;
|
|
|
|
switch (c1.m_type) {
|
|
case KindOfInt64:
|
|
case KindOfBoolean:
|
|
if (c2.m_type != c1.m_type) return false;
|
|
return c1.m_data.num == c2.m_data.num;
|
|
case KindOfDouble:
|
|
if (c2.m_type != c1.m_type) return false;
|
|
return c1.m_data.dbl == c2.m_data.dbl;
|
|
|
|
case KindOfStaticString:
|
|
case KindOfString:
|
|
if (!IS_STRING_TYPE(c2.m_type)) return false;
|
|
return c1.m_data.pstr->same(c2.m_data.pstr);
|
|
|
|
case KindOfArray:
|
|
if (c2.m_type != KindOfArray) return false;
|
|
return c1.m_data.parr->equal(c2.m_data.parr, true);
|
|
|
|
case KindOfObject:
|
|
return c2.m_type == KindOfObject &&
|
|
c1.m_data.pobj == c2.m_data.pobj;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
not_reached();
|
|
}
|
|
|
|
bool tvSame(TypedValue tv1, TypedValue tv2) {
|
|
assert(tvIsPlausible(&tv1));
|
|
assert(tvIsPlausible(&tv2));
|
|
return cellSame(*tvToCell(&tv1), *tvToCell(&tv2));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
* XXX: HOT_FUNC selections are basically whatever random choices were
|
|
* in the old code ... we should probably re-evaluate this.
|
|
*/
|
|
|
|
bool cellEqual(Cell cell, bool val) {
|
|
return cellRelOp(Eq(), cell, val);
|
|
}
|
|
|
|
HOT_FUNC
|
|
bool cellEqual(Cell cell, int64_t val) {
|
|
return cellRelOp(Eq(), cell, val);
|
|
}
|
|
|
|
bool cellEqual(Cell cell, double val) {
|
|
return cellRelOp(Eq(), cell, val);
|
|
}
|
|
|
|
bool cellEqual(Cell cell, const StringData* val) {
|
|
return cellRelOp(Eq(), cell, val);
|
|
}
|
|
|
|
bool cellEqual(Cell cell, const ArrayData* val) {
|
|
return cellRelOp(Eq(), cell, val);
|
|
}
|
|
|
|
bool cellEqual(Cell cell, const ObjectData* val) {
|
|
return cellRelOp(Eq(), cell, val);
|
|
}
|
|
|
|
bool cellEqual(Cell c1, Cell c2) {
|
|
return cellRelOp(Eq(), c1, c2);
|
|
}
|
|
|
|
HOT_FUNC
|
|
bool tvEqual(TypedValue tv1, TypedValue tv2) {
|
|
return tvRelOp(Eq(), tv1, tv2);
|
|
}
|
|
|
|
bool cellLess(Cell cell, bool val) {
|
|
return cellRelOp(Lt(), cell, val);
|
|
}
|
|
|
|
bool cellLess(Cell cell, int64_t val) {
|
|
return cellRelOp(Lt(), cell, val);
|
|
}
|
|
|
|
bool cellLess(Cell cell, double val) {
|
|
return cellRelOp(Lt(), cell, val);
|
|
}
|
|
|
|
bool cellLess(Cell cell, const StringData* val) {
|
|
return cellRelOp(Lt(), cell, val);
|
|
}
|
|
|
|
bool cellLess(Cell cell, const ArrayData* val) {
|
|
return cellRelOp(Lt(), cell, val);
|
|
}
|
|
|
|
bool cellLess(Cell cell, const ObjectData* val) {
|
|
return cellRelOp(Lt(), cell, val);
|
|
}
|
|
|
|
bool cellLess(Cell c1, Cell c2) {
|
|
return cellRelOp(Lt(), c1, c2);
|
|
}
|
|
|
|
HOT_FUNC
|
|
bool tvLess(TypedValue tv1, TypedValue tv2) {
|
|
return tvRelOp(Lt(), tv1, tv2);
|
|
}
|
|
|
|
bool cellGreater(Cell cell, bool val) {
|
|
return cellRelOp(Gt(), cell, val);
|
|
}
|
|
|
|
//NB: was HOT_FUNC in old code ... dunno if this makes sense anymore.
|
|
bool cellGreater(Cell cell, int64_t val) {
|
|
return cellRelOp(Gt(), cell, val);
|
|
}
|
|
|
|
bool cellGreater(Cell cell, double val) {
|
|
return cellRelOp(Gt(), cell, val);
|
|
}
|
|
|
|
bool cellGreater(Cell cell, const StringData* val) {
|
|
return cellRelOp(Gt(), cell, val);
|
|
}
|
|
|
|
bool cellGreater(Cell cell, const ArrayData* val) {
|
|
return cellRelOp(Gt(), cell, val);
|
|
}
|
|
|
|
bool cellGreater(Cell cell, const ObjectData* val) {
|
|
return cellRelOp(Gt(), cell, val);
|
|
}
|
|
|
|
bool cellGreater(Cell c1, Cell c2) {
|
|
return cellRelOp(Gt(), c1, c2);
|
|
}
|
|
|
|
bool tvGreater(TypedValue tv1, TypedValue tv2) {
|
|
return tvRelOp(Gt(), tv1, tv2);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
bool cellLessOrEqual(Cell c1, Cell c2) {
|
|
assert(cellIsPlausible(&c1));
|
|
assert(cellIsPlausible(&c2));
|
|
|
|
if ((c1.m_type == KindOfArray && c2.m_type == KindOfArray) ||
|
|
(c1.m_type == KindOfObject && c2.m_type == KindOfObject)) {
|
|
return cellLess(c1, c2) || cellEqual(c1, c2);
|
|
}
|
|
return !cellGreater(c1, c2);
|
|
}
|
|
|
|
bool cellGreaterOrEqual(Cell c1, Cell c2) {
|
|
assert(cellIsPlausible(&c1));
|
|
assert(cellIsPlausible(&c2));
|
|
|
|
if ((c1.m_type == KindOfArray && c2.m_type == KindOfArray) ||
|
|
(c1.m_type == KindOfObject && c2.m_type == KindOfObject)) {
|
|
return cellGreater(c1, c2) || cellEqual(c1, c2);
|
|
}
|
|
return !cellLess(c1, c2);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
}
|
|
|