Skip to content
Open
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
3 changes: 3 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -13594,6 +13594,9 @@ def warn_acc_var_referenced_lacks_op
"reference has no effect">,
InGroup<DiagGroup<"openacc-var-lacks-operation">>,
DefaultError;
def warn_out_of_scope_var_usage
: Warning<"variable %0 declared in 'if' block is either false or null">,
InGroup<DiagGroup<"conditional-scope">>;

// AMDGCN builtins diagnostics
def err_amdgcn_load_lds_size_invalid_value : Error<"invalid size value">;
Expand Down
29 changes: 29 additions & 0 deletions clang/lib/Sema/SemaStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -971,6 +971,35 @@ StmtResult Sema::ActOnIfStmt(SourceLocation IfLoc,
if (!ConstevalOrNegatedConsteval && !elseStmt)
DiagnoseEmptyStmtBody(RParenLoc, thenStmt, diag::warn_empty_if_body);

// Checks for if condition variable usage in else scope
if (elseStmt) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm.. This whole thing is a little too pattern matchy for me, and without a visitor is now going to just do this the 'hard' way.

I think ActOnIfStmt is probably the wrong place for this entirely, its too late at this point. We should probably do it somewhere in ActOnVariableDeclarator (or something like that?) and check the scope. Scope itself doesn't do diagnostics, but will have the context that we are 'in' a for loop, and can store the condition variable.

As this is written, an if-else chain ends up having the last else body checked N times. In addition to everything else, thats just wrong.

Instead, when we see a var-reference being created, we should be checking all the active if statements, and be variables that we've stored along the way.

if (auto* CondVar = dyn_cast_or_null<VarDecl>(Cond.get().first)) {
bool usedInElse = false;
std::function<bool(Stmt*)> checkForUsage = [&](Stmt *S) -> bool {
if (!S) return false;
if (DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(S)) {
if (DRE->getDecl() == CondVar) {
return true;
}
}

for (Stmt *Child: S->children()) {
if (checkForUsage(Child)) {
return true;
}
}

return false;
};
usedInElse = checkForUsage(elseStmt);

if (usedInElse) {
Diag(elseStmt->getBeginLoc(), diag::warn_out_of_scope_var_usage)
<< CondVar->getName();
}
}
}

if (ConstevalOrNegatedConsteval ||
StatementKind == IfStatementKind::Constexpr) {
auto DiagnoseLikelihood = [&](const Stmt *S) {
Expand Down
74 changes: 74 additions & 0 deletions clang/test/Sema/warn-conditional-scope.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// RUN: %clang_cc1 -fsyntax-only -verify -Wconditional-scope -DTEST_1 %s
// RUN: %clang_cc1 -fsyntax-only -verify -Wconditional-scope -DTEST_2 %s
// RUN: %clang_cc1 -fsyntax-only -verify -Wconditional-scope -DTEST_3 %s

int *get_something();
int *get_something_else();
int *get_something_else_again();
int *get_something_else_again_now();

#ifdef TEST_1

int test() {
if (int *ptr = get_something()) {
return ptr[0] * ptr[0];
}
// expected-warning@+1{{variable ptr declared in 'if' block is either false or null}}
else if (int *ptr2 = get_something_else()) {
return ptr[0] * ptr2[0];
}
// expected-warning@+1{{variable ptr2 declared in 'if' block is either false or null}}
else if (int* ptr3 = get_something_else_again()) {
return ptr[0] * ptr2[0] * ptr3[0];
}
// expected-warning@+1{{variable ptr3 declared in 'if' block is either false or null}}
else if (int *ptr4 = get_something_else_again_now()) {
return ptr[0] * ptr2[0] * ptr3[0] * ptr4[0];
}
else {
return -1;
}
}

#endif

#ifdef TEST_2

int test() {
if (int *ptr = get_something()) {
return ptr[0] * ptr[0];
}
// expected-warning@+1{{variable ptr declared in 'if' block is either false or null}}
else if (int *ptr2 = get_something_else()) {
return ptr2[0] * ptr2[0];
}
// expected-warning@+1{{variable ptr2 declared in 'if' block is either false or null}}
else if (int* ptr3 = get_something_else_again()) {
return ptr[0] * ptr2[0] * ptr3[0];
}
else {
return -1;
}
}

#endif

#ifdef TEST_3

int test() {
if (int *ptr = get_something()) {
return ptr[0] * ptr[0];
}
else if (int *ptr2 = get_something_else()) {
return ptr2[0] * ptr2[0];
}
// expected-warning@+1{{variable ptr2 declared in 'if' block is either false or null}}
else if (int* ptr3 = get_something_else_again()) {
return ptr2[0] * ptr2[0] * ptr3[0];
}
else {
return -1;
}
}

#endif