-
Notifications
You must be signed in to change notification settings - Fork 7.8k
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
Improve error reporting in random extension #9071
Conversation
This exposes the underlying exception, improving debugging: Fatal error: Uncaught Exception: Cannot open source device in php-src/test.php:5 Stack trace: #0 php-src/test.php(5): Random\Engine\Secure->generate() #1 {main} Next RuntimeException: Random number generation failed in php-src/test.php:5 Stack trace: #0 php-src/test.php(5): Random\Engine\Secure->generate() #1 {main} thrown in php-src/test.php on line 5
This exposes the underlying exception, improving debugging: Exception: Cannot open source device in php-src/test.php:17 Stack trace: #0 php-src/test.php(17): Random\Randomizer->getInt(1, 3) #1 {main} Next RuntimeException: Random number generation failed in php-src/test.php:17 Stack trace: #0 php-src/test.php(17): Random\Randomizer->getInt(1, 3) #1 {main}
This improves debugging, because the actual reason for the failure is available as a previous Exception: DomainException: The returned string must not be empty in php-src/test.php:17 Stack trace: #0 php-src/test.php(17): Random\Randomizer->getBytes(123) #1 {main} Next RuntimeException: Random number generation failed in php-src/test.php:17 Stack trace: #0 php-src/test.php(17): Random\Randomizer->getBytes(123) #1 {main}
…rs in 50 attempts This improves debugging, because the actual reason for the failure is available as a previous Exception: RuntimeException: Failed to generate an acceptable random number in 50 attempts in php-src/test.php:17 Stack trace: #0 php-src/test.php(17): Random\Randomizer->getInt(1, 3) #1 {main} Next RuntimeException: Random number generation failed in php-src/test.php:17 Stack trace: #0 php-src/test.php(17): Random\Randomizer->getInt(1, 3) #1 {main}
Select parameters for ->getInt() that will actually lead to unsafe behavior.
If an engine fails once it will be permanently poisoned by setting `->last_unsafe`. This is undesirable for the test, because it skews the results. Fix this by creating a fresh engine for each "assertion".
I was struggling with the appropriate message. |
ext/random/engine_user.c
Outdated
@@ -50,6 +53,7 @@ static uint64_t generate(php_random_status *status) | |||
result += ((uint64_t) (unsigned char) Z_STRVAL(retval)[i]) << (8 * i); | |||
} | |||
} else { | |||
zend_throw_exception(spl_ce_DomainException, "A random engine must return a non-empty string", 0); |
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 rather use a ValueError 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.
I considered that, but ValueError
is documented as:
A ValueError is thrown when the type of an argument is correct but the value of it is incorrect.
Thus it is documented to apply to parameters / arguments, which is not exactly the case 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.
We could update the ValueError
documentation; but probably throwing an Error
directly is the way to go. zend_throw_error()
accepts NULL
for the first parameter, and that is already used quite a lot across php-src. I don't think that introducing a new error type would be particularly useful, since Error
s are not supposed to be caught.
As we print the full stringified exception we implicitly assert the type of the exception. No need to be overly specific in the catch block.
Some generic thoughts on exceptions, followed by opinions for the PR. Each 'module' should define and only use its own exceptions.It is strongly preferred to create new exceptions rather than re-using exceptions that already exist in the engine. The reason for this is your should be able to write code like this:
Rather than have to break blocks up like this:
I know PHP has been terrible at this in the past. And also that exactly what a 'module' is internally inside core PHP is not entirely well-defined. But still. We can try to do better. Each 'module' should define a base extension that can be caught:For anyone calling your code, it is useful to be able to catch any exception your code might throw with a single catch statement:
rather than having to catch every possible exception:
Though the exact amount of value that produces depends on how many child/specific exceptions there are, with this approach providing more value when there are more exceptions. Should base extensions be a class or interface?I don't know. You should ask someone smarter than me. If I had to guess, I say interface and give myself about a 60% chance of being correct. Exceptions or ErrorExceptions are for when the program is probably written correctly, but something external to the program or computer is failing. E.g. when the database has fallen over in the middle of a query, throwing an exception is correct. Errors are for when the program is probably written incorrectly, and a someone is going to have to go into the code and fix something. Or for when the computer it is running on is seriously sick. e.g. not being able to open a file descriptor because the machine has reached the file descriptor limit means it's time to stop trying to run the program. Exception names should duplicate the namespace name to avoid collisions.If we had two exceptions of e.g. this errors:
Have to alias:
By including the namespace or module name and instead having This is probably the part people would be most likely to disagree on. And so...When the string returned is zero length aka engine_user.c:56:
Logic error because it’s the implementation of the random generator that is very probably at fault. And UnserializeThe places where an exception is thrown in PHP_METHOD(Random_Randomizer, __unserialize) could be left as they are. Technically it would be far saner if there was a single exception of Everywhere elsePretty much all of the rest should probably be:
RuntimeError because something went wrong that might not be in the programmers control, but also can't be ignored. e.g. a complete lack of entropy or file descriptors. So that includes both of those in php_random_bytes: And all of the other ones that you weren't even asking about, e.g:
When this happens something is probably very wrong with the system. Anyway, "It's just naming things, how hard could it be?" /s. |
@Danack Thank you for the extensive essay on this matter. That thing should likely be preserved somewhere for others in my position. Also thanks to the others who voiced their opinions of not using the SPL exceptions. I have adjusted this PR to use a plain As adjusting the "user-visible" Exception might need additional RM approval I plan to create a follow-up PR that does so based on Danack's suggestions. |
@Danack Is it? I believe it should not break compatibility if that new exception would extend the plain Exception, as any existing catch blocks continue to catch. |
@Girgias @TimWolla @Danack By the way, changing the type of Throwable to be thrown causes a conflict with the RFC. |
That's a minor implementation detail and not really of concern for a conflict with the RFC. And this is resolved by just merging... |
@Girgias |
No description provided.