Skip to content
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

[clang][dataflow] For bugprone-unchecked-optional-access report range #131055

Merged
merged 3 commits into from
Mar 17, 2025

Conversation

jvoung
Copy link
Contributor

@jvoung jvoung commented Mar 13, 2025

Report the range in diagnostics, in addition to the location
in case the range helps disambiguate a little in chained -> expressions.

b->a->f->x = 1;
^~~~~~~

instead of just:

b->a->f->x = 1;
^

As a followup we should probably also report the location/range
of an -> if that operator is used. Like:

b->a->f->x = 1;
       ^~

jvoung added 2 commits March 13, 2025 02:26
Report the range in diagnostics, in addition to the location
in case the range helps disambiguate a little in chained `->` expressions.

b->a->f->x = 1;
^~~~~~~

instead of just:

b->a->f->x = 1;
^

As a followup we should probably also report the location/range
of an `->` if that operator is used. Like:

b->a->f->x = 1;
       ^~
@jvoung jvoung marked this pull request as ready for review March 13, 2025 12:10
@llvmbot llvmbot added clang Clang issues not falling into any other category clang-tools-extra clang-tidy clang:dataflow Clang Dataflow Analysis framework - https://clang.llvm.org/docs/DataFlowAnalysisIntro.html clang:analysis labels Mar 13, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 13, 2025

@llvm/pr-subscribers-clang-analysis
@llvm/pr-subscribers-clang-tidy
@llvm/pr-subscribers-clang-tools-extra

@llvm/pr-subscribers-clang

Author: Jan Voung (jvoung)

Changes

Report the range in diagnostics, in addition to the location
in case the range helps disambiguate a little in chained -> expressions.

b->a->f->x = 1;
^~~~~~~

instead of just:

b->a->f->x = 1;
^

As a followup we should probably also report the location/range
of an -> if that operator is used. Like:

b->a->f->x = 1;
       ^~

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

4 Files Affected:

  • (modified) clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp (+10-7)
  • (modified) clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h (+9-2)
  • (modified) clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp (+8-7)
  • (modified) clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp (+5-5)
diff --git a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
index 0a0e212f345ed..0b51d5677929c 100644
--- a/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.cpp
@@ -19,6 +19,7 @@
 namespace clang::tidy::bugprone {
 using ast_matchers::MatchFinder;
 using dataflow::UncheckedOptionalAccessDiagnoser;
+using dataflow::UncheckedOptionalAccessDiagnostic;
 using dataflow::UncheckedOptionalAccessModel;
 
 static constexpr llvm::StringLiteral FuncID("fun");
@@ -52,14 +53,16 @@ void UncheckedOptionalAccessCheck::check(
   UncheckedOptionalAccessDiagnoser Diagnoser(ModelOptions);
   // FIXME: Allow user to set the (defaulted) SAT iterations max for
   // `diagnoseFunction` with config options.
-  if (llvm::Expected<llvm::SmallVector<SourceLocation>> Locs =
-          dataflow::diagnoseFunction<UncheckedOptionalAccessModel,
-                                     SourceLocation>(*FuncDecl, *Result.Context,
-                                                     Diagnoser))
-    for (const SourceLocation &Loc : *Locs)
-      diag(Loc, "unchecked access to optional value");
+  if (llvm::Expected<llvm::SmallVector<UncheckedOptionalAccessDiagnostic>>
+          Diags = dataflow::diagnoseFunction<UncheckedOptionalAccessModel,
+                                             UncheckedOptionalAccessDiagnostic>(
+              *FuncDecl, *Result.Context, Diagnoser))
+    for (const UncheckedOptionalAccessDiagnostic &Diag : *Diags) {
+      diag(Diag.Range.getBegin(), "unchecked access to optional value")
+          << Diag.Range;
+    }
   else
-    llvm::consumeError(Locs.takeError());
+    llvm::consumeError(Diags.takeError());
 }
 
 } // namespace clang::tidy::bugprone
diff --git a/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h b/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h
index fb11c2e230e32..696c9f4a6cf5c 100644
--- a/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h
+++ b/clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h
@@ -20,6 +20,7 @@
 #include "clang/Analysis/FlowSensitive/CachedConstAccessorsLattice.h"
 #include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
 #include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
+#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
 #include "clang/Analysis/FlowSensitive/NoopLattice.h"
 #include "clang/Basic/SourceLocation.h"
 #include "llvm/ADT/SmallVector.h"
@@ -71,12 +72,17 @@ class UncheckedOptionalAccessModel
       TransferMatchSwitch;
 };
 
+/// Diagnostic information for an unchecked optional access.
+struct UncheckedOptionalAccessDiagnostic {
+  CharSourceRange Range;
+};
+
 class UncheckedOptionalAccessDiagnoser {
 public:
   UncheckedOptionalAccessDiagnoser(
       UncheckedOptionalAccessModelOptions Options = {});
 
-  llvm::SmallVector<SourceLocation>
+  llvm::SmallVector<UncheckedOptionalAccessDiagnostic>
   operator()(const CFGElement &Elt, ASTContext &Ctx,
              const TransferStateForDiagnostics<UncheckedOptionalAccessLattice>
                  &State) {
@@ -84,7 +90,8 @@ class UncheckedOptionalAccessDiagnoser {
   }
 
 private:
-  CFGMatchSwitch<const Environment, llvm::SmallVector<SourceLocation>>
+  CFGMatchSwitch<const Environment,
+                 llvm::SmallVector<UncheckedOptionalAccessDiagnostic>>
       DiagnoseMatchSwitch;
 };
 
diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
index c28424fac8fef..164d2574132dd 100644
--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp
@@ -1120,8 +1120,8 @@ auto buildTransferMatchSwitch() {
       .Build();
 }
 
-llvm::SmallVector<SourceLocation> diagnoseUnwrapCall(const Expr *ObjectExpr,
-                                                     const Environment &Env) {
+llvm::SmallVector<UncheckedOptionalAccessDiagnostic>
+diagnoseUnwrapCall(const Expr *ObjectExpr, const Environment &Env) {
   if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
           getLocBehindPossiblePointer(*ObjectExpr, Env))) {
     auto *Prop = Env.getValue(locForHasValue(*OptionalLoc));
@@ -1132,9 +1132,9 @@ llvm::SmallVector<SourceLocation> diagnoseUnwrapCall(const Expr *ObjectExpr,
   }
 
   // Record that this unwrap is *not* provably safe.
-  // FIXME: include either the name of the optional (if applicable) or a source
-  // range of the access for easier interpretation of the result.
-  return {ObjectExpr->getBeginLoc()};
+  // FIXME: include the name of the optional (if applicable).
+  auto Range = CharSourceRange::getTokenRange(ObjectExpr->getSourceRange());
+  return {UncheckedOptionalAccessDiagnostic{Range}};
 }
 
 auto buildDiagnoseMatchSwitch(
@@ -1143,8 +1143,9 @@ auto buildDiagnoseMatchSwitch(
   // lot of duplicated work (e.g. string comparisons), consider providing APIs
   // that avoid it through memoization.
   auto IgnorableOptional = ignorableOptional(Options);
-  return CFGMatchSwitchBuilder<const Environment,
-                               llvm::SmallVector<SourceLocation>>()
+  return CFGMatchSwitchBuilder<
+             const Environment,
+             llvm::SmallVector<UncheckedOptionalAccessDiagnostic>>()
       // optional::value
       .CaseOfCFGStmt<CXXMemberCallExpr>(
           valueCall(IgnorableOptional),
diff --git a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
index 46fad6b655c4d..60f1a662a512b 100644
--- a/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
+++ b/clang/unittests/Analysis/FlowSensitive/UncheckedOptionalAccessModelTest.cpp
@@ -1336,7 +1336,7 @@ class UncheckedOptionalAccessTest
       T Make();
     )");
     UncheckedOptionalAccessModelOptions Options{IgnoreSmartPointerDereference};
-    std::vector<SourceLocation> Diagnostics;
+    std::vector<UncheckedOptionalAccessDiagnostic> Diagnostics;
     llvm::Error Error = checkDataflow<UncheckedOptionalAccessModel>(
         AnalysisInputs<UncheckedOptionalAccessModel>(
             SourceCode, std::move(FuncMatcher),
@@ -1369,17 +1369,17 @@ class UncheckedOptionalAccessTest
           }
           auto &SrcMgr = AO.ASTCtx.getSourceManager();
           llvm::DenseSet<unsigned> DiagnosticLines;
-          for (SourceLocation &Loc : Diagnostics) {
-            unsigned Line = SrcMgr.getPresumedLineNumber(Loc);
+          for (const UncheckedOptionalAccessDiagnostic &Diag : Diagnostics) {
+            unsigned Line = SrcMgr.getPresumedLineNumber(Diag.Range.getBegin());
             DiagnosticLines.insert(Line);
             if (!AnnotationLines.contains(Line)) {
               IntrusiveRefCntPtr<DiagnosticOptions> DiagOpts(
                   new DiagnosticOptions());
               TextDiagnostic TD(llvm::errs(), AO.ASTCtx.getLangOpts(),
                                 DiagOpts.get());
-              TD.emitDiagnostic(FullSourceLoc(Loc, SrcMgr),
+              TD.emitDiagnostic(FullSourceLoc(Diag.Range.getBegin(), SrcMgr),
                                 DiagnosticsEngine::Error,
-                                "unexpected diagnostic", {}, {});
+                                "unexpected diagnostic", {Diag.Range}, {});
             }
           }
 

Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we modify any of the tests to check for the range?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah yes! Added an expectation annotation for ranges and a test case.

@jvoung jvoung merged commit 6f659b0 into llvm:main Mar 17, 2025
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:analysis clang:dataflow Clang Dataflow Analysis framework - https://clang.llvm.org/docs/DataFlowAnalysisIntro.html clang Clang issues not falling into any other category clang-tidy clang-tools-extra
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants