Skip to content
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

RFC: Marking return values as important (#[\NoDiscard]) #17599

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

TimWolla
Copy link
Member

@TimWolla TimWolla commented Jan 27, 2025

@TimWolla TimWolla force-pushed the return-value-unused branch from 7f8ad60 to 6f8d462 Compare January 28, 2025 15:38
@TimWolla TimWolla force-pushed the return-value-unused branch from 6f8d462 to 7a5a475 Compare January 28, 2025 16:31
Copy link
Member

@iluuu1994 iluuu1994 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There could be some edge-cases with the optimizer. For example:

if (foo());

Should or should this not error? It will not without opcache, but will with.

Zend/zend_vm_def.h Outdated Show resolved Hide resolved
ir_GUARD(ret, jit_STUB_ADDR(jit, jit_stub_exception_handler));
}

if ((func->common.fn_flags & ZEND_ACC_NODISCARD) && !RETURN_VALUE_USED(opline)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably combine these two checks, and handle them both in the helper to avoid assembly-bloat.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that initially, but failed to access RETURN_VALUE_USED(opline) within the helper. It's also doing some shenanigans with getting OPLINE_D as parameter and then treating it as zend_execute_data*. I would need an explanation of how to make this change.

But independent of that, I expect the warning to rarely be triggered in practice, because it is meant to point out unsafe code. Thus I would also expect this branch not to be taken, thus not generating the corresponding assembly. I would expect this to be more efficient than merging the two checks in a single helper.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I meant to comment this for the function JIT above. The tracing part is fine.

I tried that initially, but failed to access RETURN_VALUE_USED(opline) within the helper

You shouldn't need to. You can create two versions, one that checks for deprecated only, and one that checks for deprecated and deprecated & nodiscard that is only emitted when the opline result is known to be unused at JIT-time.

Zend/Optimizer/optimize_func_calls.c Outdated Show resolved Hide resolved
@@ -3927,15 +3927,15 @@ ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, zend_function *fbc) /*
ZEND_ASSERT(!(fbc->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE));
if (fbc->type == ZEND_INTERNAL_FUNCTION && !(CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_FUNCTIONS)) {
if (init_op->opcode == ZEND_INIT_FCALL && !zend_execute_internal) {
if (!(fbc->common.fn_flags & ZEND_ACC_DEPRECATED)) {
if (!(fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, we can allow ZEND_DO_ICALL if we know the return value is used. Annoyingly, the return value is only removed after the call has compiled:

SET_UNUSED(opline->result);

Maybe this could be improved by handlingZEND_AST_CALL in zend_compile_stmt() and passing a NULL result.

return result;
}

ZEND_API ZEND_COLD void ZEND_FASTCALL zend_nodiscard_function(const zend_function *fbc)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Name should include error or throw.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This naming is consistent with zend_deprecated_function(), zend_deprecated_class_constant(), zend_false_to_array_deprecated() and probably others. I can rename the function (should I then rename all of them)?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since they are ZEND_API, renaming them seems unnecessary. I'd prefer just properly naming the new ones, but I understand your reasoning to keep it consistent.

zend_deprecated_function(fbc);
const uint32_t no_discard = (!RETURN_VALUE_USED(opline)) * ZEND_ACC_NODISCARD;

if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The vast majority of code omits != 0. I'd prefer removing it, it's only noise.

@TimWolla
Copy link
Member Author

Should or should this not error? It will not without opcache, but will with.

Should not warn, but the RFC explicitly acknowledges OPcache optimizations (which is part of the reason for the explicit (void) cast): https://wiki.php.net/rfc/marking_return_value_as_important#void_cast_to_suppress_the_warning

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants