/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010- 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace HPHP; /////////////////////////////////////////////////////////////////////////////// // constructors/destructors inline void UnaryOpExpression::ctorInit() { switch (m_op) { case T_INC: case T_DEC: m_localEffects = AssignEffect; m_exp->setContext(Expression::OprLValue); m_exp->setContext(Expression::DeepOprLValue); // this is hacky, what we need is LValueWrapper if (!m_exp->is(Expression::KindOfSimpleVariable)) { m_exp->setContext(Expression::LValue); m_exp->clearContext(Expression::NoLValueWrapper); } break; case T_PRINT: m_localEffects = IOEffect; break; case T_EXIT: case T_INCLUDE: case T_INCLUDE_ONCE: case T_REQUIRE: case T_REQUIRE_ONCE: case T_EVAL: case T_CLONE: m_localEffects = UnknownEffect; break; case T_ISSET: case T_EMPTY: setExistContext(); break; case T_UNSET: m_localEffects = AssignEffect; m_exp->setContext(UnsetContext); break; case T_CLASS: case T_FUNCTION: m_localEffects = CreateEffect; break; case T_ARRAY: default: break; } } void UnaryOpExpression::setDefinedScope(BlockScopeRawPtr scope) { always_assert(m_op == T_CLASS || m_op == T_FUNCTION); m_definedScope = scope; } UnaryOpExpression::UnaryOpExpression (EXPRESSION_CONSTRUCTOR_BASE_PARAMETERS, ExpressionPtr exp, int op, bool front) : Expression(EXPRESSION_CONSTRUCTOR_BASE_PARAMETER_VALUES), m_exp(exp), m_op(op), m_front(front) { ctorInit(); } UnaryOpExpression::UnaryOpExpression (EXPRESSION_CONSTRUCTOR_PARAMETERS, ExpressionPtr exp, int op, bool front) : Expression(EXPRESSION_CONSTRUCTOR_PARAMETER_VALUES(UnaryOpExpression)), m_exp(exp), m_op(op), m_front(front) { ctorInit(); } ExpressionPtr UnaryOpExpression::clone() { UnaryOpExpressionPtr exp(new UnaryOpExpression(*this)); Expression::deepCopy(exp); exp->m_exp = Clone(m_exp); return exp; } bool UnaryOpExpression::isTemporary() const { switch (m_op) { case '!': case '+': case '-': case '~': case T_ARRAY: return true; } return false; } bool UnaryOpExpression::isScalar() const { switch (m_op) { case '!': case '+': case '-': case '~': case '@': return m_exp->isScalar(); case T_ARRAY: return (!m_exp || m_exp->isScalar()); default: break; } return false; } bool UnaryOpExpression::isCast() const { switch (m_op) { case T_INT_CAST: case T_DOUBLE_CAST: case T_STRING_CAST: case T_ARRAY_CAST: case T_OBJECT_CAST: case T_BOOL_CAST: return true; default: break; } return false; } TypePtr UnaryOpExpression::getCastType() const { switch (m_op) { case T_INT_CAST: return Type::Int64; case T_DOUBLE_CAST: return Type::Double; case T_STRING_CAST: return Type::String; case T_ARRAY_CAST: return Type::Array; case T_OBJECT_CAST: return Type::Object; case T_BOOL_CAST: return Type::Boolean; default: break; } return TypePtr(); } bool UnaryOpExpression::isRefable(bool checkError /*= false */) const { if (m_op == T_INC || m_op == T_DEC) { return m_exp->isRefable(checkError); } return false; } bool UnaryOpExpression::isThis() const { return false; } bool UnaryOpExpression::containsDynamicConstant(AnalysisResultPtr ar) const { switch (m_op) { case '+': case '-': case T_ARRAY: return m_exp && m_exp->containsDynamicConstant(ar); default: break; } return false; } bool UnaryOpExpression::getScalarValue(Variant &value) { if (m_exp) { if (m_op == T_ARRAY) { return m_exp->getScalarValue(value); } Variant t; return m_exp->getScalarValue(t) && preCompute(t, value); } if (m_op != T_ARRAY) return false; value = Array::Create(); return true; } /////////////////////////////////////////////////////////////////////////////// // parser functions void UnaryOpExpression::onParse(AnalysisResultConstPtr ar, FileScopePtr scope) { if (m_op == T_EVAL) { ConstructPtr self = shared_from_this(); Compiler::Error(Compiler::UseEvaluation, self); scope->setAttribute(FileScope::ContainsLDynamicVariable); } } /////////////////////////////////////////////////////////////////////////////// // static analysis functions void UnaryOpExpression::analyzeProgram(AnalysisResultPtr ar) { if (m_exp) m_exp->analyzeProgram(ar); if ((m_op == T_CLASS || m_op == T_FUNCTION) && ar->getPhase() == AnalysisResult::AnalyzeFinal) { ar->link(getFileScope(), m_definedScope->getContainingFile()); } } bool UnaryOpExpression::preCompute(CVarRef value, Variant &result) { bool ret = true; try { g_context->setThrowAllErrors(true); switch(m_op) { case '!': result = (!toBoolean(value)); break; case '+': result = value.unary_plus(); break; case '-': result = value.negate(); break; case '~': result = ~value; break; case '@': result = value; break; case T_INT_CAST: result = value.toInt64(); break; case T_DOUBLE_CAST: result = toDouble(value); break; case T_STRING_CAST: result = toString(value); break; case T_BOOL_CAST: result = toBoolean(value); break; case T_EMPTY: result = empty(value); break; case T_ISSET: result = isset(value); break; case T_INC: case T_DEC: assert(false); default: ret = false; break; } } catch (...) { ret = false; } g_context->setThrowAllErrors(false); return ret; } ConstructPtr UnaryOpExpression::getNthKid(int n) const { switch (n) { case 0: return m_exp; default: assert(false); break; } return ConstructPtr(); } int UnaryOpExpression::getKidCount() const { return 1; } void UnaryOpExpression::setNthKid(int n, ConstructPtr cp) { switch (n) { case 0: m_exp = boost::dynamic_pointer_cast(cp); break; default: assert(false); break; } } bool UnaryOpExpression::canonCompare(ExpressionPtr e) const { if (!Expression::canonCompare(e)) return false; UnaryOpExpressionPtr u = static_pointer_cast(e); return m_op == u->m_op && m_front == u->m_front; } ExpressionPtr UnaryOpExpression::preOptimize(AnalysisResultConstPtr ar) { Variant value; Variant result; if (m_exp && ar->getPhase() >= AnalysisResult::FirstPreOptimize) { if (m_op == T_UNSET) { if (m_exp->isScalar() || (m_exp->is(KindOfExpressionList) && static_pointer_cast(m_exp)->getCount() == 0)) { recomputeEffects(); return CONSTANT("null"); } return ExpressionPtr(); } } if (m_op == T_ISSET && m_exp->is(KindOfExpressionList) && static_pointer_cast(m_exp)->getListKind() == ExpressionList::ListKindParam) { ExpressionListPtr el(static_pointer_cast(m_exp)); result = true; int i = 0, n = el->getCount(); for (; i < n; i++) { ExpressionPtr e((*el)[i]); if (!e || !e->isScalar() || !e->getScalarValue(value)) break; if (!isset(value)) { result = false; } } if (i == n) { return replaceValue(makeScalarExpression(ar, result)); } } else if (m_op != T_ARRAY && m_exp && m_exp->isScalar() && m_exp->getScalarValue(value) && preCompute(value, result)) { return replaceValue(makeScalarExpression(ar, result)); } else if (m_op == T_BOOL_CAST) { switch (m_exp->getKindOf()) { default: break; case KindOfBinaryOpExpression: { int op = static_pointer_cast(m_exp)->getOp(); switch (op) { case T_LOGICAL_OR: case T_BOOLEAN_OR: case T_LOGICAL_AND: case T_BOOLEAN_AND: case T_LOGICAL_XOR: case T_INSTANCEOF: case '<': case T_IS_SMALLER_OR_EQUAL: case '>': case T_IS_GREATER_OR_EQUAL: case T_IS_IDENTICAL: case T_IS_NOT_IDENTICAL: case T_IS_EQUAL: case T_IS_NOT_EQUAL: return m_exp; } break; } case KindOfUnaryOpExpression: { int op = static_pointer_cast(m_exp)->getOp(); switch (op) { case T_BOOL_CAST: case '!': case T_ISSET: case T_EMPTY: case T_PRINT: return m_exp; } break; } } } return ExpressionPtr(); } ExpressionPtr UnaryOpExpression::postOptimize(AnalysisResultConstPtr ar) { if (m_op == T_PRINT && m_exp->is(KindOfEncapsListExpression) && !m_exp->hasEffect()) { EncapsListExpressionPtr e = static_pointer_cast (m_exp); e->stripConcat(); } if (m_op == T_UNSET_CAST && !hasEffect()) { if (!getScope()->getVariables()-> getAttribute(VariableTable::ContainsCompact) || !m_exp->isScalar()) { return CONSTANT("null"); } } else if (m_op == T_UNSET && m_exp->is(KindOfExpressionList) && !static_pointer_cast(m_exp)->getCount()) { recomputeEffects(); return CONSTANT("null"); } else if (m_op == T_BOOL_CAST) { if (m_exp->getActualType() && m_exp->getActualType()->is(Type::KindOfBoolean)) { return replaceValue(m_exp); } } else if (m_op != T_ARRAY && m_exp && m_exp->isScalar()) { Variant value; Variant result; if (m_exp->getScalarValue(value) && preCompute(value, result)) { return replaceValue(makeScalarExpression(ar, result)); } } return ExpressionPtr(); } void UnaryOpExpression::setExistContext() { if (m_exp) { if (m_exp->is(Expression::KindOfExpressionList)) { ExpressionListPtr exps = dynamic_pointer_cast(m_exp); if (exps->getListKind() == ExpressionList::ListKindParam) { for (int i = 0; i < exps->getCount(); i++) { (*exps)[i]->setContext(Expression::ExistContext); } return; } } m_exp->setContext(Expression::ExistContext); } } TypePtr UnaryOpExpression::inferTypes(AnalysisResultPtr ar, TypePtr type, bool coerce) { TypePtr et; // expected m_exp's type TypePtr rt; // return type switch (m_op) { case '!': et = rt = Type::Boolean; break; case '+': case '-': et = Type::Numeric; rt = Type::Numeric; break; case T_INC: case T_DEC: case '~': et = rt = Type::Primitive; break; case T_CLONE: et = Type::Some; rt = Type::Object; break; case '@': et = type; rt = Type::Variant; break; case T_INT_CAST: et = rt = Type::Int64; break; case T_DOUBLE_CAST: et = rt = Type::Double; break; case T_STRING_CAST: et = rt = Type::String; break; case T_ARRAY: et = Type::Some; rt = Type::Array; break; case T_ARRAY_CAST: et = rt = Type::Array; break; case T_OBJECT_CAST: et = rt = Type::Object; break; case T_BOOL_CAST: et = rt = Type::Boolean; break; case T_UNSET_CAST: et = Type::Some; rt = Type::Variant; break; case T_UNSET: et = Type::Null; rt = Type::Variant; break; case T_EXIT: et = Type::Primitive; rt = Type::Variant; break; case T_PRINT: et = Type::String; rt = Type::Int64; break; case T_ISSET: et = Type::Variant; rt = Type::Boolean; setExistContext(); break; case T_EMPTY: et = Type::Some; rt = Type::Boolean; setExistContext(); break; case T_INCLUDE: case T_INCLUDE_ONCE: case T_REQUIRE: case T_REQUIRE_ONCE: et = Type::String; rt = Type::Variant; break; case T_EVAL: et = Type::String; rt = Type::Any; getScope()->getVariables()->forceVariants(ar, VariableTable::AnyVars); break; case T_DIR: case T_FILE: et = rt = Type::String; break; default: assert(false); } if (m_exp) { TypePtr expType = m_exp->inferAndCheck(ar, et, false); if (Type::SameType(expType, Type::String) && (m_op == T_INC || m_op == T_DEC)) { rt = expType = m_exp->inferAndCheck(ar, Type::Variant, true); } switch (m_op) { case '+': case '-': if (Type::SameType(expType, Type::Int64) || Type::SameType(expType, Type::Double)) { rt = expType; } break; case T_INC: case T_DEC: case '~': if (Type::SameType(expType, Type::Int64) || Type::SameType(expType, Type::Double) || Type::SameType(expType, Type::String)) { rt = expType; } break; case T_ISSET: case T_EMPTY: if (m_exp->is(Expression::KindOfExpressionList)) { ExpressionListPtr exps = dynamic_pointer_cast(m_exp); if (exps->getListKind() == ExpressionList::ListKindParam) { for (int i = 0; i < exps->getCount(); i++) { SetExpTypeForExistsContext(ar, (*exps)[i], m_op == T_EMPTY); } } } else { SetExpTypeForExistsContext(ar, m_exp, m_op == T_EMPTY); } break; default: break; } } return rt; } void UnaryOpExpression::SetExpTypeForExistsContext(AnalysisResultPtr ar, ExpressionPtr e, bool allowPrimitives) { if (!e) return; TypePtr at(e->getActualType()); if (!allowPrimitives && at && at->isExactType() && at->isPrimitive()) { at = e->inferAndCheck(ar, Type::Variant, true); } TypePtr it(e->getImplementedType()); TypePtr et(e->getExpectedType()); if (et && et->is(Type::KindOfVoid)) e->setExpectedType(TypePtr()); if (at && (!it || Type::IsMappedToVariant(it)) && ((allowPrimitives && Type::HasFastCastMethod(at)) || (!allowPrimitives && (at->is(Type::KindOfObject) || at->is(Type::KindOfArray) || at->is(Type::KindOfString))))) { e->setExpectedType(it ? at : TypePtr()); } } ExpressionPtr UnaryOpExpression::unneededHelper() { if ((m_op != '@' && m_op != T_ISSET && m_op != T_EMPTY) || !m_exp->getContainedEffects()) { return Expression::unneededHelper(); } if (m_op == '@') { m_exp = m_exp->unneeded(); } return static_pointer_cast(shared_from_this()); } /////////////////////////////////////////////////////////////////////////////// // code generation functions void UnaryOpExpression::outputPHP(CodeGenerator &cg, AnalysisResultPtr ar) { if (m_front) { switch (m_op) { case T_CLONE: cg_printf("clone "); break; case T_INC: cg_printf("++"); break; case T_DEC: cg_printf("--"); break; case '+': cg_printf("+"); break; case '-': cg_printf("-"); break; case '!': cg_printf("!"); break; case '~': cg_printf("~"); break; case T_INT_CAST: cg_printf("(int)"); break; case T_DOUBLE_CAST: cg_printf("(double)"); break; case T_STRING_CAST: cg_printf("(string)"); break; case T_ARRAY_CAST: cg_printf("(array)"); break; case T_OBJECT_CAST: cg_printf("(object)"); break; case T_BOOL_CAST: cg_printf("(bool)"); break; case T_UNSET: cg_printf("unset("); break; case T_UNSET_CAST: cg_printf("(unset)"); break; case T_EXIT: cg_printf("exit("); break; case '@': cg_printf("@"); break; case T_ARRAY: cg_printf("array("); break; case T_PRINT: cg_printf("print "); break; case T_ISSET: cg_printf("isset("); break; case T_EMPTY: cg_printf("empty("); break; case T_INCLUDE: cg_printf("include "); break; case T_INCLUDE_ONCE: cg_printf("include_once "); break; case T_EVAL: cg_printf("eval("); break; case T_REQUIRE: cg_printf("require "); break; case T_REQUIRE_ONCE: cg_printf("require_once "); break; case T_FILE: cg_printf("__FILE__"); break; case T_DIR: cg_printf("__DIR__"); break; case T_CLASS: cg_printf("class "); break; case T_FUNCTION: cg_printf("function "); break; default: assert(false); } } if (m_exp) m_exp->outputPHP(cg, ar); if (m_front) { switch (m_op) { case T_UNSET: case T_EXIT: case T_ARRAY: case T_ISSET: case T_EMPTY: case T_EVAL: cg_printf(")"); break; default: break; } } else { switch (m_op) { case T_INC: cg_printf("++"); break; case T_DEC: cg_printf("--"); break; default: assert(false); } } }