Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
- __Breaking change__: Certain synthetic USRs have been modified to ensure they
cannot collide with real USRs. It's very unlikely that any user facing USRs
are affected.
- __Breaking change__: Dart const values will be generated for global variables
marked const in C (e.g. static const int) instead of symbol lookups. This
supports integers, doubles, and string literals. Including the variable name
in the globals -> symbol-address configuration will still generate symbol
lookups.


## 20.1.1

Expand Down
110 changes: 2 additions & 108 deletions pkgs/ffigen/lib/src/header_parser/sub_parsers/macro_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import 'dart:ffi';
import 'dart:io';
import 'dart:typed_data';

import 'package:ffi/ffi.dart';
import 'package:logging/logging.dart';
Expand Down Expand Up @@ -130,13 +129,13 @@ void _macroVariablevisitor(
originalName: context.savedMacros[macroName]!.originalName,
name: macroName,
rawType: 'double',
rawValue: _writeDoubleAsString(
rawValue: writeDoubleAsString(
clang.clang_EvalResult_getAsDouble(e),
),
);
break;
case clang_types.CXEvalResultKind.CXEval_StrLiteral:
final rawValue = _getWrittenRepresentation(
final rawValue = getWrittenStringRepresentation(
macroName,
clang.clang_EvalResult_getAsStr(e),
context,
Expand Down Expand Up @@ -243,108 +242,3 @@ class MacroVariableString {
return s.substring(nameStart, nameStart + len);
}
}

/// Gets a written representation string of a C string.
///
/// E.g- For a string "Hello\nWorld", The new line character is converted to \n.
/// Note: The string is considered to be Utf8, but is treated as Extended ASCII,
/// if the conversion fails.
String _getWrittenRepresentation(
String macroName,
Pointer<Char> strPtr,
Context context,
) {
final sb = StringBuffer();
try {
// Consider string to be Utf8 encoded by default.
sb.clear();
// This throws a Format Exception if string isn't Utf8 so that we handle it
// in the catch block.
final result = strPtr.cast<Utf8>().toDartString();
for (final s in result.runes) {
sb.write(_getWritableChar(s));
}
} catch (e) {
// Handle string if it isn't Utf8. String is considered to be
// Extended ASCII in this case.
context.logger.warning(
"Couldn't decode Macro string '$macroName' as Utf8, using "
'ASCII instead.',
);
sb.clear();
final length = strPtr.cast<Utf8>().length;
final charList = Uint8List.view(
strPtr.cast<Uint8>().asTypedList(length).buffer,
0,
length,
);

for (final char in charList) {
sb.write(_getWritableChar(char, utf8: false));
}
}

return sb.toString();
}

/// Creates a writable char from [char] code.
///
/// E.g- `\` is converted to `\\`.
String _getWritableChar(int char, {bool utf8 = true}) {
/// Handle control characters.
if (char >= 0 && char < 32 || char == 127) {
/// Handle these - `\b \t \n \v \f \r` as special cases.
switch (char) {
case 8: // \b
return r'\b';
case 9: // \t
return r'\t';
case 10: // \n
return r'\n';
case 11: // \v
return r'\v';
case 12: // \f
return r'\f';
case 13: // \r
return r'\r';
default:
final h = char.toRadixString(16).toUpperCase().padLeft(2, '0');
return '\\x$h';
}
}

/// Handle characters - `$ ' \` these need to be escaped when writing to file.
switch (char) {
case 36: // $
return r'\$';
case 39: // '
return r"\'";
case 92: // \
return r'\\';
}

/// In case encoding is not Utf8, we know all characters will fall in [0..255]
/// Print range [128..255] as `\xHH`.
if (!utf8) {
final h = char.toRadixString(16).toUpperCase().padLeft(2, '0');
return '\\x$h';
}

/// In all other cases, simply convert to string.
return String.fromCharCode(char);
}

/// Converts a double to a string, handling cases like Infinity and NaN.
String _writeDoubleAsString(double d) {
if (d.isFinite) {
return d.toString();
} else {
// The only Non-Finite numbers are Infinity, NegativeInfinity and NaN.
if (d.isInfinite) {
return d.isNegative
? strings.doubleNegativeInfinity
: strings.doubleInfinity;
}
return strings.doubleNaN;
}
}
64 changes: 61 additions & 3 deletions pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,79 @@ import '../clang_bindings/clang_bindings.dart' as clang_types;
import '../utils.dart';

/// Parses a global variable
Global? parseVarDeclaration(Context context, clang_types.CXCursor cursor) {
Binding? parseVarDeclaration(Context context, clang_types.CXCursor cursor) {
final logger = context.logger;
final config = context.config;
final nativeOutputStyle = config.output.style is NativeExternalBindings;
final bindingsIndex = context.bindingsIndex;
final name = cursor.spelling();
final usr = cursor.usr();

if (bindingsIndex.isSeenGlobalVar(usr)) {
return bindingsIndex.getSeenGlobalVar(usr);
}
if (bindingsIndex.isSeenVariableConstant(usr)) {
return bindingsIndex.getSeenVariableConstant(usr);
}

final decl = Declaration(usr: usr, originalName: name);
final cType = cursor.type();

logger.fine('++++ Adding Global: ${cursor.completeStringRepr()}');
// Try to evaluate as a constant first,
// unless the config asks for the variable's address.
if (cType.isConstQualified && !config.globals.includeSymbolAddress(decl)) {
final evalResult = clang.clang_Cursor_Evaluate(cursor);
final evalKind = clang.clang_EvalResult_getKind(evalResult);
Constant? constant;

final cType = cursor.type();
switch (evalKind) {
case clang_types.CXEvalResultKind.CXEval_Int:
final value = clang.clang_EvalResult_getAsLongLong(evalResult);
constant = Constant(
usr: usr,
originalName: name,
name: config.globals.rename(decl),
dartDoc: getCursorDocComment(context, cursor),
rawType: 'int',
rawValue: value.toString(),
);
break;
case clang_types.CXEvalResultKind.CXEval_Float:
final value = clang.clang_EvalResult_getAsDouble(evalResult);
constant = Constant(
usr: usr,
originalName: name,
name: config.globals.rename(decl),
dartDoc: getCursorDocComment(context, cursor),
rawType: 'double',
rawValue: writeDoubleAsString(value),
);
break;
case clang_types.CXEvalResultKind.CXEval_StrLiteral:
final value = clang.clang_EvalResult_getAsStr(evalResult);
final rawValue = getWrittenStringRepresentation(name, value, context);
constant = Constant(
usr: usr,
originalName: name,
name: config.globals.rename(decl),
dartDoc: getCursorDocComment(context, cursor),
rawType: 'String',
rawValue: "'$rawValue'",
);
break;
}
clang.clang_EvalResult_dispose(evalResult);

if (constant != null) {
logger.fine(
'++++ Adding Constant from Global: ${cursor.completeStringRepr()}',
);
bindingsIndex.addVariableConstantToSeen(usr, constant);
return constant;
}
}

logger.fine('++++ Adding Global: ${cursor.completeStringRepr()}');

final type = cType.toCodeGenType(
context,
Expand Down
113 changes: 113 additions & 0 deletions pkgs/ffigen/lib/src/header_parser/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.

import 'dart:ffi';
import 'dart:typed_data';

import 'package:ffi/ffi.dart';
import 'package:logging/logging.dart';
Expand All @@ -11,6 +12,7 @@ import '../code_generator.dart';
import '../config_provider/config_types.dart';
import '../context.dart';
import '../strings.dart';
import '../strings.dart' as strings;
import 'clang_bindings/clang_bindings.dart' as clang_types;
import 'type_extractor/extractor.dart';

Expand Down Expand Up @@ -491,6 +493,7 @@ class BindingsIndex {
final Map<String, Constant> _unnamedEnumConstants = {};
final Map<String, String> _macros = {};
final Map<String, Global> _globals = {};
final Map<String, Constant> _variableConstants = {};
final Map<String, Typealias> _typealiases = {};
final Map<String, EnumClass> _enums = {};
final Map<String, Compound> _compounds = {};
Expand All @@ -512,6 +515,11 @@ class BindingsIndex {
bool isSeenGlobalVar(String usr) => _globals.containsKey(usr);
void addGlobalVarToSeen(String usr, Global global) => _globals[usr] = global;
Global? getSeenGlobalVar(String usr) => _globals[usr];
bool isSeenVariableConstant(String usr) =>
_variableConstants.containsKey(usr);
void addVariableConstantToSeen(String usr, Constant constant) =>
_variableConstants[usr] = constant;
Constant? getSeenVariableConstant(String usr) => _variableConstants[usr];
bool isSeenTypealias(String usr) => _typealiases.containsKey(usr);
void addTypealiasToSeen(String usr, Typealias t) => _typealiases[usr] = t;
Typealias? getSeenTypealias(String usr) => _typealiases[usr];
Expand Down Expand Up @@ -589,3 +597,108 @@ class CursorIndex {
}
}
}

/// Converts a double to a string, handling cases like Infinity and NaN.
String writeDoubleAsString(double d) {
if (d.isFinite) {
return d.toString();
} else {
// The only Non-Finite numbers are Infinity, NegativeInfinity and NaN.
if (d.isInfinite) {
return d.isNegative
? strings.doubleNegativeInfinity
: strings.doubleInfinity;
}
return strings.doubleNaN;
}
}

/// Gets a written representation string of a C string.
///
/// E.g- For a string "Hello\nWorld", The new line character is converted to \n.
/// Note: The string is considered to be Utf8, but is treated as Extended ASCII,
/// if the conversion fails.
String getWrittenStringRepresentation(
String varName,
Pointer<Char> strPtr,
Context context,
) {
final sb = StringBuffer();
try {
// Consider string to be Utf8 encoded by default.
sb.clear();
// This throws a Format Exception if string isn't Utf8 so that we handle it
// in the catch block.
final result = strPtr.cast<Utf8>().toDartString();
for (final s in result.runes) {
sb.write(_getWritableChar(s));
}
} catch (e) {
// Handle string if it isn't Utf8. String is considered to be
// Extended ASCII in this case.
context.logger.warning(
"Couldn't decode string value for '$varName' as Utf8, using "
'ASCII instead.',
);
sb.clear();
final length = strPtr.cast<Utf8>().length;
final charList = Uint8List.view(
strPtr.cast<Uint8>().asTypedList(length).buffer,
0,
length,
);

for (final char in charList) {
sb.write(_getWritableChar(char, utf8: false));
}
}

return sb.toString();
}

/// Creates a writable char from [char] code.
///
/// E.g- `\` is converted to `\\`.
String _getWritableChar(int char, {bool utf8 = true}) {
/// Handle control characters.
if (char >= 0 && char < 32 || char == 127) {
/// Handle these - `\b \t \n \v \f \r` as special cases.
switch (char) {
case 8: // \b
return r'\b';
case 9: // \t
return r'\t';
case 10: // \n
return r'\n';
case 11: // \v
return r'\v';
case 12: // \f
return r'\f';
case 13: // \r
return r'\r';
default:
final h = char.toRadixString(16).toUpperCase().padLeft(2, '0');
return '\\x$h';
}
}

/// Handle characters - `$ ' \` these need to be escaped when writing to file.
switch (char) {
case 36: // $
return r'\$';
case 39: // '
return r"\'";
case 92: // \
return r'\\';
}

/// In case encoding is not Utf8, we know all characters will fall in [0..255]
/// Print range [128..255] as `\xHH`.
if (!utf8) {
final h = char.toRadixString(16).toUpperCase().padLeft(2, '0');
return '\\x$h';
}

/// In all other cases, simply convert to string.
return String.fromCharCode(char);
}
7 changes: 7 additions & 0 deletions pkgs/ffigen/lib/src/visitor/apply_config_filters.dart
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,13 @@ class ApplyConfigFiltersVisitation extends Visitation {
@override
void visitGlobal(Global node) => _visitImpl(node, config.globals);

@override
void visitConstant(Constant node) {
// MacroConstant and UnnamedEnumConstant have their own overrides, so this
// only applies to base Constants (e.g. from static const variables).
_visitImpl(node, config.globals);
}

@override
void visitTypealias(Typealias node) => _visitImpl(node, config.typedefs);
}
Loading