blob: 89ab23fadf13ae0fa4958eb0e7c2f582e02d45ff [file]
// 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,
);
}
}