Skip to content

Instantly share code, notes, and snippets.

@scannell
Created January 13, 2014 15:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scannell/24b3b018f7c1954991b0 to your computer and use it in GitHub Desktop.
Save scannell/24b3b018f7c1954991b0 to your computer and use it in GitHub Desktop.
bindTo first cut
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