diff options
Diffstat (limited to 'clang/lib/Analysis/FlowSensitive')
3 files changed, 294 insertions, 3 deletions
diff --git a/clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt b/clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt index 89bbe87..d1236f5 100644 --- a/clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt +++ b/clang/lib/Analysis/FlowSensitive/Models/CMakeLists.txt @@ -1,6 +1,7 @@ add_clang_library(clangAnalysisFlowSensitiveModels ChromiumCheckModel.cpp UncheckedOptionalAccessModel.cpp + UncheckedStatusOrAccessModel.cpp LINK_LIBS clangAnalysis diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp index bb703ef..0fa333e 100644 --- a/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedOptionalAccessModel.cpp @@ -241,9 +241,9 @@ auto nulloptTypeDecl() { 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", - "bsl::in_place_t")); + return namedDecl(hasAnyName("std::in_place_t", "absl::in_place_t", + "base::in_place_t", "folly::in_place_t", + "bsl::in_place_t")); } auto isOptionalNulloptConstructor() { diff --git a/clang/lib/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.cpp b/clang/lib/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.cpp new file mode 100644 index 0000000..c88a470 --- /dev/null +++ b/clang/lib/Analysis/FlowSensitive/Models/UncheckedStatusOrAccessModel.cpp @@ -0,0 +1,290 @@ +//===- 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/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 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 +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); +} + +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) + .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 |