Page MenuHomePhabricator

Improve static_assert/_Static_assert diagnostics
ClosedPublic

Authored by aaron.ballman on Jan 25 2021, 2:39 PM.

Details

Summary

I noticed that our diagnostics relating to static assertions are a bit confused. For instance, when in MS compatibility mode in C (where we accept static_assert even without including <assert.h>), we would fail to warn the user that they were using the wrong spelling (even in pedantic mode), we were missing a compatibility warning about using _Static_assert in earlier standards modes, diagnostics for the optional message were not reflected in C as they were in C++, etc.

This patch improves the diagnostic functionality in both C and C++ mode. It adds -Wc89-c99-c11-c17-compat and -Wc89-c99-c11-c17-compat-pedantic diagnostics groups and adds new C-specific diagnostics for a static assertion without a diagnostic message.

Diff Detail

Unit TestsFailed

TimeTest
30 msx64 debian > Clang.FixIt::fixit-static-assert.cpp
Script: -- : 'RUN: at line 1'; /mnt/disks/ssd0/agent/llvm-project/build/bin/clang -cc1 -internal-isystem /mnt/disks/ssd0/agent/llvm-project/build/lib/clang/13.0.0/include -nostdsysteminc -std=c++14 /mnt/disks/ssd0/agent/llvm-project/clang/test/FixIt/fixit-static-assert.cpp -fdiagnostics-parseable-fixits /mnt/disks/ssd0/agent/llvm-project/clang/test/FixIt/fixit-static-assert.cpp 2>&1 | /mnt/disks/ssd0/agent/llvm-project/build/bin/FileCheck /mnt/disks/ssd0/agent/llvm-project/clang/test/FixIt/fixit-static-assert.cpp
50 msx64 debian > Flang.Semantics::resolve102.f90
Script: -- : 'RUN: at line 1'; /mnt/disks/ssd0/agent/llvm-project/flang/test/Semantics/test_errors.sh /mnt/disks/ssd0/agent/llvm-project/flang/test/Semantics/resolve102.f90 /mnt/disks/ssd0/agent/llvm-project/build/tools/flang/test/Semantics/Output/resolve102.f90.tmp /mnt/disks/ssd0/agent/llvm-project/build/bin/f18 -intrinsic-module-directory /mnt/disks/ssd0/agent/llvm-project/build/tools/flang/include/flang
80 msx64 windows > Clang.FixIt::fixit-static-assert.cpp
Script: -- : 'RUN: at line 1'; c:\ws\w16n2-1\llvm-project\premerge-checks\build\bin\clang.exe -cc1 -internal-isystem c:\ws\w16n2-1\llvm-project\premerge-checks\build\lib\clang\13.0.0\include -nostdsysteminc -std=c++14 C:\ws\w16n2-1\llvm-project\premerge-checks\clang\test\FixIt\fixit-static-assert.cpp -fdiagnostics-parseable-fixits C:\ws\w16n2-1\llvm-project\premerge-checks\clang\test\FixIt\fixit-static-assert.cpp 2>&1 | c:\ws\w16n2-1\llvm-project\premerge-checks\build\bin\filecheck.exe C:\ws\w16n2-1\llvm-project\premerge-checks\clang\test\FixIt\fixit-static-assert.cpp
40 msx64 windows > LLVM.ExecutionEngine/JITLink/AArch64::MachO_arm64_ehframe.test
Script: -- : 'RUN: at line 1'; c:\ws\w16n2-1\llvm-project\premerge-checks\build\bin\llvm-jitlink.exe -noexec C:\ws\w16n2-1\llvm-project\premerge-checks\llvm\test\ExecutionEngine\JITLink\AArch64/Inputs/MachO_arm64_ehframe.o
60 msx64 windows > LLVM.ExecutionEngine/JITLink/AArch64::MachO_arm64_relocations.s
Script: -- : 'RUN: at line 1'; rm -rf C:\ws\w16n2-1\llvm-project\premerge-checks\build\test\ExecutionEngine\JITLink\AArch64\Output\MachO_arm64_relocations.s.tmp && mkdir -p C:\ws\w16n2-1\llvm-project\premerge-checks\build\test\ExecutionEngine\JITLink\AArch64\Output\MachO_arm64_relocations.s.tmp
View Full Test Results (22 Failed)

Event Timeline

aaron.ballman requested review of this revision.Jan 25 2021, 2:39 PM
aaron.ballman created this revision.
rsmith added inline comments.Jan 28 2021, 4:53 PM
clang/lib/Parse/ParseDeclCXX.cpp
884–886

I don't think this diagnostic is useful as-is: on Windows, including <assert.h> doesn't help because it doesn't #define static_assert. And people hitting this also can't switch to using _Static_assert, because MSVC doesn't provide it, only static_assert.

If we want to warn here, we could perhaps check whether <assert.h> has been included, but getting that check correct across PCH / modules is not straightforward. (If we knew what include guard the CRT's assert.h used (if any), I guess we could check whether that's defined, but that'd be a bit of a hack.) But I'm somewhat inclined to think we don't have a good way to distinguish between the good cases and the bad ones, so we shouldn't warn. Hopefully MS will fix their CRT at some point and we can stop providing this compatibility hack entirely (or start warning on it by default).

aaron.ballman added inline comments.Jan 29 2021, 4:57 AM
clang/lib/Parse/ParseDeclCXX.cpp
884–886

Are you sure they don't support _Static_assert yet? I seem to be able to use it fine: https://godbolt.org/z/vG47he

That said, this does appear to be only available in newer versions of MSVC, so perhaps you're correct about the diagnostic being a bit unhelpful. My primary concern is that use of static_assert in C is a nonconforming extension and we default to -fms-compatibility on Windows when Clang is built by MSVC. So it's not difficult to accidentally run into this, but the only warning we give on it with -Weverything -pedantic is how it's not compatible with C++98.

WDYT?

aaron.ballman marked an inline comment as not done.Feb 4 2021, 9:03 AM
aaron.ballman added inline comments.
clang/lib/Parse/ParseDeclCXX.cpp
884–886

I suppose one option would be to look at what version of MSVC we're trying to be compatible with to see if that's a version that supports /std:c11 and only emit this diagnostic in that case, but tbh, that feels like it'll lead to confusing diagnostic behavior (esp given that we default to ms compatibility mode silently when you build Clang with MSVC on Windows).

Given that MSVC does support _Static_assert when you enable C11 or later language mode, I'm inclined to warn on this construct by default.

WDYT?

aaron.ballman marked 2 inline comments as not done.Feb 11 2021, 7:55 AM

Ping.

rsmith added a reviewer: rnk.Feb 11 2021, 3:15 PM

FYI, https://reviews.llvm.org/D17444 captures some of the history here.

clang/lib/Parse/ParseDeclCXX.cpp
884–886

Well, it's good to see that they've made progress, but it looks like their <assert.h> still doesn't #define static_assert, so I think we still don't have an actionable warning we can produce here. We can't reasonably tell people to include <assert.h> (as this patch does) because that doesn't work. And it doesn't seem reasonable to tell people to use _Static_assert instead, if they actually have included <assert.h>. (I don't think we want to encourage people to use _Static_assert instead of <assert.h> + static_assert.)

So I don't think MSVC adding support for _Static_assert really changes anything here -- until their <assert.h> works, or we find some good way to detect whether it was properly included, this warning will fire on both correct code and incorrect code, which doesn't seem all that useful.

aaron.ballman added inline comments.Feb 12 2021, 5:27 AM
clang/lib/Parse/ParseDeclCXX.cpp
884–886

And it doesn't seem reasonable to tell people to use _Static_assert instead, if they actually have included <assert.h>. (I don't think we want to encourage people to use _Static_assert instead of <assert.h> + static_assert.)

Ideally, yes. But this isn't ideal -- we produce no diagnostic for this nonconforming extension and that's causing pain in practice. As an example of where I ran into this: I had a header file that was shared between C and (mostly) C++ code and added a static_assert to it but forgot to add #include <assert.h>. This compiled great in MSVC and clang-cl, but when compiled with clang on CI is when I finally found the issue. e.g., like this: https://godbolt.org/z/cs8YGb

If I had to pick between behaviors, I think I'd prefer pushing people towards using _Static_assert even if assert.h is included over silently accepting a nonconforming extension in pedantic mode.

Rather than trying to see what header guard was used by <assert.h>, couldn't we assume that if assert is defined as a macro then <assert.h> must have been included (or the user triggered UB and gets what they get)? So the logic could be: only diagnose use of the static_assert (keyword) in C mode if assert is not defined?

We can't reasonably tell people to include <assert.h> (as this patch does) because that doesn't work.

But it does (as far as the user is concerned)? In MS compatibility mode, static_assert is always accepted, so the include isn't strictly necessary but isn't harmful either. Outside of MS compatibility mode, the include is required to spell it static_assert because we won't treat it as a keyword. If we go with the "only diagnose when assert is not defined as a macro" approach, it would give us the desired behavior here.

rsmith added inline comments.Feb 17 2021, 1:58 PM
clang/lib/Parse/ParseDeclCXX.cpp
884–886

we produce no diagnostic for this nonconforming extension

Well, I'd argue that it's not a non-conforming extension so much as it's a different language mode that has different rules. We don't accept static_assert if -fms-compatibility is disabled. I don't think conformance to ISO C has any bearing here, because you're not compiling the input in ISO C mode.

So the logic could be: only diagnose use of the static_assert (keyword) in C mode if assert is not defined?

Interesting, I'd not considered that. I think that could work well enough.

This approach wouldn't work when compiling preprocessed output, though perhaps we could fix that too, by making the preprocessor implicitly inject a #define static_assert _Static_assert if it sees a #define of assert in MS compatibility mode. We'd need to keep the static_assert keyword around for compatibility with code that uses it without a suitable #include, but we could then warn on it unconditionally, as this patch does.

We can't reasonably tell people to include <assert.h> (as this patch does) because that doesn't work.

But it does (as far as the user is concerned)?

As far as the user is concerned, if their source file already includes <assert.h> and they get a warning that says "you need to include <assert.h>", they would reasonably conclude that that's a compiler bug. I think we need to avoid that possibility.

Updated based on review feedback.

@rnk / @thakis Can you take a look at this and see if you're happy with this "defining assert implicitly defines static_assert" approach?

clang/include/clang/Basic/DiagnosticParseKinds.td
427–434

Perhaps:

def ext_ms_static_assert : ExtWarn<
  "use of 'static_assert' without inclusion of <assert.h> is a Microsoft extension">,
  "<assert.h>">, InGroup<MicrosoftStaticAssert>;

I think MicrosoftStaticAssert should also be a subgroup of the Microsoft (-Wmicrosoft) warning group.

clang/lib/Lex/PPDirectives.cpp
2884

Do we need to give the expansion token a source location? What do diagnostics pointing at this token look like?

clang/lib/Parse/ParseDeclCXX.cpp
891

I don't think we need the isMacroDefined check here; we will have a static_assert macro in that case, so we won't see a kw_static_assert token. Checking for assert being defined means we won't warn on this, and we should:

#include <assert.h>
#undef static_assert
static_assert(1, "");
aaron.ballman marked 2 inline comments as done.

Updating based on review feedback.

clang/lib/Lex/PPDirectives.cpp
2884

Given:

#define assert

// This is a comment
int i = 0;
static_assert(0, "test");

I get:

F:\llvm-project>llvm\out\build\x64-Debug\bin\clang.exe -fsyntax-only "F:\Aaron B
allman\Desktop\test.c"
F:\Aaron Ballman\Desktop\test.c:5:1: error: static_assert failed "test"
static_assert(0, "test");
^             ~
F:\Aaron Ballman\Desktop\test.c:5:1: note: expanded from macro 'static_assert'
static_assert(0, "test");
^
1 error generated.

which is a bit funky due to the note. However, I'm not certain what source location to give it. If I give it the location of the assert identifier, then I get output like:

F:\llvm-project>llvm\out\build\x64-Debug\bin\clang.exe -fsyntax-only "F:\Aaron B
allman\Desktop\test.c"
F:\Aaron Ballman\Desktop\test.c:5:1: error: static_assert failed "test"
static_assert(0, "test");
^             ~
F:\Aaron Ballman\Desktop\test.c:1:9: note: expanded from macro 'static_assert'
#define assert
        ^
1 error generated.

which seems even more mysterious.

Rebasing on ToT.

rnk added a comment.Mar 1 2021, 1:50 PM

I guess my only concern is, what happens if MSVC fixes assert.h? Do we need to make the implicit #define static_assert _Static_assert conditional on the absence of any static_assert definition?

Address review comments from @rnk

Only define the static_assert macro if one is not already defined and test the expected behavior.

rnk accepted this revision.Mar 2 2021, 11:26 AM

lgtm, but please make sure that Richard is happy

This revision is now accepted and ready to land.Mar 2 2021, 11:26 AM
rsmith accepted this revision.Mar 2 2021, 4:43 PM
rsmith added a comment.Mar 2 2021, 4:47 PM

Looks like test/FixIt/fixit-static-assert.cpp is failing in Phabricator's pre-merge checks: B91556. Please take a look at that before landing this; I think there's a decent chance that it's indicative of a real problem.

aaron.ballman closed this revision.Mar 3 2021, 5:49 AM

Looks like test/FixIt/fixit-static-assert.cpp is failing in Phabricator's pre-merge checks: B91556. Please take a look at that before landing this; I think there's a decent chance that it's indicative of a real problem.

The issue was that I was using Extension instead of ExtWarn for the 'static_assert' with no message warnings -- I changed it to use ExtWarn and fixed up the new tests. If you prefer I use Extension instead, I'm happy to do that and change the fixit test instead.

I've commit in 8da090381d567d0ec555840f6b2a651d2997e4b3, thank you for the reviews!