1
0
Fork 0

Skip caller-allocates out-params instead of miscompiling them

GIR caller-allocates out-params (char *outbuf, gunichar *result) require
the caller to allocate and size the buffer, unlike callee-allocates T**
out-params where the callee fills in the pointee. The out-param
marshalling declared a single Swift scalar and passed its address, which
is correct only for the callee-allocates shape; for caller-allocates, C
wrote buffer payload past the one-element slot (g_unichar_fully_decompose)
or the payload was reinterpreted as a pointer and dereferenced
(g_unichar_to_utf8, segfault). GIR carries no buffer-size field, so these
are now skipped rather than generated. The guard also reclassifies
time_val_from_iso8601/signal_query/type_query from plainRecord to the
accurate caller-allocates reason.
This commit is contained in:
Brendan Szymanski 2026-07-17 21:16:53 -04:00
parent 16e5449cad
commit b61512c4eb
3 changed files with 34 additions and 7 deletions

View file

@ -699,6 +699,21 @@ func planParameters(
// Direction check
if param.direction == .out {
// Caller-allocates out-params (`char *outbuf`, `gunichar *result`)
// hand the C function a pointer to storage the CALLER must size and
// own unlike callee-allocates out-params (`T **out`) where the
// callee fills in the pointee. Our out-param marshalling declares a
// single Swift scalar and passes its address, which is correct only
// for the callee-allocates shape; for caller-allocates it lets C
// write buffer payload past a one-element slot (memory corruption)
// or reinterpret payload as a pointer (segfault, e.g.
// g_unichar_to_utf8). GIR carries no buffer-size field, so there is
// nothing to allocate against skip rather than guess.
if param.callerAllocates {
return .skip(SkipEntry(symbol: "", cIdentifier: nil,
reason: .outParameter,
detail: "caller-allocates out-param '\(param.name)' (no buffer size in GIR)"))
}
// Out-params are collected and returned as Swift values rather than
// passed as arguments. Map the VALUE type (not the pointer-to-pointer).
let mappingResult = Result { try map(param.type, nullable: param.isNullable,

View file

@ -2774,8 +2774,8 @@
},
{
"cIdentifier" : "g_time_val_from_iso8601",
"detail" : "out-param 'time_': 'GLib.TimeVal' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "caller-allocates out-param 'time_' (no buffer size in GIR)",
"reason" : "outParameter",
"symbol" : "GLib.time_val_from_iso8601"
},
{
@ -2850,6 +2850,18 @@
"reason" : "arrayWithoutLength",
"symbol" : "GLib.ucs4_to_utf8"
},
{
"cIdentifier" : "g_unichar_fully_decompose",
"detail" : "caller-allocates out-param 'result' (no buffer size in GIR)",
"reason" : "outParameter",
"symbol" : "GLib.unichar_fully_decompose"
},
{
"cIdentifier" : "g_unichar_to_utf8",
"detail" : "caller-allocates out-param 'outbuf' (no buffer size in GIR)",
"reason" : "outParameter",
"symbol" : "GLib.unichar_to_utf8"
},
{
"cIdentifier" : "g_unicode_canonical_decomposition",
"detail" : "parameter 'result_len' C type 'gsize*' is a pointer",
@ -3063,7 +3075,7 @@
],
"module" : "GLib",
"stats" : {
"boundCallables" : 335,
"boundCallables" : 333,
"boundTypes" : 246,
"totalCallables" : 724,
"totalTypes" : 367

View file

@ -794,8 +794,8 @@
},
{
"cIdentifier" : "g_signal_query",
"detail" : "out-param 'query': 'GObject.SignalQuery' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "caller-allocates out-param 'query' (no buffer size in GIR)",
"reason" : "outParameter",
"symbol" : "GObject.signal_query"
},
{
@ -974,8 +974,8 @@
},
{
"cIdentifier" : "g_type_query",
"detail" : "out-param 'query': 'GObject.TypeQuery' has no GType registration or lifetime functions",
"reason" : "plainRecord",
"detail" : "caller-allocates out-param 'query' (no buffer size in GIR)",
"reason" : "outParameter",
"symbol" : "GObject.type_query"
},
{