This is an archive of the discontinued LLVM Phabricator instance.

[Clang] Allow long as size_t printf argument on 32-bit Windows platforms.
Needs ReviewPublic

Authored by jacek on Apr 20 2020, 10:48 AM.

Details

Reviewers
hans
Group Reviewers
Restricted Project
Summary

On 32-bit Windows platform, int and long is mixed all over the place in platform headers. The most notable example is SIZE_T, which is unsigned long, unlike size_t, which is unsigned int. %I, %z and %j formats do the right thing for those types, but clang issues a warning, leaving no good way to print SIZE_T (other than disabling warnings or adding useless casts).

GCC supports only %I for mingw targets, but allows both int and long to be used.

This is causing problems when using clang to build Wine PE files.

Diff Detail

Event Timeline

jacek created this revision.Apr 20 2020, 10:48 AM
Herald added a project: Restricted Project. · View Herald TranscriptApr 20 2020, 10:48 AM

Personally, I don't consider those to be useless casts. They appropriately ensure that the types match between the arguments and the format specifiers, rather than relying on underlying details of what SIZE_T expands to for a given target. I would expect these to at least use the warn_format_conversion_argument_type_mismatch_confusion diagnostic rather than be silenced entirely.

Personally, I don't consider those to be useless casts. They appropriately ensure that the types match between the arguments and the format specifiers, rather than relying on underlying details of what SIZE_T expands to for a given target. I would expect these to at least use the warn_format_conversion_argument_type_mismatch_confusion diagnostic rather than be silenced entirely.

+1. This is useful to anyone trying to write code that compiles for both 32- and 64-bit code.

rnk edited reviewers, added: hans; removed: rnk.May 15 2020, 6:51 AM

I'll re-iterate my opinion that the casts currently required to bypass the warnings are useful (e.g., if you ever want to port the code to 64-bit). There are lots of places where the Windows API essentially required uncomfortable conversions, and we can't paper over all of them. The way to get it right in all those other places is to be very aware of the underlying types. Hiding an uncomfortable detail here and there seems like a disservice.

clang/lib/AST/FormatString.cpp
407

I'm not convinced isOSMSVCRT is the right check. The troubling typedefs are [[ https://docs.microsoft.com/en-us/windows/win32/winprog/windows-data-types#size-t | SIZE_T and SSIZE_T ]], which come from the Windows API and are unrelated to Microsoft's C run-time library. I think isOSWindows would be more precise.

I'll re-iterate my opinion that the casts currently required to bypass the warnings are useful (e.g., if you ever want to port the code to 64-bit). There are lots of places where the Windows API essentially required uncomfortable conversions, and we can't paper over all of them. The way to get it right in all those other places is to be very aware of the underlying types. Hiding an uncomfortable detail here and there seems like a disservice.

+1

hans added a comment.May 15 2020, 10:24 AM

clang issues a warning, leaving no good way to print SIZE_T (other than disabling warnings or adding useless casts)

I also don't think Clang should change here though. The warning is legit for code that cares about portability, and inserting a cast is probably the best fix for such code. For code that doesn't care, disabling the warning seems like the way to go.

rsmith added a subscriber: rsmith.May 15 2020, 12:19 PM
rsmith added inline comments.
clang/lib/AST/FormatString.cpp
407

We already care about typedef-names used to spell argument types in some cases (see namedTyoeToLengthModifier. If this is a case of MSVC providing a typedef that people *think* is size_t, that is always the same size and representation as size_t, and that is intended to be fully interchangeable with size_t, but sometimes is a different type, then it might be reasonable to detect whether the type was spelled using that SIZE_T typedef when targeting Windows, and exempt only that case.

I mean, assuming that we want to allow such code at all and not push people to cast explicitly from SIZE_T to size_t. I don't have a strong opinion on that; I think it rather depends on intended idiomatic usage on that platform.

I don't mind dropping the patch, we can mitigate the problem in Wine. My understanding is that we could use "pedantic" logic similar to NSInteger in checkFormatExpr, please let me know if you'd like something like that.

I still think that using casts in such cases is not the right pattern. For an example, if developer knows that SIZE_T is a type that will always be compatible with %z, the code:
printf("%zd", size);
is not wrong. Proposed casts would change all such cases to:
printf("%zd", (size_t)size);
Now imagine that I made a mistake and changed type of size to a type of different size. In the first variant, clang will issue a warning. In the second case, the cast will hide the warning, but I'm probably interested in changing the format instead.

hans added a comment.May 18 2020, 5:10 AM

Doing special handling for SIZE_T, as opposed to long in general, on Windows as Richard suggests seems reasonable to me.

Doing special handling for SIZE_T, as opposed to long in general, on Windows as Richard suggests seems reasonable to me.

I would be fine with this so long as it doesn't allow %zu to match an unsigned int, etc. SIZE_T matching a %z seems reasonable to me.