Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
3 changes: 3 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ Changes in existing checks
and accessing ``value``. The option `IgnoreSmartPointerDereference` should
no longer be needed and will be removed.

- Improved :doc:`bugprone-unchecked-optional-access
<clang-tidy/checks/bugprone/unchecked-optional-access>` by fixing false positive from const reference accessors to objects containing optional member.

- Improved :doc:`bugprone-unsafe-functions
<clang-tidy/checks/bugprone/unsafe-functions>` check to allow specifying
additional C++ member functions to match.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,22 @@ void handleConstMemberCall(const CallExpr *CE,
return;
}

// Cache if the const method returns a reference
if (RecordLoc != nullptr && CE->isGLValue()) {
const FunctionDecl *DirectCallee = CE->getDirectCallee();
if (DirectCallee == nullptr)
return;

StorageLocation &Loc =
State.Lattice.getOrCreateConstMethodReturnStorageLocation(
*RecordLoc, DirectCallee, State.Env, [&](StorageLocation &Loc) {
// no-op
});

State.Env.setStorageLocation(*CE, Loc);
return;
}

// Cache if the const method returns a boolean or pointer type.
// We may decide to cache other return types in the future.
if (RecordLoc != nullptr &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3863,6 +3863,200 @@ TEST_P(UncheckedOptionalAccessTest, ConstBoolAccessorWithModInBetween) {
)cc");
}

TEST_P(UncheckedOptionalAccessTest,
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObject) {
ExpectDiagnosticsFor(R"cc(
#include "unchecked_optional_access_test.h"

struct A {
const $ns::$optional<int>& get() const { return x; }

$ns::$optional<int> x;
};

struct B {
const A& getA() const { return a; }

A a;
};

void target(B& b) {
if (b.getA().get().has_value()) {
b.getA().get().value();
}
}
)cc");
}

TEST_P(
UncheckedOptionalAccessTest,
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObjectWithoutValueCheck) {
ExpectDiagnosticsFor(R"cc(
#include "unchecked_optional_access_test.h"

struct A {
const $ns::$optional<int>& get() const { return x; }

$ns::$optional<int> x;
};

struct B {
const A& getA() const { return a; }

A a;
};

void target(B& b) {
b.getA().get().value(); // [[unsafe]]
}
)cc");
}

TEST_P(UncheckedOptionalAccessTest,
ConstRefToOptionalSavedAsTemporaryVariable) {
ExpectDiagnosticsFor(R"cc(
#include "unchecked_optional_access_test.h"

struct A {
const $ns::$optional<int>& get() const { return x; }

$ns::$optional<int> x;
};

struct B {
const A& getA() const { return a; }

A a;
};

void target(B& b) {
const auto& opt = b.getA().get();
if (opt.has_value()) {
opt.value();
}
}
)cc");
}

TEST_P(UncheckedOptionalAccessTest,
ConstRefAccessorToOptionalViaAccessorToHoldingObjectByValue) {
ExpectDiagnosticsFor(R"cc(
#include "unchecked_optional_access_test.h"

struct A {
const $ns::$optional<int>& get() const { return x; }

$ns::$optional<int> x;
};

struct B {
const A copyA() const { return a; }

A a;
};

void target(B& b) {
if (b.copyA().get().has_value()) {
b.copyA().get().value(); // [[unsafe]]
}
}
)cc");
}

TEST_P(UncheckedOptionalAccessTest,
ConstRefAccessorToOptionalViaNonConstRefAccessorToHoldingObject) {
ExpectDiagnosticsFor(R"cc(
#include "unchecked_optional_access_test.h"

struct A {
const $ns::$optional<int>& get() const { return x; }

$ns::$optional<int> x;
};

struct B {
A& getA() { return a; }

A a;
};

void target(B& b) {
if (b.getA().get().has_value()) {
b.getA().get().value(); // [[unsafe]]
}
}
)cc");
}

TEST_P(
UncheckedOptionalAccessTest,
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObjectWithModAfterCheck) {
ExpectDiagnosticsFor(R"cc(
#include "unchecked_optional_access_test.h"

struct A {
const $ns::$optional<int>& get() const { return x; }

$ns::$optional<int> x;
};

struct B {
const A& getA() const { return a; }

A& getA() { return a; }

void clear() { a = A{}; }

A a;
};

void target(B& b) {
// changing field A via non-const getter after const getter check
if (b.getA().get().has_value()) {
b.getA() = A{};
b.getA().get().value(); // [[unsafe]]
}

// calling non-const method which might change field A
if (b.getA().get().has_value()) {
b.clear();
b.getA().get().value(); // [[unsafe]]
}
}
)cc");
}

TEST_P(
UncheckedOptionalAccessTest,
ConstRefAccessorToOptionalViaConstRefAccessorToHoldingObjectWithAnotherConstCallAfterCheck) {
ExpectDiagnosticsFor(R"cc(
#include "unchecked_optional_access_test.h"

struct A {
const $ns::$optional<int>& get() const { return x; }

$ns::$optional<int> x;
};

struct B {
const A& getA() const { return a; }

void callWithoutChanges() const {
// no-op
}

A a;
};

void target(B& b) {
if (b.getA().get().has_value()) {
b.callWithoutChanges(); // calling const method which cannot change A
b.getA().get().value();
}
}
)cc");
}

// FIXME: Add support for:
// - constructors (copy, move)
// - assignment operators (default, copy, move)
Expand Down