Repository navigation
Description
Hi,
General context of this work is in #188, including the AI-disclaimers.
No in-tree codec is affected by this issue; std compiles, so no std/ package has a public non-coroutine method with a run-time-checked argument and a non-empty result. Claude judges the prospective risk of it as "low": only ever a compile error (an empty struct returned where a value is required), with no unsafe binary.
The following is Sol 6.1's writeup of the issue. Includes a repro, the generated C, and the MSVC C2440 return-type error, with a passing control.
For a public non-coroutine method with a run-time-checked argument (here a refined integer) and a non-empty result type, the generated argument check returns wuffs_base__make_empty_struct() whatever the declared result type. wuffs-c gen accepts the method, but the generated C does not compile.
Minimal repro:
pub struct thing?()
pub func thing.f!(x: base.u32[..= 1]) base.u32 {
return 0
}
Actual: at 4be865da, wuffs-c gen -package_name repro repro.wuffs exits 0 with nothing on stderr. The generated function includes:
WUFFS_BASE__MAYBE_STATIC uint32_t
wuffs_repro__thing__f(
wuffs_repro__thing* self,
uint32_t a_x) {
// Receiver checks omitted here.
if (a_x > 1) {
self->private_impl.magic = WUFFS_BASE__DISABLED;
return wuffs_base__make_empty_struct();
}
return 0u;
}Compiling it as C with MSVC (cl /nologo /c on a file containing #define WUFFS_IMPLEMENTATION and #include "repro.c", with the output of wuffs-c gen -package_name base saved as wuffs-base.c next to repro.c) exits 2:
repro.c(256): error C2440: 'return': cannot convert from 'wuffs_base__empty_struct' to 'uint32_t'
Control: replacing only base.u32[..= 1] with base.u32 removes the run-time check; generation and compilation both exit 0. The ? receiver has the magic field, so this repro does not involve the related report on public methods of non-? structs (#202).
Expected: accepted code generates compilable C, returning a value of the declared result type when an argument is invalid.
Cause: writeFuncImplPrologue calls the public argument validator:
Lines 422 to 427 in 4be865d
| // For public functions, check (at runtime) the other args for bounds and | |
| // null-ness. For private functions, those checks are done at compile time. | |
| if g.currFunk.astFunc.Public() { | |
| if err := g.writeFuncImplArgChecks(b, g.currFunk.astFunc); err != nil { | |
| return err | |
| } |
writeFuncImplArgChecks collects the bounds checks:
Lines 576 to 622 in 4be865d
| func (g *gen) writeFuncImplArgChecks(b *buffer, n *a.Func) error { | |
| checks := []string(nil) | |
| for _, o := range n.In().Fields() { | |
| o := o.AsField() | |
| oTyp := o.XType() | |
| // TODO: Also check elements, for array-typed arguments. | |
| switch { | |
| case oTyp.IsIOTokenType() || (oTyp.Decorator() == t.IDPtr): | |
| checks = append(checks, fmt.Sprintf("!%s%s", aPrefix, o.Name().Str(g.tm))) | |
| case oTyp.IsRefined(): | |
| bounds := [2]*big.Int{} | |
| for i, bound := range oTyp.Bounds() { | |
| if bound != nil { | |
| if cv := bound.ConstValue(); cv != nil { | |
| bounds[i] = cv | |
| } | |
| } | |
| } | |
| if qid := oTyp.QID(); qid[0] == t.IDBase { | |
| if key := qid[1]; key < t.ID(len(numTypeBounds)) { | |
| ntb := numTypeBounds[key] | |
| for i := 0; i < 2; i++ { | |
| if bounds[i] != nil && ntb[i] != nil && bounds[i].Cmp(ntb[i]) == 0 { | |
| bounds[i] = nil | |
| continue | |
| } | |
| } | |
| } | |
| } | |
| for i, bound := range bounds { | |
| if bound != nil { | |
| op := '<' | |
| if i != 0 { | |
| op = '>' | |
| } | |
| checks = append(checks, fmt.Sprintf("%s%s %c %s", aPrefix, o.Name().Str(g.tm), op, bound)) | |
| } | |
| } | |
| } | |
| } | |
| if len(checks) == 0 { | |
| return nil | |
| } |
Lines AAD8 635-637 unconditionally return an empty struct for non-coroutines, whatever the result type; the TODO acknowledges this:
Lines 624 to 639 in 4be865d
| b.writes("if (") | |
| for i, c := range checks { | |
| if i != 0 { | |
| b.writes(" || ") | |
| } | |
| b.writes(c) | |
| } | |
| b.writes(") {\n") | |
| b.writes("self->private_impl.magic = WUFFS_BASE__DISABLED;\n") | |
| if g.currFunk.astFunc.Effect().Coroutine() { | |
| b.writes("return wuffs_base__make_status(wuffs_base__error__bad_argument);\n") | |
| } else { | |
| // TODO: don't assume that the return type is empty. | |
| b.printf("return wuffs_base__make_empty_struct();\n") | |
| } | |
| b.writes("}\n") |
Both defects at once: with a plain receiver, pub struct thing(value: base.u32), and the same method, wuffs-c gen -package_name both both.wuffs exits 0 and MSVC reports both (exit 2):
both.c(175): error C2039: 'magic': is not a member of '<unnamed-tag>'
both.c(179): error C2039: 'magic': is not a member of '<unnamed-tag>'
both.c(180): error C2440: 'return': cannot convert from 'wuffs_base__empty_struct' to 'uint32_t'
Correcting either assumption alone leaves the other error.
Suggested fix: return a value of the declared result type in that branch, as the receiver check in the same prologue already does through writeOutParamZeroValue (internal/cgen/func.go:380-410).
Activity
dio-gh commentedon Nov 3, 2026
Hi,
This is a related case of this issue's argument check; the general context of this work, including the AI-disclaimers, is in #188.
No in-tree codec is affected: std has no receiver-less public func, and none of its 125 pure public methods takes an argument that needs a run-time check. Claude judges the prospective risk of it as "low": the generated C fails to compile.
The following is Sol 6.1's writeup of the issue. Includes a receiver-less repro and its generated C, two pure-method variants, MSVC C2065, C2223, C2166 and C2440 diagnostics, and two compiling controls. The cause is reasoned from source inspection; the repros and controls were compiled, not run.
A public func's run-time argument check writes
self->private_impl.magic = WUFFS_BASE__DISABLED;on failure. A receiver-less func has noself, and a pure method'sselfpoints to a const receiver. Both cases generate C that fails to compile.Minimal repro:
Actual: at
b36a81d3,wuffs-c gen -package_name repro repro.wuffsexits 0 and emits:MSVC rejects the amalgamation:
As a pure method,
pub func repro.check(x: base.u32[..= 1]), the same body getsconst wuffs_repro__repro* selfand fails on the store:With a non-empty result,
pub func repro.ident(x: base.u32[..= 1]) base.u32 { return args.x }, the pure method also produces this issue's C2440:Controls: the same body as an impure method,
pub func repro.poke!(x: base.u32[..= 1]), compiles. A receiver-less func whose argument needs no run-time check also compiles.Expected: an argument check should not write to a missing or const receiver, and should return the declared result type. Alternatively, the checker could reject run-time-checked arguments on such funcs.
Cause:
writeFuncImplArgChecksemits the disabling store for every public func with an emitted argument check:wuffs/internal/cgen/func.go
Lines 624 to 639 in b36a81d
These receiver errors come from the same failure branch as this issue's incompatible empty-struct return.