blob: 94aa05559c5db042f1d2a5451851eed80d58f6e0 [file] [edit]
//===- UncheckedStatusOrAccessModel.cpp -----------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "clang/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.h"
#include <cassert>
#include <utility>
#include "clang/AST/DeclCXX.h"
#include "clang/AST/DeclTemplate.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/TypeBase.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/ASTMatchers/ASTMatchersInternal.h"
#include "clang/Analysis/CFG.h"
#include "clang/Analysis/FlowSensitive/CFGMatchSwitch.h"
#include "clang/Analysis/FlowSensitive/DataflowAnalysis.h"
#include "clang/Analysis/FlowSensitive/DataflowEnvironment.h"
#include "clang/Analysis/FlowSensitive/MatchSwitch.h"
#include "clang/Analysis/FlowSensitive/RecordOps.h"
#include "clang/Analysis/FlowSensitive/SmartPointerAccessorCaching.h"
#include "clang/Analysis/FlowSensitive/StorageLocation.h"
#include "clang/Analysis/FlowSensitive/Value.h"
#include "clang/Basic/LLVM.h"
#include "clang/Basic/SourceLocation.h"
#include "llvm/ADT/StringMap.h"
namespace clang::dataflow::statusor_model {
namespace {
using ::clang::ast_matchers::MatchFinder;
using ::clang::ast_matchers::StatementMatcher;
} // namespace
static bool namespaceEquals(const NamespaceDecl *NS,
clang::ArrayRef<clang::StringRef> NamespaceNames) {
while (!NamespaceNames.empty() && NS) {
if (NS->getName() != NamespaceNames.consume_back())
return false;
NS = dyn_cast_or_null<NamespaceDecl>(NS->getParent());
}
return NamespaceNames.empty() && !NS;
}
// TODO: move this to a proper place to share with the rest of clang
static bool isTypeNamed(QualType Type, clang::ArrayRef<clang::StringRef> NS,
StringRef Name) {
if (Type.isNull())
return false;
if (auto *RD = Type->getAsRecordDecl())
if (RD->getName() == Name)
if (const auto *N = dyn_cast_or_null<NamespaceDecl>(RD->getDeclContext()))
return namespaceEquals(N, NS);
return false;
}
static bool isStatusOrOperatorBaseType(QualType Type) {
return isTypeNamed(Type, {"absl", "internal_statusor"}, "OperatorBase");
}
static bool isSafeUnwrap(RecordStorageLocation *StatusOrLoc,
const Environment &Env) {
if (!StatusOrLoc)
return false;
auto &StatusLoc = locForStatus(*StatusOrLoc);
auto *OkVal = Env.get<BoolValue>(locForOk(StatusLoc));
return OkVal != nullptr && Env.proves(OkVal->formula());
}
static ClassTemplateSpecializationDecl *
getStatusOrBaseClass(const QualType &Ty) {
auto *RD = Ty->getAsCXXRecordDecl();
if (RD == nullptr)
return nullptr;
if (isStatusOrType(Ty) ||
// In case we are analyzing code under OperatorBase itself that uses
// operator* (e.g. to implement operator->).
isStatusOrOperatorBaseType(Ty))
return cast<ClassTemplateSpecializationDecl>(RD);
if (!RD->hasDefinition())
return nullptr;
for (const auto &Base : RD->bases())
if (auto *QT = getStatusOrBaseClass(Base.getType()))
return QT;
return nullptr;
}
static QualType getStatusOrValueType(ClassTemplateSpecializationDecl *TRD) {
return TRD->getTemplateArgs().get(0).getAsType();
}
static auto ofClassStatus() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return ofClass(hasName("::absl::Status"));
}
static auto isStatusMemberCallWithName(llvm::StringRef member_name) {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxMemberCallExpr(
on(expr(unless(cxxThisExpr()))),
callee(cxxMethodDecl(hasName(member_name), ofClassStatus())));
}
static auto isStatusOrMemberCallWithName(llvm::StringRef member_name) {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxMemberCallExpr(
on(expr(unless(cxxThisExpr()))),
callee(cxxMethodDecl(
hasName(member_name),
ofClass(anyOf(statusOrClass(), statusOrOperatorBaseClass())))));
}
static auto isStatusOrOperatorCallWithName(llvm::StringRef operator_name) {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxOperatorCallExpr(
hasOverloadedOperatorName(operator_name),
callee(cxxMethodDecl(
ofClass(anyOf(statusOrClass(), statusOrOperatorBaseClass())))));
}
static auto valueCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return anyOf(isStatusOrMemberCallWithName("value"),
isStatusOrMemberCallWithName("ValueOrDie"));
}
static auto valueOperatorCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return expr(anyOf(isStatusOrOperatorCallWithName("*"),
isStatusOrOperatorCallWithName("->")));
}
static auto isComparisonOperatorCall(llvm::StringRef operator_name) {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxOperatorCallExpr(
hasOverloadedOperatorName(operator_name), argumentCountIs(2),
hasArgument(0, anyOf(hasType(statusType()), hasType(statusOrType()))),
hasArgument(1, anyOf(hasType(statusType()), hasType(statusOrType()))));
}
static auto isOkStatusCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(callee(functionDecl(hasName("::absl::OkStatus"))));
}
static auto isNotOkStatusCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(callee(functionDecl(hasAnyName(
"::absl::AbortedError", "::absl::AlreadyExistsError",
"::absl::CancelledError", "::absl::DataLossError",
"::absl::DeadlineExceededError", "::absl::FailedPreconditionError",
"::absl::InternalError", "::absl::InvalidArgumentError",
"::absl::NotFoundError", "::absl::OutOfRangeError",
"::absl::PermissionDeniedError", "::absl::ResourceExhaustedError",
"::absl::UnauthenticatedError", "::absl::UnavailableError",
"::absl::UnimplementedError", "::absl::UnknownError"))));
}
static auto isPointerComparisonOperatorCall(std::string operator_name) {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return binaryOperator(hasOperatorName(operator_name),
hasLHS(hasType(hasCanonicalType(pointerType(
pointee(anyOf(statusOrType(), statusType())))))),
hasRHS(hasType(hasCanonicalType(pointerType(
pointee(anyOf(statusOrType(), statusType())))))));
}
// The nullPointerConstant in the two matchers below is to support
// absl::StatusOr<void*> X = nullptr.
// nullptr does not match the bound type.
// TODO: be less restrictive around convertible types in general.
static auto isStatusOrValueAssignmentCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxOperatorCallExpr(
hasOverloadedOperatorName("="),
callee(cxxMethodDecl(ofClass(statusOrClass()))),
hasArgument(1, anyOf(hasType(hasUnqualifiedDesugaredType(
type(equalsBoundNode("T")))),
nullPointerConstant())));
}
static auto isStatusOrValueConstructor() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxConstructExpr(
hasType(statusOrType()),
hasArgument(0,
anyOf(hasType(hasCanonicalType(type(equalsBoundNode("T")))),
nullPointerConstant(),
hasType(namedDecl(hasAnyName("absl::in_place_t",
"std::in_place_t"))))));
}
static auto isStatusOrConstructor() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxConstructExpr(hasType(statusOrType()));
}
static auto isStatusConstructor() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxConstructExpr(hasType(statusType()));
}
static auto isLoggingGetReferenceableValueCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(callee(
functionDecl(hasName("::absl::log_internal::GetReferenceableValue"))));
}
static auto isLoggingCheckEqImpl() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(
callee(functionDecl(hasName("::absl::log_internal::Check_EQImpl"))));
}
static auto isAsStatusCallWithStatus() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(
callee(functionDecl(hasName("::absl::log_internal::AsStatus"))),
hasArgument(0, hasType(statusClass())));
}
static auto isAsStatusCallWithStatusOr() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(
callee(functionDecl(hasName("::absl::log_internal::AsStatus"))),
hasArgument(0, hasType(statusOrType())));
}
static auto possiblyReferencedStatusOrType() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return anyOf(statusOrType(), referenceType(pointee(statusOrType())));
}
static auto isConstStatusOrAccessorMemberCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxMemberCallExpr(callee(
cxxMethodDecl(parameterCountIs(0), isConst(),
returns(qualType(possiblyReferencedStatusOrType())))));
}
static auto isConstStatusOrAccessorMemberOperatorCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxOperatorCallExpr(
callee(cxxMethodDecl(parameterCountIs(0), isConst(),
returns(possiblyReferencedStatusOrType()))));
}
static auto isConstStatusOrPointerAccessorMemberCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxMemberCallExpr(callee(cxxMethodDecl(
parameterCountIs(0), isConst(),
returns(pointerType(pointee(possiblyReferencedStatusOrType()))))));
}
static auto isConstStatusOrPointerAccessorMemberOperatorCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxOperatorCallExpr(callee(cxxMethodDecl(
parameterCountIs(0), isConst(),
returns(pointerType(pointee(possiblyReferencedStatusOrType()))))));
}
static auto isNonConstMemberCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxMemberCallExpr(callee(cxxMethodDecl(unless(isConst()))));
}
static auto isNonConstMemberOperatorCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxOperatorCallExpr(callee(cxxMethodDecl(unless(isConst()))));
}
static auto isMakePredicateFormatterFromIsOkMatcherCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(
callee(functionDecl(
hasName("::testing::internal::MakePredicateFormatterFromMatcher"))),
hasArgument(
0, hasType(cxxRecordDecl(hasAnyName(
"::testing::status::internal_status::IsOkMatcher",
"::absl_testing::status_internal::IsOkMatcher",
"::testing::status::internal_status::IsOkAndHoldsMatcher",
"::absl_testing::status_internal::IsOkAndHoldsMatcher")))));
}
static auto isStatusIsOkMatcherCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(callee(functionDecl(hasAnyName(
"::testing::status::StatusIs", "absl_testing::StatusIs",
"::testing::status::CanonicalStatusIs",
"::absl_testing::CanonicalStatusIs"))),
hasArgument(0, declRefExpr(to(enumConstantDecl(hasAnyName(
"::absl::StatusCode::kOk", "OK"))))));
}
static auto isMakePredicateFormatterFromStatusIsMatcherCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(
callee(functionDecl(
hasName("::testing::internal::MakePredicateFormatterFromMatcher"))),
hasArgument(0, hasType(cxxRecordDecl(hasAnyName(
"::testing::status::internal_status::StatusIsMatcher",
"::testing::status::internal_status::"
"CanonicalStatusIsMatcher",
"::absl_testing::status_internal::StatusIsMatcher",
"::absl_testing::status_internal::"
"CanonicalStatusIsMatcher")))));
}
static auto isPredicateFormatterFromStatusMatcherCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxOperatorCallExpr(
hasOverloadedOperatorName("()"),
callee(cxxMethodDecl(ofClass(
hasName("testing::internal::PredicateFormatterFromMatcher")))),
hasArgument(2, hasType(statusType())));
}
static auto isPredicateFormatterFromStatusOrMatcherCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxOperatorCallExpr(
hasOverloadedOperatorName("()"),
callee(cxxMethodDecl(ofClass(
hasName("testing::internal::PredicateFormatterFromMatcher")))),
hasArgument(2, hasType(statusOrType())));
}
static auto isAssertionResultOperatorBoolCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxMemberCallExpr(
on(expr(unless(cxxThisExpr()))),
callee(cxxMethodDecl(hasName("operator bool"),
ofClass(hasName("testing::AssertionResult")))));
}
static auto isAssertionResultConstructFromBoolCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxConstructExpr(
hasType(recordDecl(hasName("testing::AssertionResult"))),
hasArgument(0, hasType(booleanType())));
}
static auto isStatusOrReturningCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(
callee(functionDecl(returns(possiblyReferencedStatusOrType()))));
}
static auto isStatusOrPtrReturningCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(callee(functionDecl(returns(hasUnqualifiedDesugaredType(
pointerType(pointee(possiblyReferencedStatusOrType())))))));
}
static auto isStatusPtrReturningCall() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return callExpr(callee(functionDecl(returns(hasUnqualifiedDesugaredType(
pointerType(pointee(hasUnqualifiedDesugaredType(
recordType(hasDeclaration(statusClass()))))))))));
}
static auto
buildDiagnoseMatchSwitch(const UncheckedStatusOrAccessModelOptions &Options) {
return CFGMatchSwitchBuilder<const Environment,
llvm::SmallVector<SourceLocation>>()
// StatusOr::value, StatusOr::ValueOrDie
.CaseOfCFGStmt<CXXMemberCallExpr>(
valueCall(),
[](const CXXMemberCallExpr *E,
const ast_matchers::MatchFinder::MatchResult &,
const Environment &Env) {
if (!isSafeUnwrap(getImplicitObjectLocation(*E, Env), Env))
return llvm::SmallVector<SourceLocation>({E->getExprLoc()});
return llvm::SmallVector<SourceLocation>();
})
// StatusOr::operator*, StatusOr::operator->
.CaseOfCFGStmt<CXXOperatorCallExpr>(
valueOperatorCall(),
[](const CXXOperatorCallExpr *E,
const ast_matchers::MatchFinder::MatchResult &,
const Environment &Env) {
RecordStorageLocation *StatusOrLoc =
Env.get<RecordStorageLocation>(*E->getArg(0));
if (!isSafeUnwrap(StatusOrLoc, Env))
return llvm::SmallVector<SourceLocation>({E->getOperatorLoc()});
return llvm::SmallVector<SourceLocation>();
})
.Build();
}
UncheckedStatusOrAccessDiagnoser::UncheckedStatusOrAccessDiagnoser(
UncheckedStatusOrAccessModelOptions Options)
: DiagnoseMatchSwitch(buildDiagnoseMatchSwitch(Options)) {}
llvm::SmallVector<SourceLocation> UncheckedStatusOrAccessDiagnoser::operator()(
const CFGElement &Elt, ASTContext &Ctx,
const TransferStateForDiagnostics<UncheckedStatusOrAccessModel::Lattice>
&State) {
return DiagnoseMatchSwitch(Elt, Ctx, State.Env);
}
BoolValue &initializeStatus(RecordStorageLocation &StatusLoc,
Environment &Env) {
auto &OkVal = Env.makeAtomicBoolValue();
Env.setValue(locForOk(StatusLoc), OkVal);
return OkVal;
}
BoolValue &initializeStatusOr(RecordStorageLocation &StatusOrLoc,
Environment &Env) {
return initializeStatus(locForStatus(StatusOrLoc), Env);
}
clang::ast_matchers::DeclarationMatcher statusOrClass() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return classTemplateSpecializationDecl(
hasName("absl::StatusOr"),
hasTemplateArgument(0, refersToType(type().bind("T"))));
}
clang::ast_matchers::DeclarationMatcher statusClass() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return cxxRecordDecl(hasName("absl::Status"));
}
clang::ast_matchers::DeclarationMatcher statusOrOperatorBaseClass() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return classTemplateSpecializationDecl(
hasName("absl::internal_statusor::OperatorBase"));
}
clang::ast_matchers::TypeMatcher statusOrType() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return hasCanonicalType(qualType(hasDeclaration(statusOrClass())));
}
clang::ast_matchers::TypeMatcher statusType() {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return hasCanonicalType(qualType(hasDeclaration(statusClass())));
}
bool isStatusOrType(QualType Type) {
return isTypeNamed(Type, {"absl"}, "StatusOr");
}
bool isStatusType(QualType Type) {
return isTypeNamed(Type, {"absl"}, "Status");
}
static bool isPredicateFormatterFromMatcherType(QualType Type) {
return isTypeNamed(Type, {"testing", "internal"},
"PredicateFormatterFromMatcher");
}
static bool isAssertionResultType(QualType Type) {
return isTypeNamed(Type, {"testing"}, "AssertionResult");
}
static bool isStatusIsMatcherType(QualType Type) {
return isTypeNamed(Type, {"testing", "status", "internal_status"},
"StatusIsMatcher") ||
isTypeNamed(Type, {"testing", "status", "internal_status"},
"CanonicalStatusIsMatcher") ||
isTypeNamed(Type, {"absl_testing", "status_internal"},
"StatusIsMatcher") ||
isTypeNamed(Type, {"absl_testing", "status_internal"},
"CanonicalStatusIsMatcher");
}
llvm::StringMap<QualType> getSyntheticFields(QualType Ty, QualType StatusType,
const CXXRecordDecl &RD) {
if (auto *TRD = getStatusOrBaseClass(Ty))
return {{"status", StatusType}, {"value", getStatusOrValueType(TRD)}};
if (isStatusType(Ty) || (RD.hasDefinition() &&
RD.isDerivedFrom(StatusType->getAsCXXRecordDecl())))
return {{"ok", RD.getASTContext().BoolTy}};
if (isAssertionResultType(Ty))
return {{"ok", RD.getASTContext().BoolTy}};
if (isPredicateFormatterFromMatcherType(Ty))
return {{"ok_predicate", RD.getASTContext().BoolTy}};
if (isStatusIsMatcherType(Ty))
return {{"ok_matcher", RD.getASTContext().BoolTy}};
return {};
}
RecordStorageLocation &locForStatus(RecordStorageLocation &StatusOrLoc) {
return cast<RecordStorageLocation>(StatusOrLoc.getSyntheticField("status"));
}
StorageLocation &locForOk(RecordStorageLocation &StatusLoc) {
return StatusLoc.getSyntheticField("ok");
}
BoolValue &valForOk(RecordStorageLocation &StatusLoc, Environment &Env) {
if (auto *Val = Env.get<BoolValue>(locForOk(StatusLoc)))
return *Val;
return initializeStatus(StatusLoc, Env);
}
static StorageLocation &locForOkPredicate(RecordStorageLocation &StatusLoc) {
return StatusLoc.getSyntheticField("ok_predicate");
}
static StorageLocation &locForOkMatcher(RecordStorageLocation &StatusLoc) {
return StatusLoc.getSyntheticField("ok_matcher");
}
static void transferStatusOrOkCall(const CXXMemberCallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
RecordStorageLocation *StatusOrLoc =
getImplicitObjectLocation(*Expr, State.Env);
if (StatusOrLoc == nullptr)
return;
auto &OkVal = valForOk(locForStatus(*StatusOrLoc), State.Env);
State.Env.setValue(*Expr, OkVal);
}
static void transferStatusCall(const CXXMemberCallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
RecordStorageLocation *StatusOrLoc =
getImplicitObjectLocation(*Expr, State.Env);
if (StatusOrLoc == nullptr)
return;
RecordStorageLocation &StatusLoc = locForStatus(*StatusOrLoc);
if (State.Env.getValue(locForOk(StatusLoc)) == nullptr)
initializeStatusOr(*StatusOrLoc, State.Env);
if (Expr->isPRValue())
copyRecord(StatusLoc, State.Env.getResultObjectLocation(*Expr), State.Env);
else
State.Env.setStorageLocation(*Expr, StatusLoc);
}
static void transferStatusOkCall(const CXXMemberCallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
RecordStorageLocation *StatusLoc =
getImplicitObjectLocation(*Expr, State.Env);
if (StatusLoc == nullptr)
return;
if (Value *Val = State.Env.getValue(locForOk(*StatusLoc)))
State.Env.setValue(*Expr, *Val);
}
static void transferStatusUpdateCall(const CXXMemberCallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
// S.Update(OtherS) sets S to the error code of OtherS if it is OK,
// otherwise does nothing.
assert(Expr->getNumArgs() == 1);
auto *Arg = Expr->getArg(0);
RecordStorageLocation *ArgRecord =
Arg->isPRValue() ? &State.Env.getResultObjectLocation(*Arg)
: State.Env.get<RecordStorageLocation>(*Arg);
RecordStorageLocation *ThisLoc = getImplicitObjectLocation(*Expr, State.Env);
if (ThisLoc == nullptr || ArgRecord == nullptr)
return;
auto &ThisOkVal = valForOk(*ThisLoc, State.Env);
auto &ArgOkVal = valForOk(*ArgRecord, State.Env);
auto &A = State.Env.arena();
auto &NewVal = State.Env.makeAtomicBoolValue();
State.Env.assume(A.makeImplies(A.makeNot(ThisOkVal.formula()),
A.makeNot(NewVal.formula())));
State.Env.assume(A.makeImplies(NewVal.formula(), ArgOkVal.formula()));
State.Env.setValue(locForOk(*ThisLoc), NewVal);
}
static BoolValue *evaluateStatusEquality(RecordStorageLocation &LhsStatusLoc,
RecordStorageLocation &RhsStatusLoc,
Environment &Env) {
auto &A = Env.arena();
// Logically, a Status object is composed of an error code that could take one
// of multiple possible values, including the "ok" value. We track whether a
// Status object has an "ok" value and represent this as an `ok` bit. Equality
// of Status objects compares their error codes. Therefore, merely comparing
// the `ok` bits isn't sufficient: when two Status objects are assigned non-ok
// error codes the equality of their respective error codes matters. Since we
// only track the `ok` bits, we can't make any conclusions about equality when
// we know that two Status objects have non-ok values.
auto &LhsOkVal = valForOk(LhsStatusLoc, Env);
auto &RhsOkVal = valForOk(RhsStatusLoc, Env);
auto &Res = Env.makeAtomicBoolValue();
// lhs && rhs => res (a.k.a. !res => !lhs || !rhs)
Env.assume(A.makeImplies(A.makeAnd(LhsOkVal.formula(), RhsOkVal.formula()),
Res.formula()));
// res => (lhs == rhs)
Env.assume(A.makeImplies(
Res.formula(), A.makeEquals(LhsOkVal.formula(), RhsOkVal.formula())));
return &Res;
}
static BoolValue *
evaluateStatusOrEquality(RecordStorageLocation &LhsStatusOrLoc,
RecordStorageLocation &RhsStatusOrLoc,
Environment &Env) {
auto &A = Env.arena();
// Logically, a StatusOr<T> object is composed of two values - a Status and a
// value of type T. Equality of StatusOr objects compares both values.
// Therefore, merely comparing the `ok` bits of the Status values isn't
// sufficient. When two StatusOr objects are engaged, the equality of their
// respective values of type T matters. Similarly, when two StatusOr objects
// have Status values that have non-ok error codes, the equality of the error
// codes matters. Since we only track the `ok` bits of the Status values, we
// can't make any conclusions about equality when we know that two StatusOr
// objects are engaged or when their Status values contain non-ok error codes.
auto &LhsOkVal = valForOk(locForStatus(LhsStatusOrLoc), Env);
auto &RhsOkVal = valForOk(locForStatus(RhsStatusOrLoc), Env);
auto &res = Env.makeAtomicBoolValue();
// res => (lhs == rhs)
Env.assume(A.makeImplies(
res.formula(), A.makeEquals(LhsOkVal.formula(), RhsOkVal.formula())));
return &res;
}
static BoolValue *evaluateEquality(const Expr *LhsExpr, const Expr *RhsExpr,
Environment &Env) {
// Check the type of both sides in case an operator== is added that admits
// different types.
if (isStatusOrType(LhsExpr->getType()) &&
isStatusOrType(RhsExpr->getType())) {
auto *LhsStatusOrLoc = Env.get<RecordStorageLocation>(*LhsExpr);
if (LhsStatusOrLoc == nullptr)
return nullptr;
auto *RhsStatusOrLoc = Env.get<RecordStorageLocation>(*RhsExpr);
if (RhsStatusOrLoc == nullptr)
return nullptr;
return evaluateStatusOrEquality(*LhsStatusOrLoc, *RhsStatusOrLoc, Env);
}
if (isStatusType(LhsExpr->getType()) && isStatusType(RhsExpr->getType())) {
auto *LhsStatusLoc = Env.get<RecordStorageLocation>(*LhsExpr);
if (LhsStatusLoc == nullptr)
return nullptr;
auto *RhsStatusLoc = Env.get<RecordStorageLocation>(*RhsExpr);
if (RhsStatusLoc == nullptr)
return nullptr;
return evaluateStatusEquality(*LhsStatusLoc, *RhsStatusLoc, Env);
}
return nullptr;
}
static void transferComparisonOperator(const CXXOperatorCallExpr *Expr,
LatticeTransferState &State,
bool IsNegative) {
auto *LhsAndRhsVal =
evaluateEquality(Expr->getArg(0), Expr->getArg(1), State.Env);
if (LhsAndRhsVal == nullptr)
return;
if (IsNegative)
State.Env.setValue(*Expr, State.Env.makeNot(*LhsAndRhsVal));
else
State.Env.setValue(*Expr, *LhsAndRhsVal);
}
static RecordStorageLocation *getPointeeLocation(const Expr &Expr,
Environment &Env) {
if (auto *PointerVal = Env.get<PointerValue>(Expr))
return dyn_cast<RecordStorageLocation>(&PointerVal->getPointeeLoc());
return nullptr;
}
static BoolValue *evaluatePointerEquality(const Expr *LhsExpr,
const Expr *RhsExpr,
Environment &Env) {
assert(LhsExpr->getType()->isPointerType());
assert(RhsExpr->getType()->isPointerType());
RecordStorageLocation *LhsStatusLoc = nullptr;
RecordStorageLocation *RhsStatusLoc = nullptr;
if (isStatusOrType(LhsExpr->getType()->getPointeeType()) &&
isStatusOrType(RhsExpr->getType()->getPointeeType())) {
auto *LhsStatusOrLoc = getPointeeLocation(*LhsExpr, Env);
auto *RhsStatusOrLoc = getPointeeLocation(*RhsExpr, Env);
if (LhsStatusOrLoc == nullptr || RhsStatusOrLoc == nullptr)
return nullptr;
LhsStatusLoc = &locForStatus(*LhsStatusOrLoc);
RhsStatusLoc = &locForStatus(*RhsStatusOrLoc);
} else if (isStatusType(LhsExpr->getType()->getPointeeType()) &&
isStatusType(RhsExpr->getType()->getPointeeType())) {
LhsStatusLoc = getPointeeLocation(*LhsExpr, Env);
RhsStatusLoc = getPointeeLocation(*RhsExpr, Env);
}
if (LhsStatusLoc == nullptr || RhsStatusLoc == nullptr)
return nullptr;
auto &LhsOkVal = valForOk(*LhsStatusLoc, Env);
auto &RhsOkVal = valForOk(*RhsStatusLoc, Env);
auto &Res = Env.makeAtomicBoolValue();
auto &A = Env.arena();
Env.assume(A.makeImplies(
Res.formula(), A.makeEquals(LhsOkVal.formula(), RhsOkVal.formula())));
return &Res;
}
static void transferPointerComparisonOperator(const BinaryOperator *Expr,
LatticeTransferState &State,
bool IsNegative) {
auto *LhsAndRhsVal =
evaluatePointerEquality(Expr->getLHS(), Expr->getRHS(), State.Env);
if (LhsAndRhsVal == nullptr)
return;
if (IsNegative)
State.Env.setValue(*Expr, State.Env.makeNot(*LhsAndRhsVal));
else
State.Env.setValue(*Expr, *LhsAndRhsVal);
}
static void transferOkStatusCall(const CallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
auto &OkVal =
initializeStatus(State.Env.getResultObjectLocation(*Expr), State.Env);
State.Env.assume(OkVal.formula());
}
static void transferNotOkStatusCall(const CallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
auto &OkVal =
initializeStatus(State.Env.getResultObjectLocation(*Expr), State.Env);
auto &A = State.Env.arena();
State.Env.assume(A.makeNot(OkVal.formula()));
}
static void transferEmplaceCall(const CXXMemberCallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
RecordStorageLocation *StatusOrLoc =
getImplicitObjectLocation(*Expr, State.Env);
if (StatusOrLoc == nullptr)
return;
auto &OkVal = valForOk(locForStatus(*StatusOrLoc), State.Env);
State.Env.assume(OkVal.formula());
}
static void transferValueAssignmentCall(const CXXOperatorCallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(Expr->getNumArgs() > 1);
auto *StatusOrLoc = State.Env.get<RecordStorageLocation>(*Expr->getArg(0));
if (StatusOrLoc == nullptr)
return;
auto &OkVal = initializeStatusOr(*StatusOrLoc, State.Env);
State.Env.assume(OkVal.formula());
}
static void transferValueConstructor(const CXXConstructExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
auto &OkVal =
initializeStatusOr(State.Env.getResultObjectLocation(*Expr), State.Env);
State.Env.assume(OkVal.formula());
}
static void transferStatusOrConstructor(const CXXConstructExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
RecordStorageLocation &StatusOrLoc = State.Env.getResultObjectLocation(*Expr);
RecordStorageLocation &StatusLoc = locForStatus(StatusOrLoc);
if (State.Env.getValue(locForOk(StatusLoc)) == nullptr)
initializeStatusOr(StatusOrLoc, State.Env);
}
static void transferStatusConstructor(const CXXConstructExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
RecordStorageLocation &StatusLoc = State.Env.getResultObjectLocation(*Expr);
if (State.Env.getValue(locForOk(StatusLoc)) == nullptr)
initializeStatus(StatusLoc, State.Env);
}
static void
transferLoggingGetReferenceableValueCall(const CallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(Expr->getNumArgs() == 1);
if (Expr->getArg(0)->isPRValue())
return;
auto *ArgLoc = State.Env.getStorageLocation(*Expr->getArg(0));
if (ArgLoc == nullptr)
return;
State.Env.setStorageLocation(*Expr, *ArgLoc);
}
static void transferLoggingCheckEqImpl(const CallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(Expr->getNumArgs() > 2);
auto *EqVal = evaluateEquality(Expr->getArg(0), Expr->getArg(1), State.Env);
if (EqVal == nullptr)
return;
// Consider modelling this more accurately instead of assigning BoolValue
// as the value of an expression of pointer type.
// For now, this is being handled in transferPointerToBoolean.
State.Env.setValue(*Expr, State.Env.makeNot(*EqVal));
}
static void transferAsStatusCallWithStatus(const CallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(Expr->getNumArgs() == 1);
auto *ArgLoc = State.Env.get<RecordStorageLocation>(*Expr->getArg(0));
if (ArgLoc == nullptr)
return;
if (State.Env.getValue(locForOk(*ArgLoc)) == nullptr)
initializeStatus(*ArgLoc, State.Env);
auto &ExprVal = State.Env.create<PointerValue>(*ArgLoc);
State.Env.setValue(*Expr, ExprVal);
}
static void transferAsStatusCallWithStatusOr(const CallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(Expr->getNumArgs() == 1);
auto *ArgLoc = State.Env.get<RecordStorageLocation>(*Expr->getArg(0));
if (ArgLoc == nullptr)
return;
RecordStorageLocation &StatusLoc = locForStatus(*ArgLoc);
if (State.Env.getValue(locForOk(StatusLoc)) == nullptr)
initializeStatusOr(*ArgLoc, State.Env);
auto &ExprVal = State.Env.create<PointerValue>(StatusLoc);
State.Env.setValue(*Expr, ExprVal);
}
static void transferPointerToBoolean(const ImplicitCastExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
if (auto *SubExprVal =
dyn_cast_or_null<BoolValue>(State.Env.getValue(*Expr->getSubExpr())))
State.Env.setValue(*Expr, *SubExprVal);
}
static void transferStatusOrReturningCall(const CallExpr *Expr,
LatticeTransferState &State) {
RecordStorageLocation *StatusOrLoc =
Expr->isPRValue() ? &State.Env.getResultObjectLocation(*Expr)
: State.Env.get<RecordStorageLocation>(*Expr);
if (StatusOrLoc != nullptr &&
State.Env.getValue(locForOk(locForStatus(*StatusOrLoc))) == nullptr)
initializeStatusOr(*StatusOrLoc, State.Env);
}
static bool doHandleConstStatusOrAccessorMemberCall(
const CallExpr *Expr, RecordStorageLocation *RecordLoc,
const MatchFinder::MatchResult &Result, LatticeTransferState &State) {
assert(isStatusOrType(Expr->getType()));
if (RecordLoc == nullptr)
return false;
const FunctionDecl *DirectCallee = Expr->getDirectCallee();
if (DirectCallee == nullptr)
return false;
StorageLocation &Loc =
State.Lattice.getOrCreateConstMethodReturnStorageLocation(
*RecordLoc, DirectCallee, State.Env, [&](StorageLocation &Loc) {
initializeStatusOr(cast<RecordStorageLocation>(Loc), State.Env);
});
if (Expr->isPRValue()) {
auto &ResultLoc = State.Env.getResultObjectLocation(*Expr);
copyRecord(cast<RecordStorageLocation>(Loc), ResultLoc, State.Env);
} else {
State.Env.setStorageLocation(*Expr, Loc);
}
return true;
}
static void handleConstStatusOrAccessorMemberCall(
const CallExpr *Expr, RecordStorageLocation *RecordLoc,
const MatchFinder::MatchResult &Result, LatticeTransferState &State) {
if (!doHandleConstStatusOrAccessorMemberCall(Expr, RecordLoc, Result, State))
transferStatusOrReturningCall(Expr, State);
}
static void handleConstStatusOrPointerAccessorMemberCall(
const CallExpr *Expr, RecordStorageLocation *RecordLoc,
const MatchFinder::MatchResult &Result, LatticeTransferState &State) {
if (RecordLoc == nullptr)
return;
auto *Val = State.Lattice.getOrCreateConstMethodReturnValue(*RecordLoc, Expr,
State.Env);
State.Env.setValue(*Expr, *Val);
}
static void
transferConstStatusOrAccessorMemberCall(const CXXMemberCallExpr *Expr,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
handleConstStatusOrAccessorMemberCall(
Expr, getImplicitObjectLocation(*Expr, State.Env), Result, State);
}
static void transferConstStatusOrAccessorMemberOperatorCall(
const CXXOperatorCallExpr *Expr, const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
auto *RecordLoc = cast_or_null<RecordStorageLocation>(
State.Env.getStorageLocation(*Expr->getArg(0)));
handleConstStatusOrAccessorMemberCall(Expr, RecordLoc, Result, State);
}
static void transferConstStatusOrPointerAccessorMemberCall(
const CXXMemberCallExpr *Expr, const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
handleConstStatusOrPointerAccessorMemberCall(
Expr, getImplicitObjectLocation(*Expr, State.Env), Result, State);
}
static void transferConstStatusOrPointerAccessorMemberOperatorCall(
const CXXOperatorCallExpr *Expr, const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
auto *RecordLoc = cast_or_null<RecordStorageLocation>(
State.Env.getStorageLocation(*Expr->getArg(0)));
handleConstStatusOrPointerAccessorMemberCall(Expr, RecordLoc, Result, State);
}
static void handleNonConstMemberCall(const CallExpr *Expr,
RecordStorageLocation *RecordLoc,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
if (RecordLoc) {
State.Lattice.clearConstMethodReturnValues(*RecordLoc);
State.Lattice.clearConstMethodReturnStorageLocations(*RecordLoc);
}
if (isStatusOrType(Expr->getType()))
transferStatusOrReturningCall(Expr, State);
}
static void transferNonConstMemberCall(const CXXMemberCallExpr *Expr,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
handleNonConstMemberCall(Expr, getImplicitObjectLocation(*Expr, State.Env),
Result, State);
}
static void
transferNonConstMemberOperatorCall(const CXXOperatorCallExpr *Expr,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
auto *RecordLoc = cast_or_null<RecordStorageLocation>(
State.Env.getStorageLocation(*Expr->getArg(0)));
handleNonConstMemberCall(Expr, RecordLoc, Result, State);
}
static void transferMakePredicateFormatterFromIsOkMatcherCall(
const CallExpr *Expr, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
State.Env.setValue(
locForOkPredicate(State.Env.getResultObjectLocation(*Expr)),
State.Env.getBoolLiteralValue(true));
}
static void transferStatusIsOkMatcherCall(const CallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
BoolValue &OkMatcherVal = State.Env.getBoolLiteralValue(true);
State.Env.setValue(locForOkMatcher(State.Env.getResultObjectLocation(*Expr)),
OkMatcherVal);
}
static void transferMakePredicateFormatterFromStatusIsMatcherCall(
const CallExpr *Expr, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(Expr->isPRValue());
auto &Loc = State.Env.getResultObjectLocation(*Expr->getArg(0));
auto &OkMatcherLoc = locForOkMatcher(Loc);
BoolValue *OkMatcherVal = State.Env.get<BoolValue>(OkMatcherLoc);
if (OkMatcherVal == nullptr)
return;
State.Env.setValue(
locForOkPredicate(State.Env.getResultObjectLocation(*Expr)),
*OkMatcherVal);
}
static void
transferPredicateFormatterMatcherCall(const CXXOperatorCallExpr *Expr,
LatticeTransferState &State,
bool IsStatusOr) {
auto *Loc = State.Env.get<RecordStorageLocation>(*Expr->getArg(0));
if (Loc == nullptr)
return;
auto *ObjectLoc = State.Env.get<RecordStorageLocation>(*Expr->getArg(2));
if (ObjectLoc == nullptr)
return;
auto &OkPredicateLoc = locForOkPredicate(*Loc);
BoolValue *OkPredicateVal = State.Env.get<BoolValue>(OkPredicateLoc);
if (OkPredicateVal == nullptr)
return;
if (IsStatusOr)
ObjectLoc = &locForStatus(*ObjectLoc);
auto &StatusOk = valForOk(*ObjectLoc, State.Env);
auto &A = State.Env.arena();
auto &Res = State.Env.makeAtomicBoolValue();
State.Env.assume(
A.makeImplies(OkPredicateVal->formula(),
A.makeEquals(StatusOk.formula(), Res.formula())));
State.Env.setValue(locForOk(State.Env.getResultObjectLocation(*Expr)), Res);
}
static void
transferAssertionResultConstructFromBoolCall(const CXXConstructExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
assert(Expr->getNumArgs() > 0);
auto *StatusAdaptorLoc = State.Env.get<StorageLocation>(*Expr->getArg(0));
if (StatusAdaptorLoc == nullptr)
return;
BoolValue *OkVal = State.Env.get<BoolValue>(*StatusAdaptorLoc);
if (OkVal == nullptr)
return;
State.Env.setValue(locForOk(State.Env.getResultObjectLocation(*Expr)),
*OkVal);
}
static void
transferAssertionResultOperatorBoolCall(const CXXMemberCallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
auto *RecordLoc = getImplicitObjectLocation(*Expr, State.Env);
if (RecordLoc == nullptr)
return;
BoolValue *OkVal = State.Env.get<BoolValue>(locForOk(*RecordLoc));
if (OkVal == nullptr)
return;
auto &A = State.Env.arena();
auto &Res = State.Env.makeAtomicBoolValue();
State.Env.assume(A.makeEquals(OkVal->formula(), Res.formula()));
State.Env.setValue(*Expr, Res);
}
static void transferDerefCall(const CXXOperatorCallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
auto *StatusOrLoc = State.Env.get<RecordStorageLocation>(*Expr->getArg(0));
if (StatusOrLoc && State.Env.getStorageLocation(*Expr) == nullptr)
State.Env.setStorageLocation(*Expr,
StatusOrLoc->getSyntheticField("value"));
}
static void transferArrowCall(const CXXOperatorCallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
auto *StatusOrLoc = State.Env.get<RecordStorageLocation>(*Expr->getArg(0));
if (!StatusOrLoc)
return;
State.Env.setValue(*Expr, State.Env.create<PointerValue>(
StatusOrLoc->getSyntheticField("value")));
}
static void transferValueCall(const CXXMemberCallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
auto *StatusOrLoc = getImplicitObjectLocation(*Expr, State.Env);
if (StatusOrLoc && State.Env.getStorageLocation(*Expr) == nullptr)
State.Env.setStorageLocation(*Expr,
StatusOrLoc->getSyntheticField("value"));
}
static void transferStatusOrPtrReturningCall(const CallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
PointerValue *PointerVal =
dyn_cast_or_null<PointerValue>(State.Env.getValue(*Expr));
if (!PointerVal) {
PointerVal = cast<PointerValue>(State.Env.createValue(Expr->getType()));
State.Env.setValue(*Expr, *PointerVal);
}
auto *RecordLoc =
dyn_cast_or_null<RecordStorageLocation>(&PointerVal->getPointeeLoc());
if (RecordLoc != nullptr &&
State.Env.getValue(locForOk(locForStatus(*RecordLoc))) == nullptr)
initializeStatusOr(*RecordLoc, State.Env);
}
static void transferStatusPtrReturningCall(const CallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
PointerValue *PointerVal =
dyn_cast_or_null<PointerValue>(State.Env.getValue(*Expr));
if (!PointerVal) {
PointerVal = cast<PointerValue>(State.Env.createValue(Expr->getType()));
State.Env.setValue(*Expr, *PointerVal);
}
auto *RecordLoc =
dyn_cast_or_null<RecordStorageLocation>(&PointerVal->getPointeeLoc());
if (RecordLoc != nullptr &&
State.Env.getValue(locForOk(*RecordLoc)) == nullptr)
initializeStatus(*RecordLoc, State.Env);
}
static RecordStorageLocation *
getSmartPtrLikeStorageLocation(const Expr &E, const Environment &Env) {
if (!E.isPRValue())
return dyn_cast_or_null<RecordStorageLocation>(Env.getStorageLocation(E));
if (auto *PointerVal = dyn_cast_or_null<PointerValue>(Env.getValue(E)))
return dyn_cast_or_null<RecordStorageLocation>(
&PointerVal->getPointeeLoc());
return nullptr;
}
CFGMatchSwitch<LatticeTransferState>
buildTransferMatchSwitch(ASTContext &Ctx,
CFGMatchSwitchBuilder<LatticeTransferState> Builder) {
using namespace ::clang::ast_matchers; // NOLINT: Too many names
return std::move(Builder)
.CaseOfCFGStmt<CallExpr>(
isMakePredicateFormatterFromIsOkMatcherCall(),
transferMakePredicateFormatterFromIsOkMatcherCall)
.CaseOfCFGStmt<CallExpr>(isStatusIsOkMatcherCall(),
transferStatusIsOkMatcherCall)
.CaseOfCFGStmt<CallExpr>(
isMakePredicateFormatterFromStatusIsMatcherCall(),
transferMakePredicateFormatterFromStatusIsMatcherCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isPredicateFormatterFromStatusOrMatcherCall(),
[](const CXXOperatorCallExpr *Expr, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferPredicateFormatterMatcherCall(Expr, State,
/*IsStatusOr=*/true);
})
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isPredicateFormatterFromStatusMatcherCall(),
[](const CXXOperatorCallExpr *Expr, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferPredicateFormatterMatcherCall(Expr, State,
/*IsStatusOr=*/false);
})
.CaseOfCFGStmt<CXXConstructExpr>(
isAssertionResultConstructFromBoolCall(),
transferAssertionResultConstructFromBoolCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isAssertionResultOperatorBoolCall(),
transferAssertionResultOperatorBoolCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isStatusOrMemberCallWithName("ok"),
transferStatusOrOkCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isStatusOrMemberCallWithName("status"),
transferStatusCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isStatusMemberCallWithName("ok"),
transferStatusOkCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isStatusMemberCallWithName("Update"),
transferStatusUpdateCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isComparisonOperatorCall("=="),
[](const CXXOperatorCallExpr *Expr, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferComparisonOperator(Expr, State,
/*IsNegative=*/false);
})
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isComparisonOperatorCall("!="),
[](const CXXOperatorCallExpr *Expr, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferComparisonOperator(Expr, State,
/*IsNegative=*/true);
})
.CaseOfCFGStmt<BinaryOperator>(
isPointerComparisonOperatorCall("=="),
[](const BinaryOperator *Expr, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferPointerComparisonOperator(Expr, State,
/*IsNegative=*/false);
})
.CaseOfCFGStmt<BinaryOperator>(
isPointerComparisonOperatorCall("!="),
[](const BinaryOperator *Expr, const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferPointerComparisonOperator(Expr, State,
/*IsNegative=*/true);
})
.CaseOfCFGStmt<CallExpr>(isOkStatusCall(), transferOkStatusCall)
.CaseOfCFGStmt<CallExpr>(isNotOkStatusCall(), transferNotOkStatusCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isStatusOrMemberCallWithName("emplace"),
transferEmplaceCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isStatusOrValueAssignmentCall(),
transferValueAssignmentCall)
.CaseOfCFGStmt<CXXConstructExpr>(isStatusOrValueConstructor(),
transferValueConstructor)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isStatusOrOperatorCallWithName("->"),
transferArrowCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isStatusOrOperatorCallWithName("*"),
transferDerefCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(isStatusOrMemberCallWithName("value"),
transferValueCall)
.CaseOfCFGStmt<CallExpr>(isAsStatusCallWithStatus(),
transferAsStatusCallWithStatus)
.CaseOfCFGStmt<CallExpr>(isAsStatusCallWithStatusOr(),
transferAsStatusCallWithStatusOr)
.CaseOfCFGStmt<CallExpr>(isLoggingGetReferenceableValueCall(),
transferLoggingGetReferenceableValueCall)
.CaseOfCFGStmt<CallExpr>(isLoggingCheckEqImpl(),
transferLoggingCheckEqImpl)
// This needs to go before the const accessor call matcher, because these
// look like them, but we model `operator`* and `get` to return the same
// object. Also, we model them for non-const cases.
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isPointerLikeOperatorStar(),
[](const CXXOperatorCallExpr *E,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
transferSmartPointerLikeCachedDeref(
E, getSmartPtrLikeStorageLocation(*E->getArg(0), State.Env),
State, [](StorageLocation &Loc) {});
})
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isPointerLikeOperatorArrow(),
[](const CXXOperatorCallExpr *E,
const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
transferSmartPointerLikeCachedGet(
E, getSmartPtrLikeStorageLocation(*E->getArg(0), State.Env),
State, [](StorageLocation &Loc) {});
})
.CaseOfCFGStmt<CXXMemberCallExpr>(
isSmartPointerLikeValueMethodCall(),
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
transferSmartPointerLikeCachedDeref(
E, getImplicitObjectLocation(*E, State.Env), State,
[](StorageLocation &Loc) {});
})
.CaseOfCFGStmt<CXXMemberCallExpr>(
isSmartPointerLikeGetMethodCall(),
[](const CXXMemberCallExpr *E, const MatchFinder::MatchResult &Result,
LatticeTransferState &State) {
transferSmartPointerLikeCachedGet(
E, getImplicitObjectLocation(*E, State.Env), State,
[](StorageLocation &Loc) {});
})
// const accessor calls
.CaseOfCFGStmt<CXXMemberCallExpr>(isConstStatusOrAccessorMemberCall(),
transferConstStatusOrAccessorMemberCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isConstStatusOrAccessorMemberOperatorCall(),
transferConstStatusOrAccessorMemberOperatorCall)
.CaseOfCFGStmt<CXXMemberCallExpr>(
isConstStatusOrPointerAccessorMemberCall(),
transferConstStatusOrPointerAccessorMemberCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(
isConstStatusOrPointerAccessorMemberOperatorCall(),
transferConstStatusOrPointerAccessorMemberOperatorCall)
// non-const member calls that may modify the state of an object.
.CaseOfCFGStmt<CXXMemberCallExpr>(isNonConstMemberCall(),
transferNonConstMemberCall)
.CaseOfCFGStmt<CXXOperatorCallExpr>(isNonConstMemberOperatorCall(),
transferNonConstMemberOperatorCall)
// N.B. this has to be after transferConstMemberCall, otherwise we would
// always return a fresh RecordStorageLocation for the StatusOr.
.CaseOfCFGStmt<CallExpr>(isStatusOrReturningCall(),
[](const CallExpr *Expr,
const MatchFinder::MatchResult &,
LatticeTransferState &State) {
transferStatusOrReturningCall(Expr, State);
})
.CaseOfCFGStmt<CallExpr>(isStatusOrPtrReturningCall(),
transferStatusOrPtrReturningCall)
.CaseOfCFGStmt<CallExpr>(isStatusPtrReturningCall(),
transferStatusPtrReturningCall)
// N.B. These need to come after all other CXXConstructExpr.
// These are there to make sure that every Status and StatusOr object
// have their ok boolean initialized when constructed. If we were to
// lazily initialize them when we first access them, we can produce
// false positives if that first access is in a control flow statement.
// You can comment out these two constructors and see tests fail.
.CaseOfCFGStmt<CXXConstructExpr>(isStatusOrConstructor(),
transferStatusOrConstructor)
.CaseOfCFGStmt<CXXConstructExpr>(isStatusConstructor(),
transferStatusConstructor)
.CaseOfCFGStmt<ImplicitCastExpr>(
implicitCastExpr(hasCastKind(CK_PointerToBoolean)),
transferPointerToBoolean)
.Build();
}
QualType findStatusType(const ASTContext &Ctx) {
for (Type *Ty : Ctx.getTypes())
if (isStatusType(QualType(Ty, 0)))
return QualType(Ty, 0);
return QualType();
}
UncheckedStatusOrAccessModel::UncheckedStatusOrAccessModel(ASTContext &Ctx,
Environment &Env)
: DataflowAnalysis<UncheckedStatusOrAccessModel,
UncheckedStatusOrAccessModel::Lattice>(Ctx),
TransferMatchSwitch(buildTransferMatchSwitch(Ctx, {})) {
QualType StatusType = findStatusType(Ctx);
Env.getDataflowAnalysisContext().setSyntheticFieldCallback(
[StatusType](QualType Ty) -> llvm::StringMap<QualType> {
CXXRecordDecl *RD = Ty->getAsCXXRecordDecl();
if (RD == nullptr)
return {};
if (auto Fields = getSyntheticFields(Ty, StatusType, *RD);
!Fields.empty())
return Fields;
return {};
});
}
void UncheckedStatusOrAccessModel::transfer(const CFGElement &Elt, Lattice &L,
Environment &Env) {
LatticeTransferState State(L, Env);
TransferMatchSwitch(Elt, getASTContext(), State);
}
} // namespace clang::dataflow::statusor_model