[clang] [clang][CodeGen] Choose the array-bounds comparison from context (PR #218015)
Usama Hameed via cfe-commits
cfe-commits at lists.llvm.org
Fri Aug 21 12:46:48 PDT 2026
https://github.com/usama54321 created https://github.com/llvm/llvm-project/pull/218015
`-fsanitize=array-bounds` emits `icmp ult` when a subscripted element must exist and
`icmp ule` when the expression only forms an address, since `&a[N]` and `a + N` are well
defined (C99 6.5.3.2p3, 6.5.6p8). Which one is right depends on the context the lvalue
appears in, not on the subscript — so it is an inherited attribute. Today it is guessed
instead: `EmitCheckedLValue` is strict when its operand *happens to be* a subscript, so any
wrapper defeats it (`(a[i]) = 1`, `*&a[i] = 1`), and emitters that never called it never had
the information at all (`a[i]++`, complex, aggregates, ARC, member calls, `.*`, reference
binding). This threads an `ObjectRequirement_t` down through the lvalue emitters so each
context states what it needs, and removes the guess.
The order of commits is:
- A test commit recording the comparison emitted today for 91 contexts across C,
C++, ObjC ARC and `__ptrauth`.
- The plumbing, NFC — the parameter exists but nothing requests it.
- One commit per rule, each citing the rule and flipping only the lines it reaches.
- A commit documenting the one gap left open, with an XFAIL test.
- EmitCheckedLValue` is deleted, once no caller depends on its guess.
fixes #215699
>From 798a8050c2cefd9a4f9605fdd93f946da22dd45c Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:54:30 -0700
Subject: [PATCH 01/17] [clang][CodeGen][NFC] Record which comparison
array-bounds emits per context
-fsanitize=array-bounds emits `icmp ult` when a subscripted element must exist
and `icmp ule` when the expression only forms an address, since forming `&a[N]`
is well defined (C99 6.5.6p8). Nothing tests that distinction today: the
dedicated bounds tests assert only that a handler is called.
Record the comparison emitted for every context a subscript can appear in. Many
are wrong, so later commits in this series change individual CHECK lines and each
shows in its diff exactly which contexts it affects. Cases that form an address
and then use the object are paired with the direct spelling of the same access,
so the two cannot silently disagree.
---
.../CodeGen/ubsan-array-bounds-baseline-arc.m | 51 ++++
.../ubsan-array-bounds-baseline-ptrauth.c | 24 ++
.../CodeGen/ubsan-array-bounds-baseline.c | 260 +++++++++++++++++
.../ubsan-array-bounds-baseline.cpp | 273 ++++++++++++++++++
4 files changed, 608 insertions(+)
create mode 100644 clang/test/CodeGen/ubsan-array-bounds-baseline-arc.m
create mode 100644 clang/test/CodeGen/ubsan-array-bounds-baseline-ptrauth.c
create mode 100644 clang/test/CodeGen/ubsan-array-bounds-baseline.c
create mode 100644 clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline-arc.m b/clang/test/CodeGen/ubsan-array-bounds-baseline-arc.m
new file mode 100644
index 0000000000000..aeea218214688
--- /dev/null
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline-arc.m
@@ -0,0 +1,51 @@
+// Baseline for the Objective-C ARC contexts a subscript can appear in. Split
+// from ubsan-array-bounds-baseline.c because ARC's __weak needs a Darwin
+// triple, not because the contexts are unrelated.
+// RUN: %clang_cc1 -triple arm64-apple-macosx11.0.0 -emit-llvm \
+// RUN: -fsanitize=array-bounds \
+// RUN: -Wno-array-bounds -fobjc-arc -fobjc-runtime-has-weak -fblocks \
+// RUN: %s -o - | FileCheck %s
+
+__weak id wa[4];
+id st[4];
+__unsafe_unretained id ua[4];
+__strong id *p;
+
+//===----------------------------------------------------------------------===//
+// Contexts that require the element to exist.
+//===----------------------------------------------------------------------===//
+
+// CHECK-LABEL: define {{.*}}@arc_weak_store(
+// CHECK: icmp ult i64 {{.*}}, 4
+void arc_weak_store(int i, id v) { wa[i] = v; }
+
+// CHECK-LABEL: define {{.*}}@arc_strong_store(
+// CHECK: icmp ule i64 {{.*}}, 4
+void arc_strong_store(int i, id v) { st[i] = v; }
+
+// CHECK-LABEL: define {{.*}}@arc_weak_load(
+// CHECK: icmp ule i64 {{.*}}, 4
+void arc_weak_load(int i, id *o) { *o = wa[i]; }
+
+// The remaining lifetimes take their own emitters, so each is covered rather
+// than assumed to follow from the two above.
+
+// CHECK-LABEL: define {{.*}}@arc_strong_load(
+// CHECK: icmp ule i64 {{.*}}, 4
+void arc_strong_load(int i, id *o) { *o = st[i]; }
+
+// CHECK-LABEL: define {{.*}}@arc_unsafe_store(
+// CHECK: icmp ule i64 {{.*}}, 4
+void arc_unsafe_store(int i, id v) { ua[i] = v; }
+
+// CHECK-LABEL: define {{.*}}@arc_unsafe_load(
+// CHECK: icmp ule i64 {{.*}}, 4
+void arc_unsafe_load(int i, id *o) { *o = ua[i]; }
+
+//===----------------------------------------------------------------------===//
+// Address-only: `ule` is required.
+//===----------------------------------------------------------------------===//
+
+// CHECK-LABEL: define {{.*}}@arc_ctl_addr(
+// CHECK: icmp ule i64 {{.*}}, 4
+void arc_ctl_addr(int i) { p = &st[i]; }
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline-ptrauth.c b/clang/test/CodeGen/ubsan-array-bounds-baseline-ptrauth.c
new file mode 100644
index 0000000000000..c5814a6e25973
--- /dev/null
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline-ptrauth.c
@@ -0,0 +1,24 @@
+// A __ptrauth-qualified pointer is loaded through its own path in
+// CGPointerAuth.cpp, which needs a triple with pointer authentication, hence a
+// file of its own. See ubsan-array-bounds-baseline.c for the rest.
+//
+// REQUIRES: aarch64-registered-target
+// RUN: %clang_cc1 -triple arm64e-apple-macosx11.0.0 -fptrauth-calls \
+// RUN: -fptrauth-intrinsics -fptrauth-returns -emit-llvm \
+// RUN: -fsanitize=array-bounds -Wno-array-bounds %s -o - | FileCheck %s
+
+#define AQ __ptrauth(2, 1, 42)
+
+int *AQ pa[4];
+
+// CHECK-LABEL: define {{.*}}@p_load(
+// CHECK: icmp ult i64 {{.*}}, 4
+int *p_load(int i) { return pa[i]; }
+
+// CHECK-LABEL: define {{.*}}@p_load_paren(
+// CHECK: icmp ult i64 {{.*}}, 4
+int *p_load_paren(int i) { return (pa[i]); }
+
+// CHECK-LABEL: define {{.*}}@p_load_deref_addr(
+// CHECK: icmp ule i64 {{.*}}, 4
+int *p_load_deref_addr(int i) { return *&pa[i]; }
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline.c b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
new file mode 100644
index 0000000000000..cf91740887125
--- /dev/null
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
@@ -0,0 +1,260 @@
+// Baseline record of the comparison -fsanitize=array-bounds emits for each
+// context a subscript can appear in. `ult` requires the element to exist; `ule`
+// also accepts an index equal to the bound, which is right only where the
+// expression forms an address without reaching the object (C99 6.5.6p8,
+// C99 6.5.3.2p3).
+//
+// A case named `..._arrow` spells the same access as its non-arrow counterpart,
+// through a pointer instead; C99 6.5.2.3p4 makes them one expression, so the two
+// must agree.
+//
+// Later commits in this series change individual CHECK lines, so each shows in
+// its diff exactly which contexts it affects.
+//
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -fsanitize=array-bounds \
+// RUN: -Wno-array-bounds -std=c11 %s -o - | FileCheck %s
+//
+// The __block section needs -fblocks, so it is checked under its own prefix.
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -fsanitize=array-bounds \
+// RUN: -Wno-array-bounds -std=c11 -fblocks -DBLOCKS %s -o - \
+// RUN: | FileCheck %s --check-prefixes=CHECK,BLOCKS
+
+struct S {
+ int x;
+};
+typedef int v4 __attribute__((ext_vector_type(4)));
+struct CB {
+ int n;
+ int fam[] __attribute__((counted_by(n)));
+};
+
+int a[4];
+int a2[4][4];
+struct S sa[4];
+_Complex double ca[4];
+v4 va[4];
+int *p;
+struct S *q;
+int v;
+struct S aggl;
+_Complex double cv;
+void sink(struct S);
+
+//===----------------------------------------------------------------------===//
+// Contexts that require the element to exist.
+//===----------------------------------------------------------------------===//
+
+// CHECK-LABEL: define {{.*}}@c_store(
+// CHECK: icmp ult i64 {{.*}}, 4
+void c_store(int i) { a[i] = 1; }
+
+// CHECK-LABEL: define {{.*}}@c_load(
+// CHECK: icmp ult i64 {{.*}}, 4
+void c_load(int i) { v = a[i]; }
+
+// CHECK-LABEL: define {{.*}}@c_load_deref_addr(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_load_deref_addr(int i) { v = *&a[i]; }
+
+// CHECK-LABEL: define {{.*}}@c_load_cast(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_load_cast(int i) { v = *(int *)&a[i]; }
+
+// CHECK-LABEL: define {{.*}}@c_compound(
+// CHECK: icmp ult i64 {{.*}}, 4
+void c_compound(int i) { a[i] += 1; }
+
+// CHECK-LABEL: define {{.*}}@c_compound_paren(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_compound_paren(int i) { (a[i]) += 1; }
+
+// CHECK-LABEL: define {{.*}}@c_compound_deref_addr(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_compound_deref_addr(int i) { *&a[i] += 1; }
+
+// CHECK-LABEL: define {{.*}}@c_postinc(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_postinc(int i) { a[i]++; }
+
+// CHECK-LABEL: define {{.*}}@c_postdec(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_postdec(int i) { a[i]--; }
+
+// CHECK-LABEL: define {{.*}}@c_preinc(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_preinc(int i) { ++a[i]; }
+
+// CHECK-LABEL: define {{.*}}@c_predec(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_predec(int i) { --a[i]; }
+
+// CHECK-LABEL: define {{.*}}@c_agg_store(
+// CHECK: icmp ult i64 {{.*}}, 4
+void c_agg_store(int i) { sa[i] = aggl; }
+
+// CHECK-LABEL: define {{.*}}@c_agg_store_paren(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_agg_store_paren(int i) { (sa[i]) = aggl; }
+
+// CHECK-LABEL: define {{.*}}@c_agg_store_deref_addr(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_agg_store_deref_addr(int i) { *&sa[i] = aggl; }
+
+// CHECK-LABEL: define {{.*}}@c_agg_load(
+// CHECK: icmp ult i64 {{.*}}, 4
+void c_agg_load(int i) { aggl = sa[i]; }
+
+// CHECK-LABEL: define {{.*}}@c_agg_load_deref_addr(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_agg_load_deref_addr(int i) { aggl = *&sa[i]; }
+
+// CHECK-LABEL: define {{.*}}@c_member_dot(
+// CHECK: icmp ult i64 {{.*}}, 4
+void c_member_dot(int i) { sa[i].x = 1; }
+
+// CHECK-LABEL: define {{.*}}@c_member_dot_deref_addr(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_member_dot_deref_addr(int i) { (*&sa[i]).x = 1; }
+
+// CHECK-LABEL: define {{.*}}@c_member_dot_deref_addr_load(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_member_dot_deref_addr_load(int i) { v = (*&sa[i]).x; }
+
+// CHECK-LABEL: define {{.*}}@c_member_arrow(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_member_arrow(int i) { (&sa[i])->x = 1; }
+
+// Taking the address of a member is not one of the rewrites in C99 6.5.3.2p3,
+// so unlike ctl_struct_addr these require the element to exist.
+// CHECK-LABEL: define {{.*}}@c_member_dot_addr(
+// CHECK: icmp ult i64 {{.*}}, 4
+void c_member_dot_addr(int i) { p = &sa[i].x; }
+
+// CHECK-LABEL: define {{.*}}@c_member_arrow_addr(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_member_arrow_addr(int i) { p = &(&sa[i])->x; }
+
+// CHECK-LABEL: define {{.*}}@c_complex_real(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_complex_real(int i) { __real__ ca[i] = 1; }
+
+// CHECK-LABEL: define {{.*}}@c_complex_imag(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_complex_imag(int i) { __imag__ ca[i] = 1; }
+
+// CHECK-LABEL: define {{.*}}@c_complex_load(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_complex_load(int i) { cv = ca[i]; }
+
+// CHECK-LABEL: define {{.*}}@c_complex_store(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_complex_store(int i) { ca[i] = cv; }
+
+// CHECK-LABEL: define {{.*}}@c_complex_compound(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_complex_compound(int i) { ca[i] += cv; }
+
+// CHECK-LABEL: define {{.*}}@c_complex_incdec(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_complex_incdec(int i) { ca[i]++; }
+
+// CHECK-LABEL: define {{.*}}@c_vec_elem(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_vec_elem(int i) { va[i].x = 1; }
+
+// CHECK-LABEL: define {{.*}}@c_vec_elem_arrow(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_vec_elem_arrow(int i) { (&va[i])->x = 1; }
+
+// The component belongs to a temporary, not to an element; the
+// element is read in order to build it.
+// CHECK-LABEL: define {{.*}}@c_vec_rvalue(
+// CHECK: icmp ult i64 {{.*}}, 4
+void c_vec_rvalue(int i) {
+ v = (va[i] + va[0]).x;
+}
+
+// CHECK-LABEL: define {{.*}}@c_byval(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_byval(int i) { sink(sa[i]); }
+
+// Same expression as c_store: C99 6.5.3.2p3 makes `&*E` into `E`.
+// CHECK-LABEL: define {{.*}}@c_deref_addr(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_deref_addr(int i) { *&a[i] = 1; }
+
+// CHECK-LABEL: define {{.*}}@c_paren(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_paren(int i) { (a[i]) = 1; }
+
+// CHECK-LABEL: define {{.*}}@c_extension(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_extension(int i) { __extension__(a[i]) = 1; }
+
+// CHECK-LABEL: define {{.*}}@c_cast_store(
+// CHECK: icmp ule i64 {{.*}}, 4
+void c_cast_store(int i) { *(int *)&a[i] = 1; }
+
+// CHECK-LABEL: define {{.*}}@c_counted(
+// CHECK: icmp ult i32 {{.*}}
+void c_counted(struct CB *s, int i) { s->fam[i] = 1; }
+
+//===----------------------------------------------------------------------===//
+// __block storage. AggExprEmitter::VisitBinAssign has a separate path for a
+// __block LHS whose RHS has side effects -- the comment there calls it
+// "pretty semantically fragile" -- so both shapes are covered: only the first
+// reaches that path.
+//===----------------------------------------------------------------------===//
+
+#ifdef BLOCKS
+struct S mk(void);
+
+// A side-effecting right operand takes a different path from
+// blk_plain below.
+// BLOCKS-LABEL: define {{.*}}@blk_side_effect(
+// BLOCKS: icmp ult i64 {{.*}}, 4
+void blk_side_effect(int i) {
+ __block struct S barr[4];
+ barr[i] = mk();
+ (void)barr;
+}
+
+// BLOCKS-LABEL: define {{.*}}@blk_plain(
+// BLOCKS: icmp ult i64 {{.*}}, 4
+void blk_plain(int i, struct S v) {
+ __block struct S barr[4];
+ barr[i] = v;
+ (void)barr;
+}
+#endif
+
+//===----------------------------------------------------------------------===//
+// Address-only contexts. `ule` is required here: these expressions form an
+// address without reaching the object, so a strict comparison would reject
+// correct code.
+//===----------------------------------------------------------------------===//
+
+// CHECK-LABEL: define {{.*}}@ctl_addr(
+// CHECK: icmp ule i64 {{.*}}, 4
+void ctl_addr(int i) { p = &a[i]; }
+
+// CHECK-LABEL: define {{.*}}@ctl_decay(
+// CHECK: icmp ule i64 {{.*}}, 4
+void ctl_decay(int i) { p = a + i; }
+
+// CHECK-LABEL: define {{.*}}@ctl_row(
+// CHECK: icmp ule i64 {{.*}}, 4
+void ctl_row(int i) { p = a2[i]; }
+
+// Same expression as ctl_row: C99 6.5.3.2p3.
+// CHECK-LABEL: define {{.*}}@ctl_row_elem0(
+// CHECK: icmp ult i64 {{.*}}, 4
+void ctl_row_elem0(int i) { p = &a2[i][0]; }
+
+// CHECK-LABEL: define {{.*}}@ctl_struct_addr(
+// CHECK: icmp ule i64 {{.*}}, 4
+void ctl_struct_addr(int i) { q = &sa[i]; }
+
+// CHECK-LABEL: define {{.*}}@ctl_addr_deref(
+// CHECK: icmp ule i64 {{.*}}, 4
+void ctl_addr_deref(int i) { p = &*&a[i]; }
diff --git a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
new file mode 100644
index 0000000000000..731eb21e316b0
--- /dev/null
+++ b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
@@ -0,0 +1,273 @@
+// C++ counterpart of ubsan-array-bounds-baseline.c: reference binding, member
+// calls, base conversions, pointers to members and default arguments. As there,
+// a case named `..._arrow` is the same access as its non-arrow counterpart and
+// the two must agree.
+//
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -fsanitize=array-bounds \
+// RUN: -Wno-array-bounds -std=c++17 %s -o - | FileCheck %s
+//
+// Constructors are emitted after the free functions, so the cases whose check
+// lands in one are checked under their own prefix.
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -fsanitize=array-bounds \
+// RUN: -Wno-array-bounds -std=c++17 %s -o - | FileCheck %s \
+// RUN: --check-prefix=CTOR
+
+struct T {
+ T();
+ ~T();
+};
+struct M {
+ int f;
+ void m();
+};
+struct Base {
+ int b;
+};
+struct Derived : Base {
+ int d;
+};
+struct Agg {
+ int x;
+};
+
+int a[4];
+int a2[4][4];
+M ma[4];
+Derived da[4];
+Agg agga[4];
+Agg agglocal;
+int gidx;
+int *p;
+M *q;
+int M::*pmf = &M::f;
+
+// Separate from M so that adding a vptr does not perturb the cases above.
+struct MS {
+ int f;
+ static void stat();
+ virtual void virt();
+};
+MS msa[4];
+
+//===----------------------------------------------------------------------===//
+// Contexts that require the element to exist.
+//===----------------------------------------------------------------------===//
+
+// CHECK-LABEL: define {{.*}}@_Z10x_ref_bindi(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_ref_bind(int i) {
+ int &r = a[i];
+ (void)r;
+}
+
+// CHECK-LABEL: define {{.*}}@_Z11x_ref_consti(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_ref_const(int i) {
+ const int &r = a[i];
+ (void)r;
+}
+
+// CHECK-LABEL: define {{.*}}@_Z14x_ref_cleanupsi(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_ref_cleanups(int i) {
+ int &r = (T(), a[i]);
+ (void)r;
+}
+
+// CHECK-LABEL: define {{.*}}@_Z12x_ref_returni(
+// CHECK: icmp ule i64 {{.*}}, 4
+int &x_ref_return(int i) { return a[i]; }
+
+void takes_ref(int &);
+// CHECK-LABEL: define {{.*}}@_Z11x_ref_parami(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_ref_param(int i) { takes_ref(a[i]); }
+
+// CHECK-LABEL: define {{.*}}@_Z16x_ref_structuredi(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_ref_structured(int i) {
+ auto &[e] = agga[i];
+ (void)e;
+}
+
+// A default argument bound to a reference.
+int &pick(int &r = a[gidx]);
+// CHECK-LABEL: define {{.*}}@_Z13x_default_argv(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_default_arg() { pick(); }
+
+// A default member initializer bound to a reference. The check lands in the
+// constructor.
+struct R {
+ int &r = a[gidx];
+};
+// CTOR-LABEL: define {{.*}}@_ZN1RC2Ev(
+// CTOR: icmp ule i64 {{.*}}, 4
+void x_default_init() {
+ R x;
+ (void)x;
+}
+
+struct S {
+ int &r;
+ S(int i) : r(a[i]) {}
+};
+// CTOR-LABEL: define {{.*}}@_ZN1SC2Ei(
+// CTOR: icmp ule i64 {{.*}}, 4
+void x_ref_meminit(int i) {
+ S x(i);
+ (void)x;
+}
+
+// CHECK-LABEL: define {{.*}}@_Z13x_member_calli(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_member_call(int i) { ma[i].m(); }
+
+// CHECK-LABEL: define {{.*}}@_Z19x_member_call_arrowi(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_member_call_arrow(int i) { (&ma[i])->m(); }
+
+// CHECK-LABEL: define {{.*}}@_Z21x_member_call_virtuali(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_member_call_virtual(int i) { msa[i].virt(); }
+
+// TODO: confirm with reviewers. C++ [class.static]p2 says the object expression
+// is evaluated, but the call does not use the object, and whether evaluating a
+// glvalue that designates no object is undefined when nothing reads it is not
+// settled (see CWG 232). Permissive is the conservative answer; CHECK-NOT guards
+// against the member-call rule over-applying to it.
+// CHECK-LABEL: define {{.*}}@_Z20x_member_call_statici(
+// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK-NOT: icmp ult
+void x_member_call_static(int i) { msa[i].stat(); }
+
+// CHECK-LABEL: define {{.*}}@_Z12x_member_doti(
+// CHECK: icmp ult i64 {{.*}}, 4
+void x_member_dot(int i) { ma[i].f = 1; }
+
+// CHECK-LABEL: define {{.*}}@_Z14x_member_arrowi(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_member_arrow(int i) { (&ma[i])->f = 1; }
+
+// CHECK-LABEL: define {{.*}}@_Z20x_trivial_assign_lhsi(
+// CHECK: icmp ult i64 {{.*}}, 4
+void x_trivial_assign_lhs(int i) { agga[i] = agglocal; }
+
+// CHECK-LABEL: define {{.*}}@_Z20x_trivial_assign_rhsi(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_trivial_assign_rhs(int i) { agglocal = agga[i]; }
+
+// The same assignment spelled as an explicit operator= call.
+// CHECK-LABEL: define {{.*}}@_Z17x_explicit_assigni(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_explicit_assign(int i) { agglocal.operator=(agga[i]); }
+
+// CHECK-LABEL: define {{.*}}@_Z17x_derived_to_basei(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_derived_to_base(int i) {
+ Base &r = da[i];
+ (void)r;
+}
+
+// CHECK-LABEL: define {{.*}}@_Z18x_static_cast_basei(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_static_cast_base(int i) {
+ Base &r = static_cast<Base &>(da[i]);
+ (void)r;
+}
+
+// The other direction.
+Base ba[4];
+// CHECK-LABEL: define {{.*}}@_Z17x_base_to_derivedi(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_base_to_derived(int i) {
+ Derived &r = static_cast<Derived &>(ba[i]);
+ (void)r;
+}
+
+// CHECK-LABEL: define {{.*}}@_Z15x_ptr_to_memberi(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_ptr_to_member(int i) { ma[i].*pmf = 1; }
+
+// CHECK-LABEL: define {{.*}}@_Z21x_ptr_to_member_arrowi(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_ptr_to_member_arrow(int i) { (&ma[i])->*pmf = 1; }
+
+// CHECK-LABEL: define {{.*}}@_Z10x_cond_armib(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_cond_arm(int i, bool c) { (c ? a[i] : a[0]) = 1; }
+
+// An assignment is a prvalue in C and an lvalue in C++, so the four cases below
+// reach a different emitter than their equivalents in the C file.
+// CHECK-LABEL: define {{.*}}@_Z7x_storei(
+// CHECK: icmp ult i64 {{.*}}, 4
+void x_store(int i) { a[i] = 1; }
+
+// CHECK-LABEL: define {{.*}}@_Z7x_pareni(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_paren(int i) { (a[i]) = 1; }
+
+// CHECK-LABEL: define {{.*}}@_Z12x_deref_addri(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_deref_addr(int i) { *&a[i] = 1; }
+
+// C has no glvalue comma, so the C file can only test the pointer
+// form, c_comma_addr.
+// CHECK-LABEL: define {{.*}}@_Z7x_commai(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_comma(int i) { (1, a[i]) = 1; }
+
+// CHECK-LABEL: define {{.*}}@_Z11x_copy_initi(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_copy_init(int i) {
+ Agg b = agga[i];
+ (void)b;
+}
+
+// The element passed by value, through the same constructor.
+void xsink(Agg);
+// CHECK-LABEL: define {{.*}}@_Z7x_byvali(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_byval(int i) { xsink(agga[i]); }
+
+// With a non-trivial copy constructor there is a real call and the argument binds
+// to `const T &`, so this is reference binding instead.
+struct NT {
+ NT();
+ NT(const NT &);
+ int x;
+};
+NT nta[4];
+void ntsink(NT);
+// A non-trivial copy constructor makes a real call, so the
+// argument binds to a reference instead.
+// CHECK-LABEL: define {{.*}}@_Z18x_byval_nontriviali(
+// CHECK: icmp ule i64 {{.*}}, 4
+void x_byval_nontrivial(int i) { ntsink(nta[i]); }
+
+//===----------------------------------------------------------------------===//
+// Address-only contexts: `ule` is required.
+//===----------------------------------------------------------------------===//
+
+// CHECK-LABEL: define {{.*}}@_Z9xctl_addri(
+// CHECK: icmp ule i64 {{.*}}, 4
+void xctl_addr(int i) { p = &a[i]; }
+
+// CHECK-LABEL: define {{.*}}@_Z8xctl_rowi(
+// CHECK: icmp ule i64 {{.*}}, 4
+void xctl_row(int i) { p = a2[i]; }
+
+// Same expression as xctl_row.
+// CHECK-LABEL: define {{.*}}@_Z14xctl_row_elem0i(
+// CHECK: icmp ult i64 {{.*}}, 4
+void xctl_row_elem0(int i) { p = &a2[i][0]; }
+
+// CHECK-LABEL: define {{.*}}@_Z16xctl_struct_addri(
+// CHECK: icmp ule i64 {{.*}}, 4
+void xctl_struct_addr(int i) { q = &ma[i]; }
+
+// The pointer form of the conversion in x_derived_to_base, which
+// is arithmetic rather than a subobject designation.
+// CHECK-LABEL: define {{.*}}@_Z13xctl_base_ptri(
+// CHECK: icmp ule i64 {{.*}}, 4
+Base *xctl_base_ptr(int i) { return &da[i]; }
>From 22771f40d5accfdd9a6d7efcf5d60981fb1737bd Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:54:33 -0700
Subject: [PATCH 02/17] [clang][CodeGen][NFC] Thread an object requirement
through the lvalue emitters
Whether a subscript needs the strict comparison depends on the context the lvalue
appears in, not on the subscript itself. That context was expressed by which
function a caller chose: twenty-one places called EmitCheckedLValue rather than
EmitLValue. But EmitCheckedLValue could only pass the answer on to a subscript
that was its immediate operand, so any wrapper -- parentheses, a cast, `*&` --
dropped it on the way down.
Add a parameter carrying the requirement and thread it through the lvalue
emitters, so callers can state it and it survives the trip, as TypeCheckKind
already does. Also rename EmitBoundsCheck's `Accessed` to a requirement of the
same type, since the old name described the wrong predicate. Nothing requests the
requirement yet, so nothing changes what is emitted.
---
clang/lib/CodeGen/CGExpr.cpp | 179 ++++++++++++++++------------
clang/lib/CodeGen/CGExprScalar.cpp | 5 +-
clang/lib/CodeGen/CodeGenFunction.h | 45 ++++---
3 files changed, 135 insertions(+), 94 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 9201e40bc13a1..03c7cfa69931a 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1285,7 +1285,7 @@ llvm::Value *CodeGenFunction::EmitLoadOfCountedByField(
void CodeGenFunction::EmitBoundsCheck(const Expr *ArrayExpr,
const Expr *ArrayExprBase,
llvm::Value *IndexVal, QualType IndexType,
- bool Accessed) {
+ ObjectRequirement_t Req) {
assert(SanOpts.has(SanitizerKind::ArrayBounds) &&
"should not be called unless adding bounds checks");
const LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
@@ -1295,7 +1295,7 @@ void CodeGenFunction::EmitBoundsCheck(const Expr *ArrayExpr,
*this, ArrayExprBase, ArrayExprBaseType, StrictFlexArraysLevel);
EmitBoundsCheckImpl(ArrayExpr, ArrayExprBaseType, IndexVal, IndexType,
- BoundsVal, getContext().getSizeType(), Accessed);
+ BoundsVal, getContext().getSizeType(), Req);
}
void CodeGenFunction::EmitBoundsCheckImpl(const Expr *ArrayExpr,
@@ -1303,7 +1303,7 @@ void CodeGenFunction::EmitBoundsCheckImpl(const Expr *ArrayExpr,
llvm::Value *IndexVal,
QualType IndexType,
llvm::Value *BoundsVal,
- QualType BoundsType, bool Accessed) {
+ QualType BoundsType, ObjectRequirement_t Req) {
if (!BoundsVal)
return;
@@ -1329,8 +1329,9 @@ void CodeGenFunction::EmitBoundsCheckImpl(const Expr *ArrayExpr,
EmitCheckTypeDescriptor(IndexType),
};
- llvm::Value *Check = Accessed ? Builder.CreateICmpULT(IndexInst, BoundsInst)
- : Builder.CreateICmpULE(IndexInst, BoundsInst);
+ llvm::Value *Check = Req == ObjectRequired
+ ? Builder.CreateICmpULT(IndexInst, BoundsInst)
+ : Builder.CreateICmpULE(IndexInst, BoundsInst);
if (BoundsSigned) {
// Don't allow a negative bounds.
@@ -1479,10 +1480,11 @@ static Address emitPointerArithmetic(CodeGenFunction &CGF,
/*Offset=*/nullptr, IsKnownNonNull);
}
-static Address EmitPointerWithAlignment(const Expr *E, LValueBaseInfo *BaseInfo,
- TBAAAccessInfo *TBAAInfo,
- KnownNonNull_t IsKnownNonNull,
- CodeGenFunction &CGF) {
+static Address
+EmitPointerWithAlignment(const Expr *E, LValueBaseInfo *BaseInfo,
+ TBAAAccessInfo *TBAAInfo,
+ KnownNonNull_t IsKnownNonNull, CodeGenFunction &CGF,
+ CodeGenFunction::ObjectRequirement_t Req) {
// We allow this with ObjC object pointers because of fragile ABIs.
assert(E->getType()->isPointerType() ||
E->getType()->isObjCObjectPointerType());
@@ -1504,8 +1506,9 @@ static Address EmitPointerWithAlignment(const Expr *E, LValueBaseInfo *BaseInfo,
LValueBaseInfo InnerBaseInfo;
TBAAAccessInfo InnerTBAAInfo;
- Address Addr = CGF.EmitPointerWithAlignment(
- CE->getSubExpr(), &InnerBaseInfo, &InnerTBAAInfo, IsKnownNonNull);
+ Address Addr =
+ CGF.EmitPointerWithAlignment(CE->getSubExpr(), &InnerBaseInfo,
+ &InnerTBAAInfo, IsKnownNonNull, Req);
if (BaseInfo) *BaseInfo = InnerBaseInfo;
if (TBAAInfo) *TBAAInfo = InnerTBAAInfo;
@@ -1579,7 +1582,7 @@ static Address EmitPointerWithAlignment(const Expr *E, LValueBaseInfo *BaseInfo,
// Unary &.
if (const UnaryOperator *UO = dyn_cast<UnaryOperator>(E)) {
if (UO->getOpcode() == UO_AddrOf) {
- LValue LV = CGF.EmitLValue(UO->getSubExpr(), IsKnownNonNull);
+ LValue LV = CGF.EmitLValue(UO->getSubExpr(), IsKnownNonNull, Req);
if (BaseInfo) *BaseInfo = LV.getBaseInfo();
if (TBAAInfo) *TBAAInfo = LV.getTBAAInfo();
return LV.getAddress();
@@ -1594,7 +1597,7 @@ static Address EmitPointerWithAlignment(const Expr *E, LValueBaseInfo *BaseInfo,
case Builtin::BIaddressof:
case Builtin::BI__addressof:
case Builtin::BI__builtin_addressof: {
- LValue LV = CGF.EmitLValue(Call->getArg(0), IsKnownNonNull);
+ LValue LV = CGF.EmitLValue(Call->getArg(0), IsKnownNonNull, Req);
if (BaseInfo) *BaseInfo = LV.getBaseInfo();
if (TBAAInfo) *TBAAInfo = LV.getTBAAInfo();
return LV.getAddress();
@@ -1618,11 +1621,13 @@ static Address EmitPointerWithAlignment(const Expr *E, LValueBaseInfo *BaseInfo,
/// EmitPointerWithAlignment - Given an expression of pointer type, try to
/// derive a more accurate bound on the alignment of the pointer.
-Address CodeGenFunction::EmitPointerWithAlignment(
- const Expr *E, LValueBaseInfo *BaseInfo, TBAAAccessInfo *TBAAInfo,
- KnownNonNull_t IsKnownNonNull) {
- Address Addr =
- ::EmitPointerWithAlignment(E, BaseInfo, TBAAInfo, IsKnownNonNull, *this);
+Address CodeGenFunction::EmitPointerWithAlignment(const Expr *E,
+ LValueBaseInfo *BaseInfo,
+ TBAAAccessInfo *TBAAInfo,
+ KnownNonNull_t IsKnownNonNull,
+ ObjectRequirement_t Req) {
+ Address Addr = ::EmitPointerWithAlignment(E, BaseInfo, TBAAInfo,
+ IsKnownNonNull, *this, Req);
if (IsKnownNonNull && !Addr.isKnownNonNull())
Addr.setKnownNonNull();
return Addr;
@@ -1702,23 +1707,28 @@ bool CodeGenFunction::IsWrappedCXXThis(const Expr *Obj) {
LValue CodeGenFunction::EmitCheckedLValue(const Expr *E, TypeCheckKind TCK) {
LValue LV;
if (SanOpts.has(SanitizerKind::ArrayBounds) && isa<ArraySubscriptExpr>(E))
- LV = EmitArraySubscriptExpr(cast<ArraySubscriptExpr>(E), /*Accessed*/true);
+ LV = EmitArraySubscriptExpr(cast<ArraySubscriptExpr>(E), ObjectRequired);
else
LV = EmitLValue(E);
- if (!isa<DeclRefExpr>(E) && !LV.isBitField() && LV.isSimple()) {
- SanitizerSet SkippedChecks;
- if (const auto *ME = dyn_cast<MemberExpr>(E)) {
- bool IsBaseCXXThis = IsWrappedCXXThis(ME->getBase());
- if (IsBaseCXXThis)
- SkippedChecks.set(SanitizerKind::Alignment, true);
- if (IsBaseCXXThis || isa<DeclRefExpr>(ME->getBase()))
- SkippedChecks.set(SanitizerKind::Null, true);
- }
- EmitTypeCheck(TCK, E->getExprLoc(), LV, E->getType(), SkippedChecks);
- }
+ EmitTypeCheck(TCK, E, LV);
return LV;
}
+void CodeGenFunction::EmitTypeCheck(TypeCheckKind TCK, const Expr *E,
+ LValue LV) {
+ if (isa<DeclRefExpr>(E) || LV.isBitField() || !LV.isSimple())
+ return;
+ SanitizerSet SkippedChecks;
+ if (const auto *ME = dyn_cast<MemberExpr>(E)) {
+ bool IsBaseCXXThis = IsWrappedCXXThis(ME->getBase());
+ if (IsBaseCXXThis)
+ SkippedChecks.set(SanitizerKind::Alignment, true);
+ if (IsBaseCXXThis || isa<DeclRefExpr>(ME->getBase()))
+ SkippedChecks.set(SanitizerKind::Null, true);
+ }
+ EmitTypeCheck(TCK, E->getExprLoc(), LV, E->getType(), SkippedChecks);
+}
+
/// EmitLValue - Emit code to compute a designator that specifies the location
/// of the expression.
///
@@ -1735,12 +1745,13 @@ LValue CodeGenFunction::EmitCheckedLValue(const Expr *E, TypeCheckKind TCK) {
/// length type, this is not possible.
///
LValue CodeGenFunction::EmitLValue(const Expr *E,
- KnownNonNull_t IsKnownNonNull) {
+ KnownNonNull_t IsKnownNonNull,
+ ObjectRequirement_t Req) {
// Running with sufficient stack space to avoid deeply nested expressions
// cause a stack overflow.
LValue LV;
CGM.runWithSufficientStackSpace(
- E->getExprLoc(), [&] { LV = EmitLValueHelper(E, IsKnownNonNull); });
+ E->getExprLoc(), [&] { LV = EmitLValueHelper(E, IsKnownNonNull, Req); });
if (IsKnownNonNull && !LV.isKnownNonNull())
LV.setKnownNonNull();
@@ -1748,7 +1759,8 @@ LValue CodeGenFunction::EmitLValue(const Expr *E,
}
LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
- KnownNonNull_t IsKnownNonNull) {
+ KnownNonNull_t IsKnownNonNull,
+ ObjectRequirement_t Req) {
ApplyDebugLocation DL(*this, E);
switch (E->getStmtClass()) {
default: return EmitUnsupportedLValue(E, "l-value expression");
@@ -1761,7 +1773,7 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
case Expr::ObjCIsaExprClass:
return EmitObjCIsaExpr(cast<ObjCIsaExpr>(E));
case Expr::BinaryOperatorClass:
- return EmitBinaryOperatorLValue(cast<BinaryOperator>(E));
+ return EmitBinaryOperatorLValue(cast<BinaryOperator>(E), Req);
case Expr::CompoundAssignOperatorClass: {
QualType Ty = E->getType();
if (const AtomicType *AT = Ty->getAs<AtomicType>())
@@ -1777,7 +1789,7 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
return EmitCallExprLValue(cast<CallExpr>(E));
case Expr::CXXRewrittenBinaryOperatorClass:
return EmitLValue(cast<CXXRewrittenBinaryOperator>(E)->getSemanticForm(),
- IsKnownNonNull);
+ IsKnownNonNull, Req);
case Expr::VAArgExprClass:
return EmitVAArgExprLValue(cast<VAArgExpr>(E));
case Expr::DeclRefExprClass:
@@ -1786,13 +1798,13 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
const ConstantExpr *CE = cast<ConstantExpr>(E);
if (llvm::Value *Result = ConstantEmitter(*this).tryEmitConstantExpr(CE))
return MakeNaturalAlignPointeeAddrLValue(Result, CE->getType());
- return EmitLValue(cast<ConstantExpr>(E)->getSubExpr(), IsKnownNonNull);
+ return EmitLValue(cast<ConstantExpr>(E)->getSubExpr(), IsKnownNonNull, Req);
}
case Expr::ParenExprClass:
- return EmitLValue(cast<ParenExpr>(E)->getSubExpr(), IsKnownNonNull);
+ return EmitLValue(cast<ParenExpr>(E)->getSubExpr(), IsKnownNonNull, Req);
case Expr::GenericSelectionExprClass:
return EmitLValue(cast<GenericSelectionExpr>(E)->getResultExpr(),
- IsKnownNonNull);
+ IsKnownNonNull, Req);
case Expr::PredefinedExprClass:
return EmitPredefinedLValue(cast<PredefinedExpr>(E));
case Expr::StringLiteralClass:
@@ -1816,7 +1828,7 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
case Expr::ExprWithCleanupsClass: {
const auto *cleanups = cast<ExprWithCleanups>(E);
RunCleanupsScope Scope(*this);
- LValue LV = EmitLValue(cleanups->getSubExpr(), IsKnownNonNull);
+ LValue LV = EmitLValue(cleanups->getSubExpr(), IsKnownNonNull, Req);
if (LV.isSimple()) {
// Defend against branches out of gnu statement expressions surrounded by
// cleanups.
@@ -1835,12 +1847,12 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
case Expr::CXXDefaultArgExprClass: {
auto *DAE = cast<CXXDefaultArgExpr>(E);
CXXDefaultArgExprScope Scope(*this, DAE);
- return EmitLValue(DAE->getExpr(), IsKnownNonNull);
+ return EmitLValue(DAE->getExpr(), IsKnownNonNull, Req);
}
case Expr::CXXDefaultInitExprClass: {
auto *DIE = cast<CXXDefaultInitExpr>(E);
CXXDefaultInitExprScope Scope(*this, DIE);
- return EmitLValue(DIE->getExpr(), IsKnownNonNull);
+ return EmitLValue(DIE->getExpr(), IsKnownNonNull, Req);
}
case Expr::CXXTypeidExprClass:
return EmitCXXTypeidLValue(cast<CXXTypeidExpr>(E));
@@ -1852,9 +1864,9 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
case Expr::StmtExprClass:
return EmitStmtExprLValue(cast<StmtExpr>(E));
case Expr::UnaryOperatorClass:
- return EmitUnaryOpLValue(cast<UnaryOperator>(E));
+ return EmitUnaryOpLValue(cast<UnaryOperator>(E), Req);
case Expr::ArraySubscriptExprClass:
- return EmitArraySubscriptExpr(cast<ArraySubscriptExpr>(E));
+ return EmitArraySubscriptExpr(cast<ArraySubscriptExpr>(E), Req);
case Expr::MatrixSingleSubscriptExprClass:
return EmitMatrixSingleSubscriptExpr(cast<MatrixSingleSubscriptExpr>(E));
case Expr::MatrixSubscriptExprClass:
@@ -1872,16 +1884,18 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
case Expr::CompoundLiteralExprClass:
return EmitCompoundLiteralLValue(cast<CompoundLiteralExpr>(E));
case Expr::ConditionalOperatorClass:
- return EmitConditionalOperatorLValue(cast<ConditionalOperator>(E));
+ return EmitConditionalOperatorLValue(cast<ConditionalOperator>(E), Req);
case Expr::BinaryConditionalOperatorClass:
- return EmitConditionalOperatorLValue(cast<BinaryConditionalOperator>(E));
+ return EmitConditionalOperatorLValue(cast<BinaryConditionalOperator>(E),
+ Req);
case Expr::ChooseExprClass:
- return EmitLValue(cast<ChooseExpr>(E)->getChosenSubExpr(), IsKnownNonNull);
+ return EmitLValue(cast<ChooseExpr>(E)->getChosenSubExpr(), IsKnownNonNull,
+ Req);
case Expr::OpaqueValueExprClass:
return EmitOpaqueValueLValue(cast<OpaqueValueExpr>(E));
case Expr::SubstNonTypeTemplateParmExprClass:
return EmitLValue(cast<SubstNonTypeTemplateParmExpr>(E)->getReplacement(),
- IsKnownNonNull);
+ IsKnownNonNull, Req);
case Expr::ImplicitCastExprClass:
case Expr::CStyleCastExprClass:
case Expr::CXXFunctionalCastExprClass:
@@ -1891,7 +1905,7 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
case Expr::CXXConstCastExprClass:
case Expr::CXXAddrspaceCastExprClass:
case Expr::ObjCBridgedCastExprClass:
- return EmitCastLValue(cast<CastExpr>(E));
+ return EmitCastLValue(cast<CastExpr>(E), Req);
case Expr::MaterializeTemporaryExprClass:
return EmitMaterializeTemporaryExpr(cast<MaterializeTemporaryExpr>(E));
@@ -1901,7 +1915,8 @@ LValue CodeGenFunction::EmitLValueHelper(const Expr *E,
case Expr::CoyieldExprClass:
return EmitCoyieldLValue(cast<CoyieldExpr>(E));
case Expr::PackIndexingExprClass:
- return EmitLValue(cast<PackIndexingExpr>(E)->getSelectedExpr());
+ return EmitLValue(cast<PackIndexingExpr>(E)->getSelectedExpr(),
+ IsKnownNonNull, Req);
case Expr::HLSLOutArgExprClass:
llvm_unreachable("cannot emit a HLSL out argument directly");
}
@@ -3836,10 +3851,11 @@ LValue CodeGenFunction::EmitDeclRefLValue(const DeclRefExpr *E) {
llvm_unreachable("Unhandled DeclRefExpr");
}
-LValue CodeGenFunction::EmitUnaryOpLValue(const UnaryOperator *E) {
+LValue CodeGenFunction::EmitUnaryOpLValue(const UnaryOperator *E,
+ ObjectRequirement_t Req) {
// __extension__ doesn't affect lvalue-ness.
if (E->getOpcode() == UO_Extension)
- return EmitLValue(E->getSubExpr());
+ return EmitLValue(E->getSubExpr(), NotKnownNonNull, Req);
QualType ExprTy = getContext().getCanonicalType(E->getSubExpr()->getType());
switch (E->getOpcode()) {
@@ -3851,7 +3867,7 @@ LValue CodeGenFunction::EmitUnaryOpLValue(const UnaryOperator *E) {
LValueBaseInfo BaseInfo;
TBAAAccessInfo TBAAInfo;
Address Addr = EmitPointerWithAlignment(E->getSubExpr(), &BaseInfo,
- &TBAAInfo);
+ &TBAAInfo, NotKnownNonNull, Req);
LValue LV = MakeAddrLValue(Addr, T, BaseInfo, TBAAInfo);
LV.getQuals().setAddressSpace(ExprTy.getAddressSpace());
@@ -4957,7 +4973,7 @@ static std::optional<int64_t> getOffsetDifferenceInBits(CodeGenFunction &CGF,
/// similar to emit the correct GEP.
void CodeGenFunction::EmitCountedByBoundsChecking(
const Expr *ArrayExpr, QualType ArrayType, Address ArrayInst,
- QualType IndexType, llvm::Value *IndexVal, bool Accessed,
+ QualType IndexType, llvm::Value *IndexVal, ObjectRequirement_t Req,
bool FlexibleArray) {
const auto *ME = dyn_cast<MemberExpr>(ArrayExpr->IgnoreImpCasts());
if (!ME || !ME->getMemberDecl()->getType()->isCountAttributedType())
@@ -5001,12 +5017,12 @@ void CodeGenFunction::EmitCountedByBoundsChecking(
// Now emit the bounds checking.
EmitBoundsCheckImpl(ArrayExpr, ArrayType, IndexVal, IndexType, BoundsVal,
- CountFD->getType(), Accessed);
+ CountFD->getType(), Req);
}
}
LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
- bool Accessed) {
+ ObjectRequirement_t Req) {
// The index must always be an integer, which is not an aggregate. Emit it
// in lexical order (this complexity is, sadly, required by C++17).
llvm::Value *IdxPre =
@@ -5024,7 +5040,8 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
SignedIndices |= IdxSigned;
if (SanOpts.has(SanitizerKind::ArrayBounds))
- EmitBoundsCheck(E, E->getBase(), Idx, IdxTy, Accessed);
+ EmitBoundsCheck(E, E->getBase(), Idx, IdxTy,
+ Req);
// Extend or truncate the index type to 32 or 64-bits.
if (Promote && Idx->getType() != IntPtrTy)
@@ -5141,14 +5158,15 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
// For simple multidimensional array indexing, set the 'accessed' flag for
// better bounds-checking of the base expression.
if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(Array))
- ArrayLV = EmitArraySubscriptExpr(ASE, /*Accessed*/ true);
+ ArrayLV = EmitArraySubscriptExpr(ASE, ObjectRequired);
else
ArrayLV = EmitLValue(Array);
auto *Idx = EmitIdxAfterBase(/*Promote*/true);
if (SanOpts.has(SanitizerKind::ArrayBounds))
EmitCountedByBoundsChecking(Array, Array->getType(), ArrayLV.getAddress(),
- E->getIdx()->getType(), Idx, Accessed,
+ E->getIdx()->getType(), Idx,
+ Req,
/*FlexibleArray=*/true);
// Propagate the alignment from the array itself to the result.
@@ -5202,7 +5220,8 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
if (const auto *CE = dyn_cast_if_present<CastExpr>(Base);
CE && CE->getCastKind() == CK_LValueToRValue)
EmitCountedByBoundsChecking(CE, ptrType, Address::invalid(),
- E->getIdx()->getType(), Idx, Accessed,
+ E->getIdx()->getType(), Idx,
+ Req,
/*FlexibleArray=*/false);
}
}
@@ -5442,7 +5461,7 @@ LValue CodeGenFunction::EmitArraySectionExpr(const ArraySectionExpr *E,
// For simple multidimensional array indexing, set the 'accessed' flag for
// better bounds-checking of the base expression.
if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(Array))
- ArrayLV = EmitArraySubscriptExpr(ASE, /*Accessed*/ true);
+ ArrayLV = EmitArraySubscriptExpr(ASE, ObjectRequired);
else
ArrayLV = EmitLValue(Array);
@@ -6032,21 +6051,23 @@ LValue CodeGenFunction::EmitInitListLValue(const InitListExpr *E) {
/// Emit the operand of a glvalue conditional operator. This is either a glvalue
/// or a (possibly-parenthesized) throw-expression. If this is a throw, no
/// LValue is returned and the current block has been terminated.
-static std::optional<LValue> EmitLValueOrThrowExpression(CodeGenFunction &CGF,
- const Expr *Operand) {
+static std::optional<LValue>
+EmitLValueOrThrowExpression(CodeGenFunction &CGF, const Expr *Operand,
+ CodeGenFunction::ObjectRequirement_t Req) {
if (auto *ThrowExpr = dyn_cast<CXXThrowExpr>(Operand->IgnoreParens())) {
CGF.EmitCXXThrowExpr(ThrowExpr, /*KeepInsertionPoint*/false);
return std::nullopt;
}
- return CGF.EmitLValue(Operand);
+ return CGF.EmitLValue(Operand, NotKnownNonNull, Req);
}
namespace {
// Handle the case where the condition is a constant evaluatable simple integer,
// which means we don't have to separately handle the true/false blocks.
std::optional<LValue> HandleConditionalOperatorLValueSimpleCase(
- CodeGenFunction &CGF, const AbstractConditionalOperator *E) {
+ CodeGenFunction &CGF, const AbstractConditionalOperator *E,
+ CodeGenFunction::ObjectRequirement_t Req) {
const Expr *condExpr = E->getCond();
bool CondExprBool;
if (CGF.ConstantFoldsToSimpleInteger(condExpr, CondExprBool)) {
@@ -6070,7 +6091,7 @@ std::optional<LValue> HandleConditionalOperatorLValueSimpleCase(
Address(llvm::UndefValue::get(Ty), ElemTy, CharUnits::One()),
Dead->getType());
}
- return CGF.EmitLValue(Live);
+ return CGF.EmitLValue(Live, NotKnownNonNull, Req);
}
}
return std::nullopt;
@@ -6129,7 +6150,7 @@ void CodeGenFunction::EmitIgnoredConditionalOperator(
}
OpaqueValueMapping binding(*this, E);
- if (HandleConditionalOperatorLValueSimpleCase(*this, E))
+ if (HandleConditionalOperatorLValueSimpleCase(*this, E, ObjectNotRequired))
return;
EmitConditionalBlocks(*this, E, [](CodeGenFunction &CGF, const Expr *E) {
@@ -6138,7 +6159,7 @@ void CodeGenFunction::EmitIgnoredConditionalOperator(
});
}
LValue CodeGenFunction::EmitConditionalOperatorLValue(
- const AbstractConditionalOperator *expr) {
+ const AbstractConditionalOperator *expr, ObjectRequirement_t Req) {
if (!expr->isGLValue()) {
// ?: here should be an aggregate.
assert(hasAggregateEvaluationKind(expr->getType()) &&
@@ -6148,12 +6169,12 @@ LValue CodeGenFunction::EmitConditionalOperatorLValue(
OpaqueValueMapping binding(*this, expr);
if (std::optional<LValue> Res =
- HandleConditionalOperatorLValueSimpleCase(*this, expr))
+ HandleConditionalOperatorLValueSimpleCase(*this, expr, Req))
return *Res;
ConditionalInfo Info = EmitConditionalBlocks(
- *this, expr, [](CodeGenFunction &CGF, const Expr *E) {
- return EmitLValueOrThrowExpression(CGF, E);
+ *this, expr, [Req](CodeGenFunction &CGF, const Expr *E) {
+ return EmitLValueOrThrowExpression(CGF, E, Req);
});
if ((Info.LHS && !Info.LHS->isSimple()) ||
@@ -6187,7 +6208,8 @@ LValue CodeGenFunction::EmitConditionalOperatorLValue(
/// access one of its members. This can happen for all the reasons that casts
/// are permitted with aggregate result, including noop aggregate casts, and
/// cast from scalar to union.
-LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) {
+LValue CodeGenFunction::EmitCastLValue(const CastExpr *E,
+ ObjectRequirement_t Req) {
llvm::scope_exit RestoreCurCast([this, Prev = CurCast] { CurCast = Prev; });
CurCast = E;
switch (E->getCastKind()) {
@@ -6266,13 +6288,13 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) {
case CK_CPointerToObjCPointerCast:
case CK_BlockPointerToObjCPointerCast:
case CK_LValueToRValue:
- return EmitLValue(E->getSubExpr());
+ return EmitLValue(E->getSubExpr(), NotKnownNonNull, Req);
case CK_NoOp: {
// CK_NoOp can model a qualification conversion, which can remove an array
// bound and change the IR type.
// FIXME: Once pointee types are removed from IR, remove this.
- LValue LV = EmitLValue(E->getSubExpr());
+ LValue LV = EmitLValue(E->getSubExpr(), NotKnownNonNull, Req);
// Propagate the volatile qualifer to LValue, if exist in E.
if (E->changesVolatileQualification())
LV.getQuals() = E->getType().getQualifiers();
@@ -6334,7 +6356,7 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) {
const auto *CE = cast<ExplicitCastExpr>(E);
CGM.EmitExplicitCastExprType(CE, this);
- LValue LV = EmitLValue(E->getSubExpr());
+ LValue LV = EmitLValue(E->getSubExpr(), NotKnownNonNull, Req);
Address V = LV.getAddress().withElementType(
ConvertTypeForMem(CE->getTypeAsWritten()->getPointeeType()));
@@ -6347,7 +6369,7 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) {
CGM.getTBAAInfoForSubobject(LV, E->getType()));
}
case CK_AddressSpaceConversion: {
- LValue LV = EmitLValue(E->getSubExpr());
+ LValue LV = EmitLValue(E->getSubExpr(), NotKnownNonNull, Req);
QualType DestTy = getContext().getPointerType(E->getType());
llvm::Value *V =
performAddrSpaceCast(LV.getPointer(*this), ConvertType(DestTy));
@@ -6356,7 +6378,7 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E) {
E->getType(), LV.getBaseInfo(), LV.getTBAAInfo());
}
case CK_ObjCObjectLValueCast: {
- LValue LV = EmitLValue(E->getSubExpr());
+ LValue LV = EmitLValue(E->getSubExpr(), NotKnownNonNull, Req);
Address V = LV.getAddress().withElementType(ConvertType(E->getType()));
return MakeAddrLValue(V, E->getType(), LV.getBaseInfo(),
CGM.getTBAAInfoForSubobject(LV, E->getType()));
@@ -6696,12 +6718,13 @@ CGCallee CodeGenFunction::EmitCallee(const Expr *E) {
return callee;
}
-LValue CodeGenFunction::EmitBinaryOperatorLValue(const BinaryOperator *E) {
+LValue CodeGenFunction::EmitBinaryOperatorLValue(const BinaryOperator *E,
+ ObjectRequirement_t Req) {
// Comma expressions just emit their LHS then their RHS as an l-value.
if (E->getOpcode() == BO_Comma) {
EmitIgnoredExpr(E->getLHS());
EnsureInsertPoint();
- return EmitLValue(E->getRHS());
+ return EmitLValue(E->getRHS(), NotKnownNonNull, Req);
}
if (E->getOpcode() == BO_PtrMemD ||
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 67014904ffb37..7fad49eaebaee 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -2193,7 +2193,8 @@ Value *ScalarExprEmitter::VisitArraySubscriptExpr(ArraySubscriptExpr *E) {
QualType IdxTy = E->getIdx()->getType();
if (CGF.SanOpts.has(SanitizerKind::ArrayBounds))
- CGF.EmitBoundsCheck(E, E->getBase(), Idx, IdxTy, /*Accessed*/true);
+ CGF.EmitBoundsCheck(E, E->getBase(), Idx, IdxTy,
+ CodeGenFunction::ObjectRequired);
Value *Ret = Builder.CreateExtractElement(Base, Idx, "vecext");
@@ -4581,7 +4582,7 @@ llvm::Value *CodeGenFunction::EmitPointerArithmetic(
if (SanOpts.has(SanitizerKind::ArrayBounds))
EmitBoundsCheck(BO, pointerOperand, index, indexOperand->getType(),
- /*Accessed*/ false);
+ CodeGenFunction::ObjectNotRequired);
const PointerType *pointerType =
pointerOperand->getType()->getAs<PointerType>();
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index b4449948af19d..1a641ed9c2f0f 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3370,6 +3370,10 @@ class CodeGenFunction : public CodeGenTypeCache {
/// calls to EmitTypeCheck can be skipped.
bool sanitizePerformTypeCheck() const;
+ /// Emit the checks \p TCK calls for on \p LV, the lvalue \p E designates,
+ /// skipping cases that cannot or need not be checked.
+ void EmitTypeCheck(TypeCheckKind TCK, const Expr *E, LValue LV);
+
void EmitTypeCheck(TypeCheckKind TCK, SourceLocation Loc, LValue LV,
QualType Type, SanitizerSet SkippedChecks = SanitizerSet(),
llvm::Value *ArraySize = nullptr) {
@@ -3397,15 +3401,23 @@ class CodeGenFunction : public CodeGenTypeCache {
SanitizerSet SkippedChecks = SanitizerSet(),
llvm::Value *ArraySize = nullptr);
- /// Emit a check that \p Base points into an array object, which
- /// we can access at index \p Index. \p Accessed should be \c false if we
- /// this expression is used as an lvalue, for instance in "&Arr[Idx]".
+ /// Whether the context an lvalue appears in requires the object it designates
+ /// to exist. Threaded down through the lvalue emitters so that a subscript can
+ /// tell `x = a[i]` (which requires the element) from `p = &a[i]` (which does
+ /// not, per C99 6.5.6p8 and 6.5.3.2p3).
+ enum ObjectRequirement_t { ObjectNotRequired, ObjectRequired };
+
+ /// Emit a check that \p Base points into an array object, which we can access
+ /// at index \p Index. \p Req selects the comparison: strict when the
+ /// designated element must exist, otherwise an index equal to the bound is
+ /// accepted.
void EmitBoundsCheck(const Expr *ArrayExpr, const Expr *ArrayExprBase,
- llvm::Value *Index, QualType IndexType, bool Accessed);
+ llvm::Value *Index, QualType IndexType,
+ ObjectRequirement_t Req);
void EmitBoundsCheckImpl(const Expr *ArrayExpr, QualType ArrayBaseType,
llvm::Value *IndexVal, QualType IndexType,
llvm::Value *BoundsVal, QualType BoundsType,
- bool Accessed);
+ ObjectRequirement_t Req);
/// Returns debug info, with additional annotation if
/// CGM.getCodeGenOpts().SanitizeAnnotateDebugInfo[Ordinal] is enabled for
@@ -3436,7 +3448,7 @@ class CodeGenFunction : public CodeGenTypeCache {
// counted_by attribute.
void EmitCountedByBoundsChecking(const Expr *ArrayExpr, QualType ArrayType,
Address ArrayInst, QualType IndexType,
- llvm::Value *IndexVal, bool Accessed,
+ llvm::Value *IndexVal, ObjectRequirement_t Req,
bool FlexibleArray);
llvm::Value *EmitScalarPrePostIncDec(const UnaryOperator *E, LValue LV,
@@ -4332,10 +4344,12 @@ class CodeGenFunction : public CodeGenTypeCache {
/// variable length type, this is not possible.
///
LValue EmitLValue(const Expr *E,
- KnownNonNull_t IsKnownNonNull = NotKnownNonNull);
+ KnownNonNull_t IsKnownNonNull = NotKnownNonNull,
+ ObjectRequirement_t Req = ObjectNotRequired);
private:
- LValue EmitLValueHelper(const Expr *E, KnownNonNull_t IsKnownNonNull);
+ LValue EmitLValueHelper(const Expr *E, KnownNonNull_t IsKnownNonNull,
+ ObjectRequirement_t Req);
public:
/// Same as EmitLValue but additionally we generate checking code to
@@ -4484,7 +4498,8 @@ class CodeGenFunction : public CodeGenTypeCache {
llvm::Value *&Result);
// Note: only available for agg return types
- LValue EmitBinaryOperatorLValue(const BinaryOperator *E);
+ LValue EmitBinaryOperatorLValue(const BinaryOperator *E,
+ ObjectRequirement_t Req);
LValue EmitCompoundAssignmentLValue(const CompoundAssignOperator *E);
// Note: only available for agg return types
LValue EmitCallExprLValue(const CallExpr *E,
@@ -4495,9 +4510,9 @@ class CodeGenFunction : public CodeGenTypeCache {
LValue EmitStringLiteralLValue(const StringLiteral *E);
LValue EmitObjCEncodeExprLValue(const ObjCEncodeExpr *E);
LValue EmitPredefinedLValue(const PredefinedExpr *E);
- LValue EmitUnaryOpLValue(const UnaryOperator *E);
+ LValue EmitUnaryOpLValue(const UnaryOperator *E, ObjectRequirement_t Req);
LValue EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
- bool Accessed = false);
+ ObjectRequirement_t Req);
llvm::Value *EmitMatrixIndexExpr(const Expr *E);
LValue EmitMatrixSingleSubscriptExpr(const MatrixSingleSubscriptExpr *E);
LValue EmitMatrixSubscriptExpr(const MatrixSubscriptExpr *E);
@@ -4510,8 +4525,9 @@ class CodeGenFunction : public CodeGenTypeCache {
LValue EmitCompoundLiteralLValue(const CompoundLiteralExpr *E);
LValue EmitInitListLValue(const InitListExpr *E);
void EmitIgnoredConditionalOperator(const AbstractConditionalOperator *E);
- LValue EmitConditionalOperatorLValue(const AbstractConditionalOperator *E);
- LValue EmitCastLValue(const CastExpr *E);
+ LValue EmitConditionalOperatorLValue(const AbstractConditionalOperator *E,
+ ObjectRequirement_t Req);
+ LValue EmitCastLValue(const CastExpr *E, ObjectRequirement_t Req);
LValue EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E);
LValue EmitOpaqueValueLValue(const OpaqueValueExpr *e);
LValue EmitHLSLArrayAssignLValue(const BinaryOperator *E);
@@ -5615,7 +5631,8 @@ class CodeGenFunction : public CodeGenTypeCache {
Address
EmitPointerWithAlignment(const Expr *Addr, LValueBaseInfo *BaseInfo = nullptr,
TBAAAccessInfo *TBAAInfo = nullptr,
- KnownNonNull_t IsKnownNonNull = NotKnownNonNull);
+ KnownNonNull_t IsKnownNonNull = NotKnownNonNull,
+ ObjectRequirement_t Req = ObjectNotRequired);
/// If \p E references a parameter with pass_object_size info or a constant
/// array size modifier, emit the object size divided by the size of \p EltTy.
>From 75b0e68e717661d0399161f2d63afdc5efed5978 Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:54:36 -0700
Subject: [PATCH 03/17] [clang][CodeGen] Stop the array-decay peephole forcing
a strict bounds check
-fsanitize=array-bounds rejected `&a2[i][0]` for i equal to the element count,
though C99 6.5.3.2p3 makes that expression `a2[i]`, which is accepted. The cause
is the -O0 peephole that emits one GEP for a nested subscript instead of two: it
also forced the strict comparison on the base, overriding what the enclosing
context had asked for. Take the requirement we were given instead.
This is the only change in the series that makes a check more permissive rather
than stricter. The regenerated counted-by test looks unrelated at a glance --
`icmp ult i32 %idx1, 42` becomes `ult ... 43` -- because it runs at -O2, where
InstCombine rewrites `ule 42` as `ult 43`; at -O0 the same expression emits
`icmp ule`.
---
clang/lib/CodeGen/CGExpr.cpp | 11 ++++-------
clang/test/CodeGen/attr-counted-by-with-sanitizers.c | 12 ++++++------
clang/test/CodeGen/ubsan-array-bounds-baseline.c | 2 +-
.../test/CodeGenCXX/ubsan-array-bounds-baseline.cpp | 2 +-
4 files changed, 12 insertions(+), 15 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 03c7cfa69931a..2a1c61d458cb4 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5154,13 +5154,10 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
// "gep x, i" here. Emit one "gep A, 0, i".
assert(Array->getType()->isArrayType() &&
"Array to pointer decay must have array source type!");
- LValue ArrayLV;
- // For simple multidimensional array indexing, set the 'accessed' flag for
- // better bounds-checking of the base expression.
- if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(Array))
- ArrayLV = EmitArraySubscriptExpr(ASE, ObjectRequired);
- else
- ArrayLV = EmitLValue(Array);
+ // The single GEP is a shortcut: A is still the object this subscript
+ // indexes into, so it takes the requirement we were given rather than one
+ // of its own.
+ LValue ArrayLV = EmitLValue(Array, NotKnownNonNull, Req);
auto *Idx = EmitIdxAfterBase(/*Promote*/true);
if (SanOpts.has(SanitizerKind::ArrayBounds))
diff --git a/clang/test/CodeGen/attr-counted-by-with-sanitizers.c b/clang/test/CodeGen/attr-counted-by-with-sanitizers.c
index e840db632957e..43875611e13eb 100644
--- a/clang/test/CodeGen/attr-counted-by-with-sanitizers.c
+++ b/clang/test/CodeGen/attr-counted-by-with-sanitizers.c
@@ -529,12 +529,12 @@ size_t test_return_bdos_of_anon_struct(struct union_of_fams *p) {
// SANITIZE-WITH-ATTR-NEXT: [[COUNTED_BY_LOAD:%.*]] = load i8, ptr [[TMP0]], align 4
// SANITIZE-WITH-ATTR-NEXT: [[TMP1:%.*]] = zext i8 [[COUNTED_BY_LOAD]] to i32, !nosanitize [[META6]]
// SANITIZE-WITH-ATTR-NEXT: [[TMP2:%.*]] = icmp ult i32 [[INDEX]], [[TMP1]], !nosanitize [[META6]]
-// SANITIZE-WITH-ATTR-NEXT: br i1 [[TMP2]], label %[[CONT14:.*]], label %[[HANDLER_OUT_OF_BOUNDS:.*]], !prof [[PROF7]], !nosanitize [[META6]]
+// SANITIZE-WITH-ATTR-NEXT: br i1 [[TMP2]], label %[[CONT16:.*]], label %[[HANDLER_OUT_OF_BOUNDS:.*]], !prof [[PROF7]], !nosanitize [[META6]]
// SANITIZE-WITH-ATTR: [[HANDLER_OUT_OF_BOUNDS]]:
// SANITIZE-WITH-ATTR-NEXT: [[TMP3:%.*]] = zext i32 [[INDEX]] to i64, !nosanitize [[META6]]
// SANITIZE-WITH-ATTR-NEXT: tail call void @__ubsan_handle_out_of_bounds_abort(ptr nonnull @[[GLOB16:[0-9]+]], i64 [[TMP3]]) #[[ATTR7]], !nosanitize [[META6]]
// SANITIZE-WITH-ATTR-NEXT: unreachable, !nosanitize [[META6]]
-// SANITIZE-WITH-ATTR: [[CONT14]]:
+// SANITIZE-WITH-ATTR: [[CONT16]]:
// SANITIZE-WITH-ATTR-NEXT: [[INTS:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 9
// SANITIZE-WITH-ATTR-NEXT: [[IDXPROM:%.*]] = zext nneg i32 [[INDEX]] to i64
// SANITIZE-WITH-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[INTS]], i64 [[IDXPROM]]
@@ -612,12 +612,12 @@ void test_assign_bdos_of_struct_to_union_fam(struct union_of_fams *p, int index)
// SANITIZE-WITH-ATTR-NEXT: [[TMP1:%.*]] = icmp ult i32 [[INDEX]], [[COUNTED_BY_LOAD]], !nosanitize [[META6]]
// SANITIZE-WITH-ATTR-NEXT: [[TMP2:%.*]] = icmp sgt i32 [[COUNTED_BY_LOAD]], 0, !nosanitize [[META6]]
// SANITIZE-WITH-ATTR-NEXT: [[TMP3:%.*]] = and i1 [[TMP2]], [[TMP1]], !nosanitize [[META6]]
-// SANITIZE-WITH-ATTR-NEXT: br i1 [[TMP3]], label %[[CONT14:.*]], label %[[HANDLER_OUT_OF_BOUNDS:.*]], !prof [[PROF7]], !nosanitize [[META6]]
+// SANITIZE-WITH-ATTR-NEXT: br i1 [[TMP3]], label %[[CONT16:.*]], label %[[HANDLER_OUT_OF_BOUNDS:.*]], !prof [[PROF7]], !nosanitize [[META6]]
// SANITIZE-WITH-ATTR: [[HANDLER_OUT_OF_BOUNDS]]:
// SANITIZE-WITH-ATTR-NEXT: [[TMP4:%.*]] = zext i32 [[INDEX]] to i64, !nosanitize [[META6]]
// SANITIZE-WITH-ATTR-NEXT: tail call void @__ubsan_handle_out_of_bounds_abort(ptr nonnull @[[GLOB19:[0-9]+]], i64 [[TMP4]]) #[[ATTR7]], !nosanitize [[META6]]
// SANITIZE-WITH-ATTR-NEXT: unreachable, !nosanitize [[META6]]
-// SANITIZE-WITH-ATTR: [[CONT14]]:
+// SANITIZE-WITH-ATTR: [[CONT16]]:
// SANITIZE-WITH-ATTR-NEXT: [[BYTES:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 12
// SANITIZE-WITH-ATTR-NEXT: [[IDXPROM:%.*]] = sext i32 [[INDEX]] to i64
// SANITIZE-WITH-ATTR-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds i8, ptr [[BYTES]], i64 [[IDXPROM]]
@@ -1464,7 +1464,7 @@ struct multi_subscripts {
// SANITIZE-WITH-ATTR-LABEL: define dso_local i64 @test_return_bdos_of_multiple_indices(
// SANITIZE-WITH-ATTR-SAME: ptr noundef [[PTR:%.*]], i32 noundef [[IDX1:%.*]], i32 noundef [[IDX2:%.*]]) local_unnamed_addr #[[ATTR0]] {
// SANITIZE-WITH-ATTR-NEXT: [[ENTRY:.*:]]
-// SANITIZE-WITH-ATTR-NEXT: [[TMP0:%.*]] = icmp ult i32 [[IDX1]], 42
+// SANITIZE-WITH-ATTR-NEXT: [[TMP0:%.*]] = icmp ult i32 [[IDX1]], 43
// SANITIZE-WITH-ATTR-NEXT: br i1 [[TMP0]], label %[[CONT1:.*]], label %[[HANDLER_OUT_OF_BOUNDS:.*]], !prof [[PROF7]], !nosanitize [[META6]]
// SANITIZE-WITH-ATTR: [[HANDLER_OUT_OF_BOUNDS]]:
// SANITIZE-WITH-ATTR-NEXT: [[TMP1:%.*]] = sext i32 [[IDX1]] to i64, !nosanitize [[META6]]
@@ -1483,7 +1483,7 @@ struct multi_subscripts {
// SANITIZE-WITHOUT-ATTR-LABEL: define dso_local i64 @test_return_bdos_of_multiple_indices(
// SANITIZE-WITHOUT-ATTR-SAME: ptr noundef [[PTR:%.*]], i32 noundef [[IDX1:%.*]], i32 noundef [[IDX2:%.*]]) local_unnamed_addr #[[ATTR0]] {
// SANITIZE-WITHOUT-ATTR-NEXT: [[ENTRY:.*:]]
-// SANITIZE-WITHOUT-ATTR-NEXT: [[TMP0:%.*]] = icmp ult i32 [[IDX1]], 42
+// SANITIZE-WITHOUT-ATTR-NEXT: [[TMP0:%.*]] = icmp ult i32 [[IDX1]], 43
// SANITIZE-WITHOUT-ATTR-NEXT: br i1 [[TMP0]], label %[[CONT1:.*]], label %[[HANDLER_OUT_OF_BOUNDS:.*]], !prof [[PROF10]], !nosanitize [[META9]]
// SANITIZE-WITHOUT-ATTR: [[HANDLER_OUT_OF_BOUNDS]]:
// SANITIZE-WITHOUT-ATTR-NEXT: [[TMP1:%.*]] = sext i32 [[IDX1]] to i64, !nosanitize [[META9]]
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline.c b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
index cf91740887125..1db817f1a9b80 100644
--- a/clang/test/CodeGen/ubsan-array-bounds-baseline.c
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
@@ -248,7 +248,7 @@ void ctl_row(int i) { p = a2[i]; }
// Same expression as ctl_row: C99 6.5.3.2p3.
// CHECK-LABEL: define {{.*}}@ctl_row_elem0(
-// CHECK: icmp ult i64 {{.*}}, 4
+// CHECK: icmp ule i64 {{.*}}, 4
void ctl_row_elem0(int i) { p = &a2[i][0]; }
// CHECK-LABEL: define {{.*}}@ctl_struct_addr(
diff --git a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
index 731eb21e316b0..3774f846476bf 100644
--- a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
+++ b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
@@ -259,7 +259,7 @@ void xctl_row(int i) { p = a2[i]; }
// Same expression as xctl_row.
// CHECK-LABEL: define {{.*}}@_Z14xctl_row_elem0i(
-// CHECK: icmp ult i64 {{.*}}, 4
+// CHECK: icmp ule i64 {{.*}}, 4
void xctl_row_elem0(int i) { p = &a2[i][0]; }
// CHECK-LABEL: define {{.*}}@_Z16xctl_struct_addri(
>From 104d7426c171b815b31f182335111f1ccf501409 Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:54:40 -0700
Subject: [PATCH 04/17] [clang][CodeGen] Require the object for ++ and --
`a[i]++` was accepted for an out-of-bounds index, though it reads and writes the
element just as `a[i] += 1` does, which was rejected. Require the object for
all four increment and decrement operators.
---
clang/lib/CodeGen/CGExprScalar.cpp | 12 ++++++++----
clang/test/CodeGen/ubsan-array-bounds-baseline.c | 8 ++++----
2 files changed, 12 insertions(+), 8 deletions(-)
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 7fad49eaebaee..e3ce0ec3d890d 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -693,20 +693,24 @@ class ScalarExprEmitter
Value *VisitStmtExpr(const StmtExpr *E);
// Unary Operators.
+ LValue EmitIncDecOperand(const UnaryOperator *E) {
+ return CGF.EmitLValue(E->getSubExpr(), NotKnownNonNull,
+ CodeGenFunction::ObjectRequired);
+ }
Value *VisitUnaryPostDec(const UnaryOperator *E) {
- LValue LV = EmitLValue(E->getSubExpr());
+ LValue LV = EmitIncDecOperand(E);
return EmitScalarPrePostIncDec(E, LV, false, false);
}
Value *VisitUnaryPostInc(const UnaryOperator *E) {
- LValue LV = EmitLValue(E->getSubExpr());
+ LValue LV = EmitIncDecOperand(E);
return EmitScalarPrePostIncDec(E, LV, true, false);
}
Value *VisitUnaryPreDec(const UnaryOperator *E) {
- LValue LV = EmitLValue(E->getSubExpr());
+ LValue LV = EmitIncDecOperand(E);
return EmitScalarPrePostIncDec(E, LV, false, true);
}
Value *VisitUnaryPreInc(const UnaryOperator *E) {
- LValue LV = EmitLValue(E->getSubExpr());
+ LValue LV = EmitIncDecOperand(E);
return EmitScalarPrePostIncDec(E, LV, true, true);
}
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline.c b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
index 1db817f1a9b80..0721a9b1475b3 100644
--- a/clang/test/CodeGen/ubsan-array-bounds-baseline.c
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
@@ -73,19 +73,19 @@ void c_compound_paren(int i) { (a[i]) += 1; }
void c_compound_deref_addr(int i) { *&a[i] += 1; }
// CHECK-LABEL: define {{.*}}@c_postinc(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_postinc(int i) { a[i]++; }
// CHECK-LABEL: define {{.*}}@c_postdec(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_postdec(int i) { a[i]--; }
// CHECK-LABEL: define {{.*}}@c_preinc(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_preinc(int i) { ++a[i]; }
// CHECK-LABEL: define {{.*}}@c_predec(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_predec(int i) { --a[i]; }
// CHECK-LABEL: define {{.*}}@c_agg_store(
>From 1e6ae2077b2663b41c0476df357befa248d97d44 Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:54:43 -0700
Subject: [PATCH 05/17] [clang][CodeGen] State the requirement at an
assignment's left operand
An out-of-bounds store was rejected only when the subscript was written directly
as the left operand. `(a[i]) = 1`, `__extension__ (a[i]) = 1`, `*&a[i] = 1` and
`*(int *)&a[i] = 1` were accepted, though each stores into the same element, and
an lvalue that designates no object must not be evaluated (C99 6.3.2.1p1). State
the requirement at the assignment instead of inferring it from the shape of the
operand, in the scalar, aggregate and compound assignment emitters alike -- C99
6.5.16.2p3 makes `E1 op= E2` store into E1 as well. C and C++ reach different
emitters for this, so both are updated.
---
clang/lib/CodeGen/CGExpr.cpp | 6 ++++--
clang/lib/CodeGen/CGExprAgg.cpp | 8 ++++++--
clang/lib/CodeGen/CGExprScalar.cpp | 17 +++++++++++++----
.../test/CodeGen/attr-counted-by-for-pointers.c | 2 +-
.../test/CodeGen/ubsan-array-bounds-baseline.c | 16 ++++++++--------
.../CodeGenCXX/ubsan-array-bounds-baseline.cpp | 8 ++++----
6 files changed, 36 insertions(+), 21 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 2a1c61d458cb4..de3569c21c686 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -6746,7 +6746,8 @@ LValue CodeGenFunction::EmitBinaryOperatorLValue(const BinaryOperator *E,
case TEK_Scalar: {
if (PointerAuthQualifier PtrAuth =
E->getLHS()->getType().getPointerAuth()) {
- LValue LV = EmitCheckedLValue(E->getLHS(), TCK_Store);
+ LValue LV = EmitLValue(E->getLHS(), NotKnownNonNull, ObjectRequired);
+ EmitTypeCheck(TCK_Store, E->getLHS(), LV);
LValue CopiedLV = LV;
CopiedLV.getQuals().removePointerAuth();
llvm::Value *RV =
@@ -6785,7 +6786,8 @@ LValue CodeGenFunction::EmitBinaryOperatorLValue(const BinaryOperator *E,
} else
RV = EmitAnyExpr(E->getRHS());
- LValue LV = EmitCheckedLValue(E->getLHS(), TCK_Store);
+ LValue LV = EmitLValue(E->getLHS(), NotKnownNonNull, ObjectRequired);
+ EmitTypeCheck(TCK_Store, E->getLHS(), LV);
if (RV.isScalar())
EmitNullabilityCheck(LV, RV.getScalarVal(), E->getExprLoc());
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index bc35ffdaad2bd..4b26a73e11eb5 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -1381,7 +1381,9 @@ void AggExprEmitter::VisitBinAssign(const BinaryOperator *E) {
Visit(E->getRHS());
// Now emit the LHS and copy into it.
- LValue LHS = CGF.EmitCheckedLValue(E->getLHS(), CodeGenFunction::TCK_Store);
+ LValue LHS = CGF.EmitLValue(E->getLHS(), NotKnownNonNull,
+ CodeGenFunction::ObjectRequired);
+ CGF.EmitTypeCheck(CodeGenFunction::TCK_Store, E->getLHS(), LHS);
// That copy is an atomic copy if the LHS is atomic.
if (LHS.getType()->isAtomicType() ||
@@ -1399,7 +1401,9 @@ void AggExprEmitter::VisitBinAssign(const BinaryOperator *E) {
return;
}
- LValue LHS = CGF.EmitCheckedLValue(E->getLHS(), CodeGenFunction::TCK_Store);
+ LValue LHS = CGF.EmitLValue(E->getLHS(), NotKnownNonNull,
+ CodeGenFunction::ObjectRequired);
+ CGF.EmitTypeCheck(CodeGenFunction::TCK_Store, E->getLHS(), LHS);
// If we have an atomic type, evaluate into the destination and then
// do an atomic copy.
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index e3ce0ec3d890d..628d9655421ec 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -692,6 +692,13 @@ class ScalarExprEmitter
Value *VisitStmtExpr(const StmtExpr *E);
+ LValue EmitAssignmentDest(const BinaryOperator *E) {
+ LValue LV = CGF.EmitLValue(E->getLHS(), NotKnownNonNull,
+ CodeGenFunction::ObjectRequired);
+ CGF.EmitTypeCheck(CodeGenFunction::TCK_Store, E->getLHS(), LV);
+ return LV;
+ }
+
// Unary Operators.
LValue EmitIncDecOperand(const UnaryOperator *E) {
return CGF.EmitLValue(E->getSubExpr(), NotKnownNonNull,
@@ -4096,7 +4103,9 @@ LValue ScalarExprEmitter::EmitCompoundAssignLValue(
OpInfo.FPFeatures = E->getFPFeaturesInEffect(CGF.getLangOpts());
OpInfo.E = E;
// Load/convert the LHS.
- LValue LHSLV = EmitCheckedLValue(E->getLHS(), CodeGenFunction::TCK_Store);
+ LValue LHSLV = CGF.EmitLValue(E->getLHS(), NotKnownNonNull,
+ CodeGenFunction::ObjectRequired);
+ CGF.EmitTypeCheck(CodeGenFunction::TCK_Store, E->getLHS(), LHSLV);
llvm::PHINode *atomicPHI = nullptr;
if (const AtomicType *atomicTy = LHSTy->getAs<AtomicType>()) {
@@ -5450,7 +5459,7 @@ Value *ScalarExprEmitter::VisitBinAssign(const BinaryOperator *E) {
LValue LHS;
if (PointerAuthQualifier PtrAuth = E->getLHS()->getType().getPointerAuth()) {
- LValue LV = CGF.EmitCheckedLValue(E->getLHS(), CodeGenFunction::TCK_Store);
+ LValue LV = EmitAssignmentDest(E);
LV.getQuals().removePointerAuth();
llvm::Value *RV =
CGF.EmitPointerAuthQualify(PtrAuth, E->getRHS(), LV.getAddress());
@@ -5479,7 +5488,7 @@ Value *ScalarExprEmitter::VisitBinAssign(const BinaryOperator *E) {
case Qualifiers::OCL_Weak:
RHS = Visit(E->getRHS());
- LHS = EmitCheckedLValue(E->getLHS(), CodeGenFunction::TCK_Store);
+ LHS = EmitAssignmentDest(E);
RHS = CGF.EmitARCStoreWeak(LHS.getAddress(), RHS, Ignore);
break;
@@ -5496,7 +5505,7 @@ Value *ScalarExprEmitter::VisitBinAssign(const BinaryOperator *E) {
else
RHS = Visit(E->getRHS());
- LHS = EmitCheckedLValue(E->getLHS(), CodeGenFunction::TCK_Store);
+ LHS = EmitAssignmentDest(E);
// Store the value into the LHS. Bit-fields are handled specially
// because the result is altered by the store, i.e., [C99 6.5.16p1]
diff --git a/clang/test/CodeGen/attr-counted-by-for-pointers.c b/clang/test/CodeGen/attr-counted-by-for-pointers.c
index e3d65789aac36..9d41b5d059c09 100644
--- a/clang/test/CodeGen/attr-counted-by-for-pointers.c
+++ b/clang/test/CodeGen/attr-counted-by-for-pointers.c
@@ -140,7 +140,7 @@ void test_store_subscript_through_cast(struct annotated_ptr *p, int index, struc
// SANITIZE-WITH-ATTR-NEXT: [[ENTRY:.*:]]
// SANITIZE-WITH-ATTR-NEXT: [[DOTCOUNTED_BY_GEP:%.*]] = getelementptr inbounds nuw i8, ptr [[P]], i64 16
// SANITIZE-WITH-ATTR-NEXT: [[DOTCOUNTED_BY_LOAD:%.*]] = load i32, ptr [[DOTCOUNTED_BY_GEP]], align 8
-// SANITIZE-WITH-ATTR-NEXT: [[TMP0:%.*]] = icmp ule i32 [[INDEX]], [[DOTCOUNTED_BY_LOAD]], !nosanitize [[META6]]
+// SANITIZE-WITH-ATTR-NEXT: [[TMP0:%.*]] = icmp ult i32 [[INDEX]], [[DOTCOUNTED_BY_LOAD]], !nosanitize [[META6]]
// SANITIZE-WITH-ATTR-NEXT: [[TMP1:%.*]] = icmp sgt i32 [[DOTCOUNTED_BY_LOAD]], 0, !nosanitize [[META6]]
// SANITIZE-WITH-ATTR-NEXT: [[TMP2:%.*]] = and i1 [[TMP1]], [[TMP0]], !nosanitize [[META6]]
// SANITIZE-WITH-ATTR-NEXT: br i1 [[TMP2]], label %[[CONT10:.*]], label %[[HANDLER_OUT_OF_BOUNDS:.*]], !prof [[PROF7]], !nosanitize [[META6]]
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline.c b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
index 0721a9b1475b3..522d4f83a2b31 100644
--- a/clang/test/CodeGen/ubsan-array-bounds-baseline.c
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
@@ -65,11 +65,11 @@ void c_load_cast(int i) { v = *(int *)&a[i]; }
void c_compound(int i) { a[i] += 1; }
// CHECK-LABEL: define {{.*}}@c_compound_paren(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_compound_paren(int i) { (a[i]) += 1; }
// CHECK-LABEL: define {{.*}}@c_compound_deref_addr(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_compound_deref_addr(int i) { *&a[i] += 1; }
// CHECK-LABEL: define {{.*}}@c_postinc(
@@ -93,11 +93,11 @@ void c_predec(int i) { --a[i]; }
void c_agg_store(int i) { sa[i] = aggl; }
// CHECK-LABEL: define {{.*}}@c_agg_store_paren(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_agg_store_paren(int i) { (sa[i]) = aggl; }
// CHECK-LABEL: define {{.*}}@c_agg_store_deref_addr(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_agg_store_deref_addr(int i) { *&sa[i] = aggl; }
// CHECK-LABEL: define {{.*}}@c_agg_load(
@@ -180,19 +180,19 @@ void c_byval(int i) { sink(sa[i]); }
// Same expression as c_store: C99 6.5.3.2p3 makes `&*E` into `E`.
// CHECK-LABEL: define {{.*}}@c_deref_addr(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_deref_addr(int i) { *&a[i] = 1; }
// CHECK-LABEL: define {{.*}}@c_paren(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_paren(int i) { (a[i]) = 1; }
// CHECK-LABEL: define {{.*}}@c_extension(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_extension(int i) { __extension__(a[i]) = 1; }
// CHECK-LABEL: define {{.*}}@c_cast_store(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_cast_store(int i) { *(int *)&a[i] = 1; }
// CHECK-LABEL: define {{.*}}@c_counted(
diff --git a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
index 3774f846476bf..04584492c2beb 100644
--- a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
+++ b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
@@ -194,7 +194,7 @@ void x_ptr_to_member(int i) { ma[i].*pmf = 1; }
void x_ptr_to_member_arrow(int i) { (&ma[i])->*pmf = 1; }
// CHECK-LABEL: define {{.*}}@_Z10x_cond_armib(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_cond_arm(int i, bool c) { (c ? a[i] : a[0]) = 1; }
// An assignment is a prvalue in C and an lvalue in C++, so the four cases below
@@ -204,17 +204,17 @@ void x_cond_arm(int i, bool c) { (c ? a[i] : a[0]) = 1; }
void x_store(int i) { a[i] = 1; }
// CHECK-LABEL: define {{.*}}@_Z7x_pareni(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_paren(int i) { (a[i]) = 1; }
// CHECK-LABEL: define {{.*}}@_Z12x_deref_addri(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_deref_addr(int i) { *&a[i] = 1; }
// C has no glvalue comma, so the C file can only test the pointer
// form, c_comma_addr.
// CHECK-LABEL: define {{.*}}@_Z7x_commai(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_comma(int i) { (1, a[i]) = 1; }
// CHECK-LABEL: define {{.*}}@_Z11x_copy_initi(
>From 7b93800ca572a4853cdc511b96ac1acaf6ba20cb Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:54:47 -0700
Subject: [PATCH 06/17] [clang][CodeGen] Note where the object requirement is
lost, and test it
`*(c ? &a[i] : &a[0]) = 1` and `*(1, &a[i]) = 1` are still accepted:
EmitPointerWithAlignment does not handle those two shapes, as its existing TODO
notes, and the requirement cannot follow the fallback into the scalar emitter.
Record that where the TODO is, and add both cases so the gap stays visible.
---
clang/lib/CodeGen/CGExpr.cpp | 6 ++++++
.../ubsan-array-bounds-pointer-shapes.c | 19 +++++++++++++++++++
2 files changed, 25 insertions(+)
create mode 100644 clang/test/CodeGen/ubsan-array-bounds-pointer-shapes.c
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index de3569c21c686..c248283c44ddb 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1612,6 +1612,12 @@ EmitPointerWithAlignment(const Expr *E, LValueBaseInfo *BaseInfo,
}
// TODO: conditional operators, comma.
+ //
+ // Req is dropped for those two shapes, since EmitScalarExpr below cannot carry
+ // it; implementing the TODO would fix that too. Only the pointer-valued
+ // spellings are affected: `(c ? a[i] : a[0]) = 1` and `(1, a[i]) = 1` are
+ // glvalues in C++ and stay in the lvalue emitter.
+ // See clang/test/CodeGen/ubsan-array-bounds-pointer-shapes.c.
// Otherwise, use the alignment of the type.
return CGF.makeNaturalAddressForPointer(
diff --git a/clang/test/CodeGen/ubsan-array-bounds-pointer-shapes.c b/clang/test/CodeGen/ubsan-array-bounds-pointer-shapes.c
new file mode 100644
index 0000000000000..7939876e39ade
--- /dev/null
+++ b/clang/test/CodeGen/ubsan-array-bounds-pointer-shapes.c
@@ -0,0 +1,19 @@
+// Stores through a pointer-valued conditional or comma: the two shapes named by
+// the "TODO: conditional operators, comma" in EmitPointerWithAlignment. The
+// requirement is lost there, so neither is rejected. This file asserts the
+// answer they should get, so implementing that TODO will make it pass and the
+// XFAIL can go.
+//
+// XFAIL: *
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -fsanitize=array-bounds \
+// RUN: -Wno-array-bounds -std=c11 %s -o - | FileCheck %s
+
+int a[4];
+
+// CHECK-LABEL: define {{.*}}@cond_arm(
+// CHECK: icmp ult i64 {{.*}}, 4
+void cond_arm(int i, int c) { *(c ? &a[i] : &a[0]) = 1; }
+
+// CHECK-LABEL: define {{.*}}@comma_addr(
+// CHECK: icmp ult i64 {{.*}}, 4
+void comma_addr(int i) { *(1, &a[i]) = 1; }
>From c55a3aa96c30c222208366fa7152ec7687084de2 Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:54:50 -0700
Subject: [PATCH 07/17] [clang][CodeGen] Require the object for a by-value
aggregate argument
Passing an element by value copies it, so an out-of-bounds index should be
rejected (C99 6.3.2.1p1), but was accepted. Require the object where the argument is
emitted -- in C, and in the trivial copy constructor that C++ uses for the same
source line.
---
clang/lib/CodeGen/CGCall.cpp | 3 ++-
clang/lib/CodeGen/CGClass.cpp | 3 ++-
clang/test/CodeGen/ubsan-array-bounds-baseline.c | 2 +-
clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp | 4 ++--
4 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index e0993ef1e61b3..9da4f5dc47abe 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5321,7 +5321,8 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
ICE->getSubExpr()->getType().getAddressSpace() !=
LangAS::hlsl_constant &&
!type->isArrayParameterType() && !type.isNonTrivialToPrimitiveCopy()) {
- LValue L = EmitLValue(cast<CastExpr>(E)->getSubExpr());
+ LValue L = EmitLValue(cast<CastExpr>(E)->getSubExpr(), NotKnownNonNull,
+ ObjectRequired);
assert(L.isSimple());
args.addUncopiedAggregate(L, type);
return;
diff --git a/clang/lib/CodeGen/CGClass.cpp b/clang/lib/CodeGen/CGClass.cpp
index 9c0c1cbdeb219..e84cc77944c78 100644
--- a/clang/lib/CodeGen/CGClass.cpp
+++ b/clang/lib/CodeGen/CGClass.cpp
@@ -2305,7 +2305,8 @@ void CodeGenFunction::EmitCXXConstructorCall(
assert(E->getNumArgs() == 1 && "unexpected argcount for trivial ctor");
const Expr *Arg = E->getArg(0);
- LValue Src = EmitCheckedLValue(Arg, TCK_Load);
+ LValue Src = EmitLValue(Arg, NotKnownNonNull, ObjectRequired);
+ EmitTypeCheck(TCK_Load, Arg, Src);
CanQualType DestTy = getContext().getCanonicalTagType(D->getParent());
LValue Dest = MakeAddrLValue(This, DestTy);
EmitAggregateCopyCtor(Dest, Src, ThisAVS.mayOverlap());
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline.c b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
index 522d4f83a2b31..711011be8c195 100644
--- a/clang/test/CodeGen/ubsan-array-bounds-baseline.c
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
@@ -175,7 +175,7 @@ void c_vec_rvalue(int i) {
}
// CHECK-LABEL: define {{.*}}@c_byval(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_byval(int i) { sink(sa[i]); }
// Same expression as c_store: C99 6.5.3.2p3 makes `&*E` into `E`.
diff --git a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
index 04584492c2beb..8ce16b7409c39 100644
--- a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
+++ b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
@@ -218,7 +218,7 @@ void x_deref_addr(int i) { *&a[i] = 1; }
void x_comma(int i) { (1, a[i]) = 1; }
// CHECK-LABEL: define {{.*}}@_Z11x_copy_initi(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_copy_init(int i) {
Agg b = agga[i];
(void)b;
@@ -227,7 +227,7 @@ void x_copy_init(int i) {
// The element passed by value, through the same constructor.
void xsink(Agg);
// CHECK-LABEL: define {{.*}}@_Z7x_byvali(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_byval(int i) { xsink(agga[i]); }
// With a non-trivial copy constructor there is a real call and the argument binds
>From 220b573d97fda5bf7db21be52aa9d9c764b47931 Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:54:54 -0700
Subject: [PATCH 08/17] [clang][CodeGen] Require the object when binding a
reference
`int &r = a[i];` was accepted for an out-of-bounds index, though [dcl.ref]p5
requires the glvalue to designate an existing object -- a rule
EmitReferenceBindingToExpr already quotes to justify its null and alignment
checks. Require the object there, which covers declarations, default arguments,
default member initializers, and arguments that bind to a reference.
---
clang/lib/CodeGen/CGExpr.cpp | 2 +-
.../ubsan-array-bounds-baseline.cpp | 20 +++++++++----------
2 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index c248283c44ddb..dfba63a13e7c3 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -699,7 +699,7 @@ EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
RValue
CodeGenFunction::EmitReferenceBindingToExpr(const Expr *E) {
// Emit the expression as an lvalue.
- LValue LV = EmitLValue(E);
+ LValue LV = EmitLValue(E, NotKnownNonNull, ObjectRequired);
assert(LV.isSimple());
llvm::Value *Value = LV.getPointer(*this);
diff --git a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
index 8ce16b7409c39..e52b542a8c087 100644
--- a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
+++ b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
@@ -54,37 +54,37 @@ MS msa[4];
//===----------------------------------------------------------------------===//
// CHECK-LABEL: define {{.*}}@_Z10x_ref_bindi(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_ref_bind(int i) {
int &r = a[i];
(void)r;
}
// CHECK-LABEL: define {{.*}}@_Z11x_ref_consti(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_ref_const(int i) {
const int &r = a[i];
(void)r;
}
// CHECK-LABEL: define {{.*}}@_Z14x_ref_cleanupsi(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_ref_cleanups(int i) {
int &r = (T(), a[i]);
(void)r;
}
// CHECK-LABEL: define {{.*}}@_Z12x_ref_returni(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
int &x_ref_return(int i) { return a[i]; }
void takes_ref(int &);
// CHECK-LABEL: define {{.*}}@_Z11x_ref_parami(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_ref_param(int i) { takes_ref(a[i]); }
// CHECK-LABEL: define {{.*}}@_Z16x_ref_structuredi(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_ref_structured(int i) {
auto &[e] = agga[i];
(void)e;
@@ -93,7 +93,7 @@ void x_ref_structured(int i) {
// A default argument bound to a reference.
int &pick(int &r = a[gidx]);
// CHECK-LABEL: define {{.*}}@_Z13x_default_argv(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_default_arg() { pick(); }
// A default member initializer bound to a reference. The check lands in the
@@ -102,7 +102,7 @@ struct R {
int &r = a[gidx];
};
// CTOR-LABEL: define {{.*}}@_ZN1RC2Ev(
-// CTOR: icmp ule i64 {{.*}}, 4
+// CTOR: icmp ult i64 {{.*}}, 4
void x_default_init() {
R x;
(void)x;
@@ -113,7 +113,7 @@ struct S {
S(int i) : r(a[i]) {}
};
// CTOR-LABEL: define {{.*}}@_ZN1SC2Ei(
-// CTOR: icmp ule i64 {{.*}}, 4
+// CTOR: icmp ult i64 {{.*}}, 4
void x_ref_meminit(int i) {
S x(i);
(void)x;
@@ -242,7 +242,7 @@ void ntsink(NT);
// A non-trivial copy constructor makes a real call, so the
// argument binds to a reference instead.
// CHECK-LABEL: define {{.*}}@_Z18x_byval_nontriviali(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_byval_nontrivial(int i) { ntsink(nta[i]); }
//===----------------------------------------------------------------------===//
>From 6ea9f7fbe9a6a416a2dc32f825a944ba5e37efb1 Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:54:57 -0700
Subject: [PATCH 09/17] [clang][CodeGen] Require the object for a glvalue base
conversion
`Base &r = da[i];` was accepted, though the conversion names a base class
subobject of the element (C++ [expr.static.cast]p2). Require the object for
glvalue base conversions in either direction. The pointer form of the same
conversion is address arithmetic and stays permissive.
---
clang/lib/CodeGen/CGExpr.cpp | 4 ++--
clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp | 6 +++---
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index dfba63a13e7c3..577a0370fc56a 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -6315,7 +6315,7 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E,
case CK_UncheckedDerivedToBase:
case CK_DerivedToBase: {
auto *DerivedClassDecl = E->getSubExpr()->getType()->castAsCXXRecordDecl();
- LValue LV = EmitLValue(E->getSubExpr());
+ LValue LV = EmitLValue(E->getSubExpr(), NotKnownNonNull, ObjectRequired);
Address This = LV.getAddress();
// Perform the derived-to-base conversion
@@ -6333,7 +6333,7 @@ LValue CodeGenFunction::EmitCastLValue(const CastExpr *E,
return EmitAggExprToLValue(E);
case CK_BaseToDerived: {
auto *DerivedClassDecl = E->getType()->castAsCXXRecordDecl();
- LValue LV = EmitLValue(E->getSubExpr());
+ LValue LV = EmitLValue(E->getSubExpr(), NotKnownNonNull, ObjectRequired);
// Perform the base-to-derived conversion
Address Derived = GetAddressOfDerivedClass(
diff --git a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
index e52b542a8c087..516cc3f3b0a9b 100644
--- a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
+++ b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
@@ -163,14 +163,14 @@ void x_trivial_assign_rhs(int i) { agglocal = agga[i]; }
void x_explicit_assign(int i) { agglocal.operator=(agga[i]); }
// CHECK-LABEL: define {{.*}}@_Z17x_derived_to_basei(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_derived_to_base(int i) {
Base &r = da[i];
(void)r;
}
// CHECK-LABEL: define {{.*}}@_Z18x_static_cast_basei(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_static_cast_base(int i) {
Base &r = static_cast<Base &>(da[i]);
(void)r;
@@ -179,7 +179,7 @@ void x_static_cast_base(int i) {
// The other direction.
Base ba[4];
// CHECK-LABEL: define {{.*}}@_Z17x_base_to_derivedi(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_base_to_derived(int i) {
Derived &r = static_cast<Derived &>(ba[i]);
(void)r;
>From 733decc1fdbf088cf566f6577284d0586fecc9f7 Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:55:02 -0700
Subject: [PATCH 10/17] [clang][CodeGen] Require the object for a member access
`(&s[i])->x = 1` was accepted while the equivalent `s[i].x = 1` was rejected,
though C99 6.5.2.3p4 defines `E1->E2` as `(*E1).E2`. Require the object when
emitting the base of a member access, in both spellings: `E1.E2` designates a
member of the object E1 designates (C99 6.5.2.3p3), so the requirement belongs
to the operator rather than to the enclosing context. That also covers a wrapped
base such as `(*&s[i]).x`, which the `.` path got right only while the subscript
was its immediate operand.
---
clang/lib/CodeGen/CGExpr.cpp | 9 ++++++---
clang/test/CodeGen/ubsan-array-bounds-baseline.c | 8 ++++----
clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp | 2 +-
3 files changed, 11 insertions(+), 8 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 577a0370fc56a..bd37e76f9b969 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5623,7 +5623,8 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr *E) {
if (E->isArrow()) {
LValueBaseInfo BaseInfo;
TBAAAccessInfo TBAAInfo;
- Address Addr = EmitPointerWithAlignment(BaseExpr, &BaseInfo, &TBAAInfo);
+ Address Addr = EmitPointerWithAlignment(BaseExpr, &BaseInfo, &TBAAInfo,
+ NotKnownNonNull, ObjectRequired);
QualType PtrTy = BaseExpr->getType()->getPointeeType();
SanitizerSet SkippedChecks;
bool IsBaseCXXThis = IsWrappedCXXThis(BaseExpr);
@@ -5634,8 +5635,10 @@ LValue CodeGenFunction::EmitMemberExpr(const MemberExpr *E) {
EmitTypeCheck(TCK_MemberAccess, E->getExprLoc(), Addr, PtrTy,
/*Alignment=*/CharUnits::Zero(), SkippedChecks);
BaseLV = MakeAddrLValue(Addr, PtrTy, BaseInfo, TBAAInfo);
- } else
- BaseLV = EmitCheckedLValue(BaseExpr, TCK_MemberAccess);
+ } else {
+ BaseLV = EmitLValue(BaseExpr, NotKnownNonNull, ObjectRequired);
+ EmitTypeCheck(TCK_MemberAccess, BaseExpr, BaseLV);
+ }
NamedDecl *ND = E->getMemberDecl();
if (auto *Field = dyn_cast<FieldDecl>(ND)) {
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline.c b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
index 711011be8c195..191e1923d7035 100644
--- a/clang/test/CodeGen/ubsan-array-bounds-baseline.c
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
@@ -113,15 +113,15 @@ void c_agg_load_deref_addr(int i) { aggl = *&sa[i]; }
void c_member_dot(int i) { sa[i].x = 1; }
// CHECK-LABEL: define {{.*}}@c_member_dot_deref_addr(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_member_dot_deref_addr(int i) { (*&sa[i]).x = 1; }
// CHECK-LABEL: define {{.*}}@c_member_dot_deref_addr_load(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_member_dot_deref_addr_load(int i) { v = (*&sa[i]).x; }
// CHECK-LABEL: define {{.*}}@c_member_arrow(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_member_arrow(int i) { (&sa[i])->x = 1; }
// Taking the address of a member is not one of the rewrites in C99 6.5.3.2p3,
@@ -131,7 +131,7 @@ void c_member_arrow(int i) { (&sa[i])->x = 1; }
void c_member_dot_addr(int i) { p = &sa[i].x; }
// CHECK-LABEL: define {{.*}}@c_member_arrow_addr(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_member_arrow_addr(int i) { p = &(&sa[i])->x; }
// CHECK-LABEL: define {{.*}}@c_complex_real(
diff --git a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
index 516cc3f3b0a9b..be6359d36d142 100644
--- a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
+++ b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
@@ -146,7 +146,7 @@ void x_member_call_static(int i) { msa[i].stat(); }
void x_member_dot(int i) { ma[i].f = 1; }
// CHECK-LABEL: define {{.*}}@_Z14x_member_arrowi(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_member_arrow(int i) { (&ma[i])->f = 1; }
// CHECK-LABEL: define {{.*}}@_Z20x_trivial_assign_lhsi(
>From 274740261c51385c9217d569d95fa37ae9c687f0 Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:55:05 -0700
Subject: [PATCH 11/17] [clang][CodeGen] Require the object for a member call
and a trivial assignment
A member call on an out-of-bounds element was accepted, though calling a
non-static member function requires an object
(C++ [class.mfct.non-static]p2). Require it for the object expression, whether
written with `.` or `->`, and for both operands of a trivial assignment.
---
clang/lib/CodeGen/CGExprCXX.cpp | 30 ++++++++++++-------
.../ubsan-aggregate-null-align-bounds.c | 13 ++++----
.../ubsan-array-bounds-baseline.cpp | 10 +++----
3 files changed, 32 insertions(+), 21 deletions(-)
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index e400a5c5a49c5..cffa8a620f059 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -268,7 +268,9 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorMemberCallExpr(
if (auto *OCE = dyn_cast<CXXOperatorCallExpr>(CE)) {
if (OCE->isAssignmentOp()) {
if (TrivialAssignment) {
- TrivialAssignmentRHS = EmitCheckedLValue(CE->getArg(1), TCK_Load);
+ TrivialAssignmentRHS =
+ EmitLValue(CE->getArg(1), NotKnownNonNull, ObjectRequired);
+ EmitTypeCheck(TCK_Load, CE->getArg(1), TrivialAssignmentRHS);
} else {
RtlArgs = &RtlArgStorage;
EmitCallArgs(*RtlArgs, MD->getType()->castAs<FunctionProtoType>(),
@@ -284,13 +286,16 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorMemberCallExpr(
if (IsArrow) {
LValueBaseInfo BaseInfo;
TBAAAccessInfo TBAAInfo;
- Address ThisValue = EmitPointerWithAlignment(Base, &BaseInfo, &TBAAInfo);
+ Address ThisValue = EmitPointerWithAlignment(Base, &BaseInfo, &TBAAInfo,
+ NotKnownNonNull,
+ ObjectRequired);
return MakeAddrLValue(ThisValue, Base->getType()->getPointeeType(),
BaseInfo, TBAAInfo);
}
+ LValue LV = EmitLValue(Base, NotKnownNonNull, ObjectRequired);
if (EmitCheckedForStore)
- return EmitCheckedLValue(Base, TCK_Store);
- return EmitLValue(Base);
+ EmitTypeCheck(TCK_Store, Base, LV);
+ return LV;
};
if (const CXXConstructorDecl *Ctor = dyn_cast<CXXConstructorDecl>(MD)) {
@@ -323,12 +328,17 @@ RValue CodeGenFunction::EmitCXXMemberOrOperatorMemberCallExpr(
// when it isn't necessary; just produce the proper effect here.
LValue This = getLValueForThis(/*EmitCheckedForStore=*/true);
- // It's important that we use the result of EmitCheckedLValue here rather
- // than emitting call arguments, in order to preserve TBAA information
- // from the RHS.
- LValue RHS = isa<CXXOperatorCallExpr>(CE)
- ? TrivialAssignmentRHS
- : EmitCheckedLValue(*CE->arg_begin(), TCK_Load);
+ // It's important that we emit the RHS as an lvalue here rather than
+ // emitting call arguments, in order to preserve TBAA information from the
+ // RHS.
+ LValue RHS;
+ if (isa<CXXOperatorCallExpr>(CE)) {
+ RHS = TrivialAssignmentRHS;
+ } else {
+ const Expr *RHSExpr = *CE->arg_begin();
+ RHS = EmitLValue(RHSExpr, NotKnownNonNull, ObjectRequired);
+ EmitTypeCheck(TCK_Load, RHSExpr, RHS);
+ }
EmitAggregateAssign(This, RHS, CE->getType());
return RValue::get(This.getPointer(*this));
}
diff --git a/clang/test/CodeGen/ubsan-aggregate-null-align-bounds.c b/clang/test/CodeGen/ubsan-aggregate-null-align-bounds.c
index 7fd2e6c2d0300..02cb52cbdde5b 100644
--- a/clang/test/CodeGen/ubsan-aggregate-null-align-bounds.c
+++ b/clang/test/CodeGen/ubsan-aggregate-null-align-bounds.c
@@ -1,6 +1,6 @@
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -fsanitize=null,alignment,array-bounds -Wno-array-bounds -std=c11 -O0 %s -o - | FileCheck %s --check-prefixes=CHECK,C
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -fsanitize=null,alignment,array-bounds -Wno-array-bounds -std=c11 -O0 %s -o - | FileCheck %s --check-prefix=CHECK
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -fsanitize=null,alignment,array-bounds -Wno-array-bounds -std=c++17 -x c++ -O0 %s -o - | FileCheck %s --check-prefixes=CHECK,CXX
-// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -fsanitize=null,alignment,array-bounds -Wno-array-bounds -std=c11 -O0 -DUSE_UNION %s -o - | FileCheck %s --check-prefixes=CHECK,C
+// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -fsanitize=null,alignment,array-bounds -Wno-array-bounds -std=c11 -O0 -DUSE_UNION %s -o - | FileCheck %s --check-prefix=CHECK
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -fsanitize=null,alignment,array-bounds -Wno-array-bounds -std=c++17 -x c++ -O0 -DUSE_UNION %s -o - | FileCheck %s --check-prefixes=CHECK,CXX
#ifdef USE_UNION
@@ -89,12 +89,13 @@ void test_init_from_subscript(AGG arr[4]) {
}
// Array bounds - out-of-bounds access (RHS)
-// Note: GCC also does not detect the out-of-bounds access here when compiled as
-// C++.
+// Both languages detect this now. C++ used to miss it: the read goes through the
+// implicitly-declared operator=, whose argument carries a const conversion, so
+// the old shape test in EmitCheckedLValue did not recognise the subscript.
+// (GCC still does not detect it when compiled as C++.)
// CHECK-LABEL: define {{.*}}@test_oob_rhs(
-// C: br i1 false, label %cont, label %handler.out_of_bounds
-// CXX: br i1 true, label %cont, label %handler.out_of_bounds
+// CHECK: br i1 false, label %cont, label %handler.out_of_bounds
// CHECK: handler.out_of_bounds:
// CHECK-NEXT: call void @__ubsan_handle_out_of_bounds_abort
// CHECK: handler.type_mismatch:
diff --git a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
index be6359d36d142..c3da9c6c4daa1 100644
--- a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
+++ b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
@@ -120,15 +120,15 @@ void x_ref_meminit(int i) {
}
// CHECK-LABEL: define {{.*}}@_Z13x_member_calli(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_member_call(int i) { ma[i].m(); }
// CHECK-LABEL: define {{.*}}@_Z19x_member_call_arrowi(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_member_call_arrow(int i) { (&ma[i])->m(); }
// CHECK-LABEL: define {{.*}}@_Z21x_member_call_virtuali(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_member_call_virtual(int i) { msa[i].virt(); }
// TODO: confirm with reviewers. C++ [class.static]p2 says the object expression
@@ -154,12 +154,12 @@ void x_member_arrow(int i) { (&ma[i])->f = 1; }
void x_trivial_assign_lhs(int i) { agga[i] = agglocal; }
// CHECK-LABEL: define {{.*}}@_Z20x_trivial_assign_rhsi(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_trivial_assign_rhs(int i) { agglocal = agga[i]; }
// The same assignment spelled as an explicit operator= call.
// CHECK-LABEL: define {{.*}}@_Z17x_explicit_assigni(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_explicit_assign(int i) { agglocal.operator=(agga[i]); }
// CHECK-LABEL: define {{.*}}@_Z17x_derived_to_basei(
>From ff9c31bea9449176da36f9a883781e7c6d5e3cbd Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:55:08 -0700
Subject: [PATCH 12/17] [clang][CodeGen] Require the object for .* and ->*
`ma[i].*pmf = 1` was accepted, though the result designates a member of the
element (C++ [expr.mptr.oper]p4). Require the object for the left operand of `.*`
and `->*`.
---
clang/lib/CodeGen/CGExpr.cpp | 6 ++++--
clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp | 4 ++--
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index bd37e76f9b969..7013d66a905d4 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -7250,9 +7250,11 @@ LValue CodeGenFunction::
EmitPointerToDataMemberBinaryExpr(const BinaryOperator *E) {
Address BaseAddr = Address::invalid();
if (E->getOpcode() == BO_PtrMemI) {
- BaseAddr = EmitPointerWithAlignment(E->getLHS());
+ BaseAddr = EmitPointerWithAlignment(E->getLHS(), nullptr, nullptr,
+ NotKnownNonNull, ObjectRequired);
} else {
- BaseAddr = EmitLValue(E->getLHS()).getAddress();
+ BaseAddr =
+ EmitLValue(E->getLHS(), NotKnownNonNull, ObjectRequired).getAddress();
}
llvm::Value *OffsetV = EmitScalarExpr(E->getRHS());
diff --git a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
index c3da9c6c4daa1..e92c6dc6e82fe 100644
--- a/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
+++ b/clang/test/CodeGenCXX/ubsan-array-bounds-baseline.cpp
@@ -186,11 +186,11 @@ void x_base_to_derived(int i) {
}
// CHECK-LABEL: define {{.*}}@_Z15x_ptr_to_memberi(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_ptr_to_member(int i) { ma[i].*pmf = 1; }
// CHECK-LABEL: define {{.*}}@_Z21x_ptr_to_member_arrowi(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void x_ptr_to_member_arrow(int i) { (&ma[i])->*pmf = 1; }
// CHECK-LABEL: define {{.*}}@_Z10x_cond_armib(
>From 63b81b7fc63b8311a1cf9987b4022a71714026da Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:55:12 -0700
Subject: [PATCH 13/17] [clang][CodeGen] Require the object for a vector
component
`va[i].x = 1` was accepted, though a component names storage inside the
element. Require the object for the two lvalue base forms of a vector component.
A temporary base is unaffected, since the component then belongs to the
temporary.
---
clang/lib/CodeGen/CGExpr.cpp | 5 +++--
clang/test/CodeGen/ubsan-array-bounds-baseline.c | 4 ++--
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 7013d66a905d4..7201bf0e4ce42 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -5498,7 +5498,8 @@ EmitExtVectorElementExpr(const ExtVectorElementExpr *E) {
// it.
LValueBaseInfo BaseInfo;
TBAAAccessInfo TBAAInfo;
- Address Ptr = EmitPointerWithAlignment(E->getBase(), &BaseInfo, &TBAAInfo);
+ Address Ptr = EmitPointerWithAlignment(E->getBase(), &BaseInfo, &TBAAInfo,
+ NotKnownNonNull, ObjectRequired);
const auto *PT = E->getBase()->getType()->castAs<PointerType>();
Base = MakeAddrLValue(Ptr, PT->getPointeeType(), BaseInfo, TBAAInfo);
Base.getQuals().removeObjCGCAttr();
@@ -5506,7 +5507,7 @@ EmitExtVectorElementExpr(const ExtVectorElementExpr *E) {
// Otherwise, if the base is an lvalue ( as in the case of foo.x.x),
// emit the base as an lvalue.
assert(E->getBase()->getType()->isVectorType());
- Base = EmitLValue(E->getBase());
+ Base = EmitLValue(E->getBase(), NotKnownNonNull, ObjectRequired);
} else {
// Otherwise, the base is a normal rvalue (as in (V+V).x), emit it as such.
assert(E->getBase()->getType()->isVectorType() &&
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline.c b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
index 191e1923d7035..56b38a9686cb8 100644
--- a/clang/test/CodeGen/ubsan-array-bounds-baseline.c
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
@@ -159,11 +159,11 @@ void c_complex_compound(int i) { ca[i] += cv; }
void c_complex_incdec(int i) { ca[i]++; }
// CHECK-LABEL: define {{.*}}@c_vec_elem(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_vec_elem(int i) { va[i].x = 1; }
// CHECK-LABEL: define {{.*}}@c_vec_elem_arrow(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_vec_elem_arrow(int i) { (&va[i])->x = 1; }
// The component belongs to a temporary, not to an element; the
>From c39c1cde52e202d07449e5d9dfb521ea9a5cfbbe Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:55:16 -0700
Subject: [PATCH 14/17] [clang][CodeGen] Require the object for complex lvalues
No complex lvalue was rejected for an out-of-bounds index -- reads, stores,
component stores and compound assignment alike -- though the scalar equivalents
were, and an lvalue that designates no object must not be evaluated
(C99 6.3.2.1p1). Require it in the four complex lvalue paths.
---
clang/lib/CodeGen/CGExpr.cpp | 2 +-
clang/lib/CodeGen/CGExprComplex.cpp | 13 +++++++++----
clang/test/CodeGen/ubsan-array-bounds-baseline.c | 12 ++++++------
3 files changed, 16 insertions(+), 11 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 7201bf0e4ce42..198f6ab812fc4 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -3889,7 +3889,7 @@ LValue CodeGenFunction::EmitUnaryOpLValue(const UnaryOperator *E,
}
case UO_Real:
case UO_Imag: {
- LValue LV = EmitLValue(E->getSubExpr());
+ LValue LV = EmitLValue(E->getSubExpr(), NotKnownNonNull, ObjectRequired);
assert(LV.isSimple() && "real/imag on non-ordinary l-value");
// __real is valid on scalars. This is a faster way of testing that.
diff --git a/clang/lib/CodeGen/CGExprComplex.cpp b/clang/lib/CodeGen/CGExprComplex.cpp
index 350cbd18c7ed7..566ab8f66673f 100644
--- a/clang/lib/CodeGen/CGExprComplex.cpp
+++ b/clang/lib/CodeGen/CGExprComplex.cpp
@@ -72,7 +72,9 @@ class ComplexExprEmitter
/// value l-value, this method emits the address of the l-value, then loads
/// and returns the result.
ComplexPairTy EmitLoadOfLValue(const Expr *E) {
- return EmitLoadOfLValue(CGF.EmitLValue(E), E->getExprLoc());
+ return EmitLoadOfLValue(
+ CGF.EmitLValue(E, NotKnownNonNull, CodeGenFunction::ObjectRequired),
+ E->getExprLoc());
}
ComplexPairTy EmitLoadOfLValue(LValue LV, SourceLocation Loc);
@@ -196,7 +198,8 @@ class ComplexExprEmitter
// Operators.
ComplexPairTy VisitPrePostIncDec(const UnaryOperator *E, bool isInc,
bool isPre) {
- LValue LV = CGF.EmitLValue(E->getSubExpr());
+ LValue LV = CGF.EmitLValue(E->getSubExpr(), NotKnownNonNull,
+ CodeGenFunction::ObjectRequired);
return CGF.EmitComplexPrePostIncDec(E, LV, isInc, isPre);
}
ComplexPairTy VisitUnaryPostDec(const UnaryOperator *E) {
@@ -1265,7 +1268,8 @@ LValue ComplexExprEmitter::EmitCompoundAssignLValue(
}
}
- LValue LHS = CGF.EmitLValue(E->getLHS());
+ LValue LHS =
+ CGF.EmitLValue(E->getLHS(), NotKnownNonNull, CodeGenFunction::ObjectRequired);
// Load from the l-value and convert it.
SourceLocation Loc = E->getExprLoc();
@@ -1352,7 +1356,8 @@ LValue ComplexExprEmitter::EmitBinAssignLValue(const BinaryOperator *E,
Val = Visit(E->getRHS());
// Compute the address to store into.
- LValue LHS = CGF.EmitLValue(E->getLHS());
+ LValue LHS =
+ CGF.EmitLValue(E->getLHS(), NotKnownNonNull, CodeGenFunction::ObjectRequired);
// Store the result value into the LHS lvalue.
EmitStoreOfComplex(Val, LHS, /*isInit*/ false);
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline.c b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
index 56b38a9686cb8..415fc3027ae24 100644
--- a/clang/test/CodeGen/ubsan-array-bounds-baseline.c
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
@@ -135,27 +135,27 @@ void c_member_dot_addr(int i) { p = &sa[i].x; }
void c_member_arrow_addr(int i) { p = &(&sa[i])->x; }
// CHECK-LABEL: define {{.*}}@c_complex_real(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_complex_real(int i) { __real__ ca[i] = 1; }
// CHECK-LABEL: define {{.*}}@c_complex_imag(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_complex_imag(int i) { __imag__ ca[i] = 1; }
// CHECK-LABEL: define {{.*}}@c_complex_load(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_complex_load(int i) { cv = ca[i]; }
// CHECK-LABEL: define {{.*}}@c_complex_store(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_complex_store(int i) { ca[i] = cv; }
// CHECK-LABEL: define {{.*}}@c_complex_compound(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_complex_compound(int i) { ca[i] += cv; }
// CHECK-LABEL: define {{.*}}@c_complex_incdec(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_complex_incdec(int i) { ca[i]++; }
// CHECK-LABEL: define {{.*}}@c_vec_elem(
>From 51d5280410682842a38c47453280f78803770590 Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:55:20 -0700
Subject: [PATCH 15/17] [clang][CodeGen] Require the object for ARC loads and
stores
A store to a __strong element was accepted while the __weak store beside it was
rejected. An ownership qualifier changes the retain and release sequence around an
access, not whether the element has to exist (C99 6.3.2.1p1). Require the object
for ARC loads and stores.
---
clang/lib/CodeGen/CGObjC.cpp | 13 ++++++++-----
.../test/CodeGen/ubsan-array-bounds-baseline-arc.m | 10 +++++-----
2 files changed, 13 insertions(+), 10 deletions(-)
diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp
index c724a063bafe8..558bed93f7a0c 100644
--- a/clang/lib/CodeGen/CGObjC.cpp
+++ b/clang/lib/CodeGen/CGObjC.cpp
@@ -2993,7 +2993,8 @@ static TryEmitResult tryEmitARCRetainLoadOfScalar(CodeGenFunction &CGF,
!type.isConstQualified() &&
type.getObjCLifetime() == Qualifiers::OCL_Strong) {
// Emit the lvalue.
- LValue lv = CGF.EmitLValue(e);
+ LValue lv = CGF.EmitLValue(e, NotKnownNonNull,
+ CodeGenFunction::ObjectRequired);
// Load the object pointer.
llvm::Value *result = CGF.EmitLoadOfLValue(lv,
@@ -3025,7 +3026,9 @@ static TryEmitResult tryEmitARCRetainLoadOfScalar(CodeGenFunction &CGF,
!shouldRetainObjCLifetime(type.getObjCLifetime()));
}
- return tryEmitARCRetainLoadOfScalar(CGF, CGF.EmitLValue(e), type);
+ return tryEmitARCRetainLoadOfScalar(
+ CGF, CGF.EmitLValue(e, NotKnownNonNull, CodeGenFunction::ObjectRequired),
+ type);
}
typedef llvm::function_ref<llvm::Value *(CodeGenFunction &CGF,
@@ -3670,7 +3673,7 @@ CodeGenFunction::EmitARCStoreUnsafeUnretained(const BinaryOperator *e,
}
// Emit the LHS and perform the store.
- LValue lvalue = EmitLValue(e->getLHS());
+ LValue lvalue = EmitLValue(e->getLHS(), NotKnownNonNull, ObjectRequired);
EmitStoreOfScalar(value, lvalue);
return std::pair<LValue,llvm::Value*>(std::move(lvalue), value);
@@ -3693,7 +3696,7 @@ CodeGenFunction::EmitARCStoreStrong(const BinaryOperator *e,
hasImmediateRetain = true;
}
- LValue lvalue = EmitLValue(e->getLHS());
+ LValue lvalue = EmitLValue(e->getLHS(), NotKnownNonNull, ObjectRequired);
// If the RHS was emitted retained, expand this.
if (hasImmediateRetain) {
@@ -3710,7 +3713,7 @@ CodeGenFunction::EmitARCStoreStrong(const BinaryOperator *e,
std::pair<LValue,llvm::Value*>
CodeGenFunction::EmitARCStoreAutoreleasing(const BinaryOperator *e) {
llvm::Value *value = EmitARCRetainAutoreleaseScalarExpr(e->getRHS());
- LValue lvalue = EmitLValue(e->getLHS());
+ LValue lvalue = EmitLValue(e->getLHS(), NotKnownNonNull, ObjectRequired);
EmitStoreOfScalar(value, lvalue);
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline-arc.m b/clang/test/CodeGen/ubsan-array-bounds-baseline-arc.m
index aeea218214688..879305d78c9bc 100644
--- a/clang/test/CodeGen/ubsan-array-bounds-baseline-arc.m
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline-arc.m
@@ -20,26 +20,26 @@
void arc_weak_store(int i, id v) { wa[i] = v; }
// CHECK-LABEL: define {{.*}}@arc_strong_store(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void arc_strong_store(int i, id v) { st[i] = v; }
// CHECK-LABEL: define {{.*}}@arc_weak_load(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void arc_weak_load(int i, id *o) { *o = wa[i]; }
// The remaining lifetimes take their own emitters, so each is covered rather
// than assumed to follow from the two above.
// CHECK-LABEL: define {{.*}}@arc_strong_load(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void arc_strong_load(int i, id *o) { *o = st[i]; }
// CHECK-LABEL: define {{.*}}@arc_unsafe_store(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void arc_unsafe_store(int i, id v) { ua[i] = v; }
// CHECK-LABEL: define {{.*}}@arc_unsafe_load(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void arc_unsafe_load(int i, id *o) { *o = ua[i]; }
//===----------------------------------------------------------------------===//
>From 8d2b91b40d59f80dae466b5d7145b4ae71eb8e51 Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:55:23 -0700
Subject: [PATCH 16/17] [clang][CodeGen] Require the object for an
lvalue-to-rvalue conversion
Reading `*&a[i]` was accepted for an out-of-bounds index while reading `a[i]` was
rejected, though C99 6.3.2.1p2 yields the value stored in the object the lvalue
designates, so that object has to exist however the lvalue is spelled. Require it
where the scalar and aggregate emitters load their operand, and in the __ptrauth
load path that does the same; the complex emitter already required it.
---
clang/lib/CodeGen/CGExprAgg.cpp | 9 ++++++---
clang/lib/CodeGen/CGExprScalar.cpp | 9 ++++-----
clang/lib/CodeGen/CGPointerAuth.cpp | 3 ++-
clang/test/CodeGen/ubsan-array-bounds-baseline-ptrauth.c | 2 +-
clang/test/CodeGen/ubsan-array-bounds-baseline.c | 6 +++---
5 files changed, 16 insertions(+), 13 deletions(-)
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 4b26a73e11eb5..efe2118a7fddd 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -246,7 +246,9 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
/// represents a value lvalue, this method emits the address of the lvalue,
/// then loads the result into DestPtr.
void AggExprEmitter::EmitAggLoadOfLValue(const Expr *E) {
- LValue LV = CGF.EmitCheckedLValue(E, CodeGenFunction::TCK_Load);
+ LValue LV =
+ CGF.EmitLValue(E, NotKnownNonNull, CodeGenFunction::ObjectRequired);
+ CGF.EmitTypeCheck(CodeGenFunction::TCK_Load, E, LV);
// If the type of the l-value is atomic, then do an atomic load.
if (LV.getType()->isAtomicType() || CGF.LValueIsSuitableForInlineAtomic(LV)) {
@@ -832,8 +834,9 @@ void AggExprEmitter::VisitCastExpr(CastExpr *E) {
case CK_Dynamic: {
// FIXME: Can this actually happen? We have no test coverage for it.
assert(isa<CXXDynamicCastExpr>(E) && "CK_Dynamic without a dynamic_cast?");
- LValue LV =
- CGF.EmitCheckedLValue(E->getSubExpr(), CodeGenFunction::TCK_Load);
+ LValue LV = CGF.EmitLValue(E->getSubExpr(), NotKnownNonNull,
+ CodeGenFunction::ObjectRequired);
+ CGF.EmitTypeCheck(CodeGenFunction::TCK_Load, E->getSubExpr(), LV);
// FIXME: Do we also need to handle property references here?
if (LV.isSimple())
CGF.EmitDynamicCast(LV.getAddress(), cast<CXXDynamicCastExpr>(E));
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 628d9655421ec..c867ac20d75a3 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -319,9 +319,6 @@ class ScalarExprEmitter
llvm::Type *ConvertType(QualType T) { return CGF.ConvertType(T); }
LValue EmitLValue(const Expr *E) { return CGF.EmitLValue(E); }
- LValue EmitCheckedLValue(const Expr *E, CodeGenFunction::TypeCheckKind TCK) {
- return CGF.EmitCheckedLValue(E, TCK);
- }
void EmitBinOpCheck(
ArrayRef<std::pair<Value *, SanitizerKind::SanitizerOrdinal>> Checks,
@@ -369,8 +366,10 @@ class ScalarExprEmitter
/// value l-value, this method emits the address of the l-value, then loads
/// and returns the result.
Value *EmitLoadOfLValue(const Expr *E) {
- Value *V = EmitLoadOfLValue(EmitCheckedLValue(E, CodeGenFunction::TCK_Load),
- E->getExprLoc());
+ LValue LV =
+ CGF.EmitLValue(E, NotKnownNonNull, CodeGenFunction::ObjectRequired);
+ CGF.EmitTypeCheck(CodeGenFunction::TCK_Load, E, LV);
+ Value *V = EmitLoadOfLValue(LV, E->getExprLoc());
EmitLValueAlignmentAssumption(E, V);
return V;
diff --git a/clang/lib/CodeGen/CGPointerAuth.cpp b/clang/lib/CodeGen/CGPointerAuth.cpp
index 6889899107266..60addd32f7280 100644
--- a/clang/lib/CodeGen/CGPointerAuth.cpp
+++ b/clang/lib/CodeGen/CGPointerAuth.cpp
@@ -246,7 +246,8 @@ CodeGenFunction::EmitOrigPointerRValue(const Expr *E) {
}
// Otherwise, load and use the pointer
- LValue LV = EmitCheckedLValue(E, CodeGenFunction::TCK_Load);
+ LValue LV = EmitLValue(E, NotKnownNonNull, ObjectRequired);
+ EmitTypeCheck(CodeGenFunction::TCK_Load, E, LV);
return emitLoadOfOrigPointerRValue(*this, LV, E->getExprLoc());
}
}
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline-ptrauth.c b/clang/test/CodeGen/ubsan-array-bounds-baseline-ptrauth.c
index c5814a6e25973..943605a63d2c8 100644
--- a/clang/test/CodeGen/ubsan-array-bounds-baseline-ptrauth.c
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline-ptrauth.c
@@ -20,5 +20,5 @@ int *p_load(int i) { return pa[i]; }
int *p_load_paren(int i) { return (pa[i]); }
// CHECK-LABEL: define {{.*}}@p_load_deref_addr(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
int *p_load_deref_addr(int i) { return *&pa[i]; }
diff --git a/clang/test/CodeGen/ubsan-array-bounds-baseline.c b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
index 415fc3027ae24..19c255683eb28 100644
--- a/clang/test/CodeGen/ubsan-array-bounds-baseline.c
+++ b/clang/test/CodeGen/ubsan-array-bounds-baseline.c
@@ -53,11 +53,11 @@ void c_store(int i) { a[i] = 1; }
void c_load(int i) { v = a[i]; }
// CHECK-LABEL: define {{.*}}@c_load_deref_addr(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_load_deref_addr(int i) { v = *&a[i]; }
// CHECK-LABEL: define {{.*}}@c_load_cast(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_load_cast(int i) { v = *(int *)&a[i]; }
// CHECK-LABEL: define {{.*}}@c_compound(
@@ -105,7 +105,7 @@ void c_agg_store_deref_addr(int i) { *&sa[i] = aggl; }
void c_agg_load(int i) { aggl = sa[i]; }
// CHECK-LABEL: define {{.*}}@c_agg_load_deref_addr(
-// CHECK: icmp ule i64 {{.*}}, 4
+// CHECK: icmp ult i64 {{.*}}, 4
void c_agg_load_deref_addr(int i) { aggl = *&sa[i]; }
// CHECK-LABEL: define {{.*}}@c_member_dot(
>From d964973b3b3e795b646dad259056e77cf853314e Mon Sep 17 00:00:00 2001
From: Usama Hameed <u_hameed at apple.com>
Date: Fri, 21 Aug 2026 02:55:27 -0700
Subject: [PATCH 17/17] [clang][CodeGen][NFC] Remove EmitCheckedLValue
Every context that needs the strict comparison now states so itself, so the
helper's guess -- strict when its operand happened to be a subscript -- has no
remaining users that rely on it. Convert the three callers left, none of which
changes what is emitted: the ObjC store path, the counted_by pointer path and the
HLSL buffer member access. Delete the helper.
---
clang/lib/CodeGen/CGExpr.cpp | 13 ++-----------
clang/lib/CodeGen/CGHLSLRuntime.cpp | 5 +++--
clang/lib/CodeGen/CGObjC.cpp | 5 +++--
clang/lib/CodeGen/CodeGenFunction.h | 5 -----
4 files changed, 8 insertions(+), 20 deletions(-)
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 198f6ab812fc4..5548eab84bacd 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1710,16 +1710,6 @@ bool CodeGenFunction::IsWrappedCXXThis(const Expr *Obj) {
return true;
}
-LValue CodeGenFunction::EmitCheckedLValue(const Expr *E, TypeCheckKind TCK) {
- LValue LV;
- if (SanOpts.has(SanitizerKind::ArrayBounds) && isa<ArraySubscriptExpr>(E))
- LV = EmitArraySubscriptExpr(cast<ArraySubscriptExpr>(E), ObjectRequired);
- else
- LV = EmitLValue(E);
- EmitTypeCheck(TCK, E, LV);
- return LV;
-}
-
void CodeGenFunction::EmitTypeCheck(TypeCheckKind TCK, const Expr *E,
LValue LV) {
if (isa<DeclRefExpr>(E) || LV.isBitField() || !LV.isSimple())
@@ -5001,7 +4991,8 @@ void CodeGenFunction::EmitCountedByBoundsChecking(
if (!ArrayInst.isValid()) {
// An invalid Address indicates we're checking a pointer array access.
// Emit the checked L-Value here.
- LValue LV = EmitCheckedLValue(ArrayExpr, TCK_MemberAccess);
+ LValue LV = EmitLValue(ArrayExpr, NotKnownNonNull, ObjectRequired);
+ EmitTypeCheck(TCK_MemberAccess, ArrayExpr, LV);
ArrayInst = LV.getAddress();
}
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 814894ea14da7..7e45a9912f9d7 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -2242,8 +2242,9 @@ bool CGHLSLRuntime::emitBufferCopy(CodeGenFunction &CGF, const Expr *E,
LValue CGHLSLRuntime::emitBufferMemberExpr(CodeGenFunction &CGF,
const MemberExpr *E) {
- LValue Base =
- CGF.EmitCheckedLValue(E->getBase(), CodeGenFunction::TCK_MemberAccess);
+ LValue Base = CGF.EmitLValue(E->getBase(), NotKnownNonNull,
+ CodeGenFunction::ObjectRequired);
+ CGF.EmitTypeCheck(CodeGenFunction::TCK_MemberAccess, E->getBase(), Base);
auto *Field = dyn_cast<FieldDecl>(E->getMemberDecl());
assert(Field && "Unexpected access into HLSL buffer");
diff --git a/clang/lib/CodeGen/CGObjC.cpp b/clang/lib/CodeGen/CGObjC.cpp
index 558bed93f7a0c..f3abf6c5507ed 100644
--- a/clang/lib/CodeGen/CGObjC.cpp
+++ b/clang/lib/CodeGen/CGObjC.cpp
@@ -3347,8 +3347,9 @@ Result ARCExprEmitter<Impl,Result>::
Result result = asImpl().visit(e->getRHS());
// Perform the store.
- LValue lvalue =
- CGF.EmitCheckedLValue(e->getLHS(), CodeGenFunction::TCK_Store);
+ LValue lvalue = CGF.EmitLValue(e->getLHS(), NotKnownNonNull,
+ CodeGenFunction::ObjectRequired);
+ CGF.EmitTypeCheck(CodeGenFunction::TCK_Store, e->getLHS(), lvalue);
CGF.EmitStoreThroughLValue(RValue::get(asImpl().getValueOfResult(result)),
lvalue);
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 1a641ed9c2f0f..821ac899c7499 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4352,11 +4352,6 @@ class CodeGenFunction : public CodeGenTypeCache {
ObjectRequirement_t Req);
public:
- /// Same as EmitLValue but additionally we generate checking code to
- /// guard against undefined behavior. This is only suitable when we know
- /// that the address will be used to access the object.
- LValue EmitCheckedLValue(const Expr *E, TypeCheckKind TCK);
-
RValue convertTempToRValue(Address addr, QualType type, SourceLocation Loc);
void EmitAtomicInit(Expr *E, LValue lvalue);
More information about the cfe-commits
mailing list