Skip to content

Commit 543f112

Browse files
authored
[C] Add -Wjump-bypasses-init (#138009)
We already diagnose when a jump bypasses initialization in C++ because such code is ill-formed there. However, we were not using this to diagnose those same jumps in C. -Wjump-bypasses-init is grouped under -Wc++-compat and diagnoses this situation as a compatibility issue with C++. This diagnostic is off by default. The diagnostic could perhaps be enabled by default for C, but due to the design of jump diagnostic handling, it not a trivial task. For now, we'll add the diagnostic as off-by-default so we get incremental improvement, but a follow-up could try to refactor jump diagnostics so we can enable this by default in C and have it as a C++ compatibility diagnostic as well.
1 parent 7d01b85 commit 543f112

File tree

6 files changed

+130
-31
lines changed

6 files changed

+130
-31
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,10 @@ C Language Changes
203203
``-Wunterminated-string-initialization``. However, this diagnostic is not
204204
silenced by the ``nonstring`` attribute as these initializations are always
205205
incompatible with C++.
206+
- Added ``-Wjump-bypasses-init``, which is off by default and grouped under
207+
``-Wc++-compat``. It diagnoses when a jump (``goto`` to its label, ``switch``
208+
to its ``case``) will bypass the initialization of a local variable, which is
209+
invalid in C++.
206210
- Added the existing ``-Wduplicate-decl-specifier`` diagnostic, which is on by
207211
default, to ``-Wc++-compat`` because duplicated declaration specifiers are
208212
not valid in C++.

clang/include/clang/Basic/DiagnosticGroups.td

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,12 @@ def DefaultConstInit : DiagGroup<"default-const-init",
177177
def ImplicitVoidPtrCast : DiagGroup<"implicit-void-ptr-cast">;
178178
def ImplicitIntToEnumCast : DiagGroup<"implicit-int-enum-cast",
179179
[ImplicitEnumEnumCast]>;
180+
def JumpBypassesInit : DiagGroup<"jump-bypasses-init">;
180181
def TentativeDefnCompat : DiagGroup<"tentative-definition-compat">;
181182
def CXXCompat: DiagGroup<"c++-compat", [ImplicitVoidPtrCast, DefaultConstInit,
182-
ImplicitIntToEnumCast, CppKeywordInC,
183-
HiddenCppDecl, TentativeDefnCompat,
184-
InitStringTooLongForCpp,
183+
ImplicitIntToEnumCast, HiddenCppDecl,
184+
InitStringTooLongForCpp, CppKeywordInC,
185+
TentativeDefnCompat, JumpBypassesInit,
185186
DuplicateDeclSpecifier]>;
186187

187188
def ExternCCompat : DiagGroup<"extern-c-compat">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6561,18 +6561,28 @@ def ext_goto_into_protected_scope : ExtWarn<
65616561
def warn_cxx98_compat_goto_into_protected_scope : Warning<
65626562
"jump from this goto statement to its label is incompatible with C++98">,
65636563
InGroup<CXX98Compat>, DefaultIgnore;
6564+
def warn_cpp_compat_goto_into_protected_scope : Warning<
6565+
"jump from this goto statement to its label is incompatible with C++">,
6566+
InGroup<JumpBypassesInit>, DefaultIgnore;
65646567
def err_switch_into_protected_scope : Error<
65656568
"cannot jump from switch statement to this case label">;
65666569
def warn_cxx98_compat_switch_into_protected_scope : Warning<
65676570
"jump from switch statement to this case label is incompatible with C++98">,
65686571
InGroup<CXX98Compat>, DefaultIgnore;
6572+
def warn_cpp_compat_switch_into_protected_scope : Warning<
6573+
"jump from switch statement to this case label is incompatible with C++">,
6574+
InGroup<JumpBypassesInit>, DefaultIgnore;
65696575
def err_indirect_goto_without_addrlabel : Error<
65706576
"indirect goto in function with no address-of-label expressions">;
65716577
def err_indirect_goto_in_protected_scope : Error<
65726578
"cannot jump from this %select{indirect|asm}0 goto statement to one of its possible targets">;
65736579
def warn_cxx98_compat_indirect_goto_in_protected_scope : Warning<
65746580
"jump from this %select{indirect|asm}0 goto statement to one of its possible targets "
65756581
"is incompatible with C++98">, InGroup<CXX98Compat>, DefaultIgnore;
6582+
def warn_cpp_compat_indirect_goto_in_protected_scope : Warning<
6583+
"jump from this %select{indirect|asm}0 goto statement to one of its possible "
6584+
"targets is incompatible with C++">,
6585+
InGroup<JumpBypassesInit>, DefaultIgnore;
65766586
def note_indirect_goto_target : Note<
65776587
"possible target of %select{indirect|asm}0 goto statement">;
65786588
def note_protected_by_variable_init : Note<

clang/lib/Sema/JumpDiagnostics.cpp

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class JumpScopeChecker {
9393
unsigned TargetScope);
9494
void CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
9595
unsigned JumpDiag, unsigned JumpDiagWarning,
96-
unsigned JumpDiagCXX98Compat);
96+
unsigned JumpDiagCompat);
9797
void CheckGotoStmt(GotoStmt *GS);
9898
const Attr *GetMustTailAttr(AttributedStmt *AS);
9999

@@ -179,9 +179,8 @@ static ScopePair GetDiagForGotoScopeDecl(Sema &S, const Decl *D) {
179179
}
180180
}
181181

182-
if (const Expr *Init = VD->getInit(); S.Context.getLangOpts().CPlusPlus &&
183-
VD->hasLocalStorage() && Init &&
184-
!Init->containsErrors()) {
182+
if (const Expr *Init = VD->getInit();
183+
VD->hasLocalStorage() && Init && !Init->containsErrors()) {
185184
// C++11 [stmt.dcl]p3:
186185
// A program that jumps from a point where a variable with automatic
187186
// storage duration is not in scope to a point where it is in scope
@@ -680,7 +679,9 @@ void JumpScopeChecker::VerifyJumps() {
680679
CheckJump(GS, GS->getLabel()->getStmt(), GS->getGotoLoc(),
681680
diag::err_goto_into_protected_scope,
682681
diag::ext_goto_into_protected_scope,
683-
diag::warn_cxx98_compat_goto_into_protected_scope);
682+
S.getLangOpts().CPlusPlus
683+
? diag::warn_cxx98_compat_goto_into_protected_scope
684+
: diag::warn_cpp_compat_goto_into_protected_scope);
684685
}
685686
CheckGotoStmt(GS);
686687
continue;
@@ -708,7 +709,9 @@ void JumpScopeChecker::VerifyJumps() {
708709
CheckJump(IGS, Target->getStmt(), IGS->getGotoLoc(),
709710
diag::err_goto_into_protected_scope,
710711
diag::ext_goto_into_protected_scope,
711-
diag::warn_cxx98_compat_goto_into_protected_scope);
712+
S.getLangOpts().CPlusPlus
713+
? diag::warn_cxx98_compat_goto_into_protected_scope
714+
: diag::warn_cpp_compat_goto_into_protected_scope);
712715
continue;
713716
}
714717

@@ -725,7 +728,9 @@ void JumpScopeChecker::VerifyJumps() {
725728
else
726729
Loc = SC->getBeginLoc();
727730
CheckJump(SS, SC, Loc, diag::err_switch_into_protected_scope, 0,
728-
diag::warn_cxx98_compat_switch_into_protected_scope);
731+
S.getLangOpts().CPlusPlus
732+
? diag::warn_cxx98_compat_switch_into_protected_scope
733+
: diag::warn_cpp_compat_switch_into_protected_scope);
729734
}
730735
}
731736
}
@@ -867,6 +872,13 @@ static bool IsCXX98CompatWarning(Sema &S, unsigned InDiagNote) {
867872
InDiagNote == diag::note_protected_by_variable_non_pod;
868873
}
869874

875+
/// Returns true if a particular note should be a C++ compatibility warning in
876+
/// C mode with -Wc++-compat.
877+
static bool IsCppCompatWarning(Sema &S, unsigned InDiagNote) {
878+
return !S.getLangOpts().CPlusPlus &&
879+
InDiagNote == diag::note_protected_by_variable_init;
880+
}
881+
870882
/// Produce primary diagnostic for an indirect jump statement.
871883
static void DiagnoseIndirectOrAsmJumpStmt(Sema &S, Stmt *Jump,
872884
LabelDecl *Target, bool &Diagnosed) {
@@ -906,34 +918,43 @@ void JumpScopeChecker::DiagnoseIndirectOrAsmJump(Stmt *Jump, unsigned JumpScope,
906918
S.Diag(Scopes[I].Loc, Scopes[I].OutDiag);
907919
}
908920

909-
SmallVector<unsigned, 10> ToScopesCXX98Compat;
921+
SmallVector<unsigned, 10> ToScopesCXX98Compat, ToScopesCppCompat;
910922

911923
// Now walk into the scopes containing the label whose address was taken.
912924
for (unsigned I = TargetScope; I != Common; I = Scopes[I].ParentScope)
913925
if (IsCXX98CompatWarning(S, Scopes[I].InDiag))
914926
ToScopesCXX98Compat.push_back(I);
927+
else if (IsCppCompatWarning(S, Scopes[I].InDiag))
928+
ToScopesCppCompat.push_back(I);
915929
else if (Scopes[I].InDiag) {
916930
DiagnoseIndirectOrAsmJumpStmt(S, Jump, Target, Diagnosed);
917931
S.Diag(Scopes[I].Loc, Scopes[I].InDiag);
918932
}
919933

920-
// Diagnose this jump if it would be ill-formed in C++98.
921-
if (!Diagnosed && !ToScopesCXX98Compat.empty()) {
934+
// Diagnose this jump if it would be ill-formed in C++[98].
935+
if (!Diagnosed) {
922936
bool IsAsmGoto = isa<GCCAsmStmt>(Jump);
923-
S.Diag(Jump->getBeginLoc(),
924-
diag::warn_cxx98_compat_indirect_goto_in_protected_scope)
925-
<< IsAsmGoto;
926-
S.Diag(Target->getStmt()->getIdentLoc(), diag::note_indirect_goto_target)
927-
<< IsAsmGoto;
928-
NoteJumpIntoScopes(ToScopesCXX98Compat);
937+
auto Diag = [&](unsigned DiagId, const SmallVectorImpl<unsigned> &Notes) {
938+
S.Diag(Jump->getBeginLoc(), DiagId) << IsAsmGoto;
939+
S.Diag(Target->getStmt()->getIdentLoc(), diag::note_indirect_goto_target)
940+
<< IsAsmGoto;
941+
NoteJumpIntoScopes(Notes);
942+
};
943+
if (!ToScopesCXX98Compat.empty())
944+
Diag(diag::warn_cxx98_compat_indirect_goto_in_protected_scope,
945+
ToScopesCXX98Compat);
946+
else if (!ToScopesCppCompat.empty())
947+
Diag(diag::warn_cpp_compat_indirect_goto_in_protected_scope,
948+
ToScopesCppCompat);
929949
}
930950
}
931951

932952
/// CheckJump - Validate that the specified jump statement is valid: that it is
933953
/// jumping within or out of its current scope, not into a deeper one.
934954
void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
935-
unsigned JumpDiagError, unsigned JumpDiagWarning,
936-
unsigned JumpDiagCXX98Compat) {
955+
unsigned JumpDiagError,
956+
unsigned JumpDiagWarning,
957+
unsigned JumpDiagCompat) {
937958
if (CHECK_PERMISSIVE(!LabelAndGotoScopes.count(From)))
938959
return;
939960
if (CHECK_PERMISSIVE(!LabelAndGotoScopes.count(To)))
@@ -973,15 +994,16 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
973994
if (CommonScope == ToScope) return;
974995

975996
// Pull out (and reverse) any scopes we might need to diagnose skipping.
976-
SmallVector<unsigned, 10> ToScopesCXX98Compat;
997+
SmallVector<unsigned, 10> ToScopesCompat;
977998
SmallVector<unsigned, 10> ToScopesError;
978999
SmallVector<unsigned, 10> ToScopesWarning;
9791000
for (unsigned I = ToScope; I != CommonScope; I = Scopes[I].ParentScope) {
9801001
if (S.getLangOpts().MSVCCompat && JumpDiagWarning != 0 &&
9811002
IsMicrosoftJumpWarning(JumpDiagError, Scopes[I].InDiag))
9821003
ToScopesWarning.push_back(I);
983-
else if (IsCXX98CompatWarning(S, Scopes[I].InDiag))
984-
ToScopesCXX98Compat.push_back(I);
1004+
else if (IsCXX98CompatWarning(S, Scopes[I].InDiag) ||
1005+
IsCppCompatWarning(S, Scopes[I].InDiag))
1006+
ToScopesCompat.push_back(I);
9851007
else if (Scopes[I].InDiag)
9861008
ToScopesError.push_back(I);
9871009
}
@@ -1001,10 +1023,10 @@ void JumpScopeChecker::CheckJump(Stmt *From, Stmt *To, SourceLocation DiagLoc,
10011023
NoteJumpIntoScopes(ToScopesError);
10021024
}
10031025

1004-
// Handle -Wc++98-compat warnings if the jump is well-formed.
1005-
if (ToScopesError.empty() && !ToScopesCXX98Compat.empty()) {
1006-
S.Diag(DiagLoc, JumpDiagCXX98Compat);
1007-
NoteJumpIntoScopes(ToScopesCXX98Compat);
1026+
// Handle -Wc++98-compat or -Wc++-compat warnings if the jump is well-formed.
1027+
if (ToScopesError.empty() && !ToScopesCompat.empty()) {
1028+
S.Diag(DiagLoc, JumpDiagCompat);
1029+
NoteJumpIntoScopes(ToScopesCompat);
10081030
}
10091031
}
10101032

clang/lib/Sema/SemaDecl.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13724,15 +13724,17 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, bool DirectInit) {
1372413724
return;
1372513725
}
1372613726

13727-
if (VDecl->hasLocalStorage())
13728-
setFunctionHasBranchProtectedScope();
13729-
1373013727
if (DiagnoseUnexpandedParameterPack(Init, UPPC_Initializer)) {
1373113728
VDecl->setInvalidDecl();
1373213729
return;
1373313730
}
1373413731
}
1373513732

13733+
// If the variable has an initializer and local storage, check whether
13734+
// anything jumps over the initialization.
13735+
if (VDecl->hasLocalStorage())
13736+
setFunctionHasBranchProtectedScope();
13737+
1373613738
// OpenCL 1.1 6.5.2: "Variables allocated in the __local address space inside
1373713739
// a kernel function cannot be initialized."
1373813740
if (VDecl->getType().getAddressSpace() == LangAS::opencl_local) {
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// RUN: %clang_cc1 -fsyntax-only -verify=c,both -Wjump-bypasses-init %s
2+
// RUN: %clang_cc1 -fsyntax-only -verify=c,both -Wc++-compat %s
3+
// RUN: %clang_cc1 -fsyntax-only -verify=good %s
4+
// RUN: %clang_cc1 -fsyntax-only -verify=cxx,both -x c++ %s
5+
// good-no-diagnostics
6+
7+
void goto_func_1(void) {
8+
goto ouch; // c-warning {{jump from this goto statement to its label is incompatible with C++}} \
9+
cxx-error {{cannot jump from this goto statement to its label}}
10+
int i = 12; // both-note {{jump bypasses variable initialization}}
11+
12+
ouch:
13+
;
14+
}
15+
16+
void goto_func_2(void) {
17+
goto ouch;
18+
static int i = 12; // This initialization is not jumped over, so no warning.
19+
20+
ouch:
21+
;
22+
}
23+
24+
void switch_func(int i) {
25+
switch (i) {
26+
int x = 12; // both-note {{jump bypasses variable initialization}}
27+
case 0: // c-warning {{jump from switch statement to this case label is incompatible with C++}} \
28+
cxx-error {{cannot jump from switch statement to this case label}}
29+
break;
30+
}
31+
}
32+
33+
// Statement expressions are a bit strange in that they seem to allow for
34+
// jumping past initialization without being diagnosed, even in C++. Perhaps
35+
// this should change?
36+
void f(void) {
37+
({
38+
goto ouch;
39+
int i = 12;
40+
});
41+
42+
for (int i = ({ goto ouch; int x = 10; x;}); i < 0; ++i) {
43+
}
44+
45+
ouch:
46+
;
47+
}
48+
49+
void indirect(int n) {
50+
DirectJump:
51+
;
52+
53+
void *Table[] = {&&DirectJump, &&Later};
54+
goto *Table[n]; // c-warning {{jump from this indirect goto statement to one of its possible targets is incompatible with C++}} \
55+
cxx-error {{cannot jump from this indirect goto statement to one of its possible targets}}
56+
57+
int x = 12; // both-note {{jump bypasses variable initialization}}
58+
Later: // both-note {{possible target of indirect goto statement}}
59+
;
60+
}

0 commit comments

Comments
 (0)