blob: 1cdd2cd1fe65ca54e16af1fe8799aac554c3b23f [file]
// Copyright (c) 2020, 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.
import '../code_generator.dart';
import '../config_provider/config_types.dart' show VarArgFunction;
import '../context.dart';
import '../header_parser/sub_parsers/api_availability.dart';
import '../strings.dart' as strings;
import '../visitor/ast.dart';
import 'binding_string.dart';
import 'local_variables.dart';
import 'scope.dart';
import 'utils.dart';
import 'writer.dart';
/// A binding for C function.
///
/// For example, take the following C function.
///
/// ```c
/// int sum(int a, int b);
/// ```
///
/// The generated Dart code for this function (without `FfiNative`) is as
/// follows.
///
/// ```dart
/// int sum(int a, int b) {
/// return _sum(a, b);
/// }
///
/// final _dart_sum _sum = _dylib.lookupFunction<_c_sum, _dart_sum>('sum');
///
/// typedef _c_sum = ffi.Int32 Function(ffi.Int32 a, ffi.Int32 b);
///
/// typedef _dart_sum = int Function(int a, int b);
/// ```
///
/// When using `Native`, the code is as follows.
///
/// ```dart
/// @ffi.Native<ffi.Int32 Function(ffi.Int32 a, ffi.Int32 b)>('sum')
/// external int sum(int a, int b);
/// ```
class Func extends LookUpBinding with HasLocalScope {
final FunctionType functionType;
bool exposeSymbolAddress;
bool exposeFunctionTypedefs;
bool isLeaf;
final bool objCReturnsRetained;
final bool useNameForLookup;
bool recordUse;
final ApiAvailability? apiAvailability;
final bool isVariadic;
List<VarArgFunction> varArgs;
@override
final bool loadFromNativeAsset;
/// The symbol for the internal function or method name, used for record use
/// mapping and avoiding collisions.
final Symbol funcVarSymbol;
bool get needsWrapper => !functionType.sameDartAndFfiDartType && !isInternal;
/// Contains typealias for function type if [exposeFunctionTypedefs] is true.
Typealias? _exposedFunctionTypealias;
/// [originalName] is looked up in dynamic library, if not
/// provided, takes the value of [name].
Func({
super.usr,
required String name,
super.originalName,
super.dartDoc,
required Type returnType,
List<Parameter> parameters = const [],
List<Parameter> varArgParameters = const [],
this.exposeSymbolAddress = false,
this.exposeFunctionTypedefs = false,
this.isLeaf = false,
this.objCReturnsRetained = false,
this.useNameForLookup = false,
this.recordUse = false,
super.isInternal,
this.loadFromNativeAsset = false,
this.apiAvailability,
this.isVariadic = false,
this.varArgs = const [],
}) : functionType = FunctionType(
returnType: returnType,
parameters: parameters,
varArgParameters: varArgParameters,
),
funcVarSymbol = Symbol('_$name', SymbolKind.method),
super(symbol: Symbol(name, SymbolKind.method)) {
for (var i = 0; i < functionType.parameters.length; i++) {
if (functionType.parameters[i].symbol.oldName.isEmpty) {
functionType.parameters[i].symbol = Symbol('arg$i', SymbolKind.field);
}
}
// Get function name with first letter in upper case.
final upperCaseName = name[0].toUpperCase() + name.substring(1);
if (exposeFunctionTypedefs) {
_exposedFunctionTypealias = Typealias(
name: upperCaseName,
type: functionType,
genFfiDartType: true,
isInternal: true,
);
}
}
/// Expands variant [Func] bindings based on [varArgs].
List<Func> expandVarArgs() {
final expanded = <Func>[];
for (final vaFunc in varArgs) {
final f = Func(
dartDoc: dartDoc,
usr: usr.isNotEmpty
? '$usr${strings.synthUsrChar} vaFunc: ${vaFunc.postfix}'
: '',
name: symbol.oldName + vaFunc.postfix,
originalName: originalName,
returnType: functionType.returnType,
parameters: functionType.parameters,
varArgParameters: [
for (final ta in vaFunc.types)
Parameter(type: ta, name: 'va', objCConsumed: false),
],
exposeSymbolAddress: exposeSymbolAddress,
exposeFunctionTypedefs: exposeFunctionTypedefs,
isLeaf: isLeaf,
recordUse: recordUse,
objCReturnsRetained: objCReturnsRetained,
loadFromNativeAsset: loadFromNativeAsset,
apiAvailability: apiAvailability,
useNameForLookup: useNameForLookup,
isInternal: isInternal,
);
f.userDefinedIsIncluded = userDefinedIsIncluded;
expanded.add(f);
}
return expanded;
}
@override
BindingString toBindingString(Writer w) {
final s = StringBuffer();
final enclosingFuncName = name;
s.write(makeDartDoc(dartDoc));
final deprecatedAnnotation = apiAvailability?.deprecatedAnnotation;
if (deprecatedAnnotation != null) {
s.write('$deprecatedAnnotation\n');
}
final context = w.context;
final cType =
_exposedFunctionTypealias?.getCType(context) ??
functionType.getCType(context, writeArgumentNames: false);
final dartType =
_exposedFunctionTypealias?.getFfiDartType(context) ??
functionType.getFfiDartType(context, writeArgumentNames: false);
final needsWrapper = !functionType.sameDartAndFfiDartType && !isInternal;
final funcVarName = funcVarSymbol.name;
final ffiReturnType = functionType.returnType.getFfiDartType(context);
final ffiArgDeclString = functionType.dartTypeParameters
.map((p) => '${p.type.getFfiDartType(context)} ${p.name},\n')
.join('');
final lookupName = useNameForLookup ? name : originalName;
final String dartReturnType;
final String dartArgDeclString;
final String funcImplCall;
final localVars = LocalVariables(localScope);
if (needsWrapper) {
dartReturnType = functionType.returnType.getDartType(context);
dartArgDeclString = functionType.dartTypeParameters
.map((p) => '${p.type.getDartType(context)} ${p.name},\n')
.join('');
final argString = functionType.dartTypeParameters
.map((p) {
final type = p.type.convertDartTypeToFfiDartType(
context,
p.name,
objCRetain: p.objCConsumed,
objCAutorelease: false,
localVariables: localVars,
);
return '$type,\n';
})
.join('');
funcImplCall = functionType.returnType.convertFfiDartTypeToDartType(
context,
'$funcVarName($argString)',
objCRetain: !objCReturnsRetained,
);
} else {
dartReturnType = ffiReturnType;
dartArgDeclString = ffiArgDeclString;
final argString = functionType.dartTypeParameters
.map((p) => '${p.name},\n')
.join('');
funcImplCall = '$funcVarName($argString)';
}
if (recordUse) {
final metaPrefix = context.libs.prefix(metaImport);
s.writeln('@$metaPrefix.RecordUse()');
}
if (loadFromNativeAsset) {
final nativeFuncName = needsWrapper ? funcVarName : enclosingFuncName;
final nativeAnnotation = makeNativeAnnotation(
w,
nativeType: cType,
dartName: nativeFuncName,
nativeSymbolName: lookupName,
isLeaf: isLeaf,
);
s.write('''
$nativeAnnotation
external $ffiReturnType $nativeFuncName($ffiArgDeclString);
''');
if (needsWrapper) {
s.write('''
$dartReturnType $enclosingFuncName($dartArgDeclString) {
${localVars.generateDeclarations()}
return $funcImplCall;
}
''');
}
if (exposeSymbolAddress) {
// Add to SymbolAddress in writer.
w.symbolAddressWriter.addNativeSymbol(
type:
'${context.libs.prefix(ffiImport)}.Pointer<'
'${context.libs.prefix(ffiImport)}.NativeFunction<$cType>>',
name: name,
);
}
} else {
final funcPointerName = context.rootScope.addPrivate('_${name}Ptr');
final isLeafString = isLeaf ? 'isLeaf:true' : '';
// Write enclosing function.
s.write('''
$dartReturnType $enclosingFuncName($dartArgDeclString) {
${localVars.generateDeclarations()}
return $funcImplCall;
}
''');
if (exposeSymbolAddress) {
// Add to SymbolAddress in writer.
w.symbolAddressWriter.addSymbol(
type:
'${context.libs.prefix(ffiImport)}.Pointer<'
'${context.libs.prefix(ffiImport)}.NativeFunction<$cType>>',
name: name,
ptrName: funcPointerName,
);
}
// Write function pointer.
final lookupStr = Namer.stringLiteral(lookupName);
final lookupFn = context.extraSymbols.lookupFuncName!.name;
s.write('''
late final $funcPointerName = $lookupFn<
${context.libs.prefix(ffiImport)}.NativeFunction<$cType>>('$lookupStr');
late final $funcVarName = $funcPointerName.asFunction<$dartType>($isLeafString);
''');
}
return BindingString(type: BindingStringType.func, string: s.toString());
}
@override
void visitChildren(Visitor visitor) {
super.visitChildren(visitor);
visitor.visit(funcVarSymbol);
visitor.visit(functionType);
visitor.visit(_exposedFunctionTypealias);
visitor.visit(ffiImport);
if (recordUse) {
visitor.visit(metaImport);
}
if (loadFromNativeAsset && exposeSymbolAddress) {
visitor.visit(selfImport);
}
}
@override
void visit(Visitation visitation) => visitation.visitFunc(this);
(String, String)? get recordUseMapping => recordUse
? (
needsWrapper ? funcVarSymbol.name : name,
useNameForLookup ? name : originalName,
)
: null;
}
/// Extension on [Iterable<Func>] to generate record use mapping.
extension FuncRecordUse on Iterable<Func> {
Map<String, String> toRecordUseMap() {
return {
for (final mapping in map((f) => f.recordUseMapping).nonNulls)
mapping.$1: mapping.$2,
};
}
}
/// Represents a Parameter, used in [Func], [Typealias], [ObjCMethod], and
/// [ObjCBlock].
class Parameter extends AstNode {
final String originalName;
Type type;
final bool objCConsumed;
bool? userDefinedIsIncluded;
Symbol symbol;
String get name => symbol.name;
Parameter({
String? originalName,
String name = '',
required Type type,
required this.objCConsumed,
}) : originalName = originalName ?? name,
symbol = Symbol(name, SymbolKind.field),
// A [NativeFunc] is wrapped with a pointer because this is a shorthand
// used in C for Pointer to function.
type = type.typealiasType is NativeFunc ? PointerType(type) : type;
String getNativeType(
Context context, {
String varName = '',
bool withAttr = true,
}) =>
'${type.getNativeType(context, varName: varName)}'
'${withAttr && objCConsumed ? ' __attribute__((ns_consumed))' : ''}';
@override
String toString() => '$type $symbol';
@override
void visitChildren(Visitor visitor) {
super.visitChildren(visitor);
visitor.visit(symbol);
visitor.visit(type);
}
bool get isNullable => type.typealiasType is ObjCNullable;
}