The name freed up by the rename now means what C means by it: the members overlay one storage, the size is the largest of them, the alignment the strictest, and nothing anywhere records which one was written. It serves two things that wanted it. Binding a C header means holding the union the library holds and reading whichever member the library's own tag says is live -- a tag Flan cannot see, because the rule relating them is prose in a manual. Overlaying an f32 on a u32 to look at its bits is the other, and it is the same read. So that read is defined rather than refused. This is the one place in the checker where bytes win over safety on purpose, and the alternative was not a safer language, it was no feature: type punning *is* reading the member that was not written. The promise is the one C's implementations make and C's standard does not -- the layout is the target's, the bytes are the bytes, a read is a reinterpretation of them -- and what is not promised is anything about bytes nobody wrote, where a member wider than the one last stored reads a tail that is indeterminate exactly as a struct's padding is. ZII narrows that to almost nothing: a union starts all-bytes-zero unless uninit says otherwise. uninit on one is allowed, unlike on a defdata. The refusal there was never about garbage; it is that a tag steers, and a tag no case names falls past every comparison in a match into a block LLVM may treat as unreachable. An untagged union steers nothing. Which is also why three things are refused, each for a reason that does not expire with a milestone. No move-only member: nothing knows which member is live, so nothing can tear one down, and unlike the struct and defdata refusals this is not waiting on recursive teardown -- there is no fact for teardown to read. No bool at any depth: an i1 loaded from a byte that is neither 0 nor 1 is a value the optimiser may assume cannot exist, and a union is the only type that can produce one. No defdata at any depth, for the reason uninit gives, arriving the other way round. An Option member is fine and the walk says why: its match is a tag test and a branch, not a chain with an unreachable tail. Two members in one literal, a match on a union, a union map key and a member written into a global initialiser are each refused by name. A union is a field list whose every offset is zero, so it travels as a Tast.structure and the checker, the emitter and the x86 backend each grow one table rather than one shape. A value is a zeroed temporary and a store -- Set over Pfield, which every backend already has -- so there is no new IR node and no layout rule spelled out a second time per backend. The LLVM type is the blob clang gives a union, the DWARF is DW_TAG_union_type with every member at zero, and the printer names the type and does not walk it: it cannot know which member is live, and one of them may be a pointer. cimport can now check what it could not. A C record holding a union member was not recorded at all, so the defstruct beside it went unchecked rather than checked wrongly; a named union member resolves to a defunion now and the whole record is compared field by field. The defunion itself is compared against the header's union as a set and not in order -- every member is at offset zero, so a permuted one is the same type and reporting it would be a finding that is not one -- while a member the header has and Flan lacks is reported, because that is what changes the size. A defunion against a C struct, or a defstruct against a C union, is reported in both directions. An anonymous union member is still skipped, and the comment now says that the gap is on the Flan side: there is nothing to declare.
101 lines
5.1 KiB
C
101 lines
5.1 KiB
C
/* A small C header, for testing the importer against something that does not
|
|
* move. The raylib case needs raylib installed, needs the right version of it,
|
|
* and needs an environment variable set, so it is the wrong thing to hang the
|
|
* refusal catalogue on: it would skip everywhere and cover nothing. This
|
|
* header has one function per decision Cimport makes, and the test asserts on
|
|
* the reasons rather than on the count.
|
|
*
|
|
* Deliberately includes nothing. A header that pulls in stdio would make the
|
|
* dump thirty times larger and would put libc's declarations in the way of
|
|
* reading the test's. */
|
|
|
|
typedef struct Pair { float x; float y; } Pair;
|
|
typedef struct Shade { unsigned char r, g, b, a; } Shade;
|
|
typedef struct Undescribed { int a; int b; } Undescribed;
|
|
|
|
/* A second typedef name for a record the package already describes under
|
|
* another one. raylib does this: struct Texture is Texture2D and also
|
|
* TextureCubemap. Both have to resolve to the one defstruct. */
|
|
typedef struct Pair Point;
|
|
|
|
typedef enum Mood { MOOD_CALM = 0, MOOD_CROSS = 1 } Mood;
|
|
|
|
/* Constants, for the defconst and defenum check.
|
|
*
|
|
* Anonymous on purpose: this is the shape raylib uses everywhere — the
|
|
* EnumDecl carries no name at all and the typedef beside it is a separate
|
|
* node — so enumerators have to be collected flat rather than keyed on the
|
|
* enum they came from. SHADE_DARK has no initialiser, so its value is counted
|
|
* from the one before rather than read; raylib's TraceLogLevel is written
|
|
* exactly that way and reading only the explicit ones would check one member
|
|
* of eight. SHADE_HALFDARK is the one a prefix rule cannot reach, the way
|
|
* raylib writes GESTURE_DOUBLETAP where every sibling is underscored. */
|
|
typedef enum { SHADE_LIGHT = 4, SHADE_MID = 5, SHADE_DARK, SHADE_HALFDARK = 9 } Shading;
|
|
|
|
/* A bitfield, which a Flan package holds as separate defconsts rather than as
|
|
* one enum because the call takes the OR of several — raylib's ConfigFlags. */
|
|
typedef enum { OPT_LOUD = 1, OPT_FAST = 2, OPT_FANCY_MODE = 4 } Options;
|
|
|
|
/* An int field a package may reasonably describe with an enum: the two are
|
|
* the same four bytes and the enum is the better face. `scale` beside it is
|
|
* the width the check must go on refusing. */
|
|
typedef struct Mode { int kind; float scale; } Mode;
|
|
|
|
/* And the same thing the other way round, so the tolerance is symmetric: the
|
|
* header names the enum and the package may say i32. */
|
|
typedef struct Feel { Mood mood; int n; } Feel;
|
|
|
|
typedef void (*Notify)(void *user, unsigned int n);
|
|
|
|
/* --- accepted --- */
|
|
void set_seed(unsigned int seed);
|
|
int add_ints(int a, int b);
|
|
Pair make_pair(float x, float y); /* aggregate out, by out-pointer */
|
|
float pair_len(Pair p); /* aggregate in, by pointer */
|
|
Shade tint(Shade base, Shade over);
|
|
int name_length(const char *text); /* const char * is a string in */
|
|
int count_at(const int *values, int n); /* T * is (Ptr T) */
|
|
Pair point_of(Point p); /* the second typedef name */
|
|
int mood_value(Mood m); /* a C enum is an int */
|
|
void take_nothing(void);
|
|
|
|
/* The pointer arm of the declare-c check, which compares against the C
|
|
* spelling because the rendering has thrown the answer away. blit's two
|
|
* parameters are void *, which is opaque about what it points at and so
|
|
* agrees with a pointer to anything; scratch is the same question in return
|
|
* position. pair_len_p is a pointer to a *named* type, which is what has to go
|
|
* on being refused when a hand-written line names a different one. */
|
|
void blit(void *dst, const void *src, int n);
|
|
void *scratch(int n);
|
|
float pair_len_p(const Pair *p);
|
|
|
|
/* --- refused, one per reason --- */
|
|
const char *name_of(int which); /* returns char * */
|
|
void fill_buffer(char *out, int cap); /* non-const char *: C writes it */
|
|
int printf_like(const char *fmt, ...); /* variadic */
|
|
void on_event(Notify cb); /* a callback */
|
|
long file_time(const char *path); /* long varies across our targets */
|
|
Undescribed make_undescribed(void); /* no defstruct for it */
|
|
|
|
/* Two names that kebab to one, so the collision is refused by name rather than
|
|
* arriving at the checker as a duplicate declaration nobody wrote. */
|
|
int Spin2D(int n);
|
|
int spin2d(int n);
|
|
|
|
/* A union, and the two records that go with it.
|
|
*
|
|
* `Slot' is the shape the FFI actually meets: a C library keeps the tag
|
|
* beside the union and states the rule relating them in prose, so the Flan
|
|
* side holds both and reads the member the tag names. It is here because the
|
|
* importer used to record *no* record containing a union member at all --
|
|
* which meant the defstruct beside it went unchecked as well -- and now a
|
|
* named one is checked member by member like anything else.
|
|
*
|
|
* `Anon' is the case that is still skipped, and the reason is not a
|
|
* limitation of the check: an anonymous union member has no name for a Flan
|
|
* field to carry and no way to reach its members, so there is nothing on the
|
|
* Flan side to compare against. */
|
|
typedef union Overlay { int i; float f; } Overlay;
|
|
typedef struct Slot { int kind; Overlay v; } Slot;
|
|
typedef struct Anon { int kind; union { int i; float f; }; } Anon;
|