: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:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -341,6 +341,8 @@ protected:
|
||||
int m_updated;
|
||||
int m_runId;
|
||||
private:
|
||||
FunctionScopeRawPtr getContainingNonClosureFunction();
|
||||
|
||||
Marks m_mark;
|
||||
BlockScopeRawPtrFlagsPtrVec m_orderedDeps;
|
||||
BlockScopeRawPtrFlagsVec m_orderedUsers;
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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) :
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 */",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
@@ -0,0 +1,12 @@
|
||||
array(2) {
|
||||
[0]=>
|
||||
string(1) "C"
|
||||
[1]=>
|
||||
string(1) "C"
|
||||
}
|
||||
array(2) {
|
||||
[0]=>
|
||||
string(1) "C"
|
||||
[1]=>
|
||||
string(1) "C"
|
||||
}
|
||||
@@ -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();
|
||||
@@ -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"
|
||||
@@ -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";
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
A
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
A
|
||||
@@ -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());
|
||||
@@ -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"
|
||||
@@ -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);
|
||||
@@ -0,0 +1 @@
|
||||
int(91)
|
||||
@@ -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());
|
||||
@@ -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}
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
filepath.filter
|
||||
@@ -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);
|
||||
@@ -0,0 +1,7 @@
|
||||
3
|
||||
3
|
||||
5
|
||||
5
|
||||
7
|
||||
7
|
||||
Destroyed
|
||||
@@ -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();
|
||||
@@ -0,0 +1,4 @@
|
||||
0
|
||||
0
|
||||
0
|
||||
1
|
||||
@@ -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)
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário