xref: /linux/tools/net/sunrpc/xdrgen/xdr_ast.py (revision a11fb8f725ea927d6e35e9f61c065f871285a901)
1#!/usr/bin/env python3
2# ex: set filetype=python:
3
4"""Define and implement the Abstract Syntax Tree for the XDR language."""
5
6import sys
7from typing import List
8from dataclasses import dataclass, KW_ONLY
9
10from lark import ast_utils, Transformer
11from lark.tree import Meta
12
13this_module = sys.modules[__name__]
14
15big_endian = []
16excluded_apis = []
17header_name = "none"
18public_apis = []
19structs = set()
20pass_by_reference = set()
21
22# (type_name, member_name) pairs whose variable-length array or
23# optional-data list member is marked "pragma aggregate" -- codec
24# emission streams the member through application hooks instead of
25# iterating a materialized C array.
26aggregate_members = set()
27
28# Source position of each "pragma aggregate" marker, so a directive
29# that cannot be honored is reported where it was written.
30aggregate_member_meta = {}
31
32# (type_name, member_name) pairs marked "pragma pages": the member's
33# content resides in the pages of the Receive or Reply buffer, so
34# the emitted codec captures or inserts those pages by reference
35# instead of copying.
36pages_members = set()
37
38# The same pairs, keyed to the directive's identifier node so a
39# diagnostic can point at the directive rather than at the type.
40pages_member_meta = {}
41
42# Names of types reachable from an RPC procedure argument, closed over
43# the types their members reach. A "pragma pages" member of one is
44# decoded in place rather than encoded by reference, and the two need
45# different C representations; see pages_member_is_decoded().
46argument_types = set()
47
48# Names of types reachable from an RPC procedure result, closed the
49# same way. A marked member of a type in both sets would need both
50# representations at once, so the front end rejects it.
51result_types = set()
52
53# Typedefs of a variable-length opaque or string, mapped to the length
54# bound. A "pragma pages" member declared through one ("path data;"
55# with "typedef string path<NFS_MAXPATHLEN>") reaches the AST as a
56# basic type reference, so the bound must be recovered from here.
57varlen_object_typedefs = {}
58
59constants = {}
60
61
62def xdr_quadlen(val: str) -> int:
63    """Return integer XDR width of an XDR type"""
64    if val in constants:
65        octets = constants[val]
66    else:
67        octets = int(val)
68    return int((octets + 3) / 4)
69
70
71symbolic_widths = {
72    "void": ["XDR_void"],
73    "bool": ["XDR_bool"],
74    "short": ["XDR_short"],
75    "unsigned_short": ["XDR_unsigned_short"],
76    "int": ["XDR_int"],
77    "unsigned_int": ["XDR_unsigned_int"],
78    "long": ["XDR_long"],
79    "unsigned_long": ["XDR_unsigned_long"],
80    "hyper": ["XDR_hyper"],
81    "unsigned_hyper": ["XDR_unsigned_hyper"],
82}
83
84# Numeric XDR widths are tracked in a dictionary that is keyed
85# by type_name because sometimes a caller has nothing more than
86# the type_name to use to figure out the numeric width.
87max_widths = {
88    "void": 0,
89    "bool": 1,
90    "short": 1,
91    "unsigned_short": 1,
92    "int": 1,
93    "unsigned_int": 1,
94    "long": 1,
95    "unsigned_long": 1,
96    "hyper": 2,
97    "unsigned_hyper": 2,
98}
99
100
101@dataclass
102class _XdrAst(ast_utils.Ast):
103    """Base class for the XDR abstract syntax tree"""
104
105    # Source position of the construct's declared identifier, when
106    # the transformer records one, so semantic diagnostics can point
107    # at the exact declaration. The KW_ONLY marker makes the fields
108    # keyword-only, so they never disturb the positional child
109    # ordering lark uses to build each node; 0 means the position was
110    # not recorded.
111    _: KW_ONLY
112    line: int = 0
113    column: int = 0
114
115
116@dataclass
117class _XdrIdentifier(_XdrAst):
118    """Corresponds to 'identifier' in the XDR language grammar"""
119
120    symbol: str
121
122
123@dataclass
124class _XdrValue(_XdrAst):
125    """Corresponds to 'value' in the XDR language grammar"""
126
127    value: str
128
129
130@dataclass
131class _XdrConstantValue(_XdrAst):
132    """Corresponds to 'constant' in the XDR language grammar"""
133
134    value: int
135
136
137@dataclass
138class _XdrTypeSpecifier(_XdrAst):
139    """Corresponds to 'type_specifier' in the XDR language grammar"""
140
141    type_name: str
142    c_classifier: str = ""
143
144
145@dataclass
146class _XdrDefinedType(_XdrTypeSpecifier):
147    """Corresponds to a type defined by the input specification"""
148
149    def symbolic_width(self) -> List:
150        """Return list containing XDR width of type's components"""
151        return [get_header_name().upper() + "_" + self.type_name + "_sz"]
152
153    def __post_init__(self):
154        if self.type_name in structs:
155            self.c_classifier = "struct "
156        symbolic_widths[self.type_name] = self.symbolic_width()
157
158
159@dataclass
160class _XdrBuiltInType(_XdrTypeSpecifier):
161    """Corresponds to a built-in XDR type"""
162
163    def symbolic_width(self) -> List:
164        """Return list containing XDR width of type's components"""
165        return symbolic_widths[self.type_name]
166
167
168@dataclass
169class _XdrDeclaration(_XdrAst):
170    """Base class of XDR type declarations"""
171
172
173@dataclass
174class _XdrFixedLengthOpaque(_XdrDeclaration):
175    """A fixed-length opaque declaration"""
176
177    name: str
178    size: str
179    template: str = "fixed_length_opaque"
180
181    def max_width(self) -> int:
182        """Return width of type in XDR_UNITS"""
183        return xdr_quadlen(self.size)
184
185    def symbolic_width(self) -> List:
186        """Return list containing XDR width of type's components"""
187        return ["XDR_QUADLEN(" + self.size + ")"]
188
189    def __post_init__(self):
190        max_widths[self.name] = self.max_width()
191        symbolic_widths[self.name] = self.symbolic_width()
192
193
194@dataclass
195class _XdrVariableLengthOpaque(_XdrDeclaration):
196    """A variable-length opaque declaration"""
197
198    name: str
199    maxsize: str
200    template: str = "variable_length_opaque"
201
202    def max_width(self) -> int:
203        """Return width of type in XDR_UNITS"""
204        return 1 + xdr_quadlen(self.maxsize)
205
206    def symbolic_width(self) -> List:
207        """Return list containing XDR width of type's components"""
208        widths = ["XDR_unsigned_int"]
209        if self.maxsize != "0":
210            widths.append("XDR_QUADLEN(" + self.maxsize + ")")
211        return widths
212
213    def __post_init__(self):
214        max_widths[self.name] = self.max_width()
215        symbolic_widths[self.name] = self.symbolic_width()
216
217
218@dataclass
219class _XdrString(_XdrDeclaration):
220    """A (NUL-terminated) variable-length string declaration"""
221
222    name: str
223    maxsize: str
224    template: str = "string"
225
226    def max_width(self) -> int:
227        """Return width of type in XDR_UNITS"""
228        return 1 + xdr_quadlen(self.maxsize)
229
230    def symbolic_width(self) -> List:
231        """Return list containing XDR width of type's components"""
232        widths = ["XDR_unsigned_int"]
233        if self.maxsize != "0":
234            widths.append("XDR_QUADLEN(" + self.maxsize + ")")
235        return widths
236
237    def __post_init__(self):
238        max_widths[self.name] = self.max_width()
239        symbolic_widths[self.name] = self.symbolic_width()
240
241
242@dataclass
243class _XdrFixedLengthArray(_XdrDeclaration):
244    """A fixed-length array declaration"""
245
246    name: str
247    spec: _XdrTypeSpecifier
248    size: str
249    template: str = "fixed_length_array"
250
251    def max_width(self) -> int:
252        """Return width of type in XDR_UNITS"""
253        return xdr_quadlen(self.size) * max_widths[self.spec.type_name]
254
255    def symbolic_width(self) -> List:
256        """Return list containing XDR width of type's components"""
257        item_width = " + ".join(symbolic_widths[self.spec.type_name])
258        return ["(" + self.size + " * (" + item_width + "))"]
259
260    def __post_init__(self):
261        max_widths[self.name] = self.max_width()
262        symbolic_widths[self.name] = self.symbolic_width()
263
264
265@dataclass
266class _XdrVariableLengthArray(_XdrDeclaration):
267    """A variable-length array declaration"""
268
269    name: str
270    spec: _XdrTypeSpecifier
271    maxsize: str
272    template: str = "variable_length_array"
273
274    def max_width(self) -> int:
275        """Return width of type in XDR_UNITS"""
276        return 1 + (xdr_quadlen(self.maxsize) * max_widths[self.spec.type_name])
277
278    def symbolic_width(self) -> List:
279        """Return list containing XDR width of type's components"""
280        widths = ["XDR_unsigned_int"]
281        if self.maxsize != "0":
282            item_width = " + ".join(symbolic_widths[self.spec.type_name])
283            widths.append("(" + self.maxsize + " * (" + item_width + "))")
284        return widths
285
286    def __post_init__(self):
287        max_widths[self.name] = self.max_width()
288        symbolic_widths[self.name] = self.symbolic_width()
289
290
291@dataclass
292class _XdrOptionalData(_XdrDeclaration):
293    """An 'optional_data' declaration"""
294
295    name: str
296    spec: _XdrTypeSpecifier
297    template: str = "optional_data"
298
299    def max_width(self) -> int:
300        """Return width of type in XDR_UNITS"""
301        return 1
302
303    def symbolic_width(self) -> List:
304        """Return list containing XDR width of type's components"""
305        return ["XDR_bool"]
306
307    def __post_init__(self):
308        structs.add(self.name)
309        pass_by_reference.add(self.name)
310        max_widths[self.name] = self.max_width()
311        symbolic_widths[self.name] = self.symbolic_width()
312
313
314@dataclass
315class _XdrBasic(_XdrDeclaration):
316    """A 'basic' declaration"""
317
318    name: str
319    spec: _XdrTypeSpecifier
320    template: str = "basic"
321
322    def max_width(self) -> int:
323        """Return width of type in XDR_UNITS"""
324        return max_widths[self.spec.type_name]
325
326    def symbolic_width(self) -> List:
327        """Return list containing XDR width of type's components"""
328        return symbolic_widths[self.spec.type_name]
329
330    def __post_init__(self):
331        max_widths[self.name] = self.max_width()
332        symbolic_widths[self.name] = self.symbolic_width()
333
334
335@dataclass
336class _XdrVoid(_XdrDeclaration):
337    """A void declaration"""
338
339    name: str = "void"
340    template: str = "void"
341
342    def max_width(self) -> int:
343        """Return width of type in XDR_UNITS"""
344        return 0
345
346    def symbolic_width(self) -> List:
347        """Return list containing XDR width of type's components"""
348        return []
349
350
351@dataclass
352class _XdrConstant(_XdrAst):
353    """Corresponds to 'constant_def' in the grammar"""
354
355    name: str
356    value: str
357
358    def __post_init__(self):
359        if self.value not in constants:
360            constants[self.name] = int(self.value, 0)
361
362
363@dataclass
364class _XdrEnumerator(_XdrAst):
365    """An 'identifier = value' enumerator"""
366
367    name: str
368    value: str
369
370    def __post_init__(self):
371        if self.value not in constants:
372            constants[self.name] = int(self.value, 0)
373
374
375@dataclass
376class _XdrEnum(_XdrAst):
377    """An XDR enum definition"""
378
379    name: str
380    enumerators: List[_XdrEnumerator]
381
382    def max_width(self) -> int:
383        """Return width of type in XDR_UNITS"""
384        return 1
385
386    def symbolic_width(self) -> List:
387        """Return list containing XDR width of type's components"""
388        return ["XDR_int"]
389
390    def __post_init__(self):
391        max_widths[self.name] = self.max_width()
392        symbolic_widths[self.name] = self.symbolic_width()
393
394
395@dataclass
396class _XdrStruct(_XdrAst):
397    """An XDR struct definition"""
398
399    name: str
400    fields: List[_XdrDeclaration]
401
402    def max_width(self) -> int:
403        """Return width of type in XDR_UNITS"""
404        width = 0
405        for field in self.fields:
406            width += field.max_width()
407        return width
408
409    def symbolic_width(self) -> List:
410        """Return list containing XDR width of type's components"""
411        widths = []
412        for field in self.fields:
413            widths += field.symbolic_width()
414        return widths
415
416    def __post_init__(self):
417        structs.add(self.name)
418        pass_by_reference.add(self.name)
419        max_widths[self.name] = self.max_width()
420        symbolic_widths[self.name] = self.symbolic_width()
421
422
423@dataclass
424class _XdrPointer(_XdrAst):
425    """An XDR pointer definition"""
426
427    name: str
428    fields: List[_XdrDeclaration]
429
430    def max_width(self) -> int:
431        """Return width of type in XDR_UNITS"""
432        width = 1
433        for field in self.fields[0:-1]:
434            width += field.max_width()
435        return width
436
437    def symbolic_width(self) -> List:
438        """Return list containing XDR width of type's components"""
439        widths = []
440        widths += ["XDR_bool"]
441        for field in self.fields[0:-1]:
442            widths += field.symbolic_width()
443        return widths
444
445    def __post_init__(self):
446        structs.add(self.name)
447        pass_by_reference.add(self.name)
448        max_widths[self.name] = self.max_width()
449        symbolic_widths[self.name] = self.symbolic_width()
450
451
452@dataclass
453class _XdrTypedef(_XdrAst):
454    """An XDR typedef"""
455
456    declaration: _XdrDeclaration
457
458    def max_width(self) -> int:
459        """Return width of type in XDR_UNITS"""
460        return self.declaration.max_width()
461
462    def symbolic_width(self) -> List:
463        """Return list containing XDR width of type's components"""
464        return self.declaration.symbolic_width()
465
466    def __post_init__(self):
467        if isinstance(self.declaration, (_XdrVariableLengthOpaque, _XdrString)):
468            varlen_object_typedefs[self.declaration.name] = self.declaration.maxsize
469        if isinstance(self.declaration, _XdrBasic):
470            new_type = self.declaration
471            if isinstance(new_type.spec, _XdrDefinedType):
472                if new_type.spec.type_name in pass_by_reference:
473                    pass_by_reference.add(new_type.name)
474                if new_type.spec.type_name in varlen_object_typedefs:
475                    varlen_object_typedefs[new_type.name] = varlen_object_typedefs[
476                        new_type.spec.type_name
477                    ]
478                max_widths[new_type.name] = self.max_width()
479                symbolic_widths[new_type.name] = self.symbolic_width()
480
481
482def pages_member_maxsize(field: _XdrDeclaration):
483    """Return the length bound of a "pragma pages" member.
484
485    Return None when FIELD is neither a variable-length opaque nor a
486    string, whether declared inline or through a typedef.
487    """
488    if isinstance(field, (_XdrVariableLengthOpaque, _XdrString)):
489        return field.maxsize
490    if isinstance(field, _XdrBasic) and field.spec.type_name in varlen_object_typedefs:
491        return varlen_object_typedefs[field.spec.type_name]
492    return None
493
494
495def pages_member_is_decoded(struct_name: str, member_name: str) -> bool:
496    """Return True when a "pragma pages" member is decoded in place.
497
498    A server decodes arguments and encodes results. A page-resident
499    member of an argument type is decoded from the pages of the
500    Receive buffer into an xdr_buf that captures the content by
501    reference; one of a result type is encoded from the Reply buffer's
502    pages and keeps the ordinary opaque or string representation.
503    """
504    return (struct_name, member_name) in pages_members and (
505        struct_name in argument_types
506    )
507
508
509@dataclass
510class _XdrCaseSpec(_XdrAst):
511    """One case in an XDR union"""
512
513    values: List[str]
514    arm: _XdrDeclaration
515    template: str = "case_spec"
516
517
518@dataclass
519class _XdrDefaultSpec(_XdrAst):
520    """Default case in an XDR union"""
521
522    arm: _XdrDeclaration
523    template: str = "default_spec"
524
525
526@dataclass
527class _XdrUnion(_XdrAst):
528    """An XDR union"""
529
530    name: str
531    discriminant: _XdrDeclaration
532    cases: List[_XdrCaseSpec]
533    default: _XdrDeclaration
534
535    def max_width(self) -> int:
536        """Return width of type in XDR_UNITS"""
537        max_width = 0
538        for case in self.cases:
539            if case.arm.max_width() > max_width:
540                max_width = case.arm.max_width()
541        if self.default:
542            if self.default.arm.max_width() > max_width:
543                max_width = self.default.arm.max_width()
544        return 1 + max_width
545
546    def symbolic_width(self) -> List:
547        """Return list containing XDR width of type's components"""
548        max_width = 0
549        for case in self.cases:
550            if case.arm.max_width() > max_width:
551                max_width = case.arm.max_width()
552                width = case.arm.symbolic_width()
553        if self.default:
554            if self.default.arm.max_width() > max_width:
555                max_width = self.default.arm.max_width()
556                width = self.default.arm.symbolic_width()
557        return symbolic_widths[self.discriminant.name] + width
558
559    def __post_init__(self):
560        structs.add(self.name)
561        pass_by_reference.add(self.name)
562        max_widths[self.name] = self.max_width()
563        symbolic_widths[self.name] = self.symbolic_width()
564
565
566@dataclass
567class _RpcProcedure(_XdrAst):
568    """RPC procedure definition"""
569
570    name: str
571    number: int
572    argument: _XdrTypeSpecifier
573    result: _XdrTypeSpecifier
574
575
576@dataclass
577class _RpcVersion(_XdrAst):
578    """RPC version definition"""
579
580    name: str
581    number: int
582    procedures: List[_RpcProcedure]
583
584
585@dataclass
586class _RpcProgram(_XdrAst):
587    """RPC program definition"""
588
589    name: str
590    number: int
591    versions: List[_RpcVersion]
592
593
594@dataclass
595class _Pragma(_XdrAst):
596    """Empty class for pragma directives"""
597
598
599@dataclass
600class _XdrPassthru(_XdrAst):
601    """Passthrough line to emit verbatim in output"""
602
603    content: str
604
605
606@dataclass
607class Definition(_XdrAst, ast_utils.WithMeta):
608    """Corresponds to 'definition' in the grammar"""
609
610    meta: Meta
611    value: _XdrAst
612
613
614@dataclass
615class Specification(_XdrAst, ast_utils.AsList):
616    """Corresponds to 'specification' in the grammar"""
617
618    definitions: List[Definition]
619
620
621class ParseToAst(Transformer):
622    """Functions that transform productions into AST nodes"""
623
624    def identifier(self, children):
625        """Instantiate one _XdrIdentifier object"""
626        token = children[0]
627        return _XdrIdentifier(token.value, line=token.line, column=token.column)
628
629    def value(self, children):
630        """Instantiate one _XdrValue object"""
631        if isinstance(children[0], _XdrIdentifier):
632            return _XdrValue(children[0].symbol)
633        return _XdrValue(children[0].children[0].value)
634
635    def constant(self, children):
636        """Instantiate one _XdrConstantValue object"""
637        match children[0].data:
638            case "decimal_constant":
639                value = int(children[0].children[0].value, base=10)
640            case "hexadecimal_constant":
641                value = int(children[0].children[0].value, base=16)
642            case "octal_constant":
643                value = int(children[0].children[0].value, base=8)
644        return _XdrConstantValue(value)
645
646    def type_specifier(self, children):
647        """Instantiate one _XdrTypeSpecifier object"""
648        if isinstance(children[0], _XdrIdentifier):
649            name = children[0].symbol
650            return _XdrDefinedType(type_name=name)
651
652        name = children[0].data.value
653        return _XdrBuiltInType(type_name=name)
654
655    def constant_def(self, children):
656        """Instantiate one _XdrConstant object"""
657        ident = children[0]
658        value = children[1].value
659        return _XdrConstant(ident.symbol, value, line=ident.line, column=ident.column)
660
661    def enum(self, children):
662        """Instantiate one _XdrEnum object"""
663        name_ident = children[0]
664
665        i = 0
666        enumerators = []
667        body = children[1]
668        while i < len(body.children):
669            ident = body.children[i]
670            value = body.children[i + 1].value
671            enumerators.append(
672                _XdrEnumerator(
673                    ident.symbol, value, line=ident.line, column=ident.column
674                )
675            )
676            i = i + 2
677
678        return _XdrEnum(
679            name_ident.symbol,
680            enumerators,
681            line=name_ident.line,
682            column=name_ident.column,
683        )
684
685    def fixed_length_opaque(self, children):
686        """Instantiate one _XdrFixedLengthOpaque declaration object"""
687        ident = children[0]
688        size = children[1].value
689
690        return _XdrFixedLengthOpaque(
691            ident.symbol, size, line=ident.line, column=ident.column
692        )
693
694    def variable_length_opaque(self, children):
695        """Instantiate one _XdrVariableLengthOpaque declaration object"""
696        ident = children[0]
697        if children[1] is not None:
698            maxsize = children[1].value
699        else:
700            maxsize = "0"
701
702        return _XdrVariableLengthOpaque(
703            ident.symbol, maxsize, line=ident.line, column=ident.column
704        )
705
706    def string(self, children):
707        """Instantiate one _XdrString declaration object"""
708        ident = children[0]
709        if children[1] is not None:
710            maxsize = children[1].value
711        else:
712            maxsize = "0"
713
714        return _XdrString(ident.symbol, maxsize, line=ident.line, column=ident.column)
715
716    def fixed_length_array(self, children):
717        """Instantiate one _XdrFixedLengthArray declaration object"""
718        spec = children[0]
719        ident = children[1]
720        size = children[2].value
721
722        return _XdrFixedLengthArray(
723            ident.symbol, spec, size, line=ident.line, column=ident.column
724        )
725
726    def variable_length_array(self, children):
727        """Instantiate one _XdrVariableLengthArray declaration object"""
728        spec = children[0]
729        ident = children[1]
730        if children[2] is not None:
731            maxsize = children[2].value
732        else:
733            maxsize = "0"
734
735        return _XdrVariableLengthArray(
736            ident.symbol, spec, maxsize, line=ident.line, column=ident.column
737        )
738
739    def optional_data(self, children):
740        """Instantiate one _XdrOptionalData declaration object"""
741        spec = children[0]
742        ident = children[1]
743
744        return _XdrOptionalData(
745            ident.symbol, spec, line=ident.line, column=ident.column
746        )
747
748    def basic(self, children):
749        """Instantiate one _XdrBasic object"""
750        spec = children[0]
751        ident = children[1]
752
753        return _XdrBasic(ident.symbol, spec, line=ident.line, column=ident.column)
754
755    def void(self, children):
756        """Instantiate one _XdrVoid declaration object"""
757
758        return _XdrVoid()
759
760    def struct(self, children):
761        """Instantiate one _XdrStruct object"""
762        ident = children[0]
763        name = ident.symbol
764        fields = children[1].children
765        pos = {"line": ident.line, "column": ident.column}
766
767        last_field = fields[-1]
768        if (
769            isinstance(last_field, _XdrOptionalData)
770            and name == last_field.spec.type_name
771        ):
772            return _XdrPointer(name, fields, **pos)
773
774        return _XdrStruct(name, fields, **pos)
775
776    def typedef(self, children):
777        """Instantiate one _XdrTypedef object"""
778        new_type = children[0]
779
780        return _XdrTypedef(new_type)
781
782    def case_spec(self, children):
783        """Instantiate one _XdrCaseSpec object"""
784        values = []
785        for item in children[0:-1]:
786            values.append(item.value)
787        arm = children[-1]
788
789        return _XdrCaseSpec(values, arm)
790
791    def default_spec(self, children):
792        """Instantiate one _XdrDefaultSpec object"""
793        arm = children[0]
794
795        return _XdrDefaultSpec(arm)
796
797    def union(self, children):
798        """Instantiate one _XdrUnion object"""
799        ident = children[0]
800
801        body = children[1]
802        discriminant = body.children[0].children[0]
803        cases = body.children[1:-1]
804        default = body.children[-1]
805
806        return _XdrUnion(
807            ident.symbol,
808            discriminant,
809            cases,
810            default,
811            line=ident.line,
812            column=ident.column,
813        )
814
815    def procedure_def(self, children):
816        """Instantiate one _RpcProcedure object"""
817        result = children[0]
818        ident = children[1]
819        argument = children[2]
820        number = children[3].value
821
822        argument_types.add(argument.type_name)
823        result_types.add(result.type_name)
824        return _RpcProcedure(
825            ident.symbol,
826            number,
827            argument,
828            result,
829            line=ident.line,
830            column=ident.column,
831        )
832
833    def version_def(self, children):
834        """Instantiate one _RpcVersion object"""
835        ident = children[0]
836        number = children[-1].value
837        procedures = children[1:-1]
838
839        return _RpcVersion(
840            ident.symbol, number, procedures, line=ident.line, column=ident.column
841        )
842
843    def program_def(self, children):
844        """Instantiate one _RpcProgram object"""
845        ident = children[0]
846        number = children[-1].value
847        versions = children[1:-1]
848
849        return _RpcProgram(
850            ident.symbol, number, versions, line=ident.line, column=ident.column
851        )
852
853    def pragma_def(self, children):
854        """Instantiate one _Pragma object"""
855        directive = children[0].children[0].data
856        match directive:
857            case "big_endian_directive":
858                big_endian.append(children[1].symbol)
859            case "exclude_directive":
860                excluded_apis.append(children[1].symbol)
861            case "header_directive":
862                global header_name
863                header_name = children[1].symbol
864            case "public_directive":
865                public_apis.append(children[1].symbol)
866            case "aggregate_directive":
867                if children[2] is None:
868                    raise XdrSemanticError(
869                        "pragma aggregate requires a type name and a member name",
870                        children[1],
871                    )
872                marked = (children[1].symbol, children[2].symbol)
873                aggregate_members.add(marked)
874                aggregate_member_meta[marked] = children[2]
875            case "pages_directive":
876                if children[2] is None:
877                    raise XdrSemanticError(
878                        "pragma pages requires a type name and a member name",
879                        children[1],
880                    )
881                marked = (children[1].symbol, children[2].symbol)
882                pages_members.add(marked)
883                pages_member_meta[marked] = children[2]
884            case _:
885                raise NotImplementedError("Directive not supported")
886        return _Pragma()
887
888    def passthru_def(self, children):
889        """Instantiate one _XdrPassthru object"""
890        token = children[0]
891        content = token.value[1:]
892        return _XdrPassthru(content)
893
894
895transformer = ast_utils.create_transformer(this_module, ParseToAst())
896
897
898def _merge_consecutive_passthru(definitions: List[Definition]) -> List[Definition]:
899    """Merge consecutive passthru definitions into single nodes"""
900    result = []
901    i = 0
902    while i < len(definitions):
903        if isinstance(definitions[i].value, _XdrPassthru):
904            lines = [definitions[i].value.content]
905            meta = definitions[i].meta
906            j = i + 1
907            while j < len(definitions) and isinstance(
908                definitions[j].value, _XdrPassthru
909            ):
910                lines.append(definitions[j].value.content)
911                j += 1
912            merged = _XdrPassthru("\n".join(lines))
913            result.append(Definition(meta, merged))
914            i = j
915        else:
916            result.append(definitions[i])
917            i += 1
918    return result
919
920
921def _meta_line(meta) -> int:
922    """Return the 1-based source line for a node's meta, or 0 if unknown"""
923    try:
924        return meta.line
925    except AttributeError:
926        return 0
927
928
929class XdrSemanticError(Exception):
930    """A specification that parses but violates an XDR semantic rule.
931
932    Detection lives in the language-independent front end because a
933    duplicate name is malformed XDR regardless of the output language.
934    """
935
936    def __init__(self, message: str, meta):
937        super().__init__(message)
938        self.message = message
939        self.line = _meta_line(meta)
940        self.column = getattr(meta, "column", 0)
941
942
943def _introduced_names(value):
944    """Yield (name, node) for each identifier a definition introduces."""
945    if isinstance(value, (_XdrStruct, _XdrUnion, _XdrPointer)):
946        yield value.name, value
947    elif isinstance(value, _XdrEnum):
948        yield value.name, value
949        for enumerator in value.enumerators:
950            yield enumerator.name, enumerator
951    elif isinstance(value, _XdrTypedef):
952        yield value.declaration.name, value.declaration
953    elif isinstance(value, _XdrConstant):
954        yield value.name, value
955    elif isinstance(value, _RpcProgram):
956        yield value.name, value
957
958
959def _check_rpc_scope_names(program: "_RpcProgram") -> None:
960    """Enforce RFC 5531 Section 12.3 scoping within an RPC program.
961
962    A version name and number are unique within the program and a
963    procedure name and number are unique within its version.
964    """
965    version_names = set()
966    version_numbers = set()
967    for version in program.versions:
968        if version.name in version_names:
969            raise XdrSemanticError(
970                f"duplicate version name '{version.name}'"
971                f" in program '{program.name}'",
972                version,
973            )
974        version_names.add(version.name)
975        if version.number in version_numbers:
976            raise XdrSemanticError(
977                f"duplicate version number {version.number}"
978                f" in program '{program.name}'",
979                version,
980            )
981        version_numbers.add(version.number)
982        procedure_names = set()
983        procedure_numbers = set()
984        for procedure in version.procedures:
985            if procedure.name in procedure_names:
986                raise XdrSemanticError(
987                    f"duplicate procedure name '{procedure.name}'"
988                    f" in version '{version.name}'",
989                    procedure,
990                )
991            procedure_names.add(procedure.name)
992            if procedure.number in procedure_numbers:
993                raise XdrSemanticError(
994                    f"duplicate procedure number {procedure.number}"
995                    f" in version '{version.name}'",
996                    procedure,
997                )
998            procedure_numbers.add(procedure.number)
999
1000
1001def check_duplicate_definitions(root: "Specification") -> None:
1002    """Reject a spec that declares an identifier more than once.
1003
1004    RFC 4506 Section 6.4 places constant and type identifiers in a
1005    single name space that must be unique within a specification.
1006    RFC 5531 Section 12.3 adds RPC program names to that name space
1007    and scopes version names and numbers to their program and
1008    procedure names and numbers to their version.
1009    """
1010    seen = {}
1011    for definition in root.definitions:
1012        for name, node in _introduced_names(definition.value):
1013            where = node if node.line else definition.meta
1014            first = seen.get(name)
1015            if first is not None:
1016                raise XdrSemanticError(
1017                    f"duplicate identifier '{name}'"
1018                    f" (first declared at line {_meta_line(first)})",
1019                    where,
1020                )
1021            seen[name] = where
1022        if isinstance(definition.value, _RpcProgram):
1023            _check_rpc_scope_names(definition.value)
1024
1025
1026# RFC 5531 (Section 9) encodes program, version, and procedure numbers
1027# as unsigned 32-bit integers, so each must fall within [0, 2**32 - 1].
1028_RPC_NUMBER_MAX = 2**32 - 1
1029
1030
1031def _check_rpc_number(kind: str, number: int, scope: str, meta) -> None:
1032    """Reject one RPC number that is negative or wider than 32 bits."""
1033    if number < 0:
1034        raise XdrSemanticError(
1035            f"negative {kind} number {number} {scope}",
1036            meta,
1037        )
1038    if number > _RPC_NUMBER_MAX:
1039        raise XdrSemanticError(
1040            f"{kind} number {number} {scope} exceeds {_RPC_NUMBER_MAX}",
1041            meta,
1042        )
1043
1044
1045def check_rpc_number_range(root: "Specification") -> None:
1046    """Reject an out-of-range program, version, or procedure number.
1047
1048    RFC 5531 assigns only unsigned constants to program, version, and
1049    procedure numbers (Section 12.3) and encodes each as an unsigned
1050    32-bit integer (Section 9). RFC 4506 Section 6.2 permits a signed
1051    decimal constant for XDR constants in general and sets no ceiling on
1052    magnitude, so the grammar accepts an out-of-range value; the range
1053    is enforced here instead. The parser retains no per-version or
1054    per-procedure source location, so a violation is reported against the
1055    program definition.
1056    """
1057    for definition in root.definitions:
1058        program = definition.value
1059        if not isinstance(program, _RpcProgram):
1060            continue
1061        _check_rpc_number(
1062            "program",
1063            program.number,
1064            f"in program '{program.name}'",
1065            definition.meta,
1066        )
1067        for version in program.versions:
1068            _check_rpc_number(
1069                "version",
1070                version.number,
1071                f"in program '{program.name}'",
1072                definition.meta,
1073            )
1074            for procedure in version.procedures:
1075                _check_rpc_number(
1076                    "procedure",
1077                    procedure.number,
1078                    f"in version '{version.name}'",
1079                    definition.meta,
1080                )
1081
1082
1083def _pages_candidate_members(value):
1084    """Yield (member_name, declaration, container) for a type's members.
1085
1086    CONTAINER is the AST node holding the declaration; it decides how
1087    the member is rendered in the target language.
1088    """
1089    if isinstance(value, _XdrStruct):
1090        for field in value.fields:
1091            yield field.name, field, value
1092    elif isinstance(value, _XdrPointer):
1093        # The trailing field is the self-reference that makes this an
1094        # XDR pointer type; the emitter does not encode it.
1095        for field in value.fields[0:-1]:
1096            yield field.name, field, value
1097    elif isinstance(value, _XdrUnion):
1098        cases = list(value.cases)
1099        if value.default is not None:
1100            cases.append(value.default)
1101        for case in cases:
1102            if not isinstance(case.arm, _XdrVoid):
1103                yield case.arm.name, case.arm, value
1104
1105
1106def check_pages_directives(root: "Specification") -> None:
1107    """Reject a "pragma pages" directive that cannot be honored.
1108
1109    Run in the front end so the diagnostic carries the directive's own
1110    source position rather than surfacing as a traceback from whichever
1111    emitter reaches the member. A directive that binds to nothing would
1112    otherwise degrade silently to the copying codec.
1113    """
1114    payloads = {}
1115    resolved = set()
1116    for definition in root.definitions:
1117        value = definition.value
1118        type_name = getattr(value, "name", None)
1119        members = dict(
1120            (name, (field, container))
1121            for name, field, container in _pages_candidate_members(value)
1122        )
1123        for marked in sorted(pages_members):
1124            if marked[0] != type_name:
1125                continue
1126            meta = pages_member_meta.get(marked)
1127            if marked[1] not in members:
1128                raise XdrSemanticError(
1129                    f"type '{type_name}' has no member '{marked[1]}'",
1130                    meta,
1131                )
1132            field, container = members[marked[1]]
1133            # A union arm declared directly as a string is generated as
1134            # a char *, with no length field for the page encoder to
1135            # read, and the union generator has no arm representation
1136            # for a direct opaque at all; inside a struct either
1137            # becomes a { len, data } object.
1138            if isinstance(container, _XdrUnion) and isinstance(
1139                field, (_XdrString, _XdrVariableLengthOpaque)
1140            ):
1141                raise XdrSemanticError(
1142                    f"union arm '{type_name}.{marked[1]}' is declared"
1143                    " directly as a string or opaque; declare it through"
1144                    " a typedef instead",
1145                    meta,
1146                )
1147            # A decoded member and an encoded one take different C
1148            # representations, and a type carries only one.
1149            if type_name in argument_types and type_name in result_types:
1150                raise XdrSemanticError(
1151                    f"'{type_name}' is reachable from both an RPC"
1152                    " argument and an RPC result; a page-resident"
1153                    " member needs distinct argument and result types",
1154                    meta,
1155                )
1156            # The union generator does not consult
1157            # pages_member_is_decoded(), so a marked arm would reach the
1158            # copying decoder and pull the payload through the bounded
1159            # scratch buffer.
1160            if isinstance(container, _XdrUnion) and type_name in argument_types:
1161                raise XdrSemanticError(
1162                    f"union arm '{type_name}.{marked[1]}' cannot be"
1163                    " decoded in place; declare a page-resident member"
1164                    " of an argument type in a struct instead",
1165                    meta,
1166                )
1167            if pages_member_maxsize(field) is None:
1168                raise XdrSemanticError(
1169                    f"'{type_name}.{marked[1]}' is neither a"
1170                    " variable-length opaque nor a string",
1171                    meta,
1172                )
1173            # svcxdr_encode_opaque_payload() consumes the whole page
1174            # vector and moves the stream into the tail; a second
1175            # payload would overwrite the first one's framing.
1176            first = payloads.get(type_name)
1177            if first is not None:
1178                raise XdrSemanticError(
1179                    f"'{type_name}' already marks member '{first}' as"
1180                    " page-resident; an encoder emits at most one"
1181                    " page-resident payload",
1182                    meta,
1183                )
1184            payloads[type_name] = marked[1]
1185            resolved.add(marked)
1186
1187    for marked in sorted(pages_members - resolved):
1188        raise XdrSemanticError(
1189            f"pragma pages names unknown type '{marked[0]}'",
1190            pages_member_meta.get(marked),
1191        )
1192
1193    _check_pages_containment(root, payloads)
1194
1195
1196def _member_type_names(value) -> list:
1197    """Return the type names a definition's members reference, one
1198    entry per member so a type used twice is counted twice. An array
1199    member's encoder emits the element type once per element, so its
1200    element type is counted twice as well."""
1201    if isinstance(value, _XdrStruct):
1202        fields = value.fields
1203    elif isinstance(value, _XdrPointer):
1204        # The trailing self-reference is list framing that the
1205        # caller's loop walks, not a member the pointer's own
1206        # encoder emits.
1207        fields = value.fields[0:-1]
1208    elif isinstance(value, _XdrUnion):
1209        fields = [case.arm for case in value.cases]
1210        if value.default:
1211            fields.append(value.default.arm)
1212    elif isinstance(value, _XdrTypedef):
1213        fields = [value.declaration]
1214    else:
1215        return []
1216    names = []
1217    for field in fields:
1218        spec = getattr(field, "spec", None)
1219        if spec is not None:
1220            names.append(spec.type_name)
1221            if isinstance(field, (_XdrFixedLengthArray, _XdrVariableLengthArray)):
1222                names.append(spec.type_name)
1223    return names
1224
1225
1226def _reachable_payloads(type_name, members, payloads, active) -> list:
1227    """Return the marked members an encoder for TYPE_NAME emits, one
1228    entry per path that reaches one, so a marked type held by two
1229    members appears twice."""
1230    if type_name in active:
1231        return []
1232    active.add(type_name)
1233    found = []
1234    if type_name in payloads:
1235        found.append((type_name, payloads[type_name]))
1236    for member_type in members.get(type_name, ()):
1237        found += _reachable_payloads(member_type, members, payloads, active)
1238    active.discard(type_name)
1239    return found
1240
1241
1242def _check_pages_containment(root: "Specification", payloads: dict) -> None:
1243    """Reject a type whose encoder reaches more than one page-resident
1244    payload through the types its members contain.
1245
1246    The one-payload check in check_pages_directives() sees only the
1247    directly marked type. A type that holds two members of a marked
1248    type, or members of two marked types, would call
1249    svcxdr_encode_opaque_payload() once per member and overwrite the
1250    first payload's framing just as a second marked member would.
1251    """
1252    members = {}
1253    for definition in root.definitions:
1254        value = definition.value
1255        name = getattr(value, "name", None)
1256        if name is None and isinstance(value, _XdrTypedef):
1257            name = value.declaration.name
1258        if name is not None:
1259            members[name] = _member_type_names(value)
1260
1261    for name in members:
1262        reached = _reachable_payloads(name, members, payloads, set())
1263        if len(reached) > 1:
1264            raise XdrSemanticError(
1265                f"'{name}' reaches {len(reached)} page-resident payloads"
1266                " through its members; an encoder emits at most one"
1267                " page-resident payload",
1268                pages_member_meta.get(reached[1]),
1269            )
1270
1271
1272def _named_type_definitions(root: "Specification") -> dict:
1273    """Return the specification's named type definitions, keyed by
1274    the name a member's type specifier refers to."""
1275    named = {}
1276    for definition in root.definitions:
1277        value = definition.value
1278        if isinstance(value, _XdrTypedef):
1279            named[value.declaration.name] = value
1280        elif isinstance(value, (_XdrStruct, _XdrUnion, _XdrPointer)):
1281            named[value.name] = value
1282    return named
1283
1284
1285def _unsized_member(type_name: str, named_types: dict, seen: set):
1286    """Return "<type>.<member>" for the first variable-length array or
1287    optional-data member reachable from TYPE_NAME, or None when the
1288    type's storage is entirely inline."""
1289    if type_name in seen:
1290        return None
1291    seen.add(type_name)
1292    value = named_types.get(type_name)
1293    if isinstance(value, _XdrTypedef):
1294        declaration = value.declaration
1295        if isinstance(declaration, (_XdrVariableLengthArray, _XdrOptionalData)):
1296            return declaration.name
1297        spec = getattr(declaration, "spec", None)
1298        if spec is None:
1299            return None
1300        return _unsized_member(spec.type_name, named_types, seen)
1301    if isinstance(value, _XdrStruct):
1302        fields = value.fields
1303    elif isinstance(value, _XdrPointer):
1304        # The trailing self-reference is list framing the element
1305        # decoder does not decode, so it needs no storage.
1306        fields = value.fields[0:-1]
1307    elif isinstance(value, _XdrUnion):
1308        fields = [case.arm for case in value.cases]
1309        if value.default:
1310            fields.append(value.default.arm)
1311    else:
1312        return None
1313    for field in fields:
1314        if isinstance(field, (_XdrVariableLengthArray, _XdrOptionalData)):
1315            return f"{type_name}.{field.name}"
1316        spec = getattr(field, "spec", None)
1317        if spec is None:
1318            continue
1319        found = _unsized_member(spec.type_name, named_types, seen)
1320        if found is not None:
1321            return found
1322    return None
1323
1324
1325def check_aggregate_directives(root: "Specification") -> None:
1326    """Reject a "pragma aggregate" directive that cannot be honored.
1327
1328    As with the pages checks, this runs in the front end so a directive
1329    naming a missing type or member, or a member with no hook-driven
1330    codec, is reported at its own source position. Left to the
1331    emitters, an unbound directive would degrade silently to the
1332    materializing codec, and a malformed one would surface as a
1333    traceback from whichever emitter reached it.
1334    """
1335    if aggregate_members and header_name == "none":
1336        raise XdrSemanticError(
1337            "pragma aggregate derives its external hook symbols from the"
1338            " pragma header name, which this specification does not set",
1339            aggregate_member_meta.get(min(aggregate_members)),
1340        )
1341
1342    named_types = _named_type_definitions(root)
1343    resolved = set()
1344    for definition in root.definitions:
1345        value = definition.value
1346        if not isinstance(value, _XdrStruct):
1347            continue
1348        fields = dict((field.name, field) for field in value.fields)
1349        element_type = None
1350        for marked in sorted(aggregate_members):
1351            if marked[0] != value.name:
1352                continue
1353            meta = aggregate_member_meta.get(marked)
1354            if marked[1] not in fields:
1355                raise XdrSemanticError(
1356                    f"type '{value.name}' has no member '{marked[1]}'",
1357                    meta,
1358                )
1359            field = fields[marked[1]]
1360            # Any other member form would emit hook prototypes that
1361            # nothing calls.
1362            if not isinstance(
1363                field, (_XdrVariableLengthArray, _XdrOptionalData)
1364            ):
1365                raise XdrSemanticError(
1366                    f"'{value.name}.{marked[1]}' is neither a"
1367                    " variable-length array nor an optional-data list",
1368                    meta,
1369                )
1370            if isinstance(field, _XdrVariableLengthArray):
1371                # The generated decoder hands each element to the
1372                # ordinary element decoder from a zero-filled local, so
1373                # a nested variable-length array or optional-data
1374                # member, which that decoder fills through a pointer
1375                # the caller was to supply, would be written through
1376                # NULL.
1377                nested = _unsized_member(
1378                    field.spec.type_name, named_types, set()
1379                )
1380                if nested is not None:
1381                    raise XdrSemanticError(
1382                        f"'{value.name}.{marked[1]}' has element type"
1383                        f" '{field.spec.type_name}', which reaches the"
1384                        f" variable-length or optional-data member"
1385                        f" '{nested}'; the aggregate decoder has no"
1386                        " storage for it",
1387                        meta,
1388                    )
1389                # The decoder hands the wire count to the begin hook
1390                # before any element is decoded, and a begin hook may
1391                # size storage from it, so the count needs a bound the
1392                # framing enforces.
1393                if field.maxsize == "0":
1394                    raise XdrSemanticError(
1395                        f"'{value.name}.{marked[1]}' declares no maximum"
1396                        " size; the aggregate decoder hands the element"
1397                        " count to the begin hook unbounded",
1398                        meta,
1399                    )
1400            else:
1401                # The optional-data encoder passes each element by
1402                # address and closes the list by encoding NULL, so the
1403                # element codec must be one that takes a pointer and
1404                # writes the value-follows framing itself.
1405                if not isinstance(
1406                    named_types.get(field.spec.type_name), _XdrPointer
1407                ):
1408                    raise XdrSemanticError(
1409                        f"'{value.name}.{marked[1]}' has element type"
1410                        f" '{field.spec.type_name}', which is not a pointer"
1411                        " type; an optional-data aggregate needs an element"
1412                        " codec that writes the value-follows framing",
1413                        meta,
1414                    )
1415                # The emitted optional-data decoder fails outright, so
1416                # an argument type carrying one could never be decoded.
1417                if value.name in argument_types:
1418                    raise XdrSemanticError(
1419                        f"'{value.name}' is reachable from an RPC argument,"
1420                        f" and the optional-data aggregate '{marked[1]}'"
1421                        " cannot be decoded",
1422                        meta,
1423                    )
1424            if element_type is None:
1425                element_type = field.spec.type_name
1426            elif field.spec.type_name != element_type:
1427                raise XdrSemanticError(
1428                    f"'{value.name}.{marked[1]}' has element type"
1429                    f" '{field.spec.type_name}', but '{value.name}' already"
1430                    f" marks a member of element type '{element_type}';"
1431                    " one hook set serves all of a type's marked members",
1432                    meta,
1433                )
1434            resolved.add(marked)
1435
1436    for marked in sorted(aggregate_members - resolved):
1437        raise XdrSemanticError(
1438            f"pragma aggregate names unknown struct '{marked[0]}'",
1439            aggregate_member_meta.get(marked),
1440        )
1441
1442
1443def _referenced_type_names(value) -> set:
1444    """Return the type names an aggregate references through its
1445    members."""
1446    if isinstance(value, (_XdrStruct, _XdrPointer)):
1447        fields = value.fields
1448    elif isinstance(value, _XdrUnion):
1449        fields = [case.arm for case in value.cases]
1450        if value.default:
1451            fields.append(value.default.arm)
1452    else:
1453        return set()
1454    names = set()
1455    for field in fields:
1456        spec = getattr(field, "spec", None)
1457        if spec is not None:
1458            names.add(spec.type_name)
1459    return names
1460
1461
1462def _expand_procedure_types(root: "Specification") -> None:
1463    """Close argument_types and result_types over the types reachable
1464    from an RPC argument or result.
1465
1466    A "pragma pages" member decoded in place can sit in a struct nested
1467    within an argument type (symlinkdata3 within SYMLINK3args), so the
1468    top-level argument types alone do not suffice.
1469    """
1470    references = {}
1471    for definition in root.definitions:
1472        value = definition.value
1473        name = getattr(value, "name", None)
1474        if name is not None:
1475            references[name] = _referenced_type_names(value)
1476        elif isinstance(value, _XdrTypedef):
1477            # An argument named through an alias reaches whatever
1478            # aggregate the alias resolves to.
1479            declaration = value.declaration
1480            spec = getattr(declaration, "spec", None)
1481            if spec is not None:
1482                references[declaration.name] = {spec.type_name}
1483
1484    for types in (argument_types, result_types):
1485        worklist = list(types)
1486        while worklist:
1487            for referenced in references.get(worklist.pop(), ()):
1488                if referenced in references and referenced not in types:
1489                    types.add(referenced)
1490                    worklist.append(referenced)
1491
1492
1493def transform_parse_tree(parse_tree):
1494    """Transform productions into an abstract syntax tree"""
1495    ast = transformer.transform(parse_tree)
1496    ast.definitions = _merge_consecutive_passthru(ast.definitions)
1497    check_duplicate_definitions(ast)
1498    check_rpc_number_range(ast)
1499    # The pages checks consult argument_types and result_types, so
1500    # close those sets over the nested aggregates before the
1501    # directives are validated.
1502    _expand_procedure_types(ast)
1503    check_pages_directives(ast)
1504    check_aggregate_directives(ast)
1505    return ast
1506
1507
1508def get_header_name() -> str:
1509    """Return header name set by pragma header directive"""
1510    return header_name
1511