This is an archive of the discontinued LLVM Phabricator instance.

[clang][dataflow] In `optional` model, match call return via hasType
ClosedPublic

Authored by samestep on Jun 9 2022, 12:18 PM.

Details

Summary

Currently the unchecked-optional-access model fails on this example:

#include <memory>
#include <optional>

void foo() {
  std::unique_ptr<std::optional<float>> x;
  *x = std::nullopt;
}

You can verify the failure by saving the file as foo.cpp and running this command:

clang-tidy -checks='-*,bugprone-unchecked-optional-access' foo.cpp -- -std=c++17

The failing assert is in the transferAssignment function of the UncheckedOptionalAccessModel.cpp file:

assert(OptionalLoc != nullptr);

The symptom can be treated by replacing that assert with an early return:

if (OptionalLoc == nullptr)
  return;

That would be better anyway since we cannot expect to always cover all possible LHS expressions, but it is out of scope for this patch and left as a followup.

Note that the failure did not occur on this very similar example:

#include <optional>

template <typename T>
struct smart_ptr {
  T& operator*() &;
  T* operator->();
};

void foo() {
  smart_ptr<std::optional<float>> x;
  *x = std::nullopt;
}

The difference is caused by the isCallReturningOptional matcher, which was previously checking the functionDecl of the callee. This patch changes it to instead use hasType directly on the call expression, fixing the failure for the std::unique_ptr example above.

Diff Detail

Event Timeline

samestep created this revision.Jun 9 2022, 12:18 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 9 2022, 12:18 PM
samestep requested review of this revision.Jun 9 2022, 12:18 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 9 2022, 12:18 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript
samestep retitled this revision from [clang][dataflow] Match call return via hasType Currently the unchecked-optional-access model fails on this example: #include <memory> #include <optional> void foo() { std::unique_ptr<std::optional<float>> x; *x = std... to [clang][dataflow] Match call return via hasTypeCurrently the unchecked-optional-access model fails on this example: #include <memory> #include <optional> void foo() { std::unique_ptr<std::optional<float>> x; *x = std....Jun 9 2022, 12:18 PM
samestep edited the summary of this revision. (Show Details)
samestep edited the summary of this revision. (Show Details)
samestep retitled this revision from [clang][dataflow] Match call return via hasTypeCurrently the unchecked-optional-access model fails on this example: #include <memory> #include <optional> void foo() { std::unique_ptr<std::optional<float>> x; *x = std... to [clang][dataflow] Match call return via hasType.Jun 9 2022, 12:21 PM
ymandel retitled this revision from [clang][dataflow] Match call return via hasType to [clang][dataflow] In `optional` model, match call return via hasType.Jun 9 2022, 12:23 PM
ymandel added a reviewer: sgatev.

Is there a test you can add that would cover this change?

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

Since we're using the ast_matchers namespace, no need to qualify. Also, there's a type alias TypeMatcher that's not in the internal namespace which would be good here. That said, I'd use QualType and write it like this:

auto TypeMatcher = qualType(anyOf(...));

Or, I think even better in this case, just inline it:

callExpr(hasType(anyOf(...)))

A test would definitely be good, but I'm not sure how to add one to UncheckedOptionalAccessModelTest.cpp, since the issue disappears when we replace std::unique_ptr with our custom simple smart_ptr. Any ideas?

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

I initially tried inlining it like you suggested, but got a compile error about multiple matching overloads. Adding qualType fixed that though, thanks!

samestep updated this revision to Diff 435649.Jun 9 2022, 12:42 PM

Simplify body using qualType

I see that the declaration of operator* in std::unique_ptr is typename add_lvalue_reference<T>::type operator*() const;. I managed to reproduce the crash with the following snippet:

#include <optional>

namespace detail {
 
template <class T>
struct type_identity { using type = T; };

template <class T>
auto try_add_lvalue_reference(int) -> type_identity<T&>;

template <class T>
auto try_add_lvalue_reference(...) -> type_identity<T>;

template <class T>
auto try_add_rvalue_reference(int) -> type_identity<T&&>;

template <class T>
auto try_add_rvalue_reference(...) -> type_identity<T>;

}  // namespace detail

template <class T>
struct add_lvalue_reference : decltype(detail::try_add_lvalue_reference<T>(0)) {};

template <class T>
struct add_rvalue_reference : decltype(detail::try_add_rvalue_reference<T>(0)) {};

template <typename T>
struct smart_ptr {
  typename add_lvalue_reference<T>::type operator*() &;
};

void foo() {
  smart_ptr<std::optional<float>> x;
  *x = std::nullopt;
}

I suggest adding the definition of add_lvalue_reference to StdTypeTraitsHeader and adding a test with the rest of the code above.

@sgatev Oh nice, thank you for figuring that out! @ymandel and I saw that on Wednesday and tried for a while to figure out how to encode it into a test using the sample implementation from cppreference, but I think we kept forgetting the ::type part. I'll add this as a test!

samestep updated this revision to Diff 435873.Jun 10 2022, 4:37 AM

Add a test

sgatev accepted this revision.Jun 10 2022, 4:53 AM
This revision is now accepted and ready to land.Jun 10 2022, 4:53 AM
This revision was landed with ongoing or failed builds.Jun 10 2022, 7:52 AM
This revision was automatically updated to reflect the committed changes.