| // 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 '../config_provider/public_ast.dart' as public_ast; | |
| import '../context.dart'; | |
| import '../header_parser/sub_parsers/api_availability.dart'; | |
| 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. | |
| /// | |
| /// <!-- no-source-file --> | |
| /// ```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. | |
| /// | |
| /// <!-- no-source-file --> | |
| /// ```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 generateTypedefs; | |
| 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. | |
| Symbol? funcVarSymbol; | |
| bool get needsWrapper => !functionType.sameDartAndFfiDartType && !isInternal; | |
| /// Contains typealias for function type if [generateTypedefs] is true. | |
| Typealias? _exposedFunctionTypealias; | |
| bool isIncluded = false; | |
| /// [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.generateTypedefs = false, | |
| this.isLeaf = false, | |
| this.objCReturnsRetained = false, | |
| this.useNameForLookup = false, | |
| this.recordUse = false, | |
| super.isInternal, | |
| this.loadFromNativeAsset = false, | |
| this.apiAvailability, | |
| this.isVariadic = false, | |
| }) : functionType = FunctionType( | |
| returnType: returnType, | |
| parameters: parameters, | |
| varArgParameters: varArgParameters, | |
| ), | |
| 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); | |
| } | |
| } | |
| } | |
| Typealias? fillExposedFunctionTypealias() { | |
| if (!generateTypedefs) return null; | |
| final upperCaseName = symbol.oldName.isEmpty | |
| ? '' | |
| : symbol.oldName[0].toUpperCase() + symbol.oldName.substring(1); | |
| return _exposedFunctionTypealias ??= Typealias( | |
| name: upperCaseName, | |
| type: functionType, | |
| genFfiDartType: true, | |
| isInternal: true, | |
| ); | |
| } | |
| Func cloneForVarArgs( | |
| String usr, | |
| String name, | |
| List<Parameter> varArgParameters, | |
| ) { | |
| final cloned = Func( | |
| usr: usr, | |
| name: name, | |
| originalName: originalName, | |
| dartDoc: dartDoc, | |
| returnType: functionType.returnType, | |
| parameters: [for (final p in functionType.parameters) p.clone()], | |
| varArgParameters: varArgParameters, | |
| exposeSymbolAddress: exposeSymbolAddress, | |
| generateTypedefs: generateTypedefs, | |
| isLeaf: isLeaf, | |
| objCReturnsRetained: objCReturnsRetained, | |
| useNameForLookup: useNameForLookup, | |
| recordUse: recordUse, | |
| isInternal: isInternal, | |
| loadFromNativeAsset: loadFromNativeAsset, | |
| apiAvailability: apiAvailability, | |
| isVariadic: isVariadic, | |
| ); | |
| cloned.isIncluded = isIncluded; | |
| return cloned; | |
| } | |
| @override | |
| public_ast.AstNode? toPublicAstNode() => public_ast.Func(this); | |
| void fillFuncVarSymbol() { | |
| funcVarSymbol ??= Symbol('_${symbol.oldName}', SymbolKind.method); | |
| } | |
| @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; | |
| Symbol symbol; | |
| String get name => symbol.name; | |
| Parameter._({ | |
| required this.originalName, | |
| required this.symbol, | |
| required Type type, | |
| required this.objCConsumed, | |
| }) : // 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; | |
| Parameter({ | |
| String? name, | |
| String? originalName, | |
| required Type type, | |
| bool objCConsumed = false, | |
| }) : this._( | |
| originalName: originalName ?? name ?? '', | |
| symbol: Symbol(name ?? originalName ?? '', SymbolKind.field), | |
| type: type, | |
| objCConsumed: objCConsumed, | |
| ); | |
| 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); | |
| } | |
| Parameter clone() => Parameter._( | |
| originalName: originalName, | |
| symbol: symbol.clone(), | |
| type: type, | |
| objCConsumed: objCConsumed, | |
| ); | |
| bool get isNullable => type.typealiasType is ObjCNullable; | |
| } |