Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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 @@ -717,6 +717,10 @@ Improvements to Clang's diagnostics
Added a new warning in this group for the case where the attribute is missing/implicit on
an override of a virtual method.

- 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
43 changes: 26 additions & 17 deletions clang/include/clang/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,24 @@
#include <optional>

namespace clang {
class APValue;
class ASTContext;
class BlockDecl;
class CXXBaseSpecifier;
class CXXMemberCallExpr;
class CXXOperatorCallExpr;
class CastExpr;
class Decl;
class IdentifierInfo;
class MaterializeTemporaryExpr;
class NamedDecl;
class ObjCPropertyRefExpr;
class OpaqueValueExpr;
class ParmVarDecl;
class StringLiteral;
class TargetInfo;
class ValueDecl;
class AllocSizeAttr;
class APValue;
class ASTContext;
class BlockDecl;
class CXXBaseSpecifier;
class CXXMemberCallExpr;
class CXXOperatorCallExpr;
class CastExpr;
class Decl;
class IdentifierInfo;
class MaterializeTemporaryExpr;
class NamedDecl;
class ObjCPropertyRefExpr;
class OpaqueValueExpr;
class ParmVarDecl;
class StringLiteral;
class TargetInfo;
class ValueDecl;

/// A simple array of base specifiers.
typedef SmallVector<CXXBaseSpecifier*, 4> CXXCastPath;
Expand Down Expand Up @@ -3256,6 +3257,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 @@ -932,6 +932,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
5 changes: 5 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3684,6 +3684,11 @@ 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 @@ -3542,6 +3542,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 @@ -9439,57 +9430,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 @@ -9499,7 +9439,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 @@ -9977,7 +9922,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
38 changes: 38 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,35 @@ 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();
auto LhsSize = 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 +7913,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 @@ -9887,6 +9919,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
Loading