4501 lines · cpp
1//===- UnsafeBufferUsage.cpp - Replace pointers with modern C++ -----------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "clang/Analysis/Analyses/UnsafeBufferUsage.h"10#include "clang/AST/APValue.h"11#include "clang/AST/ASTContext.h"12#include "clang/AST/ASTTypeTraits.h"13#include "clang/AST/Attr.h"14#include "clang/AST/Decl.h"15#include "clang/AST/DeclCXX.h"16#include "clang/AST/DeclTemplate.h"17#include "clang/AST/DynamicRecursiveASTVisitor.h"18#include "clang/AST/Expr.h"19#include "clang/AST/FormatString.h"20#include "clang/AST/ParentMapContext.h"21#include "clang/AST/Stmt.h"22#include "clang/AST/StmtVisitor.h"23#include "clang/AST/Type.h"24#include "clang/ASTMatchers/LowLevelHelpers.h"25#include "clang/Analysis/Support/FixitUtil.h"26#include "clang/Basic/SourceLocation.h"27#include "clang/Lex/Lexer.h"28#include "clang/Lex/Preprocessor.h"29#include "llvm/ADT/APInt.h"30#include "llvm/ADT/APSInt.h"31#include "llvm/ADT/STLFunctionalExtras.h"32#include "llvm/ADT/SmallSet.h"33#include "llvm/ADT/SmallVector.h"34#include "llvm/ADT/StringRef.h"35#include <cstddef>36#include <optional>37#include <queue>38#include <set>39#include <sstream>40 41using namespace clang;42 43#ifndef NDEBUG44namespace {45class StmtDebugPrinter46 : public ConstStmtVisitor<StmtDebugPrinter, std::string> {47public:48 std::string VisitStmt(const Stmt *S) { return S->getStmtClassName(); }49 50 std::string VisitBinaryOperator(const BinaryOperator *BO) {51 return "BinaryOperator(" + BO->getOpcodeStr().str() + ")";52 }53 54 std::string VisitUnaryOperator(const UnaryOperator *UO) {55 return "UnaryOperator(" + UO->getOpcodeStr(UO->getOpcode()).str() + ")";56 }57 58 std::string VisitImplicitCastExpr(const ImplicitCastExpr *ICE) {59 return "ImplicitCastExpr(" + std::string(ICE->getCastKindName()) + ")";60 }61};62 63// Returns a string of ancestor `Stmt`s of the given `DRE` in such a form:64// "DRE ==> parent-of-DRE ==> grandparent-of-DRE ==> ...".65static std::string getDREAncestorString(const DeclRefExpr *DRE,66 ASTContext &Ctx) {67 std::stringstream SS;68 const Stmt *St = DRE;69 StmtDebugPrinter StmtPriner;70 71 do {72 SS << StmtPriner.Visit(St);73 74 DynTypedNodeList StParents = Ctx.getParents(*St);75 76 if (StParents.size() > 1)77 return "unavailable due to multiple parents";78 if (StParents.empty())79 break;80 St = StParents.begin()->get<Stmt>();81 if (St)82 SS << " ==> ";83 } while (St);84 return SS.str();85}86 87} // namespace88#endif /* NDEBUG */89 90namespace {91// Using a custom `FastMatcher` instead of ASTMatchers to achieve better92// performance. FastMatcher uses simple function `matches` to find if a node93// is a match, avoiding the dependency on the ASTMatchers framework which94// provide a nice abstraction, but incur big performance costs.95class FastMatcher {96public:97 virtual bool matches(const DynTypedNode &DynNode, ASTContext &Ctx,98 const UnsafeBufferUsageHandler &Handler) = 0;99 virtual ~FastMatcher() = default;100};101 102class MatchResult {103 104public:105 template <typename T> const T *getNodeAs(StringRef ID) const {106 auto It = Nodes.find(ID);107 if (It == Nodes.end()) {108 return nullptr;109 }110 return It->second.get<T>();111 }112 113 void addNode(StringRef ID, const DynTypedNode &Node) { Nodes[ID] = Node; }114 115private:116 llvm::StringMap<DynTypedNode> Nodes;117};118} // namespace119 120#define SIZED_CONTAINER_OR_VIEW_LIST \121 "span", "array", "vector", "basic_string_view", "basic_string", \122 "initializer_list",123 124// A `RecursiveASTVisitor` that traverses all descendants of a given node "n"125// except for those belonging to a different callable of "n".126class MatchDescendantVisitor : public DynamicRecursiveASTVisitor {127public:128 // Creates an AST visitor that matches `Matcher` on all129 // descendants of a given node "n" except for the ones130 // belonging to a different callable of "n".131 MatchDescendantVisitor(ASTContext &Context, FastMatcher &Matcher,132 bool FindAll, bool ignoreUnevaluatedContext,133 const UnsafeBufferUsageHandler &NewHandler)134 : Matcher(&Matcher), FindAll(FindAll), Matches(false),135 ignoreUnevaluatedContext(ignoreUnevaluatedContext),136 ActiveASTContext(&Context), Handler(&NewHandler) {137 ShouldVisitTemplateInstantiations = true;138 ShouldVisitImplicitCode = false; // TODO: let's ignore implicit code for now139 }140 141 // Returns true if a match is found in a subtree of `DynNode`, which belongs142 // to the same callable of `DynNode`.143 bool findMatch(const DynTypedNode &DynNode) {144 Matches = false;145 if (const Stmt *StmtNode = DynNode.get<Stmt>()) {146 TraverseStmt(const_cast<Stmt *>(StmtNode));147 return Matches;148 }149 return false;150 }151 152 // The following are overriding methods from the base visitor class.153 // They are public only to allow CRTP to work. They are *not *part154 // of the public API of this class.155 156 // For the matchers so far used in safe buffers, we only need to match157 // `Stmt`s. To override more as needed.158 159 bool TraverseDecl(Decl *Node) override {160 if (!Node)161 return true;162 if (!match(*Node))163 return false;164 // To skip callables:165 if (isa<FunctionDecl, BlockDecl, ObjCMethodDecl>(Node))166 return true;167 // Traverse descendants168 return DynamicRecursiveASTVisitor::TraverseDecl(Node);169 }170 171 bool TraverseGenericSelectionExpr(GenericSelectionExpr *Node) override {172 // These are unevaluated, except the result expression.173 if (ignoreUnevaluatedContext)174 return TraverseStmt(Node->getResultExpr());175 return DynamicRecursiveASTVisitor::TraverseGenericSelectionExpr(Node);176 }177 178 bool179 TraverseUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *Node) override {180 // Unevaluated context.181 if (ignoreUnevaluatedContext)182 return true;183 return DynamicRecursiveASTVisitor::TraverseUnaryExprOrTypeTraitExpr(Node);184 }185 186 bool TraverseTypeOfExprTypeLoc(TypeOfExprTypeLoc Node,187 bool TraverseQualifier) override {188 // Unevaluated context.189 if (ignoreUnevaluatedContext)190 return true;191 return DynamicRecursiveASTVisitor::TraverseTypeOfExprTypeLoc(192 Node, TraverseQualifier);193 }194 195 bool TraverseDecltypeTypeLoc(DecltypeTypeLoc Node,196 bool TraverseQualifier) override {197 // Unevaluated context.198 if (ignoreUnevaluatedContext)199 return true;200 return DynamicRecursiveASTVisitor::TraverseDecltypeTypeLoc(201 Node, TraverseQualifier);202 }203 204 bool TraverseCXXNoexceptExpr(CXXNoexceptExpr *Node) override {205 // Unevaluated context.206 if (ignoreUnevaluatedContext)207 return true;208 return DynamicRecursiveASTVisitor::TraverseCXXNoexceptExpr(Node);209 }210 211 bool TraverseCXXTypeidExpr(CXXTypeidExpr *Node) override {212 // Unevaluated context.213 if (ignoreUnevaluatedContext)214 return true;215 return DynamicRecursiveASTVisitor::TraverseCXXTypeidExpr(Node);216 }217 218 bool TraverseCXXDefaultInitExpr(CXXDefaultInitExpr *Node) override {219 if (!TraverseStmt(Node->getExpr()))220 return false;221 return DynamicRecursiveASTVisitor::TraverseCXXDefaultInitExpr(Node);222 }223 224 bool TraverseStmt(Stmt *Node) override {225 if (!Node)226 return true;227 if (!match(*Node))228 return false;229 return DynamicRecursiveASTVisitor::TraverseStmt(Node);230 }231 232private:233 // Sets 'Matched' to true if 'Matcher' matches 'Node'234 //235 // Returns 'true' if traversal should continue after this function236 // returns, i.e. if no match is found or 'Bind' is 'BK_All'.237 template <typename T> bool match(const T &Node) {238 if (Matcher->matches(DynTypedNode::create(Node), *ActiveASTContext,239 *Handler)) {240 Matches = true;241 if (!FindAll)242 return false; // Abort as soon as a match is found.243 }244 return true;245 }246 247 FastMatcher *const Matcher;248 // When true, finds all matches. When false, finds the first match and stops.249 const bool FindAll;250 bool Matches;251 bool ignoreUnevaluatedContext;252 ASTContext *ActiveASTContext;253 const UnsafeBufferUsageHandler *Handler;254};255 256// Because we're dealing with raw pointers, let's define what we mean by that.257static bool hasPointerType(const Expr &E) {258 return isa<PointerType>(E.getType().getCanonicalType());259}260 261static bool hasArrayType(const Expr &E) {262 return isa<ArrayType>(E.getType().getCanonicalType());263}264 265static void266forEachDescendantEvaluatedStmt(const Stmt *S, ASTContext &Ctx,267 const UnsafeBufferUsageHandler &Handler,268 FastMatcher &Matcher) {269 MatchDescendantVisitor Visitor(Ctx, Matcher, /*FindAll=*/true,270 /*ignoreUnevaluatedContext=*/true, Handler);271 Visitor.findMatch(DynTypedNode::create(*S));272}273 274static void forEachDescendantStmt(const Stmt *S, ASTContext &Ctx,275 const UnsafeBufferUsageHandler &Handler,276 FastMatcher &Matcher) {277 MatchDescendantVisitor Visitor(Ctx, Matcher, /*FindAll=*/true,278 /*ignoreUnevaluatedContext=*/false, Handler);279 Visitor.findMatch(DynTypedNode::create(*S));280}281 282// Matches a `Stmt` node iff the node is in a safe-buffer opt-out region283static bool notInSafeBufferOptOut(const Stmt &Node,284 const UnsafeBufferUsageHandler *Handler) {285 return !Handler->isSafeBufferOptOut(Node.getBeginLoc());286}287 288static bool289ignoreUnsafeBufferInContainer(const Stmt &Node,290 const UnsafeBufferUsageHandler *Handler) {291 return Handler->ignoreUnsafeBufferInContainer(Node.getBeginLoc());292}293 294static bool ignoreUnsafeLibcCall(const ASTContext &Ctx, const Stmt &Node,295 const UnsafeBufferUsageHandler *Handler) {296 if (Ctx.getLangOpts().CPlusPlus)297 return Handler->ignoreUnsafeBufferInLibcCall(Node.getBeginLoc());298 return true; /* Only warn about libc calls for C++ */299}300 301// Finds any expression 'e' such that `OnResult`302// matches 'e' and 'e' is in an Unspecified Lvalue Context.303static void findStmtsInUnspecifiedLvalueContext(304 const Stmt *S, const llvm::function_ref<void(const Expr *)> OnResult) {305 if (const auto *CE = dyn_cast<ImplicitCastExpr>(S);306 CE && CE->getCastKind() == CastKind::CK_LValueToRValue)307 OnResult(CE->getSubExpr());308 if (const auto *BO = dyn_cast<BinaryOperator>(S);309 BO && BO->getOpcode() == BO_Assign)310 OnResult(BO->getLHS());311}312 313// Finds any expression `e` such that `InnerMatcher` matches `e` and314// `e` is in an Unspecified Pointer Context (UPC).315static void findStmtsInUnspecifiedPointerContext(316 const Stmt *S, llvm::function_ref<void(const Stmt *)> InnerMatcher) {317 // A UPC can be318 // 1. an argument of a function call (except the callee has [[unsafe_...]]319 // attribute), or320 // 2. the operand of a pointer-to-(integer or bool) cast operation; or321 // 3. the operand of a comparator operation; or322 // 4. the operand of a pointer subtraction operation323 // (i.e., computing the distance between two pointers); or ...324 325 if (auto *CE = dyn_cast<CallExpr>(S)) {326 if (const auto *FnDecl = CE->getDirectCallee();327 FnDecl && FnDecl->hasAttr<UnsafeBufferUsageAttr>())328 return;329 ast_matchers::matchEachArgumentWithParamType(330 *CE, [&InnerMatcher](QualType Type, const Expr *Arg) {331 if (Type->isAnyPointerType())332 InnerMatcher(Arg);333 });334 }335 336 if (auto *CE = dyn_cast<CastExpr>(S)) {337 if (CE->getCastKind() != CastKind::CK_PointerToIntegral &&338 CE->getCastKind() != CastKind::CK_PointerToBoolean)339 return;340 if (!hasPointerType(*CE->getSubExpr()))341 return;342 InnerMatcher(CE->getSubExpr());343 }344 345 // Pointer comparison operator.346 if (const auto *BO = dyn_cast<BinaryOperator>(S);347 BO && (BO->getOpcode() == BO_EQ || BO->getOpcode() == BO_NE ||348 BO->getOpcode() == BO_LT || BO->getOpcode() == BO_LE ||349 BO->getOpcode() == BO_GT || BO->getOpcode() == BO_GE)) {350 auto *LHS = BO->getLHS();351 if (hasPointerType(*LHS))352 InnerMatcher(LHS);353 354 auto *RHS = BO->getRHS();355 if (hasPointerType(*RHS))356 InnerMatcher(RHS);357 }358 359 // Pointer subtractions.360 if (const auto *BO = dyn_cast<BinaryOperator>(S);361 BO && BO->getOpcode() == BO_Sub && hasPointerType(*BO->getLHS()) &&362 hasPointerType(*BO->getRHS())) {363 // Note that here we need both LHS and RHS to be364 // pointer. Then the inner matcher can match any of365 // them:366 InnerMatcher(BO->getLHS());367 InnerMatcher(BO->getRHS());368 }369 // FIXME: any more cases? (UPC excludes the RHS of an assignment. For now370 // we don't have to check that.)371}372 373// Finds statements in unspecified untyped context i.e. any expression 'e' such374// that `InnerMatcher` matches 'e' and 'e' is in an unspecified untyped context375// (i.e the expression 'e' isn't evaluated to an RValue). For example, consider376// the following code:377// int *p = new int[4];378// int *q = new int[4];379// if ((p = q)) {}380// p = q;381// The expression `p = q` in the conditional of the `if` statement382// `if ((p = q))` is evaluated as an RValue, whereas the expression `p = q;`383// in the assignment statement is in an untyped context.384static void findStmtsInUnspecifiedUntypedContext(385 const Stmt *S, llvm::function_ref<void(const Stmt *)> InnerMatcher) {386 // An unspecified context can be387 // 1. A compound statement,388 // 2. The body of an if statement389 // 3. Body of a loop390 if (auto *CS = dyn_cast<CompoundStmt>(S)) {391 for (auto *Child : CS->body())392 InnerMatcher(Child);393 }394 if (auto *IfS = dyn_cast<IfStmt>(S)) {395 if (IfS->getThen())396 InnerMatcher(IfS->getThen());397 if (IfS->getElse())398 InnerMatcher(IfS->getElse());399 }400 // FIXME: Handle loop bodies.401}402 403// Returns true iff integer E1 is equivalent to integer E2.404//405// For now we only support such expressions:406// expr := DRE | const-value | expr BO expr407// BO := '*' | '+'408//409// FIXME: We can reuse the expression comparator of the interop analysis after410// it has been upstreamed.411static bool areEqualIntegers(const Expr *E1, const Expr *E2, ASTContext &Ctx);412static bool areEqualIntegralBinaryOperators(const BinaryOperator *E1,413 const Expr *E2_LHS,414 BinaryOperatorKind BOP,415 const Expr *E2_RHS,416 ASTContext &Ctx) {417 if (E1->getOpcode() == BOP) {418 switch (BOP) {419 // Commutative operators:420 case BO_Mul:421 case BO_Add:422 return (areEqualIntegers(E1->getLHS(), E2_LHS, Ctx) &&423 areEqualIntegers(E1->getRHS(), E2_RHS, Ctx)) ||424 (areEqualIntegers(E1->getLHS(), E2_RHS, Ctx) &&425 areEqualIntegers(E1->getRHS(), E2_LHS, Ctx));426 default:427 return false;428 }429 }430 return false;431}432 433static bool areEqualIntegers(const Expr *E1, const Expr *E2, ASTContext &Ctx) {434 E1 = E1->IgnoreParenImpCasts();435 E2 = E2->IgnoreParenImpCasts();436 if (!E1->getType()->isIntegerType() || E1->getType() != E2->getType())437 return false;438 439 Expr::EvalResult ER1, ER2;440 441 // If both are constants:442 if (E1->EvaluateAsInt(ER1, Ctx) && E2->EvaluateAsInt(ER2, Ctx))443 return ER1.Val.getInt() == ER2.Val.getInt();444 445 // Otherwise, they should have identical stmt kind:446 if (E1->getStmtClass() != E2->getStmtClass())447 return false;448 switch (E1->getStmtClass()) {449 case Stmt::DeclRefExprClass:450 return cast<DeclRefExpr>(E1)->getDecl() == cast<DeclRefExpr>(E2)->getDecl();451 case Stmt::BinaryOperatorClass: {452 auto BO2 = cast<BinaryOperator>(E2);453 return areEqualIntegralBinaryOperators(cast<BinaryOperator>(E1),454 BO2->getLHS(), BO2->getOpcode(),455 BO2->getRHS(), Ctx);456 }457 default:458 return false;459 }460}461 462// Providing that `Ptr` is a pointer and `Size` is an unsigned-integral463// expression, returns true iff they follow one of the following safe464// patterns:465// 1. Ptr is `DRE.data()` and Size is `DRE.size()`, where DRE is a hardened466// container or view;467//468// 2. Ptr is `a` and Size is `n`, where `a` is of an array-of-T with constant469// size `n`;470//471// 3. Ptr is `&var` and Size is `1`; or472// Ptr is `std::addressof(...)` and Size is `1`;473//474// 4. Size is `0`;475static bool isPtrBufferSafe(const Expr *Ptr, const Expr *Size,476 ASTContext &Ctx) {477 // Pattern 1:478 if (auto *MCEPtr = dyn_cast<CXXMemberCallExpr>(Ptr->IgnoreParenImpCasts()))479 if (auto *MCESize =480 dyn_cast<CXXMemberCallExpr>(Size->IgnoreParenImpCasts())) {481 auto *DREOfPtr = dyn_cast<DeclRefExpr>(482 MCEPtr->getImplicitObjectArgument()->IgnoreParenImpCasts());483 auto *DREOfSize = dyn_cast<DeclRefExpr>(484 MCESize->getImplicitObjectArgument()->IgnoreParenImpCasts());485 486 if (!DREOfPtr || !DREOfSize)487 return false; // not in safe pattern488 // We need to make sure 'a' is identical to 'b' for 'a.data()' and489 // 'b.size()' otherwise we do not know they match:490 if (DREOfPtr->getDecl() != DREOfSize->getDecl())491 return false;492 if (MCEPtr->getMethodDecl()->getName() != "data")493 return false;494 // `MCEPtr->getRecordDecl()` must be non-null as `DREOfPtr` is non-null:495 if (!MCEPtr->getRecordDecl()->isInStdNamespace())496 return false;497 498 auto *ObjII = MCEPtr->getRecordDecl()->getIdentifier();499 500 if (!ObjII)501 return false;502 503 bool AcceptSizeBytes = Ptr->getType()->getPointeeType()->isCharType();504 505 if (!((AcceptSizeBytes &&506 MCESize->getMethodDecl()->getName() == "size_bytes") ||507 // Note here the pointer must be a pointer-to-char type unless there508 // is explicit casting. If there is explicit casting, this branch509 // is unreachable. Thus, at this branch "size" and "size_bytes" are510 // equivalent as the pointer is a char pointer:511 MCESize->getMethodDecl()->getName() == "size"))512 return false;513 514 return llvm::is_contained({SIZED_CONTAINER_OR_VIEW_LIST},515 ObjII->getName());516 }517 518 Expr::EvalResult ER;519 520 // Pattern 2-4:521 if (Size->EvaluateAsInt(ER, Ctx)) {522 // Pattern 2:523 if (auto *DRE = dyn_cast<DeclRefExpr>(Ptr->IgnoreParenImpCasts())) {524 if (auto *CAT = Ctx.getAsConstantArrayType(DRE->getType())) {525 llvm::APSInt SizeInt = ER.Val.getInt();526 527 return llvm::APSInt::compareValues(528 SizeInt, llvm::APSInt(CAT->getSize(), true)) == 0;529 }530 return false;531 }532 533 // Pattern 3:534 if (ER.Val.getInt().isOne()) {535 if (auto *UO = dyn_cast<UnaryOperator>(Ptr->IgnoreParenImpCasts()))536 return UO && UO->getOpcode() == UnaryOperator::Opcode::UO_AddrOf;537 if (auto *CE = dyn_cast<CallExpr>(Ptr->IgnoreParenImpCasts())) {538 auto *FnDecl = CE->getDirectCallee();539 540 return FnDecl && FnDecl->getNameAsString() == "addressof" &&541 FnDecl->isInStdNamespace();542 }543 return false;544 }545 // Pattern 4:546 if (ER.Val.getInt().isZero())547 return true;548 }549 return false;550}551 552// Given a two-param std::span construct call, matches iff the call has the553// following forms:554// 1. `std::span<T>{new T[n], n}`, where `n` is a literal or a DRE555// 2. `std::span<T>{new T, 1}`556// 3. `std::span<T>{ (char *)f(args), args[N] * arg*[M]}`, where557// `f` is a function with attribute `alloc_size(N, M)`;558// `args` represents the list of arguments;559// `N, M` are parameter indexes to the allocating element number and size.560// Sometimes, there is only one parameter index representing the total561// size.562// 4. `std::span<T>{x.begin(), x.end()}` where `x` is an object in the563// SIZED_CONTAINER_OR_VIEW_LIST.564// 5. `isPtrBufferSafe` returns true for the two arguments of the span565// constructor566static bool isSafeSpanTwoParamConstruct(const CXXConstructExpr &Node,567 ASTContext &Ctx) {568 assert(Node.getNumArgs() == 2 &&569 "expecting a two-parameter std::span constructor");570 const Expr *Arg0 = Node.getArg(0)->IgnoreParenImpCasts();571 const Expr *Arg1 = Node.getArg(1)->IgnoreParenImpCasts();572 auto HaveEqualConstantValues = [&Ctx](const Expr *E0, const Expr *E1) {573 if (auto E0CV = E0->getIntegerConstantExpr(Ctx))574 if (auto E1CV = E1->getIntegerConstantExpr(Ctx)) {575 return llvm::APSInt::compareValues(*E0CV, *E1CV) == 0;576 }577 return false;578 };579 auto AreSameDRE = [](const Expr *E0, const Expr *E1) {580 if (auto *DRE0 = dyn_cast<DeclRefExpr>(E0))581 if (auto *DRE1 = dyn_cast<DeclRefExpr>(E1)) {582 return DRE0->getDecl() == DRE1->getDecl();583 }584 return false;585 };586 std::optional<llvm::APSInt> Arg1CV = Arg1->getIntegerConstantExpr(Ctx);587 588 if (Arg1CV && Arg1CV->isZero())589 // Check form 5:590 return true;591 592 // Check forms 1-2:593 switch (Arg0->getStmtClass()) {594 case Stmt::CXXNewExprClass:595 if (auto Size = cast<CXXNewExpr>(Arg0)->getArraySize()) {596 // Check form 1:597 return AreSameDRE((*Size)->IgnoreImplicit(), Arg1) ||598 HaveEqualConstantValues(*Size, Arg1);599 }600 // TODO: what's placeholder type? avoid it for now.601 if (!cast<CXXNewExpr>(Arg0)->hasPlaceholderType()) {602 // Check form 2:603 return Arg1CV && Arg1CV->isOne();604 }605 break;606 default:607 break;608 }609 610 // Check form 3:611 if (auto CCast = dyn_cast<CStyleCastExpr>(Arg0)) {612 if (!CCast->getType()->isPointerType())613 return false;614 615 QualType PteTy = CCast->getType()->getPointeeType();616 617 if (!(PteTy->isConstantSizeType() && Ctx.getTypeSizeInChars(PteTy).isOne()))618 return false;619 620 if (const auto *Call = dyn_cast<CallExpr>(CCast->getSubExpr())) {621 if (const FunctionDecl *FD = Call->getDirectCallee())622 if (auto *AllocAttr = FD->getAttr<AllocSizeAttr>()) {623 const Expr *EleSizeExpr =624 Call->getArg(AllocAttr->getElemSizeParam().getASTIndex());625 // NumElemIdx is invalid if AllocSizeAttr has 1 argument:626 ParamIdx NumElemIdx = AllocAttr->getNumElemsParam();627 628 if (!NumElemIdx.isValid())629 return areEqualIntegers(Arg1, EleSizeExpr, Ctx);630 631 const Expr *NumElesExpr = Call->getArg(NumElemIdx.getASTIndex());632 633 if (auto BO = dyn_cast<BinaryOperator>(Arg1))634 return areEqualIntegralBinaryOperators(BO, NumElesExpr, BO_Mul,635 EleSizeExpr, Ctx);636 }637 }638 }639 // Check form 4:640 auto IsMethodCallToSizedObject = [](const Stmt *Node, StringRef MethodName) {641 if (const auto *MC = dyn_cast<CXXMemberCallExpr>(Node)) {642 const auto *MD = MC->getMethodDecl();643 const auto *RD = MC->getRecordDecl();644 645 if (RD && MD)646 if (auto *II = RD->getDeclName().getAsIdentifierInfo();647 II && RD->isInStdNamespace())648 return llvm::is_contained({SIZED_CONTAINER_OR_VIEW_LIST},649 II->getName()) &&650 MD->getName() == MethodName;651 }652 return false;653 };654 655 if (IsMethodCallToSizedObject(Arg0, "begin") &&656 IsMethodCallToSizedObject(Arg1, "end"))657 return AreSameDRE(658 // We know Arg0 and Arg1 are `CXXMemberCallExpr`s:659 cast<CXXMemberCallExpr>(Arg0)660 ->getImplicitObjectArgument()661 ->IgnoreParenImpCasts(),662 cast<CXXMemberCallExpr>(Arg1)663 ->getImplicitObjectArgument()664 ->IgnoreParenImpCasts());665 666 // Check 5:667 return isPtrBufferSafe(Arg0, Arg1, Ctx);668}669 670static bool isSafeArraySubscript(const ArraySubscriptExpr &Node,671 const ASTContext &Ctx) {672 // FIXME: Proper solution:673 // - refactor Sema::CheckArrayAccess674 // - split safe/OOB/unknown decision logic from diagnostics emitting code675 // - e. g. "Try harder to find a NamedDecl to point at in the note."676 // already duplicated677 // - call both from Sema and from here678 679 uint64_t limit;680 if (const auto *CATy =681 dyn_cast<ConstantArrayType>(Node.getBase()682 ->IgnoreParenImpCasts()683 ->getType()684 ->getUnqualifiedDesugaredType())) {685 limit = CATy->getLimitedSize();686 } else if (const auto *SLiteral = dyn_cast<clang::StringLiteral>(687 Node.getBase()->IgnoreParenImpCasts())) {688 limit = SLiteral->getLength() + 1;689 } else {690 return false;691 }692 693 Expr::EvalResult EVResult;694 const Expr *IndexExpr = Node.getIdx();695 if (!IndexExpr->isValueDependent() &&696 IndexExpr->EvaluateAsInt(EVResult, Ctx)) {697 llvm::APSInt ArrIdx = EVResult.Val.getInt();698 // FIXME: ArrIdx.isNegative() we could immediately emit an error as that's a699 // bug700 if (ArrIdx.isNonNegative() && ArrIdx.getLimitedValue() < limit)701 return true;702 } else if (const auto *BE = dyn_cast<BinaryOperator>(IndexExpr)) {703 // For an integer expression `e` and an integer constant `n`, `e & n` and704 // `n & e` are bounded by `n`:705 if (BE->getOpcode() != BO_And && BE->getOpcode() != BO_Rem)706 return false;707 708 const Expr *LHS = BE->getLHS();709 const Expr *RHS = BE->getRHS();710 711 if (BE->getOpcode() == BO_Rem) {712 // If n is a negative number, then n % const can be greater than const713 if (!LHS->getType()->isUnsignedIntegerType()) {714 return false;715 }716 717 if (!RHS->isValueDependent() && RHS->EvaluateAsInt(EVResult, Ctx)) {718 llvm::APSInt result = EVResult.Val.getInt();719 if (result.isNonNegative() && result.getLimitedValue() <= limit)720 return true;721 }722 723 return false;724 }725 726 if ((!LHS->isValueDependent() &&727 LHS->EvaluateAsInt(EVResult, Ctx)) || // case: `n & e`728 (!RHS->isValueDependent() &&729 RHS->EvaluateAsInt(EVResult, Ctx))) { // `e & n`730 llvm::APSInt result = EVResult.Val.getInt();731 if (result.isNonNegative() && result.getLimitedValue() < limit)732 return true;733 }734 return false;735 }736 return false;737}738 739namespace libc_func_matchers {740// Under `libc_func_matchers`, define a set of matchers that match unsafe741// functions in libc and unsafe calls to them.742 743// A tiny parser to strip off common prefix and suffix of libc function names744// in real code.745//746// Given a function name, `matchName` returns `CoreName` according to the747// following grammar:748//749// LibcName := CoreName | CoreName + "_s"750// MatchingName := "__builtin_" + LibcName |751// "__builtin___" + LibcName + "_chk" |752// "__asan_" + LibcName753//754struct LibcFunNamePrefixSuffixParser {755 StringRef matchName(StringRef FunName, bool isBuiltin) {756 // Try to match __builtin_:757 if (isBuiltin && FunName.starts_with("__builtin_"))758 // Then either it is __builtin_LibcName or __builtin___LibcName_chk or759 // no match:760 return matchLibcNameOrBuiltinChk(761 FunName.drop_front(10 /* truncate "__builtin_" */));762 // Try to match __asan_:763 if (FunName.starts_with("__asan_"))764 return matchLibcName(FunName.drop_front(7 /* truncate of "__asan_" */));765 return matchLibcName(FunName);766 }767 768 // Parameter `Name` is the substring after stripping off the prefix769 // "__builtin_".770 StringRef matchLibcNameOrBuiltinChk(StringRef Name) {771 if (Name.starts_with("__") && Name.ends_with("_chk"))772 return matchLibcName(773 Name.drop_front(2).drop_back(4) /* truncate "__" and "_chk" */);774 return matchLibcName(Name);775 }776 777 StringRef matchLibcName(StringRef Name) {778 if (Name.ends_with("_s"))779 return Name.drop_back(2 /* truncate "_s" */);780 return Name;781 }782};783 784// Constant fold a conditional expression 'cond ? A : B' to785// - 'A', if 'cond' has constant true value;786// - 'B', if 'cond' has constant false value.787static const Expr *tryConstantFoldConditionalExpr(const Expr *E,788 const ASTContext &Ctx) {789 // FIXME: more places can use this function790 if (const auto *CE = dyn_cast<ConditionalOperator>(E)) {791 bool CondEval;792 793 if (CE->getCond()->EvaluateAsBooleanCondition(CondEval, Ctx))794 return CondEval ? CE->getLHS() : CE->getRHS();795 }796 return E;797}798 799// A pointer type expression is known to be null-terminated, if it has the800// form: E.c_str(), for any expression E of `std::string` type.801static bool isNullTermPointer(const Expr *Ptr, ASTContext &Ctx) {802 Ptr = tryConstantFoldConditionalExpr(Ptr, Ctx);803 if (isa<clang::StringLiteral>(Ptr->IgnoreParenImpCasts()))804 return true;805 if (isa<PredefinedExpr>(Ptr->IgnoreParenImpCasts()))806 return true;807 if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Ptr->IgnoreParenImpCasts())) {808 const CXXMethodDecl *MD = MCE->getMethodDecl();809 const CXXRecordDecl *RD = MCE->getRecordDecl()->getCanonicalDecl();810 811 if (MD && RD && RD->isInStdNamespace() && MD->getIdentifier())812 if (MD->getName() == "c_str" && RD->getName() == "basic_string")813 return true;814 }815 return false;816}817 818// Return true iff at least one of following cases holds:819// 1. Format string is a literal and there is an unsafe pointer argument820// corresponding to an `s` specifier;821// 2. Format string is not a literal and there is least an unsafe pointer822// argument (including the formatter argument).823//824// `UnsafeArg` is the output argument that will be set only if this function825// returns true.826static bool hasUnsafeFormatOrSArg(const CallExpr *Call, const Expr *&UnsafeArg,827 const unsigned FmtArgIdx, ASTContext &Ctx,828 bool isKprintf = false) {829 class StringFormatStringHandler830 : public analyze_format_string::FormatStringHandler {831 const CallExpr *Call;832 unsigned FmtArgIdx;833 const Expr *&UnsafeArg;834 ASTContext &Ctx;835 836 // Returns an `Expr` representing the precision if specified, null837 // otherwise.838 // The parameter `Call` is a printf call and the parameter `Precision` is839 // the precision of a format specifier of the `Call`.840 //841 // For example, for the `printf("%d, %.10s", 10, p)` call842 // `Precision` can be the precision of either "%d" or "%.10s". The former843 // one will have `NotSpecified` kind.844 const Expr *845 getPrecisionAsExpr(const analyze_printf::OptionalAmount &Precision,846 const CallExpr *Call) {847 unsigned PArgIdx = -1;848 849 if (Precision.hasDataArgument())850 PArgIdx = Precision.getPositionalArgIndex() + FmtArgIdx;851 if (0 < PArgIdx && PArgIdx < Call->getNumArgs()) {852 const Expr *PArg = Call->getArg(PArgIdx);853 854 // Strip the cast if `PArg` is a cast-to-int expression:855 if (auto *CE = dyn_cast<CastExpr>(PArg);856 CE && CE->getType()->isSignedIntegerType())857 PArg = CE->getSubExpr();858 return PArg;859 }860 if (Precision.getHowSpecified() ==861 analyze_printf::OptionalAmount::HowSpecified::Constant) {862 auto SizeTy = Ctx.getSizeType();863 llvm::APSInt PArgVal = llvm::APSInt(864 llvm::APInt(Ctx.getTypeSize(SizeTy), Precision.getConstantAmount()),865 true);866 867 return IntegerLiteral::Create(Ctx, PArgVal, Ctx.getSizeType(), {});868 }869 return nullptr;870 }871 872 public:873 StringFormatStringHandler(const CallExpr *Call, unsigned FmtArgIdx,874 const Expr *&UnsafeArg, ASTContext &Ctx)875 : Call(Call), FmtArgIdx(FmtArgIdx), UnsafeArg(UnsafeArg), Ctx(Ctx) {}876 877 bool HandlePrintfSpecifier(const analyze_printf::PrintfSpecifier &FS,878 const char *startSpecifier,879 unsigned specifierLen,880 const TargetInfo &Target) override {881 if (FS.getConversionSpecifier().getKind() !=882 analyze_printf::PrintfConversionSpecifier::sArg)883 return true; // continue parsing884 885 unsigned ArgIdx = FS.getPositionalArgIndex() + FmtArgIdx;886 887 if (!(0 < ArgIdx && ArgIdx < Call->getNumArgs()))888 // If the `ArgIdx` is invalid, give up.889 return true; // continue parsing890 891 const Expr *Arg = Call->getArg(ArgIdx);892 893 if (isNullTermPointer(Arg, Ctx))894 // If Arg is a null-terminated pointer, it is safe anyway.895 return true; // continue parsing896 897 // Otherwise, check if the specifier has a precision and if the character898 // pointer is safely bound by the precision:899 auto LengthModifier = FS.getLengthModifier();900 QualType ArgType = Arg->getType();901 bool IsArgTypeValid = // Is ArgType a character pointer type?902 ArgType->isPointerType() &&903 (LengthModifier.getKind() == LengthModifier.AsWideChar904 ? ArgType->getPointeeType()->isWideCharType()905 : ArgType->getPointeeType()->isCharType());906 907 if (auto *Precision = getPrecisionAsExpr(FS.getPrecision(), Call);908 Precision && IsArgTypeValid)909 if (isPtrBufferSafe(Arg, Precision, Ctx))910 return true;911 // Handle unsafe case:912 UnsafeArg = Call->getArg(ArgIdx); // output913 return false; // returning false stops parsing immediately914 }915 };916 917 const Expr *Fmt = Call->getArg(FmtArgIdx);918 919 if (auto *SL = dyn_cast<clang::StringLiteral>(Fmt->IgnoreParenImpCasts())) {920 if (SL->getCharByteWidth() == 1) {921 StringRef FmtStr = SL->getString();922 StringFormatStringHandler Handler(Call, FmtArgIdx, UnsafeArg, Ctx);923 924 return analyze_format_string::ParsePrintfString(925 Handler, FmtStr.begin(), FmtStr.end(), Ctx.getLangOpts(),926 Ctx.getTargetInfo(), isKprintf);927 }928 929 if (auto FmtStr = SL->tryEvaluateString(Ctx)) {930 StringFormatStringHandler Handler(Call, FmtArgIdx, UnsafeArg, Ctx);931 return analyze_format_string::ParsePrintfString(932 Handler, FmtStr->data(), FmtStr->data() + FmtStr->size(),933 Ctx.getLangOpts(), Ctx.getTargetInfo(), isKprintf);934 }935 }936 // If format is not a string literal, we cannot analyze the format string.937 // In this case, this call is considered unsafe if at least one argument938 // (including the format argument) is unsafe pointer.939 return llvm::any_of(940 llvm::make_range(Call->arg_begin() + FmtArgIdx, Call->arg_end()),941 [&UnsafeArg, &Ctx](const Expr *Arg) -> bool {942 if (Arg->getType()->isPointerType() && !isNullTermPointer(Arg, Ctx)) {943 UnsafeArg = Arg;944 return true;945 }946 return false;947 });948}949 950// Matches a FunctionDecl node such that951// 1. It's name, after stripping off predefined prefix and suffix, is952// `CoreName`; and953// 2. `CoreName` or `CoreName[str/wcs]` is one of the `PredefinedNames`, which954// is a set of libc function names.955//956// Note: For predefined prefix and suffix, see `LibcFunNamePrefixSuffixParser`.957// The notation `CoreName[str/wcs]` means a new name obtained from replace958// string "wcs" with "str" in `CoreName`.959static bool isPredefinedUnsafeLibcFunc(const FunctionDecl &Node) {960 static std::unique_ptr<std::set<StringRef>> PredefinedNames = nullptr;961 if (!PredefinedNames)962 PredefinedNames =963 std::make_unique<std::set<StringRef>, std::set<StringRef>>({964 // numeric conversion:965 "atof",966 "atoi",967 "atol",968 "atoll",969 "strtol",970 "strtoll",971 "strtoul",972 "strtoull",973 "strtof",974 "strtod",975 "strtold",976 "strtoimax",977 "strtoumax",978 // "strfromf", "strfromd", "strfroml", // C23?979 // string manipulation:980 "strcpy",981 "strncpy",982 "strlcpy",983 "strcat",984 "strncat",985 "strlcat",986 "strxfrm",987 "strdup",988 "strndup",989 // string examination:990 "strlen",991 "strnlen",992 "strcmp",993 "strncmp",994 "stricmp",995 "strcasecmp",996 "strcoll",997 "strchr",998 "strrchr",999 "strspn",1000 "strcspn",1001 "strpbrk",1002 "strstr",1003 "strtok",1004 // "mem-" functions1005 "memchr",1006 "wmemchr",1007 "memcmp",1008 "wmemcmp",1009 "memcpy",1010 "memccpy",1011 "mempcpy",1012 "wmemcpy",1013 "memmove",1014 "wmemmove",1015 "memset",1016 "wmemset",1017 // IO:1018 "fread",1019 "fwrite",1020 "fgets",1021 "fgetws",1022 "gets",1023 "fputs",1024 "fputws",1025 "puts",1026 // others1027 "strerror_s",1028 "strerror_r",1029 "bcopy",1030 "bzero",1031 "bsearch",1032 "qsort",1033 });1034 1035 auto *II = Node.getIdentifier();1036 1037 if (!II)1038 return false;1039 1040 StringRef Name = LibcFunNamePrefixSuffixParser().matchName(1041 II->getName(), Node.getBuiltinID());1042 1043 // Match predefined names:1044 if (PredefinedNames->find(Name) != PredefinedNames->end())1045 return true;1046 1047 std::string NameWCS = Name.str();1048 size_t WcsPos = NameWCS.find("wcs");1049 1050 while (WcsPos != std::string::npos) {1051 NameWCS[WcsPos++] = 's';1052 NameWCS[WcsPos++] = 't';1053 NameWCS[WcsPos++] = 'r';1054 WcsPos = NameWCS.find("wcs", WcsPos);1055 }1056 if (PredefinedNames->find(NameWCS) != PredefinedNames->end())1057 return true;1058 // All `scanf` functions are unsafe (including `sscanf`, `vsscanf`, etc.. They1059 // all should end with "scanf"):1060 return Name.ends_with("scanf");1061}1062 1063// Match a call to one of the `v*printf` functions taking `va_list`. We cannot1064// check safety for these functions so they should be changed to their1065// non-va_list versions.1066static bool isUnsafeVaListPrintfFunc(const FunctionDecl &Node) {1067 auto *II = Node.getIdentifier();1068 1069 if (!II)1070 return false;1071 1072 StringRef Name = LibcFunNamePrefixSuffixParser().matchName(1073 II->getName(), Node.getBuiltinID());1074 1075 if (!Name.ends_with("printf"))1076 return false; // neither printf nor scanf1077 return Name.starts_with("v");1078}1079 1080// Matches a call to one of the `sprintf` functions as they are always unsafe1081// and should be changed to `snprintf`.1082static bool isUnsafeSprintfFunc(const FunctionDecl &Node) {1083 auto *II = Node.getIdentifier();1084 1085 if (!II)1086 return false;1087 1088 StringRef Name = LibcFunNamePrefixSuffixParser().matchName(1089 II->getName(), Node.getBuiltinID());1090 1091 if (!Name.ends_with("printf") ||1092 // Let `isUnsafeVaListPrintfFunc` check for cases with va-list:1093 Name.starts_with("v"))1094 return false;1095 1096 StringRef Prefix = Name.drop_back(6);1097 1098 if (Prefix.ends_with("w"))1099 Prefix = Prefix.drop_back(1);1100 return Prefix == "s";1101}1102 1103// Match function declarations of `printf`, `fprintf`, `snprintf` and their wide1104// character versions. Calls to these functions can be safe if their arguments1105// are carefully made safe.1106static bool isNormalPrintfFunc(const FunctionDecl &Node) {1107 auto *II = Node.getIdentifier();1108 1109 if (!II)1110 return false;1111 1112 StringRef Name = LibcFunNamePrefixSuffixParser().matchName(1113 II->getName(), Node.getBuiltinID());1114 1115 if (!Name.ends_with("printf") || Name.starts_with("v"))1116 return false;1117 1118 StringRef Prefix = Name.drop_back(6);1119 1120 if (Prefix.ends_with("w"))1121 Prefix = Prefix.drop_back(1);1122 1123 return Prefix.empty() || Prefix == "k" || Prefix == "f" || Prefix == "sn";1124}1125 1126// This matcher requires that it is known that the callee `isNormalPrintf`.1127// Then if the format string is a string literal, this matcher matches when at1128// least one string argument is unsafe. If the format is not a string literal,1129// this matcher matches when at least one pointer type argument is unsafe.1130static bool hasUnsafePrintfStringArg(const CallExpr &Node, ASTContext &Ctx,1131 MatchResult &Result, llvm::StringRef Tag) {1132 // Determine what printf it is by examining formal parameters:1133 const FunctionDecl *FD = Node.getDirectCallee();1134 1135 assert(FD && "It should have been checked that FD is non-null.");1136 1137 unsigned NumParms = FD->getNumParams();1138 1139 if (NumParms < 1)1140 return false; // possibly some user-defined printf function1141 1142 QualType FirstParmTy = FD->getParamDecl(0)->getType();1143 1144 if (!FirstParmTy->isPointerType())1145 return false; // possibly some user-defined printf function1146 1147 QualType FirstPteTy = FirstParmTy->castAs<PointerType>()->getPointeeType();1148 1149 if (!Ctx.getFILEType()1150 .isNull() && //`FILE *` must be in the context if it is fprintf1151 FirstPteTy.getCanonicalType() == Ctx.getFILEType().getCanonicalType()) {1152 // It is a fprintf:1153 const Expr *UnsafeArg;1154 1155 if (hasUnsafeFormatOrSArg(&Node, UnsafeArg, 1, Ctx, false)) {1156 Result.addNode(Tag, DynTypedNode::create(*UnsafeArg));1157 return true;1158 }1159 return false;1160 }1161 1162 if (FirstPteTy.isConstQualified()) {1163 // If the first parameter is a `const char *`, it is a printf/kprintf:1164 bool isKprintf = false;1165 const Expr *UnsafeArg;1166 1167 if (auto *II = FD->getIdentifier())1168 isKprintf = II->getName() == "kprintf";1169 if (hasUnsafeFormatOrSArg(&Node, UnsafeArg, 0, Ctx, isKprintf)) {1170 Result.addNode(Tag, DynTypedNode::create(*UnsafeArg));1171 return true;1172 }1173 return false;1174 }1175 1176 if (NumParms > 2) {1177 QualType SecondParmTy = FD->getParamDecl(1)->getType();1178 1179 if (!FirstPteTy.isConstQualified() && SecondParmTy->isIntegerType()) {1180 // If the first parameter type is non-const qualified `char *` and the1181 // second is an integer, it is a snprintf:1182 const Expr *UnsafeArg;1183 1184 if (hasUnsafeFormatOrSArg(&Node, UnsafeArg, 2, Ctx, false)) {1185 Result.addNode(Tag, DynTypedNode::create(*UnsafeArg));1186 return true;1187 }1188 return false;1189 }1190 }1191 // We don't really recognize this "normal" printf, the only thing we1192 // can do is to require all pointers to be null-terminated:1193 for (const auto *Arg : Node.arguments())1194 if (Arg->getType()->isPointerType() && !isNullTermPointer(Arg, Ctx)) {1195 Result.addNode(Tag, DynTypedNode::create(*Arg));1196 return true;1197 }1198 return false;1199}1200 1201// This function requires that it is known that the callee `isNormalPrintf`.1202// It returns true iff the first two arguments of the call is a pointer1203// `Ptr` and an unsigned integer `Size` and they are NOT safe, i.e.,1204// `!isPtrBufferSafe(Ptr, Size)`.1205static bool hasUnsafeSnprintfBuffer(const CallExpr &Node, ASTContext &Ctx) {1206 const FunctionDecl *FD = Node.getDirectCallee();1207 1208 assert(FD && "It should have been checked that FD is non-null.");1209 1210 if (FD->getNumParams() < 3)1211 return false; // Not an snprint1212 1213 QualType FirstParmTy = FD->getParamDecl(0)->getType();1214 1215 if (!FirstParmTy->isPointerType())1216 return false; // Not an snprint1217 1218 QualType FirstPteTy = FirstParmTy->castAs<PointerType>()->getPointeeType();1219 const Expr *Buf = Node.getArg(0), *Size = Node.getArg(1);1220 1221 if (FirstPteTy.isConstQualified() || !FirstPteTy->isAnyCharacterType() ||1222 !Buf->getType()->isPointerType() ||1223 !Size->getType()->isUnsignedIntegerType())1224 return false; // not an snprintf call1225 1226 return !isPtrBufferSafe(Buf, Size, Ctx);1227}1228} // namespace libc_func_matchers1229 1230namespace {1231// Because the analysis revolves around variables and their types, we'll need to1232// track uses of variables (aka DeclRefExprs).1233using DeclUseList = SmallVector<const DeclRefExpr *, 1>;1234 1235// Convenience typedef.1236using FixItList = SmallVector<FixItHint, 4>;1237} // namespace1238 1239namespace {1240/// Gadget is an individual operation in the code that may be of interest to1241/// this analysis. Each (non-abstract) subclass corresponds to a specific1242/// rigid AST structure that constitutes an operation on a pointer-type object.1243/// Discovery of a gadget in the code corresponds to claiming that we understand1244/// what this part of code is doing well enough to potentially improve it.1245/// Gadgets can be warning (immediately deserving a warning) or fixable (not1246/// always deserving a warning per se, but requires our attention to identify1247/// it warrants a fixit).1248class Gadget {1249public:1250 enum class Kind {1251#define GADGET(x) x,1252#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"1253 };1254 1255 Gadget(Kind K) : K(K) {}1256 1257 Kind getKind() const { return K; }1258 1259#ifndef NDEBUG1260 StringRef getDebugName() const {1261 switch (K) {1262#define GADGET(x) \1263 case Kind::x: \1264 return #x;1265#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"1266 }1267 llvm_unreachable("Unhandled Gadget::Kind enum");1268 }1269#endif1270 1271 virtual bool isWarningGadget() const = 0;1272 // TODO remove this method from WarningGadget interface. It's only used for1273 // debug prints in FixableGadget.1274 virtual SourceLocation getSourceLoc() const = 0;1275 1276 /// Returns the list of pointer-type variables on which this gadget performs1277 /// its operation. Typically, there's only one variable. This isn't a list1278 /// of all DeclRefExprs in the gadget's AST!1279 virtual DeclUseList getClaimedVarUseSites() const = 0;1280 1281 virtual ~Gadget() = default;1282 1283private:1284 Kind K;1285};1286 1287/// Warning gadgets correspond to unsafe code patterns that warrants1288/// an immediate warning.1289class WarningGadget : public Gadget {1290public:1291 WarningGadget(Kind K) : Gadget(K) {}1292 1293 static bool classof(const Gadget *G) { return G->isWarningGadget(); }1294 bool isWarningGadget() const final { return true; }1295 1296 virtual void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,1297 bool IsRelatedToDecl,1298 ASTContext &Ctx) const = 0;1299 1300 virtual SmallVector<const Expr *, 1> getUnsafePtrs() const = 0;1301};1302 1303/// Fixable gadgets correspond to code patterns that aren't always unsafe but1304/// need to be properly recognized in order to emit fixes. For example, if a raw1305/// pointer-type variable is replaced by a safe C++ container, every use of such1306/// variable must be carefully considered and possibly updated.1307class FixableGadget : public Gadget {1308public:1309 FixableGadget(Kind K) : Gadget(K) {}1310 1311 static bool classof(const Gadget *G) { return !G->isWarningGadget(); }1312 bool isWarningGadget() const final { return false; }1313 1314 /// Returns a fixit that would fix the current gadget according to1315 /// the current strategy. Returns std::nullopt if the fix cannot be produced;1316 /// returns an empty list if no fixes are necessary.1317 virtual std::optional<FixItList> getFixits(const FixitStrategy &) const {1318 return std::nullopt;1319 }1320 1321 /// Returns a list of two elements where the first element is the LHS of a1322 /// pointer assignment statement and the second element is the RHS. This1323 /// two-element list represents the fact that the LHS buffer gets its bounds1324 /// information from the RHS buffer. This information will be used later to1325 /// group all those variables whose types must be modified together to prevent1326 /// type mismatches.1327 virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>1328 getStrategyImplications() const {1329 return std::nullopt;1330 }1331};1332 1333static bool isSupportedVariable(const DeclRefExpr &Node) {1334 const Decl *D = Node.getDecl();1335 return D != nullptr && isa<VarDecl>(D);1336}1337 1338// Returns true for RecordDecl of type std::unique_ptr<T[]>1339static bool isUniquePtrArray(const CXXRecordDecl *RecordDecl) {1340 if (!RecordDecl || !RecordDecl->isInStdNamespace() ||1341 RecordDecl->getNameAsString() != "unique_ptr")1342 return false;1343 1344 const ClassTemplateSpecializationDecl *class_template_specialization_decl =1345 dyn_cast<ClassTemplateSpecializationDecl>(RecordDecl);1346 if (!class_template_specialization_decl)1347 return false;1348 1349 const TemplateArgumentList &template_args =1350 class_template_specialization_decl->getTemplateArgs();1351 if (template_args.size() == 0)1352 return false;1353 1354 const TemplateArgument &first_arg = template_args[0];1355 if (first_arg.getKind() != TemplateArgument::Type)1356 return false;1357 1358 QualType referred_type = first_arg.getAsType();1359 return referred_type->isArrayType();1360}1361 1362class UniquePtrArrayAccessGadget : public WarningGadget {1363private:1364 static constexpr const char *const AccessorTag = "unique_ptr_array_access";1365 const CXXOperatorCallExpr *AccessorExpr;1366 1367public:1368 UniquePtrArrayAccessGadget(const MatchResult &Result)1369 : WarningGadget(Kind::UniquePtrArrayAccess),1370 AccessorExpr(Result.getNodeAs<CXXOperatorCallExpr>(AccessorTag)) {1371 assert(AccessorExpr &&1372 "UniquePtrArrayAccessGadget requires a matched CXXOperatorCallExpr");1373 }1374 1375 static bool classof(const Gadget *G) {1376 return G->getKind() == Kind::UniquePtrArrayAccess;1377 }1378 1379 static bool matches(const Stmt *S, const ASTContext &Ctx,1380 MatchResult &Result) {1381 1382 const CXXOperatorCallExpr *OpCall = dyn_cast<CXXOperatorCallExpr>(S);1383 if (!OpCall || OpCall->getOperator() != OO_Subscript)1384 return false;1385 1386 const Expr *Callee = OpCall->getCallee()->IgnoreParenImpCasts();1387 if (!Callee)1388 return false;1389 1390 const CXXMethodDecl *Method =1391 dyn_cast_or_null<CXXMethodDecl>(OpCall->getDirectCallee());1392 if (!Method)1393 return false;1394 1395 if (Method->getOverloadedOperator() != OO_Subscript)1396 return false;1397 1398 const CXXRecordDecl *RecordDecl = Method->getParent();1399 if (!isUniquePtrArray(RecordDecl))1400 return false;1401 1402 const Expr *IndexExpr = OpCall->getArg(1);1403 clang::Expr::EvalResult Eval;1404 1405 // Allow [0]1406 if (IndexExpr->EvaluateAsInt(Eval, Ctx) && Eval.Val.getInt().isZero())1407 return false;1408 1409 Result.addNode(AccessorTag, DynTypedNode::create(*OpCall));1410 return true;1411 }1412 void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,1413 bool IsRelatedToDecl,1414 ASTContext &Ctx) const override {1415 Handler.handleUnsafeUniquePtrArrayAccess(1416 DynTypedNode::create(*AccessorExpr), IsRelatedToDecl, Ctx);1417 }1418 1419 SourceLocation getSourceLoc() const override {1420 if (AccessorExpr)1421 return AccessorExpr->getOperatorLoc();1422 return SourceLocation();1423 }1424 1425 DeclUseList getClaimedVarUseSites() const override { return {}; }1426 SmallVector<const Expr *, 1> getUnsafePtrs() const override { return {}; }1427};1428 1429using FixableGadgetList = std::vector<std::unique_ptr<FixableGadget>>;1430using WarningGadgetList = std::vector<std::unique_ptr<WarningGadget>>;1431 1432/// An increment of a pointer-type value is unsafe as it may run the pointer1433/// out of bounds.1434class IncrementGadget : public WarningGadget {1435 static constexpr const char *const OpTag = "op";1436 const UnaryOperator *Op;1437 1438public:1439 IncrementGadget(const MatchResult &Result)1440 : WarningGadget(Kind::Increment),1441 Op(Result.getNodeAs<UnaryOperator>(OpTag)) {}1442 1443 static bool classof(const Gadget *G) {1444 return G->getKind() == Kind::Increment;1445 }1446 1447 static bool matches(const Stmt *S, const ASTContext &Ctx,1448 MatchResult &Result) {1449 const auto *UO = dyn_cast<UnaryOperator>(S);1450 if (!UO || !UO->isIncrementOp())1451 return false;1452 if (!hasPointerType(*UO->getSubExpr()->IgnoreParenImpCasts()))1453 return false;1454 Result.addNode(OpTag, DynTypedNode::create(*UO));1455 return true;1456 }1457 1458 void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,1459 bool IsRelatedToDecl,1460 ASTContext &Ctx) const override {1461 Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);1462 }1463 SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }1464 1465 DeclUseList getClaimedVarUseSites() const override {1466 SmallVector<const DeclRefExpr *, 2> Uses;1467 if (const auto *DRE =1468 dyn_cast<DeclRefExpr>(Op->getSubExpr()->IgnoreParenImpCasts())) {1469 Uses.push_back(DRE);1470 }1471 1472 return std::move(Uses);1473 }1474 1475 SmallVector<const Expr *, 1> getUnsafePtrs() const override {1476 return {Op->getSubExpr()->IgnoreParenImpCasts()};1477 }1478};1479 1480/// A decrement of a pointer-type value is unsafe as it may run the pointer1481/// out of bounds.1482class DecrementGadget : public WarningGadget {1483 static constexpr const char *const OpTag = "op";1484 const UnaryOperator *Op;1485 1486public:1487 DecrementGadget(const MatchResult &Result)1488 : WarningGadget(Kind::Decrement),1489 Op(Result.getNodeAs<UnaryOperator>(OpTag)) {}1490 1491 static bool classof(const Gadget *G) {1492 return G->getKind() == Kind::Decrement;1493 }1494 1495 static bool matches(const Stmt *S, const ASTContext &Ctx,1496 MatchResult &Result) {1497 const auto *UO = dyn_cast<UnaryOperator>(S);1498 if (!UO || !UO->isDecrementOp())1499 return false;1500 if (!hasPointerType(*UO->getSubExpr()->IgnoreParenImpCasts()))1501 return false;1502 Result.addNode(OpTag, DynTypedNode::create(*UO));1503 return true;1504 }1505 1506 void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,1507 bool IsRelatedToDecl,1508 ASTContext &Ctx) const override {1509 Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);1510 }1511 SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }1512 1513 DeclUseList getClaimedVarUseSites() const override {1514 if (const auto *DRE =1515 dyn_cast<DeclRefExpr>(Op->getSubExpr()->IgnoreParenImpCasts())) {1516 return {DRE};1517 }1518 1519 return {};1520 }1521 1522 SmallVector<const Expr *, 1> getUnsafePtrs() const override {1523 return {Op->getSubExpr()->IgnoreParenImpCasts()};1524 }1525};1526 1527/// Array subscript expressions on raw pointers as if they're arrays. Unsafe as1528/// it doesn't have any bounds checks for the array.1529class ArraySubscriptGadget : public WarningGadget {1530 static constexpr const char *const ArraySubscrTag = "ArraySubscript";1531 const ArraySubscriptExpr *ASE;1532 1533public:1534 ArraySubscriptGadget(const MatchResult &Result)1535 : WarningGadget(Kind::ArraySubscript),1536 ASE(Result.getNodeAs<ArraySubscriptExpr>(ArraySubscrTag)) {}1537 1538 static bool classof(const Gadget *G) {1539 return G->getKind() == Kind::ArraySubscript;1540 }1541 1542 static bool matches(const Stmt *S, const ASTContext &Ctx,1543 MatchResult &Result) {1544 const auto *ASE = dyn_cast<ArraySubscriptExpr>(S);1545 if (!ASE)1546 return false;1547 const auto *const Base = ASE->getBase()->IgnoreParenImpCasts();1548 if (!hasPointerType(*Base) && !hasArrayType(*Base))1549 return false;1550 const auto *Idx = dyn_cast<IntegerLiteral>(ASE->getIdx());1551 bool IsSafeIndex = (Idx && Idx->getValue().isZero()) ||1552 isa<ArrayInitIndexExpr>(ASE->getIdx());1553 if (IsSafeIndex || isSafeArraySubscript(*ASE, Ctx))1554 return false;1555 Result.addNode(ArraySubscrTag, DynTypedNode::create(*ASE));1556 return true;1557 }1558 1559 void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,1560 bool IsRelatedToDecl,1561 ASTContext &Ctx) const override {1562 Handler.handleUnsafeOperation(ASE, IsRelatedToDecl, Ctx);1563 }1564 SourceLocation getSourceLoc() const override { return ASE->getBeginLoc(); }1565 1566 DeclUseList getClaimedVarUseSites() const override {1567 if (const auto *DRE =1568 dyn_cast<DeclRefExpr>(ASE->getBase()->IgnoreParenImpCasts())) {1569 return {DRE};1570 }1571 1572 return {};1573 }1574 1575 SmallVector<const Expr *, 1> getUnsafePtrs() const override {1576 return {ASE->getBase()->IgnoreParenImpCasts()};1577 }1578};1579 1580/// A pointer arithmetic expression of one of the forms:1581/// \code1582/// ptr + n | n + ptr | ptr - n | ptr += n | ptr -= n1583/// \endcode1584class PointerArithmeticGadget : public WarningGadget {1585 static constexpr const char *const PointerArithmeticTag = "ptrAdd";1586 static constexpr const char *const PointerArithmeticPointerTag = "ptrAddPtr";1587 const BinaryOperator *PA; // pointer arithmetic expression1588 const Expr *Ptr; // the pointer expression in `PA`1589 1590public:1591 PointerArithmeticGadget(const MatchResult &Result)1592 : WarningGadget(Kind::PointerArithmetic),1593 PA(Result.getNodeAs<BinaryOperator>(PointerArithmeticTag)),1594 Ptr(Result.getNodeAs<Expr>(PointerArithmeticPointerTag)) {}1595 1596 static bool classof(const Gadget *G) {1597 return G->getKind() == Kind::PointerArithmetic;1598 }1599 1600 static bool matches(const Stmt *S, const ASTContext &Ctx,1601 MatchResult &Result) {1602 const auto *BO = dyn_cast<BinaryOperator>(S);1603 if (!BO)1604 return false;1605 const auto *LHS = BO->getLHS();1606 const auto *RHS = BO->getRHS();1607 // ptr at left1608 if (BO->getOpcode() == BO_Add || BO->getOpcode() == BO_Sub ||1609 BO->getOpcode() == BO_AddAssign || BO->getOpcode() == BO_SubAssign) {1610 if (hasPointerType(*LHS) && (RHS->getType()->isIntegerType() ||1611 RHS->getType()->isEnumeralType())) {1612 Result.addNode(PointerArithmeticPointerTag, DynTypedNode::create(*LHS));1613 Result.addNode(PointerArithmeticTag, DynTypedNode::create(*BO));1614 return true;1615 }1616 }1617 // ptr at right1618 if (BO->getOpcode() == BO_Add && hasPointerType(*RHS) &&1619 (LHS->getType()->isIntegerType() || LHS->getType()->isEnumeralType())) {1620 Result.addNode(PointerArithmeticPointerTag, DynTypedNode::create(*RHS));1621 Result.addNode(PointerArithmeticTag, DynTypedNode::create(*BO));1622 return true;1623 }1624 return false;1625 }1626 1627 void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,1628 bool IsRelatedToDecl,1629 ASTContext &Ctx) const override {1630 Handler.handleUnsafeOperation(PA, IsRelatedToDecl, Ctx);1631 }1632 SourceLocation getSourceLoc() const override { return PA->getBeginLoc(); }1633 1634 DeclUseList getClaimedVarUseSites() const override {1635 if (const auto *DRE = dyn_cast<DeclRefExpr>(Ptr->IgnoreParenImpCasts())) {1636 return {DRE};1637 }1638 1639 return {};1640 }1641 1642 SmallVector<const Expr *, 1> getUnsafePtrs() const override {1643 return {Ptr->IgnoreParenImpCasts()};1644 }1645 1646 // FIXME: pointer adding zero should be fine1647 // FIXME: this gadge will need a fix-it1648};1649 1650class SpanTwoParamConstructorGadget : public WarningGadget {1651 static constexpr const char *const SpanTwoParamConstructorTag =1652 "spanTwoParamConstructor";1653 const CXXConstructExpr *Ctor; // the span constructor expression1654 1655public:1656 SpanTwoParamConstructorGadget(const MatchResult &Result)1657 : WarningGadget(Kind::SpanTwoParamConstructor),1658 Ctor(Result.getNodeAs<CXXConstructExpr>(SpanTwoParamConstructorTag)) {}1659 1660 static bool classof(const Gadget *G) {1661 return G->getKind() == Kind::SpanTwoParamConstructor;1662 }1663 1664 static bool matches(const Stmt *S, ASTContext &Ctx, MatchResult &Result) {1665 const auto *CE = dyn_cast<CXXConstructExpr>(S);1666 if (!CE)1667 return false;1668 const auto *CDecl = CE->getConstructor();1669 const auto *CRecordDecl = CDecl->getParent();1670 auto HasTwoParamSpanCtorDecl =1671 CRecordDecl->isInStdNamespace() &&1672 CDecl->getDeclName().getAsString() == "span" && CE->getNumArgs() == 2;1673 if (!HasTwoParamSpanCtorDecl || isSafeSpanTwoParamConstruct(*CE, Ctx))1674 return false;1675 Result.addNode(SpanTwoParamConstructorTag, DynTypedNode::create(*CE));1676 return true;1677 }1678 1679 static bool matches(const Stmt *S, ASTContext &Ctx,1680 const UnsafeBufferUsageHandler *Handler,1681 MatchResult &Result) {1682 if (ignoreUnsafeBufferInContainer(*S, Handler))1683 return false;1684 return matches(S, Ctx, Result);1685 }1686 1687 void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,1688 bool IsRelatedToDecl,1689 ASTContext &Ctx) const override {1690 Handler.handleUnsafeOperationInContainer(Ctor, IsRelatedToDecl, Ctx);1691 }1692 SourceLocation getSourceLoc() const override { return Ctor->getBeginLoc(); }1693 1694 DeclUseList getClaimedVarUseSites() const override {1695 // If the constructor call is of the form `std::span{var, n}`, `var` is1696 // considered an unsafe variable.1697 if (auto *DRE = dyn_cast<DeclRefExpr>(Ctor->getArg(0))) {1698 if (isa<VarDecl>(DRE->getDecl()))1699 return {DRE};1700 }1701 return {};1702 }1703 1704 SmallVector<const Expr *, 1> getUnsafePtrs() const override { return {}; }1705};1706 1707/// A pointer initialization expression of the form:1708/// \code1709/// int *p = q;1710/// \endcode1711class PointerInitGadget : public FixableGadget {1712private:1713 static constexpr const char *const PointerInitLHSTag = "ptrInitLHS";1714 static constexpr const char *const PointerInitRHSTag = "ptrInitRHS";1715 const VarDecl *PtrInitLHS; // the LHS pointer expression in `PI`1716 const DeclRefExpr *PtrInitRHS; // the RHS pointer expression in `PI`1717 1718public:1719 PointerInitGadget(const MatchResult &Result)1720 : FixableGadget(Kind::PointerInit),1721 PtrInitLHS(Result.getNodeAs<VarDecl>(PointerInitLHSTag)),1722 PtrInitRHS(Result.getNodeAs<DeclRefExpr>(PointerInitRHSTag)) {}1723 1724 static bool classof(const Gadget *G) {1725 return G->getKind() == Kind::PointerInit;1726 }1727 1728 static bool matches(const Stmt *S,1729 llvm::SmallVectorImpl<MatchResult> &Results) {1730 const DeclStmt *DS = dyn_cast<DeclStmt>(S);1731 if (!DS || !DS->isSingleDecl())1732 return false;1733 const VarDecl *VD = dyn_cast<VarDecl>(DS->getSingleDecl());1734 if (!VD)1735 return false;1736 const Expr *Init = VD->getAnyInitializer();1737 if (!Init)1738 return false;1739 const auto *DRE = dyn_cast<DeclRefExpr>(Init->IgnoreImpCasts());1740 if (!DRE || !hasPointerType(*DRE) || !isSupportedVariable(*DRE)) {1741 return false;1742 }1743 MatchResult R;1744 R.addNode(PointerInitLHSTag, DynTypedNode::create(*VD));1745 R.addNode(PointerInitRHSTag, DynTypedNode::create(*DRE));1746 Results.emplace_back(std::move(R));1747 return true;1748 }1749 1750 virtual std::optional<FixItList>1751 getFixits(const FixitStrategy &S) const override;1752 SourceLocation getSourceLoc() const override {1753 return PtrInitRHS->getBeginLoc();1754 }1755 1756 virtual DeclUseList getClaimedVarUseSites() const override {1757 return DeclUseList{PtrInitRHS};1758 }1759 1760 virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>1761 getStrategyImplications() const override {1762 return std::make_pair(PtrInitLHS, cast<VarDecl>(PtrInitRHS->getDecl()));1763 }1764};1765 1766/// A pointer assignment expression of the form:1767/// \code1768/// p = q;1769/// \endcode1770/// where both `p` and `q` are pointers.1771class PtrToPtrAssignmentGadget : public FixableGadget {1772private:1773 static constexpr const char *const PointerAssignLHSTag = "ptrLHS";1774 static constexpr const char *const PointerAssignRHSTag = "ptrRHS";1775 const DeclRefExpr *PtrLHS; // the LHS pointer expression in `PA`1776 const DeclRefExpr *PtrRHS; // the RHS pointer expression in `PA`1777 1778public:1779 PtrToPtrAssignmentGadget(const MatchResult &Result)1780 : FixableGadget(Kind::PtrToPtrAssignment),1781 PtrLHS(Result.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),1782 PtrRHS(Result.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}1783 1784 static bool classof(const Gadget *G) {1785 return G->getKind() == Kind::PtrToPtrAssignment;1786 }1787 1788 static bool matches(const Stmt *S,1789 llvm::SmallVectorImpl<MatchResult> &Results) {1790 size_t SizeBefore = Results.size();1791 findStmtsInUnspecifiedUntypedContext(S, [&Results](const Stmt *S) {1792 const auto *BO = dyn_cast<BinaryOperator>(S);1793 if (!BO || BO->getOpcode() != BO_Assign)1794 return;1795 const auto *RHS = BO->getRHS()->IgnoreParenImpCasts();1796 if (const auto *RHSRef = dyn_cast<DeclRefExpr>(RHS);1797 !RHSRef || !hasPointerType(*RHSRef) ||1798 !isSupportedVariable(*RHSRef)) {1799 return;1800 }1801 const auto *LHS = BO->getLHS();1802 if (const auto *LHSRef = dyn_cast<DeclRefExpr>(LHS);1803 !LHSRef || !hasPointerType(*LHSRef) ||1804 !isSupportedVariable(*LHSRef)) {1805 return;1806 }1807 MatchResult R;1808 R.addNode(PointerAssignLHSTag, DynTypedNode::create(*LHS));1809 R.addNode(PointerAssignRHSTag, DynTypedNode::create(*RHS));1810 Results.emplace_back(std::move(R));1811 });1812 return SizeBefore != Results.size();1813 }1814 1815 virtual std::optional<FixItList>1816 getFixits(const FixitStrategy &S) const override;1817 SourceLocation getSourceLoc() const override { return PtrLHS->getBeginLoc(); }1818 1819 virtual DeclUseList getClaimedVarUseSites() const override {1820 return DeclUseList{PtrLHS, PtrRHS};1821 }1822 1823 virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>1824 getStrategyImplications() const override {1825 return std::make_pair(cast<VarDecl>(PtrLHS->getDecl()),1826 cast<VarDecl>(PtrRHS->getDecl()));1827 }1828};1829 1830/// An assignment expression of the form:1831/// \code1832/// ptr = array;1833/// \endcode1834/// where `p` is a pointer and `array` is a constant size array.1835class CArrayToPtrAssignmentGadget : public FixableGadget {1836private:1837 static constexpr const char *const PointerAssignLHSTag = "ptrLHS";1838 static constexpr const char *const PointerAssignRHSTag = "ptrRHS";1839 const DeclRefExpr *PtrLHS; // the LHS pointer expression in `PA`1840 const DeclRefExpr *PtrRHS; // the RHS pointer expression in `PA`1841 1842public:1843 CArrayToPtrAssignmentGadget(const MatchResult &Result)1844 : FixableGadget(Kind::CArrayToPtrAssignment),1845 PtrLHS(Result.getNodeAs<DeclRefExpr>(PointerAssignLHSTag)),1846 PtrRHS(Result.getNodeAs<DeclRefExpr>(PointerAssignRHSTag)) {}1847 1848 static bool classof(const Gadget *G) {1849 return G->getKind() == Kind::CArrayToPtrAssignment;1850 }1851 1852 static bool matches(const Stmt *S,1853 llvm::SmallVectorImpl<MatchResult> &Results) {1854 size_t SizeBefore = Results.size();1855 findStmtsInUnspecifiedUntypedContext(S, [&Results](const Stmt *S) {1856 const auto *BO = dyn_cast<BinaryOperator>(S);1857 if (!BO || BO->getOpcode() != BO_Assign)1858 return;1859 const auto *RHS = BO->getRHS()->IgnoreParenImpCasts();1860 if (const auto *RHSRef = dyn_cast<DeclRefExpr>(RHS);1861 !RHSRef ||1862 !isa<ConstantArrayType>(RHSRef->getType().getCanonicalType()) ||1863 !isSupportedVariable(*RHSRef)) {1864 return;1865 }1866 const auto *LHS = BO->getLHS();1867 if (const auto *LHSRef = dyn_cast<DeclRefExpr>(LHS);1868 !LHSRef || !hasPointerType(*LHSRef) ||1869 !isSupportedVariable(*LHSRef)) {1870 return;1871 }1872 MatchResult R;1873 R.addNode(PointerAssignLHSTag, DynTypedNode::create(*LHS));1874 R.addNode(PointerAssignRHSTag, DynTypedNode::create(*RHS));1875 Results.emplace_back(std::move(R));1876 });1877 return SizeBefore != Results.size();1878 }1879 1880 virtual std::optional<FixItList>1881 getFixits(const FixitStrategy &S) const override;1882 SourceLocation getSourceLoc() const override { return PtrLHS->getBeginLoc(); }1883 1884 virtual DeclUseList getClaimedVarUseSites() const override {1885 return DeclUseList{PtrLHS, PtrRHS};1886 }1887 1888 virtual std::optional<std::pair<const VarDecl *, const VarDecl *>>1889 getStrategyImplications() const override {1890 return {};1891 }1892};1893 1894/// A call of a function or method that performs unchecked buffer operations1895/// over one of its pointer parameters.1896class UnsafeBufferUsageAttrGadget : public WarningGadget {1897 constexpr static const char *const OpTag = "attr_expr";1898 const Expr *Op;1899 1900public:1901 UnsafeBufferUsageAttrGadget(const MatchResult &Result)1902 : WarningGadget(Kind::UnsafeBufferUsageAttr),1903 Op(Result.getNodeAs<Expr>(OpTag)) {}1904 1905 static bool classof(const Gadget *G) {1906 return G->getKind() == Kind::UnsafeBufferUsageAttr;1907 }1908 1909 static bool matches(const Stmt *S, const ASTContext &Ctx,1910 MatchResult &Result) {1911 if (auto *CE = dyn_cast<CallExpr>(S)) {1912 if (CE->getDirectCallee() &&1913 CE->getDirectCallee()->hasAttr<UnsafeBufferUsageAttr>()) {1914 Result.addNode(OpTag, DynTypedNode::create(*CE));1915 return true;1916 }1917 }1918 if (auto *ME = dyn_cast<MemberExpr>(S)) {1919 if (!isa<FieldDecl>(ME->getMemberDecl()))1920 return false;1921 if (ME->getMemberDecl()->hasAttr<UnsafeBufferUsageAttr>()) {1922 Result.addNode(OpTag, DynTypedNode::create(*ME));1923 return true;1924 }1925 }1926 return false;1927 }1928 1929 void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,1930 bool IsRelatedToDecl,1931 ASTContext &Ctx) const override {1932 Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);1933 }1934 SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }1935 1936 DeclUseList getClaimedVarUseSites() const override { return {}; }1937 1938 SmallVector<const Expr *, 1> getUnsafePtrs() const override { return {}; }1939};1940 1941/// A call of a constructor that performs unchecked buffer operations1942/// over one of its pointer parameters, or constructs a class object that will1943/// perform buffer operations that depend on the correctness of the parameters.1944class UnsafeBufferUsageCtorAttrGadget : public WarningGadget {1945 constexpr static const char *const OpTag = "cxx_construct_expr";1946 const CXXConstructExpr *Op;1947 1948public:1949 UnsafeBufferUsageCtorAttrGadget(const MatchResult &Result)1950 : WarningGadget(Kind::UnsafeBufferUsageCtorAttr),1951 Op(Result.getNodeAs<CXXConstructExpr>(OpTag)) {}1952 1953 static bool classof(const Gadget *G) {1954 return G->getKind() == Kind::UnsafeBufferUsageCtorAttr;1955 }1956 1957 static bool matches(const Stmt *S, ASTContext &Ctx, MatchResult &Result) {1958 const auto *CE = dyn_cast<CXXConstructExpr>(S);1959 if (!CE || !CE->getConstructor()->hasAttr<UnsafeBufferUsageAttr>())1960 return false;1961 // std::span(ptr, size) ctor is handled by SpanTwoParamConstructorGadget.1962 MatchResult Tmp;1963 if (SpanTwoParamConstructorGadget::matches(CE, Ctx, Tmp))1964 return false;1965 Result.addNode(OpTag, DynTypedNode::create(*CE));1966 return true;1967 }1968 1969 void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,1970 bool IsRelatedToDecl,1971 ASTContext &Ctx) const override {1972 Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);1973 }1974 SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }1975 1976 DeclUseList getClaimedVarUseSites() const override { return {}; }1977 1978 SmallVector<const Expr *, 1> getUnsafePtrs() const override { return {}; }1979};1980 1981// Warning gadget for unsafe invocation of span::data method.1982// Triggers when the pointer returned by the invocation is immediately1983// cast to a larger type.1984 1985class DataInvocationGadget : public WarningGadget {1986 constexpr static const char *const OpTag = "data_invocation_expr";1987 const ExplicitCastExpr *Op;1988 1989public:1990 DataInvocationGadget(const MatchResult &Result)1991 : WarningGadget(Kind::DataInvocation),1992 Op(Result.getNodeAs<ExplicitCastExpr>(OpTag)) {}1993 1994 static bool classof(const Gadget *G) {1995 return G->getKind() == Kind::DataInvocation;1996 }1997 1998 static bool matches(const Stmt *S, const ASTContext &Ctx,1999 MatchResult &Result) {2000 auto *CE = dyn_cast<ExplicitCastExpr>(S);2001 if (!CE)2002 return false;2003 for (auto *Child : CE->children()) {2004 if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Child);2005 MCE && isDataFunction(MCE)) {2006 Result.addNode(OpTag, DynTypedNode::create(*CE));2007 return true;2008 }2009 if (auto *Paren = dyn_cast<ParenExpr>(Child)) {2010 if (auto *MCE = dyn_cast<CXXMemberCallExpr>(Paren->getSubExpr());2011 MCE && isDataFunction(MCE)) {2012 Result.addNode(OpTag, DynTypedNode::create(*CE));2013 return true;2014 }2015 }2016 }2017 return false;2018 }2019 2020 void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,2021 bool IsRelatedToDecl,2022 ASTContext &Ctx) const override {2023 Handler.handleUnsafeOperation(Op, IsRelatedToDecl, Ctx);2024 }2025 SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }2026 2027 DeclUseList getClaimedVarUseSites() const override { return {}; }2028 2029private:2030 static bool isDataFunction(const CXXMemberCallExpr *call) {2031 if (!call)2032 return false;2033 auto *callee = call->getDirectCallee();2034 if (!callee || !isa<CXXMethodDecl>(callee))2035 return false;2036 auto *method = cast<CXXMethodDecl>(callee);2037 if (method->getNameAsString() == "data" &&2038 method->getParent()->isInStdNamespace() &&2039 llvm::is_contained({SIZED_CONTAINER_OR_VIEW_LIST},2040 method->getParent()->getName()))2041 return true;2042 return false;2043 }2044 2045 SmallVector<const Expr *, 1> getUnsafePtrs() const override { return {}; }2046};2047 2048class UnsafeLibcFunctionCallGadget : public WarningGadget {2049 const CallExpr *const Call;2050 const Expr *UnsafeArg = nullptr;2051 constexpr static const char *const Tag = "UnsafeLibcFunctionCall";2052 // Extra tags for additional information:2053 constexpr static const char *const UnsafeSprintfTag =2054 "UnsafeLibcFunctionCall_sprintf";2055 constexpr static const char *const UnsafeSizedByTag =2056 "UnsafeLibcFunctionCall_sized_by";2057 constexpr static const char *const UnsafeStringTag =2058 "UnsafeLibcFunctionCall_string";2059 constexpr static const char *const UnsafeVaListTag =2060 "UnsafeLibcFunctionCall_va_list";2061 2062 enum UnsafeKind {2063 OTHERS = 0, // no specific information, the callee function is unsafe2064 SPRINTF = 1, // never call `-sprintf`s, call `-snprintf`s instead.2065 SIZED_BY =2066 2, // the first two arguments of `snprintf` function have2067 // "__sized_by" relation but they do not conform to safe patterns2068 STRING = 3, // an argument is a pointer-to-char-as-string but does not2069 // guarantee null-termination2070 VA_LIST = 4, // one of the `-printf`s function that take va_list, which is2071 // considered unsafe as it is not compile-time check2072 } WarnedFunKind = OTHERS;2073 2074public:2075 UnsafeLibcFunctionCallGadget(const MatchResult &Result)2076 : WarningGadget(Kind::UnsafeLibcFunctionCall),2077 Call(Result.getNodeAs<CallExpr>(Tag)) {2078 if (Result.getNodeAs<Decl>(UnsafeSprintfTag))2079 WarnedFunKind = SPRINTF;2080 else if (auto *E = Result.getNodeAs<Expr>(UnsafeStringTag)) {2081 WarnedFunKind = STRING;2082 UnsafeArg = E;2083 } else if (Result.getNodeAs<CallExpr>(UnsafeSizedByTag)) {2084 WarnedFunKind = SIZED_BY;2085 UnsafeArg = Call->getArg(0);2086 } else if (Result.getNodeAs<Decl>(UnsafeVaListTag))2087 WarnedFunKind = VA_LIST;2088 }2089 2090 static bool matches(const Stmt *S, ASTContext &Ctx,2091 const UnsafeBufferUsageHandler *Handler,2092 MatchResult &Result) {2093 if (ignoreUnsafeLibcCall(Ctx, *S, Handler))2094 return false;2095 auto *CE = dyn_cast<CallExpr>(S);2096 if (!CE || !CE->getDirectCallee())2097 return false;2098 const auto *FD = dyn_cast<FunctionDecl>(CE->getDirectCallee());2099 if (!FD)2100 return false;2101 2102 bool IsGlobalAndNotInAnyNamespace =2103 FD->isGlobal() && !FD->getEnclosingNamespaceContext()->isNamespace();2104 2105 // A libc function must either be in the std:: namespace or a global2106 // function that is not in any namespace:2107 if (!FD->isInStdNamespace() && !IsGlobalAndNotInAnyNamespace)2108 return false;2109 auto isSingleStringLiteralArg = false;2110 if (CE->getNumArgs() == 1) {2111 isSingleStringLiteralArg =2112 isa<clang::StringLiteral>(CE->getArg(0)->IgnoreParenImpCasts());2113 }2114 if (!isSingleStringLiteralArg) {2115 // (unless the call has a sole string literal argument):2116 if (libc_func_matchers::isPredefinedUnsafeLibcFunc(*FD)) {2117 Result.addNode(Tag, DynTypedNode::create(*CE));2118 return true;2119 }2120 if (libc_func_matchers::isUnsafeVaListPrintfFunc(*FD)) {2121 Result.addNode(Tag, DynTypedNode::create(*CE));2122 Result.addNode(UnsafeVaListTag, DynTypedNode::create(*FD));2123 return true;2124 }2125 if (libc_func_matchers::isUnsafeSprintfFunc(*FD)) {2126 Result.addNode(Tag, DynTypedNode::create(*CE));2127 Result.addNode(UnsafeSprintfTag, DynTypedNode::create(*FD));2128 return true;2129 }2130 }2131 if (libc_func_matchers::isNormalPrintfFunc(*FD)) {2132 if (libc_func_matchers::hasUnsafeSnprintfBuffer(*CE, Ctx)) {2133 Result.addNode(Tag, DynTypedNode::create(*CE));2134 Result.addNode(UnsafeSizedByTag, DynTypedNode::create(*CE));2135 return true;2136 }2137 if (libc_func_matchers::hasUnsafePrintfStringArg(*CE, Ctx, Result,2138 UnsafeStringTag)) {2139 Result.addNode(Tag, DynTypedNode::create(*CE));2140 return true;2141 }2142 }2143 return false;2144 }2145 2146 const Stmt *getBaseStmt() const { return Call; }2147 2148 SourceLocation getSourceLoc() const override { return Call->getBeginLoc(); }2149 2150 void handleUnsafeOperation(UnsafeBufferUsageHandler &Handler,2151 bool IsRelatedToDecl,2152 ASTContext &Ctx) const override {2153 Handler.handleUnsafeLibcCall(Call, WarnedFunKind, Ctx, UnsafeArg);2154 }2155 2156 DeclUseList getClaimedVarUseSites() const override { return {}; }2157 2158 SmallVector<const Expr *, 1> getUnsafePtrs() const override { return {}; }2159};2160 2161// Represents expressions of the form `DRE[*]` in the Unspecified Lvalue2162// Context (see `findStmtsInUnspecifiedLvalueContext`).2163// Note here `[]` is the built-in subscript operator.2164class ULCArraySubscriptGadget : public FixableGadget {2165private:2166 static constexpr const char *const ULCArraySubscriptTag =2167 "ArraySubscriptUnderULC";2168 const ArraySubscriptExpr *Node;2169 2170public:2171 ULCArraySubscriptGadget(const MatchResult &Result)2172 : FixableGadget(Kind::ULCArraySubscript),2173 Node(Result.getNodeAs<ArraySubscriptExpr>(ULCArraySubscriptTag)) {2174 assert(Node != nullptr && "Expecting a non-null matching result");2175 }2176 2177 static bool classof(const Gadget *G) {2178 return G->getKind() == Kind::ULCArraySubscript;2179 }2180 2181 static bool matches(const Stmt *S,2182 llvm::SmallVectorImpl<MatchResult> &Results) {2183 size_t SizeBefore = Results.size();2184 findStmtsInUnspecifiedLvalueContext(S, [&Results](const Expr *E) {2185 const auto *ASE = dyn_cast<ArraySubscriptExpr>(E);2186 if (!ASE)2187 return;2188 const auto *DRE =2189 dyn_cast<DeclRefExpr>(ASE->getBase()->IgnoreParenImpCasts());2190 if (!DRE || !(hasPointerType(*DRE) || hasArrayType(*DRE)) ||2191 !isSupportedVariable(*DRE))2192 return;2193 MatchResult R;2194 R.addNode(ULCArraySubscriptTag, DynTypedNode::create(*ASE));2195 Results.emplace_back(std::move(R));2196 });2197 return SizeBefore != Results.size();2198 }2199 2200 virtual std::optional<FixItList>2201 getFixits(const FixitStrategy &S) const override;2202 SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }2203 2204 virtual DeclUseList getClaimedVarUseSites() const override {2205 if (const auto *DRE =2206 dyn_cast<DeclRefExpr>(Node->getBase()->IgnoreImpCasts())) {2207 return {DRE};2208 }2209 return {};2210 }2211};2212 2213// Fixable gadget to handle stand alone pointers of the form `UPC(DRE)` in the2214// unspecified pointer context (findStmtsInUnspecifiedPointerContext). The2215// gadget emits fixit of the form `UPC(DRE.data())`.2216class UPCStandalonePointerGadget : public FixableGadget {2217private:2218 static constexpr const char *const DeclRefExprTag = "StandalonePointer";2219 const DeclRefExpr *Node;2220 2221public:2222 UPCStandalonePointerGadget(const MatchResult &Result)2223 : FixableGadget(Kind::UPCStandalonePointer),2224 Node(Result.getNodeAs<DeclRefExpr>(DeclRefExprTag)) {2225 assert(Node != nullptr && "Expecting a non-null matching result");2226 }2227 2228 static bool classof(const Gadget *G) {2229 return G->getKind() == Kind::UPCStandalonePointer;2230 }2231 2232 static bool matches(const Stmt *S,2233 llvm::SmallVectorImpl<MatchResult> &Results) {2234 size_t SizeBefore = Results.size();2235 findStmtsInUnspecifiedPointerContext(S, [&Results](const Stmt *S) {2236 auto *E = dyn_cast<Expr>(S);2237 if (!E)2238 return;2239 const auto *DRE = dyn_cast<DeclRefExpr>(E->IgnoreParenImpCasts());2240 if (!DRE || (!hasPointerType(*DRE) && !hasArrayType(*DRE)) ||2241 !isSupportedVariable(*DRE))2242 return;2243 MatchResult R;2244 R.addNode(DeclRefExprTag, DynTypedNode::create(*DRE));2245 Results.emplace_back(std::move(R));2246 });2247 return SizeBefore != Results.size();2248 }2249 2250 virtual std::optional<FixItList>2251 getFixits(const FixitStrategy &S) const override;2252 SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }2253 2254 virtual DeclUseList getClaimedVarUseSites() const override { return {Node}; }2255};2256 2257class PointerDereferenceGadget : public FixableGadget {2258 static constexpr const char *const BaseDeclRefExprTag = "BaseDRE";2259 static constexpr const char *const OperatorTag = "op";2260 2261 const DeclRefExpr *BaseDeclRefExpr = nullptr;2262 const UnaryOperator *Op = nullptr;2263 2264public:2265 PointerDereferenceGadget(const MatchResult &Result)2266 : FixableGadget(Kind::PointerDereference),2267 BaseDeclRefExpr(Result.getNodeAs<DeclRefExpr>(BaseDeclRefExprTag)),2268 Op(Result.getNodeAs<UnaryOperator>(OperatorTag)) {}2269 2270 static bool classof(const Gadget *G) {2271 return G->getKind() == Kind::PointerDereference;2272 }2273 2274 static bool matches(const Stmt *S,2275 llvm::SmallVectorImpl<MatchResult> &Results) {2276 size_t SizeBefore = Results.size();2277 findStmtsInUnspecifiedLvalueContext(S, [&Results](const Stmt *S) {2278 const auto *UO = dyn_cast<UnaryOperator>(S);2279 if (!UO || UO->getOpcode() != UO_Deref)2280 return;2281 const auto *CE = dyn_cast<Expr>(UO->getSubExpr());2282 if (!CE)2283 return;2284 CE = CE->IgnoreParenImpCasts();2285 const auto *DRE = dyn_cast<DeclRefExpr>(CE);2286 if (!DRE || !isSupportedVariable(*DRE))2287 return;2288 MatchResult R;2289 R.addNode(BaseDeclRefExprTag, DynTypedNode::create(*DRE));2290 R.addNode(OperatorTag, DynTypedNode::create(*UO));2291 Results.emplace_back(std::move(R));2292 });2293 return SizeBefore != Results.size();2294 }2295 2296 DeclUseList getClaimedVarUseSites() const override {2297 return {BaseDeclRefExpr};2298 }2299 2300 virtual std::optional<FixItList>2301 getFixits(const FixitStrategy &S) const override;2302 SourceLocation getSourceLoc() const override { return Op->getBeginLoc(); }2303};2304 2305// Represents expressions of the form `&DRE[any]` in the Unspecified Pointer2306// Context (see `findStmtsInUnspecifiedPointerContext`).2307// Note here `[]` is the built-in subscript operator.2308class UPCAddressofArraySubscriptGadget : public FixableGadget {2309private:2310 static constexpr const char *const UPCAddressofArraySubscriptTag =2311 "AddressofArraySubscriptUnderUPC";2312 const UnaryOperator *Node; // the `&DRE[any]` node2313 2314public:2315 UPCAddressofArraySubscriptGadget(const MatchResult &Result)2316 : FixableGadget(Kind::ULCArraySubscript),2317 Node(Result.getNodeAs<UnaryOperator>(UPCAddressofArraySubscriptTag)) {2318 assert(Node != nullptr && "Expecting a non-null matching result");2319 }2320 2321 static bool classof(const Gadget *G) {2322 return G->getKind() == Kind::UPCAddressofArraySubscript;2323 }2324 2325 static bool matches(const Stmt *S,2326 llvm::SmallVectorImpl<MatchResult> &Results) {2327 size_t SizeBefore = Results.size();2328 findStmtsInUnspecifiedPointerContext(S, [&Results](const Stmt *S) {2329 auto *E = dyn_cast<Expr>(S);2330 if (!E)2331 return;2332 const auto *UO = dyn_cast<UnaryOperator>(E->IgnoreImpCasts());2333 if (!UO || UO->getOpcode() != UO_AddrOf)2334 return;2335 const auto *ASE = dyn_cast<ArraySubscriptExpr>(UO->getSubExpr());2336 if (!ASE)2337 return;2338 const auto *DRE =2339 dyn_cast<DeclRefExpr>(ASE->getBase()->IgnoreParenImpCasts());2340 if (!DRE || !isSupportedVariable(*DRE))2341 return;2342 MatchResult R;2343 R.addNode(UPCAddressofArraySubscriptTag, DynTypedNode::create(*UO));2344 Results.emplace_back(std::move(R));2345 });2346 return SizeBefore != Results.size();2347 }2348 2349 virtual std::optional<FixItList>2350 getFixits(const FixitStrategy &) const override;2351 SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }2352 2353 virtual DeclUseList getClaimedVarUseSites() const override {2354 const auto *ArraySubst = cast<ArraySubscriptExpr>(Node->getSubExpr());2355 const auto *DRE =2356 cast<DeclRefExpr>(ArraySubst->getBase()->IgnoreParenImpCasts());2357 return {DRE};2358 }2359};2360} // namespace2361 2362namespace {2363// An auxiliary tracking facility for the fixit analysis. It helps connect2364// declarations to its uses and make sure we've covered all uses with our2365// analysis before we try to fix the declaration.2366class DeclUseTracker {2367 using UseSetTy = llvm::SmallPtrSet<const DeclRefExpr *, 16>;2368 using DefMapTy = llvm::DenseMap<const VarDecl *, const DeclStmt *>;2369 2370 // Allocate on the heap for easier move.2371 std::unique_ptr<UseSetTy> Uses{std::make_unique<UseSetTy>()};2372 DefMapTy Defs{};2373 2374public:2375 DeclUseTracker() = default;2376 DeclUseTracker(const DeclUseTracker &) = delete; // Let's avoid copies.2377 DeclUseTracker &operator=(const DeclUseTracker &) = delete;2378 DeclUseTracker(DeclUseTracker &&) = default;2379 DeclUseTracker &operator=(DeclUseTracker &&) = default;2380 2381 // Start tracking a freshly discovered DRE.2382 void discoverUse(const DeclRefExpr *DRE) { Uses->insert(DRE); }2383 2384 // Stop tracking the DRE as it's been fully figured out.2385 void claimUse(const DeclRefExpr *DRE) {2386 assert(Uses->count(DRE) &&2387 "DRE not found or claimed by multiple matchers!");2388 Uses->erase(DRE);2389 }2390 2391 // A variable is unclaimed if at least one use is unclaimed.2392 bool hasUnclaimedUses(const VarDecl *VD) const {2393 // FIXME: Can this be less linear? Maybe maintain a map from VDs to DREs?2394 return any_of(*Uses, [VD](const DeclRefExpr *DRE) {2395 return DRE->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl();2396 });2397 }2398 2399 UseSetTy getUnclaimedUses(const VarDecl *VD) const {2400 UseSetTy ReturnSet;2401 for (auto use : *Uses) {2402 if (use->getDecl()->getCanonicalDecl() == VD->getCanonicalDecl()) {2403 ReturnSet.insert(use);2404 }2405 }2406 return ReturnSet;2407 }2408 2409 void discoverDecl(const DeclStmt *DS) {2410 for (const Decl *D : DS->decls()) {2411 if (const auto *VD = dyn_cast<VarDecl>(D)) {2412 // FIXME: Assertion temporarily disabled due to a bug in2413 // ASTMatcher internal behavior in presence of GNU2414 // statement-expressions. We need to properly investigate this2415 // because it can screw up our algorithm in other ways.2416 // assert(Defs.count(VD) == 0 && "Definition already discovered!");2417 Defs[VD] = DS;2418 }2419 }2420 }2421 2422 const DeclStmt *lookupDecl(const VarDecl *VD) const {2423 return Defs.lookup(VD);2424 }2425};2426} // namespace2427 2428// Representing a pointer type expression of the form `++Ptr` in an Unspecified2429// Pointer Context (UPC):2430class UPCPreIncrementGadget : public FixableGadget {2431private:2432 static constexpr const char *const UPCPreIncrementTag =2433 "PointerPreIncrementUnderUPC";2434 const UnaryOperator *Node; // the `++Ptr` node2435 2436public:2437 UPCPreIncrementGadget(const MatchResult &Result)2438 : FixableGadget(Kind::UPCPreIncrement),2439 Node(Result.getNodeAs<UnaryOperator>(UPCPreIncrementTag)) {2440 assert(Node != nullptr && "Expecting a non-null matching result");2441 }2442 2443 static bool classof(const Gadget *G) {2444 return G->getKind() == Kind::UPCPreIncrement;2445 }2446 2447 static bool matches(const Stmt *S,2448 llvm::SmallVectorImpl<MatchResult> &Results) {2449 // Note here we match `++Ptr` for any expression `Ptr` of pointer type.2450 // Although currently we can only provide fix-its when `Ptr` is a DRE, we2451 // can have the matcher be general, so long as `getClaimedVarUseSites` does2452 // things right.2453 size_t SizeBefore = Results.size();2454 findStmtsInUnspecifiedPointerContext(S, [&Results](const Stmt *S) {2455 auto *E = dyn_cast<Expr>(S);2456 if (!E)2457 return;2458 const auto *UO = dyn_cast<UnaryOperator>(E->IgnoreImpCasts());2459 if (!UO || UO->getOpcode() != UO_PreInc)2460 return;2461 const auto *DRE = dyn_cast<DeclRefExpr>(UO->getSubExpr());2462 if (!DRE || !isSupportedVariable(*DRE))2463 return;2464 MatchResult R;2465 R.addNode(UPCPreIncrementTag, DynTypedNode::create(*UO));2466 Results.emplace_back(std::move(R));2467 });2468 return SizeBefore != Results.size();2469 }2470 2471 virtual std::optional<FixItList>2472 getFixits(const FixitStrategy &S) const override;2473 SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }2474 2475 virtual DeclUseList getClaimedVarUseSites() const override {2476 return {dyn_cast<DeclRefExpr>(Node->getSubExpr())};2477 }2478};2479 2480// Representing a pointer type expression of the form `Ptr += n` in an2481// Unspecified Untyped Context (UUC):2482class UUCAddAssignGadget : public FixableGadget {2483private:2484 static constexpr const char *const UUCAddAssignTag =2485 "PointerAddAssignUnderUUC";2486 static constexpr const char *const OffsetTag = "Offset";2487 2488 const BinaryOperator *Node; // the `Ptr += n` node2489 const Expr *Offset = nullptr;2490 2491public:2492 UUCAddAssignGadget(const MatchResult &Result)2493 : FixableGadget(Kind::UUCAddAssign),2494 Node(Result.getNodeAs<BinaryOperator>(UUCAddAssignTag)),2495 Offset(Result.getNodeAs<Expr>(OffsetTag)) {2496 assert(Node != nullptr && "Expecting a non-null matching result");2497 }2498 2499 static bool classof(const Gadget *G) {2500 return G->getKind() == Kind::UUCAddAssign;2501 }2502 2503 static bool matches(const Stmt *S,2504 llvm::SmallVectorImpl<MatchResult> &Results) {2505 size_t SizeBefore = Results.size();2506 findStmtsInUnspecifiedUntypedContext(S, [&Results](const Stmt *S) {2507 const auto *E = dyn_cast<Expr>(S);2508 if (!E)2509 return;2510 const auto *BO = dyn_cast<BinaryOperator>(E->IgnoreImpCasts());2511 if (!BO || BO->getOpcode() != BO_AddAssign)2512 return;2513 const auto *DRE = dyn_cast<DeclRefExpr>(BO->getLHS());2514 if (!DRE || !hasPointerType(*DRE) || !isSupportedVariable(*DRE))2515 return;2516 MatchResult R;2517 R.addNode(UUCAddAssignTag, DynTypedNode::create(*BO));2518 R.addNode(OffsetTag, DynTypedNode::create(*BO->getRHS()));2519 Results.emplace_back(std::move(R));2520 });2521 return SizeBefore != Results.size();2522 }2523 2524 virtual std::optional<FixItList>2525 getFixits(const FixitStrategy &S) const override;2526 SourceLocation getSourceLoc() const override { return Node->getBeginLoc(); }2527 2528 virtual DeclUseList getClaimedVarUseSites() const override {2529 return {dyn_cast<DeclRefExpr>(Node->getLHS())};2530 }2531};2532 2533// Representing a fixable expression of the form `*(ptr + 123)` or `*(123 +2534// ptr)`:2535class DerefSimplePtrArithFixableGadget : public FixableGadget {2536 static constexpr const char *const BaseDeclRefExprTag = "BaseDRE";2537 static constexpr const char *const DerefOpTag = "DerefOp";2538 static constexpr const char *const AddOpTag = "AddOp";2539 static constexpr const char *const OffsetTag = "Offset";2540 2541 const DeclRefExpr *BaseDeclRefExpr = nullptr;2542 const UnaryOperator *DerefOp = nullptr;2543 const BinaryOperator *AddOp = nullptr;2544 const IntegerLiteral *Offset = nullptr;2545 2546public:2547 DerefSimplePtrArithFixableGadget(const MatchResult &Result)2548 : FixableGadget(Kind::DerefSimplePtrArithFixable),2549 BaseDeclRefExpr(Result.getNodeAs<DeclRefExpr>(BaseDeclRefExprTag)),2550 DerefOp(Result.getNodeAs<UnaryOperator>(DerefOpTag)),2551 AddOp(Result.getNodeAs<BinaryOperator>(AddOpTag)),2552 Offset(Result.getNodeAs<IntegerLiteral>(OffsetTag)) {}2553 2554 static bool matches(const Stmt *S,2555 llvm::SmallVectorImpl<MatchResult> &Results) {2556 auto IsPtr = [](const Expr *E, MatchResult &R) {2557 if (!E || !hasPointerType(*E))2558 return false;2559 const auto *DRE = dyn_cast<DeclRefExpr>(E->IgnoreImpCasts());2560 if (!DRE || !isSupportedVariable(*DRE))2561 return false;2562 R.addNode(BaseDeclRefExprTag, DynTypedNode::create(*DRE));2563 return true;2564 };2565 const auto IsPlusOverPtrAndInteger = [&IsPtr](const Expr *E,2566 MatchResult &R) {2567 const auto *BO = dyn_cast<BinaryOperator>(E);2568 if (!BO || BO->getOpcode() != BO_Add)2569 return false;2570 2571 const auto *LHS = BO->getLHS();2572 const auto *RHS = BO->getRHS();2573 if (isa<IntegerLiteral>(RHS) && IsPtr(LHS, R)) {2574 R.addNode(OffsetTag, DynTypedNode::create(*RHS));2575 R.addNode(AddOpTag, DynTypedNode::create(*BO));2576 return true;2577 }2578 if (isa<IntegerLiteral>(LHS) && IsPtr(RHS, R)) {2579 R.addNode(OffsetTag, DynTypedNode::create(*LHS));2580 R.addNode(AddOpTag, DynTypedNode::create(*BO));2581 return true;2582 }2583 return false;2584 };2585 size_t SizeBefore = Results.size();2586 const auto InnerMatcher = [&IsPlusOverPtrAndInteger,2587 &Results](const Expr *E) {2588 const auto *UO = dyn_cast<UnaryOperator>(E);2589 if (!UO || UO->getOpcode() != UO_Deref)2590 return;2591 2592 const auto *Operand = UO->getSubExpr()->IgnoreParens();2593 MatchResult R;2594 if (IsPlusOverPtrAndInteger(Operand, R)) {2595 R.addNode(DerefOpTag, DynTypedNode::create(*UO));2596 Results.emplace_back(std::move(R));2597 }2598 };2599 findStmtsInUnspecifiedLvalueContext(S, InnerMatcher);2600 return SizeBefore != Results.size();2601 }2602 2603 virtual std::optional<FixItList>2604 getFixits(const FixitStrategy &s) const final;2605 SourceLocation getSourceLoc() const override {2606 return DerefOp->getBeginLoc();2607 }2608 2609 virtual DeclUseList getClaimedVarUseSites() const final {2610 return {BaseDeclRefExpr};2611 }2612};2613 2614class WarningGadgetMatcher : public FastMatcher {2615 2616public:2617 WarningGadgetMatcher(WarningGadgetList &WarningGadgets)2618 : WarningGadgets(WarningGadgets) {}2619 2620 bool matches(const DynTypedNode &DynNode, ASTContext &Ctx,2621 const UnsafeBufferUsageHandler &Handler) override {2622 const Stmt *S = DynNode.get<Stmt>();2623 if (!S)2624 return false;2625 2626 MatchResult Result;2627#define WARNING_GADGET(name) \2628 if (name##Gadget::matches(S, Ctx, Result) && \2629 notInSafeBufferOptOut(*S, &Handler)) { \2630 WarningGadgets.push_back(std::make_unique<name##Gadget>(Result)); \2631 return true; \2632 }2633#define WARNING_OPTIONAL_GADGET(name) \2634 if (name##Gadget::matches(S, Ctx, &Handler, Result) && \2635 notInSafeBufferOptOut(*S, &Handler)) { \2636 WarningGadgets.push_back(std::make_unique<name##Gadget>(Result)); \2637 return true; \2638 }2639#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"2640 return false;2641 }2642 2643private:2644 WarningGadgetList &WarningGadgets;2645};2646 2647class FixableGadgetMatcher : public FastMatcher {2648 2649public:2650 FixableGadgetMatcher(FixableGadgetList &FixableGadgets,2651 DeclUseTracker &Tracker)2652 : FixableGadgets(FixableGadgets), Tracker(Tracker) {}2653 2654 bool matches(const DynTypedNode &DynNode, ASTContext &Ctx,2655 const UnsafeBufferUsageHandler &Handler) override {2656 bool matchFound = false;2657 const Stmt *S = DynNode.get<Stmt>();2658 if (!S) {2659 return matchFound;2660 }2661 2662 llvm::SmallVector<MatchResult> Results;2663#define FIXABLE_GADGET(name) \2664 if (name##Gadget::matches(S, Results)) { \2665 for (const auto &R : Results) { \2666 FixableGadgets.push_back(std::make_unique<name##Gadget>(R)); \2667 matchFound = true; \2668 } \2669 Results = {}; \2670 }2671#include "clang/Analysis/Analyses/UnsafeBufferUsageGadgets.def"2672 // In parallel, match all DeclRefExprs so that to find out2673 // whether there are any uncovered by gadgets.2674 if (auto *DRE = findDeclRefExpr(S); DRE) {2675 Tracker.discoverUse(DRE);2676 matchFound = true;2677 }2678 // Also match DeclStmts because we'll need them when fixing2679 // their underlying VarDecls that otherwise don't have2680 // any backreferences to DeclStmts.2681 if (auto *DS = findDeclStmt(S); DS) {2682 Tracker.discoverDecl(DS);2683 matchFound = true;2684 }2685 return matchFound;2686 }2687 2688private:2689 const DeclRefExpr *findDeclRefExpr(const Stmt *S) {2690 const auto *DRE = dyn_cast<DeclRefExpr>(S);2691 if (!DRE || (!hasPointerType(*DRE) && !hasArrayType(*DRE)))2692 return nullptr;2693 const Decl *D = DRE->getDecl();2694 if (!D || (!isa<VarDecl>(D) && !isa<BindingDecl>(D)))2695 return nullptr;2696 return DRE;2697 }2698 const DeclStmt *findDeclStmt(const Stmt *S) {2699 const auto *DS = dyn_cast<DeclStmt>(S);2700 if (!DS)2701 return nullptr;2702 return DS;2703 }2704 FixableGadgetList &FixableGadgets;2705 DeclUseTracker &Tracker;2706};2707 2708// Scan the function and return a list of gadgets found with provided kits.2709static void findGadgets(const Stmt *S, ASTContext &Ctx,2710 const UnsafeBufferUsageHandler &Handler,2711 bool EmitSuggestions, FixableGadgetList &FixableGadgets,2712 WarningGadgetList &WarningGadgets,2713 DeclUseTracker &Tracker) {2714 WarningGadgetMatcher WMatcher{WarningGadgets};2715 forEachDescendantEvaluatedStmt(S, Ctx, Handler, WMatcher);2716 if (EmitSuggestions) {2717 FixableGadgetMatcher FMatcher{FixableGadgets, Tracker};2718 forEachDescendantStmt(S, Ctx, Handler, FMatcher);2719 }2720}2721 2722// Compares AST nodes by source locations.2723template <typename NodeTy> struct CompareNode {2724 bool operator()(const NodeTy *N1, const NodeTy *N2) const {2725 return N1->getBeginLoc().getRawEncoding() <2726 N2->getBeginLoc().getRawEncoding();2727 }2728};2729 2730std::set<const Expr *> clang::findUnsafePointers(const FunctionDecl *FD) {2731 class MockReporter : public UnsafeBufferUsageHandler {2732 public:2733 MockReporter() {}2734 void handleUnsafeOperation(const Stmt *, bool, ASTContext &) override {}2735 void handleUnsafeLibcCall(const CallExpr *, unsigned, ASTContext &,2736 const Expr *UnsafeArg = nullptr) override {}2737 void handleUnsafeOperationInContainer(const Stmt *, bool,2738 ASTContext &) override {}2739 void handleUnsafeVariableGroup(const VarDecl *,2740 const VariableGroupsManager &, FixItList &&,2741 const Decl *,2742 const FixitStrategy &) override {}2743 void handleUnsafeUniquePtrArrayAccess(const DynTypedNode &Node,2744 bool IsRelatedToDecl,2745 ASTContext &Ctx) override {}2746 bool ignoreUnsafeBufferInContainer(const SourceLocation &) const override {2747 return false;2748 }2749 bool isSafeBufferOptOut(const SourceLocation &) const override {2750 return false;2751 }2752 bool ignoreUnsafeBufferInLibcCall(const SourceLocation &) const override {2753 return false;2754 }2755 std::string getUnsafeBufferUsageAttributeTextAt(2756 SourceLocation, StringRef WSSuffix = "") const override {2757 return "";2758 }2759 };2760 2761 FixableGadgetList FixableGadgets;2762 WarningGadgetList WarningGadgets;2763 DeclUseTracker Tracker;2764 MockReporter IgnoreHandler;2765 2766 findGadgets(FD->getBody(), FD->getASTContext(), IgnoreHandler, false,2767 FixableGadgets, WarningGadgets, Tracker);2768 2769 std::set<const Expr *> Result;2770 for (auto &G : WarningGadgets) {2771 for (const Expr *E : G->getUnsafePtrs()) {2772 Result.insert(E);2773 }2774 }2775 2776 return Result;2777}2778 2779struct WarningGadgetSets {2780 std::map<const VarDecl *, std::set<const WarningGadget *>,2781 // To keep keys sorted by their locations in the map so that the2782 // order is deterministic:2783 CompareNode<VarDecl>>2784 byVar;2785 // These Gadgets are not related to pointer variables (e. g. temporaries).2786 llvm::SmallVector<const WarningGadget *, 16> noVar;2787};2788 2789static WarningGadgetSets2790groupWarningGadgetsByVar(const WarningGadgetList &AllUnsafeOperations) {2791 WarningGadgetSets result;2792 // If some gadgets cover more than one2793 // variable, they'll appear more than once in the map.2794 for (auto &G : AllUnsafeOperations) {2795 DeclUseList ClaimedVarUseSites = G->getClaimedVarUseSites();2796 2797 bool AssociatedWithVarDecl = false;2798 for (const DeclRefExpr *DRE : ClaimedVarUseSites) {2799 if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {2800 result.byVar[VD].insert(G.get());2801 AssociatedWithVarDecl = true;2802 }2803 }2804 2805 if (!AssociatedWithVarDecl) {2806 result.noVar.push_back(G.get());2807 continue;2808 }2809 }2810 return result;2811}2812 2813struct FixableGadgetSets {2814 std::map<const VarDecl *, std::set<const FixableGadget *>,2815 // To keep keys sorted by their locations in the map so that the2816 // order is deterministic:2817 CompareNode<VarDecl>>2818 byVar;2819};2820 2821static FixableGadgetSets2822groupFixablesByVar(FixableGadgetList &&AllFixableOperations) {2823 FixableGadgetSets FixablesForUnsafeVars;2824 for (auto &F : AllFixableOperations) {2825 DeclUseList DREs = F->getClaimedVarUseSites();2826 2827 for (const DeclRefExpr *DRE : DREs) {2828 if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {2829 FixablesForUnsafeVars.byVar[VD].insert(F.get());2830 }2831 }2832 }2833 return FixablesForUnsafeVars;2834}2835 2836bool clang::internal::anyConflict(const SmallVectorImpl<FixItHint> &FixIts,2837 const SourceManager &SM) {2838 // A simple interval overlap detection algorithm. Sorts all ranges by their2839 // begin location then finds the first overlap in one pass.2840 std::vector<const FixItHint *> All; // a copy of `FixIts`2841 2842 for (const FixItHint &H : FixIts)2843 All.push_back(&H);2844 std::sort(All.begin(), All.end(),2845 [&SM](const FixItHint *H1, const FixItHint *H2) {2846 return SM.isBeforeInTranslationUnit(H1->RemoveRange.getBegin(),2847 H2->RemoveRange.getBegin());2848 });2849 2850 const FixItHint *CurrHint = nullptr;2851 2852 for (const FixItHint *Hint : All) {2853 if (!CurrHint ||2854 SM.isBeforeInTranslationUnit(CurrHint->RemoveRange.getEnd(),2855 Hint->RemoveRange.getBegin())) {2856 // Either to initialize `CurrHint` or `CurrHint` does not2857 // overlap with `Hint`:2858 CurrHint = Hint;2859 } else2860 // In case `Hint` overlaps the `CurrHint`, we found at least one2861 // conflict:2862 return true;2863 }2864 return false;2865}2866 2867std::optional<FixItList>2868PtrToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {2869 const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl());2870 const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl());2871 switch (S.lookup(LeftVD)) {2872 case FixitStrategy::Kind::Span:2873 if (S.lookup(RightVD) == FixitStrategy::Kind::Span)2874 return FixItList{};2875 return std::nullopt;2876 case FixitStrategy::Kind::Wontfix:2877 return std::nullopt;2878 case FixitStrategy::Kind::Iterator:2879 case FixitStrategy::Kind::Array:2880 return std::nullopt;2881 case FixitStrategy::Kind::Vector:2882 llvm_unreachable("unsupported strategies for FixableGadgets");2883 }2884 return std::nullopt;2885}2886 2887/// \returns fixit that adds .data() call after \DRE.2888static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx,2889 const DeclRefExpr *DRE);2890 2891std::optional<FixItList>2892CArrayToPtrAssignmentGadget::getFixits(const FixitStrategy &S) const {2893 const auto *LeftVD = cast<VarDecl>(PtrLHS->getDecl());2894 const auto *RightVD = cast<VarDecl>(PtrRHS->getDecl());2895 // TLDR: Implementing fixits for non-Wontfix strategy on both LHS and RHS is2896 // non-trivial.2897 //2898 // CArrayToPtrAssignmentGadget doesn't have strategy implications because2899 // constant size array propagates its bounds. Because of that LHS and RHS are2900 // addressed by two different fixits.2901 //2902 // At the same time FixitStrategy S doesn't reflect what group a fixit belongs2903 // to and can't be generally relied on in multi-variable Fixables!2904 //2905 // E. g. If an instance of this gadget is fixing variable on LHS then the2906 // variable on RHS is fixed by a different fixit and its strategy for LHS2907 // fixit is as if Wontfix.2908 //2909 // The only exception is Wontfix strategy for a given variable as that is2910 // valid for any fixit produced for the given input source code.2911 if (S.lookup(LeftVD) == FixitStrategy::Kind::Span) {2912 if (S.lookup(RightVD) == FixitStrategy::Kind::Wontfix) {2913 return FixItList{};2914 }2915 } else if (S.lookup(LeftVD) == FixitStrategy::Kind::Wontfix) {2916 if (S.lookup(RightVD) == FixitStrategy::Kind::Array) {2917 return createDataFixit(RightVD->getASTContext(), PtrRHS);2918 }2919 }2920 return std::nullopt;2921}2922 2923std::optional<FixItList>2924PointerInitGadget::getFixits(const FixitStrategy &S) const {2925 const auto *LeftVD = PtrInitLHS;2926 const auto *RightVD = cast<VarDecl>(PtrInitRHS->getDecl());2927 switch (S.lookup(LeftVD)) {2928 case FixitStrategy::Kind::Span:2929 if (S.lookup(RightVD) == FixitStrategy::Kind::Span)2930 return FixItList{};2931 return std::nullopt;2932 case FixitStrategy::Kind::Wontfix:2933 return std::nullopt;2934 case FixitStrategy::Kind::Iterator:2935 case FixitStrategy::Kind::Array:2936 return std::nullopt;2937 case FixitStrategy::Kind::Vector:2938 llvm_unreachable("unsupported strategies for FixableGadgets");2939 }2940 return std::nullopt;2941}2942 2943static bool isNonNegativeIntegerExpr(const Expr *Expr, const VarDecl *VD,2944 const ASTContext &Ctx) {2945 if (auto ConstVal = Expr->getIntegerConstantExpr(Ctx)) {2946 if (ConstVal->isNegative())2947 return false;2948 } else if (!Expr->getType()->isUnsignedIntegerType())2949 return false;2950 return true;2951}2952 2953std::optional<FixItList>2954ULCArraySubscriptGadget::getFixits(const FixitStrategy &S) const {2955 if (const auto *DRE =2956 dyn_cast<DeclRefExpr>(Node->getBase()->IgnoreImpCasts()))2957 if (const auto *VD = dyn_cast<VarDecl>(DRE->getDecl())) {2958 switch (S.lookup(VD)) {2959 case FixitStrategy::Kind::Span: {2960 2961 // If the index has a negative constant value, we give up as no valid2962 // fix-it can be generated:2963 const ASTContext &Ctx = // FIXME: we need ASTContext to be passed in!2964 VD->getASTContext();2965 if (!isNonNegativeIntegerExpr(Node->getIdx(), VD, Ctx))2966 return std::nullopt;2967 // no-op is a good fix-it, otherwise2968 return FixItList{};2969 }2970 case FixitStrategy::Kind::Array:2971 return FixItList{};2972 case FixitStrategy::Kind::Wontfix:2973 case FixitStrategy::Kind::Iterator:2974 case FixitStrategy::Kind::Vector:2975 llvm_unreachable("unsupported strategies for FixableGadgets");2976 }2977 }2978 return std::nullopt;2979}2980 2981static std::optional<FixItList> // forward declaration2982fixUPCAddressofArraySubscriptWithSpan(const UnaryOperator *Node);2983 2984std::optional<FixItList>2985UPCAddressofArraySubscriptGadget::getFixits(const FixitStrategy &S) const {2986 auto DREs = getClaimedVarUseSites();2987 const auto *VD = cast<VarDecl>(DREs.front()->getDecl());2988 2989 switch (S.lookup(VD)) {2990 case FixitStrategy::Kind::Span:2991 return fixUPCAddressofArraySubscriptWithSpan(Node);2992 case FixitStrategy::Kind::Wontfix:2993 case FixitStrategy::Kind::Iterator:2994 case FixitStrategy::Kind::Array:2995 return std::nullopt;2996 case FixitStrategy::Kind::Vector:2997 llvm_unreachable("unsupported strategies for FixableGadgets");2998 }2999 return std::nullopt; // something went wrong, no fix-it3000}3001 3002// FIXME: this function should be customizable through format3003static StringRef getEndOfLine() {3004 static const char *const EOL = "\n";3005 return EOL;3006}3007 3008// Returns the text indicating that the user needs to provide input there:3009static std::string3010getUserFillPlaceHolder(StringRef HintTextToUser = "placeholder") {3011 std::string s = std::string("<# ");3012 s += HintTextToUser;3013 s += " #>";3014 return s;3015}3016 3017// Return the source location of the last character of the AST `Node`.3018template <typename NodeTy>3019static std::optional<SourceLocation>3020getEndCharLoc(const NodeTy *Node, const SourceManager &SM,3021 const LangOptions &LangOpts) {3022 if (unsigned TkLen =3023 Lexer::MeasureTokenLength(Node->getEndLoc(), SM, LangOpts)) {3024 SourceLocation Loc = Node->getEndLoc().getLocWithOffset(TkLen - 1);3025 3026 if (Loc.isValid())3027 return Loc;3028 }3029 return std::nullopt;3030}3031 3032// We cannot fix a variable declaration if it has some other specifiers than the3033// type specifier. Because the source ranges of those specifiers could overlap3034// with the source range that is being replaced using fix-its. Especially when3035// we often cannot obtain accurate source ranges of cv-qualified type3036// specifiers.3037// FIXME: also deal with type attributes3038static bool hasUnsupportedSpecifiers(const VarDecl *VD,3039 const SourceManager &SM) {3040 // AttrRangeOverlapping: true if at least one attribute of `VD` overlaps the3041 // source range of `VD`:3042 bool AttrRangeOverlapping = llvm::any_of(VD->attrs(), [&](Attr *At) -> bool {3043 return !(SM.isBeforeInTranslationUnit(At->getRange().getEnd(),3044 VD->getBeginLoc())) &&3045 !(SM.isBeforeInTranslationUnit(VD->getEndLoc(),3046 At->getRange().getBegin()));3047 });3048 return VD->isInlineSpecified() || VD->isConstexpr() ||3049 VD->hasConstantInitialization() || !VD->hasLocalStorage() ||3050 AttrRangeOverlapping;3051}3052 3053// Returns the `SourceRange` of `D`. The reason why this function exists is3054// that `D->getSourceRange()` may return a range where the end location is the3055// starting location of the last token. The end location of the source range3056// returned by this function is the last location of the last token.3057static SourceRange getSourceRangeToTokenEnd(const Decl *D,3058 const SourceManager &SM,3059 const LangOptions &LangOpts) {3060 SourceLocation Begin = D->getBeginLoc();3061 SourceLocation3062 End = // `D->getEndLoc` should always return the starting location of the3063 // last token, so we should get the end of the token3064 Lexer::getLocForEndOfToken(D->getEndLoc(), 0, SM, LangOpts);3065 3066 return SourceRange(Begin, End);3067}3068 3069// Returns the text of the name (with qualifiers) of a `FunctionDecl`.3070static std::optional<StringRef> getFunNameText(const FunctionDecl *FD,3071 const SourceManager &SM,3072 const LangOptions &LangOpts) {3073 SourceLocation BeginLoc = FD->getQualifier()3074 ? FD->getQualifierLoc().getBeginLoc()3075 : FD->getNameInfo().getBeginLoc();3076 // Note that `FD->getNameInfo().getEndLoc()` returns the begin location of the3077 // last token:3078 SourceLocation EndLoc = Lexer::getLocForEndOfToken(3079 FD->getNameInfo().getEndLoc(), 0, SM, LangOpts);3080 SourceRange NameRange{BeginLoc, EndLoc};3081 3082 return getRangeText(NameRange, SM, LangOpts);3083}3084 3085// Returns the text representing a `std::span` type where the element type is3086// represented by `EltTyText`.3087//3088// Note the optional parameter `Qualifiers`: one needs to pass qualifiers3089// explicitly if the element type needs to be qualified.3090static std::string3091getSpanTypeText(StringRef EltTyText,3092 std::optional<Qualifiers> Quals = std::nullopt) {3093 const char *const SpanOpen = "std::span<";3094 3095 if (Quals)3096 return SpanOpen + EltTyText.str() + ' ' + Quals->getAsString() + '>';3097 return SpanOpen + EltTyText.str() + '>';3098}3099 3100std::optional<FixItList>3101DerefSimplePtrArithFixableGadget::getFixits(const FixitStrategy &s) const {3102 const VarDecl *VD = dyn_cast<VarDecl>(BaseDeclRefExpr->getDecl());3103 3104 if (VD && s.lookup(VD) == FixitStrategy::Kind::Span) {3105 ASTContext &Ctx = VD->getASTContext();3106 // std::span can't represent elements before its begin()3107 if (auto ConstVal = Offset->getIntegerConstantExpr(Ctx))3108 if (ConstVal->isNegative())3109 return std::nullopt;3110 3111 // note that the expr may (oddly) has multiple layers of parens3112 // example:3113 // *((..(pointer + 123)..))3114 // goal:3115 // pointer[123]3116 // Fix-It:3117 // remove '*('3118 // replace ' + ' with '['3119 // replace ')' with ']'3120 3121 // example:3122 // *((..(123 + pointer)..))3123 // goal:3124 // 123[pointer]3125 // Fix-It:3126 // remove '*('3127 // replace ' + ' with '['3128 // replace ')' with ']'3129 3130 const Expr *LHS = AddOp->getLHS(), *RHS = AddOp->getRHS();3131 const SourceManager &SM = Ctx.getSourceManager();3132 const LangOptions &LangOpts = Ctx.getLangOpts();3133 CharSourceRange StarWithTrailWhitespace =3134 clang::CharSourceRange::getCharRange(DerefOp->getOperatorLoc(),3135 LHS->getBeginLoc());3136 3137 std::optional<SourceLocation> LHSLocation = getPastLoc(LHS, SM, LangOpts);3138 if (!LHSLocation)3139 return std::nullopt;3140 3141 CharSourceRange PlusWithSurroundingWhitespace =3142 clang::CharSourceRange::getCharRange(*LHSLocation, RHS->getBeginLoc());3143 3144 std::optional<SourceLocation> AddOpLocation =3145 getPastLoc(AddOp, SM, LangOpts);3146 std::optional<SourceLocation> DerefOpLocation =3147 getPastLoc(DerefOp, SM, LangOpts);3148 3149 if (!AddOpLocation || !DerefOpLocation)3150 return std::nullopt;3151 3152 CharSourceRange ClosingParenWithPrecWhitespace =3153 clang::CharSourceRange::getCharRange(*AddOpLocation, *DerefOpLocation);3154 3155 return FixItList{3156 {FixItHint::CreateRemoval(StarWithTrailWhitespace),3157 FixItHint::CreateReplacement(PlusWithSurroundingWhitespace, "["),3158 FixItHint::CreateReplacement(ClosingParenWithPrecWhitespace, "]")}};3159 }3160 return std::nullopt; // something wrong or unsupported, give up3161}3162 3163std::optional<FixItList>3164PointerDereferenceGadget::getFixits(const FixitStrategy &S) const {3165 const VarDecl *VD = cast<VarDecl>(BaseDeclRefExpr->getDecl());3166 switch (S.lookup(VD)) {3167 case FixitStrategy::Kind::Span: {3168 ASTContext &Ctx = VD->getASTContext();3169 SourceManager &SM = Ctx.getSourceManager();3170 // Required changes: *(ptr); => (ptr[0]); and *ptr; => ptr[0]3171 // Deletes the *operand3172 CharSourceRange derefRange = clang::CharSourceRange::getCharRange(3173 Op->getBeginLoc(), Op->getBeginLoc().getLocWithOffset(1));3174 // Inserts the [0]3175 if (auto LocPastOperand =3176 getPastLoc(BaseDeclRefExpr, SM, Ctx.getLangOpts())) {3177 return FixItList{{FixItHint::CreateRemoval(derefRange),3178 FixItHint::CreateInsertion(*LocPastOperand, "[0]")}};3179 }3180 break;3181 }3182 case FixitStrategy::Kind::Iterator:3183 case FixitStrategy::Kind::Array:3184 return std::nullopt;3185 case FixitStrategy::Kind::Vector:3186 llvm_unreachable("FixitStrategy not implemented yet!");3187 case FixitStrategy::Kind::Wontfix:3188 llvm_unreachable("Invalid strategy!");3189 }3190 3191 return std::nullopt;3192}3193 3194static inline std::optional<FixItList> createDataFixit(const ASTContext &Ctx,3195 const DeclRefExpr *DRE) {3196 const SourceManager &SM = Ctx.getSourceManager();3197 // Inserts the .data() after the DRE3198 std::optional<SourceLocation> EndOfOperand =3199 getPastLoc(DRE, SM, Ctx.getLangOpts());3200 3201 if (EndOfOperand)3202 return FixItList{{FixItHint::CreateInsertion(*EndOfOperand, ".data()")}};3203 3204 return std::nullopt;3205}3206 3207// Generates fix-its replacing an expression of the form UPC(DRE) with3208// `DRE.data()`3209std::optional<FixItList>3210UPCStandalonePointerGadget::getFixits(const FixitStrategy &S) const {3211 const auto VD = cast<VarDecl>(Node->getDecl());3212 switch (S.lookup(VD)) {3213 case FixitStrategy::Kind::Array:3214 case FixitStrategy::Kind::Span: {3215 return createDataFixit(VD->getASTContext(), Node);3216 // FIXME: Points inside a macro expansion.3217 break;3218 }3219 case FixitStrategy::Kind::Wontfix:3220 case FixitStrategy::Kind::Iterator:3221 return std::nullopt;3222 case FixitStrategy::Kind::Vector:3223 llvm_unreachable("unsupported strategies for FixableGadgets");3224 }3225 3226 return std::nullopt;3227}3228 3229// Generates fix-its replacing an expression of the form `&DRE[e]` with3230// `&DRE.data()[e]`:3231static std::optional<FixItList>3232fixUPCAddressofArraySubscriptWithSpan(const UnaryOperator *Node) {3233 const auto *ArraySub = cast<ArraySubscriptExpr>(Node->getSubExpr());3234 const auto *DRE = cast<DeclRefExpr>(ArraySub->getBase()->IgnoreImpCasts());3235 // FIXME: this `getASTContext` call is costly, we should pass the3236 // ASTContext in:3237 const ASTContext &Ctx = DRE->getDecl()->getASTContext();3238 const Expr *Idx = ArraySub->getIdx();3239 const SourceManager &SM = Ctx.getSourceManager();3240 const LangOptions &LangOpts = Ctx.getLangOpts();3241 std::stringstream SS;3242 bool IdxIsLitZero = false;3243 3244 if (auto ICE = Idx->getIntegerConstantExpr(Ctx))3245 if ((*ICE).isZero())3246 IdxIsLitZero = true;3247 std::optional<StringRef> DreString = getExprText(DRE, SM, LangOpts);3248 if (!DreString)3249 return std::nullopt;3250 3251 if (IdxIsLitZero) {3252 // If the index is literal zero, we produce the most concise fix-it:3253 SS << (*DreString).str() << ".data()";3254 } else {3255 std::optional<StringRef> IndexString = getExprText(Idx, SM, LangOpts);3256 if (!IndexString)3257 return std::nullopt;3258 3259 SS << "&" << (*DreString).str() << ".data()"3260 << "[" << (*IndexString).str() << "]";3261 }3262 return FixItList{3263 FixItHint::CreateReplacement(Node->getSourceRange(), SS.str())};3264}3265 3266std::optional<FixItList>3267UUCAddAssignGadget::getFixits(const FixitStrategy &S) const {3268 DeclUseList DREs = getClaimedVarUseSites();3269 3270 if (DREs.size() != 1)3271 return std::nullopt; // In cases of `Ptr += n` where `Ptr` is not a DRE, we3272 // give up3273 if (const VarDecl *VD = dyn_cast<VarDecl>(DREs.front()->getDecl())) {3274 if (S.lookup(VD) == FixitStrategy::Kind::Span) {3275 FixItList Fixes;3276 3277 const Stmt *AddAssignNode = Node;3278 StringRef varName = VD->getName();3279 const ASTContext &Ctx = VD->getASTContext();3280 3281 if (!isNonNegativeIntegerExpr(Offset, VD, Ctx))3282 return std::nullopt;3283 3284 // To transform UUC(p += n) to UUC(p = p.subspan(..)):3285 bool NotParenExpr =3286 (Offset->IgnoreParens()->getBeginLoc() == Offset->getBeginLoc());3287 std::string SS = varName.str() + " = " + varName.str() + ".subspan";3288 if (NotParenExpr)3289 SS += "(";3290 3291 std::optional<SourceLocation> AddAssignLocation = getEndCharLoc(3292 AddAssignNode, Ctx.getSourceManager(), Ctx.getLangOpts());3293 if (!AddAssignLocation)3294 return std::nullopt;3295 3296 Fixes.push_back(FixItHint::CreateReplacement(3297 SourceRange(AddAssignNode->getBeginLoc(), Node->getOperatorLoc()),3298 SS));3299 if (NotParenExpr)3300 Fixes.push_back(FixItHint::CreateInsertion(3301 Offset->getEndLoc().getLocWithOffset(1), ")"));3302 return Fixes;3303 }3304 }3305 return std::nullopt; // Not in the cases that we can handle for now, give up.3306}3307 3308std::optional<FixItList>3309UPCPreIncrementGadget::getFixits(const FixitStrategy &S) const {3310 DeclUseList DREs = getClaimedVarUseSites();3311 3312 if (DREs.size() != 1)3313 return std::nullopt; // In cases of `++Ptr` where `Ptr` is not a DRE, we3314 // give up3315 if (const VarDecl *VD = dyn_cast<VarDecl>(DREs.front()->getDecl())) {3316 if (S.lookup(VD) == FixitStrategy::Kind::Span) {3317 FixItList Fixes;3318 std::stringstream SS;3319 StringRef varName = VD->getName();3320 const ASTContext &Ctx = VD->getASTContext();3321 3322 // To transform UPC(++p) to UPC((p = p.subspan(1)).data()):3323 SS << "(" << varName.data() << " = " << varName.data()3324 << ".subspan(1)).data()";3325 std::optional<SourceLocation> PreIncLocation =3326 getEndCharLoc(Node, Ctx.getSourceManager(), Ctx.getLangOpts());3327 if (!PreIncLocation)3328 return std::nullopt;3329 3330 Fixes.push_back(FixItHint::CreateReplacement(3331 SourceRange(Node->getBeginLoc(), *PreIncLocation), SS.str()));3332 return Fixes;3333 }3334 }3335 return std::nullopt; // Not in the cases that we can handle for now, give up.3336}3337 3338// For a non-null initializer `Init` of `T *` type, this function returns3339// `FixItHint`s producing a list initializer `{Init, S}` as a part of a fix-it3340// to output stream.3341// In many cases, this function cannot figure out the actual extent `S`. It3342// then will use a place holder to replace `S` to ask users to fill `S` in. The3343// initializer shall be used to initialize a variable of type `std::span<T>`.3344// In some cases (e. g. constant size array) the initializer should remain3345// unchanged and the function returns empty list. In case the function can't3346// provide the right fixit it will return nullopt.3347//3348// FIXME: Support multi-level pointers3349//3350// Parameters:3351// `Init` a pointer to the initializer expression3352// `Ctx` a reference to the ASTContext3353static std::optional<FixItList>3354FixVarInitializerWithSpan(const Expr *Init, ASTContext &Ctx,3355 const StringRef UserFillPlaceHolder) {3356 const SourceManager &SM = Ctx.getSourceManager();3357 const LangOptions &LangOpts = Ctx.getLangOpts();3358 3359 // If `Init` has a constant value that is (or equivalent to) a3360 // NULL pointer, we use the default constructor to initialize the span3361 // object, i.e., a `std:span` variable declaration with no initializer.3362 // So the fix-it is just to remove the initializer.3363 if (Init->isNullPointerConstant(3364 Ctx,3365 // FIXME: Why does this function not ask for `const ASTContext3366 // &`? It should. Maybe worth an NFC patch later.3367 Expr::NullPointerConstantValueDependence::3368 NPC_ValueDependentIsNotNull)) {3369 std::optional<SourceLocation> InitLocation =3370 getEndCharLoc(Init, SM, LangOpts);3371 if (!InitLocation)3372 return std::nullopt;3373 3374 SourceRange SR(Init->getBeginLoc(), *InitLocation);3375 3376 return FixItList{FixItHint::CreateRemoval(SR)};3377 }3378 3379 FixItList FixIts{};3380 std::string ExtentText = UserFillPlaceHolder.data();3381 StringRef One = "1";3382 3383 // Insert `{` before `Init`:3384 FixIts.push_back(FixItHint::CreateInsertion(Init->getBeginLoc(), "{"));3385 // Try to get the data extent. Break into different cases:3386 if (auto CxxNew = dyn_cast<CXXNewExpr>(Init->IgnoreImpCasts())) {3387 // In cases `Init` is `new T[n]` and there is no explicit cast over3388 // `Init`, we know that `Init` must evaluates to a pointer to `n` objects3389 // of `T`. So the extent is `n` unless `n` has side effects. Similar but3390 // simpler for the case where `Init` is `new T`.3391 if (const Expr *Ext = CxxNew->getArraySize().value_or(nullptr)) {3392 if (!Ext->HasSideEffects(Ctx)) {3393 std::optional<StringRef> ExtentString = getExprText(Ext, SM, LangOpts);3394 if (!ExtentString)3395 return std::nullopt;3396 ExtentText = *ExtentString;3397 }3398 } else if (!CxxNew->isArray())3399 // Although the initializer is not allocating a buffer, the pointer3400 // variable could still be used in buffer access operations.3401 ExtentText = One;3402 } else if (Ctx.getAsConstantArrayType(Init->IgnoreImpCasts()->getType())) {3403 // std::span has a single parameter constructor for initialization with3404 // constant size array. The size is auto-deduced as the constructor is a3405 // function template. The correct fixit is empty - no changes should happen.3406 return FixItList{};3407 } else {3408 // In cases `Init` is of the form `&Var` after stripping of implicit3409 // casts, where `&` is the built-in operator, the extent is 1.3410 if (auto AddrOfExpr = dyn_cast<UnaryOperator>(Init->IgnoreImpCasts()))3411 if (AddrOfExpr->getOpcode() == UnaryOperatorKind::UO_AddrOf &&3412 isa_and_present<DeclRefExpr>(AddrOfExpr->getSubExpr()))3413 ExtentText = One;3414 // TODO: we can handle more cases, e.g., `&a[0]`, `&a`, `std::addressof`,3415 // and explicit casting, etc. etc.3416 }3417 3418 SmallString<32> StrBuffer{};3419 std::optional<SourceLocation> LocPassInit = getPastLoc(Init, SM, LangOpts);3420 3421 if (!LocPassInit)3422 return std::nullopt;3423 3424 StrBuffer.append(", ");3425 StrBuffer.append(ExtentText);3426 StrBuffer.append("}");3427 FixIts.push_back(FixItHint::CreateInsertion(*LocPassInit, StrBuffer.str()));3428 return FixIts;3429}3430 3431#ifndef NDEBUG3432#define DEBUG_NOTE_DECL_FAIL(D, Msg) \3433 Handler.addDebugNoteForVar((D), (D)->getBeginLoc(), \3434 "failed to produce fixit for declaration '" + \3435 (D)->getNameAsString() + "'" + (Msg))3436#else3437#define DEBUG_NOTE_DECL_FAIL(D, Msg)3438#endif3439 3440// For the given variable declaration with a pointer-to-T type, returns the text3441// `std::span<T>`. If it is unable to generate the text, returns3442// `std::nullopt`.3443static std::optional<std::string>3444createSpanTypeForVarDecl(const VarDecl *VD, const ASTContext &Ctx) {3445 assert(VD->getType()->isPointerType());3446 3447 std::optional<Qualifiers> PteTyQualifiers = std::nullopt;3448 std::optional<std::string> PteTyText = getPointeeTypeText(3449 VD, Ctx.getSourceManager(), Ctx.getLangOpts(), &PteTyQualifiers);3450 3451 if (!PteTyText)3452 return std::nullopt;3453 3454 std::string SpanTyText = "std::span<";3455 3456 SpanTyText.append(*PteTyText);3457 // Append qualifiers to span element type if any:3458 if (PteTyQualifiers) {3459 SpanTyText.append(" ");3460 SpanTyText.append(PteTyQualifiers->getAsString());3461 }3462 SpanTyText.append(">");3463 return SpanTyText;3464}3465 3466// For a `VarDecl` of the form `T * var (= Init)?`, this3467// function generates fix-its that3468// 1) replace `T * var` with `std::span<T> var`; and3469// 2) change `Init` accordingly to a span constructor, if it exists.3470//3471// FIXME: support Multi-level pointers3472//3473// Parameters:3474// `D` a pointer the variable declaration node3475// `Ctx` a reference to the ASTContext3476// `UserFillPlaceHolder` the user-input placeholder text3477// Returns:3478// the non-empty fix-it list, if fix-its are successfuly generated; empty3479// list otherwise.3480static FixItList fixLocalVarDeclWithSpan(const VarDecl *D, ASTContext &Ctx,3481 const StringRef UserFillPlaceHolder,3482 UnsafeBufferUsageHandler &Handler) {3483 if (hasUnsupportedSpecifiers(D, Ctx.getSourceManager()))3484 return {};3485 3486 FixItList FixIts{};3487 std::optional<std::string> SpanTyText = createSpanTypeForVarDecl(D, Ctx);3488 3489 if (!SpanTyText) {3490 DEBUG_NOTE_DECL_FAIL(D, " : failed to generate 'std::span' type");3491 return {};3492 }3493 3494 // Will hold the text for `std::span<T> Ident`:3495 std::stringstream SS;3496 3497 SS << *SpanTyText;3498 // Fix the initializer if it exists:3499 if (const Expr *Init = D->getInit()) {3500 std::optional<FixItList> InitFixIts =3501 FixVarInitializerWithSpan(Init, Ctx, UserFillPlaceHolder);3502 if (!InitFixIts)3503 return {};3504 FixIts.insert(FixIts.end(), std::make_move_iterator(InitFixIts->begin()),3505 std::make_move_iterator(InitFixIts->end()));3506 }3507 // For declaration of the form `T * ident = init;`, we want to replace3508 // `T * ` with `std::span<T>`.3509 // We ignore CV-qualifiers so for `T * const ident;` we also want to replace3510 // just `T *` with `std::span<T>`.3511 const SourceLocation EndLocForReplacement = D->getTypeSpecEndLoc();3512 if (!EndLocForReplacement.isValid()) {3513 DEBUG_NOTE_DECL_FAIL(D, " : failed to locate the end of the declaration");3514 return {};3515 }3516 // The only exception is that for `T *ident` we'll add a single space between3517 // "std::span<T>" and "ident".3518 // FIXME: The condition is false for identifiers expended from macros.3519 if (EndLocForReplacement.getLocWithOffset(1) == getVarDeclIdentifierLoc(D))3520 SS << " ";3521 3522 FixIts.push_back(FixItHint::CreateReplacement(3523 SourceRange(D->getBeginLoc(), EndLocForReplacement), SS.str()));3524 return FixIts;3525}3526 3527static bool hasConflictingOverload(const FunctionDecl *FD) {3528 return !FD->getDeclContext()->lookup(FD->getDeclName()).isSingleResult();3529}3530 3531// For a `FunctionDecl`, whose `ParmVarDecl`s are being changed to have new3532// types, this function produces fix-its to make the change self-contained. Let3533// 'F' be the entity defined by the original `FunctionDecl` and "NewF" be the3534// entity defined by the `FunctionDecl` after the change to the parameters.3535// Fix-its produced by this function are3536// 1. Add the `[[clang::unsafe_buffer_usage]]` attribute to each declaration3537// of 'F';3538// 2. Create a declaration of "NewF" next to each declaration of `F`;3539// 3. Create a definition of "F" (as its' original definition is now belongs3540// to "NewF") next to its original definition. The body of the creating3541// definition calls to "NewF".3542//3543// Example:3544//3545// void f(int *p); // original declaration3546// void f(int *p) { // original definition3547// p[5];3548// }3549//3550// To change the parameter `p` to be of `std::span<int>` type, we3551// also add overloads:3552//3553// [[clang::unsafe_buffer_usage]] void f(int *p); // original decl3554// void f(std::span<int> p); // added overload decl3555// void f(std::span<int> p) { // original def where param is changed3556// p[5];3557// }3558// [[clang::unsafe_buffer_usage]] void f(int *p) { // added def3559// return f(std::span(p, <# size #>));3560// }3561//3562static std::optional<FixItList>3563createOverloadsForFixedParams(const FixitStrategy &S, const FunctionDecl *FD,3564 const ASTContext &Ctx,3565 UnsafeBufferUsageHandler &Handler) {3566 // FIXME: need to make this conflict checking better:3567 if (hasConflictingOverload(FD))3568 return std::nullopt;3569 3570 const SourceManager &SM = Ctx.getSourceManager();3571 const LangOptions &LangOpts = Ctx.getLangOpts();3572 const unsigned NumParms = FD->getNumParams();3573 std::vector<std::string> NewTysTexts(NumParms);3574 std::vector<bool> ParmsMask(NumParms, false);3575 bool AtLeastOneParmToFix = false;3576 3577 for (unsigned i = 0; i < NumParms; i++) {3578 const ParmVarDecl *PVD = FD->getParamDecl(i);3579 3580 if (S.lookup(PVD) == FixitStrategy::Kind::Wontfix)3581 continue;3582 if (S.lookup(PVD) != FixitStrategy::Kind::Span)3583 // Not supported, not suppose to happen:3584 return std::nullopt;3585 3586 std::optional<Qualifiers> PteTyQuals = std::nullopt;3587 std::optional<std::string> PteTyText =3588 getPointeeTypeText(PVD, SM, LangOpts, &PteTyQuals);3589 3590 if (!PteTyText)3591 // something wrong in obtaining the text of the pointee type, give up3592 return std::nullopt;3593 // FIXME: whether we should create std::span type depends on the3594 // FixitStrategy.3595 NewTysTexts[i] = getSpanTypeText(*PteTyText, PteTyQuals);3596 ParmsMask[i] = true;3597 AtLeastOneParmToFix = true;3598 }3599 if (!AtLeastOneParmToFix)3600 // No need to create function overloads:3601 return {};3602 // FIXME Respect indentation of the original code.3603 3604 // A lambda that creates the text representation of a function declaration3605 // with the new type signatures:3606 const auto NewOverloadSignatureCreator =3607 [&SM, &LangOpts, &NewTysTexts,3608 &ParmsMask](const FunctionDecl *FD) -> std::optional<std::string> {3609 std::stringstream SS;3610 3611 SS << ";";3612 SS << getEndOfLine().str();3613 // Append: ret-type func-name "("3614 if (auto Prefix = getRangeText(3615 SourceRange(FD->getBeginLoc(), (*FD->param_begin())->getBeginLoc()),3616 SM, LangOpts))3617 SS << Prefix->str();3618 else3619 return std::nullopt; // give up3620 // Append: parameter-type-list3621 const unsigned NumParms = FD->getNumParams();3622 3623 for (unsigned i = 0; i < NumParms; i++) {3624 const ParmVarDecl *Parm = FD->getParamDecl(i);3625 3626 if (Parm->isImplicit())3627 continue;3628 if (ParmsMask[i]) {3629 // This `i`-th parameter will be fixed with `NewTysTexts[i]` being its3630 // new type:3631 SS << NewTysTexts[i];3632 // print parameter name if provided:3633 if (IdentifierInfo *II = Parm->getIdentifier())3634 SS << ' ' << II->getName().str();3635 } else if (auto ParmTypeText =3636 getRangeText(getSourceRangeToTokenEnd(Parm, SM, LangOpts),3637 SM, LangOpts)) {3638 // print the whole `Parm` without modification:3639 SS << ParmTypeText->str();3640 } else3641 return std::nullopt; // something wrong, give up3642 if (i != NumParms - 1)3643 SS << ", ";3644 }3645 SS << ")";3646 return SS.str();3647 };3648 3649 // A lambda that creates the text representation of a function definition with3650 // the original signature:3651 const auto OldOverloadDefCreator =3652 [&Handler, &SM, &LangOpts, &NewTysTexts,3653 &ParmsMask](const FunctionDecl *FD) -> std::optional<std::string> {3654 std::stringstream SS;3655 3656 SS << getEndOfLine().str();3657 // Append: attr-name ret-type func-name "(" param-list ")" "{"3658 if (auto FDPrefix = getRangeText(3659 SourceRange(FD->getBeginLoc(), FD->getBody()->getBeginLoc()), SM,3660 LangOpts))3661 SS << Handler.getUnsafeBufferUsageAttributeTextAt(FD->getBeginLoc(), " ")3662 << FDPrefix->str() << "{";3663 else3664 return std::nullopt;3665 // Append: "return" func-name "("3666 if (auto FunQualName = getFunNameText(FD, SM, LangOpts))3667 SS << "return " << FunQualName->str() << "(";3668 else3669 return std::nullopt;3670 3671 // Append: arg-list3672 const unsigned NumParms = FD->getNumParams();3673 for (unsigned i = 0; i < NumParms; i++) {3674 const ParmVarDecl *Parm = FD->getParamDecl(i);3675 3676 if (Parm->isImplicit())3677 continue;3678 // FIXME: If a parameter has no name, it is unused in the3679 // definition. So we could just leave it as it is.3680 if (!Parm->getIdentifier())3681 // If a parameter of a function definition has no name:3682 return std::nullopt;3683 if (ParmsMask[i])3684 // This is our spanified paramter!3685 SS << NewTysTexts[i] << "(" << Parm->getIdentifier()->getName().str()3686 << ", " << getUserFillPlaceHolder("size") << ")";3687 else3688 SS << Parm->getIdentifier()->getName().str();3689 if (i != NumParms - 1)3690 SS << ", ";3691 }3692 // finish call and the body3693 SS << ");}" << getEndOfLine().str();3694 // FIXME: 80-char line formatting?3695 return SS.str();3696 };3697 3698 FixItList FixIts{};3699 for (FunctionDecl *FReDecl : FD->redecls()) {3700 std::optional<SourceLocation> Loc = getPastLoc(FReDecl, SM, LangOpts);3701 3702 if (!Loc)3703 return {};3704 if (FReDecl->isThisDeclarationADefinition()) {3705 assert(FReDecl == FD && "inconsistent function definition");3706 // Inserts a definition with the old signature to the end of3707 // `FReDecl`:3708 if (auto OldOverloadDef = OldOverloadDefCreator(FReDecl))3709 FixIts.emplace_back(FixItHint::CreateInsertion(*Loc, *OldOverloadDef));3710 else3711 return {}; // give up3712 } else {3713 // Adds the unsafe-buffer attribute (if not already there) to `FReDecl`:3714 if (!FReDecl->hasAttr<UnsafeBufferUsageAttr>()) {3715 FixIts.emplace_back(FixItHint::CreateInsertion(3716 FReDecl->getBeginLoc(), Handler.getUnsafeBufferUsageAttributeTextAt(3717 FReDecl->getBeginLoc(), " ")));3718 }3719 // Inserts a declaration with the new signature to the end of `FReDecl`:3720 if (auto NewOverloadDecl = NewOverloadSignatureCreator(FReDecl))3721 FixIts.emplace_back(FixItHint::CreateInsertion(*Loc, *NewOverloadDecl));3722 else3723 return {};3724 }3725 }3726 return FixIts;3727}3728 3729// To fix a `ParmVarDecl` to be of `std::span` type.3730static FixItList fixParamWithSpan(const ParmVarDecl *PVD, const ASTContext &Ctx,3731 UnsafeBufferUsageHandler &Handler) {3732 if (hasUnsupportedSpecifiers(PVD, Ctx.getSourceManager())) {3733 DEBUG_NOTE_DECL_FAIL(PVD, " : has unsupport specifier(s)");3734 return {};3735 }3736 if (PVD->hasDefaultArg()) {3737 // FIXME: generate fix-its for default values:3738 DEBUG_NOTE_DECL_FAIL(PVD, " : has default arg");3739 return {};3740 }3741 3742 std::optional<Qualifiers> PteTyQualifiers = std::nullopt;3743 std::optional<std::string> PteTyText = getPointeeTypeText(3744 PVD, Ctx.getSourceManager(), Ctx.getLangOpts(), &PteTyQualifiers);3745 3746 if (!PteTyText) {3747 DEBUG_NOTE_DECL_FAIL(PVD, " : invalid pointee type");3748 return {};3749 }3750 3751 std::optional<StringRef> PVDNameText = PVD->getIdentifier()->getName();3752 3753 if (!PVDNameText) {3754 DEBUG_NOTE_DECL_FAIL(PVD, " : invalid identifier name");3755 return {};3756 }3757 3758 std::stringstream SS;3759 std::optional<std::string> SpanTyText = createSpanTypeForVarDecl(PVD, Ctx);3760 3761 if (PteTyQualifiers)3762 // Append qualifiers if they exist:3763 SS << getSpanTypeText(*PteTyText, PteTyQualifiers);3764 else3765 SS << getSpanTypeText(*PteTyText);3766 // Append qualifiers to the type of the parameter:3767 if (PVD->getType().hasQualifiers())3768 SS << ' ' << PVD->getType().getQualifiers().getAsString();3769 // Append parameter's name:3770 SS << ' ' << PVDNameText->str();3771 // Add replacement fix-it:3772 return {FixItHint::CreateReplacement(PVD->getSourceRange(), SS.str())};3773}3774 3775static FixItList fixVariableWithSpan(const VarDecl *VD,3776 const DeclUseTracker &Tracker,3777 ASTContext &Ctx,3778 UnsafeBufferUsageHandler &Handler) {3779 const DeclStmt *DS = Tracker.lookupDecl(VD);3780 if (!DS) {3781 DEBUG_NOTE_DECL_FAIL(VD,3782 " : variables declared this way not implemented yet");3783 return {};3784 }3785 if (!DS->isSingleDecl()) {3786 // FIXME: to support handling multiple `VarDecl`s in a single `DeclStmt`3787 DEBUG_NOTE_DECL_FAIL(VD, " : multiple VarDecls");3788 return {};3789 }3790 // Currently DS is an unused variable but we'll need it when3791 // non-single decls are implemented, where the pointee type name3792 // and the '*' are spread around the place.3793 (void)DS;3794 3795 // FIXME: handle cases where DS has multiple declarations3796 return fixLocalVarDeclWithSpan(VD, Ctx, getUserFillPlaceHolder(), Handler);3797}3798 3799static FixItList fixVarDeclWithArray(const VarDecl *D, const ASTContext &Ctx,3800 UnsafeBufferUsageHandler &Handler) {3801 FixItList FixIts{};3802 3803 // Note: the code below expects the declaration to not use any type sugar like3804 // typedef.3805 if (auto CAT = Ctx.getAsConstantArrayType(D->getType())) {3806 const QualType &ArrayEltT = CAT->getElementType();3807 assert(!ArrayEltT.isNull() && "Trying to fix a non-array type variable!");3808 // FIXME: support multi-dimensional arrays3809 if (isa<clang::ArrayType>(ArrayEltT.getCanonicalType()))3810 return {};3811 3812 const SourceLocation IdentifierLoc = getVarDeclIdentifierLoc(D);3813 3814 // Get the spelling of the element type as written in the source file3815 // (including macros, etc.).3816 auto MaybeElemTypeTxt =3817 getRangeText({D->getBeginLoc(), IdentifierLoc}, Ctx.getSourceManager(),3818 Ctx.getLangOpts());3819 if (!MaybeElemTypeTxt)3820 return {};3821 const llvm::StringRef ElemTypeTxt = MaybeElemTypeTxt->trim();3822 3823 // Find the '[' token.3824 std::optional<Token> NextTok = Lexer::findNextToken(3825 IdentifierLoc, Ctx.getSourceManager(), Ctx.getLangOpts());3826 while (NextTok && !NextTok->is(tok::l_square) &&3827 NextTok->getLocation() <= D->getSourceRange().getEnd())3828 NextTok = Lexer::findNextToken(NextTok->getLocation(),3829 Ctx.getSourceManager(), Ctx.getLangOpts());3830 if (!NextTok)3831 return {};3832 const SourceLocation LSqBracketLoc = NextTok->getLocation();3833 3834 // Get the spelling of the array size as written in the source file3835 // (including macros, etc.).3836 auto MaybeArraySizeTxt = getRangeText(3837 {LSqBracketLoc.getLocWithOffset(1), D->getTypeSpecEndLoc()},3838 Ctx.getSourceManager(), Ctx.getLangOpts());3839 if (!MaybeArraySizeTxt)3840 return {};3841 const llvm::StringRef ArraySizeTxt = MaybeArraySizeTxt->trim();3842 if (ArraySizeTxt.empty()) {3843 // FIXME: Support array size getting determined from the initializer.3844 // Examples:3845 // int arr1[] = {0, 1, 2};3846 // int arr2{3, 4, 5};3847 // We might be able to preserve the non-specified size with `auto` and3848 // `std::to_array`:3849 // auto arr1 = std::to_array<int>({0, 1, 2});3850 return {};3851 }3852 3853 std::optional<StringRef> IdentText =3854 getVarDeclIdentifierText(D, Ctx.getSourceManager(), Ctx.getLangOpts());3855 3856 if (!IdentText) {3857 DEBUG_NOTE_DECL_FAIL(D, " : failed to locate the identifier");3858 return {};3859 }3860 3861 SmallString<32> Replacement;3862 llvm::raw_svector_ostream OS(Replacement);3863 OS << "std::array<" << ElemTypeTxt << ", " << ArraySizeTxt << "> "3864 << IdentText->str();3865 3866 FixIts.push_back(FixItHint::CreateReplacement(3867 SourceRange{D->getBeginLoc(), D->getTypeSpecEndLoc()}, OS.str()));3868 }3869 3870 return FixIts;3871}3872 3873static FixItList fixVariableWithArray(const VarDecl *VD,3874 const DeclUseTracker &Tracker,3875 const ASTContext &Ctx,3876 UnsafeBufferUsageHandler &Handler) {3877 const DeclStmt *DS = Tracker.lookupDecl(VD);3878 assert(DS && "Fixing non-local variables not implemented yet!");3879 if (!DS->isSingleDecl()) {3880 // FIXME: to support handling multiple `VarDecl`s in a single `DeclStmt`3881 return {};3882 }3883 // Currently DS is an unused variable but we'll need it when3884 // non-single decls are implemented, where the pointee type name3885 // and the '*' are spread around the place.3886 (void)DS;3887 3888 // FIXME: handle cases where DS has multiple declarations3889 return fixVarDeclWithArray(VD, Ctx, Handler);3890}3891 3892// TODO: we should be consistent to use `std::nullopt` to represent no-fix due3893// to any unexpected problem.3894static FixItList3895fixVariable(const VarDecl *VD, FixitStrategy::Kind K,3896 /* The function decl under analysis */ const Decl *D,3897 const DeclUseTracker &Tracker, ASTContext &Ctx,3898 UnsafeBufferUsageHandler &Handler) {3899 if (const auto *PVD = dyn_cast<ParmVarDecl>(VD)) {3900 auto *FD = dyn_cast<clang::FunctionDecl>(PVD->getDeclContext());3901 if (!FD || FD != D) {3902 // `FD != D` means that `PVD` belongs to a function that is not being3903 // analyzed currently. Thus `FD` may not be complete.3904 DEBUG_NOTE_DECL_FAIL(VD, " : function not currently analyzed");3905 return {};3906 }3907 3908 // TODO If function has a try block we can't change params unless we check3909 // also its catch block for their use.3910 // FIXME We might support static class methods, some select methods,3911 // operators and possibly lamdas.3912 if (FD->isMain() || FD->isConstexpr() ||3913 FD->getTemplatedKind() != FunctionDecl::TemplatedKind::TK_NonTemplate ||3914 FD->isVariadic() ||3915 // also covers call-operator of lamdas3916 isa<CXXMethodDecl>(FD) ||3917 // skip when the function body is a try-block3918 (FD->hasBody() && isa<CXXTryStmt>(FD->getBody())) ||3919 FD->isOverloadedOperator()) {3920 DEBUG_NOTE_DECL_FAIL(VD, " : unsupported function decl");3921 return {}; // TODO test all these cases3922 }3923 }3924 3925 switch (K) {3926 case FixitStrategy::Kind::Span: {3927 if (VD->getType()->isPointerType()) {3928 if (const auto *PVD = dyn_cast<ParmVarDecl>(VD))3929 return fixParamWithSpan(PVD, Ctx, Handler);3930 3931 if (VD->isLocalVarDecl())3932 return fixVariableWithSpan(VD, Tracker, Ctx, Handler);3933 }3934 DEBUG_NOTE_DECL_FAIL(VD, " : not a pointer");3935 return {};3936 }3937 case FixitStrategy::Kind::Array: {3938 if (VD->isLocalVarDecl() && Ctx.getAsConstantArrayType(VD->getType()))3939 return fixVariableWithArray(VD, Tracker, Ctx, Handler);3940 3941 DEBUG_NOTE_DECL_FAIL(VD, " : not a local const-size array");3942 return {};3943 }3944 case FixitStrategy::Kind::Iterator:3945 case FixitStrategy::Kind::Vector:3946 llvm_unreachable("FixitStrategy not implemented yet!");3947 case FixitStrategy::Kind::Wontfix:3948 llvm_unreachable("Invalid strategy!");3949 }3950 llvm_unreachable("Unknown strategy!");3951}3952 3953// Returns true iff there exists a `FixItHint` 'h' in `FixIts` such that the3954// `RemoveRange` of 'h' overlaps with a macro use.3955static bool overlapWithMacro(const FixItList &FixIts) {3956 // FIXME: For now we only check if the range (or the first token) is (part of)3957 // a macro expansion. Ideally, we want to check for all tokens in the range.3958 return llvm::any_of(FixIts, [](const FixItHint &Hint) {3959 auto Range = Hint.RemoveRange;3960 if (Range.getBegin().isMacroID() || Range.getEnd().isMacroID())3961 // If the range (or the first token) is (part of) a macro expansion:3962 return true;3963 return false;3964 });3965}3966 3967// Returns true iff `VD` is a parameter of the declaration `D`:3968static bool isParameterOf(const VarDecl *VD, const Decl *D) {3969 return isa<ParmVarDecl>(VD) &&3970 VD->getDeclContext() == dyn_cast<DeclContext>(D);3971}3972 3973// Erases variables in `FixItsForVariable`, if such a variable has an unfixable3974// group mate. A variable `v` is unfixable iff `FixItsForVariable` does not3975// contain `v`.3976static void eraseVarsForUnfixableGroupMates(3977 std::map<const VarDecl *, FixItList> &FixItsForVariable,3978 const VariableGroupsManager &VarGrpMgr) {3979 // Variables will be removed from `FixItsForVariable`:3980 SmallVector<const VarDecl *, 8> ToErase;3981 3982 for (const auto &[VD, Ignore] : FixItsForVariable) {3983 VarGrpRef Grp = VarGrpMgr.getGroupOfVar(VD);3984 if (llvm::any_of(Grp,3985 [&FixItsForVariable](const VarDecl *GrpMember) -> bool {3986 return !FixItsForVariable.count(GrpMember);3987 })) {3988 // At least one group member cannot be fixed, so we have to erase the3989 // whole group:3990 for (const VarDecl *Member : Grp)3991 ToErase.push_back(Member);3992 }3993 }3994 for (auto *VarToErase : ToErase)3995 FixItsForVariable.erase(VarToErase);3996}3997 3998// Returns the fix-its that create bounds-safe function overloads for the3999// function `D`, if `D`'s parameters will be changed to safe-types through4000// fix-its in `FixItsForVariable`.4001//4002// NOTE: In case `D`'s parameters will be changed but bounds-safe function4003// overloads cannot created, the whole group that contains the parameters will4004// be erased from `FixItsForVariable`.4005static FixItList createFunctionOverloadsForParms(4006 std::map<const VarDecl *, FixItList> &FixItsForVariable /* mutable */,4007 const VariableGroupsManager &VarGrpMgr, const FunctionDecl *FD,4008 const FixitStrategy &S, ASTContext &Ctx,4009 UnsafeBufferUsageHandler &Handler) {4010 FixItList FixItsSharedByParms{};4011 4012 std::optional<FixItList> OverloadFixes =4013 createOverloadsForFixedParams(S, FD, Ctx, Handler);4014 4015 if (OverloadFixes) {4016 FixItsSharedByParms.append(*OverloadFixes);4017 } else {4018 // Something wrong in generating `OverloadFixes`, need to remove the4019 // whole group, where parameters are in, from `FixItsForVariable` (Note4020 // that all parameters should be in the same group):4021 for (auto *Member : VarGrpMgr.getGroupOfParms())4022 FixItsForVariable.erase(Member);4023 }4024 return FixItsSharedByParms;4025}4026 4027// Constructs self-contained fix-its for each variable in `FixablesForAllVars`.4028static std::map<const VarDecl *, FixItList>4029getFixIts(FixableGadgetSets &FixablesForAllVars, const FixitStrategy &S,4030 ASTContext &Ctx,4031 /* The function decl under analysis */ const Decl *D,4032 const DeclUseTracker &Tracker, UnsafeBufferUsageHandler &Handler,4033 const VariableGroupsManager &VarGrpMgr) {4034 // `FixItsForVariable` will map each variable to a set of fix-its directly4035 // associated to the variable itself. Fix-its of distinct variables in4036 // `FixItsForVariable` are disjoint.4037 std::map<const VarDecl *, FixItList> FixItsForVariable;4038 4039 // Populate `FixItsForVariable` with fix-its directly associated with each4040 // variable. Fix-its directly associated to a variable 'v' are the ones4041 // produced by the `FixableGadget`s whose claimed variable is 'v'.4042 for (const auto &[VD, Fixables] : FixablesForAllVars.byVar) {4043 FixItsForVariable[VD] =4044 fixVariable(VD, S.lookup(VD), D, Tracker, Ctx, Handler);4045 // If we fail to produce Fix-It for the declaration we have to skip the4046 // variable entirely.4047 if (FixItsForVariable[VD].empty()) {4048 FixItsForVariable.erase(VD);4049 continue;4050 }4051 for (const auto &F : Fixables) {4052 std::optional<FixItList> Fixits = F->getFixits(S);4053 4054 if (Fixits) {4055 FixItsForVariable[VD].insert(FixItsForVariable[VD].end(),4056 Fixits->begin(), Fixits->end());4057 continue;4058 }4059#ifndef NDEBUG4060 Handler.addDebugNoteForVar(4061 VD, F->getSourceLoc(),4062 ("gadget '" + F->getDebugName() + "' refused to produce a fix")4063 .str());4064#endif4065 FixItsForVariable.erase(VD);4066 break;4067 }4068 }4069 4070 // `FixItsForVariable` now contains only variables that can be4071 // fixed. A variable can be fixed if its' declaration and all Fixables4072 // associated to it can all be fixed.4073 4074 // To further remove from `FixItsForVariable` variables whose group mates4075 // cannot be fixed...4076 eraseVarsForUnfixableGroupMates(FixItsForVariable, VarGrpMgr);4077 // Now `FixItsForVariable` gets further reduced: a variable is in4078 // `FixItsForVariable` iff it can be fixed and all its group mates can be4079 // fixed.4080 4081 // Fix-its of bounds-safe overloads of `D` are shared by parameters of `D`.4082 // That is, when fixing multiple parameters in one step, these fix-its will4083 // be applied only once (instead of being applied per parameter).4084 FixItList FixItsSharedByParms{};4085 4086 if (auto *FD = dyn_cast<FunctionDecl>(D))4087 FixItsSharedByParms = createFunctionOverloadsForParms(4088 FixItsForVariable, VarGrpMgr, FD, S, Ctx, Handler);4089 4090 // The map that maps each variable `v` to fix-its for the whole group where4091 // `v` is in:4092 std::map<const VarDecl *, FixItList> FinalFixItsForVariable{4093 FixItsForVariable};4094 4095 for (auto &[Var, Ignore] : FixItsForVariable) {4096 bool AnyParm = false;4097 const auto VarGroupForVD = VarGrpMgr.getGroupOfVar(Var, &AnyParm);4098 4099 for (const VarDecl *GrpMate : VarGroupForVD) {4100 if (Var == GrpMate)4101 continue;4102 if (FixItsForVariable.count(GrpMate))4103 FinalFixItsForVariable[Var].append(FixItsForVariable[GrpMate]);4104 }4105 if (AnyParm) {4106 // This assertion should never fail. Otherwise we have a bug.4107 assert(!FixItsSharedByParms.empty() &&4108 "Should not try to fix a parameter that does not belong to a "4109 "FunctionDecl");4110 FinalFixItsForVariable[Var].append(FixItsSharedByParms);4111 }4112 }4113 // Fix-its that will be applied in one step shall NOT:4114 // 1. overlap with macros or/and templates; or4115 // 2. conflict with each other.4116 // Otherwise, the fix-its will be dropped.4117 for (auto Iter = FinalFixItsForVariable.begin();4118 Iter != FinalFixItsForVariable.end();)4119 if (overlapWithMacro(Iter->second) ||4120 clang::internal::anyConflict(Iter->second, Ctx.getSourceManager())) {4121 Iter = FinalFixItsForVariable.erase(Iter);4122 } else4123 Iter++;4124 return FinalFixItsForVariable;4125}4126 4127template <typename VarDeclIterTy>4128static FixitStrategy4129getNaiveStrategy(llvm::iterator_range<VarDeclIterTy> UnsafeVars) {4130 FixitStrategy S;4131 for (const VarDecl *VD : UnsafeVars) {4132 if (isa<ConstantArrayType>(VD->getType().getCanonicalType()))4133 S.set(VD, FixitStrategy::Kind::Array);4134 else4135 S.set(VD, FixitStrategy::Kind::Span);4136 }4137 return S;4138}4139 4140// Manages variable groups:4141class VariableGroupsManagerImpl : public VariableGroupsManager {4142 const std::vector<VarGrpTy> Groups;4143 const std::map<const VarDecl *, unsigned> &VarGrpMap;4144 const llvm::SetVector<const VarDecl *> &GrpsUnionForParms;4145 4146public:4147 VariableGroupsManagerImpl(4148 const std::vector<VarGrpTy> &Groups,4149 const std::map<const VarDecl *, unsigned> &VarGrpMap,4150 const llvm::SetVector<const VarDecl *> &GrpsUnionForParms)4151 : Groups(Groups), VarGrpMap(VarGrpMap),4152 GrpsUnionForParms(GrpsUnionForParms) {}4153 4154 VarGrpRef getGroupOfVar(const VarDecl *Var, bool *HasParm) const override {4155 if (GrpsUnionForParms.contains(Var)) {4156 if (HasParm)4157 *HasParm = true;4158 return GrpsUnionForParms.getArrayRef();4159 }4160 if (HasParm)4161 *HasParm = false;4162 4163 auto It = VarGrpMap.find(Var);4164 4165 if (It == VarGrpMap.end())4166 return {};4167 return Groups[It->second];4168 }4169 4170 VarGrpRef getGroupOfParms() const override {4171 return GrpsUnionForParms.getArrayRef();4172 }4173};4174 4175static void applyGadgets(const Decl *D, FixableGadgetList FixableGadgets,4176 WarningGadgetList WarningGadgets,4177 DeclUseTracker Tracker,4178 UnsafeBufferUsageHandler &Handler,4179 bool EmitSuggestions) {4180 if (!EmitSuggestions) {4181 // Our job is very easy without suggestions. Just warn about4182 // every problematic operation and consider it done. No need to deal4183 // with fixable gadgets, no need to group operations by variable.4184 for (const auto &G : WarningGadgets) {4185 G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/false,4186 D->getASTContext());4187 }4188 4189 // This return guarantees that most of the machine doesn't run when4190 // suggestions aren't requested.4191 assert(FixableGadgets.empty() &&4192 "Fixable gadgets found but suggestions not requested!");4193 return;4194 }4195 4196 // If no `WarningGadget`s ever matched, there is no unsafe operations in the4197 // function under the analysis. No need to fix any Fixables.4198 if (!WarningGadgets.empty()) {4199 // Gadgets "claim" variables they're responsible for. Once this loop4200 // finishes, the tracker will only track DREs that weren't claimed by any4201 // gadgets, i.e. not understood by the analysis.4202 for (const auto &G : FixableGadgets) {4203 for (const auto *DRE : G->getClaimedVarUseSites()) {4204 Tracker.claimUse(DRE);4205 }4206 }4207 }4208 4209 // If no `WarningGadget`s ever matched, there is no unsafe operations in the4210 // function under the analysis. Thus, it early returns here as there is4211 // nothing needs to be fixed.4212 //4213 // Note this claim is based on the assumption that there is no unsafe4214 // variable whose declaration is invisible from the analyzing function.4215 // Otherwise, we need to consider if the uses of those unsafe varuables needs4216 // fix.4217 // So far, we are not fixing any global variables or class members. And,4218 // lambdas will be analyzed along with the enclosing function. So this early4219 // return is correct for now.4220 if (WarningGadgets.empty())4221 return;4222 4223 WarningGadgetSets UnsafeOps =4224 groupWarningGadgetsByVar(std::move(WarningGadgets));4225 FixableGadgetSets FixablesForAllVars =4226 groupFixablesByVar(std::move(FixableGadgets));4227 4228 std::map<const VarDecl *, FixItList> FixItsForVariableGroup;4229 4230 // Filter out non-local vars and vars with unclaimed DeclRefExpr-s.4231 for (auto it = FixablesForAllVars.byVar.cbegin();4232 it != FixablesForAllVars.byVar.cend();) {4233 // FIXME: need to deal with global variables later4234 if ((!it->first->isLocalVarDecl() && !isa<ParmVarDecl>(it->first))) {4235#ifndef NDEBUG4236 Handler.addDebugNoteForVar(it->first, it->first->getBeginLoc(),4237 ("failed to produce fixit for '" +4238 it->first->getNameAsString() +4239 "' : neither local nor a parameter"));4240#endif4241 it = FixablesForAllVars.byVar.erase(it);4242 } else if (it->first->getType().getCanonicalType()->isReferenceType()) {4243#ifndef NDEBUG4244 Handler.addDebugNoteForVar(it->first, it->first->getBeginLoc(),4245 ("failed to produce fixit for '" +4246 it->first->getNameAsString() +4247 "' : has a reference type"));4248#endif4249 it = FixablesForAllVars.byVar.erase(it);4250 } else if (Tracker.hasUnclaimedUses(it->first)) {4251 it = FixablesForAllVars.byVar.erase(it);4252 } else if (it->first->isInitCapture()) {4253#ifndef NDEBUG4254 Handler.addDebugNoteForVar(it->first, it->first->getBeginLoc(),4255 ("failed to produce fixit for '" +4256 it->first->getNameAsString() +4257 "' : init capture"));4258#endif4259 it = FixablesForAllVars.byVar.erase(it);4260 } else {4261 ++it;4262 }4263 }4264 4265#ifndef NDEBUG4266 for (const auto &it : UnsafeOps.byVar) {4267 const VarDecl *const UnsafeVD = it.first;4268 auto UnclaimedDREs = Tracker.getUnclaimedUses(UnsafeVD);4269 if (UnclaimedDREs.empty())4270 continue;4271 const auto UnfixedVDName = UnsafeVD->getNameAsString();4272 for (const clang::DeclRefExpr *UnclaimedDRE : UnclaimedDREs) {4273 std::string UnclaimedUseTrace =4274 getDREAncestorString(UnclaimedDRE, D->getASTContext());4275 4276 Handler.addDebugNoteForVar(4277 UnsafeVD, UnclaimedDRE->getBeginLoc(),4278 ("failed to produce fixit for '" + UnfixedVDName +4279 "' : has an unclaimed use\nThe unclaimed DRE trace: " +4280 UnclaimedUseTrace));4281 }4282 }4283#endif4284 4285 // Fixpoint iteration for pointer assignments4286 using DepMapTy =4287 llvm::DenseMap<const VarDecl *, llvm::SetVector<const VarDecl *>>;4288 DepMapTy DependenciesMap{};4289 DepMapTy PtrAssignmentGraph{};4290 4291 for (const auto &it : FixablesForAllVars.byVar) {4292 for (const FixableGadget *fixable : it.second) {4293 std::optional<std::pair<const VarDecl *, const VarDecl *>> ImplPair =4294 fixable->getStrategyImplications();4295 if (ImplPair) {4296 std::pair<const VarDecl *, const VarDecl *> Impl = std::move(*ImplPair);4297 PtrAssignmentGraph[Impl.first].insert(Impl.second);4298 }4299 }4300 }4301 4302 /*4303 The following code does a BFS traversal of the `PtrAssignmentGraph`4304 considering all unsafe vars as starting nodes and constructs an undirected4305 graph `DependenciesMap`. Constructing the `DependenciesMap` in this manner4306 elimiates all variables that are unreachable from any unsafe var. In other4307 words, this removes all dependencies that don't include any unsafe variable4308 and consequently don't need any fixit generation.4309 Note: A careful reader would observe that the code traverses4310 `PtrAssignmentGraph` using `CurrentVar` but adds edges between `Var` and4311 `Adj` and not between `CurrentVar` and `Adj`. Both approaches would4312 achieve the same result but the one used here dramatically cuts the4313 amount of hoops the second part of the algorithm needs to jump, given that4314 a lot of these connections become "direct". The reader is advised not to4315 imagine how the graph is transformed because of using `Var` instead of4316 `CurrentVar`. The reader can continue reading as if `CurrentVar` was used,4317 and think about why it's equivalent later.4318 */4319 std::set<const VarDecl *> VisitedVarsDirected{};4320 for (const auto &[Var, ignore] : UnsafeOps.byVar) {4321 if (VisitedVarsDirected.find(Var) == VisitedVarsDirected.end()) {4322 4323 std::queue<const VarDecl *> QueueDirected{};4324 QueueDirected.push(Var);4325 while (!QueueDirected.empty()) {4326 const VarDecl *CurrentVar = QueueDirected.front();4327 QueueDirected.pop();4328 VisitedVarsDirected.insert(CurrentVar);4329 auto AdjacentNodes = PtrAssignmentGraph[CurrentVar];4330 for (const VarDecl *Adj : AdjacentNodes) {4331 if (VisitedVarsDirected.find(Adj) == VisitedVarsDirected.end()) {4332 QueueDirected.push(Adj);4333 }4334 DependenciesMap[Var].insert(Adj);4335 DependenciesMap[Adj].insert(Var);4336 }4337 }4338 }4339 }4340 4341 // `Groups` stores the set of Connected Components in the graph.4342 std::vector<VarGrpTy> Groups;4343 // `VarGrpMap` maps variables that need fix to the groups (indexes) that the4344 // variables belong to. Group indexes refer to the elements in `Groups`.4345 // `VarGrpMap` is complete in that every variable that needs fix is in it.4346 std::map<const VarDecl *, unsigned> VarGrpMap;4347 // The union group over the ones in "Groups" that contain parameters of `D`:4348 llvm::SetVector<const VarDecl *>4349 GrpsUnionForParms; // these variables need to be fixed in one step4350 4351 // Group Connected Components for Unsafe Vars4352 // (Dependencies based on pointer assignments)4353 std::set<const VarDecl *> VisitedVars{};4354 for (const auto &[Var, ignore] : UnsafeOps.byVar) {4355 if (VisitedVars.find(Var) == VisitedVars.end()) {4356 VarGrpTy &VarGroup = Groups.emplace_back();4357 std::queue<const VarDecl *> Queue{};4358 4359 Queue.push(Var);4360 while (!Queue.empty()) {4361 const VarDecl *CurrentVar = Queue.front();4362 Queue.pop();4363 VisitedVars.insert(CurrentVar);4364 VarGroup.push_back(CurrentVar);4365 auto AdjacentNodes = DependenciesMap[CurrentVar];4366 for (const VarDecl *Adj : AdjacentNodes) {4367 if (VisitedVars.find(Adj) == VisitedVars.end()) {4368 Queue.push(Adj);4369 }4370 }4371 }4372 4373 bool HasParm = false;4374 unsigned GrpIdx = Groups.size() - 1;4375 4376 for (const VarDecl *V : VarGroup) {4377 VarGrpMap[V] = GrpIdx;4378 if (!HasParm && isParameterOf(V, D))4379 HasParm = true;4380 }4381 if (HasParm)4382 GrpsUnionForParms.insert_range(VarGroup);4383 }4384 }4385 4386 // Remove a `FixableGadget` if the associated variable is not in the graph4387 // computed above. We do not want to generate fix-its for such variables,4388 // since they are neither warned nor reachable from a warned one.4389 //4390 // Note a variable is not warned if it is not directly used in any unsafe4391 // operation. A variable `v` is NOT reachable from an unsafe variable, if it4392 // does not exist another variable `u` such that `u` is warned and fixing `u`4393 // (transitively) implicates fixing `v`.4394 //4395 // For example,4396 // ```4397 // void f(int * p) {4398 // int * a = p; *p = 0;4399 // }4400 // ```4401 // `*p = 0` is a fixable gadget associated with a variable `p` that is neither4402 // warned nor reachable from a warned one. If we add `a[5] = 0` to the end of4403 // the function above, `p` becomes reachable from a warned variable.4404 for (auto I = FixablesForAllVars.byVar.begin();4405 I != FixablesForAllVars.byVar.end();) {4406 // Note `VisitedVars` contain all the variables in the graph:4407 if (!VisitedVars.count((*I).first)) {4408 // no such var in graph:4409 I = FixablesForAllVars.byVar.erase(I);4410 } else4411 ++I;4412 }4413 4414 // We assign strategies to variables that are 1) in the graph and 2) can be4415 // fixed. Other variables have the default "Won't fix" strategy.4416 FixitStrategy NaiveStrategy = getNaiveStrategy(llvm::make_filter_range(4417 VisitedVars, [&FixablesForAllVars](const VarDecl *V) {4418 // If a warned variable has no "Fixable", it is considered unfixable:4419 return FixablesForAllVars.byVar.count(V);4420 }));4421 VariableGroupsManagerImpl VarGrpMgr(Groups, VarGrpMap, GrpsUnionForParms);4422 4423 if (isa<NamedDecl>(D))4424 // The only case where `D` is not a `NamedDecl` is when `D` is a4425 // `BlockDecl`. Let's not fix variables in blocks for now4426 FixItsForVariableGroup =4427 getFixIts(FixablesForAllVars, NaiveStrategy, D->getASTContext(), D,4428 Tracker, Handler, VarGrpMgr);4429 4430 for (const auto &G : UnsafeOps.noVar) {4431 G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/false,4432 D->getASTContext());4433 }4434 4435 for (const auto &[VD, WarningGadgets] : UnsafeOps.byVar) {4436 auto FixItsIt = FixItsForVariableGroup.find(VD);4437 Handler.handleUnsafeVariableGroup(VD, VarGrpMgr,4438 FixItsIt != FixItsForVariableGroup.end()4439 ? std::move(FixItsIt->second)4440 : FixItList{},4441 D, NaiveStrategy);4442 for (const auto &G : WarningGadgets) {4443 G->handleUnsafeOperation(Handler, /*IsRelatedToDecl=*/true,4444 D->getASTContext());4445 }4446 }4447}4448 4449void clang::checkUnsafeBufferUsage(const Decl *D,4450 UnsafeBufferUsageHandler &Handler,4451 bool EmitSuggestions) {4452#ifndef NDEBUG4453 Handler.clearDebugNotes();4454#endif4455 4456 assert(D);4457 4458 SmallVector<Stmt *> Stmts;4459 4460 if (const auto *FD = dyn_cast<FunctionDecl>(D)) {4461 // We do not want to visit a Lambda expression defined inside a method4462 // independently. Instead, it should be visited along with the outer method.4463 // FIXME: do we want to do the same thing for `BlockDecl`s?4464 if (const auto *MD = dyn_cast<CXXMethodDecl>(D)) {4465 if (MD->getParent()->isLambda() && MD->getParent()->isLocalClass())4466 return;4467 }4468 4469 for (FunctionDecl *FReDecl : FD->redecls()) {4470 if (FReDecl->isExternC()) {4471 // Do not emit fixit suggestions for functions declared in an4472 // extern "C" block.4473 EmitSuggestions = false;4474 break;4475 }4476 }4477 4478 Stmts.push_back(FD->getBody());4479 4480 if (const auto *ID = dyn_cast<CXXConstructorDecl>(D)) {4481 for (const CXXCtorInitializer *CI : ID->inits()) {4482 Stmts.push_back(CI->getInit());4483 }4484 }4485 } else if (isa<BlockDecl>(D) || isa<ObjCMethodDecl>(D)) {4486 Stmts.push_back(D->getBody());4487 }4488 4489 assert(!Stmts.empty());4490 4491 FixableGadgetList FixableGadgets;4492 WarningGadgetList WarningGadgets;4493 DeclUseTracker Tracker;4494 for (Stmt *S : Stmts) {4495 findGadgets(S, D->getASTContext(), Handler, EmitSuggestions, FixableGadgets,4496 WarningGadgets, Tracker);4497 }4498 applyGadgets(D, std::move(FixableGadgets), std::move(WarningGadgets),4499 std::move(Tracker), Handler, EmitSuggestions);4500}4501