Skip to content

[clang][analyzer] Add notes to PointerSubChecker #95899

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jun 24, 2024

Conversation

balazske
Copy link
Collaborator

Notes are added to indicate the array declarations of the arrays in a found invalid pointer subtraction.

Notes are added to indicate the array declarations of the
arrays in a found invalid pointer subtraction.
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:static analyzer labels Jun 18, 2024
@llvmbot
Copy link
Member

llvmbot commented Jun 18, 2024

@llvm/pr-subscribers-clang

Author: Balázs Kéri (balazske)

Changes

Notes are added to indicate the array declarations of the arrays in a found invalid pointer subtraction.


Full diff: https://github.com/llvm/llvm-project/pull/95899.diff

2 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp (+30-15)
  • (added) clang/test/Analysis/pointer-sub-notes.c (+34)
diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
index b73534136fdf0..a983e66df0818 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
@@ -43,8 +43,6 @@ class PointerSubChecker
   bool checkArrayBounds(CheckerContext &C, const Expr *E,
                         const ElementRegion *ElemReg,
                         const MemRegion *Reg) const;
-  void reportBug(CheckerContext &C, const Expr *E,
-                 const llvm::StringLiteral &Msg) const;
 
 public:
   void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const;
@@ -57,6 +55,14 @@ bool PointerSubChecker::checkArrayBounds(CheckerContext &C, const Expr *E,
   if (!ElemReg)
     return true;
 
+  auto ReportBug = [&](const llvm::StringLiteral &Msg) {
+    if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+      auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
+      R->addRange(E->getSourceRange());
+      C.emitReport(std::move(R));
+    }
+  };
+
   ProgramStateRef State = C.getState();
   const MemRegion *SuperReg = ElemReg->getSuperRegion();
   SValBuilder &SVB = C.getSValBuilder();
@@ -64,7 +70,7 @@ bool PointerSubChecker::checkArrayBounds(CheckerContext &C, const Expr *E,
   if (SuperReg == Reg) {
     if (const llvm::APSInt *I = SVB.getKnownValue(State, ElemReg->getIndex());
         I && (!I->isOne() && !I->isZero()))
-      reportBug(C, E, Msg_BadVarIndex);
+      ReportBug(Msg_BadVarIndex);
     return false;
   }
 
@@ -77,7 +83,7 @@ bool PointerSubChecker::checkArrayBounds(CheckerContext &C, const Expr *E,
     ProgramStateRef S1, S2;
     std::tie(S1, S2) = C.getState()->assume(*IndexTooLarge);
     if (S1 && !S2) {
-      reportBug(C, E, Msg_LargeArrayIndex);
+      ReportBug(Msg_LargeArrayIndex);
       return false;
     }
   }
@@ -89,22 +95,13 @@ bool PointerSubChecker::checkArrayBounds(CheckerContext &C, const Expr *E,
     ProgramStateRef S1, S2;
     std::tie(S1, S2) = State->assume(*IndexTooSmall);
     if (S1 && !S2) {
-      reportBug(C, E, Msg_NegativeArrayIndex);
+      ReportBug(Msg_NegativeArrayIndex);
       return false;
     }
   }
   return true;
 }
 
-void PointerSubChecker::reportBug(CheckerContext &C, const Expr *E,
-                                  const llvm::StringLiteral &Msg) const {
-  if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
-    auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
-    R->addRange(E->getSourceRange());
-    C.emitReport(std::move(R));
-  }
-}
-
 void PointerSubChecker::checkPreStmt(const BinaryOperator *B,
                                      CheckerContext &C) const {
   // When doing pointer subtraction, if the two pointers do not point to the
@@ -136,6 +133,9 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B,
   if (!checkArrayBounds(C, B->getRHS(), ElemRR, LR))
     return;
 
+  const ValueDecl *DiffDeclL = nullptr;
+  const ValueDecl *DiffDeclR = nullptr;
+
   if (ElemLR && ElemRR) {
     const MemRegion *SuperLR = ElemLR->getSuperRegion();
     const MemRegion *SuperRR = ElemRR->getSuperRegion();
@@ -144,9 +144,24 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B,
     // Allow arithmetic on different symbolic regions.
     if (isa<SymbolicRegion>(SuperLR) || isa<SymbolicRegion>(SuperRR))
       return;
+    if (const auto *SuperDLR = dyn_cast<DeclRegion>(SuperLR))
+      DiffDeclL = SuperDLR->getDecl();
+    if (const auto *SuperDRR = dyn_cast<DeclRegion>(SuperRR))
+      DiffDeclR = SuperDRR->getDecl();
   }
 
-  reportBug(C, B, Msg_MemRegionDifferent);
+  if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+    auto R =
+        std::make_unique<PathSensitiveBugReport>(BT, Msg_MemRegionDifferent, N);
+    R->addRange(B->getSourceRange());
+    if (DiffDeclL)
+      R->addNote("Array at the left-hand side of subtraction",
+                 {DiffDeclL, C.getSourceManager()});
+    if (DiffDeclR)
+      R->addNote("Array at the right-hand side of subtraction",
+                 {DiffDeclR, C.getSourceManager()});
+    C.emitReport(std::move(R));
+  }
 }
 
 void ento::registerPointerSubChecker(CheckerManager &mgr) {
diff --git a/clang/test/Analysis/pointer-sub-notes.c b/clang/test/Analysis/pointer-sub-notes.c
new file mode 100644
index 0000000000000..dfdace3a58deb
--- /dev/null
+++ b/clang/test/Analysis/pointer-sub-notes.c
@@ -0,0 +1,34 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.core.PointerSub -analyzer-output=text -verify %s
+
+void negative_1() {
+  int a[3];
+  int x = -1;
+  // FIXME: should indicate that 'x' is -1
+  int d = &a[x] - &a[0]; // expected-warning{{Using a negative array index at pointer subtraction is undefined behavior}} \
+                         // expected-note{{Using a negative array index at pointer subtraction is undefined behavior}}
+}
+
+void negative_2() {
+  int a[3];
+  int *p1 = a, *p2 = a;
+  --p2;
+  // FIXME: should indicate that 'p2' is negative
+  int d = p1 - p2; // expected-warning{{Using a negative array index at pointer subtraction is undefined behavior}} \
+                   // expected-note{{Using a negative array index at pointer subtraction is undefined behavior}}
+}
+
+void different_1() {
+  int a[3]; // expected-note{{Array at the left-hand side of subtraction}}
+  int b[3]; // expected-note{{Array at the right-hand side of subtraction}}
+  int d = &a[2] - &b[0]; // expected-warning{{Subtraction of two pointers that do not point into the same array is undefined behavior}} \
+                         // expected-note{{Subtraction of two pointers that do not point into the same array is undefined behavior}}
+}
+
+void different_2() {
+  int a[3]; // expected-note{{Array at the right-hand side of subtraction}}
+  int b[3]; // expected-note{{Array at the left-hand side of subtraction}}
+  int *p1 = a + 1;
+  int *p2 = b;
+  int d = p2 - p1; // expected-warning{{Subtraction of two pointers that do not point into the same array is undefined behavior}} \
+                   // expected-note{{Subtraction of two pointers that do not point into the same array is undefined behavior}}
+}

@llvmbot
Copy link
Member

llvmbot commented Jun 18, 2024

@llvm/pr-subscribers-clang-static-analyzer-1

Author: Balázs Kéri (balazske)

Changes

Notes are added to indicate the array declarations of the arrays in a found invalid pointer subtraction.


Full diff: https://github.com/llvm/llvm-project/pull/95899.diff

2 Files Affected:

  • (modified) clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp (+30-15)
  • (added) clang/test/Analysis/pointer-sub-notes.c (+34)
diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
index b73534136fdf0..a983e66df0818 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp
@@ -43,8 +43,6 @@ class PointerSubChecker
   bool checkArrayBounds(CheckerContext &C, const Expr *E,
                         const ElementRegion *ElemReg,
                         const MemRegion *Reg) const;
-  void reportBug(CheckerContext &C, const Expr *E,
-                 const llvm::StringLiteral &Msg) const;
 
 public:
   void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const;
@@ -57,6 +55,14 @@ bool PointerSubChecker::checkArrayBounds(CheckerContext &C, const Expr *E,
   if (!ElemReg)
     return true;
 
+  auto ReportBug = [&](const llvm::StringLiteral &Msg) {
+    if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+      auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
+      R->addRange(E->getSourceRange());
+      C.emitReport(std::move(R));
+    }
+  };
+
   ProgramStateRef State = C.getState();
   const MemRegion *SuperReg = ElemReg->getSuperRegion();
   SValBuilder &SVB = C.getSValBuilder();
@@ -64,7 +70,7 @@ bool PointerSubChecker::checkArrayBounds(CheckerContext &C, const Expr *E,
   if (SuperReg == Reg) {
     if (const llvm::APSInt *I = SVB.getKnownValue(State, ElemReg->getIndex());
         I && (!I->isOne() && !I->isZero()))
-      reportBug(C, E, Msg_BadVarIndex);
+      ReportBug(Msg_BadVarIndex);
     return false;
   }
 
@@ -77,7 +83,7 @@ bool PointerSubChecker::checkArrayBounds(CheckerContext &C, const Expr *E,
     ProgramStateRef S1, S2;
     std::tie(S1, S2) = C.getState()->assume(*IndexTooLarge);
     if (S1 && !S2) {
-      reportBug(C, E, Msg_LargeArrayIndex);
+      ReportBug(Msg_LargeArrayIndex);
       return false;
     }
   }
@@ -89,22 +95,13 @@ bool PointerSubChecker::checkArrayBounds(CheckerContext &C, const Expr *E,
     ProgramStateRef S1, S2;
     std::tie(S1, S2) = State->assume(*IndexTooSmall);
     if (S1 && !S2) {
-      reportBug(C, E, Msg_NegativeArrayIndex);
+      ReportBug(Msg_NegativeArrayIndex);
       return false;
     }
   }
   return true;
 }
 
-void PointerSubChecker::reportBug(CheckerContext &C, const Expr *E,
-                                  const llvm::StringLiteral &Msg) const {
-  if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
-    auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
-    R->addRange(E->getSourceRange());
-    C.emitReport(std::move(R));
-  }
-}
-
 void PointerSubChecker::checkPreStmt(const BinaryOperator *B,
                                      CheckerContext &C) const {
   // When doing pointer subtraction, if the two pointers do not point to the
@@ -136,6 +133,9 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B,
   if (!checkArrayBounds(C, B->getRHS(), ElemRR, LR))
     return;
 
+  const ValueDecl *DiffDeclL = nullptr;
+  const ValueDecl *DiffDeclR = nullptr;
+
   if (ElemLR && ElemRR) {
     const MemRegion *SuperLR = ElemLR->getSuperRegion();
     const MemRegion *SuperRR = ElemRR->getSuperRegion();
@@ -144,9 +144,24 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B,
     // Allow arithmetic on different symbolic regions.
     if (isa<SymbolicRegion>(SuperLR) || isa<SymbolicRegion>(SuperRR))
       return;
+    if (const auto *SuperDLR = dyn_cast<DeclRegion>(SuperLR))
+      DiffDeclL = SuperDLR->getDecl();
+    if (const auto *SuperDRR = dyn_cast<DeclRegion>(SuperRR))
+      DiffDeclR = SuperDRR->getDecl();
   }
 
-  reportBug(C, B, Msg_MemRegionDifferent);
+  if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
+    auto R =
+        std::make_unique<PathSensitiveBugReport>(BT, Msg_MemRegionDifferent, N);
+    R->addRange(B->getSourceRange());
+    if (DiffDeclL)
+      R->addNote("Array at the left-hand side of subtraction",
+                 {DiffDeclL, C.getSourceManager()});
+    if (DiffDeclR)
+      R->addNote("Array at the right-hand side of subtraction",
+                 {DiffDeclR, C.getSourceManager()});
+    C.emitReport(std::move(R));
+  }
 }
 
 void ento::registerPointerSubChecker(CheckerManager &mgr) {
diff --git a/clang/test/Analysis/pointer-sub-notes.c b/clang/test/Analysis/pointer-sub-notes.c
new file mode 100644
index 0000000000000..dfdace3a58deb
--- /dev/null
+++ b/clang/test/Analysis/pointer-sub-notes.c
@@ -0,0 +1,34 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.core.PointerSub -analyzer-output=text -verify %s
+
+void negative_1() {
+  int a[3];
+  int x = -1;
+  // FIXME: should indicate that 'x' is -1
+  int d = &a[x] - &a[0]; // expected-warning{{Using a negative array index at pointer subtraction is undefined behavior}} \
+                         // expected-note{{Using a negative array index at pointer subtraction is undefined behavior}}
+}
+
+void negative_2() {
+  int a[3];
+  int *p1 = a, *p2 = a;
+  --p2;
+  // FIXME: should indicate that 'p2' is negative
+  int d = p1 - p2; // expected-warning{{Using a negative array index at pointer subtraction is undefined behavior}} \
+                   // expected-note{{Using a negative array index at pointer subtraction is undefined behavior}}
+}
+
+void different_1() {
+  int a[3]; // expected-note{{Array at the left-hand side of subtraction}}
+  int b[3]; // expected-note{{Array at the right-hand side of subtraction}}
+  int d = &a[2] - &b[0]; // expected-warning{{Subtraction of two pointers that do not point into the same array is undefined behavior}} \
+                         // expected-note{{Subtraction of two pointers that do not point into the same array is undefined behavior}}
+}
+
+void different_2() {
+  int a[3]; // expected-note{{Array at the right-hand side of subtraction}}
+  int b[3]; // expected-note{{Array at the left-hand side of subtraction}}
+  int *p1 = a + 1;
+  int *p2 = b;
+  int d = p2 - p1; // expected-warning{{Subtraction of two pointers that do not point into the same array is undefined behavior}} \
+                   // expected-note{{Subtraction of two pointers that do not point into the same array is undefined behavior}}
+}

Copy link
Contributor

@NagyDonat NagyDonat left a comment

Choose a reason for hiding this comment

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

Looks good overall, I have one remark about a rare case that would require some specialized code.

Comment on lines +147 to +150
if (const auto *SuperDLR = dyn_cast<DeclRegion>(SuperLR))
DiffDeclL = SuperDLR->getDecl();
if (const auto *SuperDRR = dyn_cast<DeclRegion>(SuperRR))
DiffDeclR = SuperDRR->getDecl();
Copy link
Contributor

Choose a reason for hiding this comment

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

Note that FieldRegions are DeclRegions, so these declarations may be data member declarations within a struct or class. This is usually not a problem, but there is a corner case where SuperLR != SuperRR, but the corresponding declarations are identical:

struct {
  int array[5];
} a, b;
int func(void) {
  return &a.array[3] - &b.array[2];
}

In this case the current code would place both notes onto the declaration of field, which would be confusing for the user.

Consider adding some code that handles this situation explicitly. (Either simply skip note creation when DiffDeclL == DiffDeclR, or create a specialized note for this case.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Warning is now omitted if both would be at the same declaration.

@balazske
Copy link
Collaborator Author

I found difficult results from the checker where it is not obvious what the problem is.
One type is this case where a negative index is found (any of these results, or check the first one):
https://codechecker-demo.eastus.cloudapp.azure.com/Default/reports?run=curl_curl-7_66_0_pointersub&is-unique=off&diff-type=New&checker-name=alpha.core.PointerSub
This type of problem is not fixed with the patch.

Other problem is when different arrays are found:
https://codechecker-demo.eastus.cloudapp.azure.com/Default/report-detail?run=vim_v8.2.1920_pointersub&is-unique=off&diff-type=New&checker-name=alpha.core.PointerSub&report-id=5494864&report-hash=9a4c8e84e0c5227d61f321ec217e88ec&report-filepath=%2Flocal%2Fpersistent_docker%2FCSA-measurements-driver-2913%2Fmeasurements_workspace%2Fvim%2Fsrc%2Fevalvars.c
This case should improve with the patch (but not tested yet).

Copy link
Contributor

@NagyDonat NagyDonat left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for the update. I added two minor edit suggestions, but after that the commit can be merged.

// expected-note{{Subtraction of two pointers that do not point into the same array is undefined behavior}}
}

int different_5() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
int different_5() {
int different_4() {

Very minor nitpick: different_3 was followed by different_5.

if (DiffDeclR)
R->addNote("Array at the right-hand side of subtraction",
{DiffDeclR, C.getSourceManager()});
if (DiffDeclL != DiffDeclR) {
Copy link
Contributor

@NagyDonat NagyDonat Jun 19, 2024

Choose a reason for hiding this comment

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

Suggested change
if (DiffDeclL != DiffDeclR) {
if (DiffDeclL != DiffDeclR) {
// The declarations may be identical even if the regions are different,
// if they are field regions within different objects:
// struct { int array[10]; } a, b;
// do_something_with(&a.array[5] - &b.array[5]);
// In this case the notes would be confusing, so don't emit them.

Add a comment that explains this if (DiffDeclL != DiffDeclR) check because otherwise it would be very surprising.

@balazske balazske requested a review from steakhal June 19, 2024 13:25
@balazske balazske merged commit c43d5f5 into llvm:main Jun 24, 2024
7 checks passed
@balazske balazske deleted the pointersub_impr branch June 24, 2024 07:28
AlexisPerry pushed a commit to llvm-project-tlp/llvm-project that referenced this pull request Jul 9, 2024
Notes are added to indicate the array declarations of the arrays in a
found invalid pointer subtraction.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:static analyzer clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants