d39bf0bcfe
Zend fatals if the type is not string, where hphp would coerce to string. In RepoAuthoritative mode, this meant that scalar expressions would be converted to strings at bytecode emission time, bypassing the fatal (or, in most cases, resulting in a different fatal).
299 linhas
9.5 KiB
C++
299 linhas
9.5 KiB
C++
/*
|
|
+----------------------------------------------------------------------+
|
|
| 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 <compiler/expression/object_method_expression.h>
|
|
#include <compiler/expression/scalar_expression.h>
|
|
#include <compiler/expression/expression_list.h>
|
|
#include <compiler/analysis/code_error.h>
|
|
#include <compiler/analysis/class_scope.h>
|
|
#include <compiler/analysis/file_scope.h>
|
|
#include <compiler/analysis/function_scope.h>
|
|
#include <compiler/statement/statement.h>
|
|
#include <util/util.h>
|
|
#include <util/hash.h>
|
|
#include <compiler/option.h>
|
|
#include <compiler/expression/simple_variable.h>
|
|
#include <compiler/analysis/variable_table.h>
|
|
#include <compiler/parser/parser.h>
|
|
|
|
using namespace HPHP;
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// constructors/destructors
|
|
|
|
ObjectMethodExpression::ObjectMethodExpression
|
|
(EXPRESSION_CONSTRUCTOR_PARAMETERS,
|
|
ExpressionPtr object, ExpressionPtr method, ExpressionListPtr params)
|
|
: FunctionCall(
|
|
EXPRESSION_CONSTRUCTOR_PARAMETER_VALUES(ObjectMethodExpression),
|
|
method, "", params, ExpressionPtr()),
|
|
m_object(object), m_bindClass(true) {
|
|
m_object->setContext(Expression::ObjectContext);
|
|
m_object->clearContext(Expression::LValue);
|
|
m_object->clearContext(Expression::AccessContext);
|
|
}
|
|
|
|
ExpressionPtr ObjectMethodExpression::clone() {
|
|
ObjectMethodExpressionPtr exp(new ObjectMethodExpression(*this));
|
|
FunctionCall::deepCopy(exp);
|
|
exp->m_object = Clone(m_object);
|
|
return exp;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// parser functions
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// static analysis functions
|
|
|
|
void ObjectMethodExpression::analyzeProgram(AnalysisResultPtr ar) {
|
|
FunctionCall::analyzeProgram(ar);
|
|
m_object->analyzeProgram(ar);
|
|
|
|
if (ar->getPhase() == AnalysisResult::AnalyzeAll) {
|
|
FunctionScopePtr func = m_funcScope;
|
|
if (!func && m_object->isThis() && !m_name.empty()) {
|
|
ClassScopePtr cls = getClassScope();
|
|
if (cls) {
|
|
m_classScope = cls;
|
|
func = cls->findFunction(ar, m_name, true, true);
|
|
if (func &&
|
|
!cls->isInterface() &&
|
|
!(func->isVirtual() &&
|
|
(func->isAbstract() ||
|
|
(func->hasOverride() &&
|
|
cls->getAttribute(ClassScope::NotFinal))) &&
|
|
!func->isPerfectVirtual())) {
|
|
m_funcScope = func;
|
|
func->addCaller(getScope());
|
|
}
|
|
}
|
|
}
|
|
|
|
markRefParams(func, m_name, canInvokeFewArgs());
|
|
}
|
|
|
|
// This is OK because AnalyzeFinal is guaranteed to run for a CPP
|
|
// target, regardless of opts (and we only need the following
|
|
// for CPP targets)
|
|
if (ar->getPhase() == AnalysisResult::AnalyzeFinal) {
|
|
// necessary because we set the expected type of m_object to
|
|
// Type::Some during type inference.
|
|
TypePtr at(m_object->getActualType());
|
|
TypePtr it(m_object->getImplementedType());
|
|
if (!m_object->isThis() && at && at->is(Type::KindOfObject)) {
|
|
if (at->isSpecificObject() && it && Type::IsMappedToVariant(it)) {
|
|
// fast-cast inference
|
|
ClassScopePtr scope(ar->findClass(at->getName()));
|
|
if (scope) {
|
|
// add a dependency to m_object's class type
|
|
// to allow the fast cast to succeed
|
|
addUserClass(ar, at->getName());
|
|
}
|
|
}
|
|
m_object->setExpectedType(at);
|
|
}
|
|
}
|
|
}
|
|
|
|
ConstructPtr ObjectMethodExpression::getNthKid(int n) const {
|
|
if (!n) return m_object;
|
|
return FunctionCall::getNthKid(n);
|
|
}
|
|
|
|
void ObjectMethodExpression::setNthKid(int n, ConstructPtr cp) {
|
|
if (!n) {
|
|
m_object = boost::dynamic_pointer_cast<Expression>(cp);
|
|
} else {
|
|
FunctionCall::setNthKid(n, cp);
|
|
}
|
|
}
|
|
|
|
TypePtr ObjectMethodExpression::inferTypes(AnalysisResultPtr ar,
|
|
TypePtr type, bool coerce) {
|
|
assert(false);
|
|
return TypePtr();
|
|
}
|
|
|
|
void ObjectMethodExpression::setInvokeParams(AnalysisResultPtr ar) {
|
|
FunctionScope::FunctionInfoPtr info;
|
|
if (Option::WholeProgram) {
|
|
info = FunctionScope::GetFunctionInfo(m_name);
|
|
}
|
|
if (!Option::WholeProgram || info || m_name.empty()) {
|
|
for (int i = m_params->getCount(); i--; ) {
|
|
if (!info || info->isRefParam(i)) {
|
|
m_params->markParam(i, canInvokeFewArgs());
|
|
}
|
|
}
|
|
}
|
|
// If we cannot find information of the so-named function, it might not
|
|
// exist, or it might go through __call(), either of which cannot have
|
|
// reference parameters.
|
|
for (int i = 0; i < m_params->getCount(); i++) {
|
|
(*m_params)[i]->inferAndCheck(ar, Type::Variant, false);
|
|
}
|
|
m_params->resetOutputCount();
|
|
}
|
|
|
|
ExpressionPtr ObjectMethodExpression::preOptimize(AnalysisResultConstPtr ar) {
|
|
if (ar->getPhase() < AnalysisResult::FirstPreOptimize) {
|
|
return ExpressionPtr();
|
|
}
|
|
|
|
if (m_classScope && m_funcScope &&
|
|
(!m_funcScope->isVirtual() ||
|
|
(Option::WholeProgram && !m_funcScope->hasOverride()))) {
|
|
|
|
if (Option::DynamicInvokeFunctions.size()) {
|
|
if (Option::DynamicInvokeFunctions.find(
|
|
m_classScope->getName() + "::" + m_funcScope->getName()) !=
|
|
Option::DynamicInvokeFunctions.end()) {
|
|
setNoInline();
|
|
}
|
|
}
|
|
|
|
return inliner(ar, m_object, "");
|
|
}
|
|
|
|
return ExpressionPtr();
|
|
}
|
|
|
|
TypePtr ObjectMethodExpression::inferAndCheck(AnalysisResultPtr ar,
|
|
TypePtr type, bool coerce) {
|
|
assert(type);
|
|
IMPLEMENT_INFER_AND_CHECK_ASSERT(getScope());
|
|
resetTypes();
|
|
reset();
|
|
|
|
ConstructPtr self = shared_from_this();
|
|
TypePtr objectType = m_object->inferAndCheck(ar, Type::Some, false);
|
|
m_valid = true;
|
|
m_bindClass = true;
|
|
|
|
if (m_name.empty()) {
|
|
m_nameExp->inferAndCheck(ar, Type::Some, false);
|
|
setInvokeParams(ar);
|
|
// we have to use a variant to hold dynamic value
|
|
return checkTypesImpl(ar, type, Type::Variant, coerce);
|
|
}
|
|
|
|
ClassScopePtr cls;
|
|
if (objectType && !objectType->getName().empty()) {
|
|
if (m_classScope && !strcasecmp(objectType->getName().c_str(),
|
|
m_classScope->getName().c_str())) {
|
|
cls = m_classScope;
|
|
} else {
|
|
cls = ar->findExactClass(shared_from_this(), objectType->getName());
|
|
}
|
|
}
|
|
|
|
if (!cls) {
|
|
m_classScope.reset();
|
|
m_funcScope.reset();
|
|
|
|
m_valid = false;
|
|
setInvokeParams(ar);
|
|
return checkTypesImpl(ar, type, Type::Variant, coerce);
|
|
}
|
|
|
|
if (m_classScope != cls) {
|
|
m_classScope = cls;
|
|
m_funcScope.reset();
|
|
}
|
|
|
|
FunctionScopePtr func = m_funcScope;
|
|
if (!func) {
|
|
func = cls->findFunction(ar, m_name, true, true);
|
|
if (!func) {
|
|
if (!cls->isTrait() &&
|
|
!cls->getAttribute(ClassScope::MayHaveUnknownMethodHandler) &&
|
|
!cls->getAttribute(ClassScope::HasUnknownMethodHandler) &&
|
|
!cls->getAttribute(ClassScope::InheritsUnknownMethodHandler)) {
|
|
if (ar->classMemberExists(m_name, AnalysisResult::MethodName)) {
|
|
if (!Option::AllDynamic) {
|
|
setDynamicByIdentifier(ar, m_name);
|
|
}
|
|
} else {
|
|
Compiler::Error(Compiler::UnknownObjectMethod, self);
|
|
}
|
|
}
|
|
|
|
m_valid = false;
|
|
setInvokeParams(ar);
|
|
return checkTypesImpl(ar, type, Type::Variant, coerce);
|
|
}
|
|
m_funcScope = func;
|
|
func->addCaller(getScope(), !type->is(Type::KindOfAny));
|
|
}
|
|
|
|
bool valid = true;
|
|
m_bindClass = func->isStatic();
|
|
|
|
// use $this inside a static function
|
|
if (m_object->isThis()) {
|
|
FunctionScopePtr localfunc = getFunctionScope();
|
|
if (localfunc->isStatic()) {
|
|
if (getScope()->isFirstPass()) {
|
|
Compiler::Error(Compiler::MissingObjectContext, self);
|
|
}
|
|
valid = false;
|
|
}
|
|
}
|
|
|
|
// invoke() will return Variant
|
|
if (cls->isInterface() ||
|
|
(func->isVirtual() &&
|
|
(!Option::WholeProgram || func->isAbstract() ||
|
|
(func->hasOverride() && cls->getAttribute(ClassScope::NotFinal))) &&
|
|
!func->isPerfectVirtual())) {
|
|
valid = false;
|
|
}
|
|
|
|
if (!valid) {
|
|
setInvokeParams(ar);
|
|
checkTypesImpl(ar, type, Type::Variant, coerce);
|
|
m_valid = false; // so we use invoke() syntax
|
|
if (!Option::AllDynamic) {
|
|
func->setDynamic();
|
|
}
|
|
assert(m_actualType);
|
|
return m_actualType;
|
|
}
|
|
|
|
assert(func);
|
|
return checkParamsAndReturn(ar, type, coerce, func, false);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// code generation functions
|
|
|
|
void ObjectMethodExpression::outputPHP(CodeGenerator &cg,
|
|
AnalysisResultPtr ar) {
|
|
m_object->outputPHP(cg, ar);
|
|
cg_printf("->");
|
|
if (m_nameExp->getKindOf() == Expression::KindOfScalarExpression) {
|
|
m_nameExp->outputPHP(cg, ar);
|
|
} else {
|
|
cg_printf("{");
|
|
m_nameExp->outputPHP(cg, ar);
|
|
cg_printf("}");
|
|
}
|
|
cg_printf("(");
|
|
m_params->outputPHP(cg, ar);
|
|
cg_printf(")");
|
|
}
|