From 237b3a4b0bfdba5db654a28ee51f00e595cbc59c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yusuf=20=C4=B0hsan=20G=C3=B6rgel?= Date: Sun, 2 Aug 2026 23:42:29 +0300 Subject: [PATCH] [jnigen] Report an empty config list entry as a config error An entry left empty under a list key reads as null, and the list it lands in is cast lazily, so nothing fails until something reaches for the element: `type 'Null' is not a subtype of type 'String' in type cast`, from a stack trace that names neither the key nor the file. Check the entries in `getStringList` and `getPathList` and name the one that is empty. A list that came from `-D` holds its own strings, so an override still wins. --- pkgs/jnigen/lib/src/config/yaml_reader.dart | 60 +++++++++++++++++---- pkgs/jnigen/test/config_test.dart | 21 ++++++++ 2 files changed, 70 insertions(+), 11 deletions(-) diff --git a/pkgs/jnigen/lib/src/config/yaml_reader.dart b/pkgs/jnigen/lib/src/config/yaml_reader.dart index c9b77de1d3..aacf24ff12 100644 --- a/pkgs/jnigen/lib/src/config/yaml_reader.dart +++ b/pkgs/jnigen/lib/src/config/yaml_reader.dart @@ -88,17 +88,55 @@ class YamlReader { /// from YAML config. Uri? getPath(String property) => _config.optionalPath(property); - List? getStringList(String property) => _config.optionalStringList( - property, - splitCliPattern: ';', - combineAllConfigs: false, - ); - - List? getPathList(String property) => _config.optionalPathList( - property, - combineAllConfigs: false, - splitCliPattern: ';', - ); + /// Reports an entry left empty in [property], which reads as null. + /// + /// A list read from the config file is cast lazily, so such an entry only + /// fails where something reaches for it, with a message that names neither + /// the property nor the file. A list that came from `-D` holds its own + /// strings and cannot carry a null. + /// + /// Pass the list through `cast()`: reading an element of the cast + /// list throws before this can see it, and casting again unwraps to the + /// source rather than stacking on top of it. + void _checkForEmptyEntries(String property, List values) { + for (var i = 0; i < values.length; ++i) { + if (values[i] == null) { + throw ConfigException( + 'Entry ${i + 1} of "$property" is empty in the config file.'); + } + } + } + + List? getStringList(String property) { + final values = _config.optionalStringList( + property, + splitCliPattern: ';', + combineAllConfigs: false, + ); + if (values == null) return null; + // The cast is what lets the check read a null; see the helper. + _checkForEmptyEntries(property, values.cast()); + return values; + } + + List? getPathList(String property) { + // `optionalPathList` reads the entries itself, so they have to be checked + // before it does. + final values = _config.optionalStringList( + property, + splitCliPattern: ';', + combineAllConfigs: false, + ); + if (values != null) { + // The cast is what lets the check read a null; see the helper. + _checkForEmptyEntries(property, values.cast()); + } + return _config.optionalPathList( + property, + combineAllConfigs: false, + splitCliPattern: ';', + ); + } String? getOneOf(String property, Set values) => _config.optionalString(property, validValues: values); diff --git a/pkgs/jnigen/test/config_test.dart b/pkgs/jnigen/test/config_test.dart index f87c9d12d7..30a5634ed7 100644 --- a/pkgs/jnigen/test/config_test.dart +++ b/pkgs/jnigen/test/config_test.dart @@ -123,5 +123,26 @@ void main() async { name: 'Nested class specified', overrides: ['-Dclasses=com.android.Clock\$Clock'], ); + + for (final property in ['classes', 'source_path']) { + test('Empty entry in $property', () { + // Not expressible as an override: the -D parser requires a value. + final dir = Directory.systemTemp.createTempSync('jnigen_config_test'); + addTearDown(() => dir.deleteSync(recursive: true)); + final yaml = File(join(dir.path, 'jnigen.yaml'))..writeAsStringSync(''' +output: + dart: + path: lib/gen.dart + structure: single_file +$property: + - "com.example.Foo" + - +${property == 'classes' ? '' : 'classes:\n - "com.example.Foo"\n'}'''); + expect( + () => Config.parseArgs(['--config', yaml.path]), + throwsA(isA()), + ); + }); + } }); }