From 082b1ea131fceafd71d0e854e42e0bbcad6cbdff Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 2 Dec 2025 14:51:52 +1100 Subject: [PATCH 1/4] [ffigen] Tighten up synthetic USRs --- .../ffigen/lib/src/code_generator/objc_block.dart | 15 +++++++-------- .../sub_parsers/functiondecl_parser.dart | 11 +++++++---- pkgs/ffigen/lib/src/header_parser/utils.dart | 4 +++- pkgs/ffigen/lib/src/strings.dart | 3 +++ pkgs/ffigen/test/regen.dart | 2 +- pkgs/ffigen/test/test_utils.dart | 4 ++-- 6 files changed, 23 insertions(+), 16 deletions(-) diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index 7e2bbec70..e51755263 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -4,6 +4,7 @@ import '../code_generator.dart'; import '../context.dart'; +import '../strings.dart'; import '../visitor/ast.dart'; import 'binding_string.dart'; @@ -108,14 +109,12 @@ class ObjCBlock extends BindingType with HasLocalScope { ) { // Create a fake USR code for the block. This code is used to dedupe blocks // with the same signature. Not intended to be human readable. - final usr = StringBuffer(); - usr.write( - 'objcBlock: ${returnType.cacheKey()} ${returnsRetained ? 'R' : ''}', - ); - for (final param in params) { - usr.write(' ${param.type.cacheKey()} ${param.objCConsumed ? 'C' : ''}'); - } - return usr.toString(); + return [ + '$synthUsrChar objcBlock:', + '${returnType.cacheKey()} ${returnsRetained ? 'R' : ''}', + for (final param in params) + '${param.type.cacheKey()} ${param.objCConsumed ? 'C' : ''}', + ].join(synthUsrChar); } bool get hasListener => returnType == voidType; diff --git a/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart b/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart index 31ab4da42..ee3ab90cd 100644 --- a/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart +++ b/pkgs/ffigen/lib/src/header_parser/sub_parsers/functiondecl_parser.dart @@ -5,6 +5,7 @@ import '../../code_generator.dart'; import '../../config_provider/config_types.dart'; import '../../context.dart'; +import '../../strings.dart'; import '../clang_bindings/clang_bindings.dart' as clang_types; import '../utils.dart'; import 'api_availability.dart'; @@ -118,7 +119,7 @@ List parseFunctionDeclaration( ); // Initialized with a single value with no prefix and empty var args. - var varArgFunctions = [VarArgFunction('', [])]; + var varArgFunctions = [null]; if (config.functions.varArgs.containsKey(funcName)) { if (clang.clang_isFunctionTypeVariadic(cursor.type()) == 1) { varArgFunctions = config.functions.varArgs[funcName]!; @@ -130,6 +131,8 @@ List parseFunctionDeclaration( } } for (final vaFunc in varArgFunctions) { + var usr = funcUsr; + if (vaFunc != null) usr += '$synthUsrChar vaFunc: ${vaFunc.postfix}'; funcs.add( Func( dartDoc: getCursorDocComment( @@ -138,13 +141,13 @@ List parseFunctionDeclaration( indent: nesting.length + commentPrefix.length, availability: apiAvailability.dartDoc, ), - usr: funcUsr + vaFunc.postfix, - name: config.functions.rename(decl) + vaFunc.postfix, + usr: usr, + name: config.functions.rename(decl) + (vaFunc?.postfix ?? ''), originalName: funcName, returnType: returnType, parameters: parameters, varArgParameters: [ - for (final ta in vaFunc.types) + for (final ta in vaFunc?.types ?? const []) Parameter(type: ta, name: 'va', objCConsumed: false), ], exposeSymbolAddress: config.functions.includeSymbolAddress(decl), diff --git a/pkgs/ffigen/lib/src/header_parser/utils.dart b/pkgs/ffigen/lib/src/header_parser/utils.dart index f2f5746e3..46ab1f0ab 100644 --- a/pkgs/ffigen/lib/src/header_parser/utils.dart +++ b/pkgs/ffigen/lib/src/header_parser/utils.dart @@ -10,6 +10,7 @@ import 'package:logging/logging.dart'; import '../code_generator.dart'; import '../config_provider/config_types.dart'; import '../context.dart'; +import '../strings.dart'; import 'clang_bindings/clang_bindings.dart' as clang_types; import 'type_extractor/extractor.dart'; @@ -87,8 +88,9 @@ extension CXSourceRangePtrExt on Pointer { extension CXCursorExt on clang_types.CXCursor { String usr() { var res = clang.clang_getCursorUSR(this).toStringAndDispose(); + assert(!res.contains(synthUsrChar)); if (isAnonymousRecordDecl()) { - res += '@offset:${sourceFileOffset()}'; + res += '$synthUsrChar anonRec: offset:${sourceFileOffset()}'; } return res; } diff --git a/pkgs/ffigen/lib/src/strings.dart b/pkgs/ffigen/lib/src/strings.dart index b6a5fdef2..4e5c18cf1 100644 --- a/pkgs/ffigen/lib/src/strings.dart +++ b/pkgs/ffigen/lib/src/strings.dart @@ -274,6 +274,9 @@ const doubleNaN = 'double.nan'; /// USR for struct `_Dart_Handle`. const dartHandleUsr = 'c:@S@_Dart_Handle'; +// A character that will never appear in real USRs, for making synthetic USRs. +const synthUsrChar = '~'; + const ffiNative = 'ffi-native'; const ffiNativeAsset = 'asset-id'; diff --git a/pkgs/ffigen/test/regen.dart b/pkgs/ffigen/test/regen.dart index 3ca5c0fe3..db23d0fc3 100644 --- a/pkgs/ffigen/test/regen.dart +++ b/pkgs/ffigen/test/regen.dart @@ -22,7 +22,7 @@ $ dart run test/setup.dart && dart run test/regen.dart && dart test void _regenConfig(Logger logger, String yamlConfigPath) { final path = p.join(packagePathForTests, yamlConfigPath); Directory.current = File(path).parent; - testConfigFromPath(path).generate(logger: logger); + testConfigFromPath(path, logger: logger).generate(logger: logger); } Future main(List args) async { diff --git a/pkgs/ffigen/test/test_utils.dart b/pkgs/ffigen/test/test_utils.dart index 762fd3341..d5665b1c8 100644 --- a/pkgs/ffigen/test/test_utils.dart +++ b/pkgs/ffigen/test/test_utils.dart @@ -235,10 +235,10 @@ FfiGenerator testConfig(String yamlBody, {String? filename, Logger? logger}) { ).configAdapter(); } -FfiGenerator testConfigFromPath(String path) { +FfiGenerator testConfigFromPath(String path, {Logger? logger}) { final file = File(path); final yamlBody = file.readAsStringSync(); - return testConfig(yamlBody, filename: path); + return testConfig(yamlBody, filename: path, logger: logger); } bool isFlutterTester = Platform.resolvedExecutable.contains('flutter_tester'); From 6a45600e9c8747fcc9838ed08845cccbf1e0e561 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Tue, 2 Dec 2025 15:02:10 +1100 Subject: [PATCH 2/4] changelog --- pkgs/ffigen/CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index 83243bff0..661143c13 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -6,6 +6,9 @@ `Categories`, `Interfaces`, and `Protocols`. - __Breaking change__: Remove deprecated `wrapperName` field from `NativeExternalBindings`. +- __Breaking change__: Certain synthetic USRs have been modified to ensure they + cannot collide with real USRs. It's very unlikely that any user facing USRs + are affected. ## 20.1.1 From d52917ee6a30f86e5589f32a7fae8c942a523d5e Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 3 Dec 2025 09:11:13 +1100 Subject: [PATCH 3/4] Fix CI --- pkgs/ffigen/lib/src/code_generator/objc_block.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index e51755263..d33bfb469 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -4,7 +4,7 @@ import '../code_generator.dart'; import '../context.dart'; -import '../strings.dart'; +import '../strings.dart' as strings; import '../visitor/ast.dart'; import 'binding_string.dart'; @@ -110,11 +110,11 @@ class ObjCBlock extends BindingType with HasLocalScope { // Create a fake USR code for the block. This code is used to dedupe blocks // with the same signature. Not intended to be human readable. return [ - '$synthUsrChar objcBlock:', + '${strings.synthUsrChar} objcBlock:', '${returnType.cacheKey()} ${returnsRetained ? 'R' : ''}', for (final param in params) '${param.type.cacheKey()} ${param.objCConsumed ? 'C' : ''}', - ].join(synthUsrChar); + ].join(strings.synthUsrChar); } bool get hasListener => returnType == voidType; From bc7803ee3cd3781db5b5d6e9291e4fb1e221c647 Mon Sep 17 00:00:00 2001 From: Liam Appelbe Date: Wed, 3 Dec 2025 09:47:02 +1100 Subject: [PATCH 4/4] Fix CI --- pkgs/ffigen/lib/src/code_generator/objc_block.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/ffigen/lib/src/code_generator/objc_block.dart b/pkgs/ffigen/lib/src/code_generator/objc_block.dart index d33bfb469..53697c301 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_block.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_block.dart @@ -114,7 +114,7 @@ class ObjCBlock extends BindingType with HasLocalScope { '${returnType.cacheKey()} ${returnsRetained ? 'R' : ''}', for (final param in params) '${param.type.cacheKey()} ${param.objCConsumed ? 'C' : ''}', - ].join(strings.synthUsrChar); + ].join(' '); } bool get hasListener => returnType == voidType;