Skip to content

Commit 3cf57e1

Browse files
authored
[mypyc] Fix undefined behavior related to empty vecs (#21094)
We used to use `&vec.buf->items`, which causes undefined behavior for an empty vec that can be triggered on GCC when using -O3, since `buf` is NULL for an empty vec. Use `offsetof` instead to calculate struct field offset, since it doesn't trigger UB if pointer is NULL.
1 parent 0c3ffd4 commit 3cf57e1

File tree

3 files changed

+16
-5
lines changed

3 files changed

+16
-5
lines changed

mypyc/codegen/emitfunc.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -809,9 +809,12 @@ def visit_get_element_ptr(self, op: GetElementPtr) -> None:
809809
# TODO: support tuple type
810810
assert isinstance(op.src_type, RStruct), op.src_type
811811
assert op.field in op.src_type.names, "Invalid field name."
812+
# Use offsetof to avoid undefined behavior when src is NULL
813+
# (e.g., vec buf pointer for empty vecs). The &((T*)p)->field
814+
# pattern is UB when p is NULL, which GCC -O3 can exploit.
812815
self.emit_line(
813-
"{} = ({})&(({} *){})->{};".format(
814-
dest, op.type._ctype, op.src_type.name, src, op.field
816+
"{} = ({})((CPyPtr){} + offsetof({}, {}));".format(
817+
dest, op.type._ctype, src, op.src_type.name, op.field
815818
)
816819
)
817820

mypyc/irbuild/vec.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ def vec_len_native(builder: LowLevelIRBuilder, val: Value) -> Value:
222222

223223

224224
def vec_items(builder: LowLevelIRBuilder, vecobj: Value) -> Value:
225+
"""Return pointer to first item in vec's buf.
226+
227+
Safe to call even when buf is NULL (empty vec), since GetElementPtr
228+
uses offsetof-based arithmetic instead of &((T*)p)->field.
229+
"""
225230
vtype = cast(RVec, vecobj.type)
226231
buf = builder.get_element(vecobj, "buf")
227232
return builder.add(GetElementPtr(buf, vtype.buf_type, "items"))

mypyc/test/test_emitfunc.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -760,13 +760,16 @@ def test_get_element_ptr(self) -> None:
760760
"Foo", ["b", "i32", "i64"], [bool_rprimitive, int32_rprimitive, int64_rprimitive]
761761
)
762762
self.assert_emit(
763-
GetElementPtr(self.o, r, "b"), """cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->b;"""
763+
GetElementPtr(self.o, r, "b"),
764+
"""cpy_r_r0 = (CPyPtr)((CPyPtr)cpy_r_o + offsetof(Foo, b));""",
764765
)
765766
self.assert_emit(
766-
GetElementPtr(self.o, r, "i32"), """cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->i32;"""
767+
GetElementPtr(self.o, r, "i32"),
768+
"""cpy_r_r0 = (CPyPtr)((CPyPtr)cpy_r_o + offsetof(Foo, i32));""",
767769
)
768770
self.assert_emit(
769-
GetElementPtr(self.o, r, "i64"), """cpy_r_r0 = (CPyPtr)&((Foo *)cpy_r_o)->i64;"""
771+
GetElementPtr(self.o, r, "i64"),
772+
"""cpy_r_r0 = (CPyPtr)((CPyPtr)cpy_r_o + offsetof(Foo, i64));""",
770773
)
771774

772775
def test_set_element(self) -> None:

0 commit comments

Comments
 (0)