mem: reject call-conv mismatches at bare-fn → fn-ptr coercion
Passing a default-conv sx function to a `callconv(.c)` fn-pointer slot (e.g. pthread_create's start routine) used to silently mismatch ABIs: the C-side caller didn't supply __sx_ctx, so the sx-side body read its first user param as garbage. The bug surfaced as a SIGSEGV inside ANativeWindow_setBuffersGeometry on Android during chess bringup. Now the compiler rejects the coercion outright at the bare-fn name lookup site: error: call-convention mismatch: 'sx_handler' is declared with default sx convention but the target type expects callconv(.c) Also: `#foreign` declarations without an explicit `callconv` now default to `.c` instead of `.default`. Every external C symbol is by definition C-conv; the previous default silently typed `objc_msgSend` (et al.) as default-conv, so the check would fire on the consumer side when the user typed a fn-ptr as `callconv(.c)`. With the foreign-default fix, the existing typed-msgSend casts in `std/objc.sx` and `gpu/metal.sx` keep type-checking and the rule is "C-conv on both sides or neither." Caught by the new check (fixed in the same commit): - `ios_gl_proc` in `platform/uikit.sx` lacked callconv(.c) but was passed to `load_gl` whose `get_proc` slot expects it. - `ffi_apply_callback` / `ffi_apply_callback2` in `examples/ffi-06-callback.sx` had default-conv fn-ptr params but the C bodies (in the companion .c) are unambiguously C-conv. Regression test: `examples/131-callconv-mismatch-diagnostic.sx` locks in the diagnostic shape (sx-conv fn → callconv(.c) slot). 153/153 example tests pass. Chess green on macOS / iOS sim / Android.
This commit is contained in:
14
examples/131-callconv-mismatch-diagnostic.sx
Normal file
14
examples/131-callconv-mismatch-diagnostic.sx
Normal file
@@ -0,0 +1,14 @@
|
||||
// Passing a default-conv sx function as a callconv(.c) fn-pointer
|
||||
// silently mismatches ABIs — historically that meant the C-side caller
|
||||
// supplied no `__sx_ctx` slot 0 and the sx-side body read garbage.
|
||||
// The compiler now rejects the coercion outright with a "call-convention
|
||||
// mismatch" diagnostic.
|
||||
|
||||
#import "modules/std.sx";
|
||||
|
||||
sx_handler :: (arg: *void) -> *void { return arg; }
|
||||
|
||||
main :: () -> s32 {
|
||||
fp : (*void) -> *void callconv(.c) = sx_handler;
|
||||
return 0;
|
||||
}
|
||||
@@ -16,8 +16,8 @@
|
||||
#source "ffi-06-callback.c";
|
||||
};
|
||||
|
||||
ffi_apply_callback :: (cb: (s32) -> s32, value: s32) -> s32 #foreign;
|
||||
ffi_apply_callback2 :: (cb: (*void, s32) -> s32, ctx: *void, v: s32) -> s32 #foreign;
|
||||
ffi_apply_callback :: (cb: (s32) -> s32 callconv(.c), value: s32) -> s32 #foreign;
|
||||
ffi_apply_callback2 :: (cb: (*void, s32) -> s32 callconv(.c), ctx: *void, v: s32) -> s32 #foreign;
|
||||
|
||||
g_callback_hits : s32 = 0;
|
||||
g_callback_sum : s32 = 0;
|
||||
|
||||
@@ -237,7 +237,8 @@ impl Platform for UIKitPlatform {
|
||||
}
|
||||
|
||||
// dlsym(RTLD_DEFAULT, name) — Apple platforms. RTLD_DEFAULT is (void*)-2.
|
||||
ios_gl_proc :: (name: [*]u8) -> *void {
|
||||
// callconv(.c) so this is callable from `load_gl`'s C-conv proc-loader slot.
|
||||
ios_gl_proc :: (name: [*]u8) -> *void callconv(.c) {
|
||||
rtld_default : *void = xx (0 - 2);
|
||||
dlsym(rtld_default, name);
|
||||
}
|
||||
|
||||
@@ -796,7 +796,13 @@ pub const Lowering = struct {
|
||||
}) catch unreachable;
|
||||
}
|
||||
|
||||
const cc: Function.CallingConvention = if (fd.call_conv == .c) .c else .default;
|
||||
// `#foreign` declarations are external C symbols by definition —
|
||||
// promote them to callconv(.c) when the user didn't write it
|
||||
// explicitly. This keeps fn-ptr coercion type-safe: anything
|
||||
// typed by name as `(args) -> ret` of a `#foreign` decl can be
|
||||
// assigned to / passed as a `callconv(.c)` fn-pointer without a
|
||||
// call-convention mismatch.
|
||||
const cc: Function.CallingConvention = if (fd.call_conv == .c or is_foreign) .c else .default;
|
||||
|
||||
// For #foreign with C name override, declare under C name and map sx name → C name
|
||||
if (is_foreign) {
|
||||
@@ -1968,6 +1974,24 @@ pub const Lowering = struct {
|
||||
const tramp_id = self.createBareFnTrampoline(fid, tt_info.closure);
|
||||
break :blk self.builder.closureCreate(tramp_id, Ref.none, tt);
|
||||
}
|
||||
// Coercing a bare fn name to a fn-pointer
|
||||
// type — the call_conv must match. A
|
||||
// default-conv sx fn assigned to a
|
||||
// callconv(.c) slot (e.g. passed to
|
||||
// pthread_create) would otherwise crash at
|
||||
// runtime when the C caller doesn't supply
|
||||
// the implicit __sx_ctx arg.
|
||||
if (tt_info == .function) {
|
||||
const func_cc = self.module.functions.items[@intFromEnum(fid)].call_conv;
|
||||
if (func_cc != tt_info.function.call_conv) {
|
||||
if (self.diagnostics) |d| {
|
||||
const want_cc = if (tt_info.function.call_conv == .c) "callconv(.c)" else "default sx convention";
|
||||
const have_cc = if (func_cc == .c) "callconv(.c)" else "default sx convention";
|
||||
d.addFmt(.err, node.span, "call-convention mismatch: '{s}' is declared with {s} but the target type expects {s}", .{ eff_fn_name, have_cc, want_cc });
|
||||
}
|
||||
break :blk self.emitPlaceholder(eff_fn_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break :blk self.builder.emit(.{ .func_ref = fid }, .s64);
|
||||
|
||||
1
tests/expected/131-callconv-mismatch-diagnostic.exit
Normal file
1
tests/expected/131-callconv-mismatch-diagnostic.exit
Normal file
@@ -0,0 +1 @@
|
||||
1
|
||||
1
tests/expected/131-callconv-mismatch-diagnostic.txt
Normal file
1
tests/expected/131-callconv-mismatch-diagnostic.txt
Normal file
@@ -0,0 +1 @@
|
||||
/Users/agra/projects/sx/examples/131-callconv-mismatch-diagnostic.sx:12:42: error: call-convention mismatch: 'sx_handler' is declared with default sx convention but the target type expects callconv(.c)
|
||||
@@ -334,5 +334,3 @@ declare ptr @sx_jni_env_tl_get() #0
|
||||
declare void @sx_jni_env_tl_set(ptr) #0
|
||||
|
||||
declare i64 @write(i32, ptr, i64)
|
||||
|
||||
|
||||
|
||||
@@ -309,5 +309,3 @@ declare ptr @sx_jni_env_tl_get() #0
|
||||
declare void @sx_jni_env_tl_set(ptr) #0
|
||||
|
||||
declare i64 @write(i32, ptr, i64)
|
||||
|
||||
|
||||
|
||||
@@ -309,5 +309,3 @@ declare ptr @sx_jni_env_tl_get() #0
|
||||
declare void @sx_jni_env_tl_set(ptr) #0
|
||||
|
||||
declare i64 @write(i32, ptr, i64)
|
||||
|
||||
|
||||
|
||||
@@ -309,5 +309,3 @@ declare ptr @sx_jni_env_tl_get() #0
|
||||
declare void @sx_jni_env_tl_set(ptr) #0
|
||||
|
||||
declare i64 @write(i32, ptr, i64)
|
||||
|
||||
|
||||
|
||||
@@ -309,5 +309,3 @@ declare ptr @sx_jni_env_tl_get() #0
|
||||
declare void @sx_jni_env_tl_set(ptr) #0
|
||||
|
||||
declare i64 @write(i32, ptr, i64)
|
||||
|
||||
|
||||
|
||||
@@ -309,5 +309,3 @@ declare ptr @sx_jni_env_tl_get() #0
|
||||
declare void @sx_jni_env_tl_set(ptr) #0
|
||||
|
||||
declare i64 @write(i32, ptr, i64)
|
||||
|
||||
|
||||
|
||||
@@ -306,5 +306,3 @@ declare ptr @sx_jni_env_tl_get() #0
|
||||
declare void @sx_jni_env_tl_set(ptr) #0
|
||||
|
||||
declare i64 @write(i32, ptr, i64)
|
||||
|
||||
|
||||
|
||||
@@ -309,5 +309,3 @@ declare ptr @sx_jni_env_tl_get() #0
|
||||
declare void @sx_jni_env_tl_set(ptr) #0
|
||||
|
||||
declare i64 @write(i32, ptr, i64)
|
||||
|
||||
|
||||
|
||||
@@ -307,5 +307,3 @@ declare ptr @sx_jni_env_tl_get() #0
|
||||
declare void @sx_jni_env_tl_set(ptr) #0
|
||||
|
||||
declare i64 @write(i32, ptr, i64)
|
||||
|
||||
|
||||
|
||||
@@ -390,5 +390,3 @@ entry:
|
||||
store ptr %selN, ptr @OBJC_SELECTOR_REFERENCES_release, align 8
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -3281,5 +3281,3 @@ entry:
|
||||
store ptr %sel, ptr @OBJC_SELECTOR_REFERENCES_tripleValue, align 8
|
||||
ret void
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user