1 //===- UnsafeBufferUsage.h - Replace pointers with modern C++ ---*- 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-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file defines an analysis that aids replacing buffer accesses through 10 // raw pointers with safer C++ abstractions such as containers and views/spans. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #ifndef LLVM_CLANG_ANALYSIS_ANALYSES_UNSAFEBUFFERUSAGE_H 15 #define LLVM_CLANG_ANALYSIS_ANALYSES_UNSAFEBUFFERUSAGE_H 16 17 #include "clang/AST/Decl.h" 18 #include "clang/AST/Expr.h" 19 #include "clang/AST/Stmt.h" 20 #include "clang/Basic/SourceLocation.h" 21 #include "llvm/Support/Debug.h" 22 #include <set> 23 24 namespace clang { 25 26 using VarGrpTy = std::vector<const VarDecl *>; 27 using VarGrpRef = ArrayRef<const VarDecl *>; 28 29 class VariableGroupsManager { 30 public: 31 VariableGroupsManager() = default; 32 virtual ~VariableGroupsManager() = default; 33 /// Returns the set of variables (including `Var`) that need to be fixed 34 /// together in one step. 35 /// 36 /// `Var` must be a variable that needs fix (so it must be in a group). 37 /// `HasParm` is an optional argument that will be set to true if the set of 38 /// variables, where `Var` is in, contains parameters. 39 virtual VarGrpRef getGroupOfVar(const VarDecl *Var, 40 bool *HasParm = nullptr) const =0; 41 42 /// Returns the non-empty group of variables that include parameters of the 43 /// analyzing function, if such a group exists. An empty group, otherwise. 44 virtual VarGrpRef getGroupOfParms() const =0; 45 }; 46 47 // FixitStrategy is a map from variables to the way we plan to emit fixes for 48 // these variables. It is figured out gradually by trying different fixes 49 // for different variables depending on gadgets in which these variables 50 // participate. 51 class FixitStrategy { 52 public: 53 enum class Kind { 54 Wontfix, // We don't plan to emit a fixit for this variable. 55 Span, // We recommend replacing the variable with std::span. 56 Iterator, // We recommend replacing the variable with std::span::iterator. 57 Array, // We recommend replacing the variable with std::array. 58 Vector // We recommend replacing the variable with std::vector. 59 }; 60 61 private: 62 using MapTy = llvm::DenseMap<const VarDecl *, Kind>; 63 64 MapTy Map; 65 66 public: 67 FixitStrategy() = default; 68 FixitStrategy(const FixitStrategy &) = delete; // Let's avoid copies. 69 FixitStrategy &operator=(const FixitStrategy &) = delete; 70 FixitStrategy(FixitStrategy &&) = default; 71 FixitStrategy &operator=(FixitStrategy &&) = default; 72 set(const VarDecl * VD,Kind K)73 void set(const VarDecl *VD, Kind K) { Map[VD] = K; } 74 lookup(const VarDecl * VD)75 Kind lookup(const VarDecl *VD) const { 76 auto I = Map.find(VD); 77 if (I == Map.end()) 78 return Kind::Wontfix; 79 80 return I->second; 81 } 82 }; 83 84 /// The interface that lets the caller handle unsafe buffer usage analysis 85 /// results by overriding this class's handle... methods. 86 class UnsafeBufferUsageHandler { 87 #ifndef NDEBUG 88 public: 89 // A self-debugging facility that you can use to notify the user when 90 // suggestions or fixits are incomplete. 91 // Uses std::function to avoid computing the message when it won't 92 // actually be displayed. 93 using DebugNote = std::pair<SourceLocation, std::string>; 94 using DebugNoteList = std::vector<DebugNote>; 95 using DebugNoteByVar = std::map<const VarDecl *, DebugNoteList>; 96 DebugNoteByVar DebugNotesByVar; 97 #endif 98 99 public: 100 UnsafeBufferUsageHandler() = default; 101 virtual ~UnsafeBufferUsageHandler() = default; 102 103 /// This analyses produces large fixits that are organized into lists 104 /// of primitive fixits (individual insertions/removals/replacements). 105 using FixItList = llvm::SmallVectorImpl<FixItHint>; 106 107 /// Invoked when an unsafe operation over raw pointers is found. 108 virtual void handleUnsafeOperation(const Stmt *Operation, 109 bool IsRelatedToDecl, ASTContext &Ctx) = 0; 110 111 /// Invoked when a call to an unsafe libc function is found. 112 /// \param PrintfInfo 113 /// is 0 if the callee function is not a member of the printf family; 114 /// is 1 if the callee is `sprintf`; 115 /// is 2 if arguments of the call have `__size_by` relation but are not in a 116 /// safe pattern; 117 /// is 3 if string arguments do not guarantee null-termination 118 /// is 4 if the callee takes va_list 119 /// \param UnsafeArg one of the actual arguments that is unsafe, non-null 120 /// only when `2 <= PrintfInfo <= 3` 121 virtual void handleUnsafeLibcCall(const CallExpr *Call, unsigned PrintfInfo, 122 ASTContext &Ctx, 123 const Expr *UnsafeArg = nullptr) = 0; 124 125 /// Invoked when an unsafe operation with a std container is found. 126 virtual void handleUnsafeOperationInContainer(const Stmt *Operation, 127 bool IsRelatedToDecl, 128 ASTContext &Ctx) = 0; 129 130 /// Invoked when a fix is suggested against a variable. This function groups 131 /// all variables that must be fixed together (i.e their types must be changed 132 /// to the same target type to prevent type mismatches) into a single fixit. 133 /// 134 /// `D` is the declaration of the callable under analysis that owns `Variable` 135 /// and all of its group mates. 136 virtual void 137 handleUnsafeVariableGroup(const VarDecl *Variable, 138 const VariableGroupsManager &VarGrpMgr, 139 FixItList &&Fixes, const Decl *D, 140 const FixitStrategy &VarTargetTypes) = 0; 141 142 #ifndef NDEBUG 143 public: areDebugNotesRequested()144 bool areDebugNotesRequested() { 145 DEBUG_WITH_TYPE("SafeBuffers", return true); 146 return false; 147 } 148 addDebugNoteForVar(const VarDecl * VD,SourceLocation Loc,std::string Text)149 void addDebugNoteForVar(const VarDecl *VD, SourceLocation Loc, 150 std::string Text) { 151 if (areDebugNotesRequested()) 152 DebugNotesByVar[VD].push_back(std::make_pair(Loc, Text)); 153 } 154 clearDebugNotes()155 void clearDebugNotes() { 156 if (areDebugNotesRequested()) 157 DebugNotesByVar.clear(); 158 } 159 #endif 160 161 public: 162 /// \return true iff buffer safety is opt-out at `Loc`; false otherwise. 163 virtual bool isSafeBufferOptOut(const SourceLocation &Loc) const = 0; 164 165 /// \return true iff unsafe uses in containers should NOT be reported at 166 /// `Loc`; false otherwise. 167 virtual bool 168 ignoreUnsafeBufferInContainer(const SourceLocation &Loc) const = 0; 169 170 /// \return true iff unsafe libc call should NOT be reported at `Loc` 171 virtual bool 172 ignoreUnsafeBufferInLibcCall(const SourceLocation &Loc) const = 0; 173 174 virtual std::string 175 getUnsafeBufferUsageAttributeTextAt(SourceLocation Loc, 176 StringRef WSSuffix = "") const = 0; 177 }; 178 179 // This function invokes the analysis and allows the caller to react to it 180 // through the handler class. 181 void checkUnsafeBufferUsage(const Decl *D, UnsafeBufferUsageHandler &Handler, 182 bool EmitSuggestions); 183 184 namespace internal { 185 // Tests if any two `FixItHint`s in `FixIts` conflict. Two `FixItHint`s 186 // conflict if they have overlapping source ranges. 187 bool anyConflict(const llvm::SmallVectorImpl<FixItHint> &FixIts, 188 const SourceManager &SM); 189 } // namespace internal 190 191 std::set<const Expr *> findUnsafePointers(const FunctionDecl *FD); 192 } // end namespace clang 193 194 #endif /* LLVM_CLANG_ANALYSIS_ANALYSES_UNSAFEBUFFERUSAGE_H */ 195