-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Catchable "Call to a member function bar() on a non-object" #647
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
Catchable "Call to a member function bar() on a non-object" #647
Conversation
This is a very nicely made pull request. However, I would say this would require an RFC or at least discussion on internals list to see if we have support for it. |
@smalyshev - thanks for the feedback! I will come up with an RFC ASAP. |
First draft for an RFC @ https://wiki.php.net/rfc/catchable-call-to-member-of-non-object |
Set the RFC to 1️⃣.0️⃣ and started discussion on internals mailing list. |
Set to voting status @ https://wiki.php.net/rfc/catchable-call-to-member-of-non-object#vote |
EX_T(opline->result.var).var.fcall_returned_reference = 0; | ||
EX_T(opline->result.var).var.ptr_ptr = &EX_T(opline->result.var).var.ptr; | ||
ZEND_VM_NEXT_OPCODE(); | ||
return; |
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.
This return;
will break the goto and switch VMs. You can just drop it, as ZEND_VM_NEXT_OPCODE()
will already transfer control from here.
Another thing to consider is how this interacts with From the tests side, It would be nice to test calls with non-cv operands, e.g. |
@nikic - thanks for the detailled feedback! I'll look into this ASAP and give you feedback one by one. |
See feedback by @nikic, php#647 (comment)
See discussion php#647 (comment)
Verified with running tests with new "-e" run-tests arg: $ make test TESTS=Zend/tests/*-on-non-objects-*phpt TEST_PHP_ARGS=-e # Tests passed : 11 (100.0%) $ make test TESTS=Zend/tests/*-on-non-objects-*phpt # Tests passed : 11 (100.0%) Before, this would cause a SEGV. Thanks @nikic for raising this concern
…thekid/php-src into catchable-fatals/methods-on-non-objects
* nesting level. Return NULL (except when return value unused) */ | ||
do { | ||
ZEND_VM_INC_OPCODE(); | ||
opline++; |
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.
This won't work correctly with switch/goto VMs, because both lines will increment the opline
variable. I would suggest to instead only increment opline
here (and also on line 9347) and replace the CHECK_EXCEPTION()
below with SAVE_OPLINE()
. (Actually check_exception and save_opline are the same, but save_opline is probably more clear about what you want here)
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.
Using the following didn't work for me:
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index 42d3f60..628db06 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -2477,7 +2477,6 @@ ZEND_VM_HANDLER(112, ZEND_INIT_METHOD_CALL, TMP|VAR|UNUSED|CV, CONST|TMP
/* No exception raised: Skip over arguments until fcall opcode with correct
* nesting level. Return NULL (except when return value unused) */
do {
- ZEND_VM_INC_OPCODE();
opline++;
} while (opline->opcode == ZEND_DO_FCALL_BY_NAME ? opline->op2.num > nesting :
@@ -2488,10 +2487,10 @@ ZEND_VM_HANDLER(112, ZEND_INIT_METHOD_CALL, TMP|VAR|UNUSED|CV, CONST|T
}
if ((opline + 1)->opcode == ZEND_EXT_FCALL_END) {
- ZEND_VM_INC_OPCODE();
+ opline++;
}
- CHECK_EXCEPTION();
+ SAVE_OPLINE();
ZEND_VM_NEXT_OPCODE();
}
The SAVE_OPLINE()
macro only sets EX(opline)
whereas ZEND_VM_INC_OPCODE();
affects OPLINE
which is authorative for the executor AFAIS.
Seems ZEND_VM_SET_OPCODE()
might be what I'm looking for.
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.
Yeah, sorry, I forgot that SAVE_OPLINE only sets EX(opline) for GOTO/SWITCH, but is a noop for CALL. ZEND_VM_SET_OPCODE sounds more like it.
@thekid Thanks for updating everything so quickly! I've added two last notes, though one is purely cosmetic, so feel free to ignore it ;) |
with `zend_do_convert_call_user_func()` in Zend/zend_compile.c
I've brought this pull request up-to-date so it's once again mergeable with $ make test TESTS=Zend/tests/*methods-*phpt
# [...]
=====================================================================
Number of tests : 16 16
Tests skipped : 0 ( 0.0%) --------
Tests warned : 0 ( 0.0%) ( 0.0%)
Tests failed : 0 ( 0.0%) ( 0.0%)
Expected fail : 0 ( 0.0%) ( 0.0%)
Tests passed : 16 (100.0%) (100.0%)
---------------------------------------------------------------------
Time taken : 1 seconds
===================================================================== |
From https://www.mail-archive.com/[email protected]/msg68933.html
If it's not sorted by someone else, I land this when I get back home later @sgolemon any news? |
Could you squash the branch into a single commit and edit the NEWS and UPGRADING files? |
I'm off to a conference for the next couple of days and will look into getting this done ASAP after I get back; the earliest next Sunday |
No matter how hard I try |
Changed ZEND_INIT_METHOD_CALL to raise an E_RECOVERABLE_ERROR and handle error handler outcome, returning NULL if error handler doesn't terminate the script. Adjusted outcome in MySQL / PDO_MySQL and ext/date Added various examples and tests Added tests for indirect calls - see http://news.php.net/php.internals/73823 Added tests verifying method calls on non-objects work inside eval() Originally developed inside php#647
Changed ZEND_INIT_METHOD_CALL to raise an E_RECOVERABLE_ERROR and handle error handler outcome, returning NULL if error handler doesn't terminate the script. Adjusted outcome in MySQL / PDO_MySQL and ext/date Added various examples and tests Added tests for indirect calls - see http://news.php.net/php.internals/73823 Added tests verifying method calls on non-objects work inside eval() Originally developed inside php#647
Changed ZEND_INIT_METHOD_CALL to raise an E_RECOVERABLE_ERROR and handle error handler outcome, returning NULL if error handler doesn't terminate the script. Adjusted outcome in MySQL / PDO_MySQL and ext/date Added various examples and tests Added tests for indirect calls - see http://news.php.net/php.internals/73823 Added tests verifying method calls on non-objects work inside eval() Originally developed inside php#647
Is this going to be merged? |
@TazeTSchnitzel I' m trying, but everytime it is a mergeable state there's noone around to do so. I myself don't have commit access to the upstream project... |
Closing this, #847 is the new place |
* pr/647: (33 commits) zend_uint -> uint32_t Fix nesting for *non*-compile-time-resolveable functions See thekid@a1a4ba9#commitcomment-7414223 Add tests for calls to nested, *non*-compile-time-resolveable functions See thekid@a1a4ba9#commitcomment-7414362 Make list of opcodes used for nesting calculation consistent with `zend_do_convert_call_user_func()` in Zend/zend_compile.c Rewrite code to use ZEND_VM_JMP() instead of repeated ZEND_VM_INC_OPCODE() calls QA: Simplify code to find matching ZEND_DO_FCALL_BY_NAME CG(context).nested_calls is stored inside the initializer's result.num and inside the finalizer's op2.num, by comparing these we don't need to count manually, and are thus safer from future expansion with specialized opcodes e.g. Fix expected fatal error, now is catchable fatal Adjust expected fatal error message Now also includes "on [TYPE]" after merge from master Check for memory leaks when not using return value Adjust expected fatal error message Now also includes "on [TYPE]" after merge from master Add tests with arrays as parameters Handle ZEND_NEW nesting Also verify nesting with dynamically called static methods Handle ZEND_INIT_NS_FCALL_BY_NAME nesting QA: Refactor: Split tests a bit to make them more comprehendable Support nested static calls Handle ZEND_EXT_FCALL_END, skipping if necessary Verified with running tests with new "-e" run-tests arg: $ make test TESTS=Zend/tests/*-on-non-objects-*phpt TEST_PHP_ARGS=-e # Tests passed : 11 (100.0%) Add support for PHP's 'extended information for debugger/profiler' mode Verify non-CV-operands also work See discussion #647 (comment) Only allocate NULL return value if it's actually used ... Conflicts: ext/date/tests/bug67118.phpt
Oh shit, the numbers looked so alike that I had merged in this PR instead of the newer one. They're the same changes minus the added test cases, so instead of reverting what I have already done I shall just add the additional test cases. My bad =( |
|
This pull request turns fatal errors generated from calls to methods on a non-object into E_RECOVERABLE_ERRORs.
Example
The above produces the following output:
Reasoning
If you want to run PHP as a webserver itself, fatal errors are problematic. For a long story on why you would want to do that in the first place, see http://marcjschmidt.de/blog/2014/02/08/php-high-performance.html
Consistency
This behavior is consistent with how type hints work. Framework authors can turn this into exceptions if they wish.
Inner workings
The essence of this PR is a patch to the Zend Engine, which raises an E_RECOVERABLE_ERROR and then skips over all opcodes until it finds the one ending the current call, and sets return value to NULL.
See this gist for the patch to Zend/zend_vm_def.h only, the full diff view contains tests as well as generated code.
Further reading
BadMethodCallException
in these situations - can be done by implementing an error handler as seen here