Arquivos
hhvm/hphp/compiler/statement/method_statement.cpp
T
mwilliams a8e7668189 Get rid of lots of outputCPP code
Very little is actually used anymore
2013-03-07 20:44:34 -08:00

1423 linhas
46 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 <runtime/base/complex_types.h>
#include <compiler/statement/method_statement.h>
#include <compiler/statement/return_statement.h>
#include <compiler/statement/statement_list.h>
#include <compiler/statement/try_statement.h>
#include <compiler/statement/label_statement.h>
#include <compiler/statement/goto_statement.h>
#include <compiler/statement/exp_statement.h>
#include <compiler/statement/switch_statement.h>
#include <compiler/statement/case_statement.h>
#include <compiler/statement/catch_statement.h>
#include <compiler/expression/modifier_expression.h>
#include <compiler/expression/expression_list.h>
#include <compiler/expression/constant_expression.h>
#include <compiler/expression/parameter_expression.h>
#include <compiler/expression/assignment_expression.h>
#include <compiler/expression/simple_variable.h>
#include <compiler/analysis/ast_walker.h>
#include <compiler/analysis/analysis_result.h>
#include <compiler/analysis/code_error.h>
#include <compiler/analysis/file_scope.h>
#include <compiler/analysis/variable_table.h>
#include <compiler/analysis/class_scope.h>
#include <compiler/analysis/function_scope.h>
#include <compiler/option.h>
#include <compiler/builtin_symbols.h>
#include <compiler/analysis/alias_manager.h>
#include <util/parser/parser.h>
#include <util/util.h>
using namespace HPHP;
using std::map;
///////////////////////////////////////////////////////////////////////////////
// constructors/destructors
MethodStatement::MethodStatement
(STATEMENT_CONSTRUCTOR_BASE_PARAMETERS,
ModifierExpressionPtr modifiers, bool ref, const string &name,
ExpressionListPtr params, StatementListPtr stmt, int attr,
const string &docComment, ExpressionListPtr attrList,
bool method /* = true */)
: Statement(STATEMENT_CONSTRUCTOR_BASE_PARAMETER_VALUES),
m_method(method), m_ref(ref), m_attribute(attr),
m_cppLength(-1), m_modifiers(modifiers),
m_originalName(name), m_params(params), m_stmt(stmt),
m_docComment(docComment), m_attrList(attrList) {
m_name = Util::toLower(name);
}
MethodStatement::MethodStatement
(STATEMENT_CONSTRUCTOR_PARAMETERS,
ModifierExpressionPtr modifiers, bool ref, const string &name,
ExpressionListPtr params, StatementListPtr stmt, int attr,
const string &docComment, ExpressionListPtr attrList,
bool method /* = true */)
: Statement(STATEMENT_CONSTRUCTOR_PARAMETER_VALUES(MethodStatement)),
m_method(method), m_ref(ref), m_attribute(attr), m_cppLength(-1),
m_modifiers(modifiers), m_originalName(name),
m_params(params), m_stmt(stmt),
m_docComment(docComment), m_attrList(attrList) {
m_name = Util::toLower(name);
}
StatementPtr MethodStatement::clone() {
MethodStatementPtr stmt(new MethodStatement(*this));
stmt->m_stmt = Clone(m_stmt);
stmt->m_params = Clone(m_params);
stmt->m_modifiers = Clone(m_modifiers);
return stmt;
}
string MethodStatement::getFullName() const {
if (m_className.empty()) return m_name;
return m_className + "::" + m_name;
}
string MethodStatement::getOriginalFullName() const {
if (m_originalClassName.empty()) return m_originalName;
return m_originalClassName + "::" + m_originalName;
}
string MethodStatement::getOriginalFullNameForInjection() const {
FunctionScopeRawPtr funcScope = getFunctionScope();
string injectionName;
if (getGeneratorFunc()) {
injectionName = funcScope->isClosureGenerator() ?
m_originalName :
m_originalName + "{continuation}";
} else if (getOrigGeneratorFunc()) {
bool needsOrig = !funcScope->getOrigGenFS()->isClosure();
injectionName = needsOrig ?
getOrigGeneratorFunc()->getOriginalName() :
m_originalName;
} else {
injectionName = m_originalName;
}
return m_originalClassName.empty() ?
injectionName :
m_originalClassName + "::" + injectionName;
}
bool MethodStatement::isRef(int index /* = -1 */) const {
if (index == -1) return m_ref;
assert(index >= 0 && index < m_params->getCount());
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[index]);
return param->isRef();
}
int MethodStatement::getRecursiveCount() const {
return m_stmt ? m_stmt->getRecursiveCount() : 0;
}
///////////////////////////////////////////////////////////////////////////////
// parser functions
FunctionScopePtr MethodStatement::onInitialParse(AnalysisResultConstPtr ar,
FileScopePtr fs) {
int minParam, maxParam;
ConstructPtr self = shared_from_this();
minParam = maxParam = 0;
bool hasRef = false;
if (m_params) {
std::set<string> names, allDeclNames;
int i = 0;
maxParam = m_params->getCount();
for (i = maxParam; i--; ) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[i]);
if (param->isRef()) hasRef = true;
if (!param->isOptional()) {
if (!minParam) minParam = i + 1;
} else if (minParam && !param->hasTypeHint()) {
Compiler::Error(Compiler::RequiredAfterOptionalParam, param);
}
allDeclNames.insert(param->getName());
}
for (i = maxParam-1; i >= 0; i--) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[i]);
if (names.find(param->getName()) != names.end()) {
Compiler::Error(Compiler::RedundantParameter, param);
for (int j = 0; j < 1000; j++) {
string name = param->getName() + lexical_cast<string>(j);
if (names.find(name) == names.end() &&
allDeclNames.find(name) == allDeclNames.end()) {
param->rename(name);
break;
}
}
}
names.insert(param->getName());
}
}
if (hasRef || m_ref) {
m_attribute |= FileScope::ContainsReference;
}
vector<UserAttributePtr> attrs;
if (m_attrList) {
for (int i = 0; i < m_attrList->getCount(); ++i) {
UserAttributePtr a =
dynamic_pointer_cast<UserAttribute>((*m_attrList)[i]);
attrs.push_back(a);
}
}
StatementPtr stmt = dynamic_pointer_cast<Statement>(shared_from_this());
FunctionScopePtr funcScope
(new FunctionScope(ar, m_method, m_name, stmt, m_ref, minParam, maxParam,
m_modifiers, m_attribute, m_docComment, fs, attrs));
if (!m_stmt) {
funcScope->setVirtual();
}
setBlockScope(funcScope);
funcScope->setParamCounts(ar, -1, -1);
return funcScope;
}
void MethodStatement::onParseRecur(AnalysisResultConstPtr ar,
ClassScopePtr classScope) {
if (m_modifiers) {
if (classScope->isInterface()) {
if (m_modifiers->isProtected() || m_modifiers->isPrivate() ||
m_modifiers->isAbstract() || m_modifiers->isFinal()) {
m_modifiers->parseTimeFatal(
Compiler::InvalidAttribute,
"Access type for interface method %s::%s() must be omitted",
classScope->getOriginalName().c_str(), getOriginalName().c_str());
}
}
if (m_modifiers->isAbstract()) {
if (m_modifiers->isPrivate() || m_modifiers->isFinal()) {
m_modifiers->parseTimeFatal(
Compiler::InvalidAttribute,
"Cannot declare abstract method %s::%s() %s",
classScope->getOriginalName().c_str(),
getOriginalName().c_str(),
m_modifiers->isPrivate() ? "private" : "final");
}
if (!classScope->isInterface() && !classScope->isAbstract()) {
/* note that classScope->isAbstract() returns true for traits */
m_modifiers->parseTimeFatal(Compiler::InvalidAttribute,
"Class %s contains abstract method %s and "
"must therefore be declared abstract",
classScope->getOriginalName().c_str(),
getOriginalName().c_str());
}
if (getStmts()) {
parseTimeFatal(Compiler::InvalidAttribute,
"Abstract method %s::%s() cannot contain body",
classScope->getOriginalName().c_str(),
getOriginalName().c_str());
}
}
}
if ((!m_modifiers || !m_modifiers->isAbstract()) &&
!getStmts() && !classScope->isInterface()) {
parseTimeFatal(Compiler::InvalidAttribute,
"Non-abstract method %s::%s() must contain body",
classScope->getOriginalName().c_str(),
getOriginalName().c_str());
}
FunctionScopeRawPtr fs = getFunctionScope();
classScope->addFunction(ar, fs);
m_className = classScope->getName();
m_originalClassName = classScope->getOriginalName();
setSpecialMethod(classScope);
if (Option::DynamicInvokeFunctions.find(getFullName()) !=
Option::DynamicInvokeFunctions.end()) {
fs->setDynamicInvoke();
}
if (m_params) {
for (int i = 0; i < m_params->getCount(); i++) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[i]);
param->parseHandler(classScope);
}
}
FunctionScope::RecordFunctionInfo(m_name, fs);
}
void MethodStatement::fixupSelfAndParentTypehints(ClassScopePtr scope) {
if (m_params) {
for (int i = 0; i < m_params->getCount(); i++) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[i]);
param->fixupSelfAndParentTypehints(scope);
}
}
}
void MethodStatement::setSpecialMethod(ClassScopePtr classScope) {
if (m_name.size() < 2 || m_name.substr(0,2) != "__") {
return;
}
int numArgs = -1;
bool isStatic = false;
if (m_name == "__construct") {
classScope->setAttribute(ClassScope::HasConstructor);
} else if (m_name == "__destruct") {
classScope->setAttribute(ClassScope::HasDestructor);
} else if (m_name == "__call") {
classScope->setAttribute(ClassScope::HasUnknownMethodHandler);
numArgs = 2;
} else if (m_name == "__get") {
classScope->setAttribute(ClassScope::HasUnknownPropGetter);
numArgs = 1;
} else if (m_name == "__set") {
classScope->setAttribute(ClassScope::HasUnknownPropSetter);
numArgs = 2;
} else if (m_name == "__isset") {
classScope->setAttribute(ClassScope::HasUnknownPropTester);
numArgs = 1;
} else if (m_name == "__unset") {
classScope->setAttribute(ClassScope::HasPropUnsetter);
numArgs = 1;
} else if (m_name == "__call") {
classScope->setAttribute(ClassScope::HasUnknownMethodHandler);
numArgs = 2;
} else if (m_name == "__callstatic") {
classScope->setAttribute(ClassScope::HasUnknownStaticMethodHandler);
numArgs = 2;
isStatic = true;
} else if (m_name == "__invoke") {
classScope->setAttribute(ClassScope::HasInvokeMethod);
} else if (m_name == "__tostring") {
numArgs = 0;
}
if (numArgs >= 0) {
// Fatal if the number of arguments is wrong
int n = m_params ? m_params->getCount() : 0;
if (numArgs != n) {
parseTimeFatal(Compiler::InvalidMagicMethod,
"Method %s::%s() must take exactly %d argument%s",
m_originalClassName.c_str(), m_originalName.c_str(),
numArgs, (numArgs == 1) ? "" : "s");
}
// Fatal if any arguments are pass by reference
if (m_params && hasRefParam()) {
parseTimeFatal(Compiler::InvalidMagicMethod,
"Method %s::%s() cannot take arguments by reference",
m_originalClassName.c_str(), m_originalName.c_str());
}
// Fatal if protected/private or if the staticness is wrong
if (m_modifiers->isProtected() || m_modifiers->isPrivate() ||
m_modifiers->isStatic() != isStatic) {
parseTimeFatal(Compiler::InvalidMagicMethod,
"Method %s::%s() must have public visibility and %sbe static",
m_originalClassName.c_str(), m_originalName.c_str(),
isStatic ? "" : "cannot ");
}
}
}
void MethodStatement::addTraitMethodToScope(AnalysisResultConstPtr ar,
ClassScopePtr classScope) {
FunctionScopeRawPtr funcScope = getFunctionScope();
classScope->addFunction(ar, funcScope);
setSpecialMethod(classScope);
FunctionScope::RecordFunctionInfo(m_name, funcScope);
}
///////////////////////////////////////////////////////////////////////////////
// static analysis functions
int MethodStatement::getLocalEffects() const {
if (m_method) return NoEffect;
FunctionScopeRawPtr scope = getFunctionScope();
return scope->isVolatile() ? OtherEffect | CanThrow : NoEffect;
}
void MethodStatement::addParamRTTI(AnalysisResultPtr ar) {
FunctionScopeRawPtr func = getFunctionScope();
VariableTablePtr variables = func->getVariables();
if (variables->getAttribute(VariableTable::ContainsDynamicVariable) ||
variables->getAttribute(VariableTable::ContainsExtract)) {
return;
}
for (int i = 0; i < m_params->getCount(); i++) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[i]);
const string &paramName = param->getName();
if (variables->isLvalParam(paramName)) continue;
TypePtr paramType = param->getActualType();
if ((paramType->is(Type::KindOfVariant) ||
paramType->is(Type::KindOfSome)) &&
!param->isRef()) {
param->setHasRTTI();
ClassScopePtr cls = getClassScope();
ar->addParamRTTIEntry(cls, func, paramName);
const string funcId = ar->getFuncId(cls, func);
ar->addRTTIFunction(funcId);
}
}
}
class TryingGotoFixer {
public:
TryingGotoFixer(AnalysisResultPtr ar) : id(0), m_ar(ar) {}
void fixTryingGotos(StatementPtr s) {
findTryingGotosRecur(s);
while (labelMap.size()) {
LabelInfoMap::iterator it = labelMap.begin(), end = labelMap.end();
while (it != end) {
if (it->second.trys.size()) {
TryStatementRawPtr t = it->second.trys.back();
for (int i = it->second.gotos.size(); i--; ) {
GotoInfo &gi = it->second.gotos[i];
if (gi.trys.size() < it->second.trys.size() ||
gi.trys[it->second.trys.size() - 1] != t) {
// the goto is not nested in the same try as the label
TryInfo & ti = tryMap[t];
if (!ti.label) {
ti.label = ++id;
}
string labelStr = lexical_cast<string>(ti.label);
if (it->second.trys.size() > 1) {
LabelInfo &li = labelMap[labelStr];
li.trys = it->second.trys;
li.trys.pop_back();
li.gotos.push_back(gi);
}
int lid = gi.goto_stmt->getId();
if (!lid) {
int &id = labelIdMap[gi.goto_stmt->label()];
if (!id) id = labelIdMap.size();
lid = id;
gi.goto_stmt->setId(lid);
ti.targets[-lid] = it->first;
} else {
ti.targets[lid] = it->first;
}
gi.goto_stmt->setLabel(labelStr);
}
}
}
labelMap.erase(it++);
}
}
fixTryingGotosRecur(s);
}
void findTryingGotosRecur(StatementPtr s) {
if (!s || FunctionWalker::SkipRecurse(s)) return;
switch (s->getKindOf()) {
case Statement::KindOfLabelStatement:
if (trys.size()) {
LabelStatementRawPtr label_stmt(
static_pointer_cast<LabelStatement>(s));
LabelInfo &li = labelMap[label_stmt->label()];
always_assert(!li.trys.size());
li.trys = trys;
}
break;
case Statement::KindOfGotoStatement: {
GotoStatementRawPtr goto_stmt(
static_pointer_cast<GotoStatement>(s));
LabelInfo &li = labelMap[goto_stmt->label()];
li.gotos.push_back(GotoInfo(goto_stmt, trys));
break;
}
case Statement::KindOfTryStatement: {
TryStatementRawPtr t(static_pointer_cast<TryStatement>(s));
trys.push_back(t);
findTryingGotosRecur(t->getBody());
trys.pop_back();
findTryingGotosRecur(t->getCatches());
return;
}
default:
break;
}
for (int i = s->getKidCount(); i--; ) {
StatementPtr child(s->getNthStmt(i));
if (child) {
findTryingGotosRecur(child);
}
}
return;
}
ExpressionPtr gotoVar(StatementPtr s) {
SimpleVariablePtr sv(
new SimpleVariable(
s->getScope(), s->getLocation(), "0_gtid"));
sv->updateSymbol(SimpleVariablePtr());
sv->getSymbol()->setHidden();
return sv;
}
StatementPtr replaceGoto(GotoStatementRawPtr goto_stmt, int id) {
StatementListPtr sl(new StatementList(
goto_stmt->getScope(), goto_stmt->getLocation()));
AssignmentExpressionPtr ae(
new AssignmentExpression(
goto_stmt->getScope(), goto_stmt->getLocation(),
gotoVar(goto_stmt),
goto_stmt->makeScalarExpression(m_ar, Variant(id > 0 ? id : 0)),
false));
ExpStatementPtr exp(
new ExpStatement(goto_stmt->getScope(), goto_stmt->getLocation(), ae));
sl->addElement(exp);
sl->addElement(goto_stmt);
return sl;
}
StatementPtr fixTryingGotosRecur(StatementPtr s) {
if (FunctionWalker::SkipRecurse(s)) return StatementPtr();
for (int i = s->getKidCount(); i--; ) {
StatementPtr child(s->getNthStmt(i));
if (child) {
StatementPtr r = fixTryingGotosRecur(child);
if (r) s->setNthKid(i, r);
}
}
switch (s->getKindOf()) {
case Statement::KindOfGotoStatement: {
GotoStatementRawPtr goto_stmt(
static_pointer_cast<GotoStatement>(s));
if (int id = goto_stmt->getId()) {
return replaceGoto(goto_stmt, id);
}
break;
}
case Statement::KindOfTryStatement: {
TryStatementRawPtr t(static_pointer_cast<TryStatement>(s));
TryInfo &ti = tryMap[t];
StatementListPtr catches = t->getCatches();
bool doTry = ti.targets.size();
bool doCatch = catches->hasReachableLabel();
if (!doTry && !doCatch) break;
StatementListPtr sl(new StatementList(
s->getScope(), s->getLocation()));
StatementListPtr newBody(new StatementList(
s->getScope(), s->getLocation()));
if (doTry) {
StatementListPtr cases(new StatementList(
s->getScope(), s->getLocation()));
for (map<int,string>::iterator it = ti.targets.begin(),
end = ti.targets.end(); it != end; ++it) {
StatementPtr g(new GotoStatement(
s->getScope(), s->getLocation(), it->second));
if (it->first < 0) {
g = replaceGoto(static_pointer_cast<GotoStatement>(g), 0);
}
CaseStatementPtr c(new CaseStatement(
s->getScope(), s->getLocation(),
s->makeScalarExpression(m_ar, abs(it->first)),
g));
cases->addElement(c);
}
SwitchStatementPtr sw(new SwitchStatement(
s->getScope(), s->getLocation(),
gotoVar(s), cases));
newBody->addElement(sw);
LabelStatementPtr lab(new LabelStatement(
s->getScope(), s->getLocation(),
lexical_cast<string>(ti.label)));
sl->addElement(lab);
}
newBody->addElement(t->getBody());
sl->addElement(StatementPtr(new TryStatement(
s->getScope(), s->getLocation(),
newBody, catches)));
if (doCatch) {
if (!ti.label) ti.label = ++id;
string afterLab = lexical_cast<string>(ti.label) + "_a";
newBody->addElement(StatementPtr(
new GotoStatement(
s->getScope(), s->getLocation(), afterLab)));
for (int i = 0, n = catches->getCount(); i < n; i++) {
CatchStatementPtr c =
static_pointer_cast<CatchStatement>((*catches)[i]);
StatementPtr body = c->getStmt();
string lab = lexical_cast<string>(ti.label) + "_c" +
lexical_cast<string>(i);
c->setStmt(StatementPtr(
new GotoStatement(
c->getScope(), c->getLocation(), lab)));
StatementListPtr newBody(new StatementList(
c->getScope(), c->getLocation()));
newBody->addElement(StatementPtr(
new LabelStatement(
c->getScope(), c->getLocation(),
lab)));
if (body) newBody->addElement(body);
if (i + 1 < n) {
newBody->addElement(
StatementPtr(new GotoStatement(
c->getScope(), c->getLocation(),
afterLab)));
}
sl->addElement(newBody);
}
sl->addElement(StatementPtr(
new LabelStatement(
s->getScope(), s->getLocation(), afterLab)));
}
return sl;
break;
}
default:
break;
}
return StatementPtr();
}
private:
typedef std::vector<TryStatementRawPtr> TryVector;
struct GotoInfo {
GotoInfo(GotoStatementRawPtr g, const TryVector &t) :
goto_stmt(g), trys(t) {}
GotoStatementRawPtr goto_stmt;
TryVector trys;
};
typedef std::vector<GotoInfo> GotoVector;
struct LabelInfo {
TryVector trys;
GotoVector gotos;
};
struct TryInfo {
int label;
std::map<int,string> targets;
};
TryVector trys;
typedef map<string,LabelInfo> LabelInfoMap;
LabelInfoMap labelMap;
map<TryStatementRawPtr,TryInfo> tryMap;
map<string,int> labelIdMap;
int id;
AnalysisResultPtr m_ar;
};
void MethodStatement::analyzeProgram(AnalysisResultPtr ar) {
FunctionScopeRawPtr funcScope = getFunctionScope();
if (m_params) {
m_params->analyzeProgram(ar);
if (Option::GenRTTIProfileData &&
ar->getPhase() == AnalysisResult::AnalyzeFinal) {
addParamRTTI(ar);
}
}
if (m_stmt) m_stmt->analyzeProgram(ar);
if (ar->getPhase() == AnalysisResult::AnalyzeAll) {
funcScope->setParamSpecs(ar);
if (funcScope->isGenerator()) {
VariableTablePtr variables = funcScope->getVariables();
Symbol *cont = variables->getSymbol(CONTINUATION_OBJECT_NAME);
cont->setHidden();
getOrigGeneratorFunc()->getFunctionScope()->addUse(
funcScope, BlockScope::UseKindClosure);
getOrigGeneratorFunc()->getFunctionScope()->setContainsBareThis(
funcScope->containsBareThis(), funcScope->containsRefThis());
getOrigGeneratorFunc()->getFunctionScope()->setContainsThis(
funcScope->containsThis());
}
if (funcScope->isSepExtension() ||
Option::IsDynamicFunction(m_method, m_name) || Option::AllDynamic) {
funcScope->setDynamic();
}
#ifndef HHVM
// This is an hphpc-only transformation to deal with gotos that jump into
// try/catch blocks. The VM does not need this transformation, and it does
// not support this transformation.
if (funcScope->hasGoto() && funcScope->hasTry()) {
TryingGotoFixer tgf(ar);
tgf.fixTryingGotos(m_stmt);
}
#endif
// TODO: this may have to expand to a concept of "virtual" functions...
if (m_method) {
funcScope->disableInline();
if (m_name.length() > 2 && m_name.substr(0,2) == "__") {
bool magic = true;
int paramCount = 0;
if (m_name == "__destruct") {
funcScope->setOverriding(Type::Variant);
} else if (m_name == "__call") {
funcScope->setOverriding(Type::Variant, Type::String, Type::Array);
paramCount = 2;
} else if (m_name == "__set") {
funcScope->setOverriding(Type::Variant, Type::String, Type::Variant);
paramCount = 2;
} else if (m_name == "__get") {
funcScope->setOverriding(Type::Variant, Type::String);
paramCount = 1;
} else if (m_name == "__isset") {
funcScope->setOverriding(Type::Boolean, Type::String);
paramCount = 1;
} else if (m_name == "__unset") {
funcScope->setOverriding(Type::Variant, Type::String);
paramCount = 1;
} else if (m_name == "__sleep") {
funcScope->setOverriding(Type::Variant);
} else if (m_name == "__wakeup") {
funcScope->setOverriding(Type::Variant);
} else if (m_name == "__set_state") {
funcScope->setOverriding(Type::Variant, Type::Variant);
paramCount = 1;
} else if (m_name == "__tostring") {
funcScope->setOverriding(Type::String);
} else if (m_name == "__clone") {
funcScope->setOverriding(Type::Variant);
} else {
paramCount = -1;
if (m_name != "__construct") {
magic = false;
}
}
if (paramCount >= 0 && paramCount != funcScope->getMaxParamCount()) {
Compiler::Error(Compiler::InvalidMagicMethod, shared_from_this());
magic = false;
}
if (magic) funcScope->setMagicMethod();
}
// ArrayAccess methods
else if (m_name.length() > 6 && m_name.substr(0, 6) == "offset") {
if (m_name == "offsetexists") {
funcScope->setOverriding(Type::Boolean, Type::Variant);
} else if (m_name == "offsetget") {
funcScope->setOverriding(Type::Variant, Type::Variant);
} else if (m_name == "offsetset") {
funcScope->setOverriding(Type::Variant, Type::Variant, Type::Variant);
} else if (m_name == "offsetunset") {
funcScope->setOverriding(Type::Variant, Type::Variant);
}
}
}
} else if (ar->getPhase() == AnalysisResult::AnalyzeFinal) {
TypePtr ret = funcScope->getReturnType();
if (ret && ret->isSpecificObject()) {
FileScopePtr fs = getFileScope();
if (fs) fs->addClassDependency(ar, ret->getName());
}
if (!getFunctionScope()->usesLSB()) {
if (StatementPtr orig = getOrigGeneratorFunc()) {
orig->getFunctionScope()->clearUsesLSB();
}
}
}
}
ConstructPtr MethodStatement::getNthKid(int n) const {
switch (n) {
case 0:
return m_modifiers;
case 1:
return m_params;
case 2:
return m_stmt;
default:
assert(false);
break;
}
return ConstructPtr();
}
int MethodStatement::getKidCount() const {
return 3;
}
void MethodStatement::setNthKid(int n, ConstructPtr cp) {
switch (n) {
case 0:
m_modifiers = boost::dynamic_pointer_cast<ModifierExpression>(cp);
break;
case 1:
m_params = boost::dynamic_pointer_cast<ExpressionList>(cp);
break;
case 2:
m_stmt = boost::dynamic_pointer_cast<StatementList>(cp);
break;
default:
assert(false);
break;
}
}
void MethodStatement::inferTypes(AnalysisResultPtr ar) {
}
void MethodStatement::inferFunctionTypes(AnalysisResultPtr ar) {
IMPLEMENT_INFER_AND_CHECK_ASSERT(getFunctionScope());
FunctionScopeRawPtr funcScope = getFunctionScope();
bool pseudoMain = funcScope->inPseudoMain();
if (m_stmt && funcScope->isFirstPass()) {
if (pseudoMain ||
funcScope->getReturnType() ||
m_stmt->hasRetExp()) {
bool lastIsReturn = false;
if (m_stmt->getCount()) {
StatementPtr lastStmt = (*m_stmt)[m_stmt->getCount()-1];
if (lastStmt->is(Statement::KindOfReturnStatement)) {
lastIsReturn = true;
}
}
if (!lastIsReturn && (!pseudoMain || Option::GenerateCPPMain)) {
ExpressionPtr constant =
makeConstant(ar, funcScope->inPseudoMain() ? "true" : "null");
ReturnStatementPtr returnStmt =
ReturnStatementPtr(
new ReturnStatement(getScope(), getLocation(), constant));
m_stmt->addElement(returnStmt);
}
}
}
if (m_params) {
m_params->inferAndCheck(ar, Type::Any, false);
}
// must also include params and use vars if this is a generator. note: we are
// OK reading the params from the AST nodes of the original generator
// function, since we have the dependency links set up
if (funcScope->isGenerator()) {
// orig function params
MethodStatementRawPtr m = getOrigGeneratorFunc();
assert(m);
VariableTablePtr variables = funcScope->getVariables();
ExpressionListPtr params = m->getParams();
if (params) {
for (int i = 0; i < params->getCount(); i++) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*params)[i]);
const string &name = param->getName();
assert(!param->isRef() || param->getType()->is(Type::KindOfVariant));
variables->addParamLike(name, param->getType(), ar, param,
funcScope->isFirstPass());
}
}
// use vars
ExpressionListPtr useVars = m->getFunctionScope()->getClosureVars();
if (useVars) {
for (int i = 0; i < useVars->getCount(); i++) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*useVars)[i]);
const string &name = param->getName();
assert(!param->isRef() || param->getType()->is(Type::KindOfVariant));
variables->addParamLike(name, param->getType(), ar, param,
funcScope->isFirstPass());
}
}
}
if (m_stmt) {
m_stmt->inferTypes(ar);
}
}
///////////////////////////////////////////////////////////////////////////////
// code generation functions
void MethodStatement::outputPHP(CodeGenerator &cg, AnalysisResultPtr ar) {
FunctionScopeRawPtr funcScope = getFunctionScope();
m_modifiers->outputPHP(cg, ar);
cg_printf(" function ");
if (m_ref) cg_printf("&");
if (!ParserBase::IsClosureOrContinuationName(m_name)) {
cg_printf("%s", m_originalName.c_str());
}
cg_printf("(");
if (m_params) m_params->outputPHP(cg, ar);
if (m_stmt) {
cg_indentBegin(") {\n");
funcScope->outputPHP(cg, ar);
m_stmt->outputPHP(cg, ar);
cg_indentEnd("}\n");
} else {
cg_printf(");\n");
}
}
bool MethodStatement::hasRefParam() {
for (int i = 0; i < m_params->getCount(); i++) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[i]);
if (param->isRef()) return true;
}
return false;
}
bool MethodStatement::outputFFI(CodeGenerator &cg, AnalysisResultPtr ar) {
FunctionScopeRawPtr funcScope = getFunctionScope();
ClassScopePtr clsScope = getClassScope();
bool pseudoMain = funcScope->inPseudoMain();
bool inClass = !m_className.empty();
// only expose public methods, and ignore those in redeclared classes
bool inaccessible =
inClass && (!m_modifiers->isPublic() || clsScope->isRedeclaring());
// skip constructors
bool isConstructor = inClass && funcScope->isConstructor(clsScope);
bool valid = !pseudoMain && !inaccessible && !isConstructor &&
(inClass || !ParserBase::IsAnonFunctionName(m_originalName));
if (cg.getContext() == CodeGenerator::CppFFIDecl ||
cg.getContext() == CodeGenerator::CppFFIImpl) {
if (valid) outputCPPFFIStub(cg, ar);
return true;
}
if (cg.getContext() == CodeGenerator::HsFFI) {
if (valid) outputHSFFIStub(cg, ar);
return true;
}
if (cg.getContext() == CodeGenerator::JavaFFI
|| cg.getContext() == CodeGenerator::JavaFFIInterface) {
if (valid) outputJavaFFIStub(cg, ar);
return true;
}
if (cg.getContext() == CodeGenerator::JavaFFICppDecl
|| cg.getContext() == CodeGenerator::JavaFFICppImpl) {
if (valid) outputJavaFFICPPStub(cg, ar);
return true;
}
if (cg.getContext() == CodeGenerator::SwigFFIDecl
|| cg.getContext() == CodeGenerator::SwigFFIImpl) {
if (valid) outputSwigFFIStub(cg, ar);
return true;
}
return false;
}
void MethodStatement::outputCPPFFIStub(CodeGenerator &cg,
AnalysisResultPtr ar) {
FunctionScopeRawPtr funcScope = getFunctionScope();
ClassScopePtr clsScope = getClassScope();
bool varArgs = funcScope->isVariableArgument();
bool ret = funcScope->getReturnType();
string fname = funcScope->getId();
bool inClass = !m_className.empty();
bool isStatic = !inClass || m_modifiers->isStatic();
if (inClass && m_modifiers->isAbstract()) {
return;
}
if (funcScope->getName() == "__offsetget_lval") {
return;
}
if (ret) {
cg_printf("int");
} else {
cg_printf("void");
}
cg_printf(" %s%s%s(", Option::FFIFnPrefix,
(inClass ? (m_className + "_cls_").c_str() : ""), fname.c_str());
if (ret) {
cg_printf("void** res");
}
bool first = !ret;
if (!isStatic) {
// instance methods need one additional parameter for the target
if (first) {
first = false;
}
else {
cg_printf(", ");
}
cg_printf("Variant *target");
}
int ac = funcScope->getMaxParamCount();
for (int i = 0; i < ac; ++i) {
if (first) {
first = false;
} else {
cg_printf(", ");
}
cg_printf("Variant *a%d", i);
}
if (varArgs) {
if (!first) {
cg_printf(", ");
}
cg_printf("Variant *va");
}
cg_printf(")");
if (cg.getContext() == CodeGenerator::CppFFIDecl) {
cg_printf(";\n");
} else {
cg_indentBegin(" {\n");
if (ret) {
cg_printf("return hphp_ffi_exportVariant(");
}
if (!inClass) {
// simple function call
cg_printf("%s%s(", Option::FunctionPrefix, fname.c_str());
} else if (isStatic) {
// static method call
cg_printf("%s%s::%s%s(", Option::ClassPrefix,
clsScope->getId().c_str(),
Option::MethodPrefix, funcScope->getName().c_str());
} else {
// instance method call
cg_printf("dynamic_cast<%s%s *>(target->getObjectData())->",
Option::ClassPrefix, clsScope->getId().c_str());
cg_printf("%s%s(", Option::MethodPrefix, funcScope->getName().c_str());
}
first = true;
if (varArgs) {
cg_printf("%d + (va->isNull() ? 0 : va->getArrayData()->size())", ac);
first = false;
}
for (int i = 0; i < ac; ++i) {
if (first) {
first = false;
} else {
cg_printf(", ");
}
cg_printf("*a%d", i);
}
if (varArgs) {
cg_printf(", va->toArray()");
}
if (ret) {
cg_printf("), res");
}
cg_printf(");\n");
cg_indentEnd("}\n");
cg.printImplSplitter();
}
return;
}
void MethodStatement::outputHSFFIStub(CodeGenerator &cg, AnalysisResultPtr ar) {
if (!m_className.empty()) {
// Haskell currently doesn't support FFI for class methods.
return;
}
FunctionScopeRawPtr funcScope = getFunctionScope();
bool varArgs = funcScope->isVariableArgument();
bool ret = funcScope->getReturnType();
string fname = funcScope->getId().c_str();
cg_indentBegin("foreign import ccall \"stubs.h %s%s\" %s%s\n",
Option::FFIFnPrefix, fname.c_str(),
Option::FFIFnPrefix, fname.c_str());
cg_printf(":: ");
if (ret) {
cg_printf("PtrPtr a -> ");
}
int ac = funcScope->getMaxParamCount();
for (int i = 0; i < ac; ++i) {
cg_printf("HphpVariantPtr -> ");
}
if (varArgs) {
cg_printf("HphpVariantPtr -> ");
}
if (ret) {
cg_printf("IO CInt");
} else {
cg_printf("IO ()");
}
cg_indentEnd("\n");
cg_printf("f_%s :: ", fname.c_str());
bool first = true;
if (ac > 0) {
cg_printf("(");
}
for (int i = 0; i < ac; ++i) {
if (first) {
first = false;
} else {
cg_printf(", ");
}
cg_printf("VariantAble a%d", i);
}
if (ac > 0) {
cg_printf(") => ");
}
for (int i = 0; i < ac; ++i) {
cg_printf("a%d -> ", i);
}
if (varArgs) {
cg_printf("[Variant] -> ");
}
if (ret) {
cg_printf("IO Variant");
} else {
cg_printf("IO ()");
}
cg_printf("\n");
cg_printf("f_%s ", fname.c_str());
for (int i = 0; i < ac; ++i) {
cg_printf("v%d ", i);
}
if (varArgs) {
cg_printf("va ");
}
cg_indentBegin("=%s\n", ret ? " alloca (\\pres ->" : "");
for (int i = 0; i < ac; ++i) {
cg_indentBegin("withExportedVariant (toVariant v%d) (\\p%d ->\n", i, i);
}
if (varArgs) {
cg_indentBegin("withVParamList va (\\pva ->\n");
}
cg_indentBegin("do\n");
cg_printf("%sffi_%s", ret ? "t <- " : "", fname.c_str());
if (ret) {
cg_printf(" pres");
}
for (int i = 0; i < ac; ++i) {
cg_printf(" p%d", i);
}
if (varArgs) {
cg_printf(" pva");
}
if (ret) {
cg_printf("\n");
cg_printf("ppres <- peek pres\n");
cg_printf("buildVariant (fromIntegral t) ppres");
}
cg_indentEnd(); // end do
if (varArgs) {
cg_indentEnd(")"); // end varargs
}
for (int i = 0; i < ac; ++i) {
cg_indentEnd(")"); // end wEV i
}
if (ret) {
cg_indentEnd(")"); // end alloca
} else {
cg_indentEnd();
}
cg_printf("\n");
return;
}
/**
* Generates the Java stub method for a PHP toplevel function.
*
* @author qixin
*/
void MethodStatement::outputJavaFFIStub(CodeGenerator &cg,
AnalysisResultPtr ar) {
FunctionScopeRawPtr funcScope = getFunctionScope();
bool varArgs = funcScope->isVariableArgument();
bool ret = funcScope->getReturnType();
bool inClass = !m_className.empty();
bool isStatic = !inClass || m_modifiers->isStatic();
string fname = funcScope->getId();
string originalName = funcScope->getOriginalName();
if (originalName.length() < fname.length()) {
// if there are functions of the same name, fname may contain "$$..."
// in the end
originalName += fname.substr(originalName.length());
}
if (originalName == "clone" || originalName == "equals"
|| originalName == "finalize" || originalName == "getClass"
|| originalName == "hashCode" || originalName == "notify"
|| originalName == "notifyAll" || originalName == "toString"
|| originalName == "wait") {
// not to clash with Java method names
originalName = "_" + originalName;
}
if (cg.getContext() == CodeGenerator::JavaFFIInterface
|| (inClass && m_modifiers->isAbstract())) {
// skip all the abstract methods, because php overriding is not very
// compatible with Java
return;
}
if (!inClass) printSource(cg);
// This Java method extracts the Variant pointer from the HphpVariant
// argument as a 64-bit integer, and then calls the native version.
bool exposeNative = false;
int ac = funcScope->getMaxParamCount();
if (ac > 0 || varArgs || !isStatic || (!ret && inClass)
|| cg.getContext() == CodeGenerator::JavaFFIInterface) {
// make methods always return something, so that they can override
// each other
cg_printf("public %s%s %s(",
(isStatic ? "static " : ""),
(!ret && !inClass ? "void" : "HphpVariant"),
originalName.c_str());
std::ostringstream args;
bool first = true;
if (!isStatic) {
// instance method has an additional parameter
args << "this.getVariantPtr()";
}
for (int i = 0; i < ac; i++) {
if (first) {
first = false;
if (!isStatic) args << ", ";
}
else {
cg_printf(", ");
args << ", ";
}
cg_printf("HphpVariant a%d", i);
args << "a" << i << ".getVariantPtr()";
}
if (varArgs) {
if (!first) {
cg_printf(", ");
args << ", ";
}
else if (!isStatic) {
args << ", ";
}
cg_printf("HphpVariant va");
args << "va.getVariantPtr()";
}
if (cg.getContext() == CodeGenerator::JavaFFIInterface) {
cg_printf(");\n\n");
return;
}
cg_indentBegin(") {\n");
cg_printf("%s%s_native(%s);\n", (ret ? "return " : ""),
originalName.c_str(),
args.str().c_str());
if (!ret && inClass) {
cg_printf("return HphpNull.phpNull();\n");
}
cg_indentEnd("}\n\n");
}
else {
exposeNative = true;
}
// the native method stub
cg_printf("%s %snative %s %s%s(",
(exposeNative ? "public" : "private"),
(isStatic ? "static " : ""), (ret ? "HphpVariant" : "void"),
originalName.c_str(),
(exposeNative ? "" : "_native"));
bool first = true;
if (!isStatic) {
// instance method has an additional parameter
cg_printf("long targetPtr");
first = false;
}
for (int i = 0; i < ac; i++) {
if (first) first = false;
else cg_printf(", ");
cg_printf("long a%d", i);
}
if (varArgs) {
if (!first) cg_printf(", ");
cg_printf("long va");
}
cg_printf(");\n\n");
}
void MethodStatement::outputJavaFFICPPStub(CodeGenerator &cg,
AnalysisResultPtr ar) {
// TODO translate PHP namespace once that is supported
string packageName = Option::JavaFFIRootPackage;
FunctionScopeRawPtr funcScope = getFunctionScope();
bool varArgs = funcScope->isVariableArgument();
bool ret = funcScope->getReturnType();
bool inClass = !m_className.empty();
bool isStatic = !inClass || m_modifiers->isStatic();
string fname = funcScope->getId();
int ac = funcScope->getMaxParamCount();
bool exposeNative = !(ac > 0 || varArgs || !isStatic || (!ret && inClass));
if (inClass && m_modifiers->isAbstract()) {
// skip all the abstract methods, because hphp doesn't generate code
// for them
return;
}
if (funcScope->getName() == "__offsetget_lval") return;
const char *clsName;
if (inClass) {
// uses capitalized original class name
ClassScopePtr cls = ar->findClass(m_className);
clsName = cls->getOriginalName().c_str();
} else {
clsName = "HphpMain";
}
string mangledName = "Java." + packageName + "." + clsName + "." + fname
+ (exposeNative ? "" : "_native");
// all the existing "_" are replaced as "_1"
Util::replaceAll(mangledName, "_", "_1");
Util::replaceAll(mangledName, ".", "_");
cg_printf("JNIEXPORT %s JNICALL\n", ret ? "jobject" : "void");
cg_printf("%s(JNIEnv *env, %s target", mangledName.c_str(),
(isStatic ? "jclass" : "jobject"));
std::ostringstream args;
bool first = true;
if (!isStatic) {
// instance method also gets an additional argument, which is a Variant
// pointer to the target, encoded in int64
first = false;
cg_printf(", jlong targetPtr");
args << "(Variant *)targetPtr";
}
for (int i = 0; i < ac; i++) {
cg_printf(", jlong a%d", i);
if (first) first = false;
else args << ", ";
args << "(Variant *)a" << i;
}
if (varArgs) {
cg_printf(", jlong va");
if (!first) args << ", ";
args << "(Variant *)va";
}
if (cg.getContext() == CodeGenerator::JavaFFICppDecl) {
// java_stubs.h
cg_printf(");\n\n");
return;
}
cg_indentBegin(") {\n");
// support static/instance methods
if (ret) {
cg_printf("void *result;\n");
cg_printf("int kind = ");
cg_printf("%s%s%s(&result",
Option::FFIFnPrefix,
(inClass ? (m_className + "_cls_").c_str() : ""), fname.c_str());
if (!isStatic || ac > 0 || varArgs) cg_printf(", ");
} else {
cg_printf("%s%s%s(", Option::FFIFnPrefix,
(inClass ? (m_className + "_cls_").c_str() : ""),
fname.c_str());
}
cg_printf("%s);\n", args.str().c_str());
if (ret) {
if (!inClass) {
// HphpMain extends hphp.Hphp.
cg_printf("jclass hphp = env->GetSuperclass(target);\n");
}
else {
cg_printf("jclass hphp = env->FindClass(\"hphp/Hphp\");\n");
}
cg_printf("return exportVariantToJava(env, hphp, result, kind);\n");
}
cg_indentEnd("}\n");
cg.printImplSplitter();
}
void MethodStatement::outputSwigFFIStub(CodeGenerator &cg,
AnalysisResultPtr ar) {
FunctionScopeRawPtr funcScope = getFunctionScope();
bool varArgs = funcScope->isVariableArgument();
bool ret = funcScope->getReturnType();
string fname = funcScope->getId();
string originalName = funcScope->getOriginalName();
int ac = funcScope->getMaxParamCount();
if (cg.getContext() == CodeGenerator::SwigFFIImpl) {
printSource(cg);
}
cg_printf("Variant *%s(HphpSession *s", originalName.c_str());
std::ostringstream args;
bool first = true;
for (int i = 0; i < ac; i++) {
cg_printf(", Variant *a%d", i);
if (first) first = false;
else args << ", ";
args << "a" << i;
}
if (varArgs) {
cg_printf(", Variant *va");
if (!first) args << ", ";
args << "va";
}
if (cg.getContext() == CodeGenerator::SwigFFIDecl) {
cg_printf(");\n\n");
return;
}
cg_indentBegin(") {\n");
if (ret) {
cg_printf("void *result;\n");
cg_printf("int kind = ");
cg_printf("%s%s(&result", Option::FFIFnPrefix, fname.c_str());
if (ac > 0 || varArgs) cg_printf(", ");
} else {
cg_printf("%s%s(", Option::FFIFnPrefix, fname.c_str());
}
cg_printf("%s);\n", args.str().c_str());
cg_printf("Variant *ret = ");
if (ret) {
cg_printf("hphpBuildVariant(kind, result);\n");
cg_printf("s->addVariant(ret);\n");
} else {
cg_printf("hphpBuildVariant(0, 0);\n");
cg_printf("s->addVariant(ret);\n");
}
cg_printf("return ret;\n");
cg_indentEnd("}\n\n");
}