Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions clang/include/clang/Analysis/CFG.h
Original file line number Diff line number Diff line change
Expand Up @@ -1251,6 +1251,7 @@ class CFG {
bool MarkElidedCXXConstructors = false;
bool AddVirtualBaseBranches = false;
bool OmitImplicitValueInitializers = false;
bool AssumeReachableDefaultInSwitchStatements = false;

BuildOptions() = default;

Expand Down
7 changes: 5 additions & 2 deletions clang/lib/Analysis/CFG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4516,10 +4516,13 @@ CFGBlock *CFGBuilder::VisitSwitchStmt(SwitchStmt *Terminator) {
//
// Note: We add a successor to a switch that is considered covered yet has no
// case statements if the enumeration has no enumerators.
// We also consider this successor reachable if
// BuildOpts.SwitchReqDefaultCoveredEnum is true.
bool SwitchAlwaysHasSuccessor = false;
SwitchAlwaysHasSuccessor |= switchExclusivelyCovered;
SwitchAlwaysHasSuccessor |= Terminator->isAllEnumCasesCovered() &&
Terminator->getSwitchCaseList();
SwitchAlwaysHasSuccessor |=
!BuildOpts.AssumeReachableDefaultInSwitchStatements &&
Terminator->isAllEnumCasesCovered() && Terminator->getSwitchCaseList();
addSuccessor(SwitchTerminatedBlock, DefaultCaseBlock,
!SwitchAlwaysHasSuccessor);

Expand Down
153 changes: 153 additions & 0 deletions clang/unittests/Analysis/CFGTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,159 @@ TEST(CFG, DependantBaseAddImplicitDtors) {
.getStatus());
}

TEST(CFG, SwitchCoveredEnumNoDefault) {
const char *Code = R"(
enum class E {E1, E2};
int f(E e) {
switch(e) {
case E::E1:
return 1;
case E::E2:
return 2;
}
return 0;
}
)";
CFG::BuildOptions Options;
Options.AssumeReachableDefaultInSwitchStatements = true;
BuildResult B = BuildCFG(Code, Options);
ASSERT_EQ(BuildResult::BuiltCFG, B.getStatus());

// [B5 (ENTRY)]
// Succs (1): B2
//
// [B1]
// 1: 0
// 2: return [B1.1];
// Preds (1): B2
// Succs (1): B0
//
// [B2]
// 1: e (ImplicitCastExpr, LValueToRValue, E)
// T: switch [B2.1]
// Preds (1): B5
// Succs (3): B3 B4 B1
//
// [B3]
// case E::E2:
// 1: 2
// 2: return [B3.1];
// Preds (1): B2
// Succs (1): B0
//
// [B4]
// case E::E1:
// 1: 1
// 2: return [B4.1];
// Preds (1): B2
// Succs (1): B0
//
// [B0 (EXIT)]
// Preds (3): B1 B3 B4

auto *CFG = B.getCFG();
const auto &Entry = CFG->getEntry();
ASSERT_EQ(1u, Entry.succ_size());
// First successor of Entry is the switch
CFGBlock *SwitchBlock = *Entry.succ_begin();
ASSERT_EQ(3u, SwitchBlock->succ_size());
// Last successor of the switch is after the switch
auto NoCaseSucc = SwitchBlock->succ_rbegin();
EXPECT_TRUE(NoCaseSucc->isReachable());

// Checking that the same node is Unreachable without this setting
Options.AssumeReachableDefaultInSwitchStatements = false;
B = BuildCFG(Code, Options);
ASSERT_EQ(BuildResult::BuiltCFG, B.getStatus());

const auto &Entry2 = B.getCFG()->getEntry();
ASSERT_EQ(1u, Entry2.succ_size());
CFGBlock *SwitchBlock2 = *Entry2.succ_begin();
ASSERT_EQ(3u, SwitchBlock2->succ_size());
auto NoCaseSucc2 = SwitchBlock2->succ_rbegin();
EXPECT_FALSE(NoCaseSucc2->isReachable());
}

TEST(CFG, SwitchCoveredEnumWithDefault) {
const char *Code = R"(
enum class E {E1, E2};
int f(E e) {
switch(e) {
case E::E1:
return 1;
case E::E2:
return 2;
default:
return 0;
}
return -1;
}
)";
CFG::BuildOptions Options;
Options.AssumeReachableDefaultInSwitchStatements = true;
BuildResult B = BuildCFG(Code, Options);
ASSERT_EQ(BuildResult::BuiltCFG, B.getStatus());

// [B6 (ENTRY)]
// Succs (1): B2
//
// [B1]
// 1: -1
// 2: return [B1.1];
// Succs (1): B0
//
// [B2]
// 1: e (ImplicitCastExpr, LValueToRValue, E)
// T: switch [B2.1]
// Preds (1): B6
// Succs (3): B4 B5 B3
//
// [B3]
// default:
// 1: 0
// 2: return [B3.1];
// Preds (1): B2
// Succs (1): B0
//
// [B4]
// case E::E2:
// 1: 2
// 2: return [B4.1];
// Preds (1): B2
// Succs (1): B0
//
// [B5]
// case E::E1:
// 1: 1
// 2: return [B5.1];
// Preds (1): B2
// Succs (1): B0
//
// [B0 (EXIT)]
// Preds (4): B1 B3 B4 B5

const auto &Entry = B.getCFG()->getEntry();
ASSERT_EQ(1u, Entry.succ_size());
// First successor of Entry is the switch
CFGBlock *SwitchBlock = *Entry.succ_begin();
ASSERT_EQ(3u, SwitchBlock->succ_size());
// Last successor of the switch is the default branch
auto defaultBlock = SwitchBlock->succ_rbegin();
EXPECT_TRUE(defaultBlock->isReachable());

// Checking that the same node is Unreachable without this setting
Options.AssumeReachableDefaultInSwitchStatements = false;
B = BuildCFG(Code, Options);
ASSERT_EQ(BuildResult::BuiltCFG, B.getStatus());

const auto &Entry2 = B.getCFG()->getEntry();
ASSERT_EQ(1u, Entry2.succ_size());
CFGBlock *SwitchBlock2 = *Entry2.succ_begin();
ASSERT_EQ(3u, SwitchBlock2->succ_size());
auto defaultBlock2 = SwitchBlock2->succ_rbegin();
EXPECT_FALSE(defaultBlock2->isReachable());
}

TEST(CFG, IsLinear) {
auto expectLinear = [](bool IsLinear, const char *Code) {
BuildResult B = BuildCFG(Code);
Expand Down