An end to implicit fall-throughs in the kernel
The problem with C's fall-through behavior is that it is implicit, with no indication of whether the behavior is intended or not. Developers learn (the hard way, sometimes) to end each case with a break statement as a matter of habit, but it's still an easy thing to forget, and the resulting code is seen by the compiler as being entirely valid. A forgotten break almost certainly introduces a bug, even if it might not manifest itself for years. Many developers have had reason to wish that the C language required an explicit indication by the programmer that fall-through behavior is desired.
Making fall-through behavior explicit
The GCC compiler has, for some time, offered a warning option (-Wimplicit-fallthrough) intended to catch missing break statements. It will trigger for any case that falls through into a succeeding case unless an explicit marker has been placed to indicate that the behavior is correct. It turns out that that there are many ways of placing this marker, depending on the value provided with -Wimplicit-fallthrough; the full set can be found in the GCC documentation. At its most lax, the presence of any comment at all prior to a case statement will allow warning-free fall-through into that case. Using -Wimplicit-fallthrough=3 causes the compiler to look for comments matching a fairly large set of regular expressions.
In the kernel, one is most likely to find comments that look like:
Style Count /* fallthrough */ 350 /* fall through */ 3,091 /* falls through */ 8 /* fall-through */ 167
Note that case was ignored in generating the above numbers, and that there are probably other variants in the code as well. All of those comment styles will be recognized by GCC with -Wimplicit-fallthrough=3 and will thus cause warnings to be suppressed.
As can be seen, there are a lot of these comments in the kernel source now, some of which date back to before the beginning of the Git era. Their number has increased in recent times, though, due mostly to a focused effort by Gustavo A. R. Silva, who has been working to eliminate every implicit fall-through warning in the kernel. The point of this effort, beyond fixing any bugs caused by missing break statements, is to make it possible to enable -Wimplicit-fallthrough=3 in the kernel build by default. As long as that option generates a pile of warnings, developers will ignore them and nobody will notice when new ones are added. Once the kernel is warning-free, though, it should be possible to keep it that way.
This work, which was supported by the Linux Foundation, has resulted in over 400 patches since 2017. As Silva has worked through the kernel source eliminating fall-through warnings, he has encountered (and fixed) a number of bugs. A quick search turns up the following commits, for example:
d64062b57eeb drm/amdgpu/gfx10: Fix missing break in switch statement 737298d18836 drm/amdkfd: Fix missing break in switch statement 60747828eac2 net: socket: Fix missing break in switch statement 84242b82d81c rtlwifi: rtl8723ae: Fix missing break in switch statement 7850b51b6c21 scsi: mpt3sas: Add missing breaks in switch statements 5e420fe63581 scsi: aacraid: Fix missing break in switch statement 09186e503486 security: mark expected switch fall-throughs and add a missing break b5be853181a8 crypto: ccree - fix missing break in switch statement 7264235ee74f IB/hfi1: Add missing break in switch statement cc5034a5d293 drm/radeon/evergreen_cs: fix missing break in switch statement 479826cc8611 staging: comedi: ni_660x: fix missing break in switch statement 5340f23df8fe gpio: sprd: Add missing break in switch statement df997abeebad iscsi_ibft: Fix missing break in switch statement 2f10d8237396 drm/amd/powerplay: Fix missing break in switch 307b00c5e695 rtl8xxxu: Fix missing break in switch 5d25ff7a5448 scsi: ips: fix missing break in switch a7ed5b3e7dca staging: comedi: tio: fix multiple missing break in switch bugs c24bfa8f21b5 spi: slave: Fix missing break in switch ad0eaee6195d ASoC: wm8994: Fix missing break in switch 9ba8376ce1e2 ptp: fix missing break in switch 4e57562b4846 iio: imu: inv_mpu6050: fix missing break in switch
The above list is clearly incomplete, though, since a number of the other fixes do not explicitly mention missing break statements in the subject line; see commit ed4ed4043a12, for example.
On July 27, a milestone of sorts was hit with this pull into the mainline from Silva's repository: the final implicit fall-through warning has been fixed, and -Wimplicit-fallthrough=3 is now the default for kernel builds. In other words, the kernel's coding style has just been tweaked to disallow the use of implicit fall-through in switch statements. It must be a satisfying conclusion to a long project.
Moving beyond comments
Naturally, though, some developers are not entirely happy with this
change. Nobody who actually works in the kernel community seems to
disagree with the idea of eliminating implicit fall-throughs, but the
mechanism used to do so does not sit well with everybody; as Peter Zijlstra put
it back in June: "I still consider it an abomination that the C
parser looks at comments
". Zijlstra, like others, would prefer that
there were another way.
As it happens, there is another way. GCC starting with version 7 allows the use of a special attribute on a statement instead:
__attribute__((fallthrough))
The use of this attribute can perhaps made more visually pleasing with a definition like:
#define __fallthrough __attribute__((fallthrough))
The __fallthrough symbol could then be used instead of the magic comments to eliminate implicit fall-through warnings. A patch set implementing this option was posted by Miguel Ojeda in October 2018; it generated a fair amount of interest but was not adopted for a couple of reasons: it doesn't work with older versions of GCC, and the LLVM Clang compiler does not implement that attribute at all. Since Clang does successfully build the kernel in some settings (and is evidently used for the Android build), breaking it is not an appealing option.
The good news is that the Clang developers appear to be working on implementing this attribute. They also, it seems, have an implementation aimed at the proposed next standard for the C language, currently called C2X. The bad news is that the syntax there is different:
[[fallthrough]]
If this version becomes an actual part of the C standard, it would probably be the preferable one to use going forward. But that may not be fully resolved for some time. In any case, the actual mechanism used to mark explicit fall-through behavior can be hidden behind a #define as shown above.
The one thing that can't be done is:
#define __fallthrough /* fall through */
since the preprocessor will simply delete the comment rather than substituting it into the source. So the comments have to remain for now. But one can imagine that, at some point in the future when a better alternative is available, those thousands of comments are likely to be replaced with something like __fallthrough in a set of massive, tree-wide patches.
Meanwhile, though, the kernel has finally reached a point where there are
no switch statements with implicit fall-through behavior, and the
build process has been amended to prevent such behavior from being added in
the future. One source of kernel bugs has been closed, a switch that can
only be seen as a best-case scenario.
Index entries for this article | |
---|---|
Kernel | Coding style |
Kernel | Security/Kernel hardening |
Security | Linux kernel/Hardening |
Posted Aug 2, 2019 2:16 UTC (Fri)
by scientes (guest, #83068)
[Link] (5 responses)
Posted Aug 2, 2019 10:30 UTC (Fri)
by jani (subscriber, #74547)
[Link] (3 responses)
Care to elaborate?
Posted Aug 2, 2019 10:38 UTC (Fri)
by mageta (subscriber, #89696)
[Link] (2 responses)
Similar things can happen with other certain diagnostics that rely on seeing un-expanded macros (e.g.: -Wtautological-compare). They will complain, because Macros are already expanded in the files sent to the compute-node.
Posted Aug 2, 2019 12:55 UTC (Fri)
by mageta (subscriber, #89696)
[Link] (1 responses)
When building with distcc, I have been passing 'KCFLAGS=-Wno-tautological-compare' for a while now. This suppresses 95% of the complaints, but not every compiler-call honors KCFLAGS.. and I have been too lazy to track down why that is, its probably a bug.
I imagine one can work around the new warnings from the fall-through diagnostics in a similar way! Have not tested it yet though.
Posted Aug 2, 2019 18:20 UTC (Fri)
by scientes (guest, #83068)
[Link]
Posted Aug 2, 2019 13:44 UTC (Fri)
by mathstuf (subscriber, #69389)
[Link]
Posted Aug 2, 2019 6:01 UTC (Fri)
by neilbrown (subscriber, #359)
[Link]
Indeed ... including 3 "fall though" ( one "fallthough" was recently removed) 14 "Intentional fallthrough" and 10 "lint -fallthrough".
And in case the compiler is a bit deaf,
Archeology is fun!
Posted Aug 2, 2019 9:36 UTC (Fri)
by domo (guest, #14031)
[Link]
... So i've been wondering how it is possible it is documented and so on...
i've used #if GNUC >= 7 for __attribute__ define and empty define #else.
the hardest thing there has been the #define name.
Posted Aug 2, 2019 10:11 UTC (Fri)
by dgm (subscriber, #49227)
[Link] (3 responses)
/* fall through */
is OK, but
/* no break */
is superior (from the perspective of a non-native English speaker).
Posted Aug 2, 2019 13:04 UTC (Fri)
by Freeaqingme (subscriber, #103259)
[Link]
So for all intents and purposes they could call it 'foobar', and you'd simply learn what 'foobar' means in this context. Furthermore, I think it's a good practice to actually describe the behavior by what it does, not by what it doesn't do.
As a fun anecdote, the PHP language for a very long time had a T_PAAMAYIM_NEKUDOTAYIM token ('::'). Even though PHP uses primarily English terms, it was written by two Israeli men, who felt it was nice to put something of their own culture/heritage in the language. Undoubtedly it's a bit weird for an English programmer to get an error message 'unexpected T_PAAMAYIM_NEKUDOTAYIM', but after you Google it the first time you simply remember that you probably put some '::' somewhere where the interpreter did not expect it.
Posted Aug 2, 2019 21:35 UTC (Fri)
by edeloget (subscriber, #88392)
[Link] (1 responses)
Posted Aug 5, 2019 10:19 UTC (Mon)
by dgm (subscriber, #49227)
[Link]
;-P
Posted Aug 2, 2019 12:21 UTC (Fri)
by mtthu (subscriber, #123091)
[Link] (7 responses)
BTW: In C++17 two other attributes have been added as well: [[maybe_unused]] and [[nodiscard]]; C++20 adds [[likely]] and [[unlikely]]. Some might be useful in C as well, some not.
Posted Aug 10, 2019 12:04 UTC (Sat)
by danieljo (guest, #6161)
[Link] (6 responses)
Posted Aug 10, 2019 13:31 UTC (Sat)
by johill (subscriber, #25196)
[Link] (5 responses)
doesn't work one though - the preprocessor will remove the comment, rather than place it
Posted Aug 11, 2019 1:03 UTC (Sun)
by nybble41 (subscriber, #55106)
[Link] (4 responses)
Since GCC 7 there is a GCC attribute you could use in place of the comment:
Posted Nov 19, 2019 15:26 UTC (Tue)
by ballombe (subscriber, #9523)
[Link] (3 responses)
Posted Nov 21, 2019 16:04 UTC (Thu)
by mathstuf (subscriber, #69389)
[Link] (2 responses)
Do you have an example to show? Other than Duff's device (which I also wouldn't classify as "very common" since it's a stereotypical "what's this weird piece of C doing" snippet), what's the use case?
Posted Nov 24, 2019 22:06 UTC (Sun)
by ballombe (subscriber, #9523)
[Link] (1 responses)
finishing after a loop unrolling
for(i=l;i>=3;i-=4)
conversion
Posted Nov 25, 2019 16:35 UTC (Mon)
by mathstuf (subscriber, #69389)
[Link]
The first seems like abusing a C string for data storage. A better data structure seems relevant here. It would certainly make the code clearer.
> a.dim
I feel like this would be wrapped up better in a `checkvec` function in the first place. Though this is C; why is one abusing a vec4 to store vec3 or vec2 information (losing cache line benefits given the usual suspects for code using such structures).
> loop unrolling
Something I expect the compiler to be better at today than myself (pending benchmarks showing that the vectorization/unrolling isn't happening).
> conversion
Well, this is what you get with crappy type hierarchies :) . I think the normal way would be something like:
baz* p = NULL;
These are interesting use cases, but I still wouldn't classify them as "common" or even idiomatic.
Posted Aug 3, 2019 9:06 UTC (Sat)
by swilmet (subscriber, #98424)
[Link]
Posted Aug 4, 2019 20:38 UTC (Sun)
by rweikusat2 (subscriber, #117920)
[Link] (14 responses)
Posted Aug 4, 2019 21:10 UTC (Sun)
by mpr22 (subscriber, #60784)
[Link] (5 responses)
Posted Aug 5, 2019 14:16 UTC (Mon)
by rweikusat2 (subscriber, #117920)
[Link] (4 responses)
Posted Aug 5, 2019 14:53 UTC (Mon)
by karkhaz (subscriber, #99844)
[Link] (3 responses)
Since you only ever take (at most) one branch of an if statement, it makes sense that programmers expect the same behaviour from the cases of a switch statement. Unlearning this mental model is difficult because people rarely use goto statements and commonly use if statements. So for most people, switch statements are a syntactic sugar for if statements (removing the need to explicitly test the value of the expression for each branch), rather than being sugar for goto statements.
Posted Aug 5, 2019 15:50 UTC (Mon)
by rweikusat2 (subscriber, #117920)
[Link] (2 responses)
Posted Aug 5, 2019 18:40 UTC (Mon)
by rweikusat2 (subscriber, #117920)
[Link]
Posted Aug 8, 2019 3:03 UTC (Thu)
by Paf (subscriber, #91811)
[Link]
What behavior would constitute “fall through” in your view? Fall through simply means proceeding from one case to the next instead of breaking out. C does so unless told otherwise, so it’s implicit. The fact that the generated assembly is just a set of jumps to labels has no bearing on this higher level language observation, which is comparing C to other languages on the behavior of the switch statement.
Basically you’re saying C doesn’t have fall through at all, because a switch statement is just a set of gotos so we should stop talking about this. That’s certainly *a* perspective. It’s not an interesting or useful one and those of us who want to have a name for this behavior that refers to the higher level language are just going to keep talking about falling through cases.
Posted Aug 5, 2019 16:45 UTC (Mon)
by excors (subscriber, #95769)
[Link] (3 responses)
The conceptual gap here is between the switch being a set of mutually exclusive cases (which is how they're typically used, and how they're typically formatted with indentation and braces and blank lines), vs it being a sequence of statements with jumps (which is how it's implemented). After some decades of experience with C, observing all the bugs caused by missing breaks, people have got wise enough to recognise their limitations and are changing the language to reduce that gap.
Posted Aug 5, 2019 21:46 UTC (Mon)
by rweikusat2 (subscriber, #117920)
[Link] (2 responses)
A switch is a form of computed goto because that's what the language definition says. A "conceptual gap" only manifests itself here if people (wrongly) regard it as something different.
It can't really determine when this feature was implemented but judging from code I've seen, I think lint must have supported flagging absence of break in switch statements since some time in the 1980s and by then, it certainly wasn't based on "decades of experience", probably more on some kind of "This new and different! It must be terribly wrong!!" knee-jerk reaction from people used to something else.
Had Linux not managed fine without this feature for almost three decades, Google employees wouldn't be paid to retrofit it to a large code base.
Posted Aug 5, 2019 22:04 UTC (Mon)
by mjg59 (subscriber, #23239)
[Link] (1 responses)
Posted Aug 6, 2019 16:55 UTC (Tue)
by rweikusat2 (subscriber, #117920)
[Link]
Some analysis of the commits also supports this: The person who worked on this produced 415 commits relating to it. 392 were about adding comments to correct switches. 23 about adding missing breaks. This means that 94.45% of the commits didn't fix anything while 5.54% were bugfixes, presumably mostly to more or less dead code handling corner cases which never happen in practice as the missing breaks would have caused runtime failures otherwise. This gets even more interesting when looking at the number of switch-cases in the kernel code: There are 265,383 of them. Which means that 0.15% didn't have a break and in 0.009% of the cases, this turned out to be wrong. Applying the usual statistican's sleight-of-hand, IOW, assuming that relative frequencies in a particilar statistic are the same as probabilities of a certain outcome in a random selection, one can thus confidently state that "empirical evidence suggests that switch cases are used incorrectly 0.009% of the time!"
That's not my idea of "a serious problem" and even if this was different, I wouldn't consider "make switch harder to use" a sensible solution to it. YMMV.
Posted Aug 15, 2019 10:18 UTC (Thu)
by ksandstr (guest, #60862)
[Link] (3 responses)
The underlying problem here is that switch-case is, with seeming regularity, learned as "what C has for select-case in Visual Basic". That, like many common assumptions regarding C-family languages, is very wrong.
Thirdly, the compiler-oriented solution implies that review will actually compile submitted code and has both care and cojones to require warnings fixed. If that's the case, nice; but I'm not even convinced that any of these "fix fallthru warning" patches' cases were hit in a specific test first to determine that fallthru is actually the correct behaviour. Done wrong, that could be worse than changing nothing.
Posted Aug 15, 2019 17:34 UTC (Thu)
by mpr22 (subscriber, #60784)
[Link] (2 responses)
It seems to me that "this case label has no statements between it and the next case label" is fairly readily distinguishable from "this case label has statements between it and the next case label, but those statements do not include a break; statement".
Posted Aug 15, 2019 20:29 UTC (Thu)
by mjg59 (subscriber, #23239)
[Link] (1 responses)
Posted Aug 16, 2019 15:02 UTC (Fri)
by nybble41 (subscriber, #55106)
[Link]
The "implicit fall-through for adjacent case labels" situation doesn't really exist, so there's no need for special handling in -Wimplicit-fallthrough. Labels apply to the following statement; when you have a list of labels with no intervening statements, every label in the list is associated with the same statement at the end, not the next label. You can never end up branching into the middle of a group of labels, so there is no fall-through.
Posted Aug 7, 2019 13:02 UTC (Wed)
by geert (subscriber, #98403)
[Link]
Of course we notice:
> Below is the list of build error/warning regressions/improvements in
https://lore.kernel.org/lkml/20190806071711.12294-1-geert...
Posted Aug 7, 2019 13:05 UTC (Wed)
by geert (subscriber, #98403)
[Link]
https://lore.kernel.org/lkml/CAMuHMdWAboq1YxVVJUop0woJTca...
Posted Aug 16, 2019 17:22 UTC (Fri)
by mirabilos (subscriber, #84359)
[Link]
The (GNU) C præprocessor and GCC also have the option -CC, which is “generally used to support lint comments” but has potentially disturbing side effects. It could be used to define a macro expanding to a comment, though.
> Miguel Ojeda
distcc
distcc
distcc
distcc
> Miguel Ojeda
An end to implicit fall-throughs in the kernel
/* !!!!!!!!! Fall Through !!!!!!!!!!!!! */
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
One annoying thing about then [[fallthrouh]] syntax is that it would still need to be hidden behind something like
An end to implicit fall-throughs in the kernel
#if COMPILER_SUPPORT
# define __fallthrough [[fallthrough]]
#else
# define __fallthrough /*something else*/
#endif
since the following is not valid C and a "raw" [[fallthrough]] is difficult to hide from older compilers.
#if NO_SUPPORT
# define [[fallthrough]] /*something else*/
#endif
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
#if !defined(__fallthrough) && defined(__has_cpp_attribute)
#if __has_cpp_attribute(fallthrough)
#define __fallthrough [[fallthrough]]
#elif __has_cpp_attribute(gnu::fallthrough)
#define __fallthrough [[gnu::fallthrough]]
#endif
#endif
#if !defined(__fallthrough) && defined(__has_attribute)
#if __has_attribute(__fallthrough__)
#define __fallthrough __attribute__((__fallthrough__))
#endif
#endif
#if !defined(__fallthrough) /* well, we tried */
#define __fallthrough ((void)0)
#endif
An end to implicit fall-throughs in the kernel
adding 'fall through' at each line instead of once is counterproductive.
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
switch (strlen(s)-1)
{
case 3: a[3] = cvt3(s[3]);
case 2: a[2] = cvt2(s[2]);
case 1: a[1] = cvt1(s[1]);
case 0: a[0] = cvt0(s[0]);
}
or
switch(a.dim)
{
case 4: checkt(a.t);
case 3: checkz(a.z);
case 2: checky(a.y);
case 1: checkx(a.x);
}
{ f(i); f(i-1);f(i-2);f(i-3);}
switch(i)
{
case 3: f(3);
case 2: f(2);
case 1: f(1);
case 0: f(0);
}
switch(type(z))
{
case FOO: z = FOO_to_BAR(z);
case BAR: z = BAR_to_BAZ(z);
case BAZ: return process_BAZ(z);
}
An end to implicit fall-throughs in the kernel
switch(type(z)) // Assuming `z` is some kind of tagged union structure.
{
case FOO: p = &z->foo.baz; break;
case BAR: p = &z->bar.baz; break;
case BAZ: p = &z->baz;
}
if (p) process_BAZ(p);
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
Try reading the C language definition. The relevant part would seem to be
An end to implicit fall-throughs in the kernel
A switch statement causes control to jump to, into, or past the statement that is the
switch body, depending on the value of a controlling expression, and on the presence of a
default label and the values of any case labels on or in the switch body. A case or
default label is accessible only within the closest enclosing switch statement.
It's just nonsense to refer to the sequential execution of sequence of statemens as "implicit fallthrough" when thinking of C control structures.
An end to implicit fall-throughs in the kernel
I would suspect assumptions based on how multiway conditionals work in other languages, eg, Pascal or the Bourne shell language, here. Such a construct can be emulated in C by combining switch and break but it doesn't have one. OTOH, a C switch can be used to do things which can't be done with a multiway conditional, eg,
An end to implicit fall-throughs in the kernel
switch ((uintptr_t)p & 3) {
case 3:
*p++ = c;
case 2:
*p++ = c;
case 1:
*p++ = c;
}
to align a pointer while storing some values (I'm aware that this is not standardized C).
One could argue that such cases are rare and that a proper multiway conditional had been a better idea.
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel
Nobody will notice when new warnings are added...
> v5.3-rc3[1] compared to v5.2[2].
>
> 133 warning regressions:
An end to implicit fall-throughs in the kernel
An end to implicit fall-throughs in the kernel