blob: c78b883687f7e9a70cd1931fdd527bf99fe7577a [file] [log] [blame]
// Copyright (c) 2023, 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 'dart:io';
import 'package:collection/collection.dart';
import 'package:ffigen/src/code_generator.dart';
import 'ffigen_util.dart';
import 'logging.dart';
class Paths {
static final currentDir = Directory.current.uri;
static final bindingsDir = currentDir.resolve("lib/src/third_party/");
// Contains extensions for our C wrapper types.
static final globalEnvExts =
bindingsDir.resolve("global_env_extensions.dart");
// Contains extensions for JNI's struct types.
static final localEnvExts =
bindingsDir.resolve("jnienv_javavm_extensions.dart");
}
const writeLocalEnvExtensions = false;
void executeDartFormat(List<Uri> files) {
final paths = files.map((u) => u.toFilePath()).toList();
logger.info('execute dart format ${paths.join(" ")}');
final format = Process.runSync('dart', ['format', ...paths]);
if (format.exitCode != 0) {
logger.severe('dart format exited with ${format.exitCode}');
stderr.writeln(format.stderr);
}
}
const globalEnvType = 'GlobalJniEnvStruct';
const localEnvType = 'JNINativeInterface';
const jvmType = 'JNIInvokeInterface';
String getCheckedGetter(Type returnType) {
if (returnType is PointerType) {
final child = returnType.child.getCType(dummyWriter);
return 'getPointer<$child>()';
}
final cType = returnType.getCType(dummyWriter);
if (cType.endsWith("ArrayPtr")) {
return 'object';
}
const mappings = {
'JBooleanMarker': 'boolean',
'JByteMarker': 'byte',
'JShortMarker': 'short',
'JCharMarker': 'char',
'JIntMarker': 'integer',
'JSizeMarker': 'integer', // jsize is aliased to jint
'JLongMarker': 'long',
'JFloatMarker': 'float',
'JDoubleMarker': 'doubleFloat',
'JObjectPtr': 'object',
'JThrowablePtr': 'object',
'JStringPtr': 'object',
'JClassPtr': 'value',
'JFieldIDPtr': 'fieldID',
'JMethodIDPtr': 'methodID',
'ffi.Int32': 'integer',
'ffi.Void': 'check()',
'JWeakPtr': 'object',
};
if (mappings.containsKey(cType)) {
return mappings[cType]!;
}
throw 'Unknown return type: $cType';
}
String? getGlobalEnvExtensionFunction(Member field, Type? checkedReturnType) {
final fieldType = field.type;
if (fieldType is PointerType && fieldType.child is NativeFunc) {
final nativeFunc = fieldType.child as NativeFunc;
final functionType = nativeFunc.type;
final returnType = functionType.returnType;
var params = functionType.parameters;
if (params.first.name.isEmpty) {
return null;
}
// Remove env parameter
params = params.sublist(1);
final signature = params
.map((p) => '${p.type.getDartType(dummyWriter)} ${p.name}')
.join(', ');
final dartType =
FunctionType(returnType: checkedReturnType!, parameters: params)
.getDartType(dummyWriter);
final callArgs = params.map((p) => p.name).join(', ');
final checkedGetter = getCheckedGetter(returnType);
var returns = returnType.getDartType(dummyWriter);
if (checkedGetter == 'boolean') {
returns = 'bool';
}
return '''
late final _${field.name} =
ptr.ref.${field.name}.asFunction<$dartType>();\n
$returns ${field.name}($signature) =>
_${field.name}($callArgs).$checkedGetter;
''';
}
return null;
}
void writeDartExtensions(Library library) {
const header = '''
// ignore_for_file: non_constant_identifier_names
// coverage:ignore-file
import "dart:ffi" as ffi;\n
import "jni_bindings_generated.dart";
''';
const importAccessors = '''
import "../accessors.dart";
''';
final globalEnvExtension = getGlobalEnvExtension(library);
final accessorExtension = getFunctionPointerExtension(
library,
'JniAccessorsStruct',
'JniAccessors',
);
File.fromUri(Paths.globalEnvExts).writeAsStringSync(preamble +
header +
importAccessors +
globalEnvExtension +
accessorExtension);
final localEnvExtsFile = File.fromUri(Paths.localEnvExts);
if (localEnvExtsFile.existsSync()) {
localEnvExtsFile.deleteSync();
}
if (!writeLocalEnvExtensions) {
return;
}
final envExtension = getFunctionPointerExtension(
library,
'JniEnv',
'LocalJniEnv',
indirect: true,
implicitThis: true,
);
final jvmExtension = getFunctionPointerExtension(
library,
'JavaVM',
'JniJavaVM',
indirect: true,
implicitThis: true,
);
localEnvExtsFile
.writeAsStringSync(preamble + header + envExtension + jvmExtension);
}
String getGlobalEnvExtension(
Library library,
) {
final env = findCompound(library, localEnvType);
final globalEnv = findCompound(library, globalEnvType);
final checkedReturnTypes = {};
for (var field in globalEnv.members) {
final fieldType = field.type;
if (fieldType is PointerType && fieldType.child is NativeFunc) {
checkedReturnTypes[field.name] =
(fieldType.child as NativeFunc).type.returnType;
}
}
final extensionFunctions = env.members
.map((m) => getGlobalEnvExtensionFunction(m, checkedReturnTypes[m.name]))
.whereNotNull()
.join('\n');
return '''
/// Wraps over Pointer<GlobalJniEnvStruct> and exposes function pointer fields
/// as methods.
class GlobalJniEnv {
final ffi.Pointer<GlobalJniEnvStruct> ptr;
GlobalJniEnv(this.ptr);
$extensionFunctions
}
''';
}
String? getFunctionPointerExtensionFunction(Member field,
{bool indirect = false, bool implicitThis = false}) {
final fieldType = field.type;
if (fieldType is PointerType && fieldType.child is NativeFunc) {
final nativeFunc = fieldType.child as NativeFunc;
final functionType = nativeFunc.type;
final returnType = functionType.returnType;
final params = functionType.parameters;
if (params.first.name.isEmpty) {
return null;
}
final visibleParams = implicitThis ? params.sublist(1) : params;
final signature = visibleParams
.map((p) => '${p.type.getDartType(dummyWriter)} ${p.name}')
.join(', ');
final dartType = FunctionType(returnType: returnType, parameters: params)
.getDartType(dummyWriter);
final callArgs = [
if (implicitThis) 'ptr',
...visibleParams.map((p) => p.name)
].join(', ');
final returns = returnType.getDartType(dummyWriter);
final dereference = indirect ? 'value.ref' : 'ref';
return '''
late final _${field.name} =
ptr.$dereference.${field.name}.asFunction<$dartType>();
$returns ${field.name}($signature) => _${field.name}($callArgs);
''';
}
return null;
}
String getFunctionPointerExtension(
Library library, String type, String wrapperClassName,
{bool indirect = false, bool implicitThis = false}) {
final typeBinding =
library.bindings.firstWhere((b) => b.name == type) as Type;
final compound = typeBinding.typealiasType.baseType as Compound;
final extensionFunctions = compound.members
.map((f) => getFunctionPointerExtensionFunction(f,
indirect: indirect, implicitThis: implicitThis))
.whereNotNull()
.join('\n');
return '''
/// Wraps over the function pointers in $type and exposes them as methods.
class $wrapperClassName {
final ffi.Pointer<$type> ptr;
$wrapperClassName(this.ptr);
$extensionFunctions
}
''';
}
void generateDartExtensions(Library library) {
writeDartExtensions(library);
executeDartFormat([Paths.globalEnvExts, Paths.localEnvExts]);
}