Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
23 changes: 19 additions & 4 deletions clang/lib/CodeGen/CGExprCXX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
#include "ConstantEmitter.h"
#include "TargetInfo.h"
#include "clang/Basic/CodeGenOptions.h"
#include "clang/Basic/Sanitizers.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
#include "clang/CodeGen/CGFunctionInfo.h"
#include "llvm/IR/Intrinsics.h"

Expand Down Expand Up @@ -1736,17 +1739,29 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) {
allocator->isReservedGlobalPlacementOperator())
result = Builder.CreateLaunderInvariantGroup(result);

// Check what type of constructor call the sanitizer is checking
// Different UB can occour with custom overloads of operator new
TypeCheckKind checkKind = CodeGenFunction::TCK_ConstructorCall;
const TargetInfo &TI = getContext().getTargetInfo();
unsigned DefaultTargetAlignment = TI.getNewAlign() / TI.getCharWidth();
SourceManager &SM = getContext().getSourceManager();
SourceLocation Loc = E->getOperatorNew()->getLocation();
bool IsCustomOverload = !SM.isInSystemHeader(Loc);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do we really need to check whether the new operator was defined by the user? If new returns bad alignment, that's bad no matter who wrote the implementation.

Also, you can override the global new operator while using the declaration from <new>.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If new somehow returns bad alignment without it being a user overload, it will still be caught with the regular constructor-on error. The new error adds information specific to cases when a user has erroneously returned a smaller than allowed alignment in their overload, so I thought it best to only emit the new error in those cases. I can certainly remove some of these checks if its too overcomplicated though.

And do you mind explaining your second point some more? I don't understand it yet!

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, "override" isn't the formally correct term. Certain overloads of operator new/delete are replaceable; see https://en.cppreference.com/w/cpp/language/replacement_function.html.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I understand correctly, thats handled by the !SM.isInSystemHeader check, seeing if this is a user replaced call. Thats tested in the new minimum-alignment test.

Copy link
Collaborator

Choose a reason for hiding this comment

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

No, the declaration of the operator is still in a system header; just the definition is in user code.

Copy link
Contributor Author

@gbMattN gbMattN Oct 27, 2025

Choose a reason for hiding this comment

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

Aha, I think I understand now. If you'd prefer then, I can remove this check, and use the new check whenever the alignment of the type is less than the system default alignment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@efriedma-quic Made the changes we talked about at the conference

if (SanOpts.has(SanitizerKind::Alignment) && IsCustomOverload &&
(DefaultTargetAlignment >
CGM.getContext().getTypeAlignInChars(allocType).getQuantity()))
checkKind = CodeGenFunction::TCK_ConstructorCallOverloadedNew;

// Emit sanitizer checks for pointer value now, so that in the case of an
// array it was checked only once and not at each constructor call. We may
// have already checked that the pointer is non-null.
// FIXME: If we have an array cookie and a potentially-throwing allocator,
// we'll null check the wrong pointer here.
SanitizerSet SkippedChecks;
SkippedChecks.set(SanitizerKind::Null, nullCheck);
EmitTypeCheck(CodeGenFunction::TCK_ConstructorCall,
E->getAllocatedTypeSourceInfo()->getTypeLoc().getBeginLoc(),
result, allocType, result.getAlignment(), SkippedChecks,
numElements);
EmitTypeCheck(
checkKind, E->getAllocatedTypeSourceInfo()->getTypeLoc().getBeginLoc(),
result, allocType, result.getAlignment(), SkippedChecks, numElements);

EmitNewInitializer(*this, E, allocType, elementTy, result, numElements,
allocSizeWithoutCookie);
Expand Down
5 changes: 4 additions & 1 deletion clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -3292,7 +3292,10 @@ class CodeGenFunction : public CodeGenTypeCache {
TCK_NonnullAssign,
/// Checking the operand of a dynamic_cast or a typeid expression. Must be
/// null or an object within its lifetime.
TCK_DynamicOperation
TCK_DynamicOperation,
/// Checking the 'this' poiner for a constructor call, including that the
/// alignment is greater or equal to the targets minimum alignment
TCK_ConstructorCallOverloadedNew
};

/// Determine whether the pointer type check \p TCK permits null pointers.
Expand Down
1 change: 1 addition & 0 deletions compiler-rt/lib/ubsan/ubsan_checks.inc
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ UBSAN_CHECK(NullptrAfterNonZeroOffset, "nullptr-after-nonzero-offset",
UBSAN_CHECK(PointerOverflow, "pointer-overflow", "pointer-overflow")
UBSAN_CHECK(MisalignedPointerUse, "misaligned-pointer-use", "alignment")
UBSAN_CHECK(AlignmentAssumption, "alignment-assumption", "alignment")
UBSAN_CHECK(AlignmentOnOverloadedNew, "alignment-new", "alignment")
UBSAN_CHECK(InsufficientObjectSize, "insufficient-object-size", "object-size")
UBSAN_CHECK(SignedIntegerOverflow, "signed-integer-overflow",
"signed-integer-overflow")
Expand Down
33 changes: 27 additions & 6 deletions compiler-rt/lib/ubsan/ubsan_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,26 @@ enum TypeCheckKind {
TCK_NonnullAssign,
/// Checking the operand of a dynamic_cast or a typeid expression. Must be
/// null or an object within its lifetime.
TCK_DynamicOperation
TCK_DynamicOperation,
/// Checking the 'this' poiner for a constructor call, including that the
/// alignment is greater or equal to the targets minimum alignment
TCK_ConstructorCallOverloadedNew
};

extern const char *const TypeCheckKinds[] = {
"load of", "store to", "reference binding to", "member access within",
"member call on", "constructor call on", "downcast of", "downcast of",
"upcast of", "cast to virtual base of", "_Nonnull binding to",
"dynamic operation on"};
"load of",
"store to",
"reference binding to",
"member access within",
"member call on",
"constructor call on",
"downcast of",
"downcast of",
"upcast of",
"cast to virtual base of",
"_Nonnull binding to",
"dynamic operation on",
"constructor call with pointer from overloaded operator new on"};
}

static void handleTypeMismatchImpl(TypeMismatchData *Data, ValueHandle Pointer,
Expand All @@ -94,7 +106,9 @@ static void handleTypeMismatchImpl(TypeMismatchData *Data, ValueHandle Pointer,
? ErrorType::NullPointerUseWithNullability
: ErrorType::NullPointerUse;
else if (Pointer & (Alignment - 1))
ET = ErrorType::MisalignedPointerUse;
ET = (Data->TypeCheckKind == TCK_ConstructorCallOverloadedNew)
? ErrorType::AlignmentOnOverloadedNew
: ErrorType::MisalignedPointerUse;
else
ET = ErrorType::InsufficientObjectSize;

Expand All @@ -117,6 +131,13 @@ static void handleTypeMismatchImpl(TypeMismatchData *Data, ValueHandle Pointer,
Diag(Loc, DL_Error, ET, "%0 null pointer of type %1")
<< TypeCheckKinds[Data->TypeCheckKind] << Data->Type;
break;
case ErrorType::AlignmentOnOverloadedNew:
Diag(Loc, DL_Error, ET,
"%0 misaligned address %1 for type %2, "
"which requires target minimum assumed alignment of %3")
<< TypeCheckKinds[Data->TypeCheckKind] << (void *)Pointer << Data->Type
<< Alignment;
break;
case ErrorType::MisalignedPointerUse:
Diag(Loc, DL_Error, ET, "%0 misaligned address %1 for type %3, "
"which requires %2 byte alignment")
Expand Down
36 changes: 36 additions & 0 deletions compiler-rt/test/ubsan/TestCases/TypeCheck/minimum-alignment.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// RUN: %clangxx %gmlt -fsanitize=alignment %s -o %t
// RUN: %run %t 2>&1 | FileCheck %s

// UNSUPPORTED: i386
// UNSUPPORTED: armv7l

// These sanitizers already overload the new operator so won't compile this test
// UNSUPPORTED: ubsan-msan
// UNSUPPORTED: ubsan-tsan

#include <cassert>
#include <cstdlib>

void *operator new(std::size_t count) {
constexpr const size_t offset = 8;

// allocate a bit more so we can safely offset it
void *ptr = std::malloc(count + offset);

// verify malloc returned 16 bytes aligned mem
static_assert(__STDCPP_DEFAULT_NEW_ALIGNMENT__ == 16);
assert(((std::ptrdiff_t)ptr & (__STDCPP_DEFAULT_NEW_ALIGNMENT__ - 1)) == 0);

return (char *)ptr + offset;
}

struct Foo {
void *_cookie1, *_cookie2;
};

static_assert(alignof(Foo) == 8);
int main() {
// CHECK: runtime error: constructor call with pointer from overloaded operator new on misaligned address 0x{{.*}} for type 'Foo', which requires target minimum assumed alignment of 16
Foo *f = new Foo;
return 0;
}