Skip to content

feat: another batch of AI generator validators#510

Merged
SJrX merged 3 commits into
242.xfrom
issue-509
Jul 26, 2026
Merged

feat: another batch of AI generator validators#510
SJrX merged 3 commits into
242.xfrom
issue-509

Conversation

@SJrX

@SJrX SJrX commented Jul 25, 2026

Copy link
Copy Markdown
Owner

No description provided.

SJrX and others added 3 commits July 25, 2026 18:31
…s and tunnel endpoints

Fifteen more (parser, ltype) pairs that had no value validator, picked by how often
the keys that resolve to them occur in a 6,680-unit corpus of Debian unit files.
Together they cover ~2,600 key occurrences in that corpus. Every grammar was written
against systemd a8e93919c3 — the same commit the plugin's gperf/man data is generated
from — and cites the C function it mirrors.

[Unit] conditions (config_parse_unit_condition_path / _string):
  condition_path (*)      every path-valued Condition…=/Assert…=; one wildcard entry
                          because all twelve ltypes parse identically
  CONDITION_ARCHITECTURE  architecture_table + "native"
  CONDITION_VIRTUALIZATION  booleans, vm/container/private-users, virtualization_table
  CONDITION_SECURITY      the closed list in condition_test_security
  CONDITION_CAPABILITY    one capability name, or a number in 0…CAP_LIMIT

Enumerations and address forms:
  config_parse_netdev_kind             [NetDev] Kind=
  config_parse_set_status              Success/RestartPrevent/RestartForceExitStatus=
  config_parse_name_policy             [Link] NamePolicy= (has kernel/keep, unlike
                                       AlternativeNamesPolicy=)
  config_parse_wireguard_peer_key      base64 32-byte key, or @credential
  config_parse_tunnel_local_address    address, "any", or a local-address type
  config_parse_tunnel_remote_address   address or "any" — no local-address types
  config_parse_address_generation_type prefixstable/eui64/static IPv6 tokens

Also fixes two pre-existing mis-mappings. CONDITION_CPU_FEATURE and
CONDITION_CONTROL_GROUP_CONTROLLER were both pointed at the boolean condition grammar,
which flagged every legitimate value — ConditionCPUFeature= alone produced 110 false
positives in the corpus. CPU features have no closed list (has_cpu_with_flag() reads
/proc/cpuinfo), so that one now only pins the shape: exactly one token, which is real,
since the parameter is never split.

The shared conditionPath()/conditionString() helpers spell the trigger/negate marker
combinations out as alternatives instead of wrapping each marker in a ZeroOrOne.
ZeroOrOne probes its inner combinator a second time to report reachable input, and the
classic engine derives its error TextRange from that number, so a value like
`!!/some/path` produced a range past the end of the value and tripped an assertion.
ConfigParseUnitConditionStringOptionValue now uses the helper too, fixing the same
latent crash there.

Checked for false positives by running every registered grammar over the whole corpus:
of 16,786 grammar-validated key occurrences, these validators reject 129, and all 129
are deliberately-malformed fixtures — KDE's syntax-highlighting test input, systemd's
own negative prefixstable/vti test cases, and one unsubstituted @etc@ autoconf template.
Whole suite green under both the classic and the list-of-successes engine.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…p] and unit paths

Thirty-four more (parser, ltype) pairs, taking the undocumented-validator count from 325 to 291.
As with the previous batch, every grammar is written against systemd a8e93919c3 — the commit the
plugin's gperf/man data is generated from — and names the C function it mirrors.

config_parse_routing_policy_rule, config_parse_route_section, config_parse_address_section and
config_parse_nexthop_section are dispatchers: the ltype indexes a ConfigSectionParser table and each
entry names the parser that actually reads the value. These commits mirror the tables entry by entry
rather than giving a whole section one grammar.

  [RoutingPolicyRule]  From=/To= (address + optional prefix), IncomingInterface=/OutgoingInterface=,
                       Priority=, GoTo=, TypeOfService=, SuppressPrefixLength=,
                       SuppressInterfaceGroup=, SourcePort=/DestinationPort=, Family=, Type=,
                       Invert=, L3MasterDevice=
  [Route]              Destination=/Source=, PreferredSource=, GatewayOnLink=, Scope=
  [Address]            Peer=, Broadcast=, PreferredLifetime=
  [NextHop]            Gateway=
  networkd/udev        [Match] Name= and .link OriginalName=, [BridgeVLAN] VLAN=, [Tunnel] Key=,
                       [IPv6Prefix] *LifetimeSec=, MTUBytes= for all three ltypes
  unit files           [Path] watch settings, [Socket] Symlinks=, [Socket] Service=,
                       [Service] Sockets=

Fixes two more pre-existing mis-mappings of the same shape as the CONDITION_CPU_FEATURE one. Every
[Address] ltype shared the uint32 grammar written for RouteMetric=, and every [NextHop] ltype shared
the boolean grammar written for OnLink=. Between them they flagged `AddPrefixRoute=no`,
`HomeAddress=yes`, `ManageTemporaryAddress=yes`, `DuplicateAddressDetection=ipv4`, `[NextHop] Id=20`,
`Family=ipv4` and `Group=1:3 20:1` — about 100 false positives in the corpus, all on values straight
out of systemd's own test-network fixtures.

Two things learned from checking against that corpus, now folded into shared combinators:

  unsignedNumber() — systemd's safe_atou* helpers pass base 0 to strtoul, so a number may be written
  in hex or octal. IntegerTerminal only reads decimal, so `TypeOfService=0x08` (from systemd's
  25-fibrule-uidrange.network) would have been flagged. Only the decimal branch carries the numeric
  bounds; the other two err towards accepting rather than flagging.

  INTERFACE_NAME / ALTERNATIVE_INTERFACE_NAME — ifname_valid_full(), including the rules that a name
  which is entirely digits is refused (it would look like an ifindex), as are `.`, `..`, `all` and
  `default`, and that the length cap is 15 or 127 depending on the caller's flags.

[Address] Address= keeps its existing NetworkAddressOptionValue, which is deliberately stricter than
in_addr_prefix_from_string_auto (it wants an IPv4 prefix of at least /8 and an explicit IPv6 prefix)
and has tests asserting that; Peer= is the same table entry and now shares it.

Re-ran every registered grammar over the 6,680-unit corpus: of 18,331 grammar-validated key
occurrences these validators reject 28, and all 28 are correct — KDE's syntax-highlighting test
input, systemd's own `hoge`/`foofoo`/`::1` negative cases, a fuzz corpus file, and one genuine bug in
a shipped Debian unit (openqa-minion-restart.path sets PathChanged= to an unsubstituted relative
path, which systemd rejects). Whole suite green under both engines.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… sources per file

A code review found ten confirmed defects in the two preceding commits. Six were false
positives — values systemd accepts that the plugin flagged — which is the failure mode
that matters most here, since a spurious red squiggle on a working unit file is worse
than no validation at all.

The root cause was method, not carelessness: the grammars were derived from the
config_parse_* entry points but the reading often stopped there instead of following
through to the leaf helper, and the corpus of real Debian units was allowed to stand in
as evidence of correctness. A corpus can show that a grammar rejects something real; it
cannot show that a grammar accepts everything legal, because legal-but-unused spellings
simply aren't in it. Every fix below is derived from the C at a8e93919c3 — the commit
systemd-build/build/last_commit_hash pins — with the man page as a cross-check.

False positives fixed:

  paths          config_parse_unit_condition_path and config_parse_path_spec hand the
                 parser the whole rvalue: no splitting, no unquoting, no unescaping.
                 path_is_valid_full() restricts no characters, so `/mnt/My Data` is one
                 perfectly good path. The old grammar stopped at the first unescaped
                 space (a convention copied from the plugin's own PathOptionValue, not
                 from systemd) and wrongly honoured backslash escapes. Split into
                 UNIT_PATH and QUOTABLE_UNIT_PATH, since [Socket] Symlinks= IS split,
                 with EXTRACT_UNQUOTE, and so does take quotes and escapes.
  numbers        safe_atou* pass base 0 to strtoul, so hex and octal are legal wherever
                 they are used. New UnsignedNumberTerminal parses in the actual base, so
                 the range check survives the change of spelling — the previous
                 alternation could only bound its decimal branch and silently let
                 `TypeOfService=0400` (256) through.
  MTU            parse_mtu bounds the value, not the spelling: `IPv6MTUBytes=1K` is 1024
                 bytes and under IPV6_MIN_MTU. New ByteSizeTerminal evaluates the suffix.
  Peer=          takes the faithful grammar rather than inheriting Address='s stricter
                 one; a bare `Peer=2001:db8::2` is accepted by systemd.
  DAD            config_parse_address_dad tries parse_boolean() FIRST and accepts it with
                 a "For historical reasons" warning. Booleans are now accepted and marked
                 deprecated, which repeats systemd's own advice instead of erroring. The
                 man page documents only the four family names — the source wins.
  Match Name=    `!` is consumed with a bare p += invert and the following
                 extract_first_word skips whitespace, so `Name=! eth0` is valid.
  capabilities   the gperf table is built with --ignore-case and capability_to_name()
                 renders lower case, so `cap_sys_admin` is as canonical as the upper-case
                 form. FlexibleLiteralChoiceTerminal grew an ignoreCase flag rather than
                 duplicating 41 names.

Beyond the ten findings, [Address] Address= had the same class of bug and predates this
work: it demanded /8…/32 for IPv4 and /64…/128 for IPv6, so it flagged
`Address=2600::1/0` — a line from systemd's own test/test-network/conf/25-veth-peer.network.
The prefix ranges are now the full family ranges. Requiring an explicit IPv6 prefix is
kept, because systemd warns about that itself. InvalidValueForNetworkAddressesTest is
updated accordingly (the `/7` case it asserted was wrong).

Every file the two commits touched now opens with a reference block naming the man page,
the parsing function, and the table or helper that decides the accepted set, all as links
pinned to a8e93919c3 so they stay valid. Combinators.kt and AiGenerated.kt additionally
record the two traps that produced most of these defects: AlternativeCombinator is
first-full-match with no backtracking under the classic engine, and a duplicate key in
AiGenerated.kt's mapOf silently wins.

Whole suite green under both engines. Re-running every registered grammar over the
6,680-unit corpus, the validators from these commits now reject only values that are
genuinely invalid: deliberately-malformed fixtures, plus an unsubstituted @etc@ template
and one relative PathChanged= in shipped Debian units.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Unit Test Results (grammar engine true)

1 258 tests   1 258 ✅  1m 3s ⏱️
  316 suites      0 💤
  316 files        0 ❌

Results for commit cfa0695.

@github-actions

Copy link
Copy Markdown

Unit Test Results (grammar engine false)

1 258 tests   1 258 ✅  1m 0s ⏱️
  316 suites      0 💤
  316 files        0 ❌

Results for commit cfa0695.

@SJrX
SJrX merged commit 3f0ca88 into 242.x Jul 26, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant