:Allow $this on closures

In Zend 5.3 they decided that closures should inherit the ##$this## from the containing scope. This brings us close to paraity with them. The remaining thing is to make

    function() use ($this) {}

fatal after we purge it from WWW.
Esse commit está contido em:
ptarjan
2013-03-20 15:25:40 -07:00
commit de Sara Golemon
commit 4d7004e955
42 arquivos alterados com 678 adições e 192 exclusões
+20 -4
Ver Arquivo
@@ -89,13 +89,29 @@ AnalysisResultRawPtr BlockScope::getContainingProgram() {
return AnalysisResultRawPtr((AnalysisResult*)bs);
}
ClassScopeRawPtr BlockScope::getContainingClass() {
FunctionScopeRawPtr BlockScope::getContainingNonClosureFunction() {
BlockScope *bs = this;
if (bs->is(BlockScope::FunctionScope)) {
// walk out through all the closures
while (bs && bs->is(BlockScope::FunctionScope)) {
HPHP::FunctionScope *fs = static_cast<HPHP::FunctionScope*>(bs);
if (!fs->isClosure() && !fs->isGeneratorFromClosure()) {
return FunctionScopeRawPtr(fs);
}
bs = bs->m_outerScope.get();
}
if (bs && !bs->is(BlockScope::ClassScope)) {
bs = 0;
return FunctionScopeRawPtr();
}
ClassScopeRawPtr BlockScope::getContainingClass() {
BlockScope *bs = getContainingNonClosureFunction().get();
if (!bs) {
bs = this;
}
if (bs && bs->is(BlockScope::FunctionScope)) {
bs = bs->m_outerScope.get();
}
if (!bs || !bs->is(BlockScope::ClassScope)) {
return ClassScopeRawPtr();
}
return ClassScopeRawPtr((HPHP::ClassScope*)bs);
}
+2
Ver Arquivo
@@ -341,6 +341,8 @@ protected:
int m_updated;
int m_runId;
private:
FunctionScopeRawPtr getContainingNonClosureFunction();
Marks m_mark;
BlockScopeRawPtrFlagsPtrVec m_orderedDeps;
BlockScopeRawPtrFlagsVec m_orderedUsers;
+42 -35
Ver Arquivo
@@ -2501,7 +2501,6 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
not_reached();
case Statement::KindOfFunctionStatement: {
assert(!node->getClassScope()); // Handled directly by emitClass().
MethodStatementPtr m(static_pointer_cast<MethodStatement>(node));
// Only called for fn defs not on the top level
StringData* nName = StringData::GetStaticString(m->getOriginalName());
@@ -2523,6 +2522,7 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
postponeMeth(m, nullptr, true);
} else {
assert(!node->getClassScope()); // Handled directly by emitClass().
FuncEmitter* fe = m_ue.newFuncEmitter(nName, false);
e.DefFunc(fe->id());
postponeMeth(m, fe, false);
@@ -3540,9 +3540,9 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
scalarExp(static_pointer_cast<ScalarExpression>(node));
// Inside traits, __class__ cannot be resolved yet,
// so emit call to get_class.
if (scalarExp->getType() == T_CLASS_C && m_curFunc &&
m_curFunc->pce() &&
(m_curFunc->pce()->attrs() & VM::AttrTrait)) {
if (scalarExp->getType() == T_CLASS_C &&
ex->getFunctionScope()->getContainingClass() &&
ex->getFunctionScope()->getContainingClass()->isTrait()) {
static const StringData* fname =
StringData::GetStaticString("get_class");
Offset fpiStart = m_ue.bcPos();
@@ -5259,6 +5259,28 @@ void EmitterVisitor::emitPostponedMeths() {
m_curFunc = fe;
if (fe->isClosureBody()) {
// We are going to keep the closure as the first local
fe->allocVarId(StringData::GetStaticString("0Closure"));
ClosureUseVarVec* useVars = p.m_closureUseVars;
auto it = useVars->begin();
while (it != useVars->end()) {
const StringData* name = it->first;
if (fe->hasVar(name) && fe->lookupVarId(name) < fe->numParams()) {
// Because PHP is insane you can have a use variable with the same
// name as a param name.
// In that case, params win (which is different than zend but much easier)
it = useVars->erase(it);
} else {
// These are all locals. I want them right after the params so I don't
// have to keep track of which one goes where at runtime.
fe->allocVarId(name);
it++;
}
}
}
// Assign ids to all of the local variables eagerly. This gives us the
// nice property that all named local variables will be assigned ids
// 0 through k-1, while any unnamed local variable will have an id >= k.
@@ -5337,35 +5359,6 @@ void EmitterVisitor::emitPostponedMeths() {
assert(tc.typeName()->data() != (const char*)0xdeadba5eba11f00d);
e.VerifyParamType(i);
}
if (fe->isClosureBody()) {
assert(p.m_closureUseVars != nullptr);
// Emit code to unpack the instance variables (which store the
// use-variables) into locals. Some of the use-variables may have the
// same name, in which case the last one wins.
unsigned n = p.m_closureUseVars->size();
for (unsigned i = 0; i < n; ++i) {
StringData* name = (*p.m_closureUseVars)[i].first;
bool byRef = (*p.m_closureUseVars)[i].second;
emitVirtualLocal(fe->lookupVarId(name));
if (i) {
m_metaInfo.add(m_ue.bcPos(), Unit::MetaInfo::GuardedThis,
false, 0, 0);
}
e.CheckThis();
m_evalStack.push(StackSym::H);
m_evalStack.push(StackSym::T);
m_evalStack.setString(name);
markProp(e);
if (byRef) {
emitVGet(e);
emitBind(e);
} else {
emitCGet(e);
emitSet(e);
}
emitPop(e);
}
}
if (funcScope->isAbstract()) {
StringData* msg =
@@ -5411,8 +5404,7 @@ void EmitterVisitor::emitPostponedMeths() {
e.ContExit();
} else {
e.Null();
if ((p.m_meth->getStmts() && p.m_meth->getStmts()->isGuarded()) ||
(fe->isClosureBody() && p.m_closureUseVars->size())) {
if ((p.m_meth->getStmts() && p.m_meth->getStmts()->isGuarded())) {
m_metaInfo.add(m_ue.bcPos(), Unit::MetaInfo::GuardedThis,
false, 0, 0);
}
@@ -5665,6 +5657,21 @@ void EmitterVisitor::emitPostponedClosureCtors() {
emitPop(e);
}
}
// call parent::__construct()
static StringData* s___construct = StringData::GetStaticString("__construct");
e.String(s___construct);
static StringData* s_Closure = StringData::GetStaticString("Closure");
e.String(s_Closure);
e.AGetC();
Offset fpiStart = m_ue.bcPos();
e.FPushClsMethodF(0);
{
FPIRegionRecorder fpi(this, m_ue, m_evalStack, fpiStart);
}
e.FCall(0);
e.PopR();
e.Null();
if (n > 0) {
m_metaInfo.add(m_ue.bcPos(), Unit::MetaInfo::GuardedThis,
@@ -47,6 +47,11 @@ ClosureExpression::ClosureExpression
ParameterExpressionPtr param(
dynamic_pointer_cast<ParameterExpression>((*vars)[i]));
assert(param);
if (param->getName() == "this") {
// "this" is automatically included.
// Once we get rid of all the callsites, make this an error
continue;
}
if (seenBefore.find(param->getName().c_str()) == seenBefore.end()) {
seenBefore.insert(param->getName().c_str());
m_vars->insertElement(param);
+14 -18
Ver Arquivo
@@ -12,6 +12,7 @@
// Preamble: C++ code inserted at beginning of ext_{name}.h
DefinePreamble(<<<CPP
#include <runtime/vm/func.h>
CPP
);
@@ -88,40 +89,35 @@ BeginClass(
array(
'name' => 'Closure',
'desc' => 'Used as the base class for all closures',
// https://phabricator.fb.com/D727298 means this can't be abstract
// 'flags' => IsAbstract,
'footer' => <<<EOT
public:
public: ObjectData* clone();
ObjectData* getThisOrClass() { return m_thisOrClass; }
const VM::Func* getInvokeFunc() { return m_func; }
HphpArray* getStaticLocals();
TypedValue* getUseVars() { return propVec(); }
int getNumUseVars() { return m_cls->numDeclProperties(); }
protected:
virtual bool php_sleep(Variant &ret);
private:
SmartPtr<HphpArray> m_VMStatics;
ObjectData* m_thisOrClass;
const VM::Func* m_func;
EOT
,
)
);
DefineProperty(
array(
'name' => '__static_locals',
'type' => String,
'flags' => IsProtected,
'desc' => 'For storing the static locals from the closure body',
));
DefineFunction(
array(
'name' => '__construct',
'args' => array(),
'return' => array(
'type' => null,
),
));
DefineFunction(
array(
'name' => '__invoke',
'flags' => VariableArguments,
'return' => array(
'type' => Variant,
),
));
EndClass();
BeginClass(
+49 -8
Ver Arquivo
@@ -17,25 +17,66 @@
#include <runtime/ext/ext_closure.h>
#include <runtime/base/builtin_functions.h>
#include <runtime/vm/translator/translator-inline.h>
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
c_Closure::c_Closure(VM::Class* cb) : ExtObjectData(cb) {}
c_Closure::~c_Closure() {}
void c_Closure::t___construct() {}
Variant c_Closure::t___invoke(int _argc, CArrRef _argv) {
always_assert(false);
return uninit_null();
c_Closure::c_Closure(VM::Class* cb) : ExtObjectData(cb),
m_thisOrClass(nullptr), m_func(nullptr) {}
c_Closure::~c_Closure() {
// same as ar->hasThis()
if (m_thisOrClass && !(intptr_t(m_thisOrClass) & 3LL)) {
m_thisOrClass->decRefCount();
}
}
void c_Closure::t___construct() {
VM::ActRec* ar;
{
// like calling CallerFrame twice
VM::Transl::VMRegAnchor _;
VMExecutionContext* context = g_vmContext;
VM::ActRec* me = context->getFP();
VM::ActRec* childClosure = context->getPrevVMState(me);
ar = context->getPrevVMState(childClosure);
}
// I don't care if it is a $this or a late bound class because we will just
// put it back in the same place on an ActRec.
m_thisOrClass = ar->m_this;
if (ar->hasThis()) {
ar->getThis()->incRefCount();
}
// Change my __invoke's m_cls to be the same as my creator's
static StringData* invokeName = StringData::GetStaticString("__invoke");
VM::Class* scope = ar->m_func->cls();
m_func = getVMClass()->lookupMethod(invokeName)->cloneAndSetClass(scope);
}
ObjectData* c_Closure::clone() {
ObjectData* obj = ObjectData::clone();
auto closure = static_cast<c_Closure*>(obj);
closure->m_VMStatics = m_VMStatics;
closure->m_thisOrClass = m_thisOrClass;
closure->m_func = m_func;
return obj;
}
bool c_Closure::php_sleep(Variant &ret) {
ret = false;
return true;
}
HphpArray* c_Closure::getStaticLocals() {
if (m_VMStatics.get() == NULL) {
m_VMStatics = NEW(HphpArray)(1);
}
return m_VMStatics.get();
}
///////////////////////////////////////////////////////////////////////////////
c_DummyClosure::c_DummyClosure(VM::Class* cb) :
-48
Ver Arquivo
@@ -70,54 +70,6 @@ TypedValue* tg_7Closure___construct(HPHP::VM::ActRec *ar) {
return &ar->m_r;
}
/*
HPHP::Variant HPHP::c_Closure::t___invoke(int, HPHP::Array const&)
_ZN4HPHP9c_Closure10t___invokeEiRKNS_5ArrayE
(return value) => rax
_rv => rdi
this_ => rsi
_argc => rdx
_argv => rcx
*/
TypedValue* th_7Closure___invoke(TypedValue* _rv, ObjectData* this_, int64_t _argc, Value* _argv) asm("_ZN4HPHP9c_Closure10t___invokeEiRKNS_5ArrayE");
TypedValue* tg_7Closure___invoke(HPHP::VM::ActRec *ar) {
TypedValue rv;
int64_t count = ar->numArgs();
TypedValue* args UNUSED = ((TypedValue*)ar) - 1;
ObjectData* this_ = (ar->hasThis() ? ar->getThis() : NULL);
if (this_) {
Array extraArgs;
{
ArrayInit ai(count-0);
for (int64_t i = 0; i < count; ++i) {
TypedValue* extraArg = ar->getExtraArg(i-0);
if (tvIsStronglyBound(extraArg)) {
ai.setRef(i-0, tvAsVariant(extraArg));
} else {
ai.set(i-0, tvAsVariant(extraArg));
}
}
extraArgs = ai.create();
}
th_7Closure___invoke((&(rv)), (this_), (count), (Value*)(&extraArgs));
if (rv.m_type == KindOfUninit) rv.m_type = KindOfNull;
frame_free_locals_inl(ar, 0);
memcpy(&ar->m_r, &rv, sizeof(TypedValue));
return &ar->m_r;
} else {
throw_instance_method_fatal("Closure::__invoke");
}
rv.m_data.num = 0LL;
rv.m_type = KindOfNull;
frame_free_locals_inl(ar, 0);
memcpy(&ar->m_r, &rv, sizeof(TypedValue));
return &ar->m_r;
return &ar->m_r;
}
HPHP::VM::Instance* new_DummyClosure_Instance(HPHP::VM::Class* cls) {
size_t nProps = cls->numDeclProperties();
size_t builtinPropSize = sizeof(c_DummyClosure) - sizeof(ObjectData);
+17 -8
Ver Arquivo
@@ -21,6 +21,7 @@
// >>>>>> Generated by idl.php. Do NOT modify. <<<<<<
#include <runtime/base/base_includes.h>
#include <runtime/vm/func.h>
namespace HPHP {
///////////////////////////////////////////////////////////////////////////////
@@ -34,20 +35,26 @@ class c_Closure : public ExtObjectData {
public:
DECLARE_CLASS(Closure, Closure, ObjectData)
// These aren't actually used in the VM
// properties
public: Object m_this;
public: String m_className;
public: String m_functionName;
public: String m___static_locals;
// need to implement
public: c_Closure(VM::Class* cls = c_Closure::s_cls);
public: ~c_Closure();
public: void t___construct();
public: Variant t___invoke(int _argc, CArrRef _argv = null_array);
// implemented by HPHP
public: c_Closure *create();
public:
public: ObjectData* clone();
ObjectData* getThisOrClass() { return m_thisOrClass; }
const VM::Func* getInvokeFunc() { return m_func; }
HphpArray* getStaticLocals();
TypedValue* getUseVars() { return propVec(); }
int getNumUseVars() { return m_cls->numDeclProperties(); }
protected:
virtual bool php_sleep(Variant &ret);
private:
SmartPtr<HphpArray> m_VMStatics;
ObjectData* m_thisOrClass;
const VM::Func* m_func;
};
///////////////////////////////////////////////////////////////////////////////
@@ -63,6 +70,8 @@ class c_DummyClosure : public ExtObjectData {
public: ~c_DummyClosure();
public: void t___construct();
// implemented by HPHP
public: c_DummyClosure *create();
};
+2 -4
Ver Arquivo
@@ -2273,7 +2273,6 @@ TypedValue* tg_20RescheduleWaitHandle___construct(VM::ActRec *ar);
TypedValue* tg_20RescheduleWaitHandle_create(VM::ActRec *ar);
VM::Instance* new_Closure_Instance(VM::Class*);
TypedValue* tg_7Closure___construct(VM::ActRec *ar);
TypedValue* tg_7Closure___invoke(VM::ActRec *ar);
VM::Instance* new_DummyClosure_Instance(VM::Class*);
TypedValue* tg_12DummyClosure___construct(VM::ActRec *ar);
VM::Instance* new_Vector_Instance(VM::Class*);
@@ -5309,10 +5308,9 @@ static const HhbcExtMethodInfo hhbc_ext_methods_RescheduleWaitHandle[] = {
{ "create", tg_20RescheduleWaitHandle_create }
};
static const long long hhbc_ext_method_count_Closure = 2;
static const long long hhbc_ext_method_count_Closure = 1;
static const HhbcExtMethodInfo hhbc_ext_methods_Closure[] = {
{ "__construct", tg_7Closure___construct },
{ "__invoke", tg_7Closure___invoke }
{ "__construct", tg_7Closure___construct }
};
static const long long hhbc_ext_method_count_DummyClosure = 1;
+23 -4
Ver Arquivo
@@ -53,6 +53,7 @@
#include <runtime/vm/translator/translator-inline.h>
#include <runtime/ext/ext_string.h>
#include <runtime/ext/ext_error.h>
#include <runtime/ext/ext_closure.h>
#include <runtime/ext/ext_continuation.h>
#include <runtime/ext/ext_function.h>
#include <runtime/ext/ext_variable.h>
@@ -1798,7 +1799,17 @@ bool VMExecutionContext::prepareFuncEntry(ActRec *ar,
}
}
}
pushLocalsAndIterators(func, nparams);
int nlocals = nparams;
if (UNLIKELY(func->isClosureBody())) {
int nuse = init_closure(ar, m_stack.top());
// init_closure doesn't move m_stack
m_stack.nalloc(nuse);
nlocals += nuse;
func = ar->m_func;
}
pushLocalsAndIterators(func, nlocals);
/*
* If we're reentering, make sure to finalize the ActRec before
@@ -6857,18 +6868,26 @@ VMExecutionContext::createContinuation(ActRec* fp,
// does. We set it up once, here, and then just change FP to point to it when
// we enter the generator body.
ActRec* ar = cont->actRec();
ar->m_func = genFunc;
if (isMethod) {
Class* cls = frameStaticClass(fp);
if (origFunc->isClosureBody()) {
genFunc = genFunc->cloneAndSetClass(cls);
}
if (obj.get()) {
ObjectData* objData = obj.get();
ar->setThis(objData);
objData->incRefCount();
} else {
ar->setClass(frameStaticClass(fp));
ar->setClass(cls);
}
} else {
ar->setThis(nullptr);
}
ar->m_func = genFunc;
ar->initNumArgs(1);
ar->setVarEnv(nullptr);
@@ -6953,7 +6972,7 @@ inline void OPTBLD_INLINE VMExecutionContext::iopCreateCont(PC& pc) {
const Func* genFunc = origFunc->getGeneratorBody(genName);
assert(genFunc != nullptr);
bool isMethod = origFunc->isNonClosureMethod();
bool isMethod = origFunc->isMethod();
c_Continuation* cont = isMethod ?
createContinuation<true>(m_fp, getArgs, origFunc, genFunc) :
createContinuation<false>(m_fp, getArgs, origFunc, genFunc);
+5
Ver Arquivo
@@ -704,6 +704,11 @@ public:
pushObjectNoRc(o);
o->incRefCount();
}
inline void ALWAYS_INLINE nalloc(size_t n) {
assert((uintptr_t)&m_top[-n] <= (uintptr_t)m_base);
m_top -= n;
}
inline Cell* ALWAYS_INLINE allocC() {
assert(m_top != m_elms);
+71 -8
Ver Arquivo
@@ -157,14 +157,25 @@ void Func::init(int numParams, bool isGenerator) {
initPrologues(numParams, isGenerator);
}
void* Func::allocFuncMem(const StringData* name, int numParams) {
void* Func::allocFuncMem(
const StringData* name, int numParams, bool needsNextClonedClosure) {
int maxNumPrologues = Func::getMaxNumPrologues(numParams);
int numExtraPrologues =
maxNumPrologues > kNumFixedPrologues ?
maxNumPrologues - kNumFixedPrologues :
0;
size_t funcSize = sizeof(Func) + numExtraPrologues * sizeof(unsigned char*);
return Util::low_malloc(funcSize);
if (needsNextClonedClosure) {
funcSize += sizeof(Func*);
}
void* mem = Util::low_malloc(funcSize);
if (needsNextClonedClosure) {
// make room for nextClonedClosure to work
Func** startOfFunc = (Func**) mem;
*startOfFunc = nullptr;
return startOfFunc + 1;
}
return mem;
}
Func::Func(Unit& unit, Id id, int line1, int line2,
@@ -224,17 +235,63 @@ Func::~Func() {
}
void Func::destroy(Func* func) {
void* mem = func;
if (func->isClosureBody() || func->isGeneratorFromClosure()) {
Func** startOfFunc = (Func**) mem;
mem = startOfFunc - 1; // move back by a pointer
}
func->~Func();
Util::low_free(func);
Util::low_free(mem);
}
Func* Func::clone() const {
Func* f = new (allocFuncMem(m_name, m_numParams)) Func(*this);
Func* f = new (allocFuncMem(
m_name,
m_numParams,
isClosureBody() || isGeneratorFromClosure()
)) Func(*this);
f->initPrologues(m_numParams, isGenerator());
f->m_funcId = InvalidId;
return f;
}
const Func* Func::cloneAndSetClass(Class* cls) const {
if (const Func* ret = findCachedClone(cls)) {
return ret;
}
static Mutex s_clonedFuncListMutex;
Lock l(s_clonedFuncListMutex);
// Check again now that I'm the writer
if (const Func* ret = findCachedClone(cls)) {
return ret;
}
Func* clonedFunc = clone();
clonedFunc->setNewFuncId();
clonedFunc->setCls(cls);
// Save it so we don't have to keep cloning it and retranslating
Func*& nextFunc = this->nextClonedClosure();
while (nextFunc) {
nextFunc = nextFunc->nextClonedClosure();
}
nextFunc = clonedFunc;
return clonedFunc;
}
const Func* Func::findCachedClone(Class* cls) const {
const Func* nextFunc = this;
while (nextFunc) {
if (nextFunc->cls() == cls) {
return nextFunc;
}
nextFunc = nextFunc->nextClonedClosure();
}
return nullptr;
}
void Func::rename(const StringData* name) {
m_name = name;
setFullName();
@@ -679,11 +736,15 @@ void FuncEmitter::allocVarId(const StringData* name) {
}
Id FuncEmitter::lookupVarId(const StringData* name) const {
assert(name != nullptr);
assert(m_localNames.find(name) != m_localNames.end());
assert(this->hasVar(name));
return m_localNames.find(name)->second;
}
bool FuncEmitter::hasVar(const StringData* name) const {
assert(name != nullptr);
return m_localNames.find(name) != m_localNames.end();
}
Id FuncEmitter::allocIterator() {
assert(m_numIterators >= m_nextFreeIterator);
Id id = m_nextFreeIterator++;
@@ -815,10 +876,12 @@ Func* FuncEmitter::create(Unit& unit, PreClass* preClass /* = NULL */) const {
Func* f = (m_pce == nullptr)
? m_ue.newFunc(this, unit, m_id, m_line1, m_line2, m_base,
m_past, m_name, attrs, m_top, m_docComment,
m_params.size(), m_isGenerator)
m_params.size(), m_isClosureBody | m_isGeneratorFromClosure,
m_isGenerator)
: m_ue.newFunc(this, unit, preClass, m_line1, m_line2, m_base,
m_past, m_name, attrs, m_top, m_docComment,
m_params.size(), m_isGenerator);
m_params.size(), m_isClosureBody | m_isGeneratorFromClosure,
m_isGenerator);
f->shared()->m_info = m_info;
f->shared()->m_returnType = m_returnType;
std::vector<Func::ParamInfo> pBuilder;
+18 -3
Ver Arquivo
@@ -137,6 +137,7 @@ struct Func {
static void destroy(Func* func);
Func* clone() const;
const Func* cloneAndSetClass(Class* cls) const;
void validate() const {
#ifdef DEBUG
@@ -326,8 +327,8 @@ struct Func {
}
/**
* If this function is a generator then it is implemented as a simple
* function that just returns another function. hasGeneratorAsBody() will be
* true for the outer functions and isGenerator() is true for the
* function that just returns another function. hasGeneratorAsBody() will be
* true for the outer functions and isGenerator() is true for the
* inner function.
*
* This isn't a pointer to the function itself because it was too hard to
@@ -353,7 +354,19 @@ struct Func {
return shared()->m_userAttributes;
}
static void* allocFuncMem(const StringData* name, int numParams);
/**
* Closure's __invoke()s have an extra pointer used to keep cloned versions
* of themselves with different contexts.
*
* const here is the equivalent of "mutable" since this is just a cache
*/
Func*& nextClonedClosure() const {
assert(isClosureBody() || isGeneratorFromClosure());
return ((Func**)this)[-1];
}
static void* allocFuncMem(
const StringData* name, int numParams, bool needsNextClonedClosure);
void setPrologue(int index, unsigned char* tca) {
m_prologueTable[index] = tca;
@@ -465,6 +478,7 @@ private:
void allocVarId(const StringData* name);
const SharedData* shared() const { return m_shared.get(); }
SharedData* shared() { return m_shared.get(); }
const Func* findCachedClone(Class* cls) const;
private:
static bool s_interceptsEnabled;
@@ -549,6 +563,7 @@ public:
}
void allocVarId(const StringData* name);
Id lookupVarId(const StringData* name) const;
bool hasVar(const StringData* name) const;
Id numParams() const { return m_params.size(); }
Id allocIterator();
+35 -22
Ver Arquivo
@@ -18,6 +18,7 @@
#include "runtime/base/zend/zend_string.h"
#include "runtime/base/array/hphp_array.h"
#include "runtime/base/builtin_functions.h"
#include "runtime/ext/ext_closure.h"
#include "runtime/ext/ext_continuation.h"
#include "runtime/ext/ext_collections.h"
#include "runtime/vm/core_types.h"
@@ -383,28 +384,10 @@ bool run_intercept_handler_for_invokefunc(TypedValue* retval,
HphpArray* get_static_locals(const ActRec* ar) {
if (ar->m_func->isClosureBody()) {
static const StringData* s___static_locals =
StringData::GetStaticString("__static_locals");
assert(ar->hasThis());
ObjectData* closureObj = ar->getThis();
assert(closureObj);
TypedValue* prop;
TypedValue ref;
tvWriteUninit(&ref);
static_cast<Instance*>(closureObj)->prop(
prop,
ref,
closureObj->getVMClass(),
s___static_locals);
if (prop->m_type == KindOfNull) {
prop->m_data.parr = NEW(HphpArray)(1);
prop->m_data.parr->incRefCount();
prop->m_type = KindOfArray;
}
assert(prop->m_type == KindOfArray);
assert(IsHphpArray(prop->m_data.parr));
assert(ref.m_type == KindOfUninit);
return static_cast<HphpArray*>(prop->m_data.parr);
TypedValue* closureLoc = frame_local(ar, 0);
c_Closure* closure = static_cast<c_Closure*>(closureLoc->m_data.pobj);
assert(closure != nullptr);
return closure->getStaticLocals();
} else if (ar->m_func->isGeneratorFromClosure()) {
TypedValue* contLoc = frame_local(ar, 0);
c_Continuation* cont = static_cast<c_Continuation*>(contLoc->m_data.pobj);
@@ -506,5 +489,35 @@ void deepInitHelper(TypedValue* propVec, const TypedValueAux* propData,
}
}
int init_closure(ActRec* ar, TypedValue* sp) {
c_Closure* closure = static_cast<c_Closure*>(ar->getThis());
// Swap in the $this or late bound class
ar->setThis(closure->getThisOrClass());
if (ar->hasThis()) {
ar->getThis()->incRefCount();
}
// Put in the correct context
ar->m_func = closure->getInvokeFunc();
// The closure is the first local.
// Similar to tvWriteObject() but we don't incref because it used to be $this
// and now it is a local, so they cancel out
TypedValue* firstLocal = --sp;
firstLocal->m_type = KindOfObject;
firstLocal->m_data.pobj = closure;
// Copy in all the use vars
TypedValue* prop = closure->getUseVars();
int n = closure->getNumUseVars();
for (int i=0; i < n; i++) {
tvDup(prop++, --sp);
}
return n + 1;
}
} } // HPHP::VM
+3
Ver Arquivo
@@ -285,5 +285,8 @@ void assertTv(const TypedValue* tv);
void deepInitHelper(TypedValue* propVec, const TypedValueAux* propData,
size_t nProps);
// returns the number of things it put on sp
int init_closure(ActRec* ar, TypedValue* sp);
} }
#endif
+11 -1
Ver Arquivo
@@ -65,6 +65,7 @@ typedef __sighandler_t *sighandler_t;
#include "runtime/base/zend/zend_string.h"
#include "runtime/base/runtime_option.h"
#include "runtime/base/server/source_root_info.h"
#include "runtime/ext/ext_closure.h"
#include "runtime/ext/ext_continuation.h"
#include "runtime/ext/ext_function.h"
#include "runtime/vm/debug/debug.h"
@@ -2341,6 +2342,15 @@ TranslatorX64::emitPrologue(Func* func, int nPassed) {
}
SrcKey funcBody(func, destPC);
if (UNLIKELY(func->isClosureBody())) {
// It is a bit of a waste having the use_vars being emitted as
// uninitialized first then overwritten here, but knowing how to change
// numParams at translation time is hard
a. mov_reg64_reg64(rVmFp, argNumToRegName[0]);
emitLea(a, rVmFp, -cellsToBytes(numParams), argNumToRegName[1]);
emitCall(a, TCA(init_closure));
}
// Move rVmSp to the right place: just past all locals
int frameCells = func->numSlotsInFrame();
if (func->isGenerator()) {
@@ -7303,7 +7313,7 @@ void TranslatorX64::translateCreateCont(const Tracelet& t,
// Even callee-saved regs need to be clean, because
// createContinuation will read all locals.
m_regMap.cleanAll();
auto helper = origFunc->isNonClosureMethod() ?
auto helper = origFunc->isMethod() ?
VMExecutionContext::createContinuation<true> :
VMExecutionContext::createContinuation<false>;
EMIT_CALL(a,
+4 -4
Ver Arquivo
@@ -2143,8 +2143,8 @@ Func* UnitEmitter::newFunc(const FuncEmitter* fe, Unit& unit, Id id, int line1,
int line2, Offset base, Offset past,
const StringData* name, Attr attrs, bool top,
const StringData* docComment, int numParams,
bool isGenerator) {
Func* f = new (Func::allocFuncMem(name, numParams))
bool needsNextClonedClosure, bool isGenerator) {
Func* f = new (Func::allocFuncMem(name, numParams, needsNextClonedClosure))
Func(unit, id, line1, line2, base, past, name, attrs,
top, docComment, numParams, isGenerator);
m_fMap[fe] = f;
@@ -2156,8 +2156,8 @@ Func* UnitEmitter::newFunc(const FuncEmitter* fe, Unit& unit,
Offset base, Offset past,
const StringData* name, Attr attrs, bool top,
const StringData* docComment, int numParams,
bool isGenerator) {
Func* f = new (Func::allocFuncMem(name, numParams))
bool needsNextClonedClosure, bool isGenerator) {
Func* f = new (Func::allocFuncMem(name, numParams, needsNextClonedClosure))
Func(unit, preClass, line1, line2, base, past, name,
attrs, top, docComment, numParams, isGenerator);
m_fMap[fe] = f;
+2 -2
Ver Arquivo
@@ -761,12 +761,12 @@ class UnitEmitter {
Offset base, Offset past,
const StringData* name, Attr attrs, bool top,
const StringData* docComment, int numParams,
bool isGenerator);
bool isClosureBody, bool isGenerator);
Func* newFunc(const FuncEmitter* fe, Unit& unit, PreClass* preClass,
int line1, int line2, Offset base, Offset past,
const StringData* name, Attr attrs, bool top,
const StringData* docComment, int numParams,
bool isGenerator);
bool isClosureBody, bool isGenerator);
Unit* create();
void returnSeen() { m_returnSeen = true; }
void pushMergeableClass(PreClassEmitter* e);
-6
Ver Arquivo
@@ -24383,15 +24383,9 @@ const char *g_class_map[] = {
(const char *)-1, NULL,
NULL,
NULL,
(const char *)0x10026040, "__invoke", "", (const char*)0, (const char*)0,
"/**\n * ( excerpt from http://php.net/manual/en/closure.invoke.php )\n *\n *\n * @return mixed\n */",
(const char *)0xffffffff, NULL,
NULL,
NULL,
NULL,
(const char *)0x2080, "__static_locals",
NULL,
NULL,
NULL,
(const char *)0x10006000, "DummyClosure", "", "", (const char *)0, (const char *)0,
"/**\n * ( excerpt from http://php.net/manual/en/class.dummyclosure.php )\n *\n * Represents an invalid closure which will fatal when used.\n *\n */",
+1 -1
Ver Arquivo
@@ -5,7 +5,7 @@
#elif EXT_TYPE == 1
#elif EXT_TYPE == 2
"Closure", "", NULL, "__construct", T(Void), S(0), NULL, S(16384), "/**\n * ( excerpt from http://php.net/manual/en/closure.construct.php )\n *\n *\n */", S(16384),"__invoke", T(Variant), S(0), NULL, S(147456), "/**\n * ( excerpt from http://php.net/manual/en/closure.invoke.php )\n *\n *\n * @return mixed\n */", S(147456),"__clone", T(Variant), S(0), NULL, S(16384), "/**\n * ( excerpt from http://php.net/manual/en/closure.clone.php )\n *\n *\n * @return mixed\n */", S(16384),NULL,S(128), "this", T(Object),S(128), "className", T(String),S(128), "functionName", T(String),S(128), "__static_locals", T(String),NULL,NULL,
"Closure", "", NULL, "__construct", T(Void), S(0), NULL, S(16384), "/**\n * ( excerpt from http://php.net/manual/en/closure.construct.php )\n *\n *\n */", S(16384),NULL,NULL,NULL,
S(16384), "/**\n * ( excerpt from http://php.net/manual/en/class.closure.php )\n *\n * Used as the base class for all closures\n *\n */", "DummyClosure", "", NULL, "__construct", T(Void), S(0), NULL, S(16384), "/**\n * ( excerpt from http://php.net/manual/en/dummyclosure.construct.php )\n *\n *\n */", S(16384),NULL,NULL,NULL,
S(16384), "/**\n * ( excerpt from http://php.net/manual/en/class.dummyclosure.php )\n *\n * Represents an invalid closure which will fatal when used.\n *\n */",
#endif
+13 -10
Ver Arquivo
@@ -24361,7 +24361,7 @@ bool TestCodeRun::TestClosure() {
"}\n"
"f();\n");
MVCR("<?php\n"
MVCRO("<?php\n"
"class Foo {\n"
" function bar() {\n"
" $abc = 123;\n"
@@ -24372,9 +24372,10 @@ bool TestCodeRun::TestClosure() {
" }\n"
"}\n"
"$a = Foo::bar();\n"
"$a(456);\n");
"$a(456);\n",
"int(456)\n");
MVCR("<?php\n"
MVCRO("<?php\n"
"class Foo {\n"
" function bar() {\n"
" $abc = 123;\n"
@@ -24385,9 +24386,10 @@ bool TestCodeRun::TestClosure() {
" }\n"
"}\n"
"$a = Foo::bar();\n"
"$a(456);\n");
"$a(456);\n",
"int(456)\n");
MVCR("<?php\n"
MVCRO("<?php\n"
"class Foo {\n"
" function bar() {\n"
" $abc = 123;\n"
@@ -24398,7 +24400,8 @@ bool TestCodeRun::TestClosure() {
" }\n"
"}\n"
"$a = Foo::bar();\n"
"$a(456);\n");
"$a(456);\n",
"int(456)\n");
MVCR("<?php\n"
"class Foo {\n"
@@ -33518,7 +33521,7 @@ bool TestCodeRun::TestTraits() {
"$a = Foo::bar();\n"
"$a(456);\n"
,
"int(123)\n"
"int(456)\n"
);
MVCRO("<?php\n"
@@ -33535,7 +33538,7 @@ bool TestCodeRun::TestTraits() {
"$a = Foo::bar();\n"
"$a(456);\n"
,
"int(123)\n"
"int(456)\n"
);
MVCRO("<?php\n"
@@ -33552,7 +33555,7 @@ bool TestCodeRun::TestTraits() {
"$a = Foo::bar();\n"
"$a(456);\n"
,
"int(123)\n");
"int(456)\n");
MVCRO("<?php\n"
"trait Too {\n"
@@ -33592,7 +33595,7 @@ bool TestCodeRun::TestTraits() {
"class Foo { use Too; }\n"
"Foo::bar();\n"
,
"string(0) \"\"\n"
"string(3) \"Foo\"\n"
"string(9) \"{closure}\"\n"
);
+1 -3
Ver Arquivo
@@ -8,13 +8,11 @@ Args: 888 500
#0 funk() called at hphp/test/vm/closure.php:17]
#1 {closure}() called at hphp/test/vm/closure.php:28]
#2 main() called at hphp/test/vm/closure.php:33]
object(Closure$)#1 (3) {
object(Closure$)#1 (2) {
["use_by_val":"Closure$":private]=>
int(123)
["use_by_ref":"Closure$":private]=>
&int(4000)
["__static_locals":protected]=>
NULL
}
int(123)
int(4000)
+30
Ver Arquivo
@@ -0,0 +1,30 @@
<?php
trait A {
public function b() {
return function() {
return array(
__CLASS__,
get_class($this)
);
};
}
}
class C {
use A;
public function d() {
return function() {
return array(
__CLASS__,
get_class($this)
);
};
}
}
$c = new C;
$b = $c->b();
var_dump($b());
$d = $c->d();
var_dump($d());
+12
Ver Arquivo
@@ -0,0 +1,12 @@
array(2) {
[0]=>
string(1) "C"
[1]=>
string(1) "C"
}
array(2) {
[0]=>
string(1) "C"
[1]=>
string(1) "C"
}
+29
Ver Arquivo
@@ -0,0 +1,29 @@
<?php
trait Too {
function bar() {
$a = function () {
var_dump(__CLASS__);
};
$a();
$a = function () {
var_dump(get_class());
};
$a();
if (isset($this)) {
$a = function () {
var_dump(get_class($this));
};
$a();
}
}
}
class Foo { use Too; }
$f = new Foo;
echo "Between\n";
$f->bar();
echo "Between\n";
$f::bar();
echo "Between\n";
Foo::bar();
+10
Ver Arquivo
@@ -0,0 +1,10 @@
Between
string(3) "Foo"
string(3) "Foo"
string(3) "Foo"
Between
string(3) "Foo"
string(3) "Foo"
Between
string(3) "Foo"
string(3) "Foo"
+18
Ver Arquivo
@@ -0,0 +1,18 @@
<?php
class A {
public function b() {
$cl = function() {
return $this->c();
};
yield $cl();
}
private function c() {
return 'A';
}
}
$a = new A;
foreach ($a->b() as $c) {
print "$c\n";
}
+1
Ver Arquivo
@@ -0,0 +1 @@
A
+20
Ver Arquivo
@@ -0,0 +1,20 @@
<?php
class A {
public function b() {
$cl = function() {
yield $this->c();
};
yield $cl();
}
private function c() {
return 'A';
}
}
$a = new A;
foreach ($a->b() as $c) {
foreach ($c as $d) {
print "$d\n";
}
}
+1
Ver Arquivo
@@ -0,0 +1 @@
A
+73
Ver Arquivo
@@ -0,0 +1,73 @@
<?php
class A {
public function testPublic() {
$a = function() {
return $this->justReturn("foo");
};
return $a();
}
public function testUse() {
$a = "foo";
$b = function() use ($a) {
return $this->justReturn($a);
};
return $b();
}
public function testParam() {
$a = "foo";
$b = function($foo) {
return $this->justReturn($foo);
};
return $b($a);
}
public function testParamAndClosure() {
$a = "foo";
$b = "bar";
$c = function($foo) use ($b) {
return $this->justReturn($foo, $b);
};
return $c($a);
}
public function testByRef() {
$a = "foo";
$b = "bar";
$c = function(&$foo) use (&$b) {
$this->double($foo, $b);
};
$c($a);
return $a.$b;
}
public function testNotByRef() {
$a = "foo";
$b = "bar";
$c = function($foo) use ($b) {
$this->double($foo, $b);
};
$c($a);
return $a.$b;
}
private function justReturn() {
return func_get_args();
}
private function double(&$a, &$b) {
$a = $a.$a;
$b = $b.$b;
}
}
$a = new A;
var_dump($a->testPublic());
var_dump($a->testUse());
var_dump($a->testParam());
var_dump($a->testParamAndClosure());
var_dump($a->testByRef());
var_dump($a->testNotByRef());
+20
Ver Arquivo
@@ -0,0 +1,20 @@
array(1) {
[0]=>
string(3) "foo"
}
array(1) {
[0]=>
string(3) "foo"
}
array(1) {
[0]=>
string(3) "foo"
}
array(2) {
[0]=>
string(3) "foo"
[1]=>
string(3) "bar"
}
string(12) "foofoobarbar"
string(6) "foobar"
+18
Ver Arquivo
@@ -0,0 +1,18 @@
<?php
class A {
public function b() {
return function() {
return function() {
return $this->c();
};
};
}
private function c() {
return 91;
}
}
$a = new A;
$b = $a->b();
$first = $b();
$second = $first();
var_dump($second);
+1
Ver Arquivo
@@ -0,0 +1 @@
int(91)
+17
Ver Arquivo
@@ -0,0 +1,17 @@
<?php
class A {
public function b() {
function c() {
return function() {
return $this->d();
};
};
return c();
}
private function d() {
return 91;
}
}
$a = new A;
$b = $a->b();
var_dump($b());
+2
Ver Arquivo
@@ -0,0 +1,2 @@
HipHop Notice: Undefined variable: this in hphp/test/vm/closure_recursive_bad.php on line 6
HipHop Fatal error: Uncaught exception 'BadMethodCallException' with message 'Call to a member function d() on a non-object' in hphp/test/vm/closure_recursive_bad.php:6\nStack trace:\n#0 hphp/test/vm/closure_recursive_bad.php(17): {closure}()\n#1 {main}
+1
Ver Arquivo
@@ -0,0 +1 @@
filepath.filter
+49
Ver Arquivo
@@ -0,0 +1,49 @@
<?php
class A {
private $x;
function __construct($x) {
$this->x = $x;
}
function __destruct() {
echo "Destroyed\n";
}
function getIncer($val) {
return function() use ($val) {
$this->x += $val;
};
}
function getPrinter() {
return function() {
echo $this->x."\n";
};
}
function printX() {
echo $this->x."\n";
}
}
$a = new A(3);
$incer = $a->getIncer(2);
$printer = $a->getPrinter();
$a->printX();
$printer();
$incer();
$a->printX();
$printer();
unset($a);
$incer();
$printer();
unset($incer);
$printer();
unset($printer);
+7
Ver Arquivo
@@ -0,0 +1,7 @@
3
3
5
5
7
7
Destroyed
+26
Ver Arquivo
@@ -0,0 +1,26 @@
<?php
class A {
private $x = 0;
function getClosureGetter () {
return function () {
return function () {
$this->x++;
};
};
}
function printX () {
echo $this->x."\n";
}
}
$a = new A;
$a->printX();
$getClosure = $a->getClosureGetter();
$a->printX();
$closure = $getClosure();
$a->printX();
$closure();
$a->printX();
+4
Ver Arquivo
@@ -0,0 +1,4 @@
0
0
0
1
+1 -3
Ver Arquivo
@@ -2,11 +2,9 @@ object(foo)#1 (2) {
["test":"foo":private]=>
int(3)
["a"]=>
object(Closure$)#2 (2) {
object(Closure$)#2 (1) {
["a":"Closure$":private]=>
*RECURSION*
["__static_locals":protected]=>
NULL
}
}
bool(true)