Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@ Improvements to Clang's diagnostics
specializations in th same way as it already did for other declarators.
(#GH147333)

- A new warning ``-Walloc-size`` has been added to detect calls to functions
decorated with the ``alloc_size`` attribute don't allocate enough space for
the target pointer type.

Improvements to Clang's time-trace
----------------------------------

Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include <optional>

namespace clang {
class AllocSizeAttr;
class APValue;
class ASTContext;
class BlockDecl;
Expand Down Expand Up @@ -3261,6 +3262,14 @@ class CallExpr : public Expr {
setDependence(getDependence() | ExprDependence::TypeValueInstantiation);
}

/// Try to get the alloc_size attribute of the callee. May return null.
const AllocSizeAttr *getCalleeAllocSizeAttr() const;

/// Get the total size in bytes allocated by calling a function decorated with
/// alloc_size. Returns std::nullopt if the the result cannot be evaluated.
std::optional<llvm::APInt>
getBytesReturnedByAllocSizeCall(const ASTContext &Ctx) const;

bool isCallToStdMove() const;

static bool classof(const Stmt *T) {
Expand Down
15 changes: 15 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -973,6 +973,21 @@ An example of how to use ``alloc_size``
assert(__builtin_object_size(a, 0) == 100);
}

When ``-Walloc-size`` is enabled, this attribute allows the compiler to
diagnose cases when the allocated memory is insufficient for the size of the
type the returned pointer is cast to.

.. code-block:: c

void *my_malloc(int a) __attribute__((alloc_size(1)));
void consumer_func(int *);

int main() {
int *ptr = my_malloc(sizeof(int)); // no warning
int *w = my_malloc(1); // warning: allocation of insufficient size '1' for type 'int' with size '4'
consumer_func(my_malloc(1)); // warning: allocation of insufficient size '1' for type 'int' with size '4'
}

.. Note:: This attribute works differently in clang than it does in GCC.
Specifically, clang will only trace ``const`` pointers (as above); we give up
on pointers that are not marked as ``const``. In the vast majority of cases,
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3687,6 +3687,10 @@ def warn_alloca_align_alignof : Warning<
"second argument to __builtin_alloca_with_align is supposed to be in bits">,
InGroup<DiagGroup<"alloca-with-align-alignof">>;

def warn_alloc_size : Warning<
"allocation of insufficient size '%0' for type %1 with size '%2'">,
InGroup<DiagGroup<"alloc-size">>;

def err_alignment_too_small : Error<
"requested alignment must be %0 or greater">;
def err_alignment_too_big : Error<
Expand Down
50 changes: 50 additions & 0 deletions clang/lib/AST/Expr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3533,6 +3533,56 @@ bool CallExpr::isBuiltinAssumeFalse(const ASTContext &Ctx) const {
Arg->EvaluateAsBooleanCondition(ArgVal, Ctx) && !ArgVal;
}

const AllocSizeAttr *CallExpr::getCalleeAllocSizeAttr() const {
if (const FunctionDecl *DirectCallee = getDirectCallee())
return DirectCallee->getAttr<AllocSizeAttr>();
if (const Decl *IndirectCallee = getCalleeDecl())
return IndirectCallee->getAttr<AllocSizeAttr>();
return nullptr;
}

std::optional<llvm::APInt>
CallExpr::getBytesReturnedByAllocSizeCall(const ASTContext &Ctx) const {
const AllocSizeAttr *AllocSize = getCalleeAllocSizeAttr();

assert(AllocSize && AllocSize->getElemSizeParam().isValid());
unsigned SizeArgNo = AllocSize->getElemSizeParam().getASTIndex();
unsigned BitsInSizeT = Ctx.getTypeSize(Ctx.getSizeType());
if (getNumArgs() <= SizeArgNo)
return {};

auto EvaluateAsSizeT = [&](const Expr *E, llvm::APSInt &Into) {
Expr::EvalResult ExprResult;
if (E->isValueDependent() ||
!E->EvaluateAsInt(ExprResult, Ctx, Expr::SE_AllowSideEffects))
return false;
Into = ExprResult.Val.getInt();
if (Into.isNegative() || !Into.isIntN(BitsInSizeT))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be BitsInSizeT-1? In the case where the size_t is signed, but the expression is unsigned-same-bits-as-size_t?

That would make the EvaluateAsSizeT result in a negative value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure I understand, isn't size_t guaranteed to be unsigned?

Also, this code isn't new, it was just moved here.

return false;
Into = Into.zext(BitsInSizeT);
return true;
};

llvm::APSInt SizeOfElem;
if (!EvaluateAsSizeT(getArg(SizeArgNo), SizeOfElem))
return {};

if (!AllocSize->getNumElemsParam().isValid())
return SizeOfElem;

llvm::APSInt NumberOfElems;
unsigned NumArgNo = AllocSize->getNumElemsParam().getASTIndex();
if (!EvaluateAsSizeT(getArg(NumArgNo), NumberOfElems))
return {};

bool Overflow;
llvm::APInt BytesAvailable = SizeOfElem.umul_ov(NumberOfElems, Overflow);
if (Overflow)
return {};

return BytesAvailable;
}

bool CallExpr::isCallToStdMove() const {
return getBuiltinCallee() == Builtin::BImove;
}
Expand Down
71 changes: 8 additions & 63 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,6 @@ namespace {
return Ctx.getLValueReferenceType(E->getType());
}

/// Given a CallExpr, try to get the alloc_size attribute. May return null.
static const AllocSizeAttr *getAllocSizeAttr(const CallExpr *CE) {
if (const FunctionDecl *DirectCallee = CE->getDirectCallee())
return DirectCallee->getAttr<AllocSizeAttr>();
if (const Decl *IndirectCallee = CE->getCalleeDecl())
return IndirectCallee->getAttr<AllocSizeAttr>();
return nullptr;
}

/// Attempts to unwrap a CallExpr (with an alloc_size attribute) from an Expr.
/// This will look through a single cast.
///
Expand All @@ -142,7 +133,7 @@ namespace {
E = Cast->getSubExpr()->IgnoreParens();

if (const auto *CE = dyn_cast<CallExpr>(E))
return getAllocSizeAttr(CE) ? CE : nullptr;
return CE->getCalleeAllocSizeAttr() ? CE : nullptr;
return nullptr;
}

Expand Down Expand Up @@ -9466,57 +9457,6 @@ bool LValueExprEvaluator::VisitBinAssign(const BinaryOperator *E) {
// Pointer Evaluation
//===----------------------------------------------------------------------===//

/// Attempts to compute the number of bytes available at the pointer
/// returned by a function with the alloc_size attribute. Returns true if we
/// were successful. Places an unsigned number into `Result`.
///
/// This expects the given CallExpr to be a call to a function with an
/// alloc_size attribute.
static bool getBytesReturnedByAllocSizeCall(const ASTContext &Ctx,
const CallExpr *Call,
llvm::APInt &Result) {
const AllocSizeAttr *AllocSize = getAllocSizeAttr(Call);

assert(AllocSize && AllocSize->getElemSizeParam().isValid());
unsigned SizeArgNo = AllocSize->getElemSizeParam().getASTIndex();
unsigned BitsInSizeT = Ctx.getTypeSize(Ctx.getSizeType());
if (Call->getNumArgs() <= SizeArgNo)
return false;

auto EvaluateAsSizeT = [&](const Expr *E, APSInt &Into) {
Expr::EvalResult ExprResult;
if (!E->EvaluateAsInt(ExprResult, Ctx, Expr::SE_AllowSideEffects))
return false;
Into = ExprResult.Val.getInt();
if (Into.isNegative() || !Into.isIntN(BitsInSizeT))
return false;
Into = Into.zext(BitsInSizeT);
return true;
};

APSInt SizeOfElem;
if (!EvaluateAsSizeT(Call->getArg(SizeArgNo), SizeOfElem))
return false;

if (!AllocSize->getNumElemsParam().isValid()) {
Result = std::move(SizeOfElem);
return true;
}

APSInt NumberOfElems;
unsigned NumArgNo = AllocSize->getNumElemsParam().getASTIndex();
if (!EvaluateAsSizeT(Call->getArg(NumArgNo), NumberOfElems))
return false;

bool Overflow;
llvm::APInt BytesAvailable = SizeOfElem.umul_ov(NumberOfElems, Overflow);
if (Overflow)
return false;

Result = std::move(BytesAvailable);
return true;
}

/// Convenience function. LVal's base must be a call to an alloc_size
/// function.
static bool getBytesReturnedByAllocSizeCall(const ASTContext &Ctx,
Expand All @@ -9526,7 +9466,12 @@ static bool getBytesReturnedByAllocSizeCall(const ASTContext &Ctx,
"Can't get the size of a non alloc_size function");
const auto *Base = LVal.getLValueBase().get<const Expr *>();
const CallExpr *CE = tryUnwrapAllocSizeCall(Base);
return getBytesReturnedByAllocSizeCall(Ctx, CE, Result);
std::optional<llvm::APInt> Size = CE->getBytesReturnedByAllocSizeCall(Ctx);
if (!Size)
return false;

Result = std::move(*Size);
return true;
}

/// Attempts to evaluate the given LValueBase as the result of a call to
Expand Down Expand Up @@ -10017,7 +9962,7 @@ bool PointerExprEvaluator::visitNonBuiltinCallExpr(const CallExpr *E) {
if (ExprEvaluatorBaseTy::VisitCallExpr(E))
return true;

if (!(InvalidBaseOK && getAllocSizeAttr(E)))
if (!(InvalidBaseOK && E->getCalleeAllocSizeAttr()))
return false;

Result.setInvalid(E);
Expand Down
42 changes: 42 additions & 0 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "clang/AST/ASTDiagnostic.h"
#include "clang/AST/ASTLambda.h"
#include "clang/AST/ASTMutationListener.h"
#include "clang/AST/Attrs.inc"
#include "clang/AST/CXXInheritance.h"
#include "clang/AST/Decl.h"
#include "clang/AST/DeclObjC.h"
Expand Down Expand Up @@ -7818,6 +7819,39 @@ ExprResult Sema::CheckExtVectorCast(SourceRange R, QualType DestTy,
return prepareVectorSplat(DestTy, CastExpr);
}

/// Check that a call to alloc_size function specifies sufficient space for the
/// destination type.
static void CheckSufficientAllocSize(Sema &S, QualType DestType,
const Expr *E) {
QualType SourceType = E->getType();
if (!DestType->isPointerType() || !SourceType->isPointerType() ||
DestType == SourceType)
return;

const auto *CE = dyn_cast<CallExpr>(E->IgnoreParenCasts());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then there's one more test I'd like to see added:

void *ptr = (void (*)(int))malloc(0); // Should diagnose
void *ptr = (void (*)(int))malloc(1); // Should not diagnose

if (!CE)
return;

// Find the total size allocated by the function call.
if (!CE->getCalleeAllocSizeAttr())
return;
std::optional<llvm::APInt> AllocSize =
CE->getBytesReturnedByAllocSizeCall(S.Context);
if (!AllocSize)
return;
auto Size = CharUnits::fromQuantity(AllocSize->getZExtValue());

QualType TargetType = DestType->getPointeeType();
// Find the destination size. As a special case function types have size of
// one byte to match the sizeof operator behavior.
auto LhsSize = TargetType->isFunctionType()
? CharUnits::One()
: S.Context.getTypeSizeInCharsIfKnown(TargetType);
if (LhsSize && Size < LhsSize)
S.Diag(E->getExprLoc(), diag::warn_alloc_size)
<< Size.getQuantity() << TargetType << LhsSize->getQuantity();
}

ExprResult
Sema::ActOnCastExpr(Scope *S, SourceLocation LParenLoc,
Declarator &D, ParsedType &Ty,
Expand Down Expand Up @@ -7883,6 +7917,8 @@ Sema::ActOnCastExpr(Scope *S, SourceLocation LParenLoc,

DiscardMisalignedMemberAddress(castType.getTypePtr(), CastExpr);

CheckSufficientAllocSize(*this, castType, CastExpr);

return BuildCStyleCastExpr(LParenLoc, castTInfo, RParenLoc, CastExpr);
}

Expand Down Expand Up @@ -9893,6 +9929,12 @@ AssignConvertType Sema::CheckSingleAssignmentConstraints(QualType LHSType,
AssignConvertType result =
CheckAssignmentConstraints(LHSType, RHS, Kind, ConvertRHS);

// If assigning a void * created by an allocation function call to some other
// type, check that the allocated size is sufficient for that type.
if (result != AssignConvertType::Incompatible &&
RHS.get()->getType()->isVoidPointerType())
CheckSufficientAllocSize(*this, LHSType, RHS.get());

// C99 6.5.16.1p2: The value of the right operand is converted to the
// type of the assignment expression.
// CheckAssignmentConstraints allows the left-hand side to be a reference,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc,unix.MismatchedDeallocator,cplusplus.NewDelete -std=c++11 -verify %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc,unix.MismatchedDeallocator,cplusplus.NewDelete,cplusplus.NewDeleteLeaks -DLEAKS -std=c++11 -verify %s
// RUN: %clang_analyze_cc1 -Wno-alloc-size -analyzer-checker=core,unix.Malloc,unix.MismatchedDeallocator,cplusplus.NewDelete -std=c++11 -verify %s
// RUN: %clang_analyze_cc1 -Wno-alloc-size -analyzer-checker=core,unix.Malloc,unix.MismatchedDeallocator,cplusplus.NewDelete,cplusplus.NewDeleteLeaks -DLEAKS -std=c++11 -verify %s

#include "Inputs/system-header-simulator-for-malloc.h"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc,unix.MismatchedDeallocator -std=c++11 -verify %s
// RUN: %clang_analyze_cc1 -Wno-alloc-size -analyzer-checker=core,unix.Malloc,unix.MismatchedDeallocator -std=c++11 -verify %s
// expected-no-diagnostics

typedef __typeof(sizeof(int)) size_t;
Expand Down
4 changes: 2 additions & 2 deletions clang/test/Analysis/MismatchedDeallocator-checker-test.mm
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.MismatchedDeallocator -fblocks -verify %s
// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.MismatchedDeallocator -fblocks -DTEST_INLINABLE_ALLOCATORS -verify %s
// RUN: %clang_analyze_cc1 -Wno-alloc-size -analyzer-checker=core,unix.MismatchedDeallocator -fblocks -verify %s
// RUN: %clang_analyze_cc1 -Wno-alloc-size -analyzer-checker=core,unix.MismatchedDeallocator -fblocks -DTEST_INLINABLE_ALLOCATORS -verify %s

#include "Inputs/system-header-simulator-objc.h"
#include "Inputs/system-header-simulator-cxx.h"
Expand Down
6 changes: 6 additions & 0 deletions clang/test/Analysis/NewDelete-checker-test.cpp
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
// RUN: %clang_analyze_cc1 -std=c++11 -fblocks %s \
// RUN: -Wno-alloc-size \
// RUN: -verify=expected,newdelete \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=cplusplus.NewDelete
//
// RUN: %clang_analyze_cc1 -DLEAKS -std=c++11 -fblocks %s \
// RUN: -Wno-alloc-size \
// RUN: -verify=expected,newdelete,leak \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=cplusplus.NewDelete \
// RUN: -analyzer-checker=cplusplus.NewDeleteLeaks
//
// RUN: %clang_analyze_cc1 -std=c++11 -fblocks -verify %s \
// RUN: -Wno-alloc-size \
// RUN: -verify=expected,leak \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=cplusplus.NewDeleteLeaks
//
// RUN: %clang_analyze_cc1 -std=c++17 -fblocks %s \
// RUN: -Wno-alloc-size \
// RUN: -verify=expected,newdelete \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=cplusplus.NewDelete
//
// RUN: %clang_analyze_cc1 -DLEAKS -std=c++17 -fblocks %s \
// RUN: -Wno-alloc-size \
// RUN: -verify=expected,newdelete,leak \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=cplusplus.NewDelete \
// RUN: -analyzer-checker=cplusplus.NewDeleteLeaks
//
// RUN: %clang_analyze_cc1 -std=c++17 -fblocks -verify %s \
// RUN: -Wno-alloc-size \
// RUN: -verify=expected,leak,inspection \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=cplusplus.NewDeleteLeaks \
Expand Down
3 changes: 3 additions & 0 deletions clang/test/Analysis/NewDelete-intersections.mm
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
// RUN: %clang_analyze_cc1 -std=c++11 -fblocks %s \
// RUN: -Wno-alloc-size \
// RUN: -verify=newdelete \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=cplusplus.NewDelete

// leak-no-diagnostics

// RUN: %clang_analyze_cc1 -std=c++11 -DLEAKS -fblocks %s \
// RUN: -Wno-alloc-size \
// RUN: -verify=leak \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=cplusplus.NewDeleteLeaks

// RUN: %clang_analyze_cc1 -std=c++11 -DLEAKS -fblocks %s \
// RUN: -Wno-alloc-size \
// RUN: -verify=mismatch \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=unix.MismatchedDeallocator
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Analysis/castsize.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// RUN: %clang_analyze_cc1 -verify %s \
// RUN: -analyzer-checker=core,unix.Malloc,alpha.core.CastSize
// RUN: -Wno-alloc-size -analyzer-checker=core,unix.Malloc,alpha.core.CastSize

typedef typeof(sizeof(int)) size_t;
void *malloc(size_t);
Expand Down
1 change: 1 addition & 0 deletions clang/test/Analysis/malloc-annotations.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// RUN: %clang_analyze_cc1 -verify \
// RUN: -Wno-alloc-size \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=alpha.deadcode.UnreachableCode \
// RUN: -analyzer-checker=alpha.core.CastSize \
Expand Down
2 changes: 1 addition & 1 deletion clang/test/Analysis/malloc-sizeof.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %clang_analyze_cc1 -analyzer-checker=unix.MallocSizeof -verify %s
// RUN: %clang_analyze_cc1 -Wno-alloc-size -analyzer-checker=unix.MallocSizeof -verify %s

#include <stddef.h>

Expand Down
Loading
Loading