Skip to content

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

Closed
wants to merge 10 commits into from

Conversation

Girgias
Copy link
Member

@Girgias Girgias commented Jan 24, 2023

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 the stream_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 internal PHP_STREAM_FLAG_SUPPRESS_ERRORS flag. (I initially went for REPORT_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.

Copy link
Member

@arnaud-lb arnaud-lb left a 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.

Comment on lines 192 to 193
return (strlen(stream->ops->label) == strlen("user-space"))
&& strncmp(stream->ops->label, "user-space", strlen("user-space")) == 0;
Copy link
Member

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

if (stream->ops == &php_stream_unix_socket_ops || stream->ops == &php_stream_unixdg_socket_ops) {

@@ -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;
Copy link
Member

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

php-src/main/streams/cast.c

Lines 193 to 194 in ebf26af

int flags = castas & PHP_STREAM_CAST_MASK;
castas &= ~PHP_STREAM_CAST_MASK;

Copy link
Member Author

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.

Copy link
Member

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.

@arnaud-lb
Copy link
Member

Since this targets master, we could add a flags parameter on the cast operation?

@Girgias
Copy link
Member Author

Girgias commented Jan 27, 2023

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.

Do we already have tests for those, if yes do you know which ones? Otherwise would be good to add some.

Since this targets master, we could add a flags parameter on the cast operation?

This was meant as a bugfix for 8.1 initally

I thought about adding a flag parameter, but that's an API break and wasn't sure if it was worth to do it.

@Girgias Girgias force-pushed the non-cast-userstream-warns branch from a2a9873 to c617e15 Compare January 27, 2023 20:43
@Girgias Girgias force-pushed the non-cast-userstream-warns branch from c617e15 to c5e04e2 Compare February 5, 2023 17:30
Copy link
Member

@bukka bukka left a 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.

Comment on lines 204 to 207
/* 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;
}
Copy link
Member

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?

Copy link
Member Author

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?

@@ -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);
Copy link
Member

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?

Copy link
Member Author

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.

@Girgias Girgias force-pushed the non-cast-userstream-warns branch from c5e04e2 to fbe2070 Compare July 17, 2023 05:55
@Girgias Girgias requested a review from bukka July 25, 2023 09:56
Copy link
Member

@bukka bukka left a 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.

@@ -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));
Copy link
Member

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;
Copy link
Member

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...

@Girgias Girgias changed the base branch from master to PHP-8.3 August 30, 2023 20:06
@Girgias Girgias force-pushed the non-cast-userstream-warns branch from fbe2070 to 4dd3caf Compare August 30, 2023 20:07
@Girgias
Copy link
Member Author

Girgias commented Aug 30, 2023

Should I backport this to 8.1 @bukka ?

@bukka
Copy link
Member

bukka commented Sep 7, 2023

@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.

@Girgias Girgias closed this in d68073c Sep 8, 2023
@Girgias Girgias deleted the non-cast-userstream-warns branch September 8, 2023 12:23
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.

3 participants