Index: lib/StaticAnalyzer/Checkers/MallocChecker.cpp =================================================================== --- lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -63,11 +63,13 @@ const Stmt *S; unsigned K : 2; // Kind enum, but stored as a bitfield. - unsigned Family : 30; // Rest of 32-bit word, currently just an allocation + unsigned ZeroAllocation : 1; // bool, true in case of a zero-size allocation. + unsigned Family : 29; // Rest of 32-bit word, currently just an allocation // family. - RefState(Kind k, const Stmt *s, unsigned family) - : S(s), K(k), Family(family) { + RefState(Kind k, const Stmt *s, unsigned family, + unsigned zeroallocation = false) + : S(s), K(k), Family(family), ZeroAllocation(zeroallocation) { assert(family != AF_None); } public: @@ -79,13 +81,16 @@ return (AllocationFamily)Family; } const Stmt *getStmt() const { return S; } + bool isZeroAllocation() const { return (bool)ZeroAllocation; } bool operator==(const RefState &X) const { - return K == X.K && S == X.S && Family == X.Family; + return K == X.K && S == X.S && Family == X.Family && + ZeroAllocation == X.ZeroAllocation; } - static RefState getAllocated(unsigned family, const Stmt *s) { - return RefState(Allocated, s, family); + static RefState getAllocated(unsigned family, const Stmt *s, + bool zeroallocation = false) { + return RefState(Allocated, s, family, zeroallocation); } static RefState getReleased(unsigned family, const Stmt *s) { return RefState(Released, s, family); @@ -100,6 +105,7 @@ void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); ID.AddPointer(S); + ID.AddInteger(ZeroAllocation); ID.AddInteger(Family); } @@ -222,6 +228,7 @@ mutable std::unique_ptr BT_FreeAlloca[CK_NumCheckKinds]; mutable std::unique_ptr BT_MismatchedDealloc; mutable std::unique_ptr BT_OffsetFree[CK_NumCheckKinds]; + mutable std::unique_ptr BT_UseZerroAllocated[CK_NumCheckKinds]; mutable IdentifierInfo *II_alloca, *II_malloc, *II_free, *II_realloc, *II_calloc, *II_valloc, *II_reallocf, *II_strndup, *II_strdup, *II_kmalloc, *II_if_nameindex, @@ -257,6 +264,12 @@ MemoryOperationKind MemKind) const; bool isStandardNewDelete(const FunctionDecl *FD, ASTContext &C) const; ///@} + + /// \brief Perform a zero-allocation check. + ProgramStateRef ZeroAllocationCheck(CheckerContext &C, const Expr *E, + const unsigned AllocationSizeArg, + ProgramStateRef State) const; + ProgramStateRef MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, const OwnershipAttr* Att, @@ -307,6 +320,9 @@ bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const; + void checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C, + const Stmt *S) const; + bool checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const; /// Check if the function is known free memory, or if it is @@ -361,6 +377,9 @@ void ReportDoubleDelete(CheckerContext &C, SymbolRef Sym) const; + void ReportUseZeroAllocated(CheckerContext &C, SourceRange Range, + SymbolRef Sym) const; + /// Find the location of the allocation for Sym on the path leading to the /// exploded node N. LeakInfo getAllocationSite(const ExplodedNode *N, SymbolRef Sym, @@ -730,6 +749,8 @@ return; if (CE->getNumArgs() < 3) { State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); + if (CE->getNumArgs() == 1) + State = ZeroAllocationCheck(C, CE, 0, State); } else if (CE->getNumArgs() == 3) { llvm::Optional MaybeState = performKernelMalloc(CE, C, State); @@ -749,12 +770,17 @@ if (CE->getNumArgs() < 1) return; State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); + State = ZeroAllocationCheck(C, CE, 0, State); } else if (FunI == II_realloc) { State = ReallocMem(C, CE, false, State); + State = ZeroAllocationCheck(C, CE, 1, State); } else if (FunI == II_reallocf) { State = ReallocMem(C, CE, true, State); + State = ZeroAllocationCheck(C, CE, 1, State); } else if (FunI == II_calloc) { State = CallocMem(C, CE, State); + State = ZeroAllocationCheck(C, CE, 0, State); + State = ZeroAllocationCheck(C, CE, 1, State); } else if (FunI == II_free) { State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory); } else if (FunI == II_strdup) { @@ -764,18 +790,23 @@ } else if (FunI == II_alloca) { State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_Alloca); + State = ZeroAllocationCheck(C, CE, 0, State); } else if (isStandardNewDelete(FD, C.getASTContext())) { // Process direct calls to operator new/new[]/delete/delete[] functions // as distinct from new/new[]/delete/delete[] expressions that are // processed by the checkPostStmt callbacks for CXXNewExpr and // CXXDeleteExpr. OverloadedOperatorKind K = FD->getOverloadedOperator(); - if (K == OO_New) + if (K == OO_New) { State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_CXXNew); - else if (K == OO_Array_New) + State = ZeroAllocationCheck(C, CE, 0, State); + } + else if (K == OO_Array_New) { State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_CXXNewArray); + State = ZeroAllocationCheck(C, CE, 0, State); + } else if (K == OO_Delete || K == OO_Array_Delete) State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory); else @@ -809,6 +840,65 @@ C.addTransition(State); } +// Performs a 0-sized allocations check. +ProgramStateRef MallocChecker::ZeroAllocationCheck(CheckerContext &C, + const Expr *E, + const unsigned AllocationSizeArg, + ProgramStateRef State) const { + if (!State) + return nullptr; + + const Expr *Arg = nullptr; + + if (const CallExpr *CE = dyn_cast(E)) { + Arg = CE->getArg(AllocationSizeArg); + } + else if (const CXXNewExpr *NE = dyn_cast(E)) { + if (NE->isArray()) + Arg = NE->getArraySize(); + else + return State; + } + else + llvm_unreachable("not a CallExpr or CXXNewExpr"); + + assert(Arg); + + Optional DefArgVal = + State->getSVal(Arg, C.getLocationContext()).getAs(); + + if (!DefArgVal) + return State; + + // Check if the allocation size is 0. + ProgramStateRef TrueState, FalseState; + SValBuilder &SvalBuilder = C.getSValBuilder(); + DefinedSVal Zero = + SvalBuilder.makeZeroVal(Arg->getType()).castAs(); + + std::tie(TrueState, FalseState) = + State->assume(SvalBuilder.evalEQ(State, *DefArgVal, Zero)); + + if (TrueState && !FalseState) { + SVal retVal = State->getSVal(E, C.getLocationContext()); + SymbolRef Sym = retVal.getAsLocSymbol(); + if (!Sym) + return State; + + const RefState *RS = State->get(Sym); + if (!RS || !RS->isAllocated()) + return State; + + return TrueState->set( + Sym, RefState::getAllocated(RS->getAllocationFamily(), + RS->getStmt(), true)); + } + + // Assume the value is non-zero going forward. + assert(FalseState); + return FalseState; +} + static QualType getDeepPointeeType(QualType T) { QualType Result = T, PointeeType = T->getPointeeType(); while (!PointeeType.isNull()) { @@ -868,6 +958,7 @@ // existing binding. State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray : AF_CXXNew); + State = ZeroAllocationCheck(C, NE, 0, State); C.addTransition(State); } @@ -1741,6 +1832,36 @@ } } +void MallocChecker::ReportUseZeroAllocated(CheckerContext &C, + SourceRange Range, + SymbolRef Sym) const { + + if (!ChecksEnabled[CK_MallocChecker] && + !ChecksEnabled[CK_NewDeleteChecker]) + return; + + Optional CheckKind = getCheckIfTracked(C, Sym); + + if (!CheckKind.hasValue()) + return; + + if (ExplodedNode *N = C.generateSink()) { + if (!BT_UseZerroAllocated[*CheckKind]) + BT_UseZerroAllocated[*CheckKind].reset(new BugType( + CheckNames[*CheckKind], "Use zero allocated", "Memory Error")); + + BugReport *R = new BugReport(*BT_UseZerroAllocated[*CheckKind], + "Use of zero-allocated memory", N); + + R->addRange(Range); + if (Sym) { + R->markInteresting(Sym); + R->addVisitor(llvm::make_unique(Sym)); + } + C.emitReport(R); + } +} + ProgramStateRef MallocChecker::ReallocMem(CheckerContext &C, const CallExpr *CE, bool FreesOnFail, @@ -2156,6 +2277,15 @@ return false; } +void MallocChecker::checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C, + const Stmt *S) const { + assert(Sym); + const RefState *RS = C.getState()->get(Sym); + + if (RS && RS->isZeroAllocation()) + ReportUseZeroAllocated(C, RS->getStmt()->getSourceRange(), Sym); +} + bool MallocChecker::checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const { if (isReleased(Sym, C)) { @@ -2169,8 +2299,12 @@ void MallocChecker::checkLocation(SVal l, bool isLoad, const Stmt *S, CheckerContext &C) const { SymbolRef Sym = l.getLocSymbolInBase(); - if (Sym) + const MemRegion *MR = l.getAsRegion()->StripCasts(); + + if (Sym) { checkUseAfterFree(Sym, C, S); + checkUseZeroAllocated(Sym, C, S); + } } // If a symbolic region is assumed to NULL (or another constant), stop tracking Index: test/Analysis/Malloc+MismatchedDeallocator_intersections.cpp =================================================================== --- test/Analysis/Malloc+MismatchedDeallocator_intersections.cpp +++ test/Analysis/Malloc+MismatchedDeallocator_intersections.cpp @@ -24,5 +24,16 @@ int *p4 = new int; delete p4; - int j = *p4; // no-warning + int j = *p4; // no-warning +} + +void testUseZeroAllocNoWarn() { + int *p1 = (int *)operator new(0); + *p1 = 1; // no-warning + + int *p2 = (int *)operator new[](0); + p2[0] = 1; // no-warning + + int *p3 = new int[0]; + p3[0] = 1; // no-warning } Index: test/Analysis/NewDelete-checker-test.cpp =================================================================== --- test/Analysis/NewDelete-checker-test.cpp +++ test/Analysis/NewDelete-checker-test.cpp @@ -87,6 +87,30 @@ new (w) PtrWrapper(new int); // no warn } +//----------------------------------------- +// check for usage of zero-allocated memory +//----------------------------------------- + +void testUseZeroAlloc1() { + int *p = (int *)operator new(0); + *p = 1; // expected-warning {{Use of zero-allocated memory}} + delete p; +} + +int testUseZeroAlloc2() { + int *p = (int *)operator new[](0); + return p[0]; // expected-warning {{Use of zero-allocated memory}} + delete[] p; +} + +void f(int); + +void testUseZeroAlloc3() { + int *p = new int[0]; + f(*p); // expected-warning {{Use of zero-allocated memory}} + delete[] p; +} + //--------------- // other checks //--------------- Index: test/Analysis/NewDelete-intersections.mm =================================================================== --- test/Analysis/NewDelete-intersections.mm +++ test/Analysis/NewDelete-intersections.mm @@ -43,6 +43,11 @@ delete p2; // no warn } +void testUseZeroAllocatedMalloced() { + int *p1 = (int *)malloc(0); + *p1 = 1; // no warn +} + //----- Test free standard new void testFreeOpNew() { void *p = operator new(0); Index: test/Analysis/malloc.c =================================================================== --- test/Analysis/malloc.c +++ test/Analysis/malloc.c @@ -200,6 +200,80 @@ char *r = reallocf(0, 12); } // expected-warning {{Potential leak of memory pointed to by}} +//------------------- Check usage of zero-allocated memory --------------------- +void CheckUseZeroAllocatedNoWarn1() { + int *p = malloc(0); + free(p); // no warning +} + +void CheckUseZeroAllocatedNoWarn2() { + int *p = alloca(0); // no warning +} + +void CheckUseZeroAllocated1() { + int *p = malloc(0); + *p = 1; // expected-warning {{Use of zero-allocated memory}} + free(p); +} + +char CheckUseZeroAllocated2() { + char *p = alloca(0); + return *p; // expected-warning {{Use of zero-allocated memory}} +} + +void UseZeroAllocated(int *p) { + if (p) + *p = 7; // expected-warning {{Use of zero-allocated memory}} +} +void CheckUseZeroAllocated3() { + int *p = malloc(0); + UseZeroAllocated(p); +} + +void f(char); +void CheckUseZeroAllocated4() { + char *p = valloc(0); + f(*p); // expected-warning {{Use of zero-allocated memory}} + free(p); +} + +void CheckUseZeroAllocated5() { + int *p = calloc(0, 2); + *p = 1; // expected-warning {{Use of zero-allocated memory}} + free(p); +} + +void CheckUseZeroAllocated6() { + int *p = calloc(2, 0); + *p = 1; // expected-warning {{Use of zero-allocated memory}} + free(p); +} + +void CheckUseZeroAllocatedPathNoWarn(_Bool b) { + int s = 0; + if (b) + s= 10; + + char *p = malloc(s); + + if (b) + *p = 1; // no warning + + free(p); +} + +void CheckUseZeroAllocatedPathWarn(_Bool b) { + int s = 10; + if (b) + s= 0; + + char *p = malloc(s); + + if (b) + *p = 1; // expected-warning {{Use of zero-allocated memory}} + + free(p); +} // This case tests that storing malloc'ed memory to a static variable which is // then returned is not leaked. In the absence of known contracts for functions