#include "clang/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/Stmt.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersMacros.h"
#include "clang/Analysis/CFG.h"
#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/Formula.h"
#include "clang/Analysis/FlowSensitive/NoopLattice.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/ErrorHandling.h"
#include <cassert>
#include <memory>
#include <optional>
#include <utility>
namespace clang {
namespace dataflow {
static bool isTopLevelNamespaceWithName(const NamespaceDecl &NS,
llvm::StringRef Name) {
return NS.getDeclName().isIdentifier() && NS.getName() == Name &&
NS.getParent() != nullptr && NS.getParent()->isTranslationUnit();
}
static bool hasOptionalClassName(const CXXRecordDecl &RD) {
if (!RD.getDeclName().isIdentifier())
return false;
if (RD.getName() == "optional") {
if (const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext()))
return N->isStdNamespace() || isTopLevelNamespaceWithName(*N, "absl");
return false;
}
if (RD.getName() == "Optional") {
const auto *N = dyn_cast_or_null<NamespaceDecl>(RD.getDeclContext());
return N != nullptr && (isTopLevelNamespaceWithName(*N, "base") ||
isTopLevelNamespaceWithName(*N, "folly"));
}
return false;
}
static const CXXRecordDecl *getOptionalBaseClass(const CXXRecordDecl *RD) {
if (RD == nullptr)
return nullptr;
if (hasOptionalClassName(*RD))
return RD;
if (!RD->hasDefinition())
return nullptr;
for (const CXXBaseSpecifier &Base : RD->bases())
if (const CXXRecordDecl *BaseClass =
getOptionalBaseClass(Base.getType()->getAsCXXRecordDecl()))
return BaseClass;
return nullptr;
}
namespace {
using namespace ::clang::ast_matchers;
using LatticeTransferState = TransferState<NoopLattice>;
AST_MATCHER(CXXRecordDecl, optionalClass) { return hasOptionalClassName(Node); }
AST_MATCHER(CXXRecordDecl, optionalOrDerivedClass) {
return getOptionalBaseClass(&Node) != nullptr;
}
auto desugarsToOptionalType() {
return hasUnqualifiedDesugaredType(
recordType(hasDeclaration(cxxRecordDecl(optionalClass()))));
}
auto desugarsToOptionalOrDerivedType() {
return hasUnqualifiedDesugaredType(
recordType(hasDeclaration(cxxRecordDecl(optionalOrDerivedClass()))));
}
auto hasOptionalType() { return hasType(desugarsToOptionalType()); }
auto hasOptionalOrDerivedType() {
return hasType(desugarsToOptionalOrDerivedType());
}
QualType getPublicType(const Expr *E) {
auto *Cast = dyn_cast<ImplicitCastExpr>(E->IgnoreParens());
if (Cast == nullptr || Cast->getCastKind() != CK_UncheckedDerivedToBase) {
QualType Ty = E->getType();
if (Ty->isPointerType())
return Ty->getPointeeType();
return Ty;
}
bool CastingFromThis = isa<CXXThisExpr>(Cast->getSubExpr());
const CXXBaseSpecifier *PublicBase = nullptr;
for (const CXXBaseSpecifier *Base : Cast->path()) {
if (Base->getAccessSpecifier() != AS_public && !CastingFromThis)
break;
PublicBase = Base;
CastingFromThis = false;
}
if (PublicBase != nullptr)
return PublicBase->getType();
return getPublicType(Cast->getSubExpr());
}
QualType getPublicReceiverType(const CXXMemberCallExpr &MCE) {
return getPublicType(MCE.getImplicitObjectArgument());
}
AST_MATCHER_P(CXXMemberCallExpr, publicReceiverType,
ast_matchers::internal::Matcher<QualType>, InnerMatcher) {
return InnerMatcher.matches(getPublicReceiverType(Node), Finder, Builder);
}
auto isOptionalMemberCallWithNameMatcher(
ast_matchers::internal::Matcher<NamedDecl> matcher,
const std::optional<StatementMatcher> &Ignorable = std::nullopt) {
return cxxMemberCallExpr(Ignorable ? on(expr(unless(*Ignorable)))
: anything(),
publicReceiverType(desugarsToOptionalType()),
callee(cxxMethodDecl(matcher)));
}
auto isOptionalOperatorCallWithName(
llvm::StringRef operator_name,
const std::optional<StatementMatcher> &Ignorable = std::nullopt) {
return cxxOperatorCallExpr(
hasOverloadedOperatorName(operator_name),
callee(cxxMethodDecl(ofClass(optionalClass()))),
Ignorable ? callExpr(unless(hasArgument(0, *Ignorable))) : callExpr());
}
auto isMakeOptionalCall() {
return callExpr(callee(functionDecl(hasAnyName(
"std::make_optional", "base::make_optional",
"absl::make_optional", "folly::make_optional"))),
hasOptionalType());
}
auto nulloptTypeDecl() {
return namedDecl(hasAnyName("std::nullopt_t", "absl::nullopt_t",
"base::nullopt_t", "folly::None"));
}
auto hasNulloptType() { return hasType(nulloptTypeDecl()); }
auto inPlaceClass() {
return recordDecl(hasAnyName("std::in_place_t", "absl::in_place_t",
"base::in_place_t", "folly::in_place_t"));
}
auto isOptionalNulloptConstructor() {
return cxxConstructExpr(
hasDeclaration(cxxConstructorDecl(parameterCountIs(1),
hasParameter(0, hasNulloptType()))),
hasOptionalOrDerivedType());
}
auto isOptionalInPlaceConstructor() {
return cxxConstructExpr(hasArgument(0, hasType(inPlaceClass())),
hasOptionalOrDerivedType());
}
auto isOptionalValueOrConversionConstructor() {
return cxxConstructExpr(
unless(hasDeclaration(
cxxConstructorDecl(anyOf(isCopyConstructor(), isMoveConstructor())))),
argumentCountIs(1), hasArgument(0, unless(hasNulloptType())),
hasOptionalOrDerivedType());
}
auto isOptionalValueOrConversionAssignment() {
return cxxOperatorCallExpr(
hasOverloadedOperatorName("="),
callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),
unless(hasDeclaration(cxxMethodDecl(
anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator())))),
argumentCountIs(2), hasArgument(1, unless(hasNulloptType())));
}
auto isOptionalNulloptAssignment() {
return cxxOperatorCallExpr(
hasOverloadedOperatorName("="),
callee(cxxMethodDecl(ofClass(optionalOrDerivedClass()))),
argumentCountIs(2), hasArgument(1, hasNulloptType()));
}
auto isStdSwapCall() {
return callExpr(callee(functionDecl(hasName("std::swap"))),
argumentCountIs(2),
hasArgument(0, hasOptionalOrDerivedType()),
hasArgument(1, hasOptionalOrDerivedType()));
}
auto isStdForwardCall() {
return callExpr(callee(functionDecl(hasName("std::forward"))),
argumentCountIs(1),
hasArgument(0, hasOptionalOrDerivedType()));
}
constexpr llvm::StringLiteral ValueOrCallID = "ValueOrCall";
auto isValueOrStringEmptyCall() {
return cxxMemberCallExpr(
callee(cxxMethodDecl(hasName("empty"))),
onImplicitObjectArgument(ignoringImplicit(
cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
callee(cxxMethodDecl(hasName("value_or"),
ofClass(optionalClass()))),
hasArgument(0, stringLiteral(hasSize(0))))
.bind(ValueOrCallID))));
}
auto isValueOrNotEqX() {
auto ComparesToSame = [](ast_matchers::internal::Matcher<Stmt> Arg) {
return hasOperands(
ignoringImplicit(
cxxMemberCallExpr(on(expr(unless(cxxThisExpr()))),
callee(cxxMethodDecl(hasName("value_or"),
ofClass(optionalClass()))),
hasArgument(0, Arg))
.bind(ValueOrCallID)),
ignoringImplicit(Arg));
};
return binaryOperation(hasOperatorName("!="),
anyOf(ComparesToSame(cxxNullPtrLiteralExpr()),
ComparesToSame(stringLiteral(hasSize(0))),
ComparesToSame(integerLiteral(equals(0)))));
}
auto isCallReturningOptional() {
return callExpr(hasType(qualType(
anyOf(desugarsToOptionalOrDerivedType(),
referenceType(pointee(desugarsToOptionalOrDerivedType()))))));
}
template <typename L, typename R>
auto isComparisonOperatorCall(L lhs_arg_matcher, R rhs_arg_matcher) {
return cxxOperatorCallExpr(
anyOf(hasOverloadedOperatorName("=="), hasOverloadedOperatorName("!=")),
argumentCountIs(2), hasArgument(0, lhs_arg_matcher),
hasArgument(1, rhs_arg_matcher));
}
const Formula &forceBoolValue(Environment &Env, const Expr &Expr) {
auto *Value = Env.get<BoolValue>(Expr);
if (Value != nullptr)
return Value->formula();
Value = &Env.makeAtomicBoolValue();
Env.setValue(Expr, *Value);
return Value->formula();
}
StorageLocation &locForHasValue(const RecordStorageLocation &OptionalLoc) {
return OptionalLoc.getSyntheticField("has_value");
}
StorageLocation &locForValue(const RecordStorageLocation &OptionalLoc) {
return OptionalLoc.getSyntheticField("value");
}
void setHasValue(RecordStorageLocation &OptionalLoc, BoolValue &HasValueVal,
Environment &Env) {
Env.setValue(locForHasValue(OptionalLoc), HasValueVal);
}
BoolValue *getHasValue(Environment &Env, RecordStorageLocation *OptionalLoc) {
if (OptionalLoc == nullptr)
return nullptr;
StorageLocation &HasValueLoc = locForHasValue(*OptionalLoc);
auto *HasValueVal = Env.get<BoolValue>(HasValueLoc);
if (HasValueVal == nullptr) {
HasValueVal = &Env.makeAtomicBoolValue();
Env.setValue(HasValueLoc, *HasValueVal);
}
return HasValueVal;
}
QualType valueTypeFromOptionalDecl(const CXXRecordDecl &RD) {
auto &CTSD = cast<ClassTemplateSpecializationDecl>(RD);
return CTSD.getTemplateArgs()[0].getAsType();
}
int countOptionalWrappers(const ASTContext &ASTCtx, QualType Type) {
const CXXRecordDecl *Optional =
getOptionalBaseClass(Type->getAsCXXRecordDecl());
if (Optional == nullptr)
return 0;
return 1 + countOptionalWrappers(
ASTCtx,
valueTypeFromOptionalDecl(*Optional).getDesugaredType(ASTCtx));
}
StorageLocation *getLocBehindPossiblePointer(const Expr &E,
const Environment &Env) {
if (E.isPRValue()) {
if (auto *PointerVal = dyn_cast_or_null<PointerValue>(Env.getValue(E)))
return &PointerVal->getPointeeLoc();
return nullptr;
}
return Env.getStorageLocation(E);
}
void transferUnwrapCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,
LatticeTransferState &State) {
if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
getLocBehindPossiblePointer(*ObjectExpr, State.Env))) {
if (State.Env.getStorageLocation(*UnwrapExpr) == nullptr)
State.Env.setStorageLocation(*UnwrapExpr, locForValue(*OptionalLoc));
}
}
void transferArrowOpCall(const Expr *UnwrapExpr, const Expr *ObjectExpr,
LatticeTransferState &State) {
if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
getLocBehindPossiblePointer(*ObjectExpr, State.Env)))
State.Env.setValue(
*UnwrapExpr, State.Env.create<PointerValue>(locForValue(*OptionalLoc)));
}
void transferMakeOptionalCall(const CallExpr *E,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
setHasValue(State.Env.getResultObjectLocation(*E),
State.Env.getBoolLiteralValue(true), State.Env);
}
void transferOptionalHasValueCall(const CXXMemberCallExpr *CallExpr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
if (auto *HasValueVal = getHasValue(
State.Env, getImplicitObjectLocation(*CallExpr, State.Env))) {
State.Env.setValue(*CallExpr, *HasValueVal);
}
}
void transferValueOrImpl(
const clang::Expr *ValueOrPredExpr, const MatchFinder::MatchResult &Result,
LatticeTransferState &State,
const Formula &(*ModelPred)(Environment &Env, const Formula &ExprVal,
const Formula &HasValueVal)) {
auto &Env = State.Env;
const auto *MCE =
Result.Nodes.getNodeAs<clang::CXXMemberCallExpr>(ValueOrCallID);
auto *HasValueVal =
getHasValue(State.Env, getImplicitObjectLocation(*MCE, State.Env));
if (HasValueVal == nullptr)
return;
Env.assume(ModelPred(Env, forceBoolValue(Env, *ValueOrPredExpr),
HasValueVal->formula()));
}
void transferValueOrStringEmptyCall(const clang::Expr *ComparisonExpr,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
return transferValueOrImpl(ComparisonExpr, Result, State,
[](Environment &Env, const Formula &ExprVal,
const Formula &HasValueVal) -> const Formula & {
auto &A = Env.arena();
return A.makeImplies(A.makeNot(ExprVal),
HasValueVal);
});
}
void transferValueOrNotEqX(const Expr *ComparisonExpr,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
transferValueOrImpl(ComparisonExpr, Result, State,
[](Environment &Env, const Formula &ExprVal,
const Formula &HasValueVal) -> const Formula & {
auto &A = Env.arena();
return A.makeImplies(ExprVal, HasValueVal);
});
}
void transferCallReturningOptional(const CallExpr *E,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
RecordStorageLocation *Loc = nullptr;
if (E->isPRValue()) {
Loc = &State.Env.getResultObjectLocation(*E);
} else {
Loc = State.Env.get<RecordStorageLocation>(*E);
if (Loc == nullptr) {
Loc = &cast<RecordStorageLocation>(State.Env.createStorageLocation(*E));
State.Env.setStorageLocation(*E, *Loc);
}
}
if (State.Env.getValue(locForHasValue(*Loc)) != nullptr)
return;
setHasValue(*Loc, State.Env.makeAtomicBoolValue(), State.Env);
}
void constructOptionalValue(const Expr &E, Environment &Env,
BoolValue &HasValueVal) {
RecordStorageLocation &Loc = Env.getResultObjectLocation(E);
setHasValue(Loc, HasValueVal, Env);
}
BoolValue &valueOrConversionHasValue(QualType DestType, const Expr &E,
const MatchFinder::MatchResult &MatchRes,
LatticeTransferState &State) {
const int DestTypeOptionalWrappersCount =
countOptionalWrappers(*MatchRes.Context, DestType);
const int ArgTypeOptionalWrappersCount = countOptionalWrappers(
*MatchRes.Context, E.getType().getNonReferenceType());
if (DestTypeOptionalWrappersCount != ArgTypeOptionalWrappersCount)
return State.Env.getBoolLiteralValue(true);
auto *Loc = State.Env.get<RecordStorageLocation>(E);
if (auto *HasValueVal = getHasValue(State.Env, Loc))
return *HasValueVal;
return State.Env.makeAtomicBoolValue();
}
void transferValueOrConversionConstructor(
const CXXConstructExpr *E, const MatchFinder::MatchResult &MatchRes,
LatticeTransferState &State) {
assert(E->getNumArgs() > 0);
constructOptionalValue(
*E, State.Env,
valueOrConversionHasValue(
E->getConstructor()->getThisType()->getPointeeType(), *E->getArg(0),
MatchRes, State));
}
void transferAssignment(const CXXOperatorCallExpr *E, BoolValue &HasValueVal,
LatticeTransferState &State) {
assert(E->getNumArgs() > 0);
if (auto *Loc = State.Env.get<RecordStorageLocation>(*E->getArg(0))) {
setHasValue(*Loc, HasValueVal, State.Env);
State.Env.setStorageLocation(*E, *Loc);
}
}
void transferValueOrConversionAssignment(
const CXXOperatorCallExpr *E, const MatchFinder::MatchResult &MatchRes,
LatticeTransferState &State) {
assert(E->getNumArgs() > 1);
transferAssignment(
E,
valueOrConversionHasValue(E->getArg(0)->getType().getNonReferenceType(),
*E->getArg(1), MatchRes, State),
State);
}
void transferNulloptAssignment(const CXXOperatorCallExpr *E,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferAssignment(E, State.Env.getBoolLiteralValue(false), State);
}
void transferSwap(RecordStorageLocation *Loc1, RecordStorageLocation *Loc2,
Environment &Env) {
if (Loc1 == nullptr) {
if (Loc2 != nullptr)
setHasValue(*Loc2, Env.makeAtomicBoolValue(), Env);
return;
}
if (Loc2 == nullptr) {
setHasValue(*Loc1, Env.makeAtomicBoolValue(), Env);
return;
}
BoolValue *BoolVal1 = getHasValue(Env, Loc1);
if (BoolVal1 == nullptr)
BoolVal1 = &Env.makeAtomicBoolValue();
BoolValue *BoolVal2 = getHasValue(Env, Loc2);
if (BoolVal2 == nullptr)
BoolVal2 = &Env.makeAtomicBoolValue();
setHasValue(*Loc1, *BoolVal2, Env);
setHasValue(*Loc2, *BoolVal1, Env);
}
void transferSwapCall(const CXXMemberCallExpr *E,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(E->getNumArgs() == 1);
auto *OtherLoc = State.Env.get<RecordStorageLocation>(*E->getArg(0));
transferSwap(getImplicitObjectLocation(*E, State.Env), OtherLoc, State.Env);
}
void transferStdSwapCall(const CallExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(E->getNumArgs() == 2);
auto *Arg0Loc = State.Env.get<RecordStorageLocation>(*E->getArg(0));
auto *Arg1Loc = State.Env.get<RecordStorageLocation>(*E->getArg(1));
transferSwap(Arg0Loc, Arg1Loc, State.Env);
}
void transferStdForwardCall(const CallExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(E->getNumArgs() == 1);
if (auto *Loc = State.Env.getStorageLocation(*E->getArg(0)))
State.Env.setStorageLocation(*E, *Loc);
}
const Formula &evaluateEquality(Arena &A, const Formula &EqVal,
const Formula &LHS, const Formula &RHS) {
return A.makeAnd(
A.makeImplies(EqVal, A.makeOr(A.makeAnd(LHS, RHS),
A.makeAnd(A.makeNot(LHS), A.makeNot(RHS)))),
A.makeImplies(A.makeNot(EqVal), A.makeOr(LHS, RHS)));
}
void transferOptionalAndOptionalCmp(const clang::CXXOperatorCallExpr *CmpExpr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
Environment &Env = State.Env;
auto &A = Env.arena();
auto *CmpValue = &forceBoolValue(Env, *CmpExpr);
auto *Arg0Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(0));
if (auto *LHasVal = getHasValue(Env, Arg0Loc)) {
auto *Arg1Loc = Env.get<RecordStorageLocation>(*CmpExpr->getArg(1));
if (auto *RHasVal = getHasValue(Env, Arg1Loc)) {
if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)
CmpValue = &A.makeNot(*CmpValue);
Env.assume(evaluateEquality(A, *CmpValue, LHasVal->formula(),
RHasVal->formula()));
}
}
}
void transferOptionalAndValueCmp(const clang::CXXOperatorCallExpr *CmpExpr,
const clang::Expr *E, Environment &Env) {
auto &A = Env.arena();
auto *CmpValue = &forceBoolValue(Env, *CmpExpr);
auto *Loc = Env.get<RecordStorageLocation>(*E);
if (auto *HasVal = getHasValue(Env, Loc)) {
if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)
CmpValue = &A.makeNot(*CmpValue);
Env.assume(
evaluateEquality(A, *CmpValue, HasVal->formula(), A.makeLiteral(true)));
}
}
void transferOptionalAndNulloptCmp(const clang::CXXOperatorCallExpr *CmpExpr,
const clang::Expr *E, Environment &Env) {
auto &A = Env.arena();
auto *CmpValue = &forceBoolValue(Env, *CmpExpr);
auto *Loc = Env.get<RecordStorageLocation>(*E);
if (auto *HasVal = getHasValue(Env, Loc)) {
if (CmpExpr->getOperator() == clang::OO_ExclaimEqual)
CmpValue = &A.makeNot(*CmpValue);
Env.assume(evaluateEquality(A, *CmpValue, HasVal->formula(),
A.makeLiteral(false)));
}
}
std::optional<StatementMatcher>
ignorableOptional(const UncheckedOptionalAccessModelOptions &Options) {
if (Options.IgnoreSmartPointerDereference) {
auto SmartPtrUse = expr(ignoringParenImpCasts(cxxOperatorCallExpr(
anyOf(hasOverloadedOperatorName("->"), hasOverloadedOperatorName("*")),
unless(hasArgument(0, expr(hasOptionalType()))))));
return expr(
anyOf(SmartPtrUse, memberExpr(hasObjectExpression(SmartPtrUse))));
}
return std::nullopt;
}
StatementMatcher
valueCall(const std::optional<StatementMatcher> &IgnorableOptional) {
return isOptionalMemberCallWithNameMatcher(hasName("value"),
IgnorableOptional);
}
StatementMatcher
valueOperatorCall(const std::optional<StatementMatcher> &IgnorableOptional) {
return expr(anyOf(isOptionalOperatorCallWithName("*", IgnorableOptional),
isOptionalOperatorCallWithName("->", IgnorableOptional)));
}
auto buildTransferMatchSwitch() {
return CFGMatchSwitchBuilder<LatticeTransferState>()
.CaseOfCFGStmt<CallExpr>(isMakeOptionalCall(), transferMakeOptionalCall)
.CaseOfCFGStmt<CXXConstructExpr>(
isOptionalInPlaceConstructor(),
[](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
constructOptionalValue(*E, State.Env,
State.Env.getBoolLiteralValue(true));
})
.CaseOfCFGStmt<CXXConstructExpr>(
isOptionalNulloptConstructor(),
[](const CXXConstructExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
constructOptionalValue(*E, State.Env,
State.Env.getBoolLiteralValue(false));
})
.CaseOfCFGStmt<CXXConstructExpr>(isOptionalValueOrConversionConstructor(),
transferValueOrConversionConstructor)
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isOptionalValueOrConversionAssignment(),
transferValueOrConversionAssignment)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isOptionalNulloptAssignment(),
transferNulloptAssignment)
.CaseOfCFGStmt<CXXMemberCallExpr>(
valueCall(std::nullopt),
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferUnwrapCall(E, E->getImplicitObjectArgument(), State);
})
.CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("*"),
[](const CallExpr *E,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferUnwrapCall(E, E->getArg(0), State);
})
.CaseOfCFGStmt<CallExpr>(isOptionalOperatorCallWithName("->"),
[](const CallExpr *E,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferArrowOpCall(E, E->getArg(0), State);
})
.CaseOfCFGStmt<CXXMemberCallExpr>(
isOptionalMemberCallWithNameMatcher(
hasAnyName("has_value", "hasValue")),
transferOptionalHasValueCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(
isOptionalMemberCallWithNameMatcher(hasName("operator bool")),
transferOptionalHasValueCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(
isOptionalMemberCallWithNameMatcher(hasName("emplace")),
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
if (RecordStorageLocation *Loc =
getImplicitObjectLocation(*E, State.Env)) {
setHasValue(*Loc, State.Env.getBoolLiteralValue(true), State.Env);
}
})
.CaseOfCFGStmt<CXXMemberCallExpr>(
isOptionalMemberCallWithNameMatcher(hasName("reset")),
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
if (RecordStorageLocation *Loc =
getImplicitObjectLocation(*E, State.Env)) {
setHasValue(*Loc, State.Env.getBoolLiteralValue(false),
State.Env);
}
})
.CaseOfCFGStmt<CXXMemberCallExpr>(
isOptionalMemberCallWithNameMatcher(hasName("swap")),
transferSwapCall)
.CaseOfCFGStmt<CallExpr>(isStdSwapCall(), transferStdSwapCall)
.CaseOfCFGStmt<CallExpr>(isStdForwardCall(), transferStdForwardCall)
.CaseOfCFGStmt<Expr>(isValueOrStringEmptyCall(),
transferValueOrStringEmptyCall)
.CaseOfCFGStmt<Expr>(isValueOrNotEqX(), transferValueOrNotEqX)
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isComparisonOperatorCall(hasOptionalType(), hasOptionalType()),
transferOptionalAndOptionalCmp)
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isComparisonOperatorCall(hasOptionalType(), hasNulloptType()),
[](const clang::CXXOperatorCallExpr *Cmp,
const MatchFinder::MatchResult &, LatticeTransferState &State) {
transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(0), State.Env);
})
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isComparisonOperatorCall(hasNulloptType(), hasOptionalType()),
[](const clang::CXXOperatorCallExpr *Cmp,
const MatchFinder::MatchResult &, LatticeTransferState &State) {
transferOptionalAndNulloptCmp(Cmp, Cmp->getArg(1), State.Env);
})
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isComparisonOperatorCall(
hasOptionalType(),
unless(anyOf(hasOptionalType(), hasNulloptType()))),
[](const clang::CXXOperatorCallExpr *Cmp,
const MatchFinder::MatchResult &, LatticeTransferState &State) {
transferOptionalAndValueCmp(Cmp, Cmp->getArg(0), State.Env);
})
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isComparisonOperatorCall(
unless(anyOf(hasOptionalType(), hasNulloptType())),
hasOptionalType()),
[](const clang::CXXOperatorCallExpr *Cmp,
const MatchFinder::MatchResult &, LatticeTransferState &State) {
transferOptionalAndValueCmp(Cmp, Cmp->getArg(1), State.Env);
})
.CaseOfCFGStmt<CallExpr>(isCallReturningOptional(),
transferCallReturningOptional)
.Build();
}
llvm::SmallVector<SourceLocation> diagnoseUnwrapCall(const Expr *ObjectExpr,
const Environment &Env) {
if (auto *OptionalLoc = cast_or_null<RecordStorageLocation>(
getLocBehindPossiblePointer(*ObjectExpr, Env))) {
auto *Prop = Env.getValue(locForHasValue(*OptionalLoc));
if (auto *HasValueVal = cast_or_null<BoolValue>(Prop)) {
if (Env.proves(HasValueVal->formula()))
return {};
}
}
return {ObjectExpr->getBeginLoc()};
}
auto buildDiagnoseMatchSwitch(
const UncheckedOptionalAccessModelOptions &Options) {
auto IgnorableOptional = ignorableOptional(Options);
return CFGMatchSwitchBuilder<const Environment,
llvm::SmallVector<SourceLocation>>()
.CaseOfCFGStmt<CXXMemberCallExpr>(
valueCall(IgnorableOptional),
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &,
const Environment &Env) {
return diagnoseUnwrapCall(E->getImplicitObjectArgument(), Env);
})
.CaseOfCFGStmt<CallExpr>(valueOperatorCall(IgnorableOptional),
[](const CallExpr *E,
const MatchFinder::MatchResult &,
const Environment &Env) {
return diagnoseUnwrapCall(E->getArg(0), Env);
})
.Build();
}
}
ast_matchers::DeclarationMatcher
UncheckedOptionalAccessModel::optionalClassDecl() {
return cxxRecordDecl(optionalClass());
}
UncheckedOptionalAccessModel::UncheckedOptionalAccessModel(ASTContext &Ctx,
Environment &Env)
: DataflowAnalysis<UncheckedOptionalAccessModel, NoopLattice>(Ctx),
TransferMatchSwitch(buildTransferMatchSwitch()) {
Env.getDataflowAnalysisContext().setSyntheticFieldCallback(
[&Ctx](QualType Ty) -> llvm::StringMap<QualType> {
const CXXRecordDecl *Optional =
getOptionalBaseClass(Ty->getAsCXXRecordDecl());
if (Optional == nullptr)
return {};
return {{"value", valueTypeFromOptionalDecl(*Optional)},
{"has_value", Ctx.BoolTy}};
});
}
void UncheckedOptionalAccessModel::transfer(const CFGElement &Elt,
NoopLattice &L, Environment &Env) {
LatticeTransferState State(L, Env);
TransferMatchSwitch(Elt, getASTContext(), State);
}
UncheckedOptionalAccessDiagnoser::UncheckedOptionalAccessDiagnoser(
UncheckedOptionalAccessModelOptions Options)
: DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {}
}
}