diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -58,6 +58,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicSize.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" @@ -389,6 +390,13 @@ // TODO: Remove mutable by moving the initializtaion to the registry function. mutable Optional KernelZeroFlagVal; + using KernelZeroSizePtrValueTy = Optional; + /// Store the value of macro called `ZERO_SIZE_PTR`. + /// The value is initialized at first use, before first use the outer + /// Optional is empty, afterwards it contains another Optional that indicates + /// if the macro value could be determined, and if yes the value itself. + mutable Optional KernelZeroSizePtrValue; + /// Process C++ operator new()'s allocation, which is the part of C++ /// new-expression that goes before the constructor. void processNewAllocation(const CXXNewExpr *NE, CheckerContext &C, @@ -658,6 +666,9 @@ CheckerContext &C); void reportLeak(SymbolRef Sym, ExplodedNode *N, CheckerContext &C) const; + + /// Test if value in ArgVal equals to value in macro `ZERO_SIZE_PTR`. + bool isArgZERO_SIZE_PTR(ProgramStateRef State, CheckerContext &C, SVal ArgVal) const; }; //===----------------------------------------------------------------------===// @@ -1677,7 +1688,12 @@ // Nonlocs can't be freed, of course. // Non-region locations (labels and fixed addresses) also shouldn't be freed. if (!R) { - ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, Family); + // Exception: + // If there is a macro called ZERO_SIZE_PTR, it could be a kernel source code + // and this value indicates a special value used for a zero-sized memory + // block. It is a constant value that is allowed to be freed. + if (!isArgZERO_SIZE_PTR(State, C, ArgVal)) + ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, Family); return nullptr; } @@ -3023,6 +3039,17 @@ return State; } +bool MallocChecker::isArgZERO_SIZE_PTR(ProgramStateRef State, CheckerContext &C, SVal ArgVal) const { + if (!KernelZeroSizePtrValue) + KernelZeroSizePtrValue = + tryExpandAsInteger("ZERO_SIZE_PTR", C.getPreprocessor()); + + const llvm::APSInt *ArgValKnown = + C.getSValBuilder().getKnownValue(State, ArgVal); + return ArgValKnown && *KernelZeroSizePtrValue && + ArgValKnown->getSExtValue() == **KernelZeroSizePtrValue; +} + static SymbolRef findFailedReallocSymbol(ProgramStateRef currState, ProgramStateRef prevState) { ReallocPairsTy currMap = currState->get(); diff --git a/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp b/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp --- a/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp @@ -126,9 +126,6 @@ if (!T.isOneOf(tok::l_paren, tok::r_paren)) FilteredTokens.push_back(T); - if (FilteredTokens.size() > 2) - return llvm::None; - // Parse an integer at the end of the macro definition. const Token &T = FilteredTokens.back(); if (!T.isLiteral()) @@ -140,11 +137,10 @@ return llvm::None; // Parse an optional minus sign. - if (FilteredTokens.size() == 2) { - if (FilteredTokens.front().is(tok::minus)) + size_t Size = FilteredTokens.size(); + if (Size >= 2) { + if (FilteredTokens[Size - 2].is(tok::minus)) IntValue = -IntValue; - else - return llvm::None; } return IntValue.getSExtValue(); diff --git a/clang/test/Analysis/kmalloc-linux-1.c b/clang/test/Analysis/kmalloc-linux-1.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/kmalloc-linux-1.c @@ -0,0 +1,27 @@ +// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux -analyzer-checker=unix.Malloc -verify %s + +#define ZERO_SIZE_PTR ((void *)16) + +void *__kmalloc(int size, int flags); + +// Linux kmalloc looks like this. +// (simplified) +void *kmalloc(int size, int flags) { + if (size == 0) + return ZERO_SIZE_PTR; + return __kmalloc(size, flags); +} + +// FIXME: malloc checker expects kfree with 2 arguments, is this correct? +// (recent kernel source code contains a `kfree(const void *)`) +void kfree(void *, int); + +void test_kmalloc_zero_sized_block_fixed_value_address() { + void *ptr = kmalloc(0, 0); // kmalloc returns a constant address + kfree(ptr, 0); // no warning about freeing a constant value +} + +void test_kfree_constant_value() { + void *ptr = (void *)1; + kfree(ptr, 0); // expected-warning{{Argument to kfree() is a constant address (1)}} +}