[analyzer] Warn if the size of the array in `new[]` is undefined

This patch introduces a new checker, called NewArraySize checker,
which detects if the expression that yields the element count of
the array in new[], results in an Undefined value.

Differential Revision: https://reviews.llvm.org/D131299
This commit is contained in:
isuckatcs 2022-08-23 09:21:16 +02:00
parent 4f688d00f4
commit a46154cb1c
8 changed files with 187 additions and 1 deletions

View File

@ -245,6 +245,22 @@ Check for uninitialized values being returned to the caller.
return x; // warn
}
.. _core-uninitialized-NewArraySize:
core.uninitialized.NewArraySize (C++)
"""""""""""""""""""""""""""""""""""""
Check if the element count in new[] is garbage or undefined.
.. code-block:: cpp
void test() {
int n;
int *arr = new int[n]; // warn: Element count in new[] is a garbage value
delete[] arr;
}
.. _cplusplus-checkers:

View File

@ -437,6 +437,10 @@ def ReturnUndefChecker : Checker<"UndefReturn">,
HelpText<"Check for uninitialized values being returned to the caller">,
Documentation<HasDocumentation>;
def UndefinedNewArraySizeChecker : Checker<"NewArraySize">,
HelpText<"Check if the size of the array in a new[] expression is undefined">,
Documentation<HasDocumentation>;
} // end "core.uninitialized"
//===----------------------------------------------------------------------===//

View File

@ -1033,6 +1033,18 @@ public:
return getOriginExpr()->getNumPlacementArgs() + getNumImplicitArgs();
}
bool isArray() const { return getOriginExpr()->isArray(); }
Optional<const clang::Expr *> getArraySizeExpr() const {
return getOriginExpr()->getArraySize();
}
SVal getArraySizeVal() const {
assert(isArray() && "The allocator call doesn't allocate and array!");
return getState()->getSVal(*getArraySizeExpr(), getLocationContext());
}
const Expr *getArgExpr(unsigned Index) const override {
// The first argument of an allocator call is the size of the allocation.
if (Index < getNumImplicitArgs())

View File

@ -120,6 +120,7 @@ add_clang_library(clangStaticAnalyzerCheckers
UndefResultChecker.cpp
UndefinedArraySubscriptChecker.cpp
UndefinedAssignmentChecker.cpp
UndefinedNewArraySizeChecker.cpp
UninitializedObject/UninitializedObjectChecker.cpp
UninitializedObject/UninitializedPointee.cpp
UnixAPIChecker.cpp

View File

@ -1733,6 +1733,10 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C,
// Fill the region with the initialization value.
State = State->bindDefaultInitial(RetVal, Init, LCtx);
// If Size is somehow undefined at this point, this line prevents a crash.
if (Size.isUndef())
Size = UnknownVal();
// Set the region's extent.
State = setDynamicExtent(State, RetVal.getAsRegion(),
Size.castAs<DefinedOrUnknownSVal>(), svalBuilder);

View File

@ -0,0 +1,80 @@
//===--- UndefinedNewArraySizeChecker.cpp -----------------------*- C++ -*--==//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This defines UndefinedNewArraySizeChecker, a builtin check in ExprEngine
// that checks if the size of the array in a new[] expression is undefined.
//
//===----------------------------------------------------------------------===//
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
using namespace clang;
using namespace ento;
namespace {
class UndefinedNewArraySizeChecker : public Checker<check::PreCall> {
private:
BugType BT{this, "Undefined array element count in new[]",
categories::LogicError};
public:
void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
void HandleUndefinedArrayElementCount(CheckerContext &C, SVal ArgVal,
const Expr *Init,
SourceRange Range) const;
};
} // namespace
void UndefinedNewArraySizeChecker::checkPreCall(const CallEvent &Call,
CheckerContext &C) const {
if (const auto *AC = dyn_cast<CXXAllocatorCall>(&Call)) {
if (!AC->isArray())
return;
auto *SizeEx = *AC->getArraySizeExpr();
auto SizeVal = AC->getArraySizeVal();
if (SizeVal.isUndef())
HandleUndefinedArrayElementCount(C, SizeVal, SizeEx,
SizeEx->getSourceRange());
}
}
void UndefinedNewArraySizeChecker::HandleUndefinedArrayElementCount(
CheckerContext &C, SVal ArgVal, const Expr *Init, SourceRange Range) const {
if (ExplodedNode *N = C.generateErrorNode()) {
SmallString<100> buf;
llvm::raw_svector_ostream os(buf);
os << "Element count in new[] is a garbage value";
auto R = std::make_unique<PathSensitiveBugReport>(BT, os.str(), N);
R->markInteresting(ArgVal);
R->addRange(Range);
bugreporter::trackExpressionValue(N, Init, *R);
C.emitReport(std::move(R));
}
}
void ento::registerUndefinedNewArraySizeChecker(CheckerManager &mgr) {
mgr.registerChecker<UndefinedNewArraySizeChecker>();
}
bool ento::shouldRegisterUndefinedNewArraySizeChecker(
const CheckerManager &mgr) {
return mgr.getLangOpts().CPlusPlus;
}

View File

@ -1,4 +1,4 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s
// RUN: %clang_analyze_cc1 -analyzer-checker=debug.ExprInspection -verify %s
void clang_analyzer_warnIfReached();

View File

@ -0,0 +1,69 @@
// RUN: %clang_analyze_cc1 %s -analyzer-checker=core.uninitialized.NewArraySize -analyzer-output=text -verify
#include "Inputs/system-header-simulator-cxx.h"
void checkUndefinedElmenetCountValue() {
int n;
// expected-note@-1{{'n' declared without an initial value}}
int *arr = new int[n]; // expected-warning{{Element count in new[] is a garbage value}}
// expected-note@-1{{Element count in new[] is a garbage value}}
}
void checkUndefinedElmenetCountMultiDimensionalValue() {
int n;
// expected-note@-1{{'n' declared without an initial value}}
auto *arr = new int[n][5]; // expected-warning{{Element count in new[] is a garbage value}}
// expected-note@-1{{Element count in new[] is a garbage value}}
}
void checkUndefinedElmenetCountReference() {
int n;
// expected-note@-1{{'n' declared without an initial value}}
int &ref = n;
// expected-note@-1{{'ref' initialized here}}
int *arr = new int[ref]; // expected-warning{{Element count in new[] is a garbage value}}
// expected-note@-1{{Element count in new[] is a garbage value}}
}
void checkUndefinedElmenetCountMultiDimensionalReference() {
int n;
// expected-note@-1{{'n' declared without an initial value}}
int &ref = n;
// expected-note@-1{{'ref' initialized here}}
auto *arr = new int[ref][5]; // expected-warning{{Element count in new[] is a garbage value}}
// expected-note@-1{{Element count in new[] is a garbage value}}
}
int foo() {
int n;
return n;
}
void checkUndefinedElmenetCountFunction() {
int *arr = new int[foo()]; // expected-warning{{Element count in new[] is a garbage value}}
// expected-note@-1{{Element count in new[] is a garbage value}}
}
void checkUndefinedElmenetCountMultiDimensionalFunction() {
auto *arr = new int[foo()][5]; // expected-warning{{Element count in new[] is a garbage value}}
// expected-note@-1{{Element count in new[] is a garbage value}}
}
void *malloc(size_t);
void checkUndefinedPlacementElementCount() {
int n;
// expected-note@-1{{'n' declared without an initial value}}
void *buffer = malloc(sizeof(std::string) * 10);
std::string *p =
::new (buffer) std::string[n]; // expected-warning{{Element count in new[] is a garbage value}}
// expected-note@-1{{Element count in new[] is a garbage value}}
}