From bf3f7eba3690d6c441844d2df634fda68749c68e Mon Sep 17 00:00:00 2001 From: Prerak Mann Date: Mon, 1 Mar 2021 22:08:04 +0530 Subject: [PATCH] Added support for Global variables (#139) --- CHANGELOG.md | 3 + README.md | 8 +- lib/src/code_generator/global.dart | 32 +++- lib/src/config_provider/config.dart | 13 ++ lib/src/header_parser/includer.dart | 10 ++ .../header_parser/sub_parsers/var_parser.dart | 47 ++++++ .../translation_unit_parser.dart | 4 + lib/src/header_parser/utils.dart | 13 ++ lib/src/strings.dart | 1 + pubspec.yaml | 2 +- test/code_generator_test.dart | 21 ++- test/header_parser_tests/globals.h | 15 ++ test/header_parser_tests/globals_test.dart | 75 +++++++++ .../_expected_sqlite_bindings.dart | 143 ++++++++++++++++++ 14 files changed, 374 insertions(+), 13 deletions(-) create mode 100644 lib/src/header_parser/sub_parsers/var_parser.dart create mode 100644 test/header_parser_tests/globals.h create mode 100644 test/header_parser_tests/globals_test.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index a7b8617a..1e49bdc7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 2.0.0-dev.4 +- Add support for parsing and generating globals. + # 2.0.0-dev.3 - Removed the usage of `--no-sound-null-safety` flag. diff --git a/README.md b/README.md index df041e78..f2c194e5 100644 --- a/README.md +++ b/README.md @@ -144,7 +144,7 @@ compiler-opts: '-I/usr/lib/llvm-9/include/' - functions
structs
enums
unnamed-enums
macros + functions
structs
enums
unnamed-enums
macros
globals Filters for declarations.
Default: all are included @@ -170,6 +170,12 @@ enums: 'CXTypeKind': # Full names have higher priority. # $1 keeps only the 1st group i.e '(.*)'. 'CXType(.*)': '$1' +globals: + exclude: + - aGlobal + rename: + # Removes '_' from beginning of a name. + - '_(.*)': '$1' ``` diff --git a/lib/src/code_generator/global.dart b/lib/src/code_generator/global.dart index b52fa702..d419e92f 100644 --- a/lib/src/code_generator/global.dart +++ b/lib/src/code_generator/global.dart @@ -2,6 +2,8 @@ // 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 'package:ffigen/src/code_generator/typedef.dart'; + import 'binding.dart'; import 'binding_string.dart'; import 'type.dart'; @@ -34,6 +36,21 @@ class Global extends LookUpBinding { dartDoc: dartDoc, ); + List? _typedefDependencies; + @override + List getTypedefDependencies(Writer w) { + if (_typedefDependencies == null) { + _typedefDependencies = []; + + // Add typedef's required by the variable's type. + final valueType = type.getBaseType(); + if (valueType.broadType == BroadType.NativeFunction) { + _typedefDependencies!.addAll(valueType.nativeFunc!.getDependencies()); + } + } + return _typedefDependencies!; + } + @override BindingString toBindingString(Writer w) { final s = StringBuffer(); @@ -41,13 +58,18 @@ class Global extends LookUpBinding { if (dartDoc != null) { s.write(makeDartDoc(dartDoc!)); } + final pointerName = w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName'); + final dartType = type.getDartType(w); + final cType = type.getCType(w); + final refOrValue = type.broadType == BroadType.Struct ? 'ref' : 'value'; - final holderVarName = - w.wrapperLevelUniqueNamer.makeUnique('_$globalVarName'); s.write( - '${w.ffiLibraryPrefix}.Pointer<${type.getCType(w)}> $holderVarName;\n'); - s.write( - "${type.getDartType(w)} get $globalVarName => ($holderVarName ??= ${w.dylibIdentifier}.lookup<${type.getCType(w)}>('$originalName')).value;\n\n"); + "late final ${w.ffiLibraryPrefix}.Pointer<$dartType> $pointerName = ${w.dylibIdentifier}.lookup<$cType>('$originalName');\n\n"); + s.write('$dartType get $globalVarName => $pointerName.$refOrValue;\n\n'); + if (type.broadType != BroadType.Struct) { + s.write( + 'set $globalVarName($dartType value) => $pointerName.value = value;\n\n'); + } return BindingString(type: BindingStringType.global, string: s.toString()); } diff --git a/lib/src/config_provider/config.dart b/lib/src/config_provider/config.dart index 96ca643d..6c11b344 100644 --- a/lib/src/config_provider/config.dart +++ b/lib/src/config_provider/config.dart @@ -51,6 +51,10 @@ class Config { Declaration get unnamedEnumConstants => _unnamedEnumConstants; late Declaration _unnamedEnumConstants; + /// Declaration config for Globals. + Declaration get globals => _globals; + late Declaration _globals; + /// Declaration config for Macro constants. Declaration get macroDecl => _macroDecl; late Declaration _macroDecl; @@ -222,6 +226,15 @@ class Config { extractedResult: (dynamic result) => _unnamedEnumConstants = result as Declaration, ), + strings.globals: Specification( + requirement: Requirement.no, + validator: declarationConfigValidator, + extractor: declarationConfigExtractor, + defaultValue: () => Declaration(), + extractedResult: (dynamic result) { + _globals = result as Declaration; + }, + ), strings.macros: Specification( requirement: Requirement.no, validator: declarationConfigValidator, diff --git a/lib/src/header_parser/includer.dart b/lib/src/header_parser/includer.dart index baa5b599..bd824761 100644 --- a/lib/src/header_parser/includer.dart +++ b/lib/src/header_parser/includer.dart @@ -47,6 +47,16 @@ bool shouldIncludeUnnamedEnumConstant(String usr, String name) { } } +bool shouldIncludeGlobalVar(String usr, String name) { + if (bindingsIndex.isSeenGlobalVar(usr) || name == '') { + return false; + } else if (config.globals.shouldInclude(name)) { + return true; + } else { + return false; + } +} + bool shouldIncludeMacro(String usr, String name) { if (bindingsIndex.isSeenMacro(usr) || name == '') { return false; diff --git a/lib/src/header_parser/sub_parsers/var_parser.dart b/lib/src/header_parser/sub_parsers/var_parser.dart new file mode 100644 index 00000000..d0248ec2 --- /dev/null +++ b/lib/src/header_parser/sub_parsers/var_parser.dart @@ -0,0 +1,47 @@ +// Copyright (c) 2021, 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 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/header_parser/data.dart'; +import 'package:ffigen/src/header_parser/includer.dart'; +import 'package:logging/logging.dart'; + +import '../clang_bindings/clang_bindings.dart' as clang_types; +import '../data.dart'; +import '../utils.dart'; + +final _logger = Logger('ffigen.header_parser.var_parser'); + +/// Parses a global variable +Global? parseVarDeclaration(clang_types.CXCursor cursor) { + final name = cursor.spelling(); + final usr = cursor.usr(); + if (bindingsIndex.isSeenGlobalVar(usr)) { + return bindingsIndex.getSeenGlobalVar(usr); + } + if (!shouldIncludeGlobalVar(usr, name)) { + return null; + } + + _logger.fine('++++ Adding Global: ${cursor.completeStringRepr()}'); + + final type = cursor.type().toCodeGenType(); + if (type.getBaseType().broadType == BroadType.Unimplemented || + type.getBaseType().isIncompleteStruct) { + _logger.fine( + '---- Removed Global, reason: unsupported type: ${cursor.completeStringRepr()}'); + _logger.warning("Skipped global variable '$name', type not supported."); + return null; + } + + final global = Global( + originalName: name, + name: config.globals.renameUsingConfig(name), + usr: usr, + type: type, + dartDoc: getCursorDocComment(cursor), + ); + bindingsIndex.addGlobalVarToSeen(usr, global); + return global; +} diff --git a/lib/src/header_parser/translation_unit_parser.dart b/lib/src/header_parser/translation_unit_parser.dart index 7c08410c..69f7f13e 100644 --- a/lib/src/header_parser/translation_unit_parser.dart +++ b/lib/src/header_parser/translation_unit_parser.dart @@ -6,6 +6,7 @@ import 'dart:ffi'; import 'package:ffigen/src/code_generator.dart'; import 'package:ffigen/src/header_parser/sub_parsers/macro_parser.dart'; +import 'package:ffigen/src/header_parser/sub_parsers/var_parser.dart'; import 'package:logging/logging.dart'; import 'clang_bindings/clang_bindings.dart' as clang_types; @@ -58,6 +59,9 @@ int _rootCursorVisitor(clang_types.CXCursor cursor, clang_types.CXCursor parent, case clang_types.CXCursorKind.CXCursor_MacroDefinition: saveMacroDefinition(cursor); break; + case clang_types.CXCursorKind.CXCursor_VarDecl: + addToBindings(parseVarDeclaration(cursor)); + break; default: _logger.finer('rootCursorVisitor: CursorKind not implemented'); } diff --git a/lib/src/header_parser/utils.dart b/lib/src/header_parser/utils.dart index f0a41278..28ca6575 100644 --- a/lib/src/header_parser/utils.dart +++ b/lib/src/header_parser/utils.dart @@ -316,6 +316,7 @@ class BindingsIndex { final Map _enumClass = {}; final Map _unnamedEnumConstants = {}; final Map _macros = {}; + final Map _globals = {}; // Stores only named typedefC used in NativeFunc. final Map _functionTypedefs = {}; @@ -367,6 +368,18 @@ class BindingsIndex { return _unnamedEnumConstants[usr]; } + bool isSeenGlobalVar(String usr) { + return _globals.containsKey(usr); + } + + void addGlobalVarToSeen(String usr, Global global) { + _globals[usr] = global; + } + + Global? getSeenGlobalVar(String usr) { + return _globals[usr]; + } + bool isSeenMacro(String usr) { return _macros.containsKey(usr); } diff --git a/lib/src/strings.dart b/lib/src/strings.dart index 8f0ac937..99e0c680 100644 --- a/lib/src/strings.dart +++ b/lib/src/strings.dart @@ -39,6 +39,7 @@ const functions = 'functions'; const structs = 'structs'; const enums = 'enums'; const unnamedEnums = 'unnamed-enums'; +const globals = 'globals'; const macros = 'macros'; // Sub-fields of Declarations. diff --git a/pubspec.yaml b/pubspec.yaml index adf29939..87663a25 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,7 @@ # BSD-style license that can be found in the LICENSE file. name: ffigen -version: 2.0.0-dev.3 +version: 2.0.0-dev.4 homepage: https://github.com/dart-lang/ffigen description: Experimental generator for FFI bindings, using LibClang to parse C header files. diff --git a/test/code_generator_test.dart b/test/code_generator_test.dart index ea6393ba..aae8b0f2 100644 --- a/test/code_generator_test.dart +++ b/test/code_generator_test.dart @@ -436,14 +436,23 @@ final ffi.DynamicLibrary _dylib; /// The symbols are looked up in [dynamicLibrary]. Bindings(ffi.DynamicLibrary dynamicLibrary): _dylib = dynamicLibrary; -ffi.Pointer _test1; -int get test1 => (_test1 ??= _dylib.lookup('test1')).value; +late final ffi.Pointer _test1 = _dylib.lookup('test1'); -ffi.Pointer> _test2; -ffi.Pointer get test2 => (_test2 ??= _dylib.lookup>('test2')).value; +int get test1 => _test1.value; -ffi.Pointer> _test5; -ffi.Pointer get test5 => (_test5 ??= _dylib.lookup>('test5')).value; +set test1(int value) => _test1.value = value; + +late final ffi.Pointer> _test2 = _dylib.lookup>('test2'); + +ffi.Pointer get test2 => _test2.value; + +set test2(ffi.Pointer value) => _test2.value = value; + +late final ffi.Pointer> _test5 = _dylib.lookup>('test5'); + +ffi.Pointer get test5 => _test5.value; + +set test5(ffi.Pointer value) => _test5.value = value; } diff --git a/test/header_parser_tests/globals.h b/test/header_parser_tests/globals.h new file mode 100644 index 00000000..f0f464fa --- /dev/null +++ b/test/header_parser_tests/globals.h @@ -0,0 +1,15 @@ +// Copyright (c) 2021, 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. + +#include +#include + +bool coolGlobal; +int32_t myInt; +int32_t *aGlobalPointer; +long double longDouble; +long double *pointerToLongDouble; + +// This should be ignored +int GlobalIgnore; diff --git a/test/header_parser_tests/globals_test.dart b/test/header_parser_tests/globals_test.dart new file mode 100644 index 00000000..7184792c --- /dev/null +++ b/test/header_parser_tests/globals_test.dart @@ -0,0 +1,75 @@ +// Copyright (c) 2021, 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 'package:ffigen/src/code_generator.dart'; +import 'package:ffigen/src/header_parser.dart' as parser; +import 'package:ffigen/src/config_provider.dart'; +import 'package:test/test.dart'; +import 'package:yaml/yaml.dart' as yaml; +import 'package:ffigen/src/strings.dart' as strings; + +import '../test_utils.dart'; + +late Library actual, expected; + +void main() { + group('globals_test', () { + setUpAll(() { + logWarnings(); + expected = expectedLibrary(); + actual = parser.parse( + Config.fromYaml(yaml.loadYaml(''' +${strings.name}: 'NativeLibrary' +${strings.description}: 'Globals Test' +${strings.output}: 'unused' +${strings.headers}: + ${strings.entryPoints}: + - 'test/header_parser_tests/globals.h' + ${strings.includeDirectives}: + - '**globals.h' +${strings.globals}: + ${strings.exclude}: + - GlobalIgnore +# Needed for stdbool.h in MacOS +${strings.compilerOpts}: '-I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks/Kernel.framework/Headers/ -Wno-nullability-completeness' + ''') as yaml.YamlMap), + ); + }); + + test('Total bindings count', () { + expect(actual.bindings.length, expected.bindings.length); + }); + + test('Parse global Values', () { + expect(actual.getBindingAsString('coolGlobal'), + expected.getBindingAsString('coolGlobal')); + expect(actual.getBindingAsString('myInt'), + expected.getBindingAsString('myInt')); + expect(actual.getBindingAsString('aGlobalPointer'), + expected.getBindingAsString('aGlobalPointer')); + }); + + test('Ignore global values', () { + expect(() => actual.getBindingAsString('GlobalIgnore'), + throwsA(TypeMatcher())); + expect(() => actual.getBindingAsString('longDouble'), + throwsA(TypeMatcher())); + expect(() => actual.getBindingAsString('pointerToLongDouble'), + throwsA(TypeMatcher())); + }); + }); +} + +Library expectedLibrary() { + return Library( + name: 'Bindings', + bindings: [ + Global(type: Type.boolean(), name: 'coolGlobal'), + Global(type: Type.nativeType(SupportedNativeType.Int32), name: 'myInt'), + Global( + type: Type.pointer(Type.nativeType(SupportedNativeType.Int32)), + name: 'aGlobalPointer'), + ], + ); +} diff --git a/test/large_integration_tests/_expected_sqlite_bindings.dart b/test/large_integration_tests/_expected_sqlite_bindings.dart index 7ea5be8f..6abdd488 100644 --- a/test/large_integration_tests/_expected_sqlite_bindings.dart +++ b/test/large_integration_tests/_expected_sqlite_bindings.dart @@ -11,6 +11,44 @@ class SQLite { /// The symbols are looked up in [dynamicLibrary]. SQLite(ffi.DynamicLibrary dynamicLibrary) : _dylib = dynamicLibrary; + /// CAPI3REF: Run-Time Library Version Numbers + /// KEYWORDS: sqlite3_version sqlite3_sourceid + /// + /// These interfaces provide the same information as the [SQLITE_VERSION], + /// [SQLITE_VERSION_NUMBER], and [SQLITE_SOURCE_ID] C preprocessor macros + /// but are associated with the library instead of the header file. ^(Cautious + /// programmers might include assert() statements in their application to + /// verify that values returned by these interfaces match the macros in + /// the header, and thus ensure that the application is + /// compiled with matching library and header files. + /// + ///
+  /// assert( sqlite3_libversion_number()==SQLITE_VERSION_NUMBER );
+  /// assert( strncmp(sqlite3_sourceid(),SQLITE_SOURCE_ID,80)==0 );
+  /// assert( strcmp(sqlite3_libversion(),SQLITE_VERSION)==0 );
+  /// 
)^ + /// + /// ^The sqlite3_version[] string constant contains the text of [SQLITE_VERSION] + /// macro. ^The sqlite3_libversion() function returns a pointer to the + /// to the sqlite3_version[] string constant. The sqlite3_libversion() + /// function is provided for use in DLLs since DLL users usually do not have + /// direct access to string constants within the DLL. ^The + /// sqlite3_libversion_number() function returns an integer equal to + /// [SQLITE_VERSION_NUMBER]. ^(The sqlite3_sourceid() function returns + /// a pointer to a string constant whose value is the same as the + /// [SQLITE_SOURCE_ID] C preprocessor macro. Except if SQLite is built + /// using an edited copy of [the amalgamation], then the last four characters + /// of the hash might be different from [SQLITE_SOURCE_ID].)^ + /// + /// See also: [sqlite_version()] and [sqlite_source_id()]. + late final ffi.Pointer> _sqlite3_version = + _dylib.lookup>('sqlite3_version'); + + ffi.Pointer get sqlite3_version => _sqlite3_version.value; + + set sqlite3_version(ffi.Pointer value) => + _sqlite3_version.value = value; + ffi.Pointer sqlite3_libversion() { return (_sqlite3_libversion ??= _dylib.lookupFunction<_c_sqlite3_libversion, _dart_sqlite3_libversion>( @@ -5458,6 +5496,111 @@ class SQLite { _dart_sqlite3_sleep? _sqlite3_sleep; + /// CAPI3REF: Name Of The Folder Holding Temporary Files + /// + /// ^(If this global variable is made to point to a string which is + /// the name of a folder (a.k.a. directory), then all temporary files + /// created by SQLite when using a built-in [sqlite3_vfs | VFS] + /// will be placed in that directory.)^ ^If this variable + /// is a NULL pointer, then SQLite performs a search for an appropriate + /// temporary file directory. + /// + /// Applications are strongly discouraged from using this global variable. + /// It is required to set a temporary folder on Windows Runtime (WinRT). + /// But for all other platforms, it is highly recommended that applications + /// neither read nor write this variable. This global variable is a relic + /// that exists for backwards compatibility of legacy applications and should + /// be avoided in new projects. + /// + /// It is not safe to read or modify this variable in more than one + /// thread at a time. It is not safe to read or modify this variable + /// if a [database connection] is being used at the same time in a separate + /// thread. + /// It is intended that this variable be set once + /// as part of process initialization and before any SQLite interface + /// routines have been called and that this variable remain unchanged + /// thereafter. + /// + /// ^The [temp_store_directory pragma] may modify this variable and cause + /// it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, + /// the [temp_store_directory pragma] always assumes that any string + /// that this variable points to is held in memory obtained from + /// [sqlite3_malloc] and the pragma may attempt to free that memory + /// using [sqlite3_free]. + /// Hence, if this variable is modified directly, either it should be + /// made NULL or made to point to memory obtained from [sqlite3_malloc] + /// or else the use of the [temp_store_directory pragma] should be avoided. + /// Except when requested by the [temp_store_directory pragma], SQLite + /// does not free the memory that sqlite3_temp_directory points to. If + /// the application wants that memory to be freed, it must do + /// so itself, taking care to only do so after all [database connection] + /// objects have been destroyed. + /// + /// Note to Windows Runtime users: The temporary directory must be set + /// prior to calling [sqlite3_open] or [sqlite3_open_v2]. Otherwise, various + /// features that require the use of temporary files may fail. Here is an + /// example of how to do this using C++ with the Windows Runtime: + /// + ///
+  /// LPCWSTR zPath = Windows::Storage::ApplicationData::Current->
+  ///       TemporaryFolder->Path->Data();
+  /// char zPathBuf[MAX_PATH + 1];
+  /// memset(zPathBuf, 0, sizeof(zPathBuf));
+  /// WideCharToMultiByte(CP_UTF8, 0, zPath, -1, zPathBuf, sizeof(zPathBuf),
+  ///       NULL, NULL);
+  /// sqlite3_temp_directory = sqlite3_mprintf("%s", zPathBuf);
+  /// 
+ late final ffi.Pointer> _sqlite3_temp_directory = + _dylib.lookup>('sqlite3_temp_directory'); + + ffi.Pointer get sqlite3_temp_directory => + _sqlite3_temp_directory.value; + + set sqlite3_temp_directory(ffi.Pointer value) => + _sqlite3_temp_directory.value = value; + + /// CAPI3REF: Name Of The Folder Holding Database Files + /// + /// ^(If this global variable is made to point to a string which is + /// the name of a folder (a.k.a. directory), then all database files + /// specified with a relative pathname and created or accessed by + /// SQLite when using a built-in windows [sqlite3_vfs | VFS] will be assumed + /// to be relative to that directory.)^ ^If this variable is a NULL + /// pointer, then SQLite assumes that all database files specified + /// with a relative pathname are relative to the current directory + /// for the process. Only the windows VFS makes use of this global + /// variable; it is ignored by the unix VFS. + /// + /// Changing the value of this variable while a database connection is + /// open can result in a corrupt database. + /// + /// It is not safe to read or modify this variable in more than one + /// thread at a time. It is not safe to read or modify this variable + /// if a [database connection] is being used at the same time in a separate + /// thread. + /// It is intended that this variable be set once + /// as part of process initialization and before any SQLite interface + /// routines have been called and that this variable remain unchanged + /// thereafter. + /// + /// ^The [data_store_directory pragma] may modify this variable and cause + /// it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, + /// the [data_store_directory pragma] always assumes that any string + /// that this variable points to is held in memory obtained from + /// [sqlite3_malloc] and the pragma may attempt to free that memory + /// using [sqlite3_free]. + /// Hence, if this variable is modified directly, either it should be + /// made NULL or made to point to memory obtained from [sqlite3_malloc] + /// or else the use of the [data_store_directory pragma] should be avoided. + late final ffi.Pointer> _sqlite3_data_directory = + _dylib.lookup>('sqlite3_data_directory'); + + ffi.Pointer get sqlite3_data_directory => + _sqlite3_data_directory.value; + + set sqlite3_data_directory(ffi.Pointer value) => + _sqlite3_data_directory.value = value; + /// CAPI3REF: Win32 Specific Interface /// /// These interfaces are available only on Windows. The