Arquivos
hhvm/hphp/compiler/statement/method_statement.cpp
T
ptarjan 8c6d77deef Put the body of a closure on the class intead of in the __invoke of the closure
Instead of having the body of the closure be in the ##__invoke()## on the ##Closure## class, instead we make an anonymous function on the real class and put the body there. The signature for this function is:

  function methodForClosure$1234($arg1, $arg2, ..., $use1, $use2, ...)

and then ##__invoke## now just takes all the params that were passed to it, puts them as the first args to the anonymous function, then takes all the use variables it had saved up and passed them in as the next params.

I tried to not have an ##__invoke## at all, but I ended up basically doing the same parameter and use var repacking in iopFCall (and would have had to do it in x86 code too). I opted for doing the rejiggering in bytecode. If I did it in raw PHP I think it would have been much slower with many ##func_get_args()## and array operations.
2013-03-05 22:07:56 -08:00

1966 linhas
66 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");
}
}
void MethodStatement::outputCPPImpl(CodeGenerator &cg, AnalysisResultPtr ar) {
FunctionScopeRawPtr funcScope = getFunctionScope();
ClassScopePtr scope = getClassScope();
if (outputFFI(cg, ar)) return;
cg.setPHPLineNo(-1);
CodeGenerator::Context context = cg.getContext();
if (context == CodeGenerator::CppImplementation) {
printSource(cg);
}
bool isWrapper = context == CodeGenerator::CppTypedParamsWrapperDecl ||
context == CodeGenerator::CppTypedParamsWrapperImpl;
bool needsWrapper = isWrapper ||
(Option::HardTypeHints && funcScope->needsTypeCheckWrapper());
const char *prefix = needsWrapper && !isWrapper ?
Option::TypedMethodPrefix : Option::MethodPrefix;
switch (context) {
case CodeGenerator::CppDeclaration:
case CodeGenerator::CppTypedParamsWrapperDecl:
{
if (!m_stmt && !funcScope->isPerfectVirtual()) {
cg_printf("// ");
}
m_modifiers->outputCPP(cg, ar);
if (!m_stmt || m_name == "__offsetget_lval" ||
funcScope->isPerfectVirtual()) {
cg_printf("virtual ");
}
TypePtr type = funcScope->getReturnType();
if (type) {
type->outputCPPDecl(cg, ar, getScope());
} else {
cg_printf("void");
}
if (m_name == "__offsetget_lval") {
cg_printf(" &___offsetget_lval(");
} else if (m_modifiers->isStatic() && m_stmt) {
// Static method wrappers get generated as support methods
bool needsClassParam = funcScope->needsClassParam();
const char *prefix = needsWrapper && !isWrapper ?
(needsClassParam ?
Option::TypedMethodImplPrefix : Option::TypedMethodPrefix) :
(needsClassParam ? Option::MethodImplPrefix : Option::MethodPrefix);
cg_printf(" %s%s(", prefix,
CodeGenerator::FormatLabel(m_name).c_str());
if (needsClassParam) {
cg_printf("CStrRef cls%s",
funcScope->isVariableArgument() ||
(m_params && m_params->getCount()) ? ", " : "");
}
} else {
cg_printf(" %s%s(", prefix,
CodeGenerator::FormatLabel(m_name).c_str());
}
funcScope->outputCPPParamsDecl(cg, ar, m_params, true);
if (m_stmt) {
int opt = Option::GetOptimizationLevel(m_cppLength);
if (opt < 3) cg_printf(") __attribute__((optimize(%d)));\n", opt);
else cg_printf(");\n");
} else if (funcScope->isPerfectVirtual()) {
cg_printf(");\n");
} else {
cg_printf(") = 0;\n");
}
if (context != CodeGenerator::CppTypedParamsWrapperDecl) {
if (funcScope->isConstructor(scope)
&& !funcScope->isAbstract() && !scope->isInterface()) {
funcScope->outputCPPCreateDecl(cg, ar);
}
if (Option::HardTypeHints && funcScope->needsTypeCheckWrapper()) {
cg.setContext(CodeGenerator::CppTypedParamsWrapperDecl);
outputCPPImpl(cg, ar);
cg.setContext(context);
}
}
}
break;
case CodeGenerator::CppImplementation:
case CodeGenerator::CppTypedParamsWrapperImpl:
if (m_stmt || funcScope->isPerfectVirtual()) {
int startLineImplementation = -1;
if (context == CodeGenerator::CppImplementation) {
startLineImplementation = cg.getLineNo(CodeGenerator::PrimaryStream);
}
TypePtr type = funcScope->getReturnType();
if (type) {
bool isHeader = cg.isFileOrClassHeader();
cg.setFileOrClassHeader(true);
type->outputCPPDecl(cg, ar, getScope());
cg.setFileOrClassHeader(isHeader);
} else {
cg_printf("void");
}
string origFuncName = getOriginalFullName();
if (Option::FunctionSections.find(origFuncName) !=
Option::FunctionSections.end()) {
string funcSection = Option::FunctionSections[origFuncName];
if (!funcSection.empty()) {
cg_printf(" __attribute__ ((section (\".text.%s\")))",
funcSection.c_str());
}
}
origFuncName = CodeGenerator::EscapeLabel(origFuncName);
if (m_name == "__offsetget_lval") {
cg_printf(" &%s%s::___offsetget_lval(",
Option::ClassPrefix, scope->getId().c_str());
} else if (m_modifiers->isStatic()) {
bool needsClassParam = funcScope->needsClassParam();
const char *prefix = needsWrapper && !isWrapper ?
(needsClassParam ?
Option::TypedMethodImplPrefix : Option::TypedMethodPrefix) :
(needsClassParam ? Option::MethodImplPrefix : Option::MethodPrefix);
cg_printf(" %s%s::%s%s(", Option::ClassPrefix,
scope->getId().c_str(), prefix,
CodeGenerator::FormatLabel(m_name).c_str());
if (needsClassParam) {
cg_printf("CStrRef cls%s",
funcScope->isVariableArgument() ||
(m_params && m_params->getCount()) ? ", " : "");
}
} else {
cg_printf(" %s%s::%s%s(", Option::ClassPrefix,
scope->getId().c_str(),
prefix, CodeGenerator::FormatLabel(m_name).c_str());
}
funcScope->outputCPPParamsDecl(cg, ar, m_params, false);
cg_indentBegin(") {\n");
if (!m_stmt) {
cg_printf("return throw_fatal(\"pure virtual\");\n");
cg_indentEnd("}\n");
} else if (context != CodeGenerator::CppTypedParamsWrapperImpl) {
if (m_stmt->hasBody()) {
const char *suffix =
(cg.getOutput() == CodeGenerator::SystemCPP ? "_BUILTIN" : "");
if (suffix[0] == '\0' && !funcScope->needsCheckMem()) {
suffix = "_NOMEM";
}
const string &injectionName =
CodeGenerator::EscapeLabel(getOriginalFullNameForInjection());
const string &scopeName =
CodeGenerator::EscapeLabel(scope->getOriginalName());
if (m_modifiers->isStatic()) {
cg_printf("STATIC_METHOD_INJECTION%s(%s, %s);\n", suffix,
scopeName.c_str(), injectionName.c_str());
} else if (cg.getOutput() != CodeGenerator::SystemCPP &&
!scope->isRedeclaring() && !scope->derivedByDynamic()) {
cg_printf("INSTANCE_METHOD_INJECTION_ROOTLESS%s(%s, %s);\n",
suffix, scopeName.c_str(), injectionName.c_str());
} else if (scope->getOriginalName() != "XhprofFrame") {
cg_printf("INSTANCE_METHOD_INJECTION%s(%s, %s);\n", suffix,
scopeName.c_str(), injectionName.c_str());
}
}
if (m_name == "__offsetget_lval") {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[0]);
cg_printf("Variant &v = %s->__lvalProxy;\n", cg.getGlobals(ar));
string lowered = Util::toLower(m_originalName);
cg_printf("v = %s%s(%s%s);\n",
prefix, lowered.c_str(),
Option::VariablePrefix, param->getName().c_str());
cg_printf("return v;\n");
} else {
outputCPPArgInjections(cg, ar, origFuncName.c_str(),
scope, funcScope);
funcScope->outputCPP(cg, ar);
if (funcScope->needsRefTemp()) {
cg.genReferenceTemp(shared_from_this());
}
if (funcScope->needsObjTemp()) {
cg_printf("ObjectData *obj_tmp UNUSED;\n");
}
cg.setContext(
CodeGenerator::NoContext); // no inner functions/classes
if (!funcScope->isStatic() && funcScope->getVariables()->
getAttribute(VariableTable::ContainsDynamicVariable)) {
Symbol *sym = funcScope->getVariables()->getSymbol("this");
if (sym && sym->declarationSet()) {
string namePrefix;
if (funcScope->isGenerator()) {
namePrefix = string(TYPED_CONTINUATION_OBJECT_NAME) + "->";
}
cg_printf("%s%sthis = this;\n", namePrefix.c_str(),
Option::VariablePrefix);
}
}
outputCPPStmt(cg, ar);
if (funcScope->needsRefTemp()) cg.clearRefereceTemp();
}
cg_indentEnd("}\n");
assert(startLineImplementation >= 0);
m_cppLength = cg.getLineNo(CodeGenerator::PrimaryStream)
- startLineImplementation;
if (Option::HardTypeHints && funcScope->needsTypeCheckWrapper()) {
cg.setContext(CodeGenerator::CppTypedParamsWrapperImpl);
outputCPPImpl(cg, ar);
}
} else {
outputCPPTypeCheckWrapper(cg, ar);
cg_indentEnd("}\n");
}
cg.setContext(context);
cg.printImplSplitter();
}
break;
default:
break;
}
}
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;
}
void MethodStatement::outputParamArrayCreate(CodeGenerator &cg, bool checkRef) {
int n = m_params->getCount();
assert(n > 0);
cg_printf("array_createvi(%d, ", n);
VariableTablePtr variables = getFunctionScope()->getVariables();
for (int i = 0; i < n; i++) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[i]);
const string &paramName = param->getName();
cg_printf("toVPOD(");
const char *pre = "";
if (!checkRef && variables->getSymbol(paramName)->isStashedVal()) {
pre = "v";
}
string name = pre + (Option::VariablePrefix +
CodeGenerator::FormatLabel(paramName));
if (checkRef && param->isRef()) {
not_reached();
} else {
cg_printf("%s", name.c_str());
}
cg_printf(")");
if (i < n - 1) {
cg_printf(", ");
} else {
cg_printf(")");
}
}
}
void MethodStatement::outputCPPArgInjections(CodeGenerator &cg,
AnalysisResultPtr ar,
const char *name,
ClassScopePtr cls,
FunctionScopePtr funcScope) {
if (cg.getOutput() != CodeGenerator::SystemCPP) {
if (funcScope->isDynamicInvoke()) {
cg_printf("INTERCEPT_INJECTION_ALWAYS(\"%s\", \"%s\", ", name, name);
} else {
cg_printf("INTERCEPT_INJECTION(\"%s\", ", name);
}
if (m_params) {
int n = m_params->getCount();
assert(n >= 0);
if (Option::GenArrayCreate && !hasRefParam()) {
if (ar->m_arrayIntegerKeyMaxSize < n) ar->m_arrayIntegerKeyMaxSize = n;
outputParamArrayCreate(cg, true);
} else {
cg_printf("(Array(ArrayInit(%d, ArrayInit::vectorInit)", n);
for (int i = 0; i < n; i++) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[i]);
const string &paramName = param->getName();
cg_printf(".set%s(%d, %s%s%s)", param->isRef() ? "Ref" : "",
i, param->isRef() ? "r" : "",
Option::VariablePrefix, paramName.c_str());
}
cg_printf(".create()))");
}
} else {
cg_printf("null_array");
}
TypePtr t = funcScope->getReturnType();
bool refRet = funcScope->isRefReturn() && t && Type::IsMappedToVariant(t);
cg_printf(", %s);\n",
!funcScope->getReturnType() ? "" :
refRet ? "strongBind(r)" : "r");
}
if (Option::GenRTTIProfileData && m_params) {
for (int i = 0; i < m_params->getCount(); i++) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[i]);
if (param->hasRTTI()) {
const string &paramName = param->getName();
int id = ar->getParamRTTIEntryId(cls, funcScope, paramName);
if (id != -1) {
cg_printf("RTTI_INJECTION(%s%s, %d);\n",
Option::VariablePrefix, paramName.c_str(), id);
}
}
}
}
}
void MethodStatement::outputCPPStmt(CodeGenerator &cg, AnalysisResultPtr ar) {
if (m_stmt) {
FunctionScopeRawPtr funcScope = getFunctionScope();
if (funcScope->inPseudoMain()) {
cg.beginHoistedClasses();
int i, n = m_stmt->getCount();
for (i = 0; i < n; ++i) {
StatementPtr s((*m_stmt)[i]);
if (s->is(Statement::KindOfClassStatement) ||
s->is(Statement::KindOfInterfaceStatement)) {
cg.addHoistedClass(s->getClassScope()->getName());
}
}
bool seenOther = false;
for (i = 0; i < n; ++i) {
StatementPtr s((*m_stmt)[i]);
if (!s || !s->hasImpl()) continue;
if (s->is(Statement::KindOfFunctionStatement)) {
s->outputCPP(cg, ar);
continue;
}
if (s->is(Statement::KindOfClassStatement) ||
s->is(Statement::KindOfInterfaceStatement)) {
ClassScopeRawPtr cls = s->getClassScope();
if (cls->isBaseClass() ||
!cls->isVolatile() ||
!cls->hasUnknownBases()) {
cls->outputCPPDef(cg);
continue;
}
const string &parent = cls->getOriginalParent();
if (cls->getBases().size() > (parent.empty() ? 0 : 1)) {
// the class is not hoistable because it implements
// an interface
continue;
}
always_assert(!parent.empty());
if (seenOther) {
ClassScopePtr pcls = ar->findClass(parent);
if (pcls) {
if (pcls->isVolatile()) {
cg_indentBegin("if (%s->CDEC(%s)) {\n",
cg.getGlobals(ar),
CodeGenerator::FormatLabel(
pcls->getName()).c_str());
}
cls->outputCPPDef(cg);
if (pcls->isVolatile()) {
cg_indentEnd("}\n");
}
continue;
}
}
} else {
seenOther = true;
}
}
cg.collectHoistedClasses(false);
for (i = 0; i < n; ++i) {
StatementPtr s((*m_stmt)[i]);
if (s->is(Statement::KindOfFunctionStatement)) continue;
if (s->is(Statement::KindOfClassStatement) ||
s->is(Statement::KindOfInterfaceStatement)) {
ClassScopeRawPtr cls = s->getClassScope();
if (cls->isBaseClass() ||
!cls->isVolatile() ||
!cls->hasUnknownBases()) {
continue;
}
cg.collectHoistedClasses(true);
s->outputCPP(cg, ar);
cg.collectHoistedClasses(false);
} else {
s->outputCPP(cg, ar);
}
}
cg.endHoistedClasses();
} else {
m_stmt->outputCPP(cg, ar);
}
}
}
void MethodStatement::outputCPPStaticMethodWrapper(CodeGenerator &cg,
AnalysisResultPtr ar,
const char *cls) {
if (!m_modifiers->isStatic() || !m_stmt) return;
CodeGenerator::Context context = cg.getContext();
FunctionScopeRawPtr funcScope = getFunctionScope();
if (!funcScope->needsClassParam()) return;
bool isWrapper = context == CodeGenerator::CppTypedParamsWrapperDecl ||
context == CodeGenerator::CppTypedParamsWrapperImpl;
bool needsWrapper = isWrapper ||
(Option::HardTypeHints && funcScope->needsTypeCheckWrapper());
m_modifiers->outputCPP(cg, ar);
TypePtr type = funcScope->getReturnType();
if (type) {
type->outputCPPDecl(cg, ar, getScope());
} else {
cg_printf("void");
}
cg_printf(" %s%s(", needsWrapper && !isWrapper ?
Option::TypedMethodPrefix : Option::MethodPrefix,
CodeGenerator::FormatLabel(m_name).c_str());
if (!isWrapper) cg.setContext(CodeGenerator::CppFunctionWrapperDecl);
funcScope->outputCPPParamsDecl(cg, ar, m_params, true);
cg_printf(") { %s%s%s(", type ? "return " : "",
needsWrapper && !isWrapper ?
Option::TypedMethodImplPrefix : Option::MethodImplPrefix,
CodeGenerator::FormatLabel(m_name).c_str());
cg_printf("%s%s::s_class_name", Option::ClassPrefix, cls);
cg.setContext(context);
if (funcScope->isVariableArgument()) {
cg_printf(", num_args");
}
if (m_params) {
for (int i = 0; i < m_params->getCount(); i++) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[i]);
assert(param);
cg_printf(", %s%s", Option::VariablePrefix, param->getName().c_str());
}
}
if (funcScope->isVariableArgument()) {
cg_printf(", args");
}
cg_printf("); }\n");
if (!isWrapper && needsWrapper) {
cg.setContext(CodeGenerator::CppTypedParamsWrapperDecl);
outputCPPStaticMethodWrapper(cg, ar, cls);
cg.setContext(context);
}
}
void MethodStatement::outputCPPTypeCheckWrapper(CodeGenerator &cg,
AnalysisResultPtr ar) {
FunctionScopeRawPtr funcScope = getFunctionScope();
TypePtr type = funcScope->getReturnType();
bool isMethod = getClassScope();
string fname = isMethod ? funcScope->getName() : funcScope->getId();
funcScope->outputCPP(cg, ar);
cg_printf("%s%s%s(", type ? "return " : "",
(isMethod ?
(m_modifiers->isStatic() && funcScope->needsClassParam() ?
Option::TypedMethodImplPrefix :
Option::TypedMethodPrefix) :
Option::TypedFunctionPrefix),
fname.c_str());
if (!isMethod) {
if (fname[0] == '0') {
cg_printf("extra, ");
}
} else if (m_modifiers->isStatic() && funcScope->needsClassParam()) {
cg_printf("cls, ");
}
if (funcScope->isVariableArgument()) {
cg_printf("num_args, ");
}
always_assert(m_params);
for (int i = 0; i < m_params->getCount(); i++) {
ParameterExpressionPtr param =
dynamic_pointer_cast<ParameterExpression>((*m_params)[i]);
assert(param);
ConstantExpressionPtr con =
dynamic_pointer_cast<ConstantExpression>(param->defaultValue());
bool needsNullGet = con && con->isNull();
if (i) cg_printf(", ");
const string &paramName =
string(Option::VariablePrefix) + param->getName();
if (TypePtr spec = funcScope->getParamTypeSpec(i)) {
if (Type::SameType(spec, funcScope->getParamType(i))) {
if (spec->isSpecificObject() && !needsNullGet) {
ClassScopePtr cls = ar->findClass(spec->getName());
assert(cls && !cls->isRedeclaring());
spec->outputCPPFastObjectCast(cg, ar, getScope(), true);
}
assert(Type::HasFastCastMethod(spec));
const char *nullGetMethod = nullptr;
switch (spec->getKindOf()) {
case Type::KindOfArray:
nullGetMethod = "getArrayDataOrNull";
break;
case Type::KindOfString:
nullGetMethod = "getStringDataOrNull";
break;
case Type::KindOfObject:
assert(spec->isSpecificObject());
nullGetMethod = "getObjectDataOrNull";
break;
default:
break;
}
assert(!needsNullGet || nullGetMethod != nullptr);
cg_printf("%s", paramName.c_str());
if (needsNullGet) {
cg_printf(".%s()", nullGetMethod);
} else {
const string &fastCast = Type::GetFastCastMethod(spec, true, true);
assert(!fastCast.empty());
cg_printf(".%s()", fastCast.c_str());
}
} else if (needsNullGet &&
funcScope->getParamType(i)->isStandardObject()) {
// a standard object can only be inferred if the spec type is
// a specific object - also, we only do this if we have
// a default value of null specified since otherwise,
// the passed in parameter cannot be null anyways
// and we do not have to generate a special type
// check in the wrapper
assert(spec->isSpecificObject());
cg_printf("%s.getObjectDataOrNull()", paramName.c_str());
} else {
cg_printf("%s", paramName.c_str());
}
} else {
cg_printf("%s", paramName.c_str());
}
}
if (funcScope->isVariableArgument()) {
cg_printf(", args");
}
cg_printf(");\n");
}
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");
}