mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 14:33:24 -04:00
xdrgen: Reject specifications that define a name twice
When an RPC specification defines the same type or constant name more than once, currently xdrgen emits every definition without complaint. The duplication surfaces later as a C compiler error about a redefined struct or function that points at generated code instead of the actual offending line in the .x source. RFC 4506 Section 6.4 places constant and type identifiers in a single name space that must be unique within a specification. Add a semantic check that enforces this rule. Link: https://patch.msgid.link/20260712203451.124902-4-cel@kernel.org Signed-off-by: Chuck Lever <cel@kernel.org>
This commit is contained in:
@@ -21,16 +21,15 @@ from generators.union import XdrUnionGenerator
|
||||
|
||||
from xdr_ast import transform_parse_tree, _RpcProgram, Specification
|
||||
from xdr_ast import _XdrEnum, _XdrPointer, _XdrTypedef, _XdrStruct, _XdrUnion
|
||||
from xdr_ast import XdrSemanticError
|
||||
from xdr_parse import xdr_parser, set_xdr_annotate
|
||||
from xdr_parse import make_error_handler, XdrParseError
|
||||
from xdr_parse import handle_transform_error
|
||||
from xdr_parse import handle_transform_error, handle_semantic_error
|
||||
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
|
||||
def emit_header_declarations(
|
||||
root: Specification, language: str, peer: str
|
||||
) -> None:
|
||||
def emit_header_declarations(root: Specification, language: str, peer: str) -> None:
|
||||
"""Emit header declarations"""
|
||||
for definition in root.definitions:
|
||||
if isinstance(definition.value, _XdrEnum):
|
||||
@@ -68,6 +67,9 @@ def subcmd(args: Namespace) -> int:
|
||||
except VisitError as e:
|
||||
handle_transform_error(e, source, args.filename)
|
||||
return 1
|
||||
except XdrSemanticError as e:
|
||||
handle_semantic_error(e, source, args.filename)
|
||||
return 1
|
||||
|
||||
gen = XdrHeaderTopGenerator(args.language, args.peer)
|
||||
gen.emit_declaration(args.filename, ast)
|
||||
|
||||
@@ -21,12 +21,12 @@ from generators.typedef import XdrTypedefGenerator
|
||||
from generators.struct import XdrStructGenerator
|
||||
from generators.union import XdrUnionGenerator
|
||||
|
||||
from xdr_ast import transform_parse_tree, Specification
|
||||
from xdr_ast import transform_parse_tree, Specification, XdrSemanticError
|
||||
from xdr_ast import _RpcProgram, _XdrConstant, _XdrEnum, _XdrPassthru, _XdrPointer
|
||||
from xdr_ast import _XdrTypedef, _XdrStruct, _XdrUnion
|
||||
from xdr_parse import xdr_parser, set_xdr_annotate
|
||||
from xdr_parse import make_error_handler, XdrParseError
|
||||
from xdr_parse import handle_transform_error
|
||||
from xdr_parse import handle_transform_error, handle_semantic_error
|
||||
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
@@ -94,6 +94,9 @@ def subcmd(args: Namespace) -> int:
|
||||
except VisitError as e:
|
||||
handle_transform_error(e, source, args.filename)
|
||||
return 1
|
||||
except XdrSemanticError as e:
|
||||
handle_semantic_error(e, source, args.filename)
|
||||
return 1
|
||||
|
||||
gen = XdrHeaderTopGenerator(args.language, args.peer)
|
||||
gen.emit_definition(args.filename, ast)
|
||||
|
||||
@@ -11,8 +11,8 @@ from lark import logger
|
||||
from lark.exceptions import VisitError
|
||||
|
||||
from xdr_parse import xdr_parser, make_error_handler, XdrParseError
|
||||
from xdr_parse import handle_transform_error
|
||||
from xdr_ast import transform_parse_tree
|
||||
from xdr_parse import handle_transform_error, handle_semantic_error
|
||||
from xdr_ast import transform_parse_tree, XdrSemanticError
|
||||
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
@@ -34,5 +34,8 @@ def subcmd(args: Namespace) -> int:
|
||||
except VisitError as e:
|
||||
handle_transform_error(e, source, args.filename)
|
||||
return 1
|
||||
except XdrSemanticError as e:
|
||||
handle_semantic_error(e, source, args.filename)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
@@ -21,11 +21,11 @@ from generators.union import XdrUnionGenerator
|
||||
|
||||
from xdr_ast import transform_parse_tree, _RpcProgram, Specification
|
||||
from xdr_ast import _XdrAst, _XdrEnum, _XdrPassthru, _XdrPointer
|
||||
from xdr_ast import _XdrStruct, _XdrTypedef, _XdrUnion
|
||||
from xdr_ast import _XdrStruct, _XdrTypedef, _XdrUnion, XdrSemanticError
|
||||
|
||||
from xdr_parse import xdr_parser, set_xdr_annotate, set_xdr_enum_validation
|
||||
from xdr_parse import make_error_handler, XdrParseError
|
||||
from xdr_parse import handle_transform_error
|
||||
from xdr_parse import handle_transform_error, handle_semantic_error
|
||||
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
@@ -123,6 +123,9 @@ def subcmd(args: Namespace) -> int:
|
||||
except VisitError as e:
|
||||
handle_transform_error(e, source, args.filename)
|
||||
return 1
|
||||
except XdrSemanticError as e:
|
||||
handle_semantic_error(e, source, args.filename)
|
||||
return 1
|
||||
match args.peer:
|
||||
case "server":
|
||||
generate_server_source(args.filename, ast, args.language)
|
||||
|
||||
@@ -814,7 +814,9 @@ def _merge_consecutive_passthru(definitions: List[Definition]) -> List[Definitio
|
||||
lines = [definitions[i].value.content]
|
||||
meta = definitions[i].meta
|
||||
j = i + 1
|
||||
while j < len(definitions) and isinstance(definitions[j].value, _XdrPassthru):
|
||||
while j < len(definitions) and isinstance(
|
||||
definitions[j].value, _XdrPassthru
|
||||
):
|
||||
lines.append(definitions[j].value.content)
|
||||
j += 1
|
||||
merged = _XdrPassthru("\n".join(lines))
|
||||
@@ -826,10 +828,67 @@ def _merge_consecutive_passthru(definitions: List[Definition]) -> List[Definitio
|
||||
return result
|
||||
|
||||
|
||||
def _meta_line(meta) -> int:
|
||||
"""Return the 1-based source line for a node's meta, or 0 if unknown"""
|
||||
try:
|
||||
return meta.line
|
||||
except AttributeError:
|
||||
return 0
|
||||
|
||||
|
||||
class XdrSemanticError(Exception):
|
||||
"""A specification that parses but violates an XDR semantic rule.
|
||||
|
||||
Detection lives in the language-independent front end because a
|
||||
duplicate name is malformed XDR regardless of the output language.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, meta):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
self.line = _meta_line(meta)
|
||||
self.column = getattr(meta, "column", 0)
|
||||
|
||||
|
||||
def _introduced_names(value):
|
||||
"""Yield (name, node) for each identifier a definition introduces."""
|
||||
if isinstance(value, (_XdrStruct, _XdrUnion, _XdrPointer)):
|
||||
yield value.name, value
|
||||
elif isinstance(value, _XdrEnum):
|
||||
yield value.name, value
|
||||
for enumerator in value.enumerators:
|
||||
yield enumerator.name, enumerator
|
||||
elif isinstance(value, _XdrTypedef):
|
||||
yield value.declaration.name, value.declaration
|
||||
elif isinstance(value, _XdrConstant):
|
||||
yield value.name, value
|
||||
|
||||
|
||||
def check_duplicate_definitions(root: "Specification") -> None:
|
||||
"""Reject a spec that declares an identifier more than once.
|
||||
|
||||
RFC 4506 Section 6.4 places constant and type identifiers in a
|
||||
single name space that must be unique within a specification.
|
||||
"""
|
||||
seen = {}
|
||||
for definition in root.definitions:
|
||||
for name, node in _introduced_names(definition.value):
|
||||
where = node if node.line else definition.meta
|
||||
first = seen.get(name)
|
||||
if first is not None:
|
||||
raise XdrSemanticError(
|
||||
f"duplicate identifier '{name}'"
|
||||
f" (first declared at line {_meta_line(first)})",
|
||||
where,
|
||||
)
|
||||
seen[name] = where
|
||||
|
||||
|
||||
def transform_parse_tree(parse_tree):
|
||||
"""Transform productions into an abstract syntax tree"""
|
||||
ast = transformer.transform(parse_tree)
|
||||
ast.definitions = _merge_consecutive_passthru(ast.definitions)
|
||||
check_duplicate_definitions(ast)
|
||||
return ast
|
||||
|
||||
|
||||
|
||||
@@ -169,6 +169,26 @@ def handle_transform_error(e: VisitError, source: str, filename: str) -> None:
|
||||
sys.stderr.write("\n".join(msg_parts) + "\n")
|
||||
|
||||
|
||||
def handle_semantic_error(e, source: str, filename: str) -> None:
|
||||
"""Report a semantic error (e.g., a duplicate name) with context.
|
||||
|
||||
Args:
|
||||
e: The XdrSemanticError carrying message and source position
|
||||
source: The XDR source text being parsed
|
||||
filename: The name of the file being parsed
|
||||
"""
|
||||
lines = source.splitlines()
|
||||
line_num = getattr(e, "line", 0)
|
||||
column = getattr(e, "column", 0)
|
||||
line_text = lines[line_num - 1] if 0 < line_num <= len(lines) else ""
|
||||
|
||||
msg_parts = [f"{filename}:{line_num}:{column}: semantic error", e.message]
|
||||
if line_text:
|
||||
msg_parts.extend(format_source_caret(line_text, column))
|
||||
|
||||
sys.stderr.write("\n".join(msg_parts) + "\n")
|
||||
|
||||
|
||||
def xdr_parser() -> Lark:
|
||||
"""Return a Lark parser instance configured with the XDR language grammar"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user