Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

bug: incorrect C array and flexible-array member generation

Đang mở
#1,035 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

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
Loại issue
Lỗi
Độ rõ ràng
Khá rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
c, go
Lĩnh vực
backend, tooling

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ả

bug

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.go files, 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, where Row is int[4], is a pointer parameter in C; the generated callback must not take a by-value Go array. Row should 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 an int ** 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.Int and 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 offset 4/4/4; generated Go 16/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 necessarily sizeof(owner).
  • Aligned: C size/alignment/tail offset 8/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.
  • static minimum-bound and const/restrict/volatile semantics 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

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. 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.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của goplus/llcppg

Tất cả issue của goplus/llcppg

Issue tương tự

Thêm issue về Go

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.