Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fixed bug #70630 (Closure::call/bind() crash with ReflectionFunction-…
…>getClosure()) This additionally removes support for binding to an unknown (not in parent hierarchy) scope. Removing support for cross-scope is necessary for certain compile-time assumptions (like class constants) to prevent unexpected results
- Loading branch information
Showing
6 changed files
with
124 additions
and
16 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
--TEST-- | ||
Bug #70630 (Closure::call/bind() crash with ReflectionFunction->getClosure()) | ||
--FILE-- | ||
<?php | ||
|
||
class a {} | ||
function foo() {} | ||
|
||
foreach (["substr", "foo"] as $fn) { | ||
$x = (new ReflectionFunction($fn))->getClosure(); | ||
$x->call(new a); | ||
Closure::bind($x, new a, "a"); | ||
} | ||
|
||
?> | ||
--EXPECTF-- | ||
|
||
Warning: Cannot bind function substr to an object in %s on line %d | ||
|
||
Warning: Cannot bind function substr to an object in %s on line %d | ||
|
||
Warning: Cannot bind function foo to an object in %s on line %d | ||
|
||
Warning: Cannot bind function foo to an object in %s on line %d |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
--TEST-- | ||
Closure::call() or Closure::bind() to independent class must fail | ||
--FILE-- | ||
<?php | ||
|
||
class foo { | ||
public $var; | ||
|
||
function initClass() { | ||
$this->var = __CLASS__; | ||
} | ||
} | ||
|
||
class bar { | ||
public $var; | ||
|
||
function initClass() { | ||
$this->var = __CLASS__; | ||
} | ||
|
||
function getVar() { | ||
assert($this->var !== null); // ensure initClass was called | ||
return $this->var; | ||
} | ||
} | ||
|
||
class baz extends bar { | ||
public $var; | ||
|
||
function initClass() { | ||
$this->var = __CLASS__; | ||
} | ||
} | ||
|
||
function callMethodOn($class, $method, $object) { | ||
$closure = (new ReflectionMethod($class, $method))->getClosure((new ReflectionClass($class))->newInstanceWithoutConstructor()); | ||
$closure = $closure->bindTo($object, $class); | ||
return $closure(); | ||
} | ||
|
||
$baz = new baz; | ||
|
||
callMethodOn("baz", "initClass", $baz); | ||
var_dump($baz->getVar()); | ||
|
||
callMethodOn("bar", "initClass", $baz); | ||
var_dump($baz->getVar()); | ||
|
||
callMethodOn("foo", "initClass", $baz); | ||
var_dump($baz->getVar()); | ||
|
||
?> | ||
--EXPECTF-- | ||
string(3) "baz" | ||
string(3) "bar" | ||
|
||
Warning: Cannot bind function foo::initClass to object of class baz in %s on line %d | ||
|
||
Fatal error: Uncaught Error: Using $this when not in object context in %s:%d | ||
Stack trace: | ||
#0 %s(%d): initClass() | ||
#1 %s(%d): callMethodOn('foo', 'initClass', Object(baz)) | ||
#2 {main} | ||
thrown in %s on line %d |
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
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
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
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: this is exactly why nobody uses
ReflectionFunction#getClosure()
, since it introduces silly rules and does dangerous assumptions.Following code is much better than
ReflectionMethod#getClosure()
(for instance methods), for example:517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing something means bc break. it definitely needs a discuss. I am going to revert this, only fix the segfault.. please drop a mail to internal about the BC break.
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@laruence please don't revert that… It actually is only about rebinding Closures of actual methods and functions (like these you get via Reflection, not actual Closures), where it is already buggy due to some compile-time assumptions.
It strictly seen is a minor BC break, of something already having buggy behavior.
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bwoebi hmm, I just feel the REAL_CLOSURE your introduced smells bad.. maybe you should fix that in reflection side? ACC_CLOSURE and ACC_REAL_CLOUSRE... :<
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@laruence There is no reference to the parent op_array or similar, hence it's not trivial to see what the origins are. The ZEND_ACC_CLOSURE is necessary in every case for proper refcounting of the Closure (and thus of the op_array). Hence the only solution I found is adding a new constant. Feel free ameliorate this patch; it's just the best and simplest I've found.
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@bwoebi okey, thanks for explaining , I need some time to understand it.
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if it's only breaking userland code that is stupid, slipping in an API change in a minor point release is pretty bogus.
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Danack This is not breaking userland code that is stupid, this is explicitly forbidding something that does not work in the first place.
Though I suspect this patch is not conservative enough and we have to forbid all rebinding for "not real" closures.
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nikic The code below works in the RC candidates for 7.0. The code does not work after Bob's commit.
I realise the code is really stupid, and would only be used by insane people, but it still seems to be a BC break that was slipped in along with a valid bug fix.
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I take offense to that :P
But yes, this patch is broken and should be reverted anyway, with a reminder that
ReflectionFunction->getClosure()
is a terrible API anyway.517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, that's what the commit attempts to do… though, I'm wondering whether we really need to remove that completely… maybe we could only just change called scope and leave scope intact (for function and method Closures).
That way the above (@Danack example) would still work, but assumptions about
self
-binding are not violated.517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Danack The fact that it works is only incidental of that particular code. If you use something like
self::class
orself::CONST
or a bunch of other things I don't remember right now, you're going to get incorrect behavior. The engine assumes that things likeself
actually do meanself
inside a class method.517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi,
Like spotted by Danack, this patch produces a BC Break... \ReflectionMethod::getClosure() is a usefull method in some cases. (like allow to implement easily some patterns for final devs). My lib (github.com/UniAlteri/states) does not work with 7.1-dev with this patch :/
Thanks.
Richard
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@frenchcomp yeah, definitely, but this is just about rebinding these (Closure::call() and Closure::bindTo() being restricted in what they accept as scopes/$this)
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But currently there are not indications in the documentation about Closure::bindTo() et Closure::call() to restrict $newthis to the same initial scope. Your path introduces a BC break and not provides an alternative !
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whisper just revert it whisper
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, please do it ;)
If there are an issue when a rebinded closure requires a non defined constant in the new scope : it is the developer's responsibility, not a php, PHP should only throw an error like other inexistant contant :) This patch should only fix the segfault.
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ocramius Sure, we could revert this on the grounds of unexpectedly having an actual BC impact, but that is no more than a temporary measure to allow further discussion. The issue that we don't properly support rebinds for real methods still exists. We can either forbid it (or at least the unsafe parts -- we can allow rebinds with objects of the same class) or we need to correctly support it. And, at least in my opinion, allowing doing something odd like this is not worth removing valuable preconditions (both for the compiler and the developer) like "
self
refers to the class the method is defined on" and "$this
is an instance of the class the method is defined on".517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that's what happens when a bad patch lands (in any project, AFAIK).
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note: not having a patch for a bug is better than having a broken patch for a bug (except for security issues, ofc)
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The patch isn't broken, it's just too conservative. I'll push a second commit with more tests and fixed behavior a bit later today.
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sigh
This means "bc compliant", ya know? ;-)
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to add that while right now the only misbehaviors I'm aware of are just that, this will likely not stay that way in the future. E.g. if we start optimizing calls of the form
self::method()
in the future by specializing them to the method signature (what we do for calls to fully qualified known functions), it likely wouldn't simply misbehave, but lead to segmentation faults.(And I do highly doubt we would want to stay away from doing things like that to support this "feature".)
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer changes with BC breaks to be discussed first as they tend to be a sensitive topic, even more when we are so close to the final 7.0 release.
517b553
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For information, the new commit is here : 881c502 but there are always a BC break ...