Comparar commits
49 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| a3803ffa8a | |||
| a85369c7ce | |||
| 1cea03f3f7 | |||
| 52db7956bb | |||
| f6f1655ca5 | |||
| 3fa077dd6f | |||
| 4caa51283a | |||
| 4f70af91f7 | |||
| 8a0dd4fae4 | |||
| afd5f72964 | |||
| ec6617f6bd | |||
| 964de35b10 | |||
| 819058d081 | |||
| e6542d070a | |||
| 83bd8ff445 | |||
| cf10678119 | |||
| 5226f80d05 | |||
| a2ffdae2cf | |||
| 785cf27e00 | |||
| b32b2ec879 | |||
| 1f638e631c | |||
| 1ab14f87af | |||
| fefd56e9d7 | |||
| 17049b3a42 | |||
| be4c7698eb | |||
| 0dcbc83f74 | |||
| d660972153 | |||
| ca437add64 | |||
| 3611bf3d58 | |||
| 565c8477f8 | |||
| 8f3512f6d3 | |||
| 7241c247dd | |||
| f06575d4e2 | |||
| cc897749f8 | |||
| b597e3a753 | |||
| 8ec621676d | |||
| a134b25671 | |||
| 15fa1ebfde | |||
| ce75713972 | |||
| adc56c6d8c | |||
| 3fb0fe8a6a | |||
| 38306b527f | |||
| e4e3bee5d3 | |||
| d25adc550b | |||
| 8b23419a37 | |||
| af5623b4fc | |||
| b51003b5aa | |||
| 5f019f5b43 | |||
| 35e347efa5 |
@@ -1503,7 +1503,8 @@ ExpressionPtr AliasManager::canonicalizeNode(
|
||||
e->is(Expression::KindOfSimpleVariable) &&
|
||||
!e->isThis()) {
|
||||
Symbol *s = spc(SimpleVariable, e)->getSymbol();
|
||||
if (s && !s->isParameter() && !s->isClosureVar()) {
|
||||
if (s && !s->isParameter() && !s->isGeneratorParameter() &&
|
||||
!s->isClosureVar()) {
|
||||
rep = e->makeConstant(m_arp, "null");
|
||||
Compiler::Error(Compiler::UseUndeclaredVariable, e);
|
||||
if (m_variables->getAttribute(VariableTable::ContainsCompact)) {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <compiler/analysis/variable_table.h>
|
||||
#include <compiler/construct.h>
|
||||
#include <compiler/expression/class_constant_expression.h>
|
||||
#include <compiler/expression/closure_expression.h>
|
||||
#include <compiler/expression/constant_expression.h>
|
||||
#include <compiler/expression/scalar_expression.h>
|
||||
#include <compiler/expression/unary_op_expression.h>
|
||||
@@ -32,6 +33,7 @@
|
||||
#include <compiler/option.h>
|
||||
#include <compiler/parser/parser.h>
|
||||
#include <compiler/statement/interface_statement.h>
|
||||
#include <compiler/statement/function_statement.h>
|
||||
#include <compiler/statement/method_statement.h>
|
||||
#include <compiler/statement/statement_list.h>
|
||||
#include <runtime/base/builtin_functions.h>
|
||||
@@ -445,6 +447,7 @@ ClassScope::importTraitMethod(const TraitMethod& traitMethod,
|
||||
cloneMeth->getModifiers()));
|
||||
cloneMeth->resetScope(cloneFuncScope, true);
|
||||
cloneFuncScope->setOuterScope(shared_from_this());
|
||||
informClosuresAboutScopeClone(cloneMeth, cloneFuncScope, ar);
|
||||
|
||||
cloneMeth->addTraitMethodToScope(ar,
|
||||
dynamic_pointer_cast<ClassScope>(shared_from_this()));
|
||||
@@ -452,6 +455,34 @@ ClassScope::importTraitMethod(const TraitMethod& traitMethod,
|
||||
return cloneMeth;
|
||||
}
|
||||
|
||||
void ClassScope::informClosuresAboutScopeClone(
|
||||
ConstructPtr root,
|
||||
FunctionScopePtr outerScope,
|
||||
AnalysisResultPtr ar) {
|
||||
|
||||
if (!root) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < root->getKidCount(); i++) {
|
||||
ConstructPtr cons = root->getNthKid(i);
|
||||
ClosureExpressionPtr closure =
|
||||
dynamic_pointer_cast<ClosureExpression>(cons);
|
||||
|
||||
if (!closure) {
|
||||
informClosuresAboutScopeClone(cons, outerScope, ar);
|
||||
continue;
|
||||
}
|
||||
|
||||
FunctionStatementPtr func = closure->getClosureFunction();
|
||||
HPHP::FunctionScopePtr funcScope = func->getFunctionScope();
|
||||
assert(funcScope->isClosure());
|
||||
funcScope->addClonedTraitOuterScope(outerScope);
|
||||
// Don't need to recurse
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ClassScope::addImportTraitMethod(const TraitMethod &traitMethod,
|
||||
const string &methName) {
|
||||
m_importMethToTraitMap[methName].push_back(traitMethod);
|
||||
|
||||
@@ -437,6 +437,9 @@ private:
|
||||
|
||||
void addImportTraitMethod(const TraitMethod &traitMethod,
|
||||
const std::string &methName);
|
||||
void informClosuresAboutScopeClone(ConstructPtr root,
|
||||
FunctionScopePtr outerScope,
|
||||
AnalysisResultPtr ar);
|
||||
|
||||
void setImportTraitMethodModifiers(const std::string &methName,
|
||||
ClassScopePtr traitCls,
|
||||
|
||||
@@ -5200,10 +5200,17 @@ void EmitterVisitor::emitPostponedMeths() {
|
||||
initScalar(dv, vNode);
|
||||
pi.setDefaultValue(dv);
|
||||
|
||||
// Simple case: it's a scalar value so we just serialize it
|
||||
VariableSerializer vs(VariableSerializer::PHPOutput);
|
||||
String result = vs.serialize(tvAsCVarRef(&dv), true);
|
||||
phpCode = StringData::GetStaticString(result.data());
|
||||
std::string orig = vNode->getComment();
|
||||
if (orig.empty()) {
|
||||
// Simple case: it's a scalar value so we just serialize it
|
||||
VariableSerializer vs(VariableSerializer::PHPOutput);
|
||||
String result = vs.serialize(tvAsCVarRef(&dv), true);
|
||||
phpCode = StringData::GetStaticString(result.get());
|
||||
} else {
|
||||
// This was optimized from a Constant, or ClassConstant
|
||||
// use the original string
|
||||
phpCode = StringData::GetStaticString(orig);
|
||||
}
|
||||
} else {
|
||||
// Non-scalar, so we have to output PHP from the AST node
|
||||
std::ostringstream os;
|
||||
|
||||
@@ -410,6 +410,46 @@ bool FunctionScope::containsReference() const {
|
||||
return m_attribute & FileScope::ContainsReference;
|
||||
}
|
||||
|
||||
void FunctionScope::setContainsThis(bool f /* = true */) {
|
||||
m_containsThis = f;
|
||||
|
||||
BlockScopePtr bs(this->getOuterScope());
|
||||
while (bs && bs->is(BlockScope::FunctionScope)) {
|
||||
FunctionScopePtr fs = static_pointer_cast<FunctionScope>(bs);
|
||||
if (!fs->isClosure()) {
|
||||
break;
|
||||
}
|
||||
fs->setContainsThis(f);
|
||||
bs = bs->getOuterScope();
|
||||
}
|
||||
|
||||
for (auto it = m_clonedTraitOuterScope.begin(); it != m_clonedTraitOuterScope.end(); it++) {
|
||||
(*it)->setContainsThis(f);
|
||||
}
|
||||
}
|
||||
|
||||
void FunctionScope::setContainsBareThis(bool f, bool ref /* = false */) {
|
||||
if (f) {
|
||||
m_containsBareThis |= ref ? 2 : 1;
|
||||
} else {
|
||||
m_containsBareThis = 0;
|
||||
}
|
||||
|
||||
BlockScopePtr bs(this->getOuterScope());
|
||||
while (bs && bs->is(BlockScope::FunctionScope)) {
|
||||
FunctionScopePtr fs = static_pointer_cast<FunctionScope>(bs);
|
||||
if (!fs->isClosure()) {
|
||||
break;
|
||||
}
|
||||
fs->setContainsBareThis(f, ref);
|
||||
bs = bs->getOuterScope();
|
||||
}
|
||||
|
||||
for (auto it = m_clonedTraitOuterScope.begin(); it != m_clonedTraitOuterScope.end(); it++) {
|
||||
(*it)->setContainsBareThis(f, ref);
|
||||
}
|
||||
}
|
||||
|
||||
bool FunctionScope::hasImpl() const {
|
||||
if (!isUserFunction()) {
|
||||
return !isAbstract();
|
||||
|
||||
@@ -176,6 +176,13 @@ public:
|
||||
m_volatile = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell this function about another outer scope that contains it.
|
||||
*/
|
||||
void addClonedTraitOuterScope(FunctionScopePtr scope) {
|
||||
m_clonedTraitOuterScope.push_back(scope);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set original name of the function, without case being lowered.
|
||||
*/
|
||||
@@ -237,16 +244,10 @@ public:
|
||||
* Whether this function contains a usage of $this
|
||||
*/
|
||||
bool containsThis() const { return m_containsThis;}
|
||||
void setContainsThis(bool f=true) { m_containsThis = f;}
|
||||
void setContainsThis(bool f = true);
|
||||
bool containsBareThis() const { return m_containsBareThis; }
|
||||
bool containsRefThis() const { return m_containsBareThis & 2; }
|
||||
void setContainsBareThis(bool f, bool ref = false) {
|
||||
if (f) {
|
||||
m_containsBareThis |= ref ? 2 : 1;
|
||||
} else {
|
||||
m_containsBareThis = 0;
|
||||
}
|
||||
}
|
||||
void setContainsBareThis(bool f, bool ref = false);
|
||||
/**
|
||||
* How many parameters a caller should provide.
|
||||
*/
|
||||
@@ -492,6 +493,7 @@ private:
|
||||
ExpressionListPtr m_closureValues;
|
||||
ReadWriteMutex m_inlineMutex;
|
||||
unsigned m_nextID; // used when cloning generators for traits
|
||||
std::list<FunctionScopeRawPtr> m_clonedTraitOuterScope;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -461,25 +461,11 @@ bool LiveDict::color(TypePtr type) {
|
||||
if (Type::SameType(type, e->getCPPType())) {
|
||||
SimpleVariablePtr sv(
|
||||
static_pointer_cast<SimpleVariable>(e));
|
||||
bool isGenParam = false;
|
||||
if (sv->getFunctionScope()->isGenerator()) {
|
||||
// do not allow coalescing of symbols which are parameters/use vars
|
||||
// in the generator (sym->isParameter() will be false b/c we are in
|
||||
// the scope of the generator function)
|
||||
FunctionScopeRawPtr origScope(sv->getFunctionScope()->getOrigGenFS());
|
||||
assert(origScope);
|
||||
Symbol *origSym =
|
||||
origScope->getVariables()->getSymbol(sv->getName());
|
||||
if (origSym &&
|
||||
(origSym->isParameter() || origSym->isClosureVar())) {
|
||||
isGenParam = true;
|
||||
}
|
||||
}
|
||||
Symbol *sym = sv->getSymbol();
|
||||
if (sym &&
|
||||
!sym->isGlobal() &&
|
||||
!sym->isParameter() &&
|
||||
!isGenParam &&
|
||||
!sym->isGeneratorParameter() &&
|
||||
!sym->isClosureVar() &&
|
||||
!sym->isStatic() &&
|
||||
!e->isThis()) {
|
||||
@@ -623,9 +609,8 @@ public:
|
||||
always_assert(e && e->is(Expression::KindOfSimpleVariable));
|
||||
SimpleVariablePtr sv(static_pointer_cast<SimpleVariable>(e));
|
||||
Symbol *sym = sv->getSymbol();
|
||||
bool inGen = sv->getFunctionScope()->isGenerator();
|
||||
if (!sym || sym->isGlobal() || sym->isStatic() || sym->isParameter() ||
|
||||
sym->isClosureVar() || sv->isThis() || inGen) {
|
||||
sym->isGeneratorParameter() || sym->isClosureVar() || sv->isThis()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -115,6 +115,8 @@ public:
|
||||
bool isIndirectAltered() const { return m_flags.m_indirectAltered; }
|
||||
bool isReferenced() const { return !m_flags.m_notReferenced; }
|
||||
bool isHidden() const { return m_flags.m_hidden; }
|
||||
bool isGeneratorParameter() const { return m_flags.m_generatorParameter; }
|
||||
bool isRefGeneratorParameter() const { return m_flags.m_refGeneratorParameter; }
|
||||
bool isClosureVar() const { return m_flags.m_closureVar; }
|
||||
bool isRefClosureVar() const { return m_flags.m_refClosureVar; }
|
||||
bool isPassClosureVar() const { return m_flags.m_passClosureVar; }
|
||||
@@ -140,6 +142,8 @@ public:
|
||||
void setIndirectAltered() { m_flags.m_indirectAltered = true; }
|
||||
void setReferenced() { m_flags.m_notReferenced = false; }
|
||||
void setHidden() { m_flags.m_hidden = true; }
|
||||
void setGeneratorParameter() { m_flags.m_generatorParameter = true; }
|
||||
void setRefGeneratorParameter() { m_flags.m_refGeneratorParameter = true; }
|
||||
void setClosureVar() { m_flags.m_closureVar = true; }
|
||||
void setRefClosureVar() { m_flags.m_refClosureVar = true; }
|
||||
void setPassClosureVar() { m_flags.m_passClosureVar = true; }
|
||||
@@ -185,7 +189,7 @@ private:
|
||||
std::string m_name;
|
||||
unsigned int m_hash;
|
||||
union {
|
||||
unsigned m_flags_val;
|
||||
uint64_t m_flags_val;
|
||||
struct {
|
||||
/* internal */
|
||||
unsigned m_declaration_set : 1;
|
||||
@@ -219,6 +223,8 @@ private:
|
||||
unsigned m_indirectAltered : 1;
|
||||
unsigned m_notReferenced : 1;
|
||||
unsigned m_hidden : 1;
|
||||
unsigned m_generatorParameter : 1;
|
||||
unsigned m_refGeneratorParameter : 1;
|
||||
unsigned m_closureVar : 1;
|
||||
unsigned m_refClosureVar : 1;
|
||||
unsigned m_passClosureVar : 1;
|
||||
@@ -227,6 +233,10 @@ private:
|
||||
unsigned m_stashedVal : 1;
|
||||
unsigned m_reseated : 1;
|
||||
} m_flags;
|
||||
|
||||
static_assert(
|
||||
sizeof(m_flags_val) == sizeof(m_flags),
|
||||
"m_flags_val must cover all the flags");
|
||||
};
|
||||
ConstructPtr m_declaration;
|
||||
ConstructPtr m_value;
|
||||
|
||||
@@ -567,6 +567,10 @@ void VariableTable::clearUsed() {
|
||||
} else {
|
||||
sym.second.setReferenced();
|
||||
}
|
||||
|
||||
if (sym.second.isRefGeneratorParameter()) {
|
||||
sym.second.setReferenced();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -473,8 +473,8 @@ int prepareOptions(CompilerOptions &po, int argc, char **argv) {
|
||||
(Util::format_pattern(po.excludeStaticPatterns[i], true));
|
||||
}
|
||||
|
||||
Option::OutputHHBC = true;
|
||||
if (po.target == "hhbc" || po.target == "run") {
|
||||
Option::OutputHHBC = true;
|
||||
Option::AnalyzePerfectVirtuals = false;
|
||||
}
|
||||
|
||||
|
||||
@@ -257,6 +257,14 @@ ExpressionPtr ArrayElementExpression::preOptimize(AnalysisResultConstPtr ar) {
|
||||
return replaceValue(makeConstant(ar, "null"));
|
||||
}
|
||||
if (m_offset->isScalar() && m_offset->getScalarValue(o)) {
|
||||
if (v.isString()) {
|
||||
if (!o.isInteger() ||
|
||||
o.toInt64Val() < 0 ||
|
||||
o.toInt64Val() >= v.toCStrRef().size()) {
|
||||
// warnings should be raised...
|
||||
return ExpressionPtr();
|
||||
}
|
||||
}
|
||||
try {
|
||||
g_context->setThrowAllErrors(true);
|
||||
Variant res = v.rvalAt(
|
||||
|
||||
@@ -249,6 +249,14 @@ ExpressionPtr AssignmentExpression::preOptimize(AnalysisResultConstPtr ar) {
|
||||
g_context->setThrowAllErrors(true);
|
||||
if (aoff) {
|
||||
if (!aoff->getScalarValue(o)) break;
|
||||
if (v.isString()) {
|
||||
if (!o.isInteger() ||
|
||||
o.toInt64Val() < 0 ||
|
||||
o.toInt64Val() >= v.toCStrRef().size()) {
|
||||
// warnings should be raised...
|
||||
break;
|
||||
}
|
||||
}
|
||||
v.set(o, r);
|
||||
} else {
|
||||
v.append(r);
|
||||
|
||||
@@ -161,12 +161,16 @@ bool BinaryOpExpression::isLogicalOrOperator() const {
|
||||
}
|
||||
|
||||
ExpressionPtr BinaryOpExpression::unneededHelper() {
|
||||
if (!isShortCircuitOperator() || !m_exp2->getContainedEffects()) {
|
||||
bool shortCircuit = isShortCircuitOperator();
|
||||
if (!m_exp2->getContainedEffects() ||
|
||||
(!shortCircuit && !m_exp1->getContainedEffects())) {
|
||||
return Expression::unneededHelper();
|
||||
}
|
||||
|
||||
m_exp2 = m_exp2->unneeded();
|
||||
m_exp2->setExpectedType(Type::Boolean);
|
||||
if (shortCircuit) {
|
||||
m_exp2 = m_exp2->unneeded();
|
||||
m_exp2->setExpectedType(Type::Boolean);
|
||||
}
|
||||
return static_pointer_cast<Expression>(shared_from_this());
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
void pushConst(const std::string &name);
|
||||
void popConst();
|
||||
void setComment(const std::string &comment) { m_comment = comment;}
|
||||
const std::string getComment() { return m_comment;}
|
||||
std::string getComment() { return m_comment;}
|
||||
bool isValid() const { return m_valid; }
|
||||
bool isDynamic() const { return m_dynamic; }
|
||||
private:
|
||||
|
||||
@@ -171,6 +171,7 @@ public:
|
||||
}
|
||||
bool hasSubExpr(ExpressionPtr sub) const;
|
||||
virtual void setComment(const std::string &) {}
|
||||
virtual std::string getComment() { return ""; }
|
||||
/**
|
||||
* Set this expression's error flags.
|
||||
*/
|
||||
|
||||
@@ -69,7 +69,7 @@ public:
|
||||
int64_t getHash() const;
|
||||
|
||||
void setComment(const std::string &comment) { m_comment = comment;}
|
||||
const std::string getComment() { return m_comment;}
|
||||
std::string getComment() { return m_comment;}
|
||||
|
||||
|
||||
bool getString(const std::string *&s) const;
|
||||
|
||||
@@ -1138,7 +1138,7 @@ TypePtr SimpleFunctionCall::inferAndCheck(AnalysisResultPtr ar, TypePtr type,
|
||||
setAttribute(VariableTable::NeedGlobalPointer);
|
||||
}
|
||||
if (m_params) {
|
||||
if (func && func->isRedeclaring()) {
|
||||
if (Option::WholeProgram && func && func->isRedeclaring()) {
|
||||
FunctionScope::FunctionInfoPtr info =
|
||||
FunctionScope::GetFunctionInfo(m_name);
|
||||
always_assert(info);
|
||||
|
||||
@@ -172,7 +172,7 @@ void SimpleVariable::analyzeProgram(AnalysisResultPtr ar) {
|
||||
}
|
||||
}
|
||||
} else if (ar->getPhase() == AnalysisResult::AnalyzeFinal) {
|
||||
if (m_sym) {
|
||||
if (m_sym && !m_this) {
|
||||
if (!m_sym->isSystem() &&
|
||||
!(getContext() &
|
||||
(LValue|RefValue|RefParameter|UnsetContext|ExistContext)) &&
|
||||
|
||||
@@ -166,7 +166,7 @@ std::string Option::GetSystemRoot() {
|
||||
throw Exception("Environment variable HPHP_HOME is not set.");
|
||||
}
|
||||
SystemRoot = home;
|
||||
SystemRoot += "/src";
|
||||
SystemRoot += "/hphp";
|
||||
}
|
||||
return SystemRoot;
|
||||
}
|
||||
|
||||
@@ -1569,10 +1569,13 @@ void Parser::onClosure(Token &out, Token &ret, Token &ref, Token ¶ms,
|
||||
Token func, name;
|
||||
onFunction(func, ret, ref, name, params, stmts, 0);
|
||||
|
||||
ClosureExpressionPtr closure = NEW_EXP(
|
||||
ClosureExpression,
|
||||
dynamic_pointer_cast<FunctionStatement>(func->stmt),
|
||||
dynamic_pointer_cast<ExpressionList>(cparams->exp));
|
||||
closure->getClosureFunction()->setContainingClosure(closure);
|
||||
out.reset();
|
||||
out->exp = NEW_EXP(ClosureExpression,
|
||||
dynamic_pointer_cast<FunctionStatement>(func->stmt),
|
||||
dynamic_pointer_cast<ExpressionList>(cparams->exp));
|
||||
out->exp = closure;
|
||||
}
|
||||
|
||||
void Parser::onClosureParam(Token &out, Token *params, Token ¶m,
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include <compiler/expression/parameter_expression.h>
|
||||
#include <compiler/expression/assignment_expression.h>
|
||||
#include <compiler/expression/simple_variable.h>
|
||||
#include <compiler/expression/closure_expression.h>
|
||||
|
||||
#include <compiler/analysis/ast_walker.h>
|
||||
#include <compiler/analysis/analysis_result.h>
|
||||
@@ -372,15 +373,44 @@ void MethodStatement::analyzeProgram(AnalysisResultPtr ar) {
|
||||
if (ar->getPhase() == AnalysisResult::AnalyzeAll) {
|
||||
funcScope->setParamSpecs(ar);
|
||||
if (funcScope->isGenerator()) {
|
||||
MethodStatementRawPtr orig = getOrigGeneratorFunc();
|
||||
VariableTablePtr variables = funcScope->getVariables();
|
||||
|
||||
Symbol *cont = variables->getSymbol(CONTINUATION_OBJECT_NAME);
|
||||
cont->setHidden();
|
||||
getOrigGeneratorFunc()->getFunctionScope()->addUse(
|
||||
funcScope, BlockScope::UseKindClosure);
|
||||
getOrigGeneratorFunc()->getFunctionScope()->setContainsBareThis(
|
||||
|
||||
orig->getFunctionScope()->addUse(funcScope, BlockScope::UseKindClosure);
|
||||
orig->getFunctionScope()->setContainsBareThis(
|
||||
funcScope->containsBareThis(), funcScope->containsRefThis());
|
||||
getOrigGeneratorFunc()->getFunctionScope()->setContainsThis(
|
||||
funcScope->containsThis());
|
||||
orig->getFunctionScope()->setContainsThis(funcScope->containsThis());
|
||||
|
||||
if (ExpressionListPtr params = orig->getParams()) {
|
||||
for (int i = 0; i < params->getCount(); ++i) {
|
||||
auto param = dynamic_pointer_cast<ParameterExpression>((*params)[i]);
|
||||
Symbol *gp = variables->addDeclaredSymbol(
|
||||
param->getName(), ConstructPtr());
|
||||
gp->setGeneratorParameter();
|
||||
if (param->isRef()) {
|
||||
gp->setRefGeneratorParameter();
|
||||
gp->setReferenced();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (ClosureExpressionRawPtr closure = orig->getContainingClosure()) {
|
||||
if (ExpressionListPtr cvars = closure->getClosureVariables()) {
|
||||
for (int i = 0; i < cvars->getCount(); ++i) {
|
||||
auto param = dynamic_pointer_cast<ParameterExpression>((*cvars)[i]);
|
||||
Symbol *gp = variables->addDeclaredSymbol(
|
||||
param->getName(), ConstructPtr());
|
||||
gp->setGeneratorParameter();
|
||||
if (param->isRef()) {
|
||||
gp->setRefGeneratorParameter();
|
||||
gp->setReferenced();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (funcScope->isSepExtension() ||
|
||||
Option::IsDynamicFunction(m_method, m_name) || Option::AllDynamic) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
DECLARE_BOOST_TYPES(ClosureExpression);
|
||||
DECLARE_BOOST_TYPES(ModifierExpression);
|
||||
DECLARE_BOOST_TYPES(ExpressionList);
|
||||
DECLARE_BOOST_TYPES(StatementList);
|
||||
@@ -104,6 +105,13 @@ public:
|
||||
return m_generatorFunc;
|
||||
}
|
||||
|
||||
void setContainingClosure(ClosureExpressionRawPtr exp) {
|
||||
m_containingClosure = exp;
|
||||
}
|
||||
ClosureExpressionRawPtr getContainingClosure() const {
|
||||
return m_containingClosure;
|
||||
}
|
||||
|
||||
void setClassName(const std::string &name) { m_className = name; }
|
||||
void setOriginalClassName(const std::string &name) {
|
||||
m_originalClassName = name;
|
||||
@@ -127,6 +135,7 @@ protected:
|
||||
std::string m_docComment;
|
||||
MethodStatementRawPtr m_origGeneratorFunc;
|
||||
MethodStatementRawPtr m_generatorFunc;
|
||||
ClosureExpressionRawPtr m_containingClosure;
|
||||
ExpressionListPtr m_attrList;
|
||||
|
||||
void setSpecialMethod(ClassScopePtr classScope);
|
||||
|
||||
@@ -31,10 +31,13 @@ int main(int argc, char** argv) {
|
||||
return HPHP::execute_program(argc, argv);
|
||||
}
|
||||
std::vector<char*> args;
|
||||
args.insert(args.begin(), argv, argv + argc);
|
||||
args.push_back(argv[0]);
|
||||
args.push_back("-vRepo.Authoritative=true");
|
||||
args.push_back("-vRepo.Local.Mode=r-");
|
||||
repo = "-vRepo.Local.Path=" + repo;
|
||||
args.push_back(const_cast<char*>(repo.c_str()));
|
||||
if (argc > 1) {
|
||||
args.insert(args.end(), argv + 1, argv + argc);
|
||||
}
|
||||
return HPHP::execute_program(args.size(), &args[0]);
|
||||
}
|
||||
|
||||
@@ -345,8 +345,9 @@ DefineFunction(
|
||||
'desc' => "This function checks if the given property exists in the specified class.\n\nAs opposed with isset(), property_exists() returns TRUE even if the property has the value NULL.",
|
||||
'flags' => HasDocComment,
|
||||
'return' => array(
|
||||
'type' => Boolean,
|
||||
'type' => Variant,
|
||||
'desc' => "Returns TRUE if the property exists, FALSE if it doesn't exist or NULL in case of an error.",
|
||||
'predicted_type' => Boolean,
|
||||
),
|
||||
'args' => array(
|
||||
array(
|
||||
|
||||
@@ -345,7 +345,8 @@ vm_decode_function(CVarRef function,
|
||||
this_ = function.asCObjRef().get();
|
||||
cls = nullptr;
|
||||
const HPHP::VM::Func *f = this_->getVMClass()->lookupMethod(invokeStr);
|
||||
if (f != nullptr && (f->attrs() & HPHP::VM::AttrStatic)) {
|
||||
if (f != nullptr &&
|
||||
((f->attrs() & HPHP::VM::AttrStatic) && !f->isClosureBody())) {
|
||||
// If __invoke is static, invoke it as such
|
||||
cls = this_->getVMClass();
|
||||
this_ = nullptr;
|
||||
|
||||
@@ -594,11 +594,11 @@ bool ClassInfo::HasAccess(CStrRef className, CStrRef methodName,
|
||||
clsInfo->hasMethod(methodName, defClass);
|
||||
if (!methodInfo) return false;
|
||||
if (methodInfo->attribute & ClassInfo::IsPublic) return true;
|
||||
CStrRef ctxName = g_vmContext->getContextClassName();
|
||||
if (ctxName->size() == 0) {
|
||||
VM::Class* ctx = g_vmContext->getContextClass();
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
const ClassInfo *ctxClass = ClassInfo::FindClass(ctxName);
|
||||
const ClassInfo *ctxClass = ClassInfo::FindClass(ctx->nameRef());
|
||||
bool hasObject = hasCallObject || g_vmContext->getThis();
|
||||
if (ctxClass) {
|
||||
return ctxClass->checkAccess(defClass, methodInfo, staticCall, hasObject);
|
||||
|
||||
@@ -591,8 +591,8 @@ public:
|
||||
|
||||
HPHP::VM::ActRec* getStackFrame();
|
||||
ObjectData* getThis();
|
||||
CStrRef getContextClassName();
|
||||
CStrRef getParentContextClassName();
|
||||
VM::Class* getContextClass();
|
||||
VM::Class* getParentContextClass();
|
||||
CStrRef getContainingFileName();
|
||||
int getLine();
|
||||
Array getCallerInfo();
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef __PREG_H__
|
||||
#define __PREG_H_
|
||||
#ifndef incl_HPHP_PREG_H_
|
||||
#define incl_HPHP_PREG_H_
|
||||
|
||||
#include <runtime/base/types.h>
|
||||
#include <runtime/base/complex_types.h>
|
||||
|
||||
@@ -72,7 +72,13 @@ void raise_recoverable_error(const char *fmt, ...) {
|
||||
raise_recoverable_error(msg);
|
||||
}
|
||||
|
||||
static int64_t g_notice_counter = 0;
|
||||
|
||||
void raise_strict_warning(const std::string &msg) {
|
||||
if (RuntimeOption::NoticeFrequency <= 0 ||
|
||||
(g_notice_counter++) % RuntimeOption::NoticeFrequency != 0) {
|
||||
return;
|
||||
}
|
||||
int errnum = ErrorConstants::STRICT;
|
||||
if (!g_context->errorNeedsHandling(errnum, true,
|
||||
ExecutionContext::NeverThrow)) {
|
||||
@@ -84,6 +90,10 @@ void raise_strict_warning(const std::string &msg) {
|
||||
}
|
||||
|
||||
void raise_strict_warning(const char *fmt, ...) {
|
||||
if (RuntimeOption::NoticeFrequency <= 0 ||
|
||||
(g_notice_counter++) % RuntimeOption::NoticeFrequency != 0) {
|
||||
return;
|
||||
}
|
||||
std::string msg;
|
||||
int errnum = ErrorConstants::STRICT;
|
||||
if (!g_context->errorNeedsHandling(errnum, true,
|
||||
@@ -117,11 +127,11 @@ void raise_warning(const std::string &msg) {
|
||||
}
|
||||
|
||||
void raise_warning(const char *fmt, ...) {
|
||||
std::string msg;
|
||||
if (RuntimeOption::WarningFrequency <= 0 ||
|
||||
(g_warning_counter++) % RuntimeOption::WarningFrequency != 0) {
|
||||
return;
|
||||
}
|
||||
std::string msg;
|
||||
int errnum = ErrorConstants::WARNING;
|
||||
if (!g_context->errorNeedsHandling(errnum, true,
|
||||
ExecutionContext::NeverThrow)) {
|
||||
@@ -151,8 +161,6 @@ void raise_debugging(const char *fmt, ...) {
|
||||
raise_debugging(msg);
|
||||
}
|
||||
|
||||
static int64_t g_notice_counter = 0;
|
||||
|
||||
void raise_notice(const std::string &msg) {
|
||||
if (RuntimeOption::NoticeFrequency <= 0 ||
|
||||
(g_notice_counter++) % RuntimeOption::NoticeFrequency != 0) {
|
||||
@@ -169,11 +177,11 @@ void raise_notice(const std::string &msg) {
|
||||
}
|
||||
|
||||
void raise_notice(const char *fmt, ...) {
|
||||
std::string msg;
|
||||
if (RuntimeOption::NoticeFrequency <= 0 ||
|
||||
(g_notice_counter++) % RuntimeOption::NoticeFrequency != 0) {
|
||||
return;
|
||||
}
|
||||
std::string msg;
|
||||
int errnum = ErrorConstants::NOTICE;
|
||||
if (!g_context->errorNeedsHandling(errnum, true,
|
||||
ExecutionContext::NeverThrow)) {
|
||||
|
||||
@@ -17,8 +17,11 @@
|
||||
#ifndef __HPHP_RUNTIME_ERROR_H__
|
||||
#define __HPHP_RUNTIME_ERROR_H__
|
||||
|
||||
#include <cstdarg>
|
||||
#include <string>
|
||||
|
||||
#include "util/base.h"
|
||||
|
||||
namespace HPHP {
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
@@ -423,7 +423,7 @@ public:
|
||||
F(bool, HHIREnableCalleeSavedOpt, true) \
|
||||
F(bool, HHIREnablePreColoring, true) \
|
||||
F(bool, HHIREnableCoalescing, true) \
|
||||
F(bool, HHIREnableMmx, true) \
|
||||
F(bool, HHIREnableMmx, false) \
|
||||
F(bool, HHIREnableRefCountOpt, true) \
|
||||
F(bool, HHIREnableSinking, true) \
|
||||
F(bool, HHIRGenerateAsserts, debug) \
|
||||
|
||||
@@ -14,19 +14,26 @@
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#include <runtime/base/util/curl_tls_workarounds.h>
|
||||
#include <runtime/base/runtime_option.h>
|
||||
#include <curl/curl.h>
|
||||
#include <curl/easy.h>
|
||||
#include <openssl/ssl.h>
|
||||
|
||||
#include "runtime/base/util/curl_tls_workarounds.h"
|
||||
#include "runtime/base/runtime_error.h"
|
||||
#include "runtime/base/runtime_option.h"
|
||||
|
||||
namespace HPHP {
|
||||
|
||||
CURLcode curl_tls_workarounds_cb(CURL *curl, void *sslctx, void *parm) {
|
||||
// Check to see if workarounds are enabled.
|
||||
if (RuntimeOption::TLSDisableTLS1_2) {
|
||||
#ifdef SSL_OP_NO_TLSv1_2
|
||||
SSL_CTX* ctx = (SSL_CTX*)sslctx;
|
||||
SSL_CTX_set_options(ctx, SSL_CTX_get_options (ctx) | SSL_OP_NO_TLSv1_2);
|
||||
#else
|
||||
raise_notice("TLSDisableTLS1_2 enabled, but this version of "
|
||||
"SSL does not support that option");
|
||||
#endif
|
||||
}
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
@@ -150,8 +150,8 @@ void StringBuffer::release() {
|
||||
}
|
||||
|
||||
void StringBuffer::resize(int size) {
|
||||
assert(size >= 0 && size < m_cap);
|
||||
if (size >= 0 && size < m_cap) {
|
||||
assert(size >= 0 && size <= m_cap);
|
||||
if (size >= 0 && size <= m_cap) {
|
||||
m_len = size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,7 +697,12 @@ Variant ZendPack::unpack(CStrRef fmt, CStrRef data) {
|
||||
}
|
||||
|
||||
v |= unpack(&input[inputpos], sizeof(int), issigned, int_map);
|
||||
ret.set(String(n, CopyString), v);
|
||||
if (type == 'i') {
|
||||
ret.set(String(n, CopyString), v);
|
||||
} else {
|
||||
uint64_t u64 = uint32_t(v);
|
||||
ret.set(String(n, CopyString), u64);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -623,8 +623,6 @@ static Variant f_hphp_get_iterator(VRefParam iterable, bool isMutable) {
|
||||
CREATE_VECTOR1(iterable));
|
||||
}
|
||||
if (iterable.isObject()) {
|
||||
CStrRef context = g_vmContext->getContextClassName();
|
||||
|
||||
ObjectData *obj = iterable.getObjectData();
|
||||
Variant iterator;
|
||||
while (obj->instanceof(SystemLib::s_IteratorAggregateClass)) {
|
||||
@@ -632,6 +630,8 @@ static Variant f_hphp_get_iterator(VRefParam iterable, bool isMutable) {
|
||||
if (!iterator.isObject()) break;
|
||||
obj = iterator.getObjectData();
|
||||
}
|
||||
VM::Class*ctx = g_vmContext->getContextClass();
|
||||
CStrRef context = ctx ? ctx->nameRef() : empty_string;
|
||||
if (isMutable) {
|
||||
if (obj->instanceof(SystemLib::s_IteratorClass)) {
|
||||
throw FatalErrorException("An iterator cannot be used for "
|
||||
|
||||
@@ -113,7 +113,7 @@ Variant f_bzdecompress(CStrRef source, int small /* = 0 */) {
|
||||
/* compression is better then 2:1, need to allocate more memory */
|
||||
bzs.avail_out = source_len;
|
||||
size = (bzs.total_out_hi32 * (unsigned int) -1) + bzs.total_out_lo32;
|
||||
dest = (char *) Util::safe_realloc(dest, bzs.avail_out + 1);
|
||||
dest = (char *) Util::safe_realloc(dest, size + bzs.avail_out + 1);
|
||||
bzs.next_out = dest + size;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,8 @@ static String get_classname(CVarRef class_or_object) {
|
||||
}
|
||||
|
||||
static inline CStrRef ctxClassName() {
|
||||
return g_vmContext->getContextClassName();
|
||||
VM::Class* ctx = g_vmContext->getContextClass();
|
||||
return ctx ? ctx->nameRef() : empty_string;
|
||||
}
|
||||
|
||||
static const VM::Class* get_cls(CVarRef class_or_object) {
|
||||
@@ -260,20 +261,27 @@ bool f_method_exists(CVarRef class_or_object, CStrRef method_name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool f_property_exists(CVarRef class_or_object, CStrRef property) {
|
||||
Variant f_property_exists(CVarRef class_or_object, CStrRef property) {
|
||||
if (class_or_object.isObject()) {
|
||||
CStrRef context = ctxClassName();
|
||||
// Call o_exists for objects, to include dynamic properties.
|
||||
return class_or_object.toObject()->o_propExists(property, context);
|
||||
}
|
||||
const ClassInfo *classInfo =
|
||||
ClassInfo::FindClass(get_classname(class_or_object));
|
||||
while (classInfo) {
|
||||
if (classInfo->hasProperty(property)) {
|
||||
return true;
|
||||
} else {
|
||||
classInfo = classInfo->getParentClassInfo();
|
||||
}
|
||||
if (!class_or_object.isString()) {
|
||||
raise_warning(
|
||||
"First parameter must either be an object or the name of an existing class"
|
||||
);
|
||||
return Variant(Variant::nullInit);
|
||||
}
|
||||
|
||||
VM::Class* cls = VM::Unit::lookupClass(get_classname(class_or_object).get());
|
||||
if (!cls) {
|
||||
return false;
|
||||
}
|
||||
bool accessible;
|
||||
VM::Slot propInd = cls->getDeclPropIndex(cls, property.get(), accessible);
|
||||
if (propInd != VM::kInvalidSlot) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -686,22 +686,23 @@ TypedValue* fg_method_exists(HPHP::VM::ActRec *ar) {
|
||||
|
||||
|
||||
/*
|
||||
bool HPHP::f_property_exists(HPHP::Variant const&, HPHP::String const&)
|
||||
HPHP::Variant HPHP::f_property_exists(HPHP::Variant const&, HPHP::String const&)
|
||||
_ZN4HPHP17f_property_existsERKNS_7VariantERKNS_6StringE
|
||||
|
||||
(return value) => rax
|
||||
class_or_object => rdi
|
||||
property => rsi
|
||||
_rv => rdi
|
||||
class_or_object => rsi
|
||||
property => rdx
|
||||
*/
|
||||
|
||||
bool fh_property_exists(TypedValue* class_or_object, Value* property) asm("_ZN4HPHP17f_property_existsERKNS_7VariantERKNS_6StringE");
|
||||
TypedValue* fh_property_exists(TypedValue* _rv, TypedValue* class_or_object, Value* property) asm("_ZN4HPHP17f_property_existsERKNS_7VariantERKNS_6StringE");
|
||||
|
||||
TypedValue * fg1_property_exists(TypedValue* rv, HPHP::VM::ActRec* ar, int64_t count) __attribute__((noinline,cold));
|
||||
TypedValue * fg1_property_exists(TypedValue* rv, HPHP::VM::ActRec* ar, int64_t count) {
|
||||
TypedValue* args UNUSED = ((TypedValue*)ar) - 1;
|
||||
rv->m_type = KindOfBoolean;
|
||||
tvCastToStringInPlace(args-1);
|
||||
rv->m_data.num = (fh_property_exists((args-0), (Value*)(args-1))) ? 1LL : 0LL;
|
||||
fh_property_exists((rv), (args-0), (Value*)(args-1));
|
||||
if (rv->m_type == KindOfUninit) rv->m_type = KindOfNull;
|
||||
return rv;
|
||||
}
|
||||
|
||||
@@ -711,8 +712,8 @@ TypedValue* fg_property_exists(HPHP::VM::ActRec *ar) {
|
||||
TypedValue* args UNUSED = ((TypedValue*)ar) - 1;
|
||||
if (count == 2LL) {
|
||||
if (IS_STRING_TYPE((args-1)->m_type)) {
|
||||
rv.m_type = KindOfBoolean;
|
||||
rv.m_data.num = (fh_property_exists((args-0), (Value*)(args-1))) ? 1LL : 0LL;
|
||||
fh_property_exists((&(rv)), (args-0), (Value*)(args-1));
|
||||
if (rv.m_type == KindOfUninit) rv.m_type = KindOfNull;
|
||||
frame_free_locals_no_this_inl(ar, 2);
|
||||
memcpy(&ar->m_r, &rv, sizeof(TypedValue));
|
||||
return &ar->m_r;
|
||||
|
||||
@@ -170,15 +170,16 @@ method_name => rsi
|
||||
bool fh_method_exists(TypedValue* class_or_object, Value* method_name) asm("_ZN4HPHP15f_method_existsERKNS_7VariantERKNS_6StringE");
|
||||
|
||||
/*
|
||||
bool HPHP::f_property_exists(HPHP::Variant const&, HPHP::String const&)
|
||||
HPHP::Variant HPHP::f_property_exists(HPHP::Variant const&, HPHP::String const&)
|
||||
_ZN4HPHP17f_property_existsERKNS_7VariantERKNS_6StringE
|
||||
|
||||
(return value) => rax
|
||||
class_or_object => rdi
|
||||
property => rsi
|
||||
_rv => rdi
|
||||
class_or_object => rsi
|
||||
property => rdx
|
||||
*/
|
||||
|
||||
bool fh_property_exists(TypedValue* class_or_object, Value* property) asm("_ZN4HPHP17f_property_existsERKNS_7VariantERKNS_6StringE");
|
||||
TypedValue* fh_property_exists(TypedValue* _rv, TypedValue* class_or_object, Value* property) asm("_ZN4HPHP17f_property_existsERKNS_7VariantERKNS_6StringE");
|
||||
|
||||
/*
|
||||
HPHP::Variant HPHP::f_get_object_vars(HPHP::Variant const&)
|
||||
|
||||
@@ -40,7 +40,7 @@ Variant f_get_parent_class(CVarRef object = null_variant);
|
||||
bool f_is_a(CVarRef class_or_object, CStrRef class_name, bool allow_string = false);
|
||||
bool f_is_subclass_of(CVarRef class_or_object, CStrRef class_name, bool allow_string = true);
|
||||
bool f_method_exists(CVarRef class_or_object, CStrRef method_name);
|
||||
bool f_property_exists(CVarRef class_or_object, CStrRef property);
|
||||
Variant f_property_exists(CVarRef class_or_object, CStrRef property);
|
||||
Variant f_get_object_vars(CVarRef object);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -71,7 +71,7 @@ c_Continuation::~c_Continuation() {
|
||||
contLocal->m_type = KindOfNull;
|
||||
|
||||
if (ar->hasVarEnv()) {
|
||||
VM::VarEnv::destroy(ar->getVarEnv());
|
||||
ar->getVarEnv()->detach(ar);
|
||||
} else {
|
||||
frame_free_locals_inl(ar, m_vmFunc->numLocals());
|
||||
}
|
||||
|
||||
@@ -6016,6 +6016,7 @@ static int exif_process_string_raw(char **result, char *value,
|
||||
* chars up to byte_count, we also have to add a single NUL character to
|
||||
* force end of string.
|
||||
*/
|
||||
*result = 0;
|
||||
if (byte_count) {
|
||||
(*result) = (char*)IM_MALLOC(byte_count + 1);
|
||||
CHECK_ALLOC_R((*result), byte_count + 1, 0);
|
||||
|
||||
@@ -480,7 +480,6 @@ void c_MutableArrayIterator::t___construct(VRefParam array) {
|
||||
m_valid = mi.advance();
|
||||
if (!m_valid) mi.~MArrayIter();
|
||||
} else if (rtv->m_type == KindOfObject) {
|
||||
CStrRef ctxStr = g_vmContext->getContextClassName();
|
||||
if (rtv->m_data.pobj->isCollection()) {
|
||||
raise_error("Collection elements cannot be taken by reference");
|
||||
}
|
||||
@@ -489,7 +488,9 @@ void c_MutableArrayIterator::t___construct(VRefParam array) {
|
||||
if (isIterator) {
|
||||
raise_error("An iterator cannot be used with foreach by reference");
|
||||
}
|
||||
Array iterArray = obj->o_toIterArray(ctxStr, true);
|
||||
VM::Class* ctx = g_vmContext->getContextClass();
|
||||
Array iterArray = obj->o_toIterArray(ctx ? ctx->nameRef() : empty_string,
|
||||
true);
|
||||
ArrayData* ad = iterArray.detach();
|
||||
MArrayIter& mi = marr();
|
||||
(void) new (&mi) MArrayIter(ad);
|
||||
|
||||
@@ -54,6 +54,41 @@ int64_t f_connection_timeout() {
|
||||
return f_connection_status() == k_CONNECTION_TIMEOUT;
|
||||
}
|
||||
|
||||
static VM::Class* getClassByName(const char* name, int len) {
|
||||
VM::Class* cls = nullptr;
|
||||
// translate "self" or "parent"
|
||||
if (len == 4 && !memcmp(name, "self", 4)) {
|
||||
cls = g_vmContext->getContextClass();
|
||||
if (!cls) {
|
||||
throw FatalErrorException("Cannot access self:: "
|
||||
"when no class scope is active");
|
||||
}
|
||||
} else if (len == 6 && !memcmp(name, "parent", 6)) {
|
||||
cls = g_vmContext->getParentContextClass();
|
||||
if (!cls) {
|
||||
throw FatalErrorException("Cannot access parent");
|
||||
}
|
||||
} else if (len == 6 && !memcmp(name, "static", 6)) {
|
||||
CallerFrame cf;
|
||||
auto ar = cf();
|
||||
if (ar) {
|
||||
if (ar->hasThis()) {
|
||||
cls = ar->getThis()->getVMClass();
|
||||
} else if (ar->hasClass()) {
|
||||
cls = ar->getClass();
|
||||
}
|
||||
}
|
||||
if (!cls) {
|
||||
throw FatalErrorException("Cannot access static:: "
|
||||
"when no class scope is active");
|
||||
}
|
||||
} else {
|
||||
String className(name, len, CopyString);
|
||||
cls = VM::Unit::loadClass(className.get());
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
|
||||
Variant f_constant(CStrRef name) {
|
||||
if (!name.get()) return null;
|
||||
const char *data = name.data();
|
||||
@@ -63,26 +98,7 @@ Variant f_constant(CStrRef name) {
|
||||
// class constant
|
||||
int classNameLen = colon - data;
|
||||
char *constantName = colon + 2;
|
||||
String className(data, classNameLen, CopyString);
|
||||
|
||||
// translate "self" or "parent"
|
||||
if (className == "self") {
|
||||
String this_class = g_vmContext->getContextClassName();
|
||||
if (this_class.empty()) {
|
||||
throw FatalErrorException("Cannot access self:: "
|
||||
"when no class scope is active");
|
||||
} else {
|
||||
className = this_class;
|
||||
}
|
||||
} else if (className == "parent") {
|
||||
String parent_class =g_vmContext->getParentContextClassName();
|
||||
if (parent_class.empty()) {
|
||||
throw FatalErrorException("Cannot access parent");
|
||||
} else {
|
||||
className = parent_class;
|
||||
}
|
||||
}
|
||||
VM::Class* cls = VM::Unit::loadClass(className.get());
|
||||
VM::Class* cls = getClassByName(data, classNameLen);
|
||||
if (cls) {
|
||||
String cnsName(constantName, data + len - constantName, CopyString);
|
||||
TypedValue* tv = cls->clsCnsGet(cnsName.get());
|
||||
@@ -123,59 +139,12 @@ bool f_defined(CStrRef name, bool autoload /* = true */) {
|
||||
// class constant
|
||||
int classNameLen = colon - data;
|
||||
char *constantName = colon + 2;
|
||||
String className(data, classNameLen, CopyString);
|
||||
|
||||
// translate "self" or "parent" or "static"
|
||||
if (className == "self") {
|
||||
String this_class = g_vmContext->getContextClassName();
|
||||
if (this_class.empty()) {
|
||||
throw FatalErrorException("Cannot access self:: "
|
||||
"when no class scope is active");
|
||||
} else {
|
||||
className = this_class;
|
||||
}
|
||||
} else if (className == "parent") {
|
||||
String parent_class = g_vmContext->getParentContextClassName();
|
||||
if (parent_class.empty()) {
|
||||
throw FatalErrorException("Cannot access parent");
|
||||
} else {
|
||||
className = parent_class;
|
||||
}
|
||||
} else if (className == "static") {
|
||||
CallerFrame cf;
|
||||
auto ar = cf();
|
||||
if (ar) {
|
||||
HPHP::VM::Class* cls;
|
||||
if (ar->hasThis()) {
|
||||
cls = ar->getThis()->getVMClass();
|
||||
} else if (ar->hasClass()) {
|
||||
cls = ar->getClass();
|
||||
} else {
|
||||
cls = NULL;
|
||||
}
|
||||
if (cls) {
|
||||
className = cls->nameRef();
|
||||
} else {
|
||||
throw FatalErrorException("Cannot access static:: "
|
||||
"when no class scope is active");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (class_exists(className)) { // taking care of volatile class
|
||||
const ClassInfo *info;
|
||||
for (String parentClass = className;
|
||||
!parentClass.empty();
|
||||
parentClass = info->getParentClass()) {
|
||||
info = ClassInfo::FindClass(parentClass);
|
||||
if (!info) {
|
||||
assert(false);
|
||||
}
|
||||
if (info->hasConstant(constantName)) return true;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
return false;
|
||||
VM::Class* cls = getClassByName(data, classNameLen);
|
||||
if (cls) {
|
||||
String cnsName(constantName, data + len - constantName, CopyString);
|
||||
return cls->clsCnsGet(cnsName.get());
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// system/uniquely defined scalar constant
|
||||
if (ClassInfo::FindConstant(name)) return true;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <pwd.h>
|
||||
#include <memory>
|
||||
|
||||
namespace HPHP {
|
||||
IMPLEMENT_DEFAULT_EXTENSION(posix);
|
||||
@@ -35,30 +36,55 @@ bool f_posix_access(CStrRef file, int mode /* = 0 */) {
|
||||
return !access(path.data(), mode);
|
||||
}
|
||||
|
||||
static Variant php_posix_group_to_array(struct group *g) {
|
||||
if (!g) {
|
||||
static Variant php_posix_group_to_array(int gid,
|
||||
CStrRef gname = null_variant) {
|
||||
// Don't pass a gid *and* a gname to this.
|
||||
assert((gid < 0) || gname.size() == 0);
|
||||
|
||||
if ((gid < 0) && (gname.size() == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int grbuflen = sysconf(_SC_GETGR_R_SIZE_MAX);
|
||||
if (grbuflen < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> grbuf(new char[grbuflen]);
|
||||
struct group gr;
|
||||
struct group *retgrptr = NULL;
|
||||
|
||||
// If we somehow reach this point and both gname and gid were
|
||||
// passed, then the gid values will override the game values,
|
||||
// but it will otherwise function just fine.
|
||||
// The assert() clause above should prevent that, however.
|
||||
if ((gname.size() > 0) &&
|
||||
getgrnam_r(gname.data(), &gr, grbuf.get(), grbuflen, &retgrptr)) {
|
||||
return false;
|
||||
} else if ((gid >= 0) &&
|
||||
getgrgid_r(gid, &gr, grbuf.get(), grbuflen, &retgrptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Array members;
|
||||
for (int count=0; g->gr_mem[count] != NULL; count++) {
|
||||
members.append(String(g->gr_mem[count], AttachLiteral));
|
||||
for (int count=0; gr.gr_mem[count] != NULL; count++) {
|
||||
members.append(String(gr.gr_mem[count], CopyString));
|
||||
}
|
||||
|
||||
Array ret;
|
||||
ret.set("name", String(g->gr_name, AttachLiteral));
|
||||
ret.set("passwd", String(g->gr_passwd, AttachLiteral));
|
||||
ret.set("name", String(gr.gr_name, CopyString));
|
||||
ret.set("passwd", String(gr.gr_passwd, CopyString));
|
||||
ret.set("members", members);
|
||||
ret.set("gid", (int)g->gr_gid);
|
||||
ret.set("gid", (int)gr.gr_gid);
|
||||
return ret;
|
||||
}
|
||||
|
||||
Variant f_posix_getgrgid(int gid) {
|
||||
return php_posix_group_to_array(getgrgid(gid));
|
||||
return php_posix_group_to_array(gid);
|
||||
}
|
||||
|
||||
Variant f_posix_getgrnam(CStrRef name) {
|
||||
return php_posix_group_to_array(getgrnam(name.data()));
|
||||
return php_posix_group_to_array(-1, name.data());
|
||||
}
|
||||
|
||||
Variant f_posix_getgroups() {
|
||||
@@ -75,28 +101,53 @@ Variant f_posix_getgroups() {
|
||||
return ret;
|
||||
}
|
||||
|
||||
static Variant php_posix_passwd_to_array(struct passwd *pw) {
|
||||
if (!pw) {
|
||||
static Variant php_posix_passwd_to_array(int uid,
|
||||
CStrRef name = null_variant) {
|
||||
// Don't pass a uid *and* a name to this.
|
||||
assert((uid < 0) || name.size() == 0);
|
||||
|
||||
if ((uid < 0) && name.size() == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int pwbuflen = sysconf(_SC_GETPW_R_SIZE_MAX);
|
||||
if (pwbuflen < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<char[]> pwbuf(new char[pwbuflen]);
|
||||
struct passwd pw;
|
||||
struct passwd *retpwptr = NULL;
|
||||
|
||||
// If we somehow reach this point and both name and uid were
|
||||
// passed, then the uid values will override the name values,
|
||||
// but it will otherwise function just fine.
|
||||
// The assert() clauses above should prevent that, however.
|
||||
if ((name.size() > 0) &&
|
||||
getpwnam_r(name.data(), &pw, pwbuf.get(), pwbuflen, &retpwptr)) {
|
||||
return false;
|
||||
} else if ((uid >= 0) &&
|
||||
getpwuid_r(uid, &pw, pwbuf.get(), pwbuflen, &retpwptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Array ret;
|
||||
ret.set("name", String(pw->pw_name, AttachLiteral));
|
||||
ret.set("passwd", String(pw->pw_passwd, AttachLiteral));
|
||||
ret.set("uid", (int)pw->pw_uid);
|
||||
ret.set("gid", (int)pw->pw_gid);
|
||||
ret.set("gecos", String(pw->pw_gecos, AttachLiteral));
|
||||
ret.set("dir", String(pw->pw_dir, AttachLiteral));
|
||||
ret.set("shell", String(pw->pw_shell, AttachLiteral));
|
||||
ret.set("name", String(pw.pw_name, CopyString));
|
||||
ret.set("passwd", String(pw.pw_passwd, CopyString));
|
||||
ret.set("uid", (int)pw.pw_uid);
|
||||
ret.set("gid", (int)pw.pw_gid);
|
||||
ret.set("gecos", String(pw.pw_gecos, CopyString));
|
||||
ret.set("dir", String(pw.pw_dir, CopyString));
|
||||
ret.set("shell", String(pw.pw_shell, CopyString));
|
||||
return ret;
|
||||
}
|
||||
|
||||
Variant f_posix_getpwnam(CStrRef username) {
|
||||
return php_posix_passwd_to_array(getpwnam(username.data()));
|
||||
return php_posix_passwd_to_array(-1, username);
|
||||
}
|
||||
|
||||
Variant f_posix_getpwuid(int uid) {
|
||||
return php_posix_passwd_to_array(getpwuid(uid));
|
||||
return php_posix_passwd_to_array(uid);
|
||||
}
|
||||
|
||||
static bool posix_addlimit(int limit, const char *name, Array &ret) {
|
||||
@@ -210,11 +261,17 @@ Variant f_posix_times() {
|
||||
}
|
||||
|
||||
Variant f_posix_ttyname(CVarRef fd) {
|
||||
char *p = ttyname(php_posix_get_fd(fd));
|
||||
if (!p) {
|
||||
int ttyname_maxlen = sysconf(_SC_TTY_NAME_MAX);
|
||||
if (ttyname_maxlen <= 0) {
|
||||
return false;
|
||||
}
|
||||
return String(p, CopyString);
|
||||
|
||||
String ttyname(ttyname_maxlen, ReserveString);
|
||||
char *p = ttyname.mutableSlice().ptr;
|
||||
if (ttyname_r(php_posix_get_fd(fd), p, ttyname_maxlen)) {
|
||||
return false;
|
||||
}
|
||||
return ttyname.setSize(strlen(p));
|
||||
}
|
||||
|
||||
Variant f_posix_uname() {
|
||||
|
||||
@@ -16,7 +16,7 @@ if (!defined('GLOBAL_SYMBOL_REDECLARED_CLASS')) {define('GLOBAL_SYMBOL_REDECLARE
|
||||
if (!defined('GLOBAL_SYMBOL_REDECLARED_FUNCTION')) {define('GLOBAL_SYMBOL_REDECLARED_FUNCTION', 5);}
|
||||
if (!defined('GLOBAL_SYMBOL_STATIC_VARIABLE')) {define('GLOBAL_SYMBOL_STATIC_VARIABLE', 1);}
|
||||
if (!defined('HPHP_TRIM_CHARLIST')) {define('HPHP_TRIM_CHARLIST', ' \n\r\t\v' . "\0" . '');}
|
||||
if (!defined('HPHP_VERSION')) {define('HPHP_VERSION', '1.0.0');}
|
||||
if (!defined('HPHP_VERSION')) {define('HPHP_VERSION', '2.0.2');}
|
||||
if (!defined('INI_SCANNER_NORMAL')) {define('INI_SCANNER_NORMAL', 0);}
|
||||
if (!defined('INI_SCANNER_RAW')) {define('INI_SCANNER_RAW', 1);}
|
||||
if (!defined('PHP_VERSION')) {define('PHP_VERSION', '5.3.3.hiphop');}
|
||||
|
||||
@@ -970,8 +970,7 @@ UnwindStatus Stack::unwindFrag(ActRec* fp, int offset,
|
||||
// count starts over for the caller frame.
|
||||
fault.m_handledCount = 0;
|
||||
|
||||
if (fp->isFromFPushCtor()) {
|
||||
assert(fp->hasThis());
|
||||
if (fp->isFromFPushCtor() && fp->hasThis()) {
|
||||
fp->getThis()->setNoDestruct();
|
||||
}
|
||||
|
||||
@@ -1425,46 +1424,22 @@ ObjectData* VMExecutionContext::getThis() {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CStrRef VMExecutionContext::getContextClassName() {
|
||||
Class* VMExecutionContext::getContextClass() {
|
||||
VMRegAnchor _;
|
||||
ActRec* ar = getFP();
|
||||
assert(ar != nullptr);
|
||||
if (ar->skipFrame()) {
|
||||
ar = getPrevVMState(ar);
|
||||
if (!ar) return empty_string;
|
||||
}
|
||||
if (ar->hasThis()) {
|
||||
return ar->getThis()->o_getClassName();
|
||||
} else if (ar->hasClass()) {
|
||||
return ar->getClass()->nameRef();
|
||||
} else {
|
||||
return empty_string;
|
||||
if (!ar) return nullptr;
|
||||
}
|
||||
return ar->m_func->cls();
|
||||
}
|
||||
|
||||
CStrRef VMExecutionContext::getParentContextClassName() {
|
||||
VMRegAnchor _;
|
||||
ActRec* ar = getFP();
|
||||
assert(ar != nullptr);
|
||||
if (ar->skipFrame()) {
|
||||
ar = getPrevVMState(ar);
|
||||
if (!ar) return empty_string;
|
||||
}
|
||||
if (ar->hasThis()) {
|
||||
const Class* cls = ar->getThis()->getVMClass();
|
||||
if (cls->parent() == nullptr) {
|
||||
return empty_string;
|
||||
}
|
||||
return cls->parent()->nameRef();
|
||||
} else if (ar->hasClass()) {
|
||||
const Class* cls = ar->getClass();
|
||||
if (cls->parent() == nullptr) {
|
||||
return empty_string;
|
||||
}
|
||||
return cls->parent()->nameRef();
|
||||
} else {
|
||||
return empty_string;
|
||||
Class* VMExecutionContext::getParentContextClass() {
|
||||
if (Class* ctx = getContextClass()) {
|
||||
return ctx->parent();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CStrRef VMExecutionContext::getContainingFileName() {
|
||||
@@ -2056,7 +2031,8 @@ void VMExecutionContext::invokeFunc(TypedValue* retval,
|
||||
// If this is a method, either this_ or cls must be non-NULL
|
||||
assert(!f->preClass() || (this_ || cls));
|
||||
// If this is a static method, this_ must be NULL
|
||||
assert(!(f->attrs() & HPHP::VM::AttrStatic) || (!this_));
|
||||
assert(!(f->attrs() & HPHP::VM::AttrStatic && !f->isClosureBody()) ||
|
||||
(!this_));
|
||||
// invName should only be non-NULL if we are calling __call or
|
||||
// __callStatic
|
||||
assert(!invName || f->name()->isame(s___call.get()) ||
|
||||
|
||||
@@ -308,6 +308,12 @@ const FPIEnt* Func::findPrecedingFPI(Offset o) const {
|
||||
return fe;
|
||||
}
|
||||
|
||||
bool Func::isClonedClosure() const {
|
||||
if (!isClosureBody()) return false;
|
||||
if (!cls()) return true;
|
||||
return cls()->lookupMethod(name()) != this;
|
||||
}
|
||||
|
||||
bool Func::isNameBindingImmutable(const Unit* fromUnit) const {
|
||||
if (RuntimeOption::EvalJitEnableRenameFunction ||
|
||||
m_attrs & AttrDynamicInvoke) {
|
||||
|
||||
@@ -144,9 +144,9 @@ struct Func {
|
||||
#endif
|
||||
}
|
||||
|
||||
FuncId getFuncId() const {
|
||||
FuncId getFuncId() const {
|
||||
assert(m_funcId != InvalidId);
|
||||
return m_funcId;
|
||||
return m_funcId;
|
||||
}
|
||||
void setFuncId(FuncId id);
|
||||
void setNewFuncId();
|
||||
@@ -320,6 +320,7 @@ struct Func {
|
||||
bool top() const { return shared()->m_top; }
|
||||
const StringData* docComment() const { return shared()->m_docComment; }
|
||||
bool isClosureBody() const { return shared()->m_isClosureBody; }
|
||||
bool isClonedClosure() const;
|
||||
bool isGenerator() const { return shared()->m_isGenerator; }
|
||||
bool isGeneratorFromClosure() const {
|
||||
return shared()->m_isGeneratorFromClosure;
|
||||
|
||||
@@ -676,7 +676,7 @@ std::string instrToString(const Opcode* it, const Unit* u /* = NULL */) {
|
||||
#define READOFF() do { \
|
||||
Offset _value = *(Offset*)it; \
|
||||
out << " " << _value; \
|
||||
if (u != nullptr) { \
|
||||
if (u != nullptr && _value >= 0) { \
|
||||
out << " (" << u->offsetOf(iStart + _value) << ")"; \
|
||||
} \
|
||||
it += sizeof(Offset); \
|
||||
|
||||
@@ -222,26 +222,6 @@ pathloop:
|
||||
}
|
||||
}
|
||||
|
||||
ConditionCode cmpOpToCC(Opcode opc) {
|
||||
switch (opc) {
|
||||
case OpGt: return CC_G;
|
||||
case OpGte: return CC_GE;
|
||||
case OpLt: return CC_L;
|
||||
case OpLte: return CC_LE;
|
||||
case OpEq: return CC_E;
|
||||
case OpNeq: return CC_NE;
|
||||
case OpSame: return CC_E;
|
||||
case OpNSame: return CC_NE;
|
||||
case InstanceOf: return CC_NZ;
|
||||
case NInstanceOf: return CC_Z;
|
||||
case InstanceOfBitmask: return CC_NZ;
|
||||
case NInstanceOfBitmask: return CC_Z;
|
||||
case IsType: return CC_NZ;
|
||||
case IsNType: return CC_Z;
|
||||
default: always_assert(0);
|
||||
}
|
||||
}
|
||||
|
||||
const char* getContextName(Class* ctx) {
|
||||
return ctx ? ctx->name()->data() : ":anonymous:";
|
||||
}
|
||||
@@ -250,7 +230,8 @@ const char* getContextName(Class* ctx) {
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
ArgDesc::ArgDesc(SSATmp* tmp, bool val) : m_imm(-1), m_zeroExtend(false) {
|
||||
ArgDesc::ArgDesc(SSATmp* tmp, bool val) : m_imm(-1), m_zeroExtend(false),
|
||||
m_done(false) {
|
||||
if (tmp->getType() == Type::None) {
|
||||
assert(val);
|
||||
m_kind = None;
|
||||
@@ -534,7 +515,7 @@ void CodeGenerator::cgJcc(IRInstruction* inst) {
|
||||
SSATmp* src1 = inst->getSrc(0);
|
||||
SSATmp* src2 = inst->getSrc(1);
|
||||
Opcode opc = inst->getOpcode();
|
||||
ConditionCode cc = cmpOpToCC(queryJmpToQueryOp(opc));
|
||||
ConditionCode cc = queryJmpToCC(opc);
|
||||
Type src1Type = src1->getType();
|
||||
Type src2Type = src2->getType();
|
||||
|
||||
@@ -564,17 +545,21 @@ void CodeGenerator::cgJcc(IRInstruction* inst) {
|
||||
// TODO: use compare with immediate or make sure simplifier
|
||||
// canonicalizes this so that constant is src2
|
||||
srcReg1 = rScratch;
|
||||
m_as.mov_imm64_reg(src1->getValRawInt(), srcReg1);
|
||||
m_as. mov_imm64_reg(src1->getValRawInt(), srcReg1);
|
||||
}
|
||||
if (src2->isConst()) {
|
||||
m_as.cmp_imm64_reg64(src2->getValRawInt(), srcReg1);
|
||||
if (src1Type.subtypeOf(Type::Bool)) {
|
||||
m_as. cmpb (src2->getValRawInt(), Reg8(int(srcReg1)));
|
||||
} else {
|
||||
m_as. cmp_imm64_reg64(src2->getValRawInt(), srcReg1);
|
||||
}
|
||||
} else {
|
||||
// Note the reverse syntax in the assembler.
|
||||
// This cmp will compute srcReg1 - srcReg2
|
||||
if (src1Type == Type::Bool) {
|
||||
if (src1Type.subtypeOf(Type::Bool)) {
|
||||
m_as. cmpb (Reg8(int(srcReg2)), Reg8(int(srcReg1)));
|
||||
} else {
|
||||
m_as.cmp_reg64_reg64(srcReg2, srcReg1);
|
||||
m_as. cmp_reg64_reg64(srcReg2, srcReg1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -593,7 +578,9 @@ void CodeGenerator::cgJmpNSame(IRInstruction* inst) { cgJcc(inst); }
|
||||
|
||||
/**
|
||||
* Once the arg sources and dests are all assigned; emit moves and exchanges
|
||||
* to put all the args in desired registers.
|
||||
* to put all the args in desired registers. In addition to moves and
|
||||
* exchanges, shuffleArgs also handles adding lea-offsets for dest registers
|
||||
* (dest = src + lea-offset) and zero extending bools (dest = zeroExtend(src)).
|
||||
*/
|
||||
typedef Transl::X64Assembler Asm;
|
||||
static void shuffleArgs(Asm& a, ArgGroup& args) {
|
||||
@@ -620,22 +607,10 @@ static void shuffleArgs(Asm& a, ArgGroup& args) {
|
||||
}
|
||||
auto dstReg = args[i].getDstReg();
|
||||
auto srcReg = args[i].getSrcReg();
|
||||
if (dstReg == srcReg) {
|
||||
// Ignore register-to-register moves whose src and dst registers
|
||||
// are the same. But emit the code for load-effective-address
|
||||
// operations whose src and dst registers are the same as
|
||||
// doRegMoves won't handle those.
|
||||
if (kind == ArgDesc::Addr) {
|
||||
// an lea whose src and dest regs are the same
|
||||
a. lea (srcReg[args[i].getImm().q()], dstReg);
|
||||
} else if (args[i].isZeroExtend()) {
|
||||
// if passing bool as TypedValue.m_data, must zero extend
|
||||
a. movzbl (rbyte(dstReg), r32(dstReg));
|
||||
}
|
||||
continue;
|
||||
if (dstReg != srcReg) {
|
||||
moves[int(dstReg)] = int(srcReg);
|
||||
argDescs[int(dstReg)] = &args[i];
|
||||
}
|
||||
moves[int(dstReg)] = int(srcReg);
|
||||
argDescs[int(dstReg)] = &args[i];
|
||||
}
|
||||
std::vector<MoveInfo> howTo;
|
||||
doRegMoves(moves, int(reg::rScratch), howTo);
|
||||
@@ -645,44 +620,46 @@ static void shuffleArgs(Asm& a, ArgGroup& args) {
|
||||
a. movq (howTo[i].m_reg1, howTo[i].m_reg2);
|
||||
} else {
|
||||
ArgDesc* argDesc = argDescs[int(howTo[i].m_reg2)];
|
||||
if (argDesc->getKind() == ArgDesc::Reg ||
|
||||
argDesc->getKind() == ArgDesc::TypeReg) {
|
||||
ArgDesc::Kind kind = argDesc->getKind();
|
||||
if (kind == ArgDesc::Reg || kind == ArgDesc::TypeReg) {
|
||||
if (argDesc->isZeroExtend()) {
|
||||
a. movzbl (rbyte(howTo[i].m_reg1), r32(howTo[i].m_reg2));
|
||||
} else {
|
||||
a. movq (howTo[i].m_reg1, howTo[i].m_reg2);
|
||||
}
|
||||
} else {
|
||||
assert(argDesc->getKind() == ArgDesc::Addr);
|
||||
assert(kind == ArgDesc::Addr);
|
||||
a. lea (howTo[i].m_reg1[argDesc->getImm().q()],
|
||||
howTo[i].m_reg2);
|
||||
}
|
||||
if (kind != ArgDesc::TypeReg) {
|
||||
argDesc->markDone();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
a. xchgq (howTo[i].m_reg1, howTo[i].m_reg2);
|
||||
ArgDesc* argDesc2 = argDescs[int(howTo[i].m_reg2)];
|
||||
if (argDesc2->getKind() == ArgDesc::Addr) {
|
||||
a. addq (argDesc2->getImm(), howTo[i].m_reg2);
|
||||
} else if (argDesc2->isZeroExtend()) {
|
||||
a. movzbl (rbyte(howTo[i].m_reg2), r32(howTo[i].m_reg2));
|
||||
}
|
||||
ArgDesc* argDesc1 = argDescs[int(howTo[i].m_reg1)];
|
||||
if (argDesc1->getKind() == ArgDesc::Addr) {
|
||||
a. addq (argDesc1->getImm(), howTo[i].m_reg1);
|
||||
} else if (argDesc1->isZeroExtend()) {
|
||||
a. movzbl (rbyte(howTo[i].m_reg1), r32(howTo[i].m_reg1));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle const-to-register moves and type shifting
|
||||
// Handle const-to-register moves, type shifting,
|
||||
// load-effective address and zero extending for bools.
|
||||
// Ignore args that have been handled by the
|
||||
// move above.
|
||||
for (size_t i = 0; i < args.size(); ++i) {
|
||||
if (args[i].getKind() == ArgDesc::Imm) {
|
||||
emitLoadImm(a, args[i].getImm().q(), args[i].getDstReg());
|
||||
} else if (args[i].getKind() == ArgDesc::TypeReg) {
|
||||
a. shlq (kTypeShiftBits, args[i].getDstReg());
|
||||
} else if (RuntimeOption::EvalHHIRGenerateAsserts &&
|
||||
args[i].getKind() == ArgDesc::None) {
|
||||
emitLoadImm(a, 0xbadbadbadbadbad, args[i].getDstReg());
|
||||
if (!args[i].done()) {
|
||||
ArgDesc::Kind kind = args[i].getKind();
|
||||
PhysReg dst = args[i].getDstReg();
|
||||
if (kind == ArgDesc::Imm) {
|
||||
emitLoadImm(a, args[i].getImm().q(), dst);
|
||||
} else if (kind == ArgDesc::TypeReg) {
|
||||
a. shlq (kTypeShiftBits, dst);
|
||||
} else if (kind == ArgDesc::Addr) {
|
||||
a. addq (args[i].getImm(), dst);
|
||||
} else if (args[i].isZeroExtend()) {
|
||||
a. movzbl (rbyte(dst), r32(dst));
|
||||
} else if (RuntimeOption::EvalHHIRGenerateAsserts &&
|
||||
kind == ArgDesc::None) {
|
||||
emitLoadImm(a, 0xbadbadbadbadbad, dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1467,10 +1444,12 @@ void CodeGenerator::cgInstanceOf(IRInstruction* inst) {
|
||||
void CodeGenerator::cgNInstanceOf(IRInstruction* inst) {
|
||||
// TODO(#2058865): having NInstanceOf is no better than InstanceOf
|
||||
// followed by boolean Not opcode.
|
||||
emitInstanceCheck(inst, inst->getDst()->getReg());
|
||||
PhysReg dstReg = inst->getDst()->getReg();
|
||||
emitInstanceCheck(inst, dstReg);
|
||||
Reg8 dr((int(dstReg)));
|
||||
auto& a = m_as;
|
||||
a. testb (al, al);
|
||||
a. setz (al);
|
||||
a. testb (dr, dr);
|
||||
a. setz (dr);
|
||||
}
|
||||
|
||||
void CodeGenerator::cgJmpInstanceOf(IRInstruction* inst) {
|
||||
@@ -1903,8 +1882,10 @@ void traceRet(ActRec* fp, Cell* sp, void* rip) {
|
||||
if (rip == TranslatorX64::Get()->getCallToExit()) {
|
||||
return;
|
||||
}
|
||||
checkFrame(fp, sp, false);
|
||||
assertTv(sp); // check return value
|
||||
checkFrame(fp, sp, /*checkLocals*/ false);
|
||||
assert(sp <= (Cell*)fp || fp->m_func->isGenerator());
|
||||
// check return value if stack not empty
|
||||
if (sp < (Cell*)fp) assertTv(sp);
|
||||
}
|
||||
|
||||
void CodeGenerator::emitTraceRet(CodeGenerator::Asm& a) {
|
||||
@@ -2251,7 +2232,7 @@ void CodeGenerator::cgExitTrace(IRInstruction* inst) {
|
||||
// Patch the original jcc;jmp, don't emit another
|
||||
IRInstruction* jcc = toSmash->getInstruction();
|
||||
Opcode opc = jcc->getOpcode();
|
||||
ConditionCode cc = cmpOpToCC(queryJmpToQueryOp(opc));
|
||||
ConditionCode cc = queryJmpToCC(opc);
|
||||
uint64_t taken = pc->getValInt();
|
||||
uint64_t notTaken = notTakenPC->getValInt();
|
||||
|
||||
|
||||
@@ -331,6 +331,8 @@ public:
|
||||
void setDstReg(PhysReg reg) { m_dstReg = reg; }
|
||||
Immed getImm() const { return m_imm; }
|
||||
bool isZeroExtend() const {return m_zeroExtend;}
|
||||
bool done() const { return m_done; }
|
||||
void markDone() { m_done = true; }
|
||||
|
||||
private: // These should be created using ArgGroup.
|
||||
friend struct ArgGroup;
|
||||
@@ -341,6 +343,7 @@ private: // These should be created using ArgGroup.
|
||||
, m_dstReg(reg::noreg)
|
||||
, m_imm(immVal)
|
||||
, m_zeroExtend(false)
|
||||
, m_done(false)
|
||||
{}
|
||||
|
||||
explicit ArgDesc(SSATmp* tmp, bool val = true);
|
||||
@@ -351,6 +354,7 @@ private:
|
||||
PhysReg m_dstReg;
|
||||
Immed m_imm;
|
||||
bool m_zeroExtend;
|
||||
bool m_done;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -772,14 +772,10 @@ void HhbcTranslator::emitContDone() {
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitContNext() {
|
||||
emitInterpOneOrPunt(Type::None);
|
||||
return;
|
||||
// Task #2140912: Fix and re-enable this
|
||||
|
||||
assert(getCurClass());
|
||||
SSATmp* cont = m_tb->genLdThis(nullptr);
|
||||
m_tb->genContPreNext(cont, getExitSlowTrace());
|
||||
m_tb->genSetPropCell(cont, CONTOFF(m_received), m_tb->genDefUninit());
|
||||
m_tb->genSetPropCell(cont, CONTOFF(m_received), m_tb->genDefInitNull());
|
||||
}
|
||||
|
||||
void HhbcTranslator::emitContSendImpl(bool raise) {
|
||||
@@ -1452,7 +1448,11 @@ void HhbcTranslator::emitFPushClsMethodD(int32_t numParams,
|
||||
SSATmp* objOrCls;
|
||||
if (!mightNotBeStatic) { // definitely static
|
||||
// static function: store base class into the m_cls/m_this slot
|
||||
objOrCls = m_tb->genDefConst(baseClass);
|
||||
if (TargetCache::isPersistentHandle(baseClass->m_cachedOffset)) {
|
||||
objOrCls = m_tb->genDefConst(baseClass);
|
||||
} else {
|
||||
objOrCls = m_tb->gen(LdClsCached, m_tb->genDefConst(className));
|
||||
}
|
||||
} else if (m_tb->isThisAvailable()) {
|
||||
// 'this' pointer is available, so use it.
|
||||
assert(getCurClass());
|
||||
@@ -2460,6 +2460,7 @@ std::vector<SSATmp*> HhbcTranslator::getSpillValues() const {
|
||||
*/
|
||||
auto* inst = elem->getInstruction();
|
||||
assert(inst->getNumSrcs() == 5);
|
||||
ret.push_back(inst->getSrc(0)); // fp
|
||||
ret.push_back(inst->getSrc(1)); // func
|
||||
ret.push_back(inst->getSrc(2)); // objOrCls
|
||||
ret.push_back(inst->getSrc(3)); // numArgs
|
||||
|
||||
@@ -629,14 +629,60 @@ inline Opcode queryToJmpOp(Opcode opc) {
|
||||
}
|
||||
|
||||
inline bool isQueryJmpOp(Opcode opc) {
|
||||
return opc >= JmpGt && opc <= JmpIsNType;
|
||||
switch (opc) {
|
||||
case JmpGt:
|
||||
case JmpGte:
|
||||
case JmpLt:
|
||||
case JmpLte:
|
||||
case JmpEq:
|
||||
case JmpNeq:
|
||||
case JmpSame:
|
||||
case JmpNSame:
|
||||
case JmpInstanceOf:
|
||||
case JmpNInstanceOf:
|
||||
case JmpInstanceOfBitmask:
|
||||
case JmpNInstanceOfBitmask:
|
||||
case JmpIsType:
|
||||
case JmpIsNType:
|
||||
case JmpZero:
|
||||
case JmpNZero:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline Opcode queryJmpToQueryOp(Opcode opc) {
|
||||
assert(isQueryJmpOp(opc));
|
||||
assert(opc != JmpZero && opc != JmpNZero);
|
||||
return Opcode(OpGt + (opc - JmpGt));
|
||||
}
|
||||
|
||||
inline ConditionCode queryJmpToCC(Opcode opc) {
|
||||
assert(isQueryJmpOp(opc));
|
||||
|
||||
switch (opc) {
|
||||
case JmpGt: return CC_G;
|
||||
case JmpGte: return CC_GE;
|
||||
case JmpLt: return CC_L;
|
||||
case JmpLte: return CC_LE;
|
||||
case JmpEq: return CC_E;
|
||||
case JmpNeq: return CC_NE;
|
||||
case JmpSame: return CC_E;
|
||||
case JmpNSame: return CC_NE;
|
||||
case JmpInstanceOf: return CC_NZ;
|
||||
case JmpNInstanceOf: return CC_Z;
|
||||
case JmpInstanceOfBitmask: return CC_NZ;
|
||||
case JmpNInstanceOfBitmask: return CC_Z;
|
||||
case JmpIsType: return CC_NZ;
|
||||
case JmpIsNType: return CC_Z;
|
||||
case JmpZero: return CC_Z;
|
||||
case JmpNZero: return CC_NZ;
|
||||
default:
|
||||
not_reached();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Right now branch fusion is too indiscriminate to handle fusing
|
||||
* with potentially expensive-to-repeat operations. TODO(#2053369)
|
||||
@@ -2027,7 +2073,7 @@ int32_t spillValueCells(IRInstruction* spillStack);
|
||||
* When SpillStack takes an ActRec, it has this many extra
|
||||
* dependencies in the spill vector for the values in the ActRec.
|
||||
*/
|
||||
constexpr int kSpillStackActRecExtraArgs = 4;
|
||||
constexpr int kSpillStackActRecExtraArgs = 5;
|
||||
|
||||
inline bool isConvIntOrPtrToBool(IRInstruction* instr) {
|
||||
if (!(instr->getOpcode() == Conv &&
|
||||
|
||||
@@ -23,10 +23,7 @@ namespace HPHP { namespace VM { namespace JIT {
|
||||
// These are the conditional branches supported for direct branch
|
||||
// to their target trace at TraceExit, TraceExitType::NormalCc
|
||||
static bool jccCanBeDirectExit(Opcode opc) {
|
||||
// JmpGt .. JmpNSame are contiguous and all use cgJcc
|
||||
return (JmpGt <= opc && opc <= JmpNSame) ||
|
||||
opc == JmpInstanceOf || opc == JmpNInstanceOf ||
|
||||
opc == JmpInstanceOfBitmask || opc == JmpNInstanceOfBitmask;
|
||||
return isQueryJmpOp(opc) && (opc != JmpIsType) && (opc != JmpIsNType);
|
||||
// TODO(#2053369): JmpIsType, etc
|
||||
}
|
||||
|
||||
|
||||
@@ -505,28 +505,30 @@ SSATmp* Simplifier::simplifyNot(SSATmp* src) {
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SIMPLIFY_COMMUTATIVE(OP, NAME) do { \
|
||||
SIMPLIFY_CONST(OP); \
|
||||
if (src1->isConst() && !src2->isConst()) { \
|
||||
return m_tb->gen##NAME(src2, src1); \
|
||||
} \
|
||||
IRInstruction* inst1 = src1->getInstruction(); \
|
||||
IRInstruction* inst2 = src2->getInstruction(); \
|
||||
if (inst1->getOpcode() == Op##NAME && inst1->getSrc(1)->isConst()) { \
|
||||
/* (X + C1) + C2 --> X + C3 */ \
|
||||
if (src2->isConst()) { \
|
||||
int64_t right = inst1->getSrc(1)->getValInt(); \
|
||||
right OP##= src2->getValInt(); \
|
||||
return m_tb->gen##NAME(inst1->getSrc(0), genDefInt(right)); \
|
||||
} \
|
||||
/* (X + C1) + (Y + C2) --> X + Y + C3 */ \
|
||||
if (inst2->getOpcode() == Op##NAME && inst2->getSrc(1)->isConst()) { \
|
||||
int64_t right = inst1->getSrc(1)->getValInt(); \
|
||||
right OP##= inst2->getSrc(1)->getValInt(); \
|
||||
SSATmp* left = m_tb->gen##NAME(inst1->getSrc(0), inst2->getSrc(0)); \
|
||||
return m_tb->gen##NAME(left, genDefInt(right)); \
|
||||
} \
|
||||
} \
|
||||
#define SIMPLIFY_COMMUTATIVE(OP, NAME) do { \
|
||||
SIMPLIFY_CONST(OP); \
|
||||
if (src1->isConst() && !src2->isConst()) { \
|
||||
return m_tb->gen##NAME(src2, src1); \
|
||||
} \
|
||||
if (src1->isA(Type::Int) && src2->isA(Type::Int)) { \
|
||||
IRInstruction* inst1 = src1->getInstruction(); \
|
||||
IRInstruction* inst2 = src2->getInstruction(); \
|
||||
if (inst1->getOpcode() == Op##NAME && inst1->getSrc(1)->isConst()) { \
|
||||
/* (X + C1) + C2 --> X + C3 */ \
|
||||
if (src2->isConst()) { \
|
||||
int64_t right = inst1->getSrc(1)->getValInt(); \
|
||||
right OP##= src2->getValInt(); \
|
||||
return m_tb->gen##NAME(inst1->getSrc(0), genDefInt(right)); \
|
||||
} \
|
||||
/* (X + C1) + (Y + C2) --> X + Y + C3 */ \
|
||||
if (inst2->getOpcode() == Op##NAME && inst2->getSrc(1)->isConst()) { \
|
||||
int64_t right = inst1->getSrc(1)->getValInt(); \
|
||||
right OP##= inst2->getSrc(1)->getValInt(); \
|
||||
SSATmp* left = m_tb->gen##NAME(inst1->getSrc(0), inst2->getSrc(0)); \
|
||||
return m_tb->gen##NAME(left, genDefInt(right)); \
|
||||
} \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SIMPLIFY_DISTRIBUTIVE(OUTOP, INOP, OUTNAME, INNAME) do { \
|
||||
@@ -766,6 +768,10 @@ SSATmp* Simplifier::simplifyCmp(Opcode opName, SSATmp* src1, SSATmp* src2) {
|
||||
if (src1->getType() == Type::Obj && src2->getType() == Type::Obj) {
|
||||
return nullptr;
|
||||
}
|
||||
// for arrays, don't simplify Same to Eq
|
||||
if (src1->getType() == Type::Arr && src2->getType() == Type::Arr) {
|
||||
return nullptr;
|
||||
}
|
||||
// Type is neither a string nor an object - simplify to OpEq/OpNeq
|
||||
if (opName == OpSame) {
|
||||
return m_tb->genCmp(OpEq, src1, src2);
|
||||
|
||||
@@ -120,8 +120,16 @@ void VectorEffects::init(Opcode op, const Type origBase,
|
||||
// definitely happen but those cases aren't handled yet. In a perfect world
|
||||
// we would remove Type::Null from baseType here but that can produce types
|
||||
// that are tricky to guard against and doesn't buy us much right now.
|
||||
baseType = baseType.isNull() ? newBase : (baseType | newBase);
|
||||
baseValChanged = true;
|
||||
if (!baseBoxed || !baseType.isString()) {
|
||||
/*
|
||||
* Uses of boxed types are always guarded, in case the inner
|
||||
* type was modified. If the base type was String, its extremely
|
||||
* likely to still be a String, so leave it as such, and we'll
|
||||
* exit in the rare case that it changed.
|
||||
*/
|
||||
baseType = baseType.isNull() ? newBase : (baseType | newBase);
|
||||
}
|
||||
baseValChanged = !baseBoxed;
|
||||
}
|
||||
if (op == SetElem && baseType.maybe(Type::Arr)) {
|
||||
// possible COW when modifying an array
|
||||
@@ -158,6 +166,12 @@ void VectorEffects::init(Opcode op, const Type origBase,
|
||||
valType = definitelyFail ? Type::InitNull : (valType | Type::InitNull);
|
||||
}
|
||||
|
||||
if (op == SetElem && baseType.maybe(Type::Str)) {
|
||||
// If the base is a String, the result will be different from the value
|
||||
// use valTypeChanged (even though the type may not have)
|
||||
valTypeChanged = true;
|
||||
}
|
||||
|
||||
// The final baseType should be a pointer/box iff the input was
|
||||
baseType = baseBoxed ? baseType.box() : baseType;
|
||||
baseType = basePtr ? baseType.ptr() : baseType;
|
||||
@@ -267,6 +281,14 @@ void HhbcTranslator::VectorTranslator::checkMIState() {
|
||||
if (simpleProp || simpleArraySet || simpleArrayGet) {
|
||||
setNoMIState();
|
||||
}
|
||||
|
||||
/*
|
||||
* In pretty much any case the vector translator will do operations
|
||||
* that can throw. Currently this means we have to have a spillStack
|
||||
* so the unwinder can handle it (eventually we'll hook this into an
|
||||
* unwind codepath). TODO(#2162354)
|
||||
*/
|
||||
m_ht.spillStack();
|
||||
}
|
||||
|
||||
void HhbcTranslator::VectorTranslator::emitMPre() {
|
||||
@@ -341,7 +363,7 @@ SSATmp* HhbcTranslator::VectorTranslator::getInput(unsigned i) {
|
||||
// as what Transl::Translator came up with.
|
||||
auto t = Type::fromRuntimeType(dl.rtt);
|
||||
if (!val->isA(t)) {
|
||||
FTRACE(0, "{}: hhir stack has a {} where Translator had a {}\n",
|
||||
FTRACE(1, "{}: hhir stack has a {} where Translator had a {}\n",
|
||||
__func__, val->getType().toString(), t.toString());
|
||||
// They'd better not be completely unrelated types...
|
||||
assert(t.subtypeOf(val->getType()));
|
||||
@@ -455,8 +477,7 @@ static inline TypedValue* baseGImpl(TypedValue *key,
|
||||
varEnv->set(name, &tv);
|
||||
base = varEnv->lookup(name);
|
||||
} else {
|
||||
tvWriteNull(&mis->tvScratch);
|
||||
base = &mis->tvScratch;
|
||||
return const_cast<TypedValue*>(init_null_variant.asTypedValue());
|
||||
}
|
||||
}
|
||||
decRefStr(name);
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
+----------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
#ifndef _TRANSLATOR_DEPS_H_
|
||||
#define _TRANSLATOR_DEPS_H
|
||||
#ifndef incl_HPHP_TRANSLATOR_DEPS_H
|
||||
#define incl_HPHP_TRANSLATOR_DEPS_H
|
||||
|
||||
#include <vector>
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
|
||||
@@ -86,9 +86,18 @@ void TranslatorX64::reqLitHelper(const ReqLitStaticArgs* args) {
|
||||
*/
|
||||
|
||||
static TCA callAndResume(ActRec *ar) {
|
||||
VMRegAnchor _(ar, true);
|
||||
g_vmContext->doFCall<true>(ar, g_vmContext->m_pc);
|
||||
return Translator::Get()->getResumeHelperRet();
|
||||
if (!ar->m_func->isClonedClosure()) {
|
||||
/*
|
||||
* If the func is a cloned closure, then the original
|
||||
* closure has already run the prolog, and the prologs
|
||||
* array is just being used as entry points for the
|
||||
* dv funclets. Dont run the prolog again.
|
||||
*/
|
||||
VMRegAnchor _(ar, true);
|
||||
g_vmContext->doFCall<true>(ar, g_vmContext->m_pc);
|
||||
return Translator::Get()->getResumeHelperRet();
|
||||
}
|
||||
return Translator::Get()->getResumeHelper();
|
||||
}
|
||||
|
||||
static_assert(rStashedAR == reg::r15,
|
||||
@@ -99,14 +108,22 @@ asm(
|
||||
".globl __fcallHelperThunk\n"
|
||||
"__fcallHelperThunk:\n"
|
||||
#if defined(__x86_64__)
|
||||
// fcallHelper is used for prologs, and (in the case of
|
||||
// closures) for dispatch to the function body. In the first
|
||||
// case, there's a call, in the second, there's a jmp.
|
||||
// We can differentiate by comparing r15 and rVmFp
|
||||
"mov %r15, %rdi\n"
|
||||
"cmp %r15, %rbp\n"
|
||||
"jne 1f\n"
|
||||
"call fcallHelper\n"
|
||||
"jmp *%rax\n"
|
||||
// fcallHelper may call doFCall. doFCall changes the return ip
|
||||
// pointed to by r15 so that it points to TranslatorX64::m_retHelper,
|
||||
// which does a REQ_POST_INTERP_RET service request. So we need to
|
||||
// to pop the return address into r15 + m_savedRip before calling
|
||||
// fcallHelper, and then push it back from r15 + m_savedRip after
|
||||
// fcallHelper returns in case it has changed it.
|
||||
"pop 0x8(%r15)\n"
|
||||
"mov %r15, %rdi\n"
|
||||
"1: pop 0x8(%r15)\n"
|
||||
"call fcallHelper\n"
|
||||
"push 0x8(%r15)\n"
|
||||
"jmp *%rax\n"
|
||||
@@ -122,7 +139,7 @@ extern "C"
|
||||
TCA fcallHelper(ActRec* ar) {
|
||||
try {
|
||||
TCA tca =
|
||||
Translator::Get()->funcPrologue((Func*)ar->m_func, ar->numArgs());
|
||||
Translator::Get()->funcPrologue((Func*)ar->m_func, ar->numArgs(), ar);
|
||||
if (tca) {
|
||||
return tca;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,11 @@
|
||||
typedef __sighandler_t *sighandler_t;
|
||||
# define RIP_REGISTER(v) (v).mc_rip
|
||||
#else
|
||||
# define RIP_REGISTER(v) (v).gregs[REG_RIP]
|
||||
# if defined(__x86_64__)
|
||||
# define RIP_REGISTER(v) (v).gregs[REG_RIP]
|
||||
# elif defined(__AARCH64EL__)
|
||||
# define RIP_REGISTER(v) (v).pc
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
@@ -2057,8 +2061,16 @@ TranslatorX64::emitPopRetIntoActRec(Asm& a) {
|
||||
a. pop (rStashedAR[AROFF(m_savedRip)]);
|
||||
}
|
||||
|
||||
static void interp_set_regs(ActRec* ar, Cell* sp, Offset pcOff) {
|
||||
assert(tl_regState == REGSTATE_DIRTY);
|
||||
tl_regState = REGSTATE_CLEAN;
|
||||
vmfp() = (Cell*)ar;
|
||||
vmsp() = sp;
|
||||
vmpc() = curUnit()->at(pcOff);
|
||||
}
|
||||
|
||||
TCA
|
||||
TranslatorX64::funcPrologue(Func* func, int nPassed) {
|
||||
TranslatorX64::funcPrologue(Func* func, int nPassed, ActRec* ar) {
|
||||
func->validate();
|
||||
TRACE(1, "funcPrologue %s(%d)\n", func->fullName()->data(), nPassed);
|
||||
int numParams = func->numParams();
|
||||
@@ -2069,6 +2081,27 @@ TranslatorX64::funcPrologue(Func* func, int nPassed) {
|
||||
// Do a quick test before grabbing the write lease
|
||||
TCA prologue;
|
||||
if (checkCachedPrologue(func, paramIndex, prologue)) return prologue;
|
||||
if (func->isClonedClosure()) {
|
||||
assert(ar);
|
||||
const Func::ParamInfoVec& paramInfo = func->params();
|
||||
Offset entry = func->base();
|
||||
for (int i = nPassed; i < numParams; ++i) {
|
||||
const Func::ParamInfo& pi = paramInfo[i];
|
||||
if (pi.hasDefaultValue()) {
|
||||
entry = pi.funcletOff();
|
||||
break;
|
||||
}
|
||||
}
|
||||
interp_set_regs(ar, (Cell*)ar - func->numSlotsInFrame(), entry);
|
||||
SrcKey funcBody(func, entry);
|
||||
TCA tca = getTranslation(funcBody, false);
|
||||
tl_regState = REGSTATE_DIRTY;
|
||||
if (tca) {
|
||||
// racy, but ok...
|
||||
func->setPrologue(paramIndex, tca);
|
||||
}
|
||||
return tca;
|
||||
}
|
||||
|
||||
// If the translator is getting replaced out from under us, refuse to
|
||||
// provide a prologue; we don't know whether this request is running on the
|
||||
@@ -2078,6 +2111,7 @@ TranslatorX64::funcPrologue(Func* func, int nPassed) {
|
||||
// Double check the prologue array now that we have the write lease
|
||||
// in case another thread snuck in and set the prologue already.
|
||||
if (checkCachedPrologue(func, paramIndex, prologue)) return prologue;
|
||||
|
||||
SpaceRecorder sr("_FuncPrologue", a);
|
||||
// If we're close to a cache line boundary, just burn some space to
|
||||
// try to keep the func and its body on fewer total lines.
|
||||
@@ -2365,7 +2399,15 @@ TranslatorX64::emitPrologue(Func* func, int nPassed) {
|
||||
// code
|
||||
emitCheckSurpriseFlagsEnter(false, fixup);
|
||||
|
||||
emitBindJmp(funcBody);
|
||||
if (func->isClosureBody() && func->cls()) {
|
||||
int entry = nPassed <= numParams ? nPassed : numParams + 1;
|
||||
// Relying on rStashedAR == rVmFp here
|
||||
a. loadq (rStashedAR[AROFF(m_func)], rax);
|
||||
a. loadq (rax[Func::prologueTableOff() + sizeof(TCA)*entry], rax);
|
||||
a. jmp (rax);
|
||||
} else {
|
||||
emitBindJmp(funcBody);
|
||||
}
|
||||
return funcBody;
|
||||
}
|
||||
|
||||
@@ -3059,6 +3101,22 @@ struct DepthGuard { bool depthOne() const { return false; } };
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
* enterTCHelper does not save callee-saved registers except %rbp. This means
|
||||
* when we call it from C++, we have to tell gcc to clobber all the other
|
||||
* callee-saved registers.
|
||||
*/
|
||||
#if defined(__x86_64__)
|
||||
# define CALLEE_SAVED_BARRIER() \
|
||||
asm volatile("" : : : "rbx", "r12", "r13", "r14", "r15")
|
||||
#elif defined(__AARCH64EL__)
|
||||
# define CALLEE_SAVED_BARRIER() \
|
||||
asm volatile("" : : : "x19", "x20", "x21", "x22", "x23", "x24", "x25", \
|
||||
"x26", "x27", "x28")
|
||||
#else
|
||||
# error What are the callee-saved registers on your system?
|
||||
#endif
|
||||
|
||||
/*
|
||||
* enterTCHelper
|
||||
*
|
||||
@@ -3959,14 +4017,6 @@ TranslatorX64::binaryArithLocal(const NormalizedInstruction &i,
|
||||
}
|
||||
}
|
||||
|
||||
static void interp_set_regs(ActRec* ar, Cell* sp, Offset pcOff) {
|
||||
assert(tl_regState == REGSTATE_DIRTY);
|
||||
tl_regState = REGSTATE_CLEAN;
|
||||
vmfp() = (Cell*)ar;
|
||||
vmsp() = sp;
|
||||
vmpc() = curUnit()->at(pcOff);
|
||||
}
|
||||
|
||||
#define O(opcode, imm, pusph, pop, flags) \
|
||||
/**
|
||||
* The interpOne methods saves m_pc, m_fp, and m_sp ExecutionContext,
|
||||
@@ -9067,7 +9117,7 @@ TranslatorX64::translateFPushObjMethodD(const Tracelet &t,
|
||||
}
|
||||
|
||||
if (func) {
|
||||
if (func->attrs() & AttrStatic) {
|
||||
if (func->attrs() & AttrStatic && !func->isClosureBody()) {
|
||||
if (func->attrs() & AttrPrivate) {
|
||||
emitVStackStoreImm(a, i, uintptr_t(curFunc()->cls()) | 1,
|
||||
thisOff, sz::qword);
|
||||
|
||||
@@ -940,7 +940,7 @@ private:
|
||||
static int shuffleArgsForMagicCall(ActRec* ar);
|
||||
static void setArgInActRec(ActRec* ar, int argNum, uint64_t datum,
|
||||
DataType t);
|
||||
TCA funcPrologue(Func* func, int nArgs);
|
||||
TCA funcPrologue(Func* func, int nArgs, ActRec* ar = nullptr);
|
||||
bool checkCachedPrologue(const Func* func, int param, TCA& plgOut) const;
|
||||
SrcKey emitPrologue(Func* func, int nArgs);
|
||||
int32_t emitNativeImpl(const Func*, bool emitSavedRIPReturn);
|
||||
|
||||
@@ -888,7 +888,7 @@ public:
|
||||
virtual void requestInit() = 0;
|
||||
virtual void requestExit() = 0;
|
||||
virtual void analyzeInstr(Tracelet& t, NormalizedInstruction& i) = 0;
|
||||
virtual TCA funcPrologue(Func* f, int nArgs) = 0;
|
||||
virtual TCA funcPrologue(Func* f, int nArgs, ActRec* ar = nullptr) = 0;
|
||||
virtual TCA getCallToExit() = 0;
|
||||
virtual TCA getRetFromInterpretedFrame() = 0;
|
||||
virtual void defineCns(StringData* name) = 0;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
"is_a", T(Boolean), S(0), "class_or_object", T(Variant), NULL, S(0), NULL, S(0), "class_name", T(String), NULL, S(0), NULL, S(0), "allow_string", T(Boolean), "b:0;", S(4), "false", S(0), NULL, S(16384), "/**\n * ( excerpt from http://php.net/manual/en/function.is-a.php )\n *\n * Checks if the given object is of this class or has this class as one of\n * its parents.\n *\n * @class_or_object\n * mixed The tested object\n * @class_name string The class name\n * @allow_string\n * bool If this parameter set to false (default), string\n * class name as object is not allowed. This also\n * prevents from calling autoloader if the class\n * doesn't exist.\n *\n * @return bool Returns TRUE if the object is of this class or has\n * this class as one of its parents, FALSE otherwise.\n */",
|
||||
"is_subclass_of", T(Boolean), S(0), "class_or_object", T(Variant), NULL, S(0), NULL, S(0), "class_name", T(String), NULL, S(0), NULL, S(0), "allow_string", T(Boolean), "b:1;", S(4), "true", S(0), NULL, S(16384), "/**\n * ( excerpt from http://php.net/manual/en/function.is-subclass-of.php )\n *\n * Checks if the given object has the class class_name as one of its\n * parents.\n *\n * @class_or_object\n * mixed A class name or an object instance\n * @class_name string The class name\n * @allow_string\n * bool If this parameter set to false, string class name as\n * object is not allowed. This also prevents from\n * calling autoloader if the class doesn't exist.\n *\n * @return bool This function returns TRUE if the object object,\n * belongs to a class which is a subclass of\n * class_name, FALSE otherwise.\n */",
|
||||
"method_exists", T(Boolean), S(0), "class_or_object", T(Variant), NULL, S(0), NULL, S(0), "method_name", T(String), NULL, S(0), NULL, S(0), NULL, S(16384), "/**\n * ( excerpt from http://php.net/manual/en/function.method-exists.php )\n *\n * Checks if the class method exists in the given object.\n *\n * @class_or_object\n * mixed An object instance or a class name\n * @method_name\n * string The method name\n *\n * @return bool Returns TRUE if the method given by method_name has\n * been defined for the given object, FALSE otherwise.\n */",
|
||||
"property_exists", T(Boolean), S(0), "class_or_object", T(Variant), NULL, S(0), NULL, S(0), "property", T(String), NULL, S(0), NULL, S(0), NULL, S(16384), "/**\n * ( excerpt from http://php.net/manual/en/function.property-exists.php )\n *\n * This function checks if the given property exists in the specified\n * class.\n *\n * As opposed with isset(), property_exists() returns TRUE even if the\n * property has the value NULL.\n *\n * @class_or_object\n * mixed The class name or an object of the class to test for\n * @property string The name of the property\n *\n * @return bool Returns TRUE if the property exists, FALSE if it\n * doesn't exist or NULL in case of an error.\n */",
|
||||
"property_exists", T(Variant), S(0), "class_or_object", T(Variant), NULL, S(0), NULL, S(0), "property", T(String), NULL, S(0), NULL, S(0), NULL, S(16384), "/**\n * ( excerpt from http://php.net/manual/en/function.property-exists.php )\n *\n * This function checks if the given property exists in the specified\n * class.\n *\n * As opposed with isset(), property_exists() returns TRUE even if the\n * property has the value NULL.\n *\n * @class_or_object\n * mixed The class name or an object of the class to test for\n * @property string The name of the property\n *\n * @return mixed Returns TRUE if the property exists, FALSE if it\n * doesn't exist or NULL in case of an error.\n */",
|
||||
"get_object_vars", T(Variant), S(0), "object", T(Variant), NULL, S(0), NULL, S(0), NULL, S(16384), "/**\n * ( excerpt from http://php.net/manual/en/function.get-object-vars.php )\n *\n * Gets the accessible non-static properties of the given object according\n * to scope.\n *\n * @object mixed An object instance.\n *\n * @return mixed Returns an associative array of defined object\n * accessible non-static properties for the specified\n * object in scope. If a property have not been\n * assigned a value, it will be returned with a NULL\n * value.\n */",
|
||||
"call_user_method_array", T(Variant), S(0), "method_name", T(String), NULL, S(0), NULL, S(0), "obj", T(Variant), NULL, S(0), NULL, S(1), "paramarr", T(Array), NULL, S(0), NULL, S(0), NULL, S(16384), "/**\n * ( excerpt from\n * http://php.net/manual/en/function.call-user-method-array.php )\n *\n *\n * @method_name\n * string The method name being called.\n * @obj mixed The object that method_name is being called on.\n * @paramarr vector An array of parameters.\n *\n * @return mixed\n */",
|
||||
"call_user_method", T(Variant), S(0), "method_name", T(String), NULL, S(0), NULL, S(0), "obj", T(Variant), NULL, S(0), NULL, S(1), NULL, S(147456), "/**\n * ( excerpt from http://php.net/manual/en/function.call-user-method.php )\n *\n *\n * @method_name\n * string The method name being called.\n * @obj mixed The object that method_name is being called on.\n *\n * @return mixed\n */",
|
||||
|
||||
@@ -457,7 +457,7 @@ const int64_t k_GRAPHEME_EXTR_MAXBYTES = 1;
|
||||
const int64_t k_GRAPHEME_EXTR_MAXCHARS = 2;
|
||||
const int64_t k_HASH_HMAC = 1;
|
||||
extern const StaticString k_HPHP_TRIM_CHARLIST("\n\r\t\v\000 ",6);
|
||||
extern const StaticString k_HPHP_VERSION("1.0.0",5);
|
||||
extern const StaticString k_HPHP_VERSION("2.0.2",5);
|
||||
const int64_t k_HTML_ENTITIES = 1;
|
||||
const int64_t k_HTML_SPECIALCHARS = 0;
|
||||
extern const StaticString k_ICONV_IMPL("glibc",5);
|
||||
@@ -12884,8 +12884,8 @@ const char *g_class_map[] = {
|
||||
NULL,
|
||||
NULL,
|
||||
(const char *)0x10006040, "property_exists", "", (const char*)0, (const char*)0,
|
||||
"/**\n * ( excerpt from http://php.net/manual/en/function.property-exists.php )\n *\n * This function checks if the given property exists in the specified\n * class.\n *\n * As opposed with isset(), property_exists() returns TRUE even if the\n * property has the value NULL.\n *\n * @class_or_object\n * mixed The class name or an object of the class to test for\n * @property string The name of the property\n *\n * @return bool Returns TRUE if the property exists, FALSE if it\n * doesn't exist or NULL in case of an error.\n */",
|
||||
(const char *)0x9, (const char *)0x2000, "class_or_object", "", (const char *)0xffffffff, "", "", NULL,
|
||||
"/**\n * ( excerpt from http://php.net/manual/en/function.property-exists.php )\n *\n * This function checks if the given property exists in the specified\n * class.\n *\n * As opposed with isset(), property_exists() returns TRUE even if the\n * property has the value NULL.\n *\n * @class_or_object\n * mixed The class name or an object of the class to test for\n * @property string The name of the property\n *\n * @return mixed Returns TRUE if the property exists, FALSE if it\n * doesn't exist or NULL in case of an error.\n */",
|
||||
(const char *)0xffffffff, (const char *)0x2000, "class_or_object", "", (const char *)0xffffffff, "", "", NULL,
|
||||
(const char *)0x2000, "property", "", (const char *)0x14, "", "", NULL,
|
||||
NULL,
|
||||
NULL,
|
||||
@@ -17963,7 +17963,7 @@ const char *g_class_map[] = {
|
||||
"GRAPHEME_EXTR_MAXCHARS", (const char*)4, "i:2;",
|
||||
"HASH_HMAC", (const char*)4, "i:1;",
|
||||
"HPHP_TRIM_CHARLIST", (const char*)13, "s:6:\"\n\r\t\v\000 \";",
|
||||
"HPHP_VERSION", (const char*)12, "s:5:\"1.0.0\";",
|
||||
"HPHP_VERSION", (const char*)12, "s:5:\"2.0.2\";",
|
||||
"HTML_ENTITIES", (const char*)4, "i:1;",
|
||||
"HTML_SPECIALCHARS", (const char*)4, "i:0;",
|
||||
"ICONV_IMPL", (const char*)12, "s:5:\"glibc\";",
|
||||
|
||||
@@ -445,7 +445,7 @@ define('GRAPHEME_EXTR_MAXBYTES', 1);
|
||||
define('GRAPHEME_EXTR_MAXCHARS', 2);
|
||||
define('HASH_HMAC', 1);
|
||||
define('HPHP_TRIM_CHARLIST', "\n\r\t\v\000 ");
|
||||
define('HPHP_VERSION', "1.0.0");
|
||||
define('HPHP_VERSION', "2.0.2");
|
||||
define('HTML_ENTITIES', 1);
|
||||
define('HTML_SPECIALCHARS', 0);
|
||||
define('ICONV_IMPL', "glibc");
|
||||
|
||||
@@ -2670,7 +2670,7 @@ DefineConstant(
|
||||
DefineConstant(
|
||||
array(
|
||||
'name' => "HPHP_VERSION",
|
||||
'value' => "1.0.0",
|
||||
'value' => "2.0.2",
|
||||
));
|
||||
|
||||
DefineConstant(
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
class X {
|
||||
function __construct() {
|
||||
}
|
||||
}
|
||||
|
||||
function handler($kind, $name) {
|
||||
if ($kind == 'exit' && $name == 'X::__construct') throw new Exception;
|
||||
}
|
||||
|
||||
function test() {
|
||||
fb_setprofile('handler');
|
||||
try {
|
||||
new X;
|
||||
} catch (Exception $e) {
|
||||
echo "ok\n";
|
||||
}
|
||||
}
|
||||
|
||||
test();
|
||||
@@ -0,0 +1 @@
|
||||
ok
|
||||
@@ -9659,6 +9659,236 @@ bool TestCodeRun::TestCollectionClasses() {
|
||||
"bool(false)\n"
|
||||
);
|
||||
|
||||
{
|
||||
HipHopSyntax w1(this);
|
||||
MVCRO("<?php\n"
|
||||
"function f() {\n"
|
||||
" $v = Vector {'a', 'b', 'c'};\n"
|
||||
" $m = new Map($v);\n"
|
||||
" var_dump($m);\n"
|
||||
" $sm = new StableMap($m);\n"
|
||||
" var_dump($sm);\n"
|
||||
"}\n"
|
||||
"function g() {\n"
|
||||
" $m = Map {'a' => 1, 2 => 'b'};\n"
|
||||
" $v = new Vector($m);\n"
|
||||
" var_dump($v);\n"
|
||||
"}\n"
|
||||
"function h() {\n"
|
||||
" $arr1 = array(11, 22, 33);\n"
|
||||
" var_dump(new Vector($arr1));\n"
|
||||
" var_dump(new Map($arr1));\n"
|
||||
" $arr2 = array('a' => 1, 2 => 'b');\n"
|
||||
" var_dump(new Vector($arr2));\n"
|
||||
" var_dump(new StableMap($arr2));\n"
|
||||
"}\n"
|
||||
"function gen() {\n"
|
||||
" yield 42;\n"
|
||||
" yield 72;\n"
|
||||
"}\n"
|
||||
"function j() {\n"
|
||||
" $v = new Vector(gen());\n"
|
||||
" var_dump($v);\n"
|
||||
"}\n"
|
||||
"f();\n"
|
||||
"g();\n"
|
||||
"h();\n"
|
||||
"j();\n"
|
||||
,
|
||||
"object(Map)#2 (3) {\n"
|
||||
" [0]=>\n"
|
||||
" string(1) \"a\"\n"
|
||||
" [1]=>\n"
|
||||
" string(1) \"b\"\n"
|
||||
" [2]=>\n"
|
||||
" string(1) \"c\"\n"
|
||||
"}\n"
|
||||
"object(StableMap)#3 (3) {\n"
|
||||
" [0]=>\n"
|
||||
" string(1) \"a\"\n"
|
||||
" [1]=>\n"
|
||||
" string(1) \"b\"\n"
|
||||
" [2]=>\n"
|
||||
" string(1) \"c\"\n"
|
||||
"}\n"
|
||||
"object(Vector)#2 (2) {\n"
|
||||
" [0]=>\n"
|
||||
" int(1)\n"
|
||||
" [1]=>\n"
|
||||
" string(1) \"b\"\n"
|
||||
"}\n"
|
||||
"object(Vector)#1 (3) {\n"
|
||||
" [0]=>\n"
|
||||
" int(11)\n"
|
||||
" [1]=>\n"
|
||||
" int(22)\n"
|
||||
" [2]=>\n"
|
||||
" int(33)\n"
|
||||
"}\n"
|
||||
"object(Map)#1 (3) {\n"
|
||||
" [0]=>\n"
|
||||
" int(11)\n"
|
||||
" [1]=>\n"
|
||||
" int(22)\n"
|
||||
" [2]=>\n"
|
||||
" int(33)\n"
|
||||
"}\n"
|
||||
"object(Vector)#1 (2) {\n"
|
||||
" [0]=>\n"
|
||||
" int(1)\n"
|
||||
" [1]=>\n"
|
||||
" string(1) \"b\"\n"
|
||||
"}\n"
|
||||
"object(StableMap)#1 (2) {\n"
|
||||
" [\"a\"]=>\n"
|
||||
" int(1)\n"
|
||||
" [2]=>\n"
|
||||
" string(1) \"b\"\n"
|
||||
"}\n"
|
||||
"object(Vector)#1 (2) {\n"
|
||||
" [0]=>\n"
|
||||
" int(42)\n"
|
||||
" [1]=>\n"
|
||||
" int(72)\n"
|
||||
"}\n"
|
||||
);
|
||||
|
||||
MVCRO("<?hh\n"
|
||||
"function g((string,int,string) $t) {}\n"
|
||||
"class C {\n"
|
||||
" public $t = Tuple {'foo', 42, '!'};\n"
|
||||
"}\n"
|
||||
"function f() {\n"
|
||||
" $t = Tuple {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,\n"
|
||||
" 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,\n"
|
||||
" 23, 24, 25, 26, 27, 28, 29, 30, 31, 32};\n"
|
||||
" var_dump(count($t));\n"
|
||||
" var_dump($t->count());\n"
|
||||
" var_dump($t->isEmpty());\n"
|
||||
" echo \"==========\\n\";\n"
|
||||
" $c = new C;\n"
|
||||
" $t = $c->t;\n"
|
||||
" var_dump(count($t));\n"
|
||||
" var_dump($t->count());\n"
|
||||
" var_dump($t->isEmpty());\n"
|
||||
" g($t);\n"
|
||||
" echo \"==========\\n\";\n"
|
||||
" foreach ($t as $k => $v) {\n"
|
||||
" var_dump($k, $v);\n"
|
||||
" }\n"
|
||||
" echo \"==========\\n\";\n"
|
||||
" var_dump($t[0], $t[1], $t[2]);\n"
|
||||
" echo \"==========\\n\";\n"
|
||||
" var_dump($t->at(0), $t->at(1), $t->at(2));\n"
|
||||
" echo \"==========\\n\";\n"
|
||||
" var_dump($t->get(0), $t->get(1), $t->get(2), $t->get(3));\n"
|
||||
" echo \"==========\\n\";\n"
|
||||
" var_dump((array)$t);\n"
|
||||
" echo \"==========\\n\";\n"
|
||||
" var_dump(serialize($t));\n"
|
||||
" var_dump(unserialize(serialize($t)));\n"
|
||||
" echo \"==========\\n\";\n"
|
||||
" var_dump($t->count());\n"
|
||||
" echo \"==========\\n\";\n"
|
||||
" var_dump($t->getIterator() instanceof Iterator);\n"
|
||||
" var_dump($t->getIterator() instanceof KeyedIterator);\n"
|
||||
" foreach ($t->getIterator() as $k => $v) {\n"
|
||||
" var_dump($k, $v);\n"
|
||||
" }\n"
|
||||
" echo \"==========\\n\";\n"
|
||||
" var_dump((array)$t);\n"
|
||||
" var_dump($t->toArray());\n"
|
||||
" echo \"==========\\n\";\n"
|
||||
" var_dump(clone $t);\n"
|
||||
"}\n"
|
||||
"f();\n"
|
||||
,
|
||||
"int(32)\n"
|
||||
"int(32)\n"
|
||||
"bool(false)\n"
|
||||
"==========\n"
|
||||
"int(3)\n"
|
||||
"int(3)\n"
|
||||
"bool(false)\n"
|
||||
"==========\n"
|
||||
"int(0)\n"
|
||||
"string(3) \"foo\"\n"
|
||||
"int(1)\n"
|
||||
"int(42)\n"
|
||||
"int(2)\n"
|
||||
"string(1) \"!\"\n"
|
||||
"==========\n"
|
||||
"string(3) \"foo\"\n"
|
||||
"int(42)\n"
|
||||
"string(1) \"!\"\n"
|
||||
"==========\n"
|
||||
"string(3) \"foo\"\n"
|
||||
"int(42)\n"
|
||||
"string(1) \"!\"\n"
|
||||
"==========\n"
|
||||
"string(3) \"foo\"\n"
|
||||
"int(42)\n"
|
||||
"string(1) \"!\"\n"
|
||||
"NULL\n"
|
||||
"==========\n"
|
||||
"array(3) {\n"
|
||||
" [0]=>\n"
|
||||
" string(3) \"foo\"\n"
|
||||
" [1]=>\n"
|
||||
" int(42)\n"
|
||||
" [2]=>\n"
|
||||
" string(1) \"!\"\n"
|
||||
"}\n"
|
||||
"==========\n"
|
||||
"string(39) \"V:5:\"Tuple\":3:{s:3:\"foo\";i:42;s:1:\"!\";}\"\n"
|
||||
"object(Tuple)#6 (3) {\n"
|
||||
" [0]=>\n"
|
||||
" string(3) \"foo\"\n"
|
||||
" [1]=>\n"
|
||||
" int(42)\n"
|
||||
" [2]=>\n"
|
||||
" string(1) \"!\"\n"
|
||||
"}\n"
|
||||
"==========\n"
|
||||
"int(3)\n"
|
||||
"==========\n"
|
||||
"bool(true)\n"
|
||||
"bool(true)\n"
|
||||
"int(0)\n"
|
||||
"string(3) \"foo\"\n"
|
||||
"int(1)\n"
|
||||
"int(42)\n"
|
||||
"int(2)\n"
|
||||
"string(1) \"!\"\n"
|
||||
"==========\n"
|
||||
"array(3) {\n"
|
||||
" [0]=>\n"
|
||||
" string(3) \"foo\"\n"
|
||||
" [1]=>\n"
|
||||
" int(42)\n"
|
||||
" [2]=>\n"
|
||||
" string(1) \"!\"\n"
|
||||
"}\n"
|
||||
"array(3) {\n"
|
||||
" [0]=>\n"
|
||||
" string(3) \"foo\"\n"
|
||||
" [1]=>\n"
|
||||
" int(42)\n"
|
||||
" [2]=>\n"
|
||||
" string(1) \"!\"\n"
|
||||
"}\n"
|
||||
"==========\n"
|
||||
"object(Tuple)#6 (3) {\n"
|
||||
" [0]=>\n"
|
||||
" string(3) \"foo\"\n"
|
||||
" [1]=>\n"
|
||||
" int(42)\n"
|
||||
" [2]=>\n"
|
||||
" string(1) \"!\"\n"
|
||||
"}\n"
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -18832,6 +19062,31 @@ bool TestCodeRun::TestClassConstant() {
|
||||
" var_dump($param->getDefaultValue());"
|
||||
"}");
|
||||
|
||||
MVCR("<?php "
|
||||
"class W {"
|
||||
" const FOO = 0;"
|
||||
"}"
|
||||
"class X extends W {"
|
||||
" const FOO = 1;"
|
||||
" static function foo() {"
|
||||
" var_dump(constant('self::FOO'));"
|
||||
" var_dump(constant('parent::FOO'));"
|
||||
" var_dump(constant('static::FOO'));"
|
||||
" var_dump(defined('self::FOO'));"
|
||||
" var_dump(defined('parent::FOO'));"
|
||||
" var_dump(defined('static::FOO'));"
|
||||
" var_dump(defined('self::BAR'));"
|
||||
" var_dump(defined('parent::BAR'));"
|
||||
" var_dump(defined('static::BAR'));"
|
||||
" }"
|
||||
"}"
|
||||
"class Y extends X {"
|
||||
" const FOO = 2;"
|
||||
" const BAR = 1;"
|
||||
"}"
|
||||
"X::foo();"
|
||||
"Y::foo();");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -21190,6 +21445,16 @@ bool TestCodeRun::TestSwitchStatement() {
|
||||
"}\n"
|
||||
"f(32);\n");
|
||||
|
||||
MVCR("<?php "
|
||||
"class X {}"
|
||||
"function test($x) {"
|
||||
" switch (true) {"
|
||||
" case $x instanceof X: var_dump('X'); break;"
|
||||
" default: var_dump('Other'); break;"
|
||||
" }"
|
||||
"}"
|
||||
"test(new X);");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -125,5 +125,9 @@ bool TestExtBzip2::test_bzdecompress() {
|
||||
ret = f_bzdecompress(ret);
|
||||
ret = f_bzdecompress(ret);
|
||||
VS(ret, str);
|
||||
str = StringUtil::Repeat("x", 1000);
|
||||
ret = f_bzcompress(str);
|
||||
ret = f_bzdecompress(ret);
|
||||
VS(ret, str);
|
||||
return Count(true);
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <test/test_ext_misc.h>
|
||||
#include <runtime/ext/ext_misc.h>
|
||||
#include <runtime/ext/ext_string.h>
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -203,8 +204,55 @@ bool TestExtMisc::test_uniqid() {
|
||||
return Count(true);
|
||||
}
|
||||
|
||||
#define VUNPACK(fmt, inp, exp) \
|
||||
{ Array __a = f_unpack(fmt, inp); \
|
||||
VS(__a.exists(1), true); \
|
||||
VS(__a[1], (int64_t)exp); }
|
||||
|
||||
bool TestExtMisc::test_unpack() {
|
||||
// covered in TestCodeRun::TestExtMisc
|
||||
// Also covered in TestCodeRun::TestExtMisc
|
||||
|
||||
String iFF = f_str_repeat("\xFF", sizeof(int));
|
||||
String le32_FF("\xFF\x00\x00\x00", 4, AttachLiteral);
|
||||
String be32_FF("\x00\x00\x00\xFF", 4, AttachLiteral);
|
||||
String le16_FF("\xFF\x00", 2, AttachLiteral);
|
||||
String be16_FF("\x00\xFF", 2, AttachLiteral);
|
||||
|
||||
uint32_t endian_check = 1;
|
||||
bool le = ((char*)&endian_check)[0];
|
||||
|
||||
// HPHP, unlike PHP, truncates overflowing ints
|
||||
if (sizeof(int) == 8) {
|
||||
VUNPACK("I", iFF, 0x7FFFFFFFFFFFFFFF);
|
||||
} else if (sizeof(int) == 4) {
|
||||
VUNPACK("I", iFF, 0xFFFFFFFF);
|
||||
} else {
|
||||
// Panic
|
||||
VS(true, false);
|
||||
}
|
||||
|
||||
VUNPACK("i", iFF, -1);
|
||||
|
||||
// LlNV test 32-bit ints specifically
|
||||
VUNPACK("L", iFF, 0xFFFFFFFF);
|
||||
VUNPACK("l", iFF, -1);
|
||||
|
||||
VUNPACK("N", be32_FF, 0xFF);
|
||||
VUNPACK("V", le32_FF, 0xFF);
|
||||
VUNPACK("V", be32_FF, 0xFF000000);
|
||||
|
||||
VUNPACK("L", le ? le32_FF : be32_FF, 0xFF);
|
||||
|
||||
// Ssnv test 16-bit shorts
|
||||
VUNPACK("S", iFF, 0xFFFF);
|
||||
VUNPACK("s", iFF, -1);
|
||||
|
||||
VUNPACK("n", be16_FF, 0xFF);
|
||||
VUNPACK("v", le16_FF, 0xFF);
|
||||
VUNPACK("v", be16_FF, 0xFF00);
|
||||
|
||||
VUNPACK("S", le ? le16_FF : be16_FF, 0xFF);
|
||||
|
||||
return Count(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,10 @@ bool TestExtPosix::test_posix_getgrgid() {
|
||||
Variant ret = f_posix_getgrgid(f_posix_getgid());
|
||||
VERIFY(!same(ret, false));
|
||||
VERIFY(!ret.toArray().empty());
|
||||
|
||||
Variant bynam = f_posix_getgrnam(ret["name"]);
|
||||
VS(ret, bynam);
|
||||
|
||||
return Count(true);
|
||||
}
|
||||
|
||||
@@ -111,6 +115,10 @@ bool TestExtPosix::test_posix_getgrnam() {
|
||||
Variant ret = f_posix_getgrnam("root");
|
||||
VERIFY(!same(ret, false));
|
||||
VERIFY(!ret.toArray().empty());
|
||||
|
||||
Variant bygid = f_posix_getgrgid(ret["gid"]);
|
||||
VS(ret, bygid);
|
||||
|
||||
return Count(true);
|
||||
}
|
||||
|
||||
@@ -150,6 +158,7 @@ bool TestExtPosix::test_posix_getpwnam() {
|
||||
Variant ret = f_posix_getpwnam("root");
|
||||
VERIFY(!same(ret, false));
|
||||
VERIFY(!ret.toArray().empty());
|
||||
VS(f_posix_getpwnam(""), false);
|
||||
return Count(true);
|
||||
}
|
||||
|
||||
@@ -157,6 +166,7 @@ bool TestExtPosix::test_posix_getpwuid() {
|
||||
Variant ret = f_posix_getpwuid(0);
|
||||
VERIFY(!same(ret, false));
|
||||
VERIFY(!ret.toArray().empty());
|
||||
VS(f_posix_getpwuid(-1), false);
|
||||
return Count(true);
|
||||
}
|
||||
|
||||
@@ -247,7 +257,7 @@ bool TestExtPosix::test_posix_times() {
|
||||
}
|
||||
|
||||
bool TestExtPosix::test_posix_ttyname() {
|
||||
f_posix_ttyname(1);
|
||||
// Jenkins doesn't have real ttys to test this with
|
||||
return Count(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
trait T {
|
||||
function f() {
|
||||
return function ($a) {
|
||||
if ($a) {
|
||||
return $this->foo;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class X {
|
||||
private $foo = 1;
|
||||
use T;
|
||||
}
|
||||
|
||||
class Y {
|
||||
private $foo = 2;
|
||||
use T;
|
||||
}
|
||||
|
||||
function test() {
|
||||
$x = new X;
|
||||
$c = $x->f();
|
||||
var_dump($c(true));
|
||||
|
||||
$y = new Y;
|
||||
$c = $y->f();
|
||||
var_dump($c("foo"));
|
||||
}
|
||||
|
||||
test();
|
||||
@@ -0,0 +1,2 @@
|
||||
int(1)
|
||||
int(2)
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
class A {
|
||||
private $c = 1;
|
||||
|
||||
function b() {
|
||||
$a = function () {
|
||||
var_dump($this);
|
||||
};
|
||||
$a();
|
||||
|
||||
$a = static function () {
|
||||
var_dump($this);
|
||||
};
|
||||
$a();
|
||||
}
|
||||
|
||||
static function c() {
|
||||
$a = function () {
|
||||
var_dump($this);
|
||||
};
|
||||
$a();
|
||||
|
||||
$a = static function () {
|
||||
var_dump($this);
|
||||
};
|
||||
$a();
|
||||
}
|
||||
|
||||
static function d() {
|
||||
var_dump(array_map(function($a) { return $a; }, array(1,2,3)));
|
||||
}
|
||||
}
|
||||
|
||||
(new A)->b();
|
||||
A::b();
|
||||
(new A)->c();
|
||||
A::c();
|
||||
(new A)->d();
|
||||
A::d();
|
||||
@@ -0,0 +1,34 @@
|
||||
object(A)#1 (1) {
|
||||
["c":"A":private]=>
|
||||
int(1)
|
||||
}
|
||||
HipHop Notice: Undefined variable: this in hphp/test/vm/closure_static.php on line 13
|
||||
NULL
|
||||
HipHop Notice: Undefined variable: this in hphp/test/vm/closure_static.php on line 8
|
||||
NULL
|
||||
HipHop Notice: Undefined variable: this in hphp/test/vm/closure_static.php on line 13
|
||||
NULL
|
||||
HipHop Notice: Undefined variable: this in hphp/test/vm/closure_static.php on line 20
|
||||
NULL
|
||||
HipHop Notice: Undefined variable: this in hphp/test/vm/closure_static.php on line 25
|
||||
NULL
|
||||
HipHop Notice: Undefined variable: this in hphp/test/vm/closure_static.php on line 20
|
||||
NULL
|
||||
HipHop Notice: Undefined variable: this in hphp/test/vm/closure_static.php on line 25
|
||||
NULL
|
||||
array(3) {
|
||||
[0]=>
|
||||
int(1)
|
||||
[1]=>
|
||||
int(2)
|
||||
[2]=>
|
||||
int(3)
|
||||
}
|
||||
array(3) {
|
||||
[0]=>
|
||||
int(1)
|
||||
[1]=>
|
||||
int(2)
|
||||
[2]=>
|
||||
int(3)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This is a test that checks that CGetM in the IR properly cleans the
|
||||
* stack before operations that can throw.
|
||||
*/
|
||||
|
||||
class Dtor {
|
||||
public function __construct() { echo "hi\n"; }
|
||||
public function __destruct() { echo "ah\n"; }
|
||||
}
|
||||
|
||||
class Something {
|
||||
public $wat;
|
||||
|
||||
public function __construct() {
|
||||
$this->wat = new Dtor();
|
||||
}
|
||||
}
|
||||
|
||||
class Unsetter {
|
||||
private $x;
|
||||
|
||||
public function __construct() {
|
||||
unset($this->x);
|
||||
}
|
||||
|
||||
public function useX($k) {
|
||||
echo "sup\n";
|
||||
$z = $k->wat + $this->x;
|
||||
return $z;
|
||||
}
|
||||
}
|
||||
|
||||
function thrower() {
|
||||
throw new Exception("wat");
|
||||
}
|
||||
|
||||
function main() {
|
||||
set_error_handler('thrower');
|
||||
$k = new Unsetter;
|
||||
$k->useX(new Something());
|
||||
}
|
||||
|
||||
try {
|
||||
main();
|
||||
} catch (Exception $x) {
|
||||
echo "out\n";
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
hi
|
||||
sup
|
||||
out
|
||||
ah
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,3 @@
|
||||
0123456789012345...6789012345678901
|
||||
Total length: 65492
|
||||
Pass
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,3 @@
|
||||
0123456789012345...7890123456789012
|
||||
Total length: 65493
|
||||
Pass
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,3 @@
|
||||
0123456789012345...8901234567890123
|
||||
Total length: 65494
|
||||
Pass
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
$total_size = 0;
|
||||
$x = <<<"EOD"
|
||||
\\EOD
|
||||
{EOD
|
||||
EOD
|
||||
;
|
||||
|
||||
echo $x;
|
||||
echo "\nTotal length: ";
|
||||
echo strlen($x);
|
||||
echo "\n";
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
\EOD
|
||||
{EOD
|
||||
Total length: 9
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,3 @@
|
||||
0123456789012345...8901234567890123
|
||||
Total length: 262144
|
||||
Pass
|
||||
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
$info = "hello";
|
||||
echo <<<SCRIPT
|
||||
<?php
|
||||
$info
|
||||
throw new Exception(<<<TXT
|
||||
$info
|
||||
|
||||
Fix the above
|
||||
TXT
|
||||
);
|
||||
SCRIPT;
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
hello
|
||||
throw new Exception(<<<TXT
|
||||
hello
|
||||
|
||||
Fix the above
|
||||
TXT
|
||||
);
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,3 @@
|
||||
0123456789012345...6789012345678901
|
||||
Total length: 65492
|
||||
Pass
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,3 @@
|
||||
0123456789012345...7890123456789012
|
||||
Total length: 65493
|
||||
Pass
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,3 @@
|
||||
0123456789012345...8901234567890123
|
||||
Total length: 65494
|
||||
Pass
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
$total_size = 0;
|
||||
$x = <<<'EOD'
|
||||
\\EOD
|
||||
{EOD
|
||||
$EOD
|
||||
EOD
|
||||
;
|
||||
|
||||
echo $x;
|
||||
echo "\nTotal length: ";
|
||||
echo strlen($x);
|
||||
echo "\n";
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
\\EOD
|
||||
{EOD
|
||||
$EOD
|
||||
Total length: 15
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,3 @@
|
||||
0123456789012345...8901234567890123
|
||||
Total length: 262144
|
||||
Pass
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário