-
-
Save scannell/24b3b018f7c1954991b0 to your computer and use it in GitHub Desktop.
bindTo first cut
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/hphp/runtime/base/types.h b/hphp/runtime/base/types.h | |
--- a/hphp/runtime/base/types.h | |
+++ b/hphp/runtime/base/types.h | |
@@ -574,6 +574,7 @@ | |
AttrSkipFrame = (1 << 22), // X // | |
AttrNative = (1 << 23), // X // | |
AttrVMEntry = (1 << 24), // X // | |
+ AttrClonedClosure = (1 << 25), // X // | |
}; | |
inline Attr operator|(Attr a, Attr b) { return Attr((int)a | (int)b); } | |
diff --git a/hphp/runtime/ext/ext_closure.h b/hphp/runtime/ext/ext_closure.h | |
--- a/hphp/runtime/ext/ext_closure.h | |
+++ b/hphp/runtime/ext/ext_closure.h | |
@@ -58,6 +58,9 @@ | |
public: // ObjectData overrides | |
void t___construct(); // must not be called for Closures | |
+ Variant t_bindto(CVarRef newthis, CVarRef newscope = "static"); | |
+ static Variant ti_bind(CObjRef closureObj, CVarRef newthis, | |
+ CVarRef newscope = "static"); | |
public: | |
diff --git a/hphp/runtime/ext/ext_closure.cpp b/hphp/runtime/ext/ext_closure.cpp | |
--- a/hphp/runtime/ext/ext_closure.cpp | |
+++ b/hphp/runtime/ext/ext_closure.cpp | |
@@ -78,5 +78,111 @@ | |
return closure; | |
} | |
+Variant c_Closure::t_bindto(CVarRef newthis, | |
+ CVarRef newscope /* = "static" */) { | |
+ return ti_bind(this, newthis, newscope); | |
+} | |
+ | |
+Variant c_Closure::ti_bind(CObjRef closureObj, CVarRef newthis, | |
+ CVarRef newscope /* = "static" */) { | |
+ | |
+ if (RuntimeOption::RepoAuthoritative) { | |
+ throw NotSupportedException(__func__, | |
+ "bindTo won't work with RepoAuthoritative mode " | |
+ "(it breaks so many assumed invariants)" | |
+ ); | |
+ } | |
+ | |
+ if (closureObj.isNull()) { | |
+ raise_error("closureObj is null"); | |
+ } | |
+ | |
+ c_Closure* closure = static_cast<c_Closure*>(closureObj.get()); | |
+ c_Closure* ret = static_cast<c_Closure*>(closure->clone()); | |
+ | |
+ if (newthis.isNull()) { | |
+ // A bit of a hack. The JIT assumes that for every BareThis opcode there is | |
+ // either a valid $this or a late bound class. | |
+ // Lets make it have a class instead of just nullptr. | |
+ ret->setClass(nullptr); | |
+ } else { | |
+ if (!newthis.isObject()) { | |
+ raise_warning("Closure::bind() expects parameter 1 to be object"); | |
+ return null_variant; | |
+ } else if (closure->m_func->attrs() & AttrStatic) { | |
+ raise_warning("Cannot bind an instance to a static closure"); | |
+ } else { | |
+ auto od = newthis.asCObjRef().get(); | |
+ ret->setThis(od); | |
+ od->incRefCount(); | |
+ } | |
+ } | |
+ | |
+ Class* newClass = nullptr; | |
+ // Differentiate nullptr from "don't change the class" | |
+ bool leaveClass = false; | |
+ | |
+ if (newscope.isString()) { | |
+ StringData* className = newscope.asCStrRef().get(); | |
+ if (className->equal(s_static.get())) { | |
+ leaveClass = true; | |
+ } else { | |
+ newClass = Unit::lookupClass(className); | |
+ if (!newClass) { | |
+ raise_warning("Undefined class: %s", className->data()); | |
+ return null_variant; | |
+ } | |
+ } | |
+ } else if (newscope.isObject()) { | |
+ newClass = newscope.asCObjRef()->getVMClass(); | |
+ } else if (newscope.isNull()) { | |
+ newClass = nullptr; | |
+ } else { | |
+ raise_warning("newscope param is neither a string nor an object"); | |
+ return null_variant; | |
+ } | |
+ | |
+ if (auto od = ret->getThis()) { | |
+ if (newClass && !od->getVMClass()->classof(newClass)) { | |
+ raise_error( | |
+ "parameter 1 is not an instanceof class %s, its class is %s", | |
+ newClass->name()->data(), | |
+ od->getVMClass()->name()->data() | |
+ ); | |
+ } | |
+ } | |
+ | |
+ Func* clonedFunc = nullptr; | |
+ | |
+ if (!leaveClass) { | |
+ clonedFunc = ret->m_func->cloneAndSetClass(newClass); | |
+ if (!ret->hasThis()) { | |
+ ret->setClass(newClass); | |
+ } | |
+ } | |
+ | |
+ // php-5.4 only does this if we didn't already clone for some reason... | |
+ if (!clonedFunc) { | |
+ // Make the staticness of the class match hasThis() | |
+ bool isStatic = ret->m_func->attrs() & AttrStatic; | |
+ if (ret->hasThis() && isStatic) { | |
+ clonedFunc = ret->m_func->clone(ret->m_func->cls()); | |
+ clonedFunc->setNewFuncId(); | |
+ clonedFunc->setAttrs(Attr(ret->m_func->attrs() & ~AttrStatic)); | |
+ } else if (!ret->hasThis() && !isStatic) { | |
+ clonedFunc = ret->m_func->clone(ret->m_func->cls()); | |
+ clonedFunc->setNewFuncId(); | |
+ clonedFunc->setAttrs(ret->m_func->attrs() | AttrStatic); | |
+ } | |
+ } | |
+ | |
+ if (clonedFunc) { | |
+ clonedFunc->setAttrs(clonedFunc->attrs() | AttrClonedClosure); | |
+ ret->m_func = clonedFunc; | |
+ } | |
+ | |
+ return ret; | |
+} | |
+ | |
/////////////////////////////////////////////////////////////////////////////// | |
} | |
diff --git a/hphp/runtime/vm/func.h b/hphp/runtime/vm/func.h | |
--- a/hphp/runtime/vm/func.h | |
+++ b/hphp/runtime/vm/func.h | |
@@ -404,6 +404,10 @@ | |
return ((Func**)this)[-1 - (int)isGenerator()]; | |
} | |
+ bool isReboundClosureBody() const { | |
+ return isClosureBody() && attrs() & AttrClonedClosure; | |
+ } | |
+ | |
static void* allocFuncMem( | |
const StringData* name, int numParams, | |
bool needsGeneratorOrigFunc, | |
diff --git a/hphp/runtime/vm/jit/hhbc-translator.cpp b/hphp/runtime/vm/jit/hhbc-translator.cpp | |
--- a/hphp/runtime/vm/jit/hhbc-translator.cpp | |
+++ b/hphp/runtime/vm/jit/hhbc-translator.cpp | |
@@ -478,15 +478,15 @@ | |
} | |
void HhbcTranslator::emitThis() { | |
- if (!curClass()) { | |
+ if (!curClass() && !curFunc()->isReboundClosureBody()) { | |
emitInterpOne(Type::Obj, 0); // will throw a fatal | |
return; | |
} | |
pushIncRef(gen(LdThis, makeExitSlow(), m_tb->fp())); | |
} | |
void HhbcTranslator::emitCheckThis() { | |
- if (!curClass()) { | |
+ if (!curClass() && !curFunc()->isReboundClosureBody()) { | |
emitInterpOne(Type::None, 0); // will throw a fatal | |
return; | |
} | |
@@ -512,7 +512,7 @@ | |
// $this is null, we can be sure in the rest of the trace that we | |
// have the this object on top of the stack, and we can eliminate | |
// further null checks of this. | |
- if (!curClass()) { | |
+ if (!curClass() && !curFunc()->isReboundClosureBody()) { | |
emitInterpOne(Type::InitNull, 0); // will raise notice and push null | |
return; | |
} | |
diff --git a/hphp/system/idl/closure.idl.json b/hphp/system/idl/closure.idl.json | |
--- a/hphp/system/idl/closure.idl.json | |
+++ b/hphp/system/idl/closure.idl.json | |
@@ -21,6 +21,49 @@ | |
], | |
"args": [ | |
] | |
+ }, | |
+ { | |
+ "name": "bindTo", | |
+ "return": { | |
+ "type": "Variant" | |
+ }, | |
+ "flags": [ | |
+ ], | |
+ "args": [ | |
+ { | |
+ "name": "newthis", | |
+ "type": "Variant" | |
+ }, | |
+ { | |
+ "name": "newscope", | |
+ "type": "Variant", | |
+ "value": "\"static\"" | |
+ } | |
+ ] | |
+ }, | |
+ { | |
+ "name": "bind", | |
+ "return": { | |
+ "type": "Variant" | |
+ }, | |
+ "flags": [ | |
+ "IsStatic" | |
+ ], | |
+ "args": [ | |
+ { | |
+ "name": "closure", | |
+ "type": "Object" | |
+ }, | |
+ { | |
+ "name": "newthis", | |
+ "type": "Variant" | |
+ }, | |
+ { | |
+ "name": "newscope", | |
+ "type": "Variant", | |
+ "value": "\"static\"" | |
+ } | |
+ ] | |
} | |
], | |
"consts": [ | |
diff --git a/hphp/test/zend/bad/ext/session/tests/session_set_save_handler_closures.php.skipif b/hphp/test/zend/bad/ext/session/tests/session_set_save_handler_closures.php.skipif | |
new file mode 100644 | |
--- /dev/null | |
+++ b/hphp/test/zend/bad/ext/session/tests/session_set_save_handler_closures.php.skipif | |
@@ -0,0 +1 @@ | |
+<?php include('skipif.inc'); ?> | |
\ No newline at end of file | |
diff --git a/hphp/test/zend/good/ext/standard/tests/general_functions/ob_start_closures.php b/hphp/test/zend/good/ext/standard/tests/general_functions/ob_start_closures.php | |
--- a/hphp/test/zend/good/ext/standard/tests/general_functions/ob_start_closures.php | |
+++ b/hphp/test/zend/good/ext/standard/tests/general_functions/ob_start_closures.php | |
@@ -1,6 +1,4 @@ | |
<?php | |
-ini_set('output_buffering', 0); | |
- | |
echo "*** Testing ob_start() : closures as output handlers ***\n"; | |
ob_start(function ($output) { |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment