Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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 @@ -551,83 +551,92 @@ void transferCallReturningOptional(const CallExpr *E,
setHasValue(*Loc, State.Env.makeAtomicBoolValue(), State.Env);
}

void handleConstMemberCall(const CallExpr *CE,
// Returns true if the const accessor is handled by caching.
// Returns false if we could not cache. We should perform default handling
// in that case.
bool handleConstMemberCall(const CallExpr *CE,
dataflow::RecordStorageLocation *RecordLoc,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
// If the const method returns an optional or reference to an optional.
if (RecordLoc != nullptr && isSupportedOptionalType(CE->getType())) {
const FunctionDecl *DirectCallee = CE->getDirectCallee();
if (DirectCallee == nullptr)
return;
StorageLocation &Loc =
State.Lattice.getOrCreateConstMethodReturnStorageLocation(
*RecordLoc, DirectCallee, State.Env, [&](StorageLocation &Loc) {
setHasValue(cast<RecordStorageLocation>(Loc),
State.Env.makeAtomicBoolValue(), State.Env);
});
if (CE->isGLValue()) {
// If the call to the const method returns a reference to an optional,
// link the call expression to the cached StorageLocation.
State.Env.setStorageLocation(*CE, Loc);
} else {
// If the call to the const method returns an optional by value, we
// need to use CopyRecord to link the optional to the result object
// of the call expression.
auto &ResultLoc = State.Env.getResultObjectLocation(*CE);
copyRecord(cast<RecordStorageLocation>(Loc), ResultLoc, State.Env);
}
return;
}
if (RecordLoc == nullptr)
return false;

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

// Initialize the optional's "has_value" property to true if the type is
// optional, otherwise no-op. If we want to support const ref to pointers or
// bools we should initialize their values here too.
auto Init = [&](StorageLocation &Loc) {
if (isSupportedOptionalType(CE->getType()))
setHasValue(cast<RecordStorageLocation>(Loc),
State.Env.makeAtomicBoolValue(), State.Env);
};
StorageLocation &Loc =
State.Lattice.getOrCreateConstMethodReturnStorageLocation(
*RecordLoc, DirectCallee, State.Env, [&](StorageLocation &Loc) {
// no-op
});
*RecordLoc, DirectCallee, State.Env, Init);

State.Env.setStorageLocation(*CE, Loc);
return;
return true;
} else {
// PRValue cases:
if (CE->getType()->isBooleanType() || CE->getType()->isPointerType()) {
// If the const method returns a boolean or pointer type.
Value *Val = State.Lattice.getOrCreateConstMethodReturnValue(
*RecordLoc, CE, State.Env);
if (Val == nullptr)
return false;
State.Env.setValue(*CE, *Val);
return true;
} else if (isSupportedOptionalType(CE->getType())) {
// If the const method returns an optional by value.
const FunctionDecl *DirectCallee = CE->getDirectCallee();
if (DirectCallee == nullptr)
return false;
StorageLocation &Loc =
State.Lattice.getOrCreateConstMethodReturnStorageLocation(
*RecordLoc, DirectCallee, State.Env, [&](StorageLocation &Loc) {
setHasValue(cast<RecordStorageLocation>(Loc),
State.Env.makeAtomicBoolValue(), State.Env);
});
// Use copyRecord to link the optional to the result object of the call
// expression.
auto &ResultLoc = State.Env.getResultObjectLocation(*CE);
copyRecord(cast<RecordStorageLocation>(Loc), ResultLoc, State.Env);
return true;
}
}

// 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 &&
(CE->getType()->isBooleanType() || CE->getType()->isPointerType())) {
Value *Val = State.Lattice.getOrCreateConstMethodReturnValue(*RecordLoc, CE,
State.Env);
if (Val == nullptr)
return;
State.Env.setValue(*CE, *Val);
return;
}
return false;
}

// Perform default handling if the call returns an optional
// but wasn't handled above (if RecordLoc is nullptr).
if (isSupportedOptionalType(CE->getType())) {
void handleConstMemberCallWithFallbacks(
const CallExpr *CE, dataflow::RecordStorageLocation *RecordLoc,
const MatchFinder::MatchResult &Result, LatticeTransferState &State) {
if (handleConstMemberCall(CE, RecordLoc, Result, State))
return;
// Perform default handling if the call returns an optional, but wasn't
// handled by caching.
if (isSupportedOptionalType(CE->getType()))
transferCallReturningOptional(CE, Result, State);
}
}

void transferValue_ConstMemberCall(const CXXMemberCallExpr *MCE,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
handleConstMemberCall(
void transferConstMemberCall(const CXXMemberCallExpr *MCE,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
handleConstMemberCallWithFallbacks(
MCE, dataflow::getImplicitObjectLocation(*MCE, State.Env), Result, State);
}

void transferValue_ConstMemberOperatorCall(
const CXXOperatorCallExpr *OCE, const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
void transferConstMemberOperatorCall(const CXXOperatorCallExpr *OCE,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
auto *RecordLoc = cast_or_null<dataflow::RecordStorageLocation>(
State.Env.getStorageLocation(*OCE->getArg(0)));
handleConstMemberCall(OCE, RecordLoc, Result, State);
handleConstMemberCallWithFallbacks(OCE, RecordLoc, Result, State);
}

void handleNonConstMemberCall(const CallExpr *CE,
Expand Down Expand Up @@ -1094,9 +1103,9 @@ auto buildTransferMatchSwitch() {

// const accessor calls
.CaseOfCFGStmt<CXXMemberCallExpr>(isZeroParamConstMemberCall(),
transferValue_ConstMemberCall)
transferConstMemberCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isZeroParamConstMemberOperatorCall(),
transferValue_ConstMemberOperatorCall)
transferConstMemberOperatorCall)
// non-const member calls that may modify the state of an object.
.CaseOfCFGStmt<CXXMemberCallExpr>(isNonConstMemberCall(),
transferValue_NonConstMemberCall)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3829,7 +3829,7 @@ TEST_P(UncheckedOptionalAccessTest, ConstBoolAccessor) {
};

void target(A& a) {
std::optional<int> opt;
$ns::$optional<int> opt;
if (a.isFoo()) {
opt = 1;
}
Expand All @@ -3851,7 +3851,7 @@ TEST_P(UncheckedOptionalAccessTest, ConstBoolAccessorWithModInBetween) {
};

void target(A& a) {
std::optional<int> opt;
$ns::$optional<int> opt;
if (a.isFoo()) {
opt = 1;
}
Expand All @@ -3870,13 +3870,13 @@ TEST_P(UncheckedOptionalAccessTest,

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

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

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

A a;
};

Expand All @@ -3896,13 +3896,13 @@ TEST_P(

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

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

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

A a;
};

Expand All @@ -3919,13 +3919,13 @@ TEST_P(UncheckedOptionalAccessTest,

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

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

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

A a;
};

Expand All @@ -3945,13 +3945,13 @@ TEST_P(UncheckedOptionalAccessTest,

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

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

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

A a;
};

Expand All @@ -3970,13 +3970,13 @@ TEST_P(UncheckedOptionalAccessTest,

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

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

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

A a;
};

Expand Down Expand Up @@ -4031,23 +4031,23 @@ TEST_P(
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
Expand All @@ -4057,6 +4057,34 @@ TEST_P(
)cc");
}

TEST_P(UncheckedOptionalAccessTest, ConstPointerRefAccessor) {
// A crash reproducer for https://github.com/llvm/llvm-project/issues/125589
// NOTE: we currently cache const ref accessors's locations.
// If we want to support const ref to pointers or bools, we should initialize
// their values.
ExpectDiagnosticsFor(R"cc(
#include "unchecked_optional_access_test.h"

struct A {
$ns::$optional<int> x;
};

struct PtrWrapper {
A*& getPtrRef() const;
void reset(A*);
};

void target(PtrWrapper p) {
if (p.getPtrRef()->x) {
*p.getPtrRef()->x; // [[unsafe]]
p.reset(nullptr);
*p.getPtrRef()->x; // [[unsafe]]
}
}
)cc",
/*IgnoreSmartPointerDereference=*/false);
}

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