/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-2013 Facebook, Inc. (http://www.facebook.com) | | Copyright (c) 1998-2010 Zend Technologies Ltd. (http://www.zend.com) | +----------------------------------------------------------------------+ | This source file is subject to version 2.00 of the Zend 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.zend.com/license/2_00.txt. | | If you did not receive a copy of the Zend license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@zend.com so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/base/tv_comparisons.h" #include #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 bool cellRelOp(Op op, Cell cell, bool val) { return op(cellToBool(cell), val); } template 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 num = stringToNumeric(cell.m_data.pstr); return num.m_type == KindOfInt64 ? op(num.m_data.num, val) : num.m_type == KindOfDouble ? op(num.m_data.dbl, val) : op(0, val); } default: break; } not_reached(); } template 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 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: { auto const num = stringToNumeric(val); return num.m_type == KindOfInt64 ? op(cell.m_data.num, num.m_data.num) : num.m_type == KindOfDouble ? op(cell.m_data.num, num.m_data.dbl) : 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(od)->t___tostring()); return op(str.get(), val); } catch (BadTypeConversionException&) { return op(true, false); } } default: break; } not_reached(); } template 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 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(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 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 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 typename std::enable_if< !std::is_pointer::value && !std::is_pointer::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(od1), const_cast(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 typename std::enable_if< !std::is_pointer::value && !std::is_pointer::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 typename std::enable_if< !std::is_pointer::value && !std::is_pointer::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); } ////////////////////////////////////////////////////////////////////// }