-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Fix: Checking if a stream is castable should not emit warnings for user defined streams #10435
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
Conversation
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'm not a fan of adding a special case for user-space streams.
php_stream_temp_cast can emit errors when php_stream_fopen_tmpfile fails. I think that php_openssl_sockop_cast may emit errors too, when calling php_stream_fill_read_buffer, but I'm not sure that can be suppressed.
main/streams/cast.c
Outdated
return (strlen(stream->ops->label) == strlen("user-space")) | ||
&& strncmp(stream->ops->label, "user-space", strlen("user-space")) == 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.
An other way to do that would be to compare stream->ops to user_stream_wops. We do something like this in
php-src/main/streams/xp_socket.c
Line 655 in ebf26af
if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) { |
main/streams/userspace.c
Outdated
@@ -1382,6 +1384,8 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) | |||
php_stream * intstream = NULL; | |||
int call_result; | |||
int ret = FAILURE; | |||
bool report_errors = !(castas & PHP_STREAM_FLAG_SUPPRESS_ERRORS); | |||
castas &= ~PHP_STREAM_FLAG_SUPPRESS_ERRORS; |
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 use PHP_STREAM_CAST_MASK here like in
Lines 193 to 194 in ebf26af
int flags = castas & PHP_STREAM_CAST_MASK; | |
castas &= ~PHP_STREAM_CAST_MASK; |
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.
Okay this doesn't work as PHP_STREAM_CAST_MASK
does not contain the PHP_STREAM_FLAG_SUPPRESS_ERRORS
flag.
#define PHP_STREAM_CAST_MASK (PHP_STREAM_CAST_TRY_HARD | PHP_STREAM_CAST_RELEASE | PHP_STREAM_CAST_INTERNAL)
I could reuse the PHP_STREAM_CAST_INTERNAL
flag but that seems kinda terrible. Not sure what to do 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.
As I mentioned a new cast flag would be better here.
Since this targets master, we could add a |
Do we already have tests for those, if yes do you know which ones? Otherwise would be good to add some.
This was meant as a bugfix for 8.1 initally I thought about adding a |
a2a9873
to
c617e15
Compare
c617e15
to
c5e04e2
Compare
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 have been thinking about this and I don't this this is the right approach. We should not be doing special exceptions for user in cast code.
There seems to be quite common pattern when we don't want to emit some warnings during casting check. As mentioned this does not need to be specific to the user stream. I think we should introduce a new flag (castas
param) for that purpose (e.g. PHP_STREAM_CAST_CHECK
) and disable selected warnings in such case. That would be more generic solution IMHO.
main/streams/cast.c
Outdated
/* Do special handling for user streams as they may not implement the cast method */ | ||
if (!show_err && is_stream_user_stream(stream)) { | ||
castas |= PHP_STREAM_FLAG_SUPPRESS_ERRORS; | ||
} |
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.
Why is this done here? This checks ret
which means it won't be executed during casting check as ret
is null. This should be only executed during the actual casting so why did you want to suppress it here as well?
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.
Doesn't it make more sense to use that fact to check if an error should be emitted or not rather than a flag then?
main/streams/cast.c
Outdated
@@ -268,7 +282,7 @@ PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show | |||
} else if (flags & PHP_STREAM_CAST_TRY_HARD) { | |||
php_stream *newstream; | |||
|
|||
newstream = php_stream_fopen_tmpfile(); | |||
newstream = php_stream_fopen_tmpfile_ex(/* emit errors */ false); |
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.
From what I see PHP_STREAM_CAST_TRY_HARD
is used only during the actual casting so why do you suppress errors 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.
That is not how I see it. We do not control what flag is being passed in what context, and if someone wants to check that a stream is castable by trying hard before attempting to do so they are currently allowed to do as such.
c5e04e2
to
fbe2070
Compare
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.
The logic seems ok. Just needs a little bit of clean up.
main/streams/userspace.c
Outdated
@@ -1398,22 +1400,28 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) | |||
|
|||
do { | |||
if (call_result == FAILURE) { | |||
php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!", | |||
if (report_errors) { | |||
php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!", | |||
ZSTR_VAL(us->wrapper->ce->name)); |
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.
NIT: it looks to me that stream convention is to use two tabs indent for continous indent so it would be nice to keep it that way.
@@ -1382,6 +1382,8 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) | |||
php_stream * intstream = NULL; | |||
int call_result; | |||
int ret = FAILURE; | |||
/* If we a checking if the stream can cast, no return pointer is provided, so do not emit errors */ | |||
bool report_errors = retptr; |
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.
Seems a bit hacky but I guess it's still better that hold call state in the stream struct so I'm cool with this. Not sure if we need temp var but not a big issue with that either...
PHP_STREAM_CAST_MASK does not contain the REPORT_ERROR bit value This reverts commit 9617c45.
fbe2070
to
4dd3caf
Compare
Should I backport this to 8.1 @bukka ? |
@Girgias I would target only 8.3 as removal of warnings might potentially break some code even though it's unlikely. But considering it's been there for ages, only 8.3 should be fine here. |
When calling
php_stream_can_cast()
the expectation is that errors about a stream not being castable are suppressed, but when a userland stream wrapper does not define thestream_cast()
method it always warns.This is a "similar" problem to #9583/#9638 in that for user defined implementation the function pointer is always defined even if the user implementation does not actually support the operation.
To solve this without unwrapping the stream wrapper and check if the method is defined, we overload the
cast_as
parameter by turning it into a bitmask which accepts the internalPHP_STREAM_FLAG_SUPPRESS_ERRORS
flag. (I initially went forREPORT_ERRORS
but the bits overlap between this constant and the CAST_AS constants)However, we only do this if the stream_wrapper is a userland wrapper (as we control the implementation) as otherwise this is an API break for stream wrappers defined by extensions.