| // Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| /// Types understood natively by `dart:ffi`. | |
| final _ffigenTypes = <Pattern>[ | |
| 'void', | |
| 'const void *', | |
| 'int', | |
| 'unsigned', | |
| 'char *', | |
| 'const char *', | |
| 'long', | |
| ]; | |
| final _posixToFfiTypes = { | |
| // mode_t shall be an integer type. | |
| 'mode_t': 'long', | |
| // blksize_t, pid_t, and ssize_t shall be signed integer types. | |
| 'blksize_t': 'long', | |
| 'pid_t': 'long', | |
| 'ssize_t': 'long', | |
| // size_t shall be an unsigned integer type. | |
| 'size_t': 'unsigned long', | |
| // blkcnt_t and off_t shall be signed integer types. | |
| 'blkcnt_t': 'long', | |
| 'off_t': 'long', | |
| }; | |
| final _ffiToDartTypes = { | |
| 'void': 'void', | |
| 'void *': 'ffi.Pointer<ffi.Void>', | |
| 'const void *': 'ffi.Pointer<ffi.Void>', | |
| 'int': 'int', | |
| 'int *': 'ffi.Pointer<ffi.Int>', | |
| 'unsigned': 'int', | |
| 'unsigned long': 'int', | |
| 'char *': 'ffi.Pointer<ffi.Char>', | |
| 'const char *': 'ffi.Pointer<ffi.Char>', | |
| 'long': 'int', | |
| }; | |
| /// A C function that validates that a Unix type (e.g. `mode_t`) is | |
| /// representable using the given equivalent type understood by `dart:ffi` | |
| /// (e.g. `long`). | |
| class DomainValidator { | |
| final String prefix; | |
| final String unixType; | |
| String get ffiType => _posixToFfiTypes[unixType]!; | |
| String get name => 'valid_$unixType'; | |
| DomainValidator(this.prefix, this.unixType); | |
| String get cDeclaration => 'int $prefix$name($ffiType);'; | |
| String get cImplementation => | |
| '$int $prefix$name($ffiType a) { return ($ffiType)(($unixType) a) == a; }'; | |
| @override | |
| bool operator ==(Object other) { | |
| return other is DomainValidator && other.unixType == unixType; | |
| } | |
| @override | |
| int get hashCode => unixType.hashCode; | |
| } | |
| /// A representation of a C function. | |
| /// | |
| /// Used to generate code that wraps an existing C function in a form that can | |
| /// be consumed by `package:ffigen`. | |
| class CFunction { | |
| final String prefix; | |
| final String name; | |
| final String unixReturnType; | |
| final List<String> unixArgumentTypes; | |
| final String? comment; | |
| final String? url; | |
| final String? errorReturn; | |
| final String ffiFunctionTypeParametersString; | |
| final String ffiFunctionNamedParametersString; | |
| final String dartFunctionParametersString; | |
| final String unixCallArgumentsWithCasts; | |
| final String dartFfiFunctionCallArguments; | |
| final bool unixReturnRangeCheckRequired; | |
| final String? unsupportedGuard; | |
| final String? parameterDomainChecks; | |
| final Set<DomainValidator> requiredDomainValidators; | |
| String get ffiReturnType => _unixToFfiType(unixReturnType); | |
| String get dartReturnType => _ffiToDartType(ffiReturnType); | |
| static String _unixToFfiType(String type) { | |
| for (final t in _posixToFfiTypes.entries) { | |
| if (t.key.matchAsPrefix(type) != null) { | |
| return t.value; | |
| } | |
| } | |
| return type; | |
| } | |
| static String _ffiToDartType(String type) { | |
| final dartType = _ffiToDartTypes[type]; | |
| if (dartType == null) { | |
| throw ArgumentError.value(type); | |
| } | |
| return dartType; | |
| } | |
| static bool acceptableType(String type) { | |
| return _ffigenTypes.any( | |
| (ffigenType) => ffigenType.matchAsPrefix(type) != null, | |
| ) || | |
| _posixToFfiTypes.keys.any( | |
| (ffigenType) => ffigenType.matchAsPrefix(type) != null, | |
| ); | |
| } | |
| CFunction._( | |
| this.prefix, | |
| this.name, | |
| this.unixReturnType, | |
| this.unixArgumentTypes, | |
| this.comment, | |
| this.url, | |
| this.ffiFunctionTypeParametersString, | |
| this.ffiFunctionNamedParametersString, | |
| this.dartFunctionParametersString, | |
| this.unixCallArgumentsWithCasts, | |
| this.dartFfiFunctionCallArguments, | |
| this.unixReturnRangeCheckRequired, | |
| this.unsupportedGuard, | |
| this.parameterDomainChecks, | |
| this.requiredDomainValidators, | |
| this.errorReturn, | |
| ); | |
| factory CFunction.fromDescription({ | |
| required String prefix, | |
| required String name, | |
| required String unixReturnType, | |
| required List<String> unixArgumentTypes, | |
| required String description, | |
| required String documentationUrl, | |
| bool availableIOS = true, | |
| bool availableAndroid = true, | |
| String? errorReturn = null, | |
| }) { | |
| if (!acceptableType(unixReturnType)) { | |
| throw Exception('invalid return type $unixReturnType for $name'); | |
| } | |
| for (var type in unixArgumentTypes) { | |
| if (!acceptableType(type)) { | |
| throw Exception('invalid argument type $type for $name'); | |
| } | |
| } | |
| final ffiNamedParameterList = <String>[]; | |
| final dartParameterList = <String>[]; | |
| final ffiParameterList = <String>[]; | |
| final unixFunctionCallArgumentsWithCasts = <String>[]; | |
| final dartFfiFunctionCallArguments = <String>[]; | |
| final typeValidations = []; | |
| final requiredDomainValidators = Set<DomainValidator>(); | |
| for (var i = 0; i < unixArgumentTypes.length; ++i) { | |
| final unixArgumentType = unixArgumentTypes[i]; | |
| final ffiArgumentType = _unixToFfiType(unixArgumentType); | |
| final dartArgumentType = _ffiToDartType(ffiArgumentType); | |
| if (unixArgumentType == 'void') { | |
| assert(i == 0); | |
| continue; | |
| } | |
| ffiParameterList.add(ffiArgumentType); | |
| ffiNamedParameterList.add('$ffiArgumentType arg$i'); | |
| dartParameterList.add('$dartArgumentType arg$i'); | |
| dartFfiFunctionCallArguments.add('arg$i'); | |
| if (unixArgumentType == ffiArgumentType) { | |
| unixFunctionCallArgumentsWithCasts.add('arg$i'); | |
| } else { | |
| final domainValidator = DomainValidator(prefix, unixArgumentType); | |
| requiredDomainValidators.add(domainValidator); | |
| unixFunctionCallArgumentsWithCasts.add('($unixArgumentType) arg$i'); | |
| typeValidations.add('!$prefix${domainValidator.name}(arg$i)'); | |
| } | |
| } | |
| ffiParameterList.add('int *'); | |
| ffiNamedParameterList.add('int * err'); | |
| dartFfiFunctionCallArguments.add('errnoPtr'); | |
| final ffiFunctionTypeParametersString = ffiParameterList.join(', '); | |
| final ffiFunctionNamedParametersString = ffiNamedParameterList.join(', '); | |
| final dartFunctionParametersString = dartParameterList.join(', '); | |
| final unixCallArgumentsWithCasts = | |
| unixFunctionCallArgumentsWithCasts.isEmpty | |
| ? '' | |
| : unixFunctionCallArgumentsWithCasts.join(', '); | |
| final unsupportedGuards = [ | |
| if (!availableIOS) 'defined(TARGET_OS_IOS)', | |
| if (!availableAndroid) 'defined(__ANDROID__)', | |
| ].join(' || '); | |
| final typeValidation = typeValidations.join(' && '); | |
| return CFunction._( | |
| prefix, | |
| name, | |
| unixReturnType, | |
| unixArgumentTypes, | |
| description, | |
| documentationUrl, | |
| ffiFunctionTypeParametersString, | |
| ffiFunctionNamedParametersString, | |
| dartFunctionParametersString, | |
| unixCallArgumentsWithCasts, | |
| dartFfiFunctionCallArguments.join(', '), | |
| unixReturnType != _unixToFfiType(unixReturnType), | |
| unsupportedGuards.isEmpty ? null : unsupportedGuards, | |
| typeValidation.isEmpty ? null : typeValidation, | |
| requiredDomainValidators, | |
| errorReturn, | |
| ); | |
| } | |
| } |