fix: return errors instead of panicking on engine and user input - #758
Merged
Merged
Conversation
Coverage Report for CI Build 30659294744Coverage increased (+0.4%) to 66.959%Details
Uncovered Changes
Coverage Regressions5 previously-covered lines in 3 files lost coverage.
Coverage Stats
💛 - Coveralls |
|
| Project | ext-php-rs |
| Branch | fix/panics-non-breaking |
| Testbed | PHP 8.4.24 (cli) (built: Jul 29 2026 09:44:18) (NTS) |
⚠️ WARNING: Truncated view!The full continuous benchmarking report exceeds the maximum length allowed on this platform.
🐰 View full continuous benchmarking report in Bencher
⚠️ WARNING: No Threshold found!Without a Threshold, no Alerts will ever be generated.
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
First slice of the "panics where a
Resultbelongs" item from the v0.16 backlog, limited to the changes that are not breaking, so they can ship on the current release train.A panic in an
extern "C"frame is not a Rust panic.try_catchcallsresume_unwindon a caught panic and the generated handlers areextern "C", notC-unwind, so the process aborts. Each fix below removes a panic reachable from engine-provided or user input.php_errorpassed the caller's message asphp_error_docref's format argument, which is declaredPHP_ATTRIBUTE_FORMAT(printf, 3, 4)in every supported version. A message containing%sor%nread garbage varargs. It is now passed as a%sargument, and thetry_intoon the error-type bits no longer panics. PHP 8.5 addedphp_error_docref_uncheckedfor exactly this reason.http_server_varsbuilt the_SERVERauto-global name withZendStr::new("_SERVER", false).as_mut_ptr(). TheZBoxdropped at the end of the statement, sozend_is_auto_globalread freed Zend memory. The same shape existed in the#[cfg(not(php81))]arm ofhttp_request_vars, which is still live for a--no-default-featuresbuild against PHP 8.0 becausemin_api_version()only floors at 8.1 under theenumfeature.ini_values()panicked on any directive whose value is not valid UTF-8.zend_ini_entry.valueis azend_stringholding arbitrary bytes fromphp.ini, so this was reachable on a normal install. Values are now decoded lossily, an unregistered directive table yields an empty map instead of a null deref, and a non-IS_PTRentry is skipped rather than trusted.throw_property_access_errorinterpolated a property name into aCString.zend_stringis length prefixed, so$obj->{"a\0b"}is legal PHP and aborted inside theextern "C"read_propertyframe. The message now falls back to a static one..throw().expect(...), turning a failed throw into an abort. Nowlet _ = e.throw();, matching the idiom already used insrc/zend/handlers.rs.FromZendObject for Stringhad twopanic!s marked// TODO: become an error, so a PHP class whose__toString()throws aborted the worker. It now returnsError::Exception,Error::ZvalConversion, or the newError::NotStringable. It also passed(*obj.ce).__tostringunchecked intozend_call_known_function, whose non-null assertion isZEND_ASSERTand therefore compiled out of a release build: a class without__toString()segfaulted rather than failing. Both the class entry and the handler are now checked.From<Error> for PhpExceptionstringified througherr.to_string(), discarding the class and stack of a caught PHP exception even thoughwith_objectexisted. It now reattaches the exception object. Worth noting the old path often did not merely degrade: theDebugdump contained NUL bytes, soCString::newrejected it and PHP reportedException: String given contains NUL-bytesinstead of anything about__toString.Also adds
.#debugand.#zts-debugdev shells. Zend assertions such as the handler check above are compiled out of a release PHP, so bugs that segfault in production only assert on aZEND_DEBUGbuild, and the flake previously offered no way to get one.Erroris#[non_exhaustive], so the newNotStringablevariant is additive.Verified on all four dev shells: NTS and ZTS, each non-debug and
ZEND_DEBUG. Both new tests were confirmed to fail without their fix rather than trusting a green run.One gap: the use-after-free fix in the
#[cfg(not(php81))]arm is not compiler-checked locally, since it needs--no-default-featuresagainst PHP 8.0.Checklist