ffigen is unable to locate system headers
- Dominant language
- Dart
- Stars
- 275
- Forks
- 144
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 47
Description
My understanding is that `ffigen` should use the same system include path as `clang`, but when I run `ffigen` on Linux it is unable to find several system header files (`stdarg.h`, `stdbool.h`, etc.) while `clang` and `gcc` are able to locate them and compile code perfectly fine.
This was originally reported as https://github.com/fzyzcjy/flutter_rust_bridge/issues/108, but after some investigation we found the underlying bug was in `ffigen`. This may also be related to https://github.com/dart-lang/native/issues/345.
Steps to reproduce:
```console
$ dart --version
Dart SDK version: 2.14.4 (stable) (Wed Oct 13 11:11:32 2021 +0200) on "linux_x64"
$ dart pub global list
ffigen 4.1.1
$ clang --version
clang version 12.0.1
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
$ mkdir /tmp/repro && cd "$_"
$ cat << EOF > header.h
#include
#include
bool less_than(int32_t a, int32_t b);
EOF
$ clang header.h # clang can typecheck the header just fine
$ echo $?
0
$ clang header.h -v # the folders clang searches for #include <...>
...
#include <...> search starts here:
/usr/local/include
/usr/lib/clang/12.0.1/include
/usr/include
End of search list.
$ cat << EOF > config.yml
name: LessThan
description: Check if one number is less than the other
output: "generated_bindings.dart"
headers:
entry-points:
- "header.h"
include-directives:
- "header.h"
EOF
$ dart pub global run ffigen --config config.yml
Running in Directory: '/tmp/repro'
Input Headers: [header.h]
[SEVERE] : Header header.h: Total errors/warnings: 1.
[SEVERE] : header.h:2:10: fatal error: 'stdbool.h' file not found [Lexical or Preprocessor Issue]
Finished, Bindings generated in /tmp/repro/generated_bindings.dart
$ echo $? # ffigen completed successfully, apparently 🤷
0
# We need to pass extra compiler options to find clang's include files
$ dart pub global run ffigen --config config.yml --compiler-opts '-I/usr/lib/clang/12.0.1/include/'
Running in Directory: '/tmp/repro'
Input Headers: [header.h]
Finished, Bindings generated in /tmp/repro/generated_bindings.dart
```
Interestingly, if I run `ffigen` from `/` (requires replacing the `output` field with its absolute path), the header files are located correctly.
```console
$ cd /
$ grep output /tmp/repro/config.yml
output: "/tmp/repro/generated_bindings.dart"
$ dart pub global run ffigen --config /tmp/repro/config.yml
Running in Directory: '/'
Input Headers: []
Finished, Bindings generated in /tmp/repro/generated_bindings.dart
```
The generated bindings without extra compiler options:
```dart
// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.
import 'dart:ffi' as ffi;
/// Check if one number is less than the other
class LessThan {
/// Holds the symbol lookup function.
final ffi.Pointer Function(String symbolName)
_lookup;
/// The symbols are looked up in [dynamicLibrary].
LessThan(ffi.DynamicLibrary dynamicLibrary) : _lookup = dynamicLibrary.lookup;
/// The symbols are looked up with [lookup].
LessThan.fromLookup(
ffi.Pointer Function(String symbolName)
lookup)
: _lookup = lookup;
int less_than(
int a,
int b,
) {
return _less_than(
a,
b,
);
}
late final _less_thanPtr =
_lookup>(
'less_than');
late final _less_than = _less_thanPtr.asFunction();
}
```
The code generated *with* extra compiler options:
```dart
// AUTO GENERATED FILE, DO NOT EDIT.
//
// Generated by `package:ffigen`.
import 'dart:ffi' as ffi;
/// Check if one number is less than the other
class LessThan {
/// Holds the symbol lookup function.
final ffi.Pointer Function(String symbolName)
_lookup;
/// The symbols are looked up in [dynamicLibrary].
LessThan(ffi.DynamicLibrary dynamicLibrary) : _lookup = dynamicLibrary.lookup;
/// The symbols are looked up with [lookup].
LessThan.fromLookup(
ffi.Pointer Function(String symbolName)
lookup)
: _lookup = lookup;
bool less_than(
int a,
int b,
) {
return _less_than(
a,
b,
) !=
0;
}
late final _less_thanPtr =
_lookup>(
'less_than');
late final _less_than = _less_thanPtr.asFunction();
}
```
As a side note, whenever this happens `ffigen` falls back to using `int` for any types it is unable to locate instead of erroring out (i.e. returning a non-zero exit code), meaning the only way for a build tool to detect these errors is by doing a hacky `string.contains()` on the output.
Instead of falling back to `int`, `ffigen` should omit a function entirely when it contains a type that can't be resolved. Unless the caller gets lucky and the function actually does return something that is compatible with `ffi.Int32`, any attempt to use the generated wrapper is going to result in broken code or UB.
Contributor guide
Assessment
This issue has not been assessed yet.