dart-lang / dart-lang/native

ffigen is unable to locate system headers

Open
#338 6 comments 12 reactions 0 assignees View on GitHub
package:ffigen
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.