- Notifications
You must be signed in to change notification settings - Fork14.5k
[clang-tidy]bugprone-unchecked-optional-access
: handleBloombergLP::bdlb:NullableValue::makeValue
to prevent false-positives#144313
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
base:main
Are you sure you want to change the base?
Conversation
bugprone-unchecked-optional-access
: handleNullableValue::makeValue
to prevent false-positivesbugprone-unchecked-optional-access
: handleBloombergLP::bdlb:NullableValue::makeValue
to prevent false-positivesllvmbot commentedJun 16, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
@llvm/pr-subscribers-clang-tidy @llvm/pr-subscribers-clang-analysis Author: Valentyn Yukhymenko (BaLiKfromUA) Changes#101450 added support for However, if (opt.isNull()) { opt.makeValue(42); } opt.value();// triggers false positive warning from `bugprone-unchecked-optional-access` My patch addresses this issue. Full diff:https://github.com/llvm/llvm-project/pull/144313.diff 3 Files Affected:
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bdlb_nullablevalue.h b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bdlb_nullablevalue.hindex 4411bcfd60a74..0812677111995 100644--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bdlb_nullablevalue.h+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/Inputs/unchecked-optional-access/bde/types/bdlb_nullablevalue.h@@ -20,6 +20,14 @@ class NullableValue : public bsl::optional<T> { const T &value() const &; T &value() &;+ constexpr T &makeValue();++ template <typename U>+ constexpr T &makeValue(U&& v);++ template <typename... ARGS>+ constexpr T &makeValueInplace(ARGS &&... args);+ // 'operator bool' is inherited from bsl::optional constexpr bool isNull() const noexcept;diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cppindex 3167b85f0e024..b910db20b3c2e 100644--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone/unchecked-optional-access.cpp@@ -141,6 +141,20 @@ void nullable_value_after_swap(BloombergLP::bdlb::NullableValue<int> &opt1, Bloo } }+void nullable_value_make_value(BloombergLP::bdlb::NullableValue<int> &opt1, BloombergLP::bdlb::NullableValue<int> &opt2) {+ if (opt1.isNull()) {+ opt1.makeValue(42);+ }++ opt1.value();++ if (opt2.isNull()) {+ opt2.makeValueInplace(42);+ }++ opt2.value();+}+ template <typename T> void function_template_without_user(const absl::optional<T> &opt) { opt.value(); // no-warningdiff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cppindex 164d2574132dd..decb32daa9410 100644--- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp+++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp@@ -985,6 +985,19 @@ auto buildTransferMatchSwitch() { isOptionalMemberCallWithNameMatcher(hasName("isNull")), transferOptionalIsNullCall)+ // NullableValue::makeValue, NullableValue::makeValueInplace+ // Only NullableValue has these methods, but this+ // will also pass for other types+ .CaseOfCFGStmt<CXXMemberCallExpr>(+ isOptionalMemberCallWithNameMatcher(hasAnyName("makeValue", "makeValueInplace")),+ [](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,+ LatticeTransferState &State) {+ if (RecordStorageLocation *Loc =+ getImplicitObjectLocation(*E, State.Env)) {+ setHasValue(*Loc, State.Env.getBoolLiteralValue(true), State.Env);+ }+ })+ // optional::emplace .CaseOfCFGStmt<CXXMemberCallExpr>( isOptionalMemberCallWithNameMatcher(hasName("emplace")), |
At the moment of the previous PR, there was a concern about configurability of check --#101450 (review) I am happy to address it separately if it's still a concern and if someone can guide me for first steps :) |
github-actionsbot commentedJun 16, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
✅ With the latest revision this PR passed the C/C++ code formatter. |
I think I was about adding an option to configure list of optional classes. Probably |
// NullableValue::makeValue, NullableValue::makeValueInplace | ||
// Only NullableValue has these methods, but this | ||
// will also pass for other types | ||
.CaseOfCFGStmt<CXXMemberCallExpr>( | ||
isOptionalMemberCallWithNameMatcher( | ||
hasAnyName("makeValue", "makeValueInplace")), | ||
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &, | ||
LatticeTransferState &State) { | ||
if (RecordStorageLocation *Loc = | ||
getImplicitObjectLocation(*E, State.Env)) { | ||
setHasValue(*Loc, State.Env.getBoolLiteralValue(true), State.Env); | ||
} | ||
}) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I think configurable option is needed instead of hard code function name for non-std libarary.
BaLiKfromUAJun 16, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@HerrCai0907 I am happy to do so! Are there any examples how it looks for other clang-tidy checks?
Also, do you want to do it in scope of this PR or as a separate one?
Since non-std function names (e.g.hasValue
) are also processed forfolly::Optional
in code above my change.
Thanks for review!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
You can look atno-malloc check for similar option with function names.
You can add function-lists both forbdlb
and other libraries. The code must be very similar.
BaLiKfromUAJun 23, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@vbvictor, thank you for the help! I checked the implementation ofno-malloc
configuration, and it does make sense to me.
Just want to double-check expectation, do we want to have a configuration like this, with just method names (name of options is subject to change):
NonStdMakeValue: makeValue;makeValueInplace.// BloombergLP::bdlb:NullableValueNonStdHasValue: hasValue.// folly::OptionalNonStdNoValue: isNull.// BloombergLP::bdlb:NullableValue
Or do we want to specify which class method belongs to:
NonStdMakeValue: BloombergLP::bdlb:NullableValue::makeValue;BloombergLP::bdlb:NullableValue::makeValueInplace.NonStdHasValue: folly::Optional::hasValue. NonStdNoValue: BloombergLP::bdlb:NullableValue::isNull.
For me, the first version looks better because:
- It's easier
- It makes it possible to easily enable this check for more "make value" custom methods in classes inherited from the optional.
However, it's less intuitive to me than option two because different non-standard methods belong to different classes. But this is how it works now, so maybe we could build on top of it, just with method names, and then improve it or extend it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'd prefer 2nd option because we have more control over what method belong to what class. Imagine this situation:
structoptional1() {hasNull();// check that it has value but it's nullptrisNull();// check that it has no value inside}structoptional2() {hasNull();// check that it has no value inside}
If we go with 1st option, we would writeNonStdNoValue: hasNull;isNull
, but that would produce errors becauseoptional1::hasNull
doesn't actually check for no value,optional1::isNull
does.
To express intentional behavior, we need to writeNonStdNoValue: optional2::hasNull;optional1::isNull
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
But if it works like option1 right now, I don't oppose implementing option1.
I suppose we could harden it in the future if ever needed. My example withoptional1
andoptional2
wouldn't likely appear in real life IMO.
BaLiKfromUAJul 6, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I started experimenting with implementing the mentioned configuration and have a doubt that I would like to talk about.
What is the preferred way to passUncheckedOptionalAccessModelOptions
intoUncheckedOptionalAccessModel
?
Currently, the existing configuration (IgnoreSmartPointerDereference
) is being passed only intoUncheckedOptionalAccessDiagnoser
, which is not what I need in my case.
If I understand correctly, passingOptions
intoModel
requires changes toDataflowAnalysis.h
.
The "cheapest" solution might be to implement code like thishere:
template<typename AnalysisT,typename OptionsT>autocreateAnalysis(ASTContext &ASTCtx, Environment &Env, OptionsT &Options) -> decltype(AnalysisT(ASTCtx, Env, OptionsT)) {returnAnalysisT(ASTCtx, Env, Options);}
Plus, modify the code that calls it and extend the constructor of theUncheckedOptionalAccessModel
.
But,theFIXME
comment nearby highlights that this approach is rather a hack and should be refactored later.
I also see that there isDataflowAnalysisContext::Options
, but I am not sure if it's super useful for my case.
@vbvictor would appreciate your feedback!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
I'll defer to@ymand on the final decision what is the best way to do this. But in my opinion, this make a strong argument that the analysis instance should always be created by the client (e.g., we should pass the analysis instance todiagnoseFunction
and similar) as all analyses might need different configuration options and we definitely don't want to deal with all that in a generic way in the framework.
Analysis reviewers, could you please take a look at this#144313 (comment). Your opinion is highly appreciated! |
Uh oh!
There was an error while loading.Please reload this page.
#101450 added support for
BloombergLP::bdlb::NullableValue
.However,
NullableValue::makeValue
andNullableValue::makeValueInplace
have been missed which impacts code like this:My patch addresses this issue.
Docs that I used for methods mocks