Skip to content

Commit 1b4f226

Browse files
authored
[clang-tidy] Add IgnoreValueCalls option to bugprone-unchecked-optional-access (#167209)
Add a new option `IgnoreValueCalls` to `bugprone-unchecked-optional-access` Closes [#163831](#163831)
1 parent 356b0dd commit 1b4f226

File tree

6 files changed

+81
-20
lines changed

6 files changed

+81
-20
lines changed

clang-tools-extra/clang-tidy/bugprone/UncheckedOptionalAccessCheck.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ class UncheckedOptionalAccessCheck : public ClangTidyCheck {
2525
public:
2626
UncheckedOptionalAccessCheck(StringRef Name, ClangTidyContext *Context)
2727
: ClangTidyCheck(Name, Context),
28-
ModelOptions{Options.get("IgnoreSmartPointerDereference", false)} {}
28+
ModelOptions{Options.get("IgnoreSmartPointerDereference", false),
29+
Options.get("IgnoreValueCalls", false)} {}
2930
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
3031
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
3132
bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
@@ -34,6 +35,7 @@ class UncheckedOptionalAccessCheck : public ClangTidyCheck {
3435
void storeOptions(ClangTidyOptions::OptionMap &Opts) override {
3536
Options.store(Opts, "IgnoreSmartPointerDereference",
3637
ModelOptions.IgnoreSmartPointerDereference);
38+
Options.store(Opts, "IgnoreValueCalls", ModelOptions.IgnoreValueCalls);
3739
}
3840

3941
private:

clang-tools-extra/docs/ReleaseNotes.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,11 @@ Changes in existing checks
367367
- Improved :doc:`bugprone-unchecked-optional-access
368368
<clang-tidy/checks/bugprone/unchecked-optional-access>` check by supporting
369369
``NullableValue::makeValue`` and ``NullableValue::makeValueInplace`` to
370-
prevent false-positives for ``BloombergLP::bdlb::NullableValue`` type.
370+
prevent false-positives for ``BloombergLP::bdlb::NullableValue`` type, and by
371+
adding the `IgnoreValueCalls` option to suppress diagnostics for
372+
``optional::value()`` and the `IgnoreSmartPointerDereference` option to
373+
ignore optionals reached via smart-pointer-like dereference, while still
374+
diagnosing UB-prone dereferences via ``operator*`` and ``operator->``.
371375

372376
- Improved :doc:`bugprone-unhandled-self-assignment
373377
<clang-tidy/checks/bugprone/unhandled-self-assignment>` check by adding

clang-tools-extra/docs/clang-tidy/checks/bugprone/unchecked-optional-access.rst

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,3 +308,22 @@ advantages:
308308
* Performance. A single check can cover many or even all accesses within
309309
scope. This gives the user the best of both worlds -- the safety of a
310310
dynamic check, but without incurring redundant costs.
311+
312+
Options
313+
-------
314+
315+
.. option:: IgnoreSmartPointerDereference
316+
317+
If set to `true`, the check ignores optionals that
318+
are reached through overloaded smart-pointer-like dereference (``operator*``,
319+
``operator->``) on classes other than the optional type itself. This helps
320+
avoid false positives where the analysis cannot equate results across such
321+
calls. This does not cover access through ``operator[]``. Default is `false`.
322+
323+
.. option:: IgnoreValueCalls
324+
325+
If set to `true`, the check does not diagnose calls
326+
to ``optional::value()``. Diagnostics for ``operator*()`` and
327+
``operator->()`` remain enabled. This is useful for codebases that
328+
intentionally rely on ``value()`` for defined, guarded access while still
329+
flagging UB-prone operator dereferences. Default is `false`.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// RUN: %check_clang_tidy %s bugprone-unchecked-optional-access %t -- \
2+
// RUN: -config="{CheckOptions: \
3+
// RUN: {bugprone-unchecked-optional-access.IgnoreValueCalls: true}}" -- \
4+
// RUN: -I %S/Inputs/unchecked-optional-access
5+
6+
#include "absl/types/optional.h"
7+
8+
struct Foo {
9+
void foo() const {}
10+
};
11+
12+
void unchecked_value_access(const absl::optional<int> &opt) {
13+
opt.value(); // no-warning
14+
}
15+
16+
void unchecked_deref_operator_access(const absl::optional<int> &opt) {
17+
*opt;
18+
// CHECK-MESSAGES: :[[@LINE-1]]:4: warning: unchecked access to optional value
19+
}
20+
21+
void unchecked_arrow_operator_access(const absl::optional<Foo> &opt) {
22+
opt->foo();
23+
// CHECK-MESSAGES: :[[@LINE-1]]:3: warning: unchecked access to optional value
24+
}
25+

clang/include/clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ struct UncheckedOptionalAccessModelOptions {
4646
/// are confident in this const accessor caching, we shouldn't need the
4747
/// IgnoreSmartPointerDereference option anymore.
4848
bool IgnoreSmartPointerDereference = false;
49+
50+
/// In generating diagnostics, ignore calls to `optional::value()`.
51+
bool IgnoreValueCalls = false;
4952
};
5053

5154
using UncheckedOptionalAccessLattice = CachedConstAccessorsLattice<NoopLattice>;

clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,26 +1153,34 @@ auto buildDiagnoseMatchSwitch(
11531153
// FIXME: Evaluate the efficiency of matchers. If using matchers results in a
11541154
// lot of duplicated work (e.g. string comparisons), consider providing APIs
11551155
// that avoid it through memoization.
1156-
auto IgnorableOptional = ignorableOptional(Options);
1157-
return CFGMatchSwitchBuilder<
1158-
const Environment,
1159-
llvm::SmallVector<UncheckedOptionalAccessDiagnostic>>()
1160-
// optional::value
1161-
.CaseOfCFGStmt<CXXMemberCallExpr>(
1162-
valueCall(IgnorableOptional),
1163-
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
1164-
const Environment &Env) {
1165-
return diagnoseUnwrapCall(E->getImplicitObjectArgument(), Env);
1166-
})
1167-
1168-
// optional::operator*, optional::operator->
1169-
.CaseOfCFGStmt<CallExpr>(valueOperatorCall(IgnorableOptional),
1170-
[](const CallExpr *E,
1156+
const auto IgnorableOptional = ignorableOptional(Options);
1157+
1158+
auto DiagBuilder =
1159+
CFGMatchSwitchBuilder<
1160+
const Environment,
1161+
llvm::SmallVector<UncheckedOptionalAccessDiagnostic>>()
1162+
// optional::operator*, optional::operator->
1163+
.CaseOfCFGStmt<CallExpr>(
1164+
valueOperatorCall(IgnorableOptional),
1165+
[](const CallExpr *E, const MatchFinder::MatchResult &,
1166+
const Environment &Env) {
1167+
return diagnoseUnwrapCall(E->getArg(0), Env);
1168+
});
1169+
1170+
auto Builder = Options.IgnoreValueCalls
1171+
? std::move(DiagBuilder)
1172+
: std::move(DiagBuilder)
1173+
// optional::value
1174+
.CaseOfCFGStmt<CXXMemberCallExpr>(
1175+
valueCall(IgnorableOptional),
1176+
[](const CXXMemberCallExpr *E,
11711177
const MatchFinder::MatchResult &,
11721178
const Environment &Env) {
1173-
return diagnoseUnwrapCall(E->getArg(0), Env);
1174-
})
1175-
.Build();
1179+
return diagnoseUnwrapCall(
1180+
E->getImplicitObjectArgument(), Env);
1181+
});
1182+
1183+
return std::move(Builder).Build();
11761184
}
11771185

11781186
} // namespace

0 commit comments

Comments
 (0)