Skip to content

Commit c698258

Browse files
Esteban Zimanyiestebanzimanyi
authored andcommitted
Read a structure from its definition, not from a declaration before it
A structure the headers declare before they define it is read from its definition: its record keeps the place where the parse first meets the structure, and its fields, offsets and file are those of the definition. A structure the headers only declare, an opaque one such as NumericData, keeps its declaration and states no field. Why. A binding laying a structure out reads its fields and offsets from the catalog. meos.h declares MeosArray and SkipList, which meos_internal.h defines; read from the first record the parse meets, the declaration, they carry no field, and neither would varlena once pg_basetypes.h declares struct varlena ahead of the splice defining it, which MobilityDB #2893 does. Measured. Over MobilityDB d5e3e9946e, three structures gain their fields and nothing else changes: MeosArray five, read in meos_internal.h for meos.h, SkipList fourteen, read in meos_internal.h for meos.h, and PCSCHEMA eleven, read in pc_api.h for meos_pointcloud.h. Over MobilityDB #2893 (dd0426728b) varlena keeps its two fields, and that catalog differs from master's only in where its structures and typedefs sit: NumericData is found in pg_basetypes.h instead of pg_numeric.h, so it no longer reads as vendored, and the structures come in another order. Witness. tests/test_struct_layout.py reads MeosArray, SkipList and varlena with their fields from the catalog, and parses a header declaring one structure before a second header defines it, beside one only declared: the first carries its fields and the second none. The suite floor goes from 341 to 343.
1 parent 0455a59 commit c698258

3 files changed

Lines changed: 46 additions & 3 deletions

File tree

‎.github/workflows/pytest.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ jobs:
9696
# carries, or a change to them is not exercised until after it merges.
9797
# Consumers use the action; this repository owns the rules.
9898
- name: Refuse a skip, and a suite that shrank
99-
run: tools/check-test-outcome.py "$RUNNER_TEMP/pytest.log" --min-tests 341
99+
run: tools/check-test-outcome.py "$RUNNER_TEMP/pytest.log" --min-tests 343
100100

101101
# The rules earn their place by refusing a log that carries what they
102102
# name. Both fixtures are written here rather than tracked, and the

‎parser/parser.py‎

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ def parse_meos(entry: Path, include_dir: Path,
170170
struct["name"] = typedef_name
171171
else:
172172
continue
173-
structs.append(struct)
173+
structs.append((struct, node.is_definition()))
174174

175175
elif node.kind == clang.cindex.CursorKind.ENUM_DECL and node.spelling:
176176
enums.append(extract_enum(node))
@@ -186,8 +186,24 @@ def _dedup(items: list) -> list:
186186
result.append(item)
187187
return result
188188

189+
def _dedup_structs(items: list) -> list:
190+
"""One record per structure, as #_dedup keeps one per name, read from its
191+
definition when the unit holds one: a forward declaration (`struct varlena;`)
192+
met before the definition has no fields, and a structure only ever declared
193+
(opaque, `struct NumericData;`) keeps its declaration. The structure keeps the
194+
place where the parse first meets it."""
195+
order, chosen = [], {}
196+
for struct, is_def in items:
197+
name = struct["name"]
198+
if name not in chosen:
199+
order.append(name)
200+
chosen[name] = (struct, is_def)
201+
elif is_def and not chosen[name][1]:
202+
chosen[name] = (struct, is_def)
203+
return [chosen[name][0] for name in order]
204+
189205
functions = _dedup(functions)
190-
structs = _dedup(structs)
206+
structs = _dedup_structs(structs)
191207
enums = _dedup(enums)
192208
macros = _dedup(macros)
193209

‎tests/test_struct_layout.py‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,33 @@ def test_core_structs_have_real_offsets(self):
7979
fld["offset_bits"], 0,
8080
f"{s['name']}.{fld['name']} has unresolved offset")
8181

82+
def test_a_structure_declared_first_is_read_from_its_definition(self):
83+
# `meos.h` declares MeosArray and SkipList before `meos_internal.h` defines
84+
# them, so their layout is read where they are defined
85+
for name, field in (("MeosArray", "count"), ("SkipList", "capacity"),
86+
("varlena", "vl_len_")):
87+
self.assertIn(field, self._fields(name), name)
88+
89+
90+
class ForwardDeclarationTests(unittest.TestCase):
91+
"""#_dedup_structs of parser/parser.py over headers that declare a structure before
92+
defining it, and one they only declare, parsed as #StructLayoutTests's catalog is."""
93+
94+
def test_the_definition_wins_and_an_opaque_structure_stays(self):
95+
import os
96+
import tempfile
97+
if not os.environ.get("MDB_SRC_ROOT"):
98+
self.skipTest("MDB_SRC_ROOT not set; the parse reads MobilityDB's families")
99+
from parser.parser import parse_all_headers
100+
with tempfile.TemporaryDirectory() as d:
101+
root = Path(d)
102+
(root / "a.h").write_text("struct S;\nstruct O;\n"
103+
"extern int f(struct S *s, struct O *o);\n")
104+
(root / "b.h").write_text("struct S { int x; double y; };\n")
105+
idl = parse_all_headers(root)
106+
structs = {s["name"]: [f["name"] for f in s["fields"]] for s in idl["structs"]}
107+
self.assertEqual(structs, {"S": ["x", "y"], "O": []})
108+
82109

83110
if __name__ == "__main__":
84111
unittest.main()

0 commit comments

Comments
 (0)