This is not necessarily correct, but prevents us from flagging lots of false positives because code usually abides by this.
976 lines
40 KiB
C++
976 lines
40 KiB
C++
//===- 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/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 clang::ast_matchers::TypeMatcher statusType() {
|
|
using namespace ::clang::ast_matchers; // NOLINT: Too many names
|
|
return hasCanonicalType(qualType(hasDeclaration(statusClass())));
|
|
}
|
|
|
|
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
|
|
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())));
|
|
}
|
|
|
|
bool isStatusOrType(QualType Type) {
|
|
return isTypeNamed(Type, {"absl"}, "StatusOr");
|
|
}
|
|
|
|
bool isStatusType(QualType Type) {
|
|
return isTypeNamed(Type, {"absl"}, "Status");
|
|
}
|
|
|
|
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}};
|
|
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 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);
|
|
}
|
|
|
|
CFGMatchSwitch<LatticeTransferState>
|
|
buildTransferMatchSwitch(ASTContext &Ctx,
|
|
CFGMatchSwitchBuilder<LatticeTransferState> Builder) {
|
|
using namespace ::clang::ast_matchers; // NOLINT: Too many names
|
|
return std::move(Builder)
|
|
.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<CallExpr>(isAsStatusCallWithStatus(),
|
|
transferAsStatusCallWithStatus)
|
|
.CaseOfCFGStmt<CallExpr>(isAsStatusCallWithStatusOr(),
|
|
transferAsStatusCallWithStatusOr)
|
|
.CaseOfCFGStmt<CallExpr>(isLoggingGetReferenceableValueCall(),
|
|
transferLoggingGetReferenceableValueCall)
|
|
.CaseOfCFGStmt<CallExpr>(isLoggingCheckEqImpl(),
|
|
transferLoggingCheckEqImpl)
|
|
// 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. 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
|