Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions pkgs/ffigen/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- __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