[clang] [analyzer] Add alpha.cplusplus.BoundsInformation checker (PR #112784)
Artem Dergachev via cfe-commits
cfe-commits at lists.llvm.org
Sun Oct 20 12:32:34 PDT 2024
================
@@ -0,0 +1,199 @@
+//== BoundsInformationChecker.cpp - bounds information checker --*- C++ -*--==//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// This defines BoundsInformationChecker, a path-sensitive checker that
+// checks that the buffer and count arguments are within the bounds of
+// the source buffer.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
+#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+class BoundsInformationChecker : public Checker<check::PreCall> {
+ const BugType BT_DifferentMemRegion{
+ this, "std::span constructor arguments from different sources",
+ categories::SecurityError};
+ const BugType BT_NonConstantSizeArg{
+ this,
+ "std::span constructor for std::array has non-constant size argument",
+ categories::SecurityError};
+ const BugType BT_OutOfBounds{
+ this,
+ "std::span constructor for std::array uses out-of-bounds size argument",
+ categories::SecurityError};
+ void reportBug(ExplodedNode *N, const Expr *E, CheckerContext &C,
+ const BugType &BT, StringRef Msg) const;
+
+public:
+ void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
+};
+} // end anonymous namespace
+
+void BoundsInformationChecker::reportBug(ExplodedNode *N, const Expr *E,
+ CheckerContext &C, const BugType &BT,
+ StringRef Msg) const {
+ // Generate a report for this bug.
+ auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
+ if (auto *CE = dyn_cast<CXXConstructExpr>(E)) {
+ bugreporter::trackExpressionValue(N, CE->getArg(0), *R);
+ bugreporter::trackExpressionValue(N, CE->getArg(1), *R);
+ }
+ C.emitReport(std::move(R));
+}
+
+static const MemRegion *GetRegionOrigin(SVal SV) {
+ const SymExpr *Sym = SV.getAsSymbol(/*IncludeBaseRegions =*/true);
+ return Sym ? Sym->getOriginRegion() : nullptr;
+}
+
+static const ValueDecl *GetExpressionOrigin(const Stmt *STMT) {
+ if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(STMT)) {
+ if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+ return VD;
+ } else if (const CXXMemberCallExpr *MCE = dyn_cast<CXXMemberCallExpr>(STMT)) {
+ if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(
+ MCE->getImplicitObjectArgument()->IgnoreParenCasts())) {
+ if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+ return VD;
+ } else if (const MemberExpr *ME = dyn_cast<MemberExpr>(
+ MCE->getImplicitObjectArgument()->IgnoreParenCasts())) {
+ if (const FieldDecl *FD = dyn_cast<FieldDecl>(ME->getMemberDecl()))
+ return FD;
+ }
+ } else if (const CXXOperatorCallExpr *OCE =
+ dyn_cast<CXXOperatorCallExpr>(STMT)) {
+ if (OCE->getNumArgs() >= 1) {
+ if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(OCE->getArg(0))) {
+ if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+ return VD;
+ }
+ }
+ } else if (const UnaryOperator *UnaryOp = dyn_cast<UnaryOperator>(STMT)) {
+ if (const ArraySubscriptExpr *ASExpr =
+ dyn_cast<ArraySubscriptExpr>(UnaryOp->getSubExpr())) {
+ if (const DeclRefExpr *DRE =
+ dyn_cast<DeclRefExpr>(ASExpr->getBase()->IgnoreParenCasts())) {
+ if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+ return VD;
+ }
+ }
+ } else if (const UnaryExprOrTypeTraitExpr *UTExpr =
+ dyn_cast<UnaryExprOrTypeTraitExpr>(STMT)) {
+ if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(
+ UTExpr->getArgumentExpr()->IgnoreParenCasts())) {
+ if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl()))
+ return VD;
+ }
+ }
+ return nullptr;
+}
+
+static const ValueDecl *GetConjuredSymbolOrigin(SVal SV) {
+ const SymExpr *Sym = SV.getAsSymbol(/*IncludeBaseRegions =*/true);
+ if (const SymbolConjured *SCArg = dyn_cast_or_null<SymbolConjured>(Sym)) {
+ if (const Stmt *STMTArg = SCArg->getStmt())
+ return GetExpressionOrigin(STMTArg);
+ }
+ return nullptr;
+}
+
+void BoundsInformationChecker::checkPreCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ // Return early if not std::span<IT>(IT, size_t) constructor.
+ // a. Check if this is a ctor for std::span.
+ CallDescription CD({CDM::CXXMethod, {"std", "span", "span"}});
+ if (!CD.matches(Call))
+ return;
+ // b. Check if std::span ctor has two arguments.
+ if (Call.getNumArgs() != 2)
+ return;
+ // c. Check if second std::span ctor argument is of type size_t.
+ if (Call.getArgExpr(1)->getType().getCanonicalType() !=
+ C.getASTContext().getSizeType())
+ return;
+
+ SVal PointerArg = Call.getArgSVal(0);
+ SVal SizeArg = Call.getArgSVal(1);
+
+ // If buffer and length params are not from the same "source", then report a
+ // bug.
+ const MemRegion *MRArg0 = GetRegionOrigin(PointerArg);
+ const MemRegion *MRArg1 = GetRegionOrigin(SizeArg);
+ if (MRArg0 && MRArg1 && MRArg0->getBaseRegion() != MRArg1->getBaseRegion()) {
+ // FIXME: Add more logic to filter out valid cases.
+ if (ExplodedNode *N = C.generateNonFatalErrorNode(C.getState())) {
+ reportBug(
+ N, Call.getOriginExpr(), C, BT_DifferentMemRegion,
+ "Constructor args for std::span are from different memory regions");
----------------
haoNoQ wrote:
I don't think you have any "positive" tests for this warning yet. It's a good idea to have some!
Wording: "memory regions" is our local terminology. Users won't necessarily understand what it means. I think we should say "from different sources" or something like that. Though, of course, this warning will probably change quite a bit as we figure out the specific cases in which we want to emit it. (It may be a good idea to have different warnings for different cases.)
https://github.com/llvm/llvm-project/pull/112784
More information about the cfe-commits
mailing list