[clang] Vasu/add null and alignment checks (PR #175032)
VASU SHARMA via cfe-commits
cfe-commits at lists.llvm.org
Thu Jan 8 09:16:57 PST 2026
https://github.com/vasu-the-sharma created https://github.com/llvm/llvm-project/pull/175032
None
>From f10bbba299bfcda6ac69af7aa3b7e11107484d72 Mon Sep 17 00:00:00 2001
From: vasu-ibm <Vasu.Sharma2 at ibm.com>
Date: Thu, 8 Jan 2026 11:49:39 -0500
Subject: [PATCH 1/2] add coverage ubsan-aggregate-null-align.c
---
.../test/CodeGen/ubsan-aggregate-null-align.c | 48 +++++++++++++++++++
1 file changed, 48 insertions(+)
create mode 100644 clang/test/CodeGen/ubsan-aggregate-null-align.c
diff --git a/clang/test/CodeGen/ubsan-aggregate-null-align.c b/clang/test/CodeGen/ubsan-aggregate-null-align.c
new file mode 100644
index 0000000000000..7ca9d32c3305b
--- /dev/null
+++ b/clang/test/CodeGen/ubsan-aggregate-null-align.c
@@ -0,0 +1,48 @@
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm %s -o - \
+// RUN: -fsanitize=null,alignment | FileCheck %s --check-prefix=CHECK-SANITIZE
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm %s -o - \
+// RUN: | FileCheck %s --check-prefix=CHECK-NO-SANITIZE
+
+struct Small { int x; };
+struct Container { struct Small inner; };
+
+// CHECK-SANITIZE-LABEL: define {{.*}}void @test_direct_assign_ptr(
+// CHECK-SANITIZE: %[[D:.*]] = load ptr, ptr %dest.addr
+// CHECK-SANITIZE: %[[S:.*]] = load ptr, ptr %src.addr
+// CHECK-SANITIZE: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[D]], ptr align 4 %[[S]], i64 4, i1 false)
+//
+// CHECK-NO-SANITIZE-LABEL: define {{.*}}void @test_direct_assign_ptr(
+// CHECK-NO-SANITIZE-NOT: @__ubsan_handle_type_mismatch
+void test_direct_assign_ptr(struct Small *dest, struct Small *src) {
+ *dest = *src;
+}
+
+// CHECK-SANITIZE-LABEL: define {{.*}}void @test_null_dest(
+// CHECK-SANITIZE: %[[D:.*]] = load ptr, ptr %dest
+// CHECK-SANITIZE: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[D]], ptr {{.*}}, i64 4, i1 false)
+//
+// CHECK-NO-SANITIZE-LABEL: define {{.*}}void @test_null_dest(
+// CHECK-NO-SANITIZE-NOT: @__ubsan_handle_type_mismatch
+void test_null_dest(struct Small *src) {
+ struct Small *dest = 0;
+ *dest = *src;
+}
+
+// CHECK-SANITIZE-LABEL: define {{.*}}void @test_nested_struct(
+// CHECK-SANITIZE: %[[VAL1:.*]] = icmp ne ptr %[[C:.*]], null
+// CHECK-SANITIZE: br i1 %{{.*}}, label %cont, label %handler.type_mismatch
+//
+// CHECK-NO-SANITIZE-LABEL: define {{.*}}void @test_nested_struct(
+// CHECK-NO-SANITIZE-NOT: @__ubsan_handle_type_mismatch
+void test_nested_struct(struct Container *c, struct Small *s) {
+ c->inner = *s;
+}
+
+// CHECK-SANITIZE-LABEL: define {{.*}}void @test_comma_operator(
+// CHECK-SANITIZE: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %{{.*}}, ptr align 4 %{{.*}}, i64 4, i1 false)
+//
+// CHECK-NO-SANITIZE-LABEL: define {{.*}}void @test_comma_operator(
+// CHECK-NO-SANITIZE-NOT: @__ubsan_handle_type_mismatch
+void test_comma_operator(struct Small *dest, struct Small *src) {
+ *dest = (0, *src);
+}
>From cc64e3f111e790760b1c624fa27ae03701f1d021 Mon Sep 17 00:00:00 2001
From: vasu-ibm <Vasu.Sharma2 at ibm.com>
Date: Thu, 8 Jan 2026 12:15:28 -0500
Subject: [PATCH 2/2] add null and alignment checks for aggregates
---
clang/lib/CodeGen/CGExprAgg.cpp | 43 +++------
clang/lib/CodeGen/CGExprCXX.cpp | 29 +++---
.../test/CodeGen/ubsan-aggregate-null-align.c | 91 +++++++++++--------
3 files changed, 82 insertions(+), 81 deletions(-)
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 7cc4d6c8f06f6..919e510a82af0 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -1292,45 +1292,29 @@ static bool isBlockVarRef(const Expr *E) {
void AggExprEmitter::VisitBinAssign(const BinaryOperator *E) {
ApplyAtomGroup Grp(CGF.getDebugInfo());
- // For an assignment to work, the value on the right has
- // to be compatible with the value on the left.
assert(CGF.getContext().hasSameUnqualifiedType(E->getLHS()->getType(),
E->getRHS()->getType())
- && "Invalid assignment");
+ && "Invalid assignment");
- // If the LHS might be a __block variable, and the RHS can
- // potentially cause a block copy, we need to evaluate the RHS first
- // so that the assignment goes the right place.
- // This is pretty semantically fragile.
if (isBlockVarRef(E->getLHS()) &&
E->getRHS()->HasSideEffects(CGF.getContext())) {
- // Ensure that we have a destination, and evaluate the RHS into that.
EnsureDest(E->getRHS()->getType());
Visit(E->getRHS());
-
- // Now emit the LHS and copy into it.
LValue LHS = CGF.EmitCheckedLValue(E->getLHS(), CodeGenFunction::TCK_Store);
- // That copy is an atomic copy if the LHS is atomic.
if (LHS.getType()->isAtomicType() ||
CGF.LValueIsSuitableForInlineAtomic(LHS)) {
CGF.EmitAtomicStore(Dest.asRValue(), LHS, /*isInit*/ false);
return;
}
-
- EmitCopy(E->getLHS()->getType(),
- AggValueSlot::forLValue(LHS, AggValueSlot::IsDestructed,
- needsGC(E->getLHS()->getType()),
- AggValueSlot::IsAliased,
- AggValueSlot::MayOverlap),
- Dest);
+ EmitFinalDestCopy(E->getLHS()->getType(), LHS);
return;
}
- LValue LHS = CGF.EmitLValue(E->getLHS());
+ // ✅ FIX: Use EmitCheckedLValue for LHS
+ LValue LHS = CGF.EmitCheckedLValue(E->getLHS(), CodeGenFunction::TCK_Store);
- // If we have an atomic type, evaluate into the destination and then
- // do an atomic copy.
+ // ✅ RE-ADD: Original atomic handling logic
if (LHS.getType()->isAtomicType() ||
CGF.LValueIsSuitableForInlineAtomic(LHS)) {
EnsureDest(E->getRHS()->getType());
@@ -1339,20 +1323,23 @@ void AggExprEmitter::VisitBinAssign(const BinaryOperator *E) {
return;
}
- // Codegen the RHS so that it stores directly into the LHS.
+ // ✅ FIX: Handle RHS based on LValue/RValue
AggValueSlot LHSSlot = AggValueSlot::forLValue(
LHS, AggValueSlot::IsDestructed, needsGC(E->getLHS()->getType()),
AggValueSlot::IsAliased, AggValueSlot::MayOverlap);
- // A non-volatile aggregate destination might have volatile member.
- if (!LHSSlot.isVolatile() &&
- CGF.hasVolatileMember(E->getLHS()->getType()))
- LHSSlot.setVolatile(true);
- CGF.EmitAggExpr(E->getRHS(), LHSSlot);
+ if (E->getRHS()->isLValue()) {
+ LValue RHS = CGF.EmitCheckedLValue(E->getRHS(), CodeGenFunction::TCK_Load);
+ CGF.EmitAggregateCopy(LHS, RHS, E->getType(), Dest.isVolatile());
+ } else {
+ if (!LHSSlot.isVolatile() && CGF.hasVolatileMember(E->getLHS()->getType()))
+ LHSSlot.setVolatile(true);
+ CGF.EmitAggExpr(E->getRHS(), LHSSlot);
+ }
- // Copy into the destination if the assignment isn't ignored.
EmitFinalDestCopy(E->getType(), LHS);
+ // ✅ RE-ADD: Original Nontrivial C struct destruction logic
if (!Dest.isIgnored() && !Dest.isExternallyDestructed() &&
E->getType().isDestructedType() == QualType::DK_nontrivial_c_struct)
CGF.pushDestroy(QualType::DK_nontrivial_c_struct, Dest.getAddress(),
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index ce2ed9026fa1f..eae27a6a3f1c8 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -267,7 +267,7 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorMemberCallExpr(
if (auto *OCE = dyn_cast<CXXOperatorCallExpr>(CE)) {
if (OCE->isAssignmentOp()) {
if (TrivialAssignment) {
- TrivialAssignmentRHS = EmitLValue(CE->getArg(1));
+ TrivialAssignmentRHS = EmitCheckedLValue(CE->getArg(1), TCK_Load);
} else {
RtlArgs = &RtlArgStorage;
EmitCallArgs(*RtlArgs, MD->getType()->castAs<FunctionProtoType>(),
@@ -309,22 +309,21 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorMemberCallExpr(
if (TrivialForCodegen) {
if (isa<CXXDestructorDecl>(MD))
return RValue::get(nullptr);
+ }
- if (TrivialAssignment) {
- // We don't like to generate the trivial copy/move assignment operator
- // when it isn't necessary; just produce the proper effect here.
- // It's important that we use the result of EmitLValue here rather than
- // emitting call arguments, in order to preserve TBAA information from
- // the RHS.
- LValue RHS = isa<CXXOperatorCallExpr>(CE)
- ? TrivialAssignmentRHS
- : EmitLValue(*CE->arg_begin());
- EmitAggregateAssign(This, RHS, CE->getType());
- return RValue::get(This.getPointer(*this));
- }
+ if (TrivialAssignment) {
+ // 1. Evaluate 'this' (Destination) as a checked store.
+ LValue This = EmitCheckedLValue(Base, TCK_Store);
+
+ // 2. Evaluate RHS (Source) as a checked load.
+ // If it's an operator call (a = b), we use the RHS evaluated at line 270.
+ // If it's a direct call (constructor), we evaluate the first argument.
+ LValue RHS = isa<CXXOperatorCallExpr>(CE)
+ ? TrivialAssignmentRHS
+ : EmitCheckedLValue(*CE->arg_begin(), TCK_Load);
- assert(MD->getParent()->mayInsertExtraPadding() &&
- "unknown trivial member function");
+ EmitAggregateAssign(This, RHS, CE->getType());
+ return RValue::get(This.getPointer(*this));
}
// Compute the function type we're calling.
diff --git a/clang/test/CodeGen/ubsan-aggregate-null-align.c b/clang/test/CodeGen/ubsan-aggregate-null-align.c
index 7ca9d32c3305b..18133327f0fc8 100644
--- a/clang/test/CodeGen/ubsan-aggregate-null-align.c
+++ b/clang/test/CodeGen/ubsan-aggregate-null-align.c
@@ -1,48 +1,63 @@
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm %s -o - \
-// RUN: -fsanitize=null,alignment | FileCheck %s --check-prefix=CHECK-SANITIZE
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm %s -o - \
-// RUN: | FileCheck %s --check-prefix=CHECK-NO-SANITIZE
-
-struct Small { int x; };
-struct Container { struct Small inner; };
-
-// CHECK-SANITIZE-LABEL: define {{.*}}void @test_direct_assign_ptr(
-// CHECK-SANITIZE: %[[D:.*]] = load ptr, ptr %dest.addr
-// CHECK-SANITIZE: %[[S:.*]] = load ptr, ptr %src.addr
-// CHECK-SANITIZE: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[D]], ptr align 4 %[[S]], i64 4, i1 false)
-//
-// CHECK-NO-SANITIZE-LABEL: define {{.*}}void @test_direct_assign_ptr(
-// CHECK-NO-SANITIZE-NOT: @__ubsan_handle_type_mismatch
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=alignment,null \
+// RUN: -emit-llvm -std=c23 %s -o - \
+// RUN: | FileCheck %s --check-prefixes=CHECK,CHECK-UBSAN
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -std=c23 %s -o - \
+// RUN: | FileCheck %s --check-prefixes=CHECK,CHECK-NO-UBSAN
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsanitize=alignment,null \
+// RUN: -emit-llvm -xc++ %s -o - \
+// RUN: | FileCheck %s --check-prefixes=CHECK,CHECK-UBSAN
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -xc++ %s -o - \
+// RUN: | FileCheck %s --check-prefixes=CHECK,CHECK-NO-UBSAN
+
+typedef struct Small { int x; } Small;
+typedef struct Container { struct Small inner; } Container;
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// CHECK-LABEL: define {{.*}}void @test_direct_assign_ptr(
void test_direct_assign_ptr(struct Small *dest, struct Small *src) {
- *dest = *src;
-}
+ // CHECK-UBSAN: %[[D:.*]] = load ptr, ptr %dest.addr
+ // CHECK-UBSAN: %[[S:.*]] = load ptr, ptr %src.addr
+
+ // Verify LHS (Dest) Check
+ // CHECK-UBSAN: %[[D_NULL:.*]] = icmp ne ptr %[[D]], null
+ // CHECK-UBSAN: %[[D_ALIGN:.*]] = and i64 %{{.*}}, 3
+ // CHECK-UBSAN: %[[D_ALIGN_OK:.*]] = icmp eq i64 %[[D_ALIGN]], 0
+ // CHECK-UBSAN: %[[D_OK:.*]] = and i1 %[[D_NULL]], %[[D_ALIGN_OK]]
+ // CHECK-UBSAN: br i1 %[[D_OK]], label %[[D_CONT:.*]], label %[[D_HANDLER:.*]]
+
+ // CHECK-UBSAN: [[D_HANDLER]]:
+ // CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1_abort
+ // CHECK-UBSAN: unreachable
+
+ // CHECK-UBSAN: [[D_CONT]]:
+ // Verify RHS (Src) Check
+ // CHECK-UBSAN: %[[S_NULL:.*]] = icmp ne ptr %[[S]], null
+ // CHECK-UBSAN: br i1 %[[S_NULL]], label %[[S_CONT:.*]], label %[[S_HANDLER:.*]]
+
+ // CHECK-UBSAN: [[S_HANDLER]]:
+ // CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1_abort
-// CHECK-SANITIZE-LABEL: define {{.*}}void @test_null_dest(
-// CHECK-SANITIZE: %[[D:.*]] = load ptr, ptr %dest
-// CHECK-SANITIZE: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[D]], ptr {{.*}}, i64 4, i1 false)
-//
-// CHECK-NO-SANITIZE-LABEL: define {{.*}}void @test_null_dest(
-// CHECK-NO-SANITIZE-NOT: @__ubsan_handle_type_mismatch
-void test_null_dest(struct Small *src) {
- struct Small *dest = 0;
+ // CHECK-UBSAN: [[S_CONT]]:
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %[[D]], ptr align 4 %[[S]], i64 4, i1 false)
+
+ // CHECK-NO-UBSAN-NOT: @__ubsan_handle_type_mismatch
*dest = *src;
}
-// CHECK-SANITIZE-LABEL: define {{.*}}void @test_nested_struct(
-// CHECK-SANITIZE: %[[VAL1:.*]] = icmp ne ptr %[[C:.*]], null
-// CHECK-SANITIZE: br i1 %{{.*}}, label %cont, label %handler.type_mismatch
-//
-// CHECK-NO-SANITIZE-LABEL: define {{.*}}void @test_nested_struct(
-// CHECK-NO-SANITIZE-NOT: @__ubsan_handle_type_mismatch
+// CHECK-LABEL: define {{.*}}void @test_nested_struct(
void test_nested_struct(struct Container *c, struct Small *s) {
+ // CHECK-UBSAN: %[[C:.*]] = load ptr, ptr %c.addr
+ // CHECK-UBSAN: icmp ne ptr %[[C]], null
+ // CHECK-UBSAN: br i1 %{{.*}}, label %[[CONT:.*]], label %[[HANDLER:.*]]
+
+ // CHECK-UBSAN: [[HANDLER]]:
+ // CHECK-UBSAN: call void @__ubsan_handle_type_mismatch_v1_abort
c->inner = *s;
}
-// CHECK-SANITIZE-LABEL: define {{.*}}void @test_comma_operator(
-// CHECK-SANITIZE: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %{{.*}}, ptr align 4 %{{.*}}, i64 4, i1 false)
-//
-// CHECK-NO-SANITIZE-LABEL: define {{.*}}void @test_comma_operator(
-// CHECK-NO-SANITIZE-NOT: @__ubsan_handle_type_mismatch
-void test_comma_operator(struct Small *dest, struct Small *src) {
- *dest = (0, *src);
+#ifdef __cplusplus
}
+#endif
More information about the cfe-commits
mailing list