From 3d7213c20e2a80c1681c37cb23cfb6700e14d127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20S=C3=A6ther?= Date: Sun, 7 Jun 2026 01:29:24 +0200 Subject: [PATCH] Harden YAML parser against nested structures; match int coercion in crosscheck src/input.c: add a depth-tracking skip_node() so stray nested mappings or sequences (e.g. a top-level array beside `conductors`, or an inner map/seq inside a conductor block) are cleanly skipped instead of throwing the key/value state machine out of parity and aborting with "need at least 2 conductors". tools/fh_crosscheck/geometry.py: coerce nw/nh with round(float(value)) to match the C parser's (int)round(safe_atof(value)), accepting float literals without truncation mismatch. Co-Authored-By: Claude Opus 4.8 --- src/input.c | 41 +++++++++++++++++++++++++++++++++ tools/fh_crosscheck/geometry.py | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/input.c b/src/input.c index 6c428d4..7b5ebdc 100644 --- a/src/input.c +++ b/src/input.c @@ -34,6 +34,28 @@ static double safe_atof(const char *str) { return str ? atof(str) : 0.0; } +/* Skip unexpected nested structures */ +static int skip_node(yaml_parser_t *parser) { + yaml_event_t event; + int depth = 1; + + while (depth > 0) { + if (!yaml_parser_parse(parser, &event)) { + return 0; + } + + if (event.type == YAML_MAPPING_START_EVENT || event.type == YAML_SEQUENCE_START_EVENT) { + depth++; + } else if (event.type == YAML_MAPPING_END_EVENT || event.type == YAML_SEQUENCE_END_EVENT) { + depth--; + } + + yaml_event_delete(&event); + } + + return 1; +} + /* Parse a conductor from YAML */ static int parse_conductor(yaml_parser_t *parser, conductor *c) { yaml_event_t event; @@ -64,6 +86,13 @@ static int parse_conductor(yaml_parser_t *parser, conductor *c) { in_mapping = 0; break; + case YAML_MAPPING_START_EVENT: + case YAML_SEQUENCE_START_EVENT: + skip_node(parser); + free(key); + key = NULL; + break; + case YAML_SCALAR_EVENT: if (key == NULL) { /* This is a key */ @@ -151,6 +180,7 @@ conductor *getinput(FILE *fp, int *n) { conductor *conductors; int conductor_count = 0; int in_conductors_sequence = 0; + int top_level_mapping_seen = 0; char *key = NULL; conductors = (conductor *)malloc(sizeof(conductor) * MAX_CONDUCTORS); @@ -212,6 +242,10 @@ conductor *getinput(FILE *fp, int *n) { in_conductors_sequence = 1; free(key); key = NULL; + } else { + skip_node(&parser); + free(key); + key = NULL; } break; @@ -228,7 +262,14 @@ conductor *getinput(FILE *fp, int *n) { } else { fprintf(stderr, "WARNING: conductor limit (%d) reached;" " extra conductors ignored\n", MAX_CONDUCTORS); + skip_node(&parser); } + } else if (!top_level_mapping_seen) { + top_level_mapping_seen = 1; + } else { + skip_node(&parser); + free(key); + key = NULL; } break; diff --git a/tools/fh_crosscheck/geometry.py b/tools/fh_crosscheck/geometry.py index 76c764b..72f75b2 100644 --- a/tools/fh_crosscheck/geometry.py +++ b/tools/fh_crosscheck/geometry.py @@ -16,7 +16,7 @@ def _coerce(key, value): if key == "name": return value if key in INT_KEYS: - return int(value) + return round(float(value)) return float(value)