-
Notifications
You must be signed in to change notification settings - Fork 200
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Segmentation fault - possibly another GC issue? #504
Comments
The bit in the above example that appears to consistently break things is the assignment This gives me my line number: if ((print("ln:" . __LINE__ . "\n")) && ($foo = $result->main) && is_object($foo) && ($foo instanceof V8Function)) { Whereas this does not: if (($foo = $result->main) && (print("ln:" . __LINE__ . "\n")) && is_object($foo) && ($foo instanceof V8Function)) { Also - for the purpose of checking whether my anonymous class was at fault somehow, I tried removing it (along with the <?php
$v = new \V8Js();
$r = $v->executeString('
a = {
main: function() {
print("foo\n");
}
};
', null, V8Js::FLAG_PROPAGATE_PHP_EXCEPTIONS);
print_r(dothings($r));
echo 'all done!' . PHP_EOL;
function dothings($result) {
if (($foo = $result->main) && (print("ln:" . __LINE__ . "\n")) && is_object($foo) && ($foo instanceof V8Function)) { // error
return $result->main();
}
} This gave me the same outcome, including the same @stesie Does this reveal enough clues to give any pointers as to where I could start debugging the C++ code? |
Yes, definitely has something with de assignment that is treated differently when inside if statement. public function __get($item) {
//return $this->result[$item] ? (fn(...$args) => $this->result[$item](...$args)) : null; // error
return isset($this->result[$item]) ? (fn(...$args) => $this->result[$item](...$args)) : null; // no error
} It's weird. |
And in fact, this code can reproduce the same behavior: <?php
$v = new \V8Js();
$r = $v->executeString('
a = {
main: function() {
print("foo\n");
}
};
', null, V8Js::FLAG_FORCE_ARRAY | V8Js::FLAG_PROPAGATE_PHP_EXCEPTIONS);
if (is_object($r['main']) && ($r['main'] instanceof V8Function)) { // no error
//if (($foo = $r['main']) && print("0") && is_object($foo) && print("1") && ($foo instanceof V8Function) && print("2")) { // error
print_r($r['main']());
}
echo 'all done!' . PHP_EOL; |
Thanks for replicating, what specific version of PHP / V8 you on? Would be good if we can at least somewhat rule a couple of things out. My C++ debugging has gotten a little rusty, but I’m either suspecting something GC or refcount related or something about changes around accessing non-existent properties in PHP8 (though I would have imagined the __get would take care of that) |
perhaps some extra clues. Tried the script with GC disabled to rule that out. I don't know too much about the PHP internals but my guess is there's something an object has to do to report back when an 'empty' check is done on it. I suppose like the __isset() magic method or the valid() iterator method (albeit low-level). The GDB backtrace seems to agree, if i'm reading it right:
// this is ok
isset($result->main);
// this will segfault
!empty($result->main);
// I guess this probably uses the same `empty` logic under the hood, hence why it also crashes:
if ($foo = $result->main) {} So i guess now all i need is a steer towards where in the V8Js code I might need to look? |
@stesie got it! I think... // v8js_v8object_class.cc
v8js_v8object_handlers.cast_object = NULL I looked at the PHP code for ref: https://github.com/php/php-src/blob/master/Zend/zend_operators.c#L2653 if (zobj->handlers->cast_object(zobj, &tmp, _IS_BOOL) == SUCCESS) { but V8Js sets this handler to NULL. I tried implementing a basic handler (that returns SUCCESS) and it worked without segfault, exiting normally. It also worked if i just commented out the explicit assignment of NULL to that handler - so perhaps there's a sensible default of sorts. Is it just a case of implementing or removing this if (Z_OBJ_HT_P(op)->cast_object) { ref: https://github.com/php/php-src/blob/PHP-7.4.3/Zend/zend_operators.c#L2599 so i guess it had some protection for when this handler was not set. anyway, a bit more digging, turns out making this a required handler was intentional: |
I've teste your solution here and confirm that works. var_dump(empty($foo = $r->main)); And got exactly same behavior with the if statement. But now with your patch, everything works! Thanks @redbullmarky !! |
Hey!
I think we've had something very similar, but this code will trigger a segfault - Ubuntu 22 & their PHP8.1, plus V8 10.9 against the latest V8Js version. To summarise, it creates an object with a function in it, wraps it inside of an anonymous class so that it can be treated more like a regular object, then passes it to a function that will validate what it needs and execute the 'main' function that JS returned.
Included near the bottom are two lines - one commented out - one that works and one that doesn't:
The problem exists in the
dothings
function - I've included two lines, one that works and one that doesn't. My suspicion is that (with the one that breaks) that$foo
is being discarded after that line (before the return) but also discarding the underlaying V8Function (if you change the return line toreturn $foo();
it also segfaults, perhaps because $result is then discarded because of not having any usage?A few pointers:
Any help would be much appreciated!
The text was updated successfully, but these errors were encountered: