diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -468,6 +468,12 @@ Dependencies<[NewDeleteChecker]>, Documentation; +def PlacementNewChecker : Checker<"PlacementNew">, + HelpText<"Check if default placement new is provided with pointers to " + "sufficient storage capacity">, + Dependencies<[NewDeleteChecker]>, + Documentation; + def CXXSelfAssignmentChecker : Checker<"SelfAssignment">, HelpText<"Checks C++ copy and move assignment operators for self assignment">, Documentation, diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -19,6 +19,7 @@ CastValueChecker.cpp CheckObjCDealloc.cpp CheckObjCInstMethSignature.cpp + CheckPlacementNew.cpp CheckSecuritySyntaxOnly.cpp CheckSizeofPointer.cpp CheckerDocumentation.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp new file mode 100644 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp @@ -0,0 +1,134 @@ +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/Support/FormatVariadic.h" + +using namespace clang; +using namespace ento; + +class PlacementNewChecker : public Checker> { +public: + void checkPreStmt(const CXXNewExpr *NE, CheckerContext &C) const; + +private: + // Returns the size of the target in a placement new expression. + // E.g. in "new (&s) long" it returns the size of `long`. + SVal getExtentSizeOfNewTarget(CheckerContext &C, const CXXNewExpr *NE, + ProgramStateRef State) const; + // Returns the size of the place in a placement new expression. + // E.g. in "new (&s) long" it returns the size of `s`. + SVal getExtentSizeOfPlace(CheckerContext &C, const Expr *NE, + ProgramStateRef State) const; + mutable std::unique_ptr BT_Placement; +}; + +SVal PlacementNewChecker::getExtentSizeOfPlace(CheckerContext &C, + const Expr *Place, + ProgramStateRef State) const { + SVal SV = C.getSVal(Place); + const MemRegion *MRegion = SV.getAsRegion(); + SValBuilder &svalBuilder = C.getSValBuilder(); + + // Handle pointers that point into an array. E.g.: + // char *buf = new char[2]; + // long *lp = ::new (buf) long; + if (const auto *TVRegion = dyn_cast(MRegion)) + // FIXME Handle offset other than 0. E.g.: + // char *buf = new char[8]; + // long *lp = ::new (buf + 1) long; + if (const auto *ERegion = dyn_cast(TVRegion)) { + const auto *SRegion = cast(ERegion->getSuperRegion()); + DefinedOrUnknownSVal Extent = SRegion->getExtent(svalBuilder); + // Get the known size of the heap allocated storage. + // This is modelled by the MallocChecker. + const llvm::APSInt *ExtentInt = svalBuilder.getKnownValue(State, Extent); + if (!ExtentInt) + return UnknownVal(); + return svalBuilder.makeIntVal(ExtentInt->getExtValue(), + svalBuilder.getArrayIndexType()); + } + + // Pointer to an array, e.g.: + // char *buf = new char[2]; + // long *lp = ::new (&buf) long; + // Or pointer to a simple variable: + // short s; + // long *lp = ::new (&s) long; + const auto *SRegion = MRegion->castAs(); + DefinedOrUnknownSVal Extent = SRegion->getExtent(svalBuilder); + return Extent; +} + +SVal PlacementNewChecker::getExtentSizeOfNewTarget( + CheckerContext &C, const CXXNewExpr *NE, ProgramStateRef State) const { + if (!State) + return SVal(); + SValBuilder &svalBuilder = C.getSValBuilder(); + SVal ElementCount; + if (NE->isArray()) { + const Expr *SizeExpr = *NE->getArraySize(); + ElementCount = C.getSVal(SizeExpr); + } else { + ElementCount = svalBuilder.makeIntVal(1, true); + } + + QualType ElementType = NE->getAllocatedType(); + ASTContext &AstContext = C.getASTContext(); + CharUnits TypeSize = AstContext.getTypeSizeInChars(ElementType); + + if (ElementCount.getAs()) { + // size in Bytes = ElementCount*TypeSize + SVal SizeInBytes = svalBuilder.evalBinOpNN( + State, BO_Mul, ElementCount.castAs(), + svalBuilder.makeArrayIndex(TypeSize.getQuantity()), + svalBuilder.getArrayIndexType()); + return SizeInBytes; + } + return SVal(); +} + +void PlacementNewChecker::checkPreStmt(const CXXNewExpr *NE, + CheckerContext &C) const { + // Check only the default placement new. + if (!NE->getOperatorNew()->isReservedGlobalPlacementOperator()) + return; + if (NE->getNumPlacementArgs() == 0) + return; + + ProgramStateRef State = C.getState(); + SVal SizeOfTarget = getExtentSizeOfNewTarget(C, NE, State); + const Expr *Place = NE->getPlacementArg(0); + SVal SizeOfPlace = getExtentSizeOfPlace(C, Place, State); + const auto SizeOfTargetCI = SizeOfTarget.getAs(); + if (!SizeOfTargetCI) + return; + const auto SizeOfPlaceCI = SizeOfPlace.getAs(); + if (!SizeOfPlaceCI) + return; + + if (SizeOfPlaceCI->getValue() < SizeOfTargetCI->getValue()) { + if (ExplodedNode *N = C.generateErrorNode(State)) { + if (!BT_Placement) + BT_Placement.reset(new BuiltinBug(this, "Insufficient storage BB")); + + std::string Msg = + llvm::formatv("Argument of default placement new provides storage " + "capacity of {0} bytes, but the allocated type " + "requires storage capacity of {1} bytes", + SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()); + + auto R = std::make_unique(*BT_Placement, Msg, N); + bugreporter::trackExpressionValue(N, Place, *R); + C.emitReport(std::move(R)); + return; + } + } +} + +void ento::registerPlacementNewChecker(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterPlacementNewChecker(const LangOptions &LO) { + return true; +} diff --git a/clang/test/Analysis/placement-new-user-defined.cpp b/clang/test/Analysis/placement-new-user-defined.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/placement-new-user-defined.cpp @@ -0,0 +1,22 @@ +// RUN: %clang_analyze_cc1 -std=c++11 %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=cplusplus.NewDelete \ +// RUN: -analyzer-checker=cplusplus.PlacementNew \ +// RUN: -analyzer-output=text -verify \ +// RUN: -triple x86_64-unknown-linux-gnu + +// expected-no-diagnostics + +#include "Inputs/system-header-simulator-cxx.h" + +struct X { + static void *operator new(std::size_t sz, void *b) { + return ::operator new(sz, b); + } + long l; +}; +void f() { + short buf; + X *p1 = new (&buf) X; + (void)p1; +} diff --git a/clang/test/Analysis/placement-new.cpp b/clang/test/Analysis/placement-new.cpp new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/placement-new.cpp @@ -0,0 +1,94 @@ +// RUN: %clang_analyze_cc1 -std=c++11 %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=cplusplus.NewDelete \ +// RUN: -analyzer-checker=cplusplus.PlacementNew \ +// RUN: -analyzer-output=text -verify \ +// RUN: -triple x86_64-unknown-linux-gnu + +#include "Inputs/system-header-simulator-cxx.h" + +void f() { + short s; // expected-note-re {{'s' declared{{.*}}}} + long *lp = ::new (&s) long; // expected-warning{{Argument of default placement new provides storage capacity of 2 bytes, but the allocated type requires storage capacity of 8 bytes}} expected-note 3 {{}} + (void)lp; +} + +namespace testArrayNew { +void f() { + short s; // expected-note-re {{'s' declared{{.*}}}} + char *buf = ::new (&s) char[8]; // expected-warning{{Argument of default placement new provides storage capacity of 2 bytes, but the allocated type requires storage capacity of 8 bytes}} expected-note 3 {{}} + (void)buf; +} +} // namespace testArrayNew + +namespace testBufferInOtherFun { +void f(void *place) { + long *lp = ::new (place) long; // expected-warning{{Argument of default placement new provides storage capacity of 2 bytes, but the allocated type requires storage capacity of 8 bytes}} expected-note 1 {{}} + (void)lp; +} +void g() { + short buf; // expected-note-re {{'buf' declared{{.*}}}} + f(&buf); // expected-note 2 {{}} +} +} // namespace testBufferInOtherFun + +namespace testArrayBuffer { +void f(void *place) { + long *lp = ::new (place) long; // expected-warning{{Argument of default placement new provides storage capacity of 2 bytes, but the allocated type requires storage capacity of 8 bytes}} expected-note 1 {{}} + (void)lp; +} +void g() { + char buf[2]; // expected-note-re {{'buf' initialized{{.*}}}} + f(&buf); // expected-note 2 {{}} +} +} // namespace testArrayBuffer + +namespace testGlobalPtrAsPlace { +void *gptr = nullptr; +short gs; +void f() { + gptr = &gs; // expected-note {{Value assigned to 'gptr'}} +} +void g() { + f(); // expected-note 2 {{}} + long *lp = ::new (gptr) long; // expected-warning{{Argument of default placement new provides storage capacity of 2 bytes, but the allocated type requires storage capacity of 8 bytes}} expected-note 1 {{}} + (void)lp; +} +} // namespace testGlobalPtrAsPlace + +namespace testRvalue { +short gs; +void *f() { + return &gs; +} +void g() { + long *lp = ::new (f()) long; // expected-warning{{Argument of default placement new provides storage capacity of 2 bytes, but the allocated type requires storage capacity of 8 bytes}} expected-note 1 {{}} + (void)lp; +} +} // namespace testRvalue + +namespace testNoWarning { +void *f(); +void g() { + long *lp = ::new (f()) long; + (void)lp; +} +} // namespace testNoWarning + +namespace testPtrToArrayAsPlace { +void f() { + //char *st = new char [8]; + char buf[3]; // expected-note-re {{'buf' initialized{{.*}}}} + void *st = buf; // expected-note-re {{'st' initialized{{.*}}}} + long *lp = ::new (st) long; // expected-warning{{Argument of default placement new provides storage capacity of 3 bytes, but the allocated type requires storage capacity of 8 bytes}} expected-note 1 {{}} + (void)lp; +} +} // namespace testPtrToArrayAsPlace + +namespace testHeapAllocatedBuffer { +void g2() { + char *buf = new char[2]; // expected-note-re {{'buf' initialized{{.*}}}} + long *lp = ::new (buf) long; // expected-warning{{Argument of default placement new provides storage capacity of 2 bytes, but the allocated type requires storage capacity of 8 bytes}} expected-note 1 {{}} + (void)lp; +} +} // namespace testHeapAllocatedBuffer