bug: incorrect C array and flexible-array member generation
Maintainer thường phản hồi trong vòng 1 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 5/5
- Thời gian dự kiến
- Hơn một tuần
- Mức phù hợp với người mới
- 28/100
Hướng nghiên cứu
Start by reproducing the reported array generation probes against the llcppg #1031 snapshot and inspect the generator paths for C arrays, typedefs, flexible array members, and callback fields. Compare generated Go storage, offsets, and strides with the C layouts described in the issue, including the strict C11 and GNU11 cases. Done means the confirmed failures are handled without changing working forms or inventing array API semantics; the report identifies no generator files or test paths.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Summary
llcppg mishandles several valid C array declarations: it omits enclosing types, loses parameter adjustment or pointer levels, turns inline flexible tails into stored pointers, or emits a callback-array view with the wrong element stride. A header containing a string-inferred array also triggers a panic.
This bug report covers the confirmed unsupported or incorrectly generated cases. It does not prescribe a new array/view API or count valid, already-working forms as failures.
Environment and evidence
- llcppg
6950e22b8e0c2008384bc06d02575c03e20323a2(#1031 snapshot), Linux/amd64. - Clang/LLVM 22.1.8; LLGo
b86d349178d6;github.com/goplus/lib v0.6.1. - Original ten generation probes, followed by 33 header-generation probes, 12 strict-C syntax/constraint probes, and 45 C/LLGo layout comparisons.
- Generated packages were built serially with LLGo. Selected field addresses, array strides and runtime accesses were also checked.
- Reported outputs below are retained evidence at that snapshot, not a new run on every later main revision. A zero exit code or successful compilation of an empty generated package is not support.
- Each C block is an independent input under the normal C generator configuration. Go blocks are actual generated
in.gofiles, not desired replacements.
Confirmed failures
| C syntax/context | Actual failure | Required behavior |
|---|---|---|
| Arrays of inline anonymous struct/union/enum elements | Enclosing owner omitted | Resolve the element declaration and preserve fixed array storage |
| Array of an enum declared inside its owner | Owner omitted | Retain the enum, constants and array field; related declaration failure in #1032 |
| Fixed-array typedef used as a parameter | Alias passed as an array value | Apply C's outermost array-to-pointer parameter adjustment through the alias |
| Multidimensional parameter with a dynamic inner bound | Generated as **c.Int |
Preserve contiguous array storage and runtime row stride, not pointer-slot semantics |
| Pointer to an unknown-bound array, directly or via typedef | Generated with an extra pointer layer | Preserve a pointer to array storage |
| Scalar, row, pointer-element and nested flexible array members | Inline tail replaced by a stored pointer; size/offsets diverge | Preserve C fixed-prefix size/alignment and tail offset |
| Accepted GNU FAM placement/containment extensions | Pointer-slot accessors or wrongly sized child types | Distinguish extension validity and preserve the selected frontend layout |
counted_by and related pointer bounds attributes |
Unsupported attributed type; entire owner omitted | Retain supported field storage/types without inventing bounds semantics |
| String-inferred array extent | Panic on CXCursor_StringLiteral |
Handle the valid header without panic and preserve completed bounds where used |
| Direct function-pointer-array member of a union | Accessor uses 16-byte function elements instead of 8-byte C pointers | Preserve native callback representation and array stride |
| Zero-length array tails | Generated binding does not have consistent C layout across storage, queries and metadata | Track the compiler dependency separately and preserve the actual C layout |
1. Array element declarations are lost
Each of the following inputs returns an empty owner output rather than a usable binding. The anonymous-struct case also occurs in miniaudio's nativeDataFormats[64].
Expected: emit/reuse the element's real declaration and preserve all fixed dimensions. A nested enum registration failure must not silently erase the enclosing record.
Anonymous struct element
C input:
struct Owner { struct { int x, y; } items[4]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
Anonymous union element
C input:
struct Owner { union { int x; double y; } items[4]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
Anonymous enum element
C input:
struct Owner { enum { Zero, One } items[4]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
Inline named enum element
C input:
struct Owner { enum E { A, B } a[4]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
2. Array parameters lose C adjustment or row-stride semantics
The same probe includes working direct spellings as controls. The incorrect fields are RowAlias and VlaRows; not every callback below is a failure.
Parameter spellings and typedef-hidden arrays
C input:
typedef int Row[4]; struct Owner { void (*fixed)(int a[4]); void (*unknown)(int a[]); void (*minimum)(int a[static 4]); void (*qualified)(int a[const restrict 4]); void (*one_vla)(int n, int a[n]); void (*star)(int a[*]); void (*rows)(int a[3][4]); void (*vla_rows)(int n, int m, int a[n][m]); void (*row_alias)(Row a); };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import "github.com/goplus/lib/c"
type Row = [4]c.Int
// llgo:type C
type Owner struct {
Fixed func(_llcppg_param1 *c.Int)
Unknown func(_llcppg_param1 *c.Int)
Minimum func(_llcppg_param1 *c.Int)
Qualified func(_llcppg_param1 *c.Int)
OneVla func(_llcppg_param1 c.Int, _llcppg_param2 *c.Int)
Star func(_llcppg_param1 *c.Int)
Rows func(_llcppg_param1 *[4]c.Int)
VlaRows func(_llcppg_param1 c.Int, _llcppg_param2 c.Int, _llcppg_param3 **c.Int)
RowAlias func(_llcppg_param1 Row)
}
Expected:
Row a, whereRowisint[4], is a pointer parameter in C; the generated callback must not take a by-value Go array.Rowshould remain an array when it denotes actual object/member storage.int a[n][m]adjusts to a pointer to contiguous rows with runtime stride. It is not anint **pointer table. The binding must not add a pointer indirection or discard the separately declared dimensions.- Preserve the already-correct
a[3][4]shape*[4]c.Intand explicit pointers to fixed arrays.
A broader probe also generated a multidimensional fixed-array alias by value. This belongs to the same adjustment failure; alias definition support alone does not establish correct parameter conversion. The exact Go API for dynamically sized rows is not selected by this report.
3. Unknown-bound arrays are confused with pointer slots
Direct pointer to an unknown-bound array
C input:
struct Owner { int (*pointer)[]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import "github.com/goplus/lib/c"
type Owner struct {
Pointer **c.Int
}
Incomplete-array typedef used through a pointer
C input:
typedef int Open[];
struct Owner { Open *p; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import "github.com/goplus/lib/c"
type Open = *c.Int
type Owner struct {
P *Open
}
Incomplete-array typedef used as a flexible member
C input:
typedef int Open[];
struct Owner { int count; Open a; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import "github.com/goplus/lib/c"
type Open = *c.Int
type Owner struct {
Count c.Int
A Open
}
Expected: distinguish an incomplete array type from a stored pointer. The pointer case addresses contiguous array storage, not a second pointer slot; the member case is an inline tail after count. Matching the outer pointer's byte size does not make **c.Int correct.
The standalone definition probe emits type Open = *c.Int, and an alias of it reuses that pointer alias. An Open parameter happens to have the expected pointer shape, but that does not validate the alias's other uses. No new opaque-array wrapper or public view API is chosen here.
4. Flexible array members change the record layout
Scalar, padded, aligned, row and pointer-element tails
C input:
struct Owner { int count; int a[]; };
struct Padded { double header; char tag; char a[]; };
struct Aligned { char tag; double a[]; };
struct Rows { int count; int a[][4]; };
struct Pointers { int count; int *a[]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import "github.com/goplus/lib/c"
type Owner struct {
Count c.Int
A *c.Int
}
type Padded struct {
Header c.Double
Tag c.Char
A *c.Char
}
type Aligned struct {
Tag c.Char
A *c.Double
}
type Rows struct {
Count c.Int
A *[4]c.Int
}
type Pointers struct {
Count c.Int
A **c.Int
}
Observed C versus generated Go layout on the tested target:
Owner: C size/alignment/tail offset4/4/4; generated Go16/8/8. Actual array stride and field address confirm the discrepancy.Padded: C size 16, alignment 8, tail offset 9; generated Go size 24 and pointer-field offset 16. Tail offset is not necessarilysizeof(owner).Aligned: C size/alignment/tail offset8/8/8; generated Go size 16.Rows: C size 4; generated Go size 16, containing a stored pointer instead of an inline tail of four-int rows.Pointers: C size 8; generated Go size 16. The tail consists of pointer elements; it is not an extra pointer field in the fixed prefix.
Expected: preserve the fixed prefix's target layout and the actual tail-element address/stride. Do not replace inline tail storage with a pointer or slice field. Length, allocation and ownership cannot be inferred from a field named count. This report does not select a new accessor/constructor API.
Named prefix fields promoted from an anonymous struct
C input:
struct Owner { struct { int count; }; int a[]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import "github.com/goplus/lib/c"
type Owner struct {
_llcppg_anon_0
A *c.Int
}
type _llcppg_anon_0 struct {
Count c.Int
}
C accepts this standard FAM placement. Promoting count does not avoid the same stored-pointer/layout failure.
Union containing a FAM-bearing struct
C input:
struct Packet { int count; int a[]; };
union Owner { struct Packet packet; double alignment; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import (
"github.com/goplus/lib/c"
"unsafe"
)
type Packet struct {
Count c.Int
A *c.Int
}
type Owner struct {
_xgo_union [1]uint64
}
func (p *Owner) XGof_ref_packet() *Packet {
return (*Packet)(unsafe.Pointer(p))
}
func (p *Owner) XGof_ref_alignment() *c.Double {
return (*c.Double)(unsafe.Pointer(p))
}
This union containment passes strict C11. C Packet has size 4, while generated Packet has size 16. The outer union's eight-byte storage can be correct while its returned child type is still wrong; checking only the outer size is insufficient.
Extension-only placements and containment
The following cases fail the recorded strict C11 checks and are accepted by GNU11. They are not presented as standard C inputs. Their observed generator output is still incorrect for the accepted extension layout.
FAM directly inside a union
C input:
union Owner { int count; int a[]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import (
"github.com/goplus/lib/c"
"unsafe"
)
type Owner struct {
_xgo_union [1]uint32
}
func (p *Owner) XGof_ref_count() *c.Int {
return (*c.Int)(unsafe.Pointer(p))
}
func (p *Owner) XGof_ref_a() **c.Int {
return (**c.Int)(unsafe.Pointer(p))
}
The generated **c.Int accessor describes a pointer slot, not the inline tail's first element.
FAM as the only field
C input:
struct Owner { int a[]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import "github.com/goplus/lib/c"
type Owner struct {
A *c.Int
}
C size/alignment is 0/4; generated Go size/alignment is 8/8.
FAM-bearing struct used as a struct member
C input:
struct Packet { int count; int a[]; };
struct Owner { int tag; struct Packet packet; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import "github.com/goplus/lib/c"
type Packet struct {
Count c.Int
A *c.Int
}
type Owner struct {
Tag c.Int
Packet Packet
}
C outer size is 8; generated Go outer size is 24.
Array of FAM-bearing records
C input:
struct Packet { int count; int a[]; };
struct Owner { struct Packet packets[2]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import "github.com/goplus/lib/c"
type Packet struct {
Count c.Int
A *c.Int
}
type Owner struct {
Packets [2]Packet
}
C outer size is 8; generated Go outer size is 32.
A union containing a FAM-bearing record embedded in a struct
C input:
struct Packet { int count; int a[]; };
union Choice { struct Packet packet; int tag; };
struct Owner { union Choice choice; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import (
"github.com/goplus/lib/c"
"unsafe"
)
type Packet struct {
Count c.Int
A *c.Int
}
type Choice struct {
_xgo_union [1]uint32
}
type Owner struct {
Choice Choice
}
func (p *Choice) XGof_ref_packet() *Packet {
return (*Packet)(unsafe.Pointer(p))
}
func (p *Choice) XGof_ref_tag() *c.Int {
return (*c.Int)(unsafe.Pointer(p))
}
The four-byte outer/union storage does not make the generated sixteen-byte Packet view valid. Preserve the frontend's actual accepted containment and offsets; do not silently claim that every FAM placement is standard C.
5. Bounds-annotated types silently erase their owners
Installed Clang accepts each attribute below in its tested position. llcppg logs an unsupported unexposed attributed type and emits no Owner, while returning exit 0. The related pointer attributes are not all FAM annotations.
FAM with counted_by
C input:
struct Owner { unsigned count; int items[] __attribute__((counted_by(count))); };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
Pointer with counted_by_or_null
C input:
struct Owner { unsigned count; int *items __attribute__((counted_by_or_null(count))); };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
Pointer with sized_by
C input:
struct Owner { unsigned bytes; void *items __attribute__((sized_by(bytes))); };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
Pointer with sized_by_or_null
C input:
struct Owner { unsigned bytes; void *items __attribute__((sized_by_or_null(bytes))); };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
Expected: the accepted annotation must not cause the entire otherwise-supported record to disappear. Preserve storage/type information and handle available relationship information explicitly; no field-name heuristic or automatic bounds-check/view API is prescribed here.
6. String-inferred array initialization panics
C input:
char text[] = "hi";
struct Owner { char a[sizeof(text)]; };
Actual diagnostic:
varHasInitExpr: unknown kind - 109
The generator panics in cl/var.go; kind 109 is CXCursor_StringLiteral. Clang has already completed text to char[3]. List and designated array initializers in separate control probes complete to int[3]/int[6] and do not trigger this panic.
Expected: processing the valid header must not panic, and the dependent Owner.a bound must retain the completed extent 3. This does not request new global-variable bindings or execution of C initializers; initialized globals are currently intentionally omitted by the generator.
7. Union callback-array accessor has the wrong element representation
Direct versus typedef-named callback arrays
C input:
typedef int (*Callback)(int);
struct Owner { int (*callbacks[4])(int); Callback aliases[4]; Callback (*rows)[4]; };
union Container { int (*callbacks[4])(int); Callback aliases[4]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import (
"github.com/goplus/lib/c"
"unsafe"
)
// llgo:type C
type Callback = func(_llcppg_param1 c.Int) c.Int
// llgo:type C
type Owner struct {
Callbacks [4]func(_llcppg_param1 c.Int) c.Int
Aliases [4]Callback
Rows *[4]Callback
}
type Container struct {
_xgo_union [4]uint64
}
func (p *Container) XGof_ref_callbacks() *[4]func(_llcppg_param1 c.Int) c.Int {
return (*[4]func(_llcppg_param1 c.Int) c.Int)(unsafe.Pointer(p))
}
func (p *Container) XGof_ref_aliases() *[4]Callback {
return (*[4]Callback)(unsafe.Pointer(p))
}
Observed on Linux/amd64:
- C function-pointer element stride: 8 bytes; four-element array size: 32 bytes.
- Direct
XGof_ref_callbacks()view: measured element stride 16 bytes, array size 64 bytes. - Typedef-based
XGof_ref_aliases()view: stride 8 bytes, array size 32 bytes.
Expected: both access paths must describe the same native C callback representation. The direct array view must not become an array of Go closures. The struct-owner forms have the expected physical field addresses/element strides; they are controls, not the union failure.
8. Zero-length tails and separate LLGo defects
Accepted GNU zero-length-array members
C input:
struct Tail { int count; int a[0]; };
struct Aligned { char tag; double a[0]; };
struct Middle { int a[0]; int count; };
struct Owner { int count; int a[0]; };
union ZeroUnion { int count; int a[0]; };
Actual generated in.go:
// Code generated by llcppg; DO NOT EDIT.
package arrayprobe
import (
"github.com/goplus/lib/c"
"unsafe"
)
type Tail struct {
Count c.Int
A [0]c.Int
}
type Aligned struct {
Tag c.Char
A [0]c.Double
}
type Middle struct {
A [0]c.Int
Count c.Int
}
type Owner struct {
Count c.Int
A [0]c.Int
}
type ZeroUnion struct {
_xgo_union [1]uint32
}
func (p *ZeroUnion) XGof_ref_count() *c.Int {
return (*c.Int)(unsafe.Pointer(p))
}
func (p *ZeroUnion) XGof_ref_a() *[0]c.Int {
return (*[0]c.Int)(unsafe.Pointer(p))
}
C Tail/Owner size and alignment are 4/4; C Aligned size and alignment are 8/8. The recorded generated LLGo array strides are 4 and 8, while unsafe.Sizeof reports 8 and 16. This is not proof that actual storage doubled: address/stride measurements and unsafe-query values disagree. Middle-position zero-length fields and the union-storage control match the tested C layout.
The underlying ordinary-Go zero-tail storage/metadata inconsistency is tracked in LLGo #2797. The separate callback-struct unsafe.Offsetof discrepancy is tracked in LLGo #2796. Neither compiler defect is fixed merely by changing an llcppg golden output.
For this issue, the generator requirement is to preserve the C extension's actual size, alignment and member offset in a consistent binding. An accidentally matching physical stride while type metadata disagrees is not support. No blanket equivalence between C [0], a standard FAM [], and a fixed [1] tail is implied.
Regression controls and excluded cases
- Fixed primitive arrays, fixed multidimensional arrays, arrays of pointers, pointers to fixed arrays, tested existing named/typedef element arrays, and fixed
[1]/[4]tails passed their recorded generation/build/layout checks. - Direct one-dimensional array parameters, one-dimensional VLA parameters, prototype
[*], and fixed-inner multidimensional parameters have the expected inspected pointer signatures. A C function returning a pointer to a fixed array also passed a runtime access check. staticminimum-bound andconst/restrict/volatilesemantics remain partial and belong with #1033. This report does not turn the absence of a newly invented runtime-check API into an array-generation bug.- Arrays of functions, void or incomplete record elements; negative bounds; invalid inner unknown dimensions; misplaced parameter-only modifiers; ordinary record VLAs; and array-valued function returns are rejected C inputs, not unsupported valid bindings.
- Local C function-body translation, C++ dependent-sized arrays and HLSL's non-decaying array parameters are outside this issue.
- Nested record/enum declaration identity issues overlap #1032. Union bit-fields remain in #1034.
Evidence notes and acceptance
The local frontend audit used LLVM source fb3c4976da3a34cd1eb981885b7c7b9dcccddcc5, separate from the installed Clang's build identity. It confirmed that FAM is contextual IncompleteArrayType, parameter adjustment applies only to the outer array, and libclang may expose the original form of a decayed parameter type. These facts explain the failure boundaries; they do not themselves prove a generated binding correct.
A fix must be checked against actual owner/member presence, declaration identity, pointer levels, target size/alignment/offsets and relevant access/call behavior—not only process exit status. Preserve the passing control cases. GNU extension validity must remain distinct from strict C validity. General cross-platform/by-value/callback ABI coverage is not claimed by the recorded probes.
Exact public representations for unknown-bound or dynamic-stride views and flexible-tail access still require separate contract approval. This report states the demonstrated faults and required C semantics; it does not approve new exposed functions, wrappers, allocators, configuration flags or compiler APIs.
- Ngôn ngữ chính
- Go
- Star
- 39
- Fork
- 13
- Merge trung bình
- 1 giờ 28 phút
- Pull request đã merge (30 ngày)
- 256
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của goplus/llcppg
-
bug
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 55/100
Maintainer thường phản hồi trong vòng 1 ngày
-
feature proposal
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 18/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Architecture-Design feature to be confirm
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Architecture-Design proposal
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của goplus/llcppg
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
SAP/crossplane-provider-btp#1118 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
benchmark: `make setup` fails because audit-exporter builds with Go 1.25.0 while go.mod requires 1.26.0Có thể đã có người làm @xsvm đã nhận hôm nay. Đang mở
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 84/100
volcano-sh/volcano#6097 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 1 ngày
-
os: RawConn methods of a closed File return internal/poll.ErrFileClosing instead of ErrClosedĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
[Bug]: Bedrock invoke-with-response-stream reports `stop_reason: "end_turn"` for tool-use turnsĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
Maintainer thường phản hồi trong vòng 1 ngày