https://github.com/ziqingluo-90 created https://github.com/llvm/llvm-project/pull/223105
PR #210457 introduces declaration rewriting. When CppBoundedBuffers rewrites a declaration or return type to a bounded_ptr/bounded_array, some of the existing uses of that entity needs retrofit. This commit creates expression rewrites for the following patterns: - Append `.data()` to call arguments when necessary parameter is not transformed but argument is transformed. - Append `.as_bounded<T>()` to call arguments when both parameter and argument are transformed but element types are not identical. - Rewrite `&e[I]` to `(e + i)` and `&*e`/`&(*e)` to `e`, if `e` is transformed. - Rewrite `(T*)e`, `static_cast<T*>(e)`, and `reinterpret_cast<T*>(e)` to `e.as_bounded<T>()`, if `e` is transformed. The 3rd step of rdar://187125348 >From 2ef8f52fd5423fee5eb3b399ef27befc1ecb7c8c Mon Sep 17 00:00:00 2001 From: Ziqing Luo <[email protected]> Date: Fri, 11 Sep 2026 16:11:28 -0700 Subject: [PATCH] [SSAF][clang-reforge] Retrofit expressions after CppBoundedBuffers decl rewrites PR #210457 introduces declaration rewriting. When CppBoundedBuffers rewrites a declaration or return type to a bounded_ptr/bounded_array, some of the existing uses of that entity needs retrofit. This commit creates expression rewrites for the following patterns: - Append '.data()' to call arguments when necessary parameter is not transformed but argument is transformed. - Append '.as_bounded<T>()' to call arguments when both parameter and argument are transformed but element types are not identical. - Rewrite '&e[i]' to '(e + i)' and '&*e'/'&(*e)' to 'e', if 'e' is transformed. - Rewrite '(T*)e', 'static_cast<T*>(e)', and 'reinterpret_cast<T*>(e)' to 'e.as_bounded<T>()', if 'e' is transformed. The 3rd step of rdar://187125348 --- .../SourceTransformation/CMakeLists.txt | 9 + .../Transformations/CppBoundedBuffers.cpp | 424 +++++++++++++++++- .../CppBoundedBuffersTest.cpp | 246 ++++++++++ .../SourceTransformation/BUILD.gn | 7 + 4 files changed, 679 insertions(+), 7 deletions(-) diff --git a/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt b/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt index 96e85ec3a50a8..951a40e874398 100644 --- a/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt +++ b/clang/lib/ScalableStaticAnalysis/SourceTransformation/CMakeLists.txt @@ -2,6 +2,13 @@ set(LLVM_LINK_COMPONENTS Support ) +# TODO: move SSAFAnalysesCommon.h to a higher-up folder shared by both +# libraries instead of reaching into a sibling library's private header. +# +# Needed to reach clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h, +# a private header shared by the two libraries. +include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../Analyses) + add_clang_library(clangScalableStaticAnalysisSourceTransformation SARIFTransformationReportFormat.cpp TransformationRegistry.cpp @@ -12,6 +19,8 @@ add_clang_library(clangScalableStaticAnalysisSourceTransformation clangAST clangBasic clangLex + clangScalableStaticAnalysisAnalyses clangScalableStaticAnalysisCore clangToolingCore + clangToolingRefactoring ) diff --git a/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp b/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp index c119ee2f01c5e..01c27c4dd7637 100644 --- a/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp +++ b/clang/lib/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.cpp @@ -7,11 +7,14 @@ //===----------------------------------------------------------------------===// #include "clang/ScalableStaticAnalysis/SourceTransformation/Transformations/CppBoundedBuffers.h" +#include "../../Analyses/SSAFAnalysesCommon.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DynamicRecursiveASTVisitor.h" +#include "clang/AST/Expr.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/Type.h" #include "clang/AST/TypeLoc.h" #include "clang/Basic/LangOptions.h" @@ -20,6 +23,7 @@ #include "clang/Frontend/SSAFOptions.h" #include "clang/Lex/Lexer.h" #include "clang/ScalableStaticAnalysis/Analyses/EntityPointerLevel/EntityPointerLevel.h" +#include "clang/ScalableStaticAnalysis/Analyses/PointerFlow/PointerFlowPairs.h" #include "clang/ScalableStaticAnalysis/Analyses/UnsafeBufferUsage/UnsafeBufferUsageAnalysis.h" #include "clang/ScalableStaticAnalysis/Core/ASTEntityMapping.h" #include "clang/ScalableStaticAnalysis/Core/Model/EntityId.h" @@ -27,12 +31,18 @@ #include "clang/ScalableStaticAnalysis/Core/Model/EntityName.h" #include "clang/ScalableStaticAnalysis/SourceTransformation/TransformationRegistry.h" #include "clang/Tooling/Core/Replacement.h" +#include "clang/Tooling/Refactoring/AtomicChange.h" +#include "llvm/ADT/DenseMap.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ErrorHandling.h" #include <cassert> #include <map> #include <optional> #include <string> +#include <vector> using namespace clang; using namespace clang::ssaf; @@ -290,6 +300,11 @@ class CollectVisitor : public DynamicRecursiveASTVisitor { /// Rewrites or reports every collected declarator and function return. class RewriteVisitor : public DynamicRecursiveASTVisitor { public: + // Decls and their ClassifyResults for all that are successfully + // transformed by `emit`: + llvm::DenseMap<const Decl *, ClassifyResult> TransformedDecls; + llvm::DenseMap<const FunctionDecl *, ClassifyResult> TransformedReturns; + RewriteVisitor(ASTContext &Ctx, DeclLevels &Decls, ReturnLevels &Returns, SourceEditEmitter &Edits, TransformationReportEmitter &Report) : Ctx(Ctx), Decls(Decls), Returns(Returns), Edits(Edits), Report(Report) { @@ -324,8 +339,12 @@ class RewriteVisitor : public DynamicRecursiveASTVisitor { if (!FunTypeLoc) return report(FD, ReportReason::EmissionFailed); - return report(FD, emit(FD->getBeginLoc(), NameLoc, - FunTypeLoc.getReturnLoc(), FD->getReturnType(), R)); + + auto Reason = emit(FD->getBeginLoc(), NameLoc, FunTypeLoc.getReturnLoc(), + FD->getReturnType(), R); + if (!Reason) + TransformedReturns[FD] = R; + return report(FD, Reason); } private: @@ -347,7 +366,12 @@ class RewriteVisitor : public DynamicRecursiveASTVisitor { if (R.Skip) return (void)report(D, *R.Skip); - report(D, emit(D->getBeginLoc(), NameLoc, TSI->getTypeLoc(), T, R)); + + auto Reason = emit(D->getBeginLoc(), NameLoc, TSI->getTypeLoc(), T, R); + + if (!Reason) + TransformedDecls[D] = R; + report(D, Reason); } /// Compute the precise source range for rewriting. The produced range is @@ -482,6 +506,108 @@ class RewriteVisitor : public DynamicRecursiveASTVisitor { TransformationReportEmitter &Report; }; +// FIXME: adding report for any unsuccessful edits +// FIXME: we need clusters to group edits atomically + +/// Traverses the whole TU and create edits for expressions in order to adapt to +/// transformed Decls. +class ExpressionRewriter { +public: + ExpressionRewriter( + ASTContext &Ctx, const SSAFOptions &Opts, SourceEditEmitter &Edits, + const llvm::DenseMap<const Decl *, ClassifyResult> &TransformedDecls, + const llvm::DenseMap<const FunctionDecl *, ClassifyResult> + &TransformedReturns) + : Ctx(Ctx), Opts(Opts), Edits(Edits), TransformedDecls(TransformedDecls), + TransformedReturns(TransformedReturns) {} + + /// Traverses the whole \c TU and create edits expressions in order to adapt + /// to transformed Decls. + void rewriteExprInTU(const TranslationUnitDecl *TU); + + /// Create edits in \c AC to update expression \c E, if it is + /// \c isPtrExprBaseTransformed, so that \c E has proper bounded type after + /// transformation. + /// + /// Note that this step is self-contained. It does not rely on the context of + /// where \c E is. If \c E's base is transformed, it needs retrofit. + /// + /// \return true iff \c E needs edits. + bool rewriteExpression(const Expr *E, tooling::AtomicChange &AC) const; + + /// Create edits for a \c PointerFlowPair that + /// 1) \c rewriteExpression for involved expressions; and + /// 2) further update expressions in order to adapt the flow to the + /// transformation. + std::optional<tooling::AtomicChange> + adaptPointerFlow(const PointerFlowPair &Pair) const; + +private: + /// Associate edit operations to \c CharSourceRange always, since they carry + /// token/char range info while single SourceLocation doesn't. This way, the + /// edit kind (replacement or insertion) cannot be inferred from the input + /// (i.e., SourceRange vs. SourceLocation). Use this enum to explicitly + /// express the kinds. + enum EditKind { Replace, InsertAtBegin, InsertAtEnd }; + + /// \param Range The source locations associated with the edit. For any \c + /// EditKind, source locations are given by a \c CharSourceRange, which + /// carries the information of whether it is a token range or a char range. + /// \param NewText The text of the edit that will replace a source range or + /// be inserted at a location. + /// \param EditKind The kind of edit: replacement or insertion. + /// \param AC IN/OUT parameter. The new edit will be added to \c AC + /// \return true if edit is successfully added to \c AC + bool addEditToAtomicChange(CharSourceRange Range, StringRef NewText, + EditKind EditKind, + tooling::AtomicChange &AC) const; + + /// \return true iff the base(s) of the pointer/array expression \c E are all + /// transformed to have bounded types. + bool isExprBaseTransformed(const Expr *E) const; + + /// \return a non-null pointer to a \c ClassifyResult, if `D` is transformed + /// to have bounded types. + const ClassifyResult *getDeclClassifyResultsIfTransformed(const Decl *D, + bool IsRet) const; + + /// \return a non-empty vector of \c ClassifyResult, the base(s) of the + /// pointer/array expression \c E are all transformed to have bounded + /// types. + std::vector<const ClassifyResult *> + getPtrExprClassifyResultsIfTransformed(const Expr *E) const; + + //==---------------- Expression rewrite rules -------------------==// + + /// - Append '.data()' to RHS, if LHS is a parameter and NOT transformed, and + /// RHS is transformed; or + /// - Append '.as_bounded<T>()' to RHS, if LHS is a parameter and both sides + /// are transformed but element types are not identical. + /// \return true iff the pattern is matched and edits were attempted to be + /// added into \c AC. + bool adaptCallArgumentInFlow(const PointerFlowPair &Pair, + tooling::AtomicChange &AC) const; + + /// Edit '&e[i]' to '(e + i)' or '&*e' to 'ptr', if 'e' is transformed. + /// \return true iff the pattern is matched and edits were attempted to be + /// added into \c AC. + bool retrofitAddrofElementAccess(const Expr *E, + tooling::AtomicChange &AC) const; + + /// Edit '(T*)e/static_cast<T>(e)/reinterpret_cast<T>(e)' to + /// 'e.as_bounded<T>()' , if 'e' is transformed. + /// \return true iff the pattern is matched and edits were attempted to be + /// added into \c AC. + bool retrofitPointerCast(const Expr *E, tooling::AtomicChange &AC) const; + + ASTContext &Ctx; + const SSAFOptions &Opts; + SourceEditEmitter &Edits; + const llvm::DenseMap<const Decl *, ClassifyResult> &TransformedDecls; + const llvm::DenseMap<const FunctionDecl *, ClassifyResult> + &TransformedReturns; +}; + } // namespace namespace clang::ssaf { @@ -607,14 +733,298 @@ void CppBoundedBuffers::HandleTranslationUnit(ASTContext &Ctx) { DeclLevels Decls; ReturnLevels Returns; - Decl *TU = Ctx.getTranslationUnitDecl(); - CollectVisitor(Reach, TUNamespace, LUNamespace, Decls, Returns) - .TraverseDecl(TU); - RewriteVisitor(Ctx, Decls, Returns, Edits, Report).TraverseDecl(TU); + auto *TU = Ctx.getTranslationUnitDecl(); + CollectVisitor(Reach, Decls, Returns).TraverseDecl(TU); + auto RV = RewriteVisitor(Ctx, Decls, Returns, Edits, Report); + + RV.TraverseDecl(TU); + + ExpressionRewriter ExprRewriter(Ctx, Opts, Edits, RV.TransformedDecls, + RV.TransformedReturns); + + ExprRewriter.rewriteExprInTU(TU); } } // namespace clang::ssaf +namespace { + +//===------------ ExpressionRewriter implementation --------------===// + +std::optional<tooling::AtomicChange> +ExpressionRewriter::adaptPointerFlow(const PointerFlowPair &Pair) const { + tooling::AtomicChange AC("", ""); + + rewriteExpression(Pair.RHS, AC); + adaptCallArgumentInFlow(Pair, AC); // FIXME: more flow patterns + return AC; +} + +bool ExpressionRewriter::rewriteExpression(const Expr *E, + tooling::AtomicChange &AC) const { + if (isExprBaseTransformed(E)) + return retrofitAddrofElementAccess(E, AC) || retrofitPointerCast(E, AC); + return false; +} + +bool ExpressionRewriter::adaptCallArgumentInFlow( + const PointerFlowPair &Pair, tooling::AtomicChange &AC) const { + const auto *PVD = + dyn_cast_or_null<ParmVarDecl>(Pair.LHS.dyn_cast<const ValueDecl *>()); + + if (!PVD) + return false; + + // '(RHS).data()' is a prvalue that can't bind to a reference. So bail. + if (PVD->getType()->isReferenceType()) + return false; + + QualType RTypeBeforeImpCast = Pair.RHS->IgnoreImpCasts()->getType(); + QualType LType = PVD->getType().getNonReferenceType(); + + // If RHS has `void*` type, it will have `char*` after transformation and + // being appened '.data()'. This type change may cause the callee to be + // silently swapped to a different overload. So bail. + if (RTypeBeforeImpCast->isVoidPointerType() && LType->isVoidPointerType()) + return false; + + auto It = TransformedDecls.find(PVD); + bool IsLHSTransformed = It != TransformedDecls.end(); + bool IsRHSTransformed = isExprBaseTransformed(Pair.RHS); + + CharSourceRange RHSRange = Lexer::getAsCharRange( + Pair.RHS->getSourceRange(), Ctx.getSourceManager(), Ctx.getLangOpts()); + + if (!IsLHSTransformed && IsRHSTransformed) { + addEditToAtomicChange(RHSRange, "(", EditKind::InsertAtBegin, AC); + addEditToAtomicChange(RHSRange, ").data()", EditKind::InsertAtEnd, AC); + return true; + } + if (IsLHSTransformed && IsRHSTransformed) { + QualType RPteTy = RTypeBeforeImpCast->getPointeeType(); + QualType LPteTy = LType->getPointeeType(); + + if (LPteTy.isNull() || RPteTy.isNull()) + return false; + + if (Ctx.hasSameType(LPteTy, RPteTy)) + return false; + + StringRef InnerSpelling = It->getSecond().InnerSpelling; + + addEditToAtomicChange(RHSRange, "(", EditKind::InsertAtBegin, AC); + addEditToAtomicChange( + RHSRange, + (").as_bounded<" + InnerSpelling + ">()").getSingleStringRef(), + EditKind::InsertAtEnd, AC); + return true; + } + return false; +} + +bool ExpressionRewriter::retrofitAddrofElementAccess( + const Expr *E, tooling::AtomicChange &AC) const { + const auto *UO = dyn_cast<UnaryOperator>(E->IgnoreParenImpCasts()); + if (!UO || UO->getOpcode() != UO_AddrOf) + return false; + + const Expr *SubExpr = UO->getSubExpr()->IgnoreParenImpCasts(); + const Expr *Ptr, *Offset = nullptr; + + if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(SubExpr)) { + Ptr = ASE->getBase(); + Offset = ASE->getIdx(); + } else if (const auto *Deref = dyn_cast<UnaryOperator>(SubExpr); + Deref && Deref->getOpcode() == UO_Deref) { + Ptr = Deref->getSubExpr(); + } else + return false; + + if (!isExprBaseTransformed(Ptr)) + return false; + + rewriteExpression(Ptr, AC); + // Ptr may have been recursively edited, so its source range should stay + // intact. + + const SourceManager &SM = Ctx.getSourceManager(); + const LangOptions &LO = Ctx.getLangOpts(); + auto PtrCR = Lexer::getAsCharRange(Ptr->getSourceRange(), SM, LO); + auto FullExprCR = Lexer::getAsCharRange(UO->getSourceRange(), SM, LO); + // Source range before `Ptr`: + auto PrePtrCR = + CharSourceRange::getCharRange(FullExprCR.getBegin(), PtrCR.getBegin()); + + if (!Offset) { + // Source range after `Ptr`: + auto PostPtrCR = + CharSourceRange::getCharRange(PtrCR.getEnd(), FullExprCR.getEnd()); + // For '&*ptr' or '&(*ptr)', drop contents in PrePtrCR and PostPtrCR: + addEditToAtomicChange(PrePtrCR, "", EditKind::Replace, AC); + addEditToAtomicChange(PostPtrCR, "", EditKind::Replace, AC); + return true; + } + + auto OffsetCR = Lexer::getAsCharRange(Offset->getSourceRange(), SM, LO); + auto PostPtrPreOffsetCR = + CharSourceRange::getCharRange(PtrCR.getEnd(), OffsetCR.getBegin()); + auto PostOffsetCR = + CharSourceRange::getCharRange(OffsetCR.getEnd(), FullExprCR.getEnd()); + + // For '&ptr[offset]' or '&(ptr[offset])', + // 1. replace contents in PrePtrCR with "(", and + // 2. replace contents in PostPtrPreOffsetCR with " + ", and + // 3. replace contents in postOffsetCR with ") ", + // results in '(ptr + offset)': + addEditToAtomicChange(PrePtrCR, "(", EditKind::Replace, AC); + addEditToAtomicChange(PostPtrPreOffsetCR, " + ", EditKind::Replace, AC); + addEditToAtomicChange(PostOffsetCR, ")", EditKind::Replace, AC); + return true; +} + +bool ExpressionRewriter::retrofitPointerCast(const Expr *E, + tooling::AtomicChange &AC) const { + const auto *CE = dyn_cast<ExplicitCastExpr>(E->IgnoreParenImpCasts()); + + if (!CE || + !isa<CStyleCastExpr, CXXStaticCastExpr, CXXReinterpretCastExpr>(CE)) + return false; + if (!isExprBaseTransformed(CE)) + return false; + + QualType DestTy = CE->getTypeAsWritten(); + QualType DestPteTy = DestTy->getPointeeType(); + + if (!DestTy->isPointerType()) + return false; + + const Expr *Ptr = CE->getSubExpr(); + rewriteExpression(Ptr, AC); + // Ptr may have been recursively edited, so its source range should stay + // intact. + + const SourceManager &SM = Ctx.getSourceManager(); + const LangOptions &LO = Ctx.getLangOpts(); + CharSourceRange PtrCR = + Lexer::getAsCharRange(CE->getSubExpr()->getSourceRange(), SM, LO); + CharSourceRange FullCastExprCR = + Lexer::getAsCharRange(CE->getSourceRange(), SM, LO); + CharSourceRange PrePtrCR = CharSourceRange::getCharRange( + FullCastExprCR.getBegin(), PtrCR.getBegin()); + CharSourceRange PostPtrCR = + CharSourceRange::getCharRange(PtrCR.getEnd(), FullCastExprCR.getEnd()); + std::string T = spell(DestPteTy, Ctx); + + // For `(T*)ptr` or *_cast<T>(ptr), + // 1. replace contents in PrePtrCR with "(", and + // 2. replace contents in PostPtrCR with ").as_bounded<T>()", + // results in '(ptr).as_bounded<T>()'. + addEditToAtomicChange(PrePtrCR, "(", EditKind::Replace, AC); + addEditToAtomicChange(PostPtrCR, ").as_bounded<" + T + ">()", + EditKind::Replace, AC); + return true; +} + +void ExpressionRewriter::rewriteExprInTU(const TranslationUnitDecl *TU) { + llvm::DenseMap<const NamedDecl *, std::vector<const NamedDecl *>> + ContributorGroups; + + findContributors(Ctx, Opts, ContributorGroups, + /*ExtractFromSystemHeaders=*/false); + + llvm::SmallVector<PointerFlowPair> Pairs; + PointerFlowPairMatcher Matcher{Ctx}; + + for (auto &[GrpCano, ContriGrp] : ContributorGroups) + for (auto *ContriDecl : ContriGrp) { + auto PairsCollector = [&Pairs, &Matcher, + &ContriDecl](const DynTypedNode &Node) { + Matcher.matches(Node, ContriDecl, Pairs); + }; + findMatchesIn(ContriDecl, PairsCollector); + } + + for (const PointerFlowPair &Pair : Pairs) { + if (auto AC = adaptPointerFlow(Pair); + AC && llvm::all_of(AC->getReplacements(), + std::mem_fn(&tooling::Replacement::isApplicable))) + for (const tooling::Replacement &R : AC->getReplacements()) + Edits.addReplacement(R); + } +} + +bool ExpressionRewriter::isExprBaseTransformed(const Expr *E) const { + return !getPtrExprClassifyResultsIfTransformed(E).empty(); +} + +const ClassifyResult * +ExpressionRewriter::getDeclClassifyResultsIfTransformed(const Decl *D, + bool IsRet) const { + auto Lookup = [](const auto &Map, const auto *Key) { + auto It = Map.find(Key); + return It == Map.end() ? nullptr : &It->second; + }; + return IsRet ? Lookup(TransformedReturns, cast<FunctionDecl>(D)) + : Lookup(TransformedDecls, D); +} + +std::vector<const ClassifyResult *> +ExpressionRewriter::getPtrExprClassifyResultsIfTransformed( + const Expr *E) const { + auto DPLs = translateDeclPointerLevel(E, Ctx); + + if (!DPLs) { + // Errors indicate no transformation for E. No further action. + llvm::consumeError(DPLs.takeError()); + return {}; + } + + std::vector<const ClassifyResult *> Result; + + for (auto &DPL : *DPLs) { + const auto *ClassifyResult = + getDeclClassifyResultsIfTransformed(DPL.Decl, DPL.IsReturn); + + if (!ClassifyResult) + return {}; + Result.push_back(ClassifyResult); + } + return Result; +} + +bool ExpressionRewriter::addEditToAtomicChange( + CharSourceRange Range, StringRef NewText, EditKind EditKind, + tooling::AtomicChange &AC) const { + assert(Range.isCharRange()); + + if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID()) + // FIXME: report... + return false; + + const SourceManager &SM = Ctx.getSourceManager(); + llvm::Error Err = [&]() -> llvm::Error { + switch (EditKind) { + case Replace: + return AC.replace(SM, Range, NewText); + case InsertAtBegin: + return AC.insert(SM, Range.getBegin(), NewText, /*InsertAfter=*/false); + case InsertAtEnd: + return AC.insert(SM, Range.getEnd(), NewText, /*InsertAfter=*/true); + } + llvm_unreachable("unhandled EditKind"); + }(); + + if (Err) { + llvm::consumeError(std::move(Err)); + // FIXME: generate a Report + // If AtomicChange has an error,the whole should be discard + return false; + } + return true; +} + +} // namespace + namespace clang::ssaf { // NOLINTNEXTLINE(misc-use-internal-linkage) volatile int CppBoundedBuffersAnchorSource = 0; diff --git a/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp b/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp index 54fc08b6e8ab7..003b03d7f2b67 100644 --- a/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp +++ b/clang/unittests/ScalableStaticAnalysis/SourceTransformation/CppBoundedBuffersTest.cpp @@ -687,4 +687,250 @@ TEST_F(CppBoundedBuffersTest, MessageForIsUnique) { } } +//===----------------------------------------------------------------------===// +// Expression rewriting tests +//===----------------------------------------------------------------------===// + +TEST_F(CppBoundedBuffersTest, CallArgAddDataDRE) { + StringRef Code = R"cpp( + void f(int *p); + void g() { int *q; f(q); } + )cpp"; + Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite, + UnsafeBufferReachableAnalysisResult &Result) { + markReachable(Suite, Result, varEntity("q", Ctx), {1}); + }); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(int *p); + void g() { bounded_ptr<int> q; f((q).data()); } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, CallArgAddDataAddrOfDeref) { + StringRef Code = R"cpp( + void f(int *p); + void g() { int *q; f(&*q); } + )cpp"; + Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite, + UnsafeBufferReachableAnalysisResult &Result) { + markReachable(Suite, Result, varEntity("q", Ctx), {1}); + }); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(int *p); + void g() { bounded_ptr<int> q; f((q).data()); } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, CallArgAddDataAddrOfDerefParen) { + StringRef Code = R"cpp( + void f(int *p); + void g() { int *q; f(&(*q)); } + )cpp"; + Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite, + UnsafeBufferReachableAnalysisResult &Result) { + markReachable(Suite, Result, varEntity("q", Ctx), {1}); + }); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(int *p); + void g() { bounded_ptr<int> q; f((q).data()); } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, CallArgAddDataAddrOfSubscript) { + StringRef Code = R"cpp( + void f(int *p); + void g(int i) { int arr[4]; f(&arr[i]); } + )cpp"; + Captured C = + run(Code, [](ASTContext &Ctx) { return varEntity("arr", Ctx); }, {1}); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(int *p); + void g(int i) { bounded_array<int, 4> arr; f(((arr + i)).data()); } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, CallArgAddDataAddrOfSubscript2) { + StringRef Code = R"cpp( + void f(int *p); + void g(int i) { int *q; f(&q[i]); } + )cpp"; + Captured C = + run(Code, [](ASTContext &Ctx) { return varEntity("q", Ctx); }, {1}); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(int *p); + void g(int i) { bounded_ptr<int> q; f(((q + i)).data()); } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, CallArgAddDataPtrCast) { + StringRef Code = R"cpp( + void f(char *p); + void g() { int *q; f((char*)q); } + )cpp"; + Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite, + UnsafeBufferReachableAnalysisResult &Result) { + markReachable(Suite, Result, varEntity("q", Ctx), {1}); + }); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(char *p); + void g() { bounded_ptr<int> q; f(((q).as_bounded<char>()).data()); } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, CallArgAddDataPtrCastStaticCast) { + StringRef Code = R"cpp( + void f(char *p); + void g() { int *q; f(reinterpret_cast<char*>(q)); } + )cpp"; + Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite, + UnsafeBufferReachableAnalysisResult &Result) { + markReachable(Suite, Result, varEntity("q", Ctx), {1}); + }); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(char *p); + void g() { bounded_ptr<int> q; f(((q).as_bounded<char>()).data()); } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, CallArgAddDataPtrCastAddrOfSubscript) { + StringRef Code = R"cpp( + void f(char *p); + void g(int i) { + int *q; + f((char*)&q[i]); + } + )cpp"; + Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite, + UnsafeBufferReachableAnalysisResult &Result) { + markReachable(Suite, Result, varEntity("q", Ctx), {1}); + }); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(char *p); + void g(int i) { + bounded_ptr<int> q; + f((((q + i)).as_bounded<char>()).data()); + } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, AssignPtrCastAddrOfSubscript) { + // The argument is a C-style cast of an address-of-subscript, '(char*)&q[i]'. + // 'q' is transformed but f's parameter is not. + StringRef Code = R"cpp( + void g(int i) { + int *q; + char *p = (char*)&q[i]; + } + )cpp"; + Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite, + UnsafeBufferReachableAnalysisResult &Result) { + markReachable(Suite, Result, varEntity("q", Ctx), {1}); + }); + EXPECT_EQ(C.Rewritten, R"cpp( + void g(int i) { + bounded_ptr<int> q; + char *p = ((q + i)).as_bounded<char>(); + } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, CallArgNoChangeImplicit) { + StringRef Code = R"cpp( + void f(int *p); + void g() { int *q; f(q); } + )cpp"; + Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite, + UnsafeBufferReachableAnalysisResult &Result) { + markReachable(Suite, Result, paramEntity("f", 0, Ctx), {1}); + }); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(bounded_ptr<int> p); + void g() { int *q; f(q); } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, CallArgNoChangeBothHardened) { + // Both the parameter and the argument are rewritten to the same bounded + // type, so the call site needs no adjustment. + StringRef Code = R"cpp( + void f(int *p); + void g() { int *q; f(q); } + )cpp"; + Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite, + UnsafeBufferReachableAnalysisResult &Result) { + markReachable(Suite, Result, paramEntity("f", 0, Ctx), {1}); + markReachable(Suite, Result, varEntity("q", Ctx), {1}); + }); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(bounded_ptr<int> p); + void g() { bounded_ptr<int> q; f(q); } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, CallArgNoChangeVoidParamVoidArg) { + // 'q' is void*, so it gets rewritten to 'bounded_ptr<char>', not + // 'bounded_ptr<void>'. If '.data()' were appended it would return + // 'char *', which could silently steal a different overload of 'f' than + // the original 'void *' argument would have. So bail rather than adding + // '.data()' when both the parameter and the argument's original type are + // 'void*'. + StringRef Code = R"cpp( + void f(void *p); + void g() { void *q; f(q); } + )cpp"; + Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite, + UnsafeBufferReachableAnalysisResult &Result) { + markReachable(Suite, Result, varEntity("q", Ctx), {1}); + }); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(void *p); + void g() { bounded_ptr<char> q; f(q); } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, CallArgNoChangeArrayDecayMatchingType) { + // 'arr' is passed directly (no cast), so its element type must already + // match 'f's parameter pointee type pre-transformation. The implicit + // 'bounded_array<T, N>' -> 'bounded_ptr<T>' conversion bridges this case, + // so no '.as_bounded<T>()' is needed at the call site. + StringRef Code = R"cpp( + void f(int *p); + void g() { int arr[4]; f(arr); } + )cpp"; + Captured C = runMarked(Code, [](ASTContext &Ctx, WPASuite &Suite, + UnsafeBufferReachableAnalysisResult &Result) { + markReachable(Suite, Result, paramEntity("f", 0, Ctx), {1}); + markReachable(Suite, Result, varEntity("arr", Ctx), {1}); + }); + EXPECT_EQ(C.Rewritten, R"cpp( + void f(bounded_ptr<int> p); + void g() { bounded_array<int, 4> arr; f(arr); } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + +TEST_F(CppBoundedBuffersTest, RHSNoChangeRHSNotHardened) { + StringRef Code = R"cpp( + void g() { int arr[4]; int *p = &arr[0]; } + )cpp"; + Captured C = + run(Code, [](ASTContext &Ctx) { return varEntity("p", Ctx); }, {1}); + EXPECT_EQ(C.Rewritten, R"cpp( + void g() { int arr[4]; bounded_ptr<int> p = &arr[0]; } + )cpp"); + EXPECT_TRUE(C.Reports.empty()); +} + } // namespace diff --git a/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/SourceTransformation/BUILD.gn b/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/SourceTransformation/BUILD.gn index e6e1af033db8a..0ffac2699ffae 100644 --- a/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/SourceTransformation/BUILD.gn +++ b/llvm/utils/gn/secondary/clang/lib/ScalableStaticAnalysis/SourceTransformation/BUILD.gn @@ -1,11 +1,18 @@ static_library("SourceTransformation") { output_name = "clangScalableStaticAnalysisSourceTransformation" configs += [ "//llvm/utils/gn/build:clang_code" ] + + # Needed to reach + # clang/lib/ScalableStaticAnalysis/Analyses/SSAFAnalysesCommon.h, a private + # header shared by the two libraries. + include_dirs = [ "//clang/lib/ScalableStaticAnalysis/Analyses" ] deps = [ "//clang/lib/AST", "//clang/lib/Basic", + "//clang/lib/ScalableStaticAnalysis/Analyses", "//clang/lib/ScalableStaticAnalysis/Core", "//clang/lib/Tooling/Core", + "//clang/lib/Tooling/Refactoring", "//llvm/lib/Support", ] sources = [ _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
