From e0d3b4cd2b66f1cef26cacbed5820ab7c22ad5b3 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 3 May 2012 18:27:39 +0000 Subject: [PATCH] Add -Wimplicit-fallthrough warning flag, which warns on fallthrough between cases in switch statements. Also add a [[clang::fallthrough]] attribute, which can be used to suppress the warning in the case of intentional fallthrough. Patch by Alexander Kornienko! The handling of C++11 attribute namespaces in this patch is temporary, and will be replaced with a cleaner mechanism in a subsequent patch. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@156086 91177308-0d34-0410-b5e6-96231b3b80d8 --- docs/LanguageExtensions.html | 54 +++++ include/clang/Basic/Attr.td | 5 + include/clang/Basic/DiagnosticGroups.td | 1 + include/clang/Basic/DiagnosticSemaKinds.td | 25 +++ include/clang/Sema/AttributeList.h | 6 +- lib/Analysis/CFG.cpp | 3 - lib/Parse/ParseDeclCXX.cpp | 40 ++-- lib/Sema/AnalysisBasedWarnings.cpp | 193 +++++++++++++++++- lib/Sema/AttributeList.cpp | 9 +- lib/Sema/SemaStmtAttr.cpp | 34 ++- .../switch-implicit-fallthrough-cxx98.cpp | 119 +++++++++++ test/SemaCXX/switch-implicit-fallthrough.cpp | 177 ++++++++++++++++ 12 files changed, 635 insertions(+), 31 deletions(-) create mode 100644 test/SemaCXX/switch-implicit-fallthrough-cxx98.cpp create mode 100644 test/SemaCXX/switch-implicit-fallthrough.cpp diff --git a/docs/LanguageExtensions.html b/docs/LanguageExtensions.html index ca3ca2a89f..0952fa0469 100644 --- a/docs/LanguageExtensions.html +++ b/docs/LanguageExtensions.html @@ -103,6 +103,11 @@
  • __sync_swap
  • +
  • Non-standard C++11 Attributes + +
  • Target-Specific Extensions + +

    Non-standard C++11 Attributes

    + + +

    Clang supports one non-standard C++11 attribute. It resides in clang +namespace.

    + + +

    The clang::fallthrough attribute

    + + +

    The clang::fallthrough attribute is used along with +-Wimplicit-fallthrough diagnostic to annotate intentional fall-through +between switch labels. It can only be applied to a null statement placed in a +point of execution between any statement and the next switch label. It is common +to mark these places with a specific comment, but this attribute is meant to +replace comments with a more strict annotation, which can be checked by the +compiler. This attribute doesn't change semantics of the code and can be used +wherever an intended fall-through occurs, but it is designed to mimic +control-flow statements like break; so it can be placed in most places +where break; can, but only if there are no statements on execution path +between it and the next switch label.

    +

    Here is an example:

    +
    +// compile with -Wimplicit-fallthrough
    +switch (n) {
    +case 33:
    +  f();
    +case 44:  // warning: unannotated fall-through
    +  g();
    +  [[clang::fallthrough]];
    +case 55:  // no warning
    +  if (x) {
    +    h();
    +    break;
    +  }
    +  else {
    +    i();
    +    [[clang::fallthrough]];
    +  }
    +case 66:  // no warning
    +  p();
    +  [[clang::fallthrough]];  // warning: fallthrough annotation does not directly
    +                           //          preceed case label
    +  q();
    +case 77:  // warning: unannotated fall-through
    +  r();
    +}
    +

    Target-Specific Extensions

    diff --git a/include/clang/Basic/Attr.td b/include/clang/Basic/Attr.td index 3638deaa4c..3d2bebb6f6 100644 --- a/include/clang/Basic/Attr.td +++ b/include/clang/Basic/Attr.td @@ -313,6 +313,11 @@ def ExtVectorType : Attr { let ASTNode = 0; } +def FallThrough : Attr { + let Spellings = ["clang___fallthrough"]; + let Subjects = [CaseStmt, DefaultStmt]; +} + def FastCall : InheritableAttr { let Spellings = ["fastcall", "__fastcall"]; } diff --git a/include/clang/Basic/DiagnosticGroups.td b/include/clang/Basic/DiagnosticGroups.td index 234412af92..17852ae1e1 100644 --- a/include/clang/Basic/DiagnosticGroups.td +++ b/include/clang/Basic/DiagnosticGroups.td @@ -208,6 +208,7 @@ def MethodDuplicate : DiagGroup<"duplicate-method-match">; def CoveredSwitchDefault : DiagGroup<"covered-switch-default">; def SwitchEnum : DiagGroup<"switch-enum">; def Switch : DiagGroup<"switch">; +def ImplicitFallthrough : DiagGroup<"implicit-fallthrough">; def Trigraphs : DiagGroup<"trigraphs">; def : DiagGroup<"type-limits">; diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index 31fb03c2e4..4af3af1da8 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -5246,6 +5246,31 @@ def warn_missing_cases : Warning< "%0 enumeration values not handled in switch: %1, %2, %3...">, InGroup; +def warn_unannotated_fallthrough : Warning< + "unannotated fall-through between switch labels">, + InGroup, DefaultIgnore; +def note_insert_fallthrough_fixit : Note< + "insert '[[clang::fallthrough]];' to silence this warning">, + InGroup, DefaultIgnore; +def note_insert_break_fixit : Note< + "insert 'break;' to avoid fall-through">, + InGroup, DefaultIgnore; +def err_fallthrough_attr_wrong_target : Error< + "clang::fallthrough attribute is only allowed on empty statements">, + InGroup; +def note_fallthrough_insert_semi_fixit : Note< + "did you forget ';'?">, + InGroup; +def err_fallthrough_attr_outside_switch : Error< + "fallthrough annotation is outside switch statement">, + InGroup; +def warn_fallthrough_attr_invalid_placement : Warning< + "fallthrough annotation does not directly precede switch label">, + InGroup; +def warn_fallthrough_attr_unreachable : Warning< + "fallthrough annotation in unreachable code">, + InGroup; + def warn_unreachable_default : Warning< "default label in switch which covers all enumeration values">, InGroup, DefaultIgnore; diff --git a/include/clang/Sema/AttributeList.h b/include/clang/Sema/AttributeList.h index 4cd0f75d62..ef6cf1c09e 100644 --- a/include/clang/Sema/AttributeList.h +++ b/include/clang/Sema/AttributeList.h @@ -131,7 +131,7 @@ private: UsedAsTypeAttr(false), IsAvailability(false), NextInPosition(0), NextInPool(0) { if (numArgs) memcpy(getArgsBuffer(), args, numArgs * sizeof(Expr*)); - AttrKind = getKind(getName()); + AttrKind = getKind(getName(), getScopeName()); } AttributeList(IdentifierInfo *attrName, SourceRange attrRange, @@ -152,7 +152,7 @@ private: new (&getAvailabilitySlot(IntroducedSlot)) AvailabilityChange(introduced); new (&getAvailabilitySlot(DeprecatedSlot)) AvailabilityChange(deprecated); new (&getAvailabilitySlot(ObsoletedSlot)) AvailabilityChange(obsoleted); - AttrKind = getKind(getName()); + AttrKind = getKind(getName(), getScopeName()); } friend class AttributePool; @@ -188,7 +188,7 @@ public: void setUsedAsTypeAttr() { UsedAsTypeAttr = true; } Kind getKind() const { return Kind(AttrKind); } - static Kind getKind(const IdentifierInfo *Name); + static Kind getKind(const IdentifierInfo *Name, const IdentifierInfo *Scope); AttributeList *getNext() const { return NextInPosition; } void setNext(AttributeList *N) { NextInPosition = N; } diff --git a/lib/Analysis/CFG.cpp b/lib/Analysis/CFG.cpp index 61c2a8709d..9a50578c4f 100644 --- a/lib/Analysis/CFG.cpp +++ b/lib/Analysis/CFG.cpp @@ -1070,9 +1070,6 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) { case Stmt::LambdaExprClass: return VisitLambdaExpr(cast(S), asc); - case Stmt::AttributedStmtClass: - return Visit(cast(S)->getSubStmt(), asc); - case Stmt::MemberExprClass: return VisitMemberExpr(cast(S), asc); diff --git a/lib/Parse/ParseDeclCXX.cpp b/lib/Parse/ParseDeclCXX.cpp index 3f06919bf6..2a64141353 100644 --- a/lib/Parse/ParseDeclCXX.cpp +++ b/lib/Parse/ParseDeclCXX.cpp @@ -2870,28 +2870,30 @@ void Parser::ParseCXX11AttributeSpecifier(ParsedAttributes &attrs, } bool AttrParsed = false; - // No scoped names are supported; ideally we could put all non-standard - // attributes into namespaces. - if (!ScopeName) { - switch (AttributeList::getKind(AttrName)) { - // No arguments - case AttributeList::AT_carries_dependency: - case AttributeList::AT_noreturn: { - if (Tok.is(tok::l_paren)) { - Diag(Tok.getLocation(), diag::err_cxx11_attribute_forbids_arguments) - << AttrName->getName(); - break; - } - - attrs.addNew(AttrName, AttrLoc, 0, AttrLoc, 0, - SourceLocation(), 0, 0, false, true); - AttrParsed = true; + switch (AttributeList::getKind(AttrName, ScopeName)) { + // No arguments + case AttributeList::AT_carries_dependency: + // FIXME: implement generic support of attributes with C++11 syntax + // see Parse/ParseDecl.cpp: ParseGNUAttributes + case AttributeList::AT_clang___fallthrough: + case AttributeList::AT_noreturn: { + if (Tok.is(tok::l_paren)) { + Diag(Tok.getLocation(), diag::err_cxx11_attribute_forbids_arguments) + << AttrName->getName(); break; } - // Silence warnings - default: break; - } + attrs.addNew(AttrName, + SourceRange(ScopeLoc.isValid() ? ScopeLoc : AttrLoc, + AttrLoc), + ScopeName, ScopeLoc, 0, + SourceLocation(), 0, 0, false, true); + AttrParsed = true; + break; + } + + // Silence warnings + default: break; } // Skip the entire parameter clause, if any diff --git a/lib/Sema/AnalysisBasedWarnings.cpp b/lib/Sema/AnalysisBasedWarnings.cpp index c3b802e4db..6e93030382 100644 --- a/lib/Sema/AnalysisBasedWarnings.cpp +++ b/lib/Sema/AnalysisBasedWarnings.cpp @@ -27,6 +27,7 @@ #include "clang/AST/StmtCXX.h" #include "clang/AST/EvaluatedExprVisitor.h" #include "clang/AST/StmtVisitor.h" +#include "clang/AST/RecursiveASTVisitor.h" #include "clang/Analysis/AnalysisContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/Analyses/ReachableCode.h" @@ -42,7 +43,9 @@ #include "llvm/ADT/StringRef.h" #include "llvm/Support/Casting.h" #include +#include #include +#include using namespace clang; @@ -522,6 +525,188 @@ static bool DiagnoseUninitializedUse(Sema &S, const VarDecl *VD, return true; } +namespace { + class FallthroughMapper : public RecursiveASTVisitor { + public: + FallthroughMapper(Sema &S) + : FoundSwitchStatements(false), + S(S) { + } + + bool foundSwitchStatements() const { return FoundSwitchStatements; } + + void markFallthroughVisited(const AttributedStmt *Stmt) { + bool Found = FallthroughStmts.erase(Stmt); + assert(Found); + } + + typedef llvm::SmallPtrSet AttrStmts; + + const AttrStmts &getFallthroughStmts() const { + return FallthroughStmts; + } + + bool checkFallThroughIntoBlock(const CFGBlock &B, int &AnnotatedCnt) { + int UnannotatedCnt = 0; + AnnotatedCnt = 0; + + std::deque BlockQueue; + + std::copy(B.pred_begin(), B.pred_end(), std::back_inserter(BlockQueue)); + + while (!BlockQueue.empty()) { + const CFGBlock *P = BlockQueue.front(); + BlockQueue.pop_front(); + + const Stmt *Term = P->getTerminator(); + if (Term && isa(Term)) + continue; // Switch statement, good. + + const SwitchCase *SW = dyn_cast_or_null(P->getLabel()); + if (SW && SW->getSubStmt() == B.getLabel() && P->begin() == P->end()) + continue; // Previous case label has no statements, good. + + if (P->pred_begin() == P->pred_end()) { // The block is unreachable. + // This only catches trivially unreachable blocks. + for (CFGBlock::const_iterator ElIt = P->begin(), ElEnd = P->end(); + ElIt != ElEnd; ++ElIt) { + if (const CFGStmt *CS = ElIt->getAs()){ + if (const AttributedStmt *AS = asFallThroughAttr(CS->getStmt())) { + S.Diag(AS->getLocStart(), + diag::warn_fallthrough_attr_unreachable); + markFallthroughVisited(AS); + ++AnnotatedCnt; + } + // Don't care about other unreachable statements. + } + } + // If there are no unreachable statements, this may be a special + // case in CFG: + // case X: { + // A a; // A has a destructor. + // break; + // } + // // <<<< This place is represented by a 'hanging' CFG block. + // case Y: + continue; + } + + const Stmt *LastStmt = getLastStmt(*P); + if (const AttributedStmt *AS = asFallThroughAttr(LastStmt)) { + markFallthroughVisited(AS); + ++AnnotatedCnt; + continue; // Fallthrough annotation, good. + } + + if (!LastStmt) { // This block contains no executable statements. + // Traverse its predecessors. + std::copy(P->pred_begin(), P->pred_end(), + std::back_inserter(BlockQueue)); + continue; + } + + ++UnannotatedCnt; + } + return !!UnannotatedCnt; + } + + // RecursiveASTVisitor setup. + bool shouldWalkTypesOfTypeLocs() const { return false; } + + bool VisitAttributedStmt(AttributedStmt *S) { + if (asFallThroughAttr(S)) + FallthroughStmts.insert(S); + return true; + } + + bool VisitSwitchStmt(SwitchStmt *S) { + FoundSwitchStatements = true; + return true; + } + + private: + + static const AttributedStmt *asFallThroughAttr(const Stmt *S) { + if (const AttributedStmt *AS = dyn_cast_or_null(S)) { + if (hasSpecificAttr(AS->getAttrs())) + return AS; + } + return 0; + } + + static const Stmt *getLastStmt(const CFGBlock &B) { + if (const Stmt *Term = B.getTerminator()) + return Term; + for (CFGBlock::const_reverse_iterator ElemIt = B.rbegin(), + ElemEnd = B.rend(); + ElemIt != ElemEnd; ++ElemIt) { + if (const CFGStmt *CS = ElemIt->getAs()) + return CS->getStmt(); + } + // Workaround to detect a statement thrown out by CFGBuilder: + // case X: {} case Y: + // case X: ; case Y: + if (const SwitchCase *SW = dyn_cast_or_null(B.getLabel())) + if (!isa(SW->getSubStmt())) + return SW->getSubStmt(); + + return 0; + } + + bool FoundSwitchStatements; + AttrStmts FallthroughStmts; + Sema &S; + }; +} + +static void DiagnoseSwitchLabelsFallthrough(Sema &S, AnalysisDeclContext &AC) { + FallthroughMapper FM(S); + FM.TraverseStmt(AC.getBody()); + + if (!FM.foundSwitchStatements()) + return; + + CFG *Cfg = AC.getCFG(); + + if (!Cfg) + return; + + int AnnotatedCnt; + + for (CFG::reverse_iterator I = Cfg->rbegin(), E = Cfg->rend(); I != E; ++I) { + const CFGBlock &B = **I; + const Stmt *Label = B.getLabel(); + + if (!Label || !isa(Label)) + continue; + + if (!FM.checkFallThroughIntoBlock(B, AnnotatedCnt)) + continue; + + S.Diag(Label->getLocStart(), diag::warn_unannotated_fallthrough); + + if (!AnnotatedCnt) { + SourceLocation L = Label->getLocStart(); + if (L.isMacroID()) + continue; + if (S.getLangOpts().CPlusPlus0x) { + S.Diag(L, diag::note_insert_fallthrough_fixit) << + FixItHint::CreateInsertion(L, "[[clang::fallthrough]]; "); + } + S.Diag(L, diag::note_insert_break_fixit) << + FixItHint::CreateInsertion(L, "break; "); + } + } + + const FallthroughMapper::AttrStmts &Fallthroughs = FM.getFallthroughStmts(); + for (FallthroughMapper::AttrStmts::const_iterator I = Fallthroughs.begin(), + E = Fallthroughs.end(); + I != E; ++I) { + S.Diag((*I)->getLocStart(), diag::warn_fallthrough_attr_invalid_placement); + } + +} + typedef std::pair UninitUse; namespace { @@ -861,7 +1046,8 @@ AnalysisBasedWarnings::IssueWarnings(sema::AnalysisBasedWarnings::Policy P, .setAlwaysAdd(Stmt::CStyleCastExprClass) .setAlwaysAdd(Stmt::DeclRefExprClass) .setAlwaysAdd(Stmt::ImplicitCastExprClass) - .setAlwaysAdd(Stmt::UnaryOperatorClass); + .setAlwaysAdd(Stmt::UnaryOperatorClass) + .setAlwaysAdd(Stmt::AttributedStmtClass); } // Construct the analysis context with the specified CFG build options. @@ -973,6 +1159,11 @@ AnalysisBasedWarnings::IssueWarnings(sema::AnalysisBasedWarnings::Policy P, } } + if (Diags.getDiagnosticLevel(diag::warn_unannotated_fallthrough, + D->getLocStart()) != DiagnosticsEngine::Ignored) { + DiagnoseSwitchLabelsFallthrough(S, AC); + } + // Collect statistics about the CFG if it was built. if (S.CollectStats && AC.isCFGBuilt()) { ++NumFunctionsAnalyzed; diff --git a/lib/Sema/AttributeList.cpp b/lib/Sema/AttributeList.cpp index 101e0384fa..8e7029350a 100644 --- a/lib/Sema/AttributeList.cpp +++ b/lib/Sema/AttributeList.cpp @@ -15,6 +15,7 @@ #include "clang/AST/Expr.h" #include "clang/Basic/IdentifierTable.h" #include "llvm/ADT/StringSwitch.h" +#include "llvm/ADT/SmallString.h" using namespace clang; size_t AttributeList::allocated_size() const { @@ -99,7 +100,8 @@ AttributePool::createIntegerAttribute(ASTContext &C, IdentifierInfo *Name, #include "clang/Sema/AttrParsedAttrKinds.inc" -AttributeList::Kind AttributeList::getKind(const IdentifierInfo *Name) { +AttributeList::Kind AttributeList::getKind(const IdentifierInfo *Name, + const IdentifierInfo *ScopeName) { StringRef AttrName = Name->getName(); // Normalize the attribute name, __foo__ becomes foo. @@ -107,5 +109,10 @@ AttributeList::Kind AttributeList::getKind(const IdentifierInfo *Name) { AttrName.size() >= 4) AttrName = AttrName.substr(2, AttrName.size() - 4); + // FIXME: implement attribute namespacing correctly. + SmallString<64> Buf; + if (ScopeName) + AttrName = ((Buf += ScopeName->getName()) += "___") += AttrName; + return ::getAttrKind(AttrName); } diff --git a/lib/Sema/SemaStmtAttr.cpp b/lib/Sema/SemaStmtAttr.cpp index 21c329758e..912d7c6482 100644 --- a/lib/Sema/SemaStmtAttr.cpp +++ b/lib/Sema/SemaStmtAttr.cpp @@ -15,20 +15,46 @@ #include "TargetAttributesSema.h" #include "clang/AST/ASTContext.h" #include "clang/Basic/SourceManager.h" +#include "clang/Lex/Lexer.h" #include "clang/Sema/DelayedDiagnostic.h" #include "clang/Sema/Lookup.h" +#include "clang/Sema/ScopeInfo.h" #include "llvm/ADT/StringExtras.h" + using namespace clang; using namespace sema; +static Attr *handleFallThroughAttr(Sema &S, Stmt *St, const AttributeList &A, + SourceRange Range) { + if (!isa(St)) { + S.Diag(A.getRange().getBegin(), diag::err_fallthrough_attr_wrong_target) + << St->getLocStart(); + if (isa(St)) { + SourceLocation L = Lexer::getLocForEndOfToken(Range.getEnd(), 0, + S.getSourceManager(), S.getLangOpts()); + S.Diag(L, diag::note_fallthrough_insert_semi_fixit) + << FixItHint::CreateInsertion(L, ";"); + } + return 0; + } + if (S.getCurFunction()->SwitchStack.empty()) { + S.Diag(A.getRange().getBegin(), diag::err_fallthrough_attr_outside_switch); + return 0; + } + return ::new (S.Context) FallThroughAttr(A.getRange(), S.Context); +} + -static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const AttributeList &A) { +static Attr *ProcessStmtAttribute(Sema &S, Stmt *St, const AttributeList &A, + SourceRange Range) { switch (A.getKind()) { + case AttributeList::AT_clang___fallthrough: + return handleFallThroughAttr(S, St, A, Range); default: // if we're here, then we parsed an attribute, but didn't recognize it as a // statement attribute => it is declaration attribute - S.Diag(A.getRange().getBegin(), diag::warn_attribute_invalid_on_stmt) << - A.getName()->getName(); + S.Diag(A.getRange().getBegin(), diag::warn_attribute_invalid_on_stmt) + << A.getName()->getName() << St->getLocStart(); return 0; } } @@ -37,7 +63,7 @@ StmtResult Sema::ProcessStmtAttributes(Stmt *S, AttributeList *AttrList, SourceRange Range) { AttrVec Attrs; for (const AttributeList* l = AttrList; l; l = l->getNext()) { - if (Attr *a = ProcessStmtAttribute(*this, S, *l)) + if (Attr *a = ProcessStmtAttribute(*this, S, *l, Range)) Attrs.push_back(a); } diff --git a/test/SemaCXX/switch-implicit-fallthrough-cxx98.cpp b/test/SemaCXX/switch-implicit-fallthrough-cxx98.cpp new file mode 100644 index 0000000000..14ffcef704 --- /dev/null +++ b/test/SemaCXX/switch-implicit-fallthrough-cxx98.cpp @@ -0,0 +1,119 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++98 -Wimplicit-fallthrough %s + + +int fallthrough(int n) { + switch (n / 10) { + if (n - 1) { + n = 100; + } else if (n - 2) { + n = 101; + } else if (n - 3) { + n = 102; + } + case -1: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert 'break;' to avoid fall-through}} + ; + case 0: {// expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert 'break;' to avoid fall-through}} + } + case 1: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert 'break;' to avoid fall-through}} + n += 100 ; + case 3: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert 'break;' to avoid fall-through}} + if (n > 0) + n += 200; + case 4: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert 'break;' to avoid fall-through}} + if (n < 0) + ; + case 5: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert 'break;' to avoid fall-through}} + switch (n) { + case 111: + break; + case 112: + break; + case 113: + break ; + } + case 6: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert 'break;' to avoid fall-through}} + n += 300; + } + switch (n / 30) { + case 11: + case 12: // no warning here, intended fall-through, no statement between labels + n += 1600; + } + switch (n / 40) { + case 13: + if (n % 2 == 0) { + return 1; + } else { + return 2; + } + case 15: // no warning here, there's no fall-through + n += 3200; + } + switch (n / 50) { + case 17: { + if (n % 2 == 0) { + return 1; + } else { + return 2; + } + } + case 19: { // no warning here, there's no fall-through + n += 6400; + return 3; + } + case 21: { // no warning here, there's no fall-through + break; + } + case 23: // no warning here, there's no fall-through + n += 128000; + break; + case 25: // no warning here, there's no fall-through + break; + } + + return n; +} + +class ClassWithDtor { +public: + ~ClassWithDtor() {} +}; + +void fallthrough2(int n) { + switch (n) { + case 0: + { + ClassWithDtor temp; + break; + } + default: // no warning here, there's no fall-through + break; + } +} + +#define MY_SWITCH(X, Y, Z, U, V) switch (X) { case Y: Z; case U: V; } +#define MY_SWITCH2(X, Y, Z) switch (X) { Y; Z; } +#define MY_CASE(X, Y) case X: Y +#define MY_CASE2(X, Y, U, V) case X: Y; case U: V + +int fallthrough_macro1(int n) { + MY_SWITCH(n, 13, n *= 2, 14, break) // expected-warning{{unannotated fall-through between switch labels}} + + switch (n + 1) { + MY_CASE(33, n += 2); + MY_CASE(44, break); // expected-warning{{unannotated fall-through between switch labels}} + MY_CASE(55, n += 3); + } + + switch (n + 3) { + MY_CASE(333, return 333); + MY_CASE2(444, n += 44, 4444, break); // expected-warning{{unannotated fall-through between switch labels}} + MY_CASE(555, n += 33); + } + + MY_SWITCH2(n + 4, MY_CASE(17, n *= 3), MY_CASE(19, break)) // expected-warning{{unannotated fall-through between switch labels}} + + MY_SWITCH2(n + 5, MY_CASE(21, break), MY_CASE2(23, n *= 7, 25, break)) // expected-warning{{unannotated fall-through between switch labels}} + + return n; +} diff --git a/test/SemaCXX/switch-implicit-fallthrough.cpp b/test/SemaCXX/switch-implicit-fallthrough.cpp new file mode 100644 index 0000000000..db5508a125 --- /dev/null +++ b/test/SemaCXX/switch-implicit-fallthrough.cpp @@ -0,0 +1,177 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -Wimplicit-fallthrough %s + + +int fallthrough(int n) { + switch (n / 10) { + if (n - 1) { + n = 100; + } else if (n - 2) { + n = 101; + } else if (n - 3) { + n = 102; + } + case -1: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert '[[clang::fallthrough]];' to silence this warning}} expected-note{{insert 'break;' to avoid fall-through}} + ; + case 0: {// expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert '[[clang::fallthrough]];' to silence this warning}} expected-note{{insert 'break;' to avoid fall-through}} + } + case 1: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert '[[clang::fallthrough]];' to silence this warning}} expected-note{{insert 'break;' to avoid fall-through}} + n += 100 ; + case 3: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert '[[clang::fallthrough]];' to silence this warning}} expected-note{{insert 'break;' to avoid fall-through}} + if (n > 0) + n += 200; + case 4: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert '[[clang::fallthrough]];' to silence this warning}} expected-note{{insert 'break;' to avoid fall-through}} + if (n < 0) + ; + case 5: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert '[[clang::fallthrough]];' to silence this warning}} expected-note{{insert 'break;' to avoid fall-through}} + switch (n) { + case 111: + break; + case 112: + break; + case 113: + break ; + } + case 6: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert '[[clang::fallthrough]];' to silence this warning}} expected-note{{insert 'break;' to avoid fall-through}} + n += 300; + } + switch (n / 20) { + case 7: + n += 400; + [[clang::fallthrough]]; + case 9: // no warning here, intended fall-through marked with an attribute + n += 800; + [[clang::fallthrough]]; + default: { // no warning here, intended fall-through marked with an attribute + if (n % 2 == 0) { + return 1; + } else { + [[clang::fallthrough]]; + } + } + case 10: // no warning here, intended fall-through marked with an attribute + if (n % 3 == 0) { + n %= 3; + } else { + [[clang::fallthrough]]; + } + case 110: // expected-warning{{unannotated fall-through between switch labels}} but no fix-it hint as we have one fall-through annotation! + n += 800; + } + switch (n / 30) { + case 11: + case 12: // no warning here, intended fall-through, no statement between labels + n += 1600; + } + switch (n / 40) { + case 13: + if (n % 2 == 0) { + return 1; + } else { + return 2; + } + case 15: // no warning here, there's no fall-through + n += 3200; + } + switch (n / 50) { + case 17: { + if (n % 2 == 0) { + return 1; + } else { + return 2; + } + } + case 19: { // no warning here, there's no fall-through + n += 6400; + return 3; + } + case 21: { // no warning here, there's no fall-through + break; + } + case 23: // no warning here, there's no fall-through + n += 128000; + break; + case 25: // no warning here, there's no fall-through + break; + } + + return n; +} + +class ClassWithDtor { +public: + ~ClassWithDtor() {} +}; + +void fallthrough2(int n) { + switch (n) { + case 0: + { + ClassWithDtor temp; + break; + } + default: // no warning here, there's no fall-through + break; + } +} + +#define MY_SWITCH(X, Y, Z, U, V) switch (X) { case Y: Z; case U: V; } +#define MY_SWITCH2(X, Y, Z) switch (X) { Y; Z; } +#define MY_CASE(X, Y) case X: Y +#define MY_CASE2(X, Y, U, V) case X: Y; case U: V + +int fallthrough_macro1(int n) { + MY_SWITCH(n, 13, n *= 2, 14, break) // expected-warning{{unannotated fall-through between switch labels}} + + switch (n + 1) { + MY_CASE(33, n += 2); + MY_CASE(44, break); // expected-warning{{unannotated fall-through between switch labels}} + MY_CASE(55, n += 3); + } + + switch (n + 3) { + MY_CASE(333, return 333); + MY_CASE2(444, n += 44, 4444, break); // expected-warning{{unannotated fall-through between switch labels}} + MY_CASE(555, n += 33); + } + + MY_SWITCH2(n + 4, MY_CASE(17, n *= 3), MY_CASE(19, break)) // expected-warning{{unannotated fall-through between switch labels}} + + MY_SWITCH2(n + 5, MY_CASE(21, break), MY_CASE2(23, n *= 7, 25, break)) // expected-warning{{unannotated fall-through between switch labels}} + + return n; +} + +int fallthrough_position(int n) { + switch (n) { + [[clang::fallthrough]]; // expected-warning{{fallthrough annotation in unreachable code}} + case 221: + [[clang::fallthrough]]; // expected-warning{{fallthrough annotation does not directly precede switch label}} + return 1; + [[clang::fallthrough]]; // expected-warning{{fallthrough annotation in unreachable code}} + case 222: + [[clang::fallthrough]]; // expected-warning{{fallthrough annotation does not directly precede switch label}} + n += 400; + case 223: // expected-warning{{unannotated fall-through between switch labels}} expected-note{{insert '[[clang::fallthrough]];' to silence this warning}} expected-note{{insert 'break;' to avoid fall-through}} + [[clang::fallthrough]]; // expected-warning{{fallthrough annotation does not directly precede switch label}} + } + return n; +} + +int fallthrough_targets(int n) { + [[clang::fallthrough]]; // expected-error{{fallthrough annotation is outside switch statement}} + + [[clang::fallthrough]] // expected-error{{fallthrough attribute is only allowed on empty statements}} + switch (n) { + case 121: + n += 400; + [[clang::fallthrough]]; // no warning here, correct target + case 123: + [[clang::fallthrough]] // expected-error{{fallthrough attribute is only allowed on empty statements}} + n += 800; + break; + [[clang::fallthrough]] // expected-error{{fallthrough attribute is only allowed on empty statements}} expected-note{{did you forget ';'?}} + case 125: + n += 1600; + } + return n; +} -- 2.40.0