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 always false or null here">,
InGroup<DiagGroup<"conditional-scope">>;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Not really a fan of this wording. Perhaps we could use a %select here to decide whether this is a false or null (or perhaps even 0?). Or perhaps we should just say false always? What do others think?

Also, a note pointing to the condition-var is probably a good idea too.

Copy link
Author

@arrowten arrowten Oct 3, 2025

Choose a reason for hiding this comment

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

Not really a fan of this wording. Perhaps we could use a %select here to decide whether this is a false or null (or perhaps even 0?). Or perhaps we should just say false always? What do others think?

I will try that.

Also, a note pointing to the condition-var is probably a good idea too.

Its pointing to the "used" condition variable though.

Copy link
Collaborator

Choose a reason for hiding this comment

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

ah, hmm... it should point to the DeclRefExpr not to the condition variable then. We want to say "this use right here is wrong".

Copy link
Author

Choose a reason for hiding this comment

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

@erichkeane Correct me if I am wrong.
In here, this is the current scenario

if (int *ptr = get_something()) {  
  return ptr[0];                        
}
else if (int *ptr2 = get_something_else()) {  
  return ptr[0] * ptr2[0];          //warns for ptr
}
else if (int *ptr3 = get_something_else_again()) { 
  return ptr[0] * ptr2[0] * ptr3[0];               // warns for ptr and ptr2
}
else if (int *ptr4 = get_something_else_again_now()) { 
  return ptr[0] * ptr2[0] * ptr3[0] * ptr4[0];          // warns for ptr, ptr2 and ptr3
}

It should warn for something else too?


// AMDGCN builtins diagnostics
def err_amdgcn_load_lds_size_invalid_value : Error<"invalid size value">;
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/Sema/Scope.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,9 @@ class Scope {
/// available for this variable in the current scope.
llvm::SmallPtrSet<VarDecl *, 8> ReturnSlots;

// Condition variable for if
VarDecl *ConditionVar = nullptr;

void setFlags(Scope *Parent, unsigned F);

public:
Expand All @@ -263,6 +266,12 @@ class Scope {
Init(Parent, ScopeFlags);
}

// getConditionVar - Gets the condition variable of the scope.
VarDecl *getConditionVar() const { return ConditionVar; }

// setConditionVar - Setter for condition variable of this scope.
void setConditionVar(VarDecl *D) { ConditionVar = D; }

/// getFlags - Return the flags for this scope.
unsigned getFlags() const { return Flags; }

Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
Expand Up @@ -7171,6 +7171,10 @@ class Sema final : public SemaBase {
/// return a reason explaining why. Otherwise, return NOUR_None.
NonOdrUseReason getNonOdrUseReasonInCurrentContext(ValueDecl *D);

/// Checks if the given DeclRefExpr refers to a variable declared in an
/// enclosing 'if' condition.
bool isConditionVarReference(const DeclRefExpr *DRE);

DeclRefExpr *BuildDeclRefExpr(ValueDecl *D, QualType Ty, ExprValueKind VK,
SourceLocation Loc,
const CXXScopeSpec *SS = nullptr);
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Parse/ParseStmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,10 @@ StmtResult Parser::ParseIfStatement(SourceLocation *TrailingElseLoc) {
SourceLocation ElseStmtLoc;
StmtResult ElseStmt;

if (VarDecl *VD = dyn_cast_or_null<VarDecl>(Cond.get().first);
!Cond.isInvalid() && VD)
getCurScope()->setConditionVar(VD->getCanonicalDecl());

if (Tok.is(tok::kw_else)) {
if (TrailingElseLoc)
*TrailingElseLoc = Tok.getLocation();
Expand Down
22 changes: 22 additions & 0 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2371,6 +2371,24 @@ NonOdrUseReason Sema::getNonOdrUseReasonInCurrentContext(ValueDecl *D) {
return NOUR_None;
}

bool Sema::isConditionVarReference(const DeclRefExpr *DRE) {
if (!DRE)
return false;

const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl());

if (!VD)
return false;

for (Scope *S = getCurScope(); S; S = S->getParent()) {
if (VarDecl *CV = S->getConditionVar())
if (VD == CV)
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think we should still use isSameEntity, and we should probably figure out why that doesn't work. If it doesn't, I have some concerns about this.

Copy link
Author

@arrowten arrowten Oct 3, 2025

Choose a reason for hiding this comment

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

In the 5th testcase, I could see that it is returning true for VarX->getType() == VarY->getType() in isSameEntity.

Copy link
Collaborator

Choose a reason for hiding this comment

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

can you give a better example? They should be different entities...

Copy link
Author

Choose a reason for hiding this comment

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

  if (bool x = get_something()) {}
  else {
    {   
      bool x = get_something_else();
      if (x) {} //throws warning here
    }   
  }

It is throwing here since it compares the local x to the if condition x and since both have the same type, it eventually throws a warning.

Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm... those shouldn't be the same entity, but I see the ASTContext function is doing a lot of assuming for the purposes of deserialization, so that is probably why. It just doesn't do what I think it should be doing.

I think we actually want Sema::DeclaresSameEntity which'll handle it correctly.

return true;
}

return false;
}

DeclRefExpr *
Sema::BuildDeclRefExpr(ValueDecl *D, QualType Ty, ExprValueKind VK,
const DeclarationNameInfo &NameInfo,
Expand Down Expand Up @@ -2425,6 +2443,10 @@ Sema::BuildDeclRefExpr(ValueDecl *D, QualType Ty, ExprValueKind VK,
if (const auto *BE = BD->getBinding())
E->setObjectKind(BE->getObjectKind());

if (isConditionVarReference(E))
Diag(E->getBeginLoc(), diag::warn_out_of_scope_var_usage)
<< D->getDeclName();

return E;
}

Expand Down
146 changes: 146 additions & 0 deletions clang/test/Sema/warn-conditional-scope.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// RUN: %clang_cc1 -fsyntax-only -verify -Wconditional-scope -DTEST_1 %s
Copy link
Collaborator

Choose a reason for hiding this comment

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

These tests aren't particularly great or varied, try your situations but also try to make them 'flow' a bit to show you've covered everything. Make sure you cover more than just pointer cases.

Also, try some tests in a dependent case, where the variable itself is dependent.

also-also: What about an unevaluated context? Or a situation where you don't actually take the variable? Should THAT warn?

Consider:

if (auto x = bar()) {
}
else {
   decltype(x) f = g; // should this warn?
}

Copy link
Author

Choose a reason for hiding this comment

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

If anyone can help with different types of testcases, I would appreciate that.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I gave you some ideas, dependence, non-evaluated contexts, etc.

// RUN: %clang_cc1 -fsyntax-only -verify -Wconditional-scope -DTEST_2 %s
// RUN: %clang_cc1 -fsyntax-only -verify -Wconditional-scope -DTEST_3 %s
// RUN: %clang_cc1 -fsyntax-only -verify -Wconditional-scope -DTEST_4 %s
// RUN: %clang_cc1 -fsyntax-only -verify -Wconditional-scope -DTEST_5 %s
// RUN: %clang_cc1 -fsyntax-only -verify -Wconditional-scope -DTEST_6 %s

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

#ifdef TEST_1
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don't use macros like this in the test, just do all of these in the same 'run' line.

Copy link
Author

Choose a reason for hiding this comment

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

Do you mean to use one single function and all of the testcases together?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Not necessarily. But you can capture multiple diagnostics in the same run, so don't do macros, just do multiple checks.


int test() {
if (int *ptr = get_something()) {
return ptr[0] * ptr[0];
}
// expected-warning@+2{{variable 'ptr' declared in 'if' block is always false or null here}}
else if (int *ptr2 = get_something_else()) {
return ptr[0] * ptr2[0];
}
// expected-warning@+3{{variable 'ptr' declared in 'if' block is always false or null here}}
// expected-warning@+2{{variable 'ptr2' declared in 'if' block is always false or null here}}
else if (int* ptr3 = get_something_else_again()) {
return ptr[0] * ptr2[0] * ptr3[0];
}
// expected-warning@+4{{variable 'ptr' declared in 'if' block is always false or null here}}
// expected-warning@+3{{variable 'ptr2' declared in 'if' block is always false or null here}}
// expected-warning@+2{{variable 'ptr3' declared in 'if' block is always false or null here}}
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];
}
else if (int *ptr2 = get_something_else()) {
return ptr2[0] * ptr2[0];
}
// expected-warning@+3{{variable 'ptr' declared in 'if' block is always false or null here}}
// expected-warning@+2{{variable 'ptr2' declared in 'if' block is always false or null here}}
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@+3{{variable 'ptr2' declared in 'if' block is always false or null here}}
// expected-warning@+2{{variable 'ptr2' declared in 'if' block is always false or null here}}
else if (int* ptr3 = get_something_else_again()) {
return ptr2[0] * ptr2[0] * ptr3[0];
}
else {
return -1;
}
}

#endif

#ifdef TEST_4

int test() {
int x = 10;

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

#endif

#ifdef TEST_5

void test() {
// expected-no-diagnostics
if (bool x = get_something()) {}
else {
{
bool x = get_something_else();
if (x) {}
}
}
}

#endif

#ifdef TEST_6

int test() {
if (int *ptr = get_something()) {
return ptr[0];
}
else if (int *ptr = get_something_else()) {
return ptr[0] * ptr[0];
}
// expected-warning@+3{{variable 'ptr' declared in 'if' block is always false or null here}}
// expected-warning@+2{{variable 'ptr' declared in 'if' block is always false or null here}}
else {
return ptr[0] * ptr[0];
}
}

#endif