Commit 93cb44c805

mlugg <mlugg@mlugg.co.uk>
2024-08-26 22:50:14
translate-c: support GCC/Clang pointer subtraction extension
Pointer subtraction on `void *` or function pointers is UB by the C spec, but is permitted by GCC and Clang as an extension. So, avoid crashing translate-c in such cases, and follow the extension behavior -- there's nothing else that could really be intended.
1 parent d3c6f71
Changed files (2)
src/translate_c.zig
@@ -1596,6 +1596,11 @@ fn transBinaryOperator(
         // @divExact(@bitCast(<platform-ptrdiff_t>, @intFromPtr(lhs) -% @intFromPtr(rhs)), @sizeOf(<lhs target type>))
         const ptrdiff_type = try transQualTypeIntWidthOf(c, qt, true);
 
+        const bitcast = try Tag.as.create(c.arena, .{
+            .lhs = ptrdiff_type,
+            .rhs = try Tag.bit_cast.create(c.arena, infixOpNode),
+        });
+
         // C standard requires that pointer subtraction operands are of the same type,
         // otherwise it is undefined behavior. So we can assume the left and right
         // sides are the same QualType and arbitrarily choose left.
@@ -1603,18 +1608,19 @@ fn transBinaryOperator(
         const lhs_qt = getExprQualType(c, lhs_expr);
         const lhs_qt_translated = try transQualType(c, scope, lhs_qt, lhs_expr.getBeginLoc());
         const c_pointer = getContainer(c, lhs_qt_translated).?;
-        const elem_type = c_pointer.castTag(.c_pointer).?.data.elem_type;
-        const sizeof = try Tag.sizeof.create(c.arena, elem_type);
-
-        const bitcast = try Tag.as.create(c.arena, .{
-            .lhs = ptrdiff_type,
-            .rhs = try Tag.bit_cast.create(c.arena, infixOpNode),
-        });
 
-        return Tag.div_exact.create(c.arena, .{
-            .lhs = bitcast,
-            .rhs = sizeof,
-        });
+        if (c_pointer.castTag(.c_pointer)) |c_pointer_payload| {
+            const sizeof = try Tag.sizeof.create(c.arena, c_pointer_payload.data.elem_type);
+            return Tag.div_exact.create(c.arena, .{
+                .lhs = bitcast,
+                .rhs = sizeof,
+            });
+        } else {
+            // This is an opaque/incomplete type. This subtraction exhibits Undefined Behavior by the C99 spec.
+            // However, allowing subtraction on `void *` and function pointers is a commonly used extension.
+            // So, just return the value in byte units, mirroring the behavior of this language extension as implemented by GCC and Clang.
+            return bitcast;
+        }
     }
     return infixOpNode;
 }
test/cases/translate_c/void_pointer_subtraction.c
@@ -0,0 +1,16 @@
+#include <stddef.h>
+ptrdiff_t sub_ptr(void *a, void *b) {
+    return a - b;
+}
+
+// translate-c
+// c_frontend=clang
+// target=x86_64-linux
+//
+// pub export fn sub_ptr(arg_a: ?*anyopaque, arg_b: ?*anyopaque) ptrdiff_t {
+//     var a = arg_a;
+//     _ = &a;
+//     var b = arg_b;
+//     _ = &b;
+//     return @as(c_long, @bitCast(@intFromPtr(a) -% @intFromPtr(b)));
+// }