Skip to content

[clang-tidy] bugprone-unchecked-optional-access: handle BloombergLP::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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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-warning
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,20 @@ 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);
}
})

Comment on lines +988 to +1001
Copy link
Contributor

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.

Copy link
Contributor Author

@BaLiKfromUA BaLiKfromUA Jun 16, 2025

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 for folly::Optional in code above my change.

Thanks for review!

Copy link
Contributor

Choose a reason for hiding this comment

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

You can look at no-malloc check for similar option with function names.

You can add function-lists both for bdlb and other libraries. The code must be very similar.

Copy link
Contributor Author

@BaLiKfromUA BaLiKfromUA Jun 23, 2025

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 of no-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:NullableValue
NonStdHasValue: hasValue. // folly::Optional
NonStdNoValue: 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.

Copy link
Contributor

@vbvictor vbvictor Jun 24, 2025

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:

struct optional1() {
   hasNull(); // check that it has value but it's nullptr
   isNull(); // check that it has no value inside
}

struct optional2() {
   hasNull(); // check that it has no value inside
}

If we go with 1st option, we would write NonStdNoValue: hasNull;isNull, but that would produce errors because optional1::hasNull doesn't actually check for no value, optional1::isNull does.
To express intentional behavior, we need to write NonStdNoValue: optional2::hasNull;optional1::isNull.

Copy link
Contributor

@vbvictor vbvictor Jun 24, 2025

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 with optional1 and optional2 wouldn't likely appear in real life IMO.

// optional::emplace
.CaseOfCFGStmt<CXXMemberCallExpr>(
isOptionalMemberCallWithNameMatcher(hasName("emplace")),
Expand Down
Loading