clang API Documentation
00001 //== ArrayBoundCheckerV2.cpp ------------------------------------*- C++ -*--==// 00002 // 00003 // The LLVM Compiler Infrastructure 00004 // 00005 // This file is distributed under the University of Illinois Open Source 00006 // License. See LICENSE.TXT for details. 00007 // 00008 //===----------------------------------------------------------------------===// 00009 // 00010 // This file defines ArrayBoundCheckerV2, which is a path-sensitive check 00011 // which looks for an out-of-bound array element access. 00012 // 00013 //===----------------------------------------------------------------------===// 00014 00015 #include "ClangSACheckers.h" 00016 #include "clang/StaticAnalyzer/Core/Checker.h" 00017 #include "clang/StaticAnalyzer/Core/CheckerManager.h" 00018 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" 00019 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" 00020 #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" 00021 #include "clang/AST/CharUnits.h" 00022 #include "llvm/ADT/SmallString.h" 00023 #include "llvm/ADT/STLExtras.h" 00024 00025 using namespace clang; 00026 using namespace ento; 00027 00028 namespace { 00029 class ArrayBoundCheckerV2 : 00030 public Checker<check::Location> { 00031 mutable OwningPtr<BuiltinBug> BT; 00032 00033 enum OOB_Kind { OOB_Precedes, OOB_Excedes, OOB_Tainted }; 00034 00035 void reportOOB(CheckerContext &C, ProgramStateRef errorState, 00036 OOB_Kind kind) const; 00037 00038 public: 00039 void checkLocation(SVal l, bool isLoad, const Stmt*S, 00040 CheckerContext &C) const; 00041 }; 00042 00043 // FIXME: Eventually replace RegionRawOffset with this class. 00044 class RegionRawOffsetV2 { 00045 private: 00046 const SubRegion *baseRegion; 00047 SVal byteOffset; 00048 00049 RegionRawOffsetV2() 00050 : baseRegion(0), byteOffset(UnknownVal()) {} 00051 00052 public: 00053 RegionRawOffsetV2(const SubRegion* base, SVal offset) 00054 : baseRegion(base), byteOffset(offset) {} 00055 00056 NonLoc getByteOffset() const { return cast<NonLoc>(byteOffset); } 00057 const SubRegion *getRegion() const { return baseRegion; } 00058 00059 static RegionRawOffsetV2 computeOffset(ProgramStateRef state, 00060 SValBuilder &svalBuilder, 00061 SVal location); 00062 00063 void dump() const; 00064 void dumpToStream(raw_ostream &os) const; 00065 }; 00066 } 00067 00068 static SVal computeExtentBegin(SValBuilder &svalBuilder, 00069 const MemRegion *region) { 00070 while (true) 00071 switch (region->getKind()) { 00072 default: 00073 return svalBuilder.makeZeroArrayIndex(); 00074 case MemRegion::SymbolicRegionKind: 00075 // FIXME: improve this later by tracking symbolic lower bounds 00076 // for symbolic regions. 00077 return UnknownVal(); 00078 case MemRegion::ElementRegionKind: 00079 region = cast<SubRegion>(region)->getSuperRegion(); 00080 continue; 00081 } 00082 } 00083 00084 void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, 00085 const Stmt* LoadS, 00086 CheckerContext &checkerContext) const { 00087 00088 // NOTE: Instead of using ProgramState::assumeInBound(), we are prototyping 00089 // some new logic here that reasons directly about memory region extents. 00090 // Once that logic is more mature, we can bring it back to assumeInBound() 00091 // for all clients to use. 00092 // 00093 // The algorithm we are using here for bounds checking is to see if the 00094 // memory access is within the extent of the base region. Since we 00095 // have some flexibility in defining the base region, we can achieve 00096 // various levels of conservatism in our buffer overflow checking. 00097 ProgramStateRef state = checkerContext.getState(); 00098 ProgramStateRef originalState = state; 00099 00100 SValBuilder &svalBuilder = checkerContext.getSValBuilder(); 00101 const RegionRawOffsetV2 &rawOffset = 00102 RegionRawOffsetV2::computeOffset(state, svalBuilder, location); 00103 00104 if (!rawOffset.getRegion()) 00105 return; 00106 00107 // CHECK LOWER BOUND: Is byteOffset < extent begin? 00108 // If so, we are doing a load/store 00109 // before the first valid offset in the memory region. 00110 00111 SVal extentBegin = computeExtentBegin(svalBuilder, rawOffset.getRegion()); 00112 00113 if (isa<NonLoc>(extentBegin)) { 00114 SVal lowerBound 00115 = svalBuilder.evalBinOpNN(state, BO_LT, rawOffset.getByteOffset(), 00116 cast<NonLoc>(extentBegin), 00117 svalBuilder.getConditionType()); 00118 00119 NonLoc *lowerBoundToCheck = dyn_cast<NonLoc>(&lowerBound); 00120 if (!lowerBoundToCheck) 00121 return; 00122 00123 ProgramStateRef state_precedesLowerBound, state_withinLowerBound; 00124 llvm::tie(state_precedesLowerBound, state_withinLowerBound) = 00125 state->assume(*lowerBoundToCheck); 00126 00127 // Are we constrained enough to definitely precede the lower bound? 00128 if (state_precedesLowerBound && !state_withinLowerBound) { 00129 reportOOB(checkerContext, state_precedesLowerBound, OOB_Precedes); 00130 return; 00131 } 00132 00133 // Otherwise, assume the constraint of the lower bound. 00134 assert(state_withinLowerBound); 00135 state = state_withinLowerBound; 00136 } 00137 00138 do { 00139 // CHECK UPPER BOUND: Is byteOffset >= extent(baseRegion)? If so, 00140 // we are doing a load/store after the last valid offset. 00141 DefinedOrUnknownSVal extentVal = 00142 rawOffset.getRegion()->getExtent(svalBuilder); 00143 if (!isa<NonLoc>(extentVal)) 00144 break; 00145 00146 SVal upperbound 00147 = svalBuilder.evalBinOpNN(state, BO_GE, rawOffset.getByteOffset(), 00148 cast<NonLoc>(extentVal), 00149 svalBuilder.getConditionType()); 00150 00151 NonLoc *upperboundToCheck = dyn_cast<NonLoc>(&upperbound); 00152 if (!upperboundToCheck) 00153 break; 00154 00155 ProgramStateRef state_exceedsUpperBound, state_withinUpperBound; 00156 llvm::tie(state_exceedsUpperBound, state_withinUpperBound) = 00157 state->assume(*upperboundToCheck); 00158 00159 // If we are under constrained and the index variables are tainted, report. 00160 if (state_exceedsUpperBound && state_withinUpperBound) { 00161 if (state->isTainted(rawOffset.getByteOffset())) 00162 reportOOB(checkerContext, state_exceedsUpperBound, OOB_Tainted); 00163 return; 00164 } 00165 00166 // If we are constrained enough to definitely exceed the upper bound, report. 00167 if (state_exceedsUpperBound) { 00168 assert(!state_withinUpperBound); 00169 reportOOB(checkerContext, state_exceedsUpperBound, OOB_Excedes); 00170 return; 00171 } 00172 00173 assert(state_withinUpperBound); 00174 state = state_withinUpperBound; 00175 } 00176 while (false); 00177 00178 if (state != originalState) 00179 checkerContext.addTransition(state); 00180 } 00181 00182 void ArrayBoundCheckerV2::reportOOB(CheckerContext &checkerContext, 00183 ProgramStateRef errorState, 00184 OOB_Kind kind) const { 00185 00186 ExplodedNode *errorNode = checkerContext.generateSink(errorState); 00187 if (!errorNode) 00188 return; 00189 00190 if (!BT) 00191 BT.reset(new BuiltinBug("Out-of-bound access")); 00192 00193 // FIXME: This diagnostics are preliminary. We should get far better 00194 // diagnostics for explaining buffer overruns. 00195 00196 SmallString<256> buf; 00197 llvm::raw_svector_ostream os(buf); 00198 os << "Out of bound memory access "; 00199 switch (kind) { 00200 case OOB_Precedes: 00201 os << "(accessed memory precedes memory block)"; 00202 break; 00203 case OOB_Excedes: 00204 os << "(access exceeds upper limit of memory block)"; 00205 break; 00206 case OOB_Tainted: 00207 os << "(index is tainted)"; 00208 break; 00209 } 00210 00211 checkerContext.EmitReport(new BugReport(*BT, os.str(), errorNode)); 00212 } 00213 00214 void RegionRawOffsetV2::dump() const { 00215 dumpToStream(llvm::errs()); 00216 } 00217 00218 void RegionRawOffsetV2::dumpToStream(raw_ostream &os) const { 00219 os << "raw_offset_v2{" << getRegion() << ',' << getByteOffset() << '}'; 00220 } 00221 00222 // FIXME: Merge with the implementation of the same method in Store.cpp 00223 static bool IsCompleteType(ASTContext &Ctx, QualType Ty) { 00224 if (const RecordType *RT = Ty->getAs<RecordType>()) { 00225 const RecordDecl *D = RT->getDecl(); 00226 if (!D->getDefinition()) 00227 return false; 00228 } 00229 00230 return true; 00231 } 00232 00233 00234 // Lazily computes a value to be used by 'computeOffset'. If 'val' 00235 // is unknown or undefined, we lazily substitute '0'. Otherwise, 00236 // return 'val'. 00237 static inline SVal getValue(SVal val, SValBuilder &svalBuilder) { 00238 return isa<UndefinedVal>(val) ? svalBuilder.makeArrayIndex(0) : val; 00239 } 00240 00241 // Scale a base value by a scaling factor, and return the scaled 00242 // value as an SVal. Used by 'computeOffset'. 00243 static inline SVal scaleValue(ProgramStateRef state, 00244 NonLoc baseVal, CharUnits scaling, 00245 SValBuilder &sb) { 00246 return sb.evalBinOpNN(state, BO_Mul, baseVal, 00247 sb.makeArrayIndex(scaling.getQuantity()), 00248 sb.getArrayIndexType()); 00249 } 00250 00251 // Add an SVal to another, treating unknown and undefined values as 00252 // summing to UnknownVal. Used by 'computeOffset'. 00253 static SVal addValue(ProgramStateRef state, SVal x, SVal y, 00254 SValBuilder &svalBuilder) { 00255 // We treat UnknownVals and UndefinedVals the same here because we 00256 // only care about computing offsets. 00257 if (x.isUnknownOrUndef() || y.isUnknownOrUndef()) 00258 return UnknownVal(); 00259 00260 return svalBuilder.evalBinOpNN(state, BO_Add, 00261 cast<NonLoc>(x), cast<NonLoc>(y), 00262 svalBuilder.getArrayIndexType()); 00263 } 00264 00265 /// Compute a raw byte offset from a base region. Used for array bounds 00266 /// checking. 00267 RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(ProgramStateRef state, 00268 SValBuilder &svalBuilder, 00269 SVal location) 00270 { 00271 const MemRegion *region = location.getAsRegion(); 00272 SVal offset = UndefinedVal(); 00273 00274 while (region) { 00275 switch (region->getKind()) { 00276 default: { 00277 if (const SubRegion *subReg = dyn_cast<SubRegion>(region)) { 00278 offset = getValue(offset, svalBuilder); 00279 if (!offset.isUnknownOrUndef()) 00280 return RegionRawOffsetV2(subReg, offset); 00281 } 00282 return RegionRawOffsetV2(); 00283 } 00284 case MemRegion::ElementRegionKind: { 00285 const ElementRegion *elemReg = cast<ElementRegion>(region); 00286 SVal index = elemReg->getIndex(); 00287 if (!isa<NonLoc>(index)) 00288 return RegionRawOffsetV2(); 00289 QualType elemType = elemReg->getElementType(); 00290 // If the element is an incomplete type, go no further. 00291 ASTContext &astContext = svalBuilder.getContext(); 00292 if (!IsCompleteType(astContext, elemType)) 00293 return RegionRawOffsetV2(); 00294 00295 // Update the offset. 00296 offset = addValue(state, 00297 getValue(offset, svalBuilder), 00298 scaleValue(state, 00299 cast<NonLoc>(index), 00300 astContext.getTypeSizeInChars(elemType), 00301 svalBuilder), 00302 svalBuilder); 00303 00304 if (offset.isUnknownOrUndef()) 00305 return RegionRawOffsetV2(); 00306 00307 region = elemReg->getSuperRegion(); 00308 continue; 00309 } 00310 } 00311 } 00312 return RegionRawOffsetV2(); 00313 } 00314 00315 00316 void ento::registerArrayBoundCheckerV2(CheckerManager &mgr) { 00317 mgr.registerChecker<ArrayBoundCheckerV2>(); 00318 }