Skip to content

Commit 5765044

Browse files
authored
[ffigen] Generate Dart constants for static const C variables (#2854)
1 parent 21b2bf7 commit 5765044

File tree

8 files changed

+488
-111
lines changed

8 files changed

+488
-111
lines changed

pkgs/ffigen/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99
- __Breaking change__: Certain synthetic USRs have been modified to ensure they
1010
cannot collide with real USRs. It's very unlikely that any user facing USRs
1111
are affected.
12+
- __Breaking change__: Dart const values will be generated for global variables
13+
marked const in C (e.g. static const int) instead of symbol lookups. This
14+
supports integers, doubles, and string literals. Including the variable name
15+
in the globals -> symbol-address configuration will still generate symbol
16+
lookups.
17+
1218

1319
## 20.1.1
1420

pkgs/ffigen/lib/src/header_parser/sub_parsers/macro_parser.dart

Lines changed: 2 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
import 'dart:ffi';
66
import 'dart:io';
7-
import 'dart:typed_data';
87

98
import 'package:ffi/ffi.dart';
109
import 'package:logging/logging.dart';
@@ -130,13 +129,13 @@ void _macroVariablevisitor(
130129
originalName: context.savedMacros[macroName]!.originalName,
131130
name: macroName,
132131
rawType: 'double',
133-
rawValue: _writeDoubleAsString(
132+
rawValue: writeDoubleAsString(
134133
clang.clang_EvalResult_getAsDouble(e),
135134
),
136135
);
137136
break;
138137
case clang_types.CXEvalResultKind.CXEval_StrLiteral:
139-
final rawValue = _getWrittenRepresentation(
138+
final rawValue = getWrittenStringRepresentation(
140139
macroName,
141140
clang.clang_EvalResult_getAsStr(e),
142141
context,
@@ -243,108 +242,3 @@ class MacroVariableString {
243242
return s.substring(nameStart, nameStart + len);
244243
}
245244
}
246-
247-
/// Gets a written representation string of a C string.
248-
///
249-
/// E.g- For a string "Hello\nWorld", The new line character is converted to \n.
250-
/// Note: The string is considered to be Utf8, but is treated as Extended ASCII,
251-
/// if the conversion fails.
252-
String _getWrittenRepresentation(
253-
String macroName,
254-
Pointer<Char> strPtr,
255-
Context context,
256-
) {
257-
final sb = StringBuffer();
258-
try {
259-
// Consider string to be Utf8 encoded by default.
260-
sb.clear();
261-
// This throws a Format Exception if string isn't Utf8 so that we handle it
262-
// in the catch block.
263-
final result = strPtr.cast<Utf8>().toDartString();
264-
for (final s in result.runes) {
265-
sb.write(_getWritableChar(s));
266-
}
267-
} catch (e) {
268-
// Handle string if it isn't Utf8. String is considered to be
269-
// Extended ASCII in this case.
270-
context.logger.warning(
271-
"Couldn't decode Macro string '$macroName' as Utf8, using "
272-
'ASCII instead.',
273-
);
274-
sb.clear();
275-
final length = strPtr.cast<Utf8>().length;
276-
final charList = Uint8List.view(
277-
strPtr.cast<Uint8>().asTypedList(length).buffer,
278-
0,
279-
length,
280-
);
281-
282-
for (final char in charList) {
283-
sb.write(_getWritableChar(char, utf8: false));
284-
}
285-
}
286-
287-
return sb.toString();
288-
}
289-
290-
/// Creates a writable char from [char] code.
291-
///
292-
/// E.g- `\` is converted to `\\`.
293-
String _getWritableChar(int char, {bool utf8 = true}) {
294-
/// Handle control characters.
295-
if (char >= 0 && char < 32 || char == 127) {
296-
/// Handle these - `\b \t \n \v \f \r` as special cases.
297-
switch (char) {
298-
case 8: // \b
299-
return r'\b';
300-
case 9: // \t
301-
return r'\t';
302-
case 10: // \n
303-
return r'\n';
304-
case 11: // \v
305-
return r'\v';
306-
case 12: // \f
307-
return r'\f';
308-
case 13: // \r
309-
return r'\r';
310-
default:
311-
final h = char.toRadixString(16).toUpperCase().padLeft(2, '0');
312-
return '\\x$h';
313-
}
314-
}
315-
316-
/// Handle characters - `$ ' \` these need to be escaped when writing to file.
317-
switch (char) {
318-
case 36: // $
319-
return r'\$';
320-
case 39: // '
321-
return r"\'";
322-
case 92: // \
323-
return r'\\';
324-
}
325-
326-
/// In case encoding is not Utf8, we know all characters will fall in [0..255]
327-
/// Print range [128..255] as `\xHH`.
328-
if (!utf8) {
329-
final h = char.toRadixString(16).toUpperCase().padLeft(2, '0');
330-
return '\\x$h';
331-
}
332-
333-
/// In all other cases, simply convert to string.
334-
return String.fromCharCode(char);
335-
}
336-
337-
/// Converts a double to a string, handling cases like Infinity and NaN.
338-
String _writeDoubleAsString(double d) {
339-
if (d.isFinite) {
340-
return d.toString();
341-
} else {
342-
// The only Non-Finite numbers are Infinity, NegativeInfinity and NaN.
343-
if (d.isInfinite) {
344-
return d.isNegative
345-
? strings.doubleNegativeInfinity
346-
: strings.doubleInfinity;
347-
}
348-
return strings.doubleNaN;
349-
}
350-
}

pkgs/ffigen/lib/src/header_parser/sub_parsers/var_parser.dart

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,79 @@ import '../clang_bindings/clang_bindings.dart' as clang_types;
1010
import '../utils.dart';
1111

1212
/// Parses a global variable
13-
Global? parseVarDeclaration(Context context, clang_types.CXCursor cursor) {
13+
Binding? parseVarDeclaration(Context context, clang_types.CXCursor cursor) {
1414
final logger = context.logger;
1515
final config = context.config;
1616
final nativeOutputStyle = config.output.style is NativeExternalBindings;
1717
final bindingsIndex = context.bindingsIndex;
1818
final name = cursor.spelling();
1919
final usr = cursor.usr();
20+
2021
if (bindingsIndex.isSeenGlobalVar(usr)) {
2122
return bindingsIndex.getSeenGlobalVar(usr);
2223
}
24+
if (bindingsIndex.isSeenVariableConstant(usr)) {
25+
return bindingsIndex.getSeenVariableConstant(usr);
26+
}
27+
2328
final decl = Declaration(usr: usr, originalName: name);
29+
final cType = cursor.type();
2430

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

27-
final cType = cursor.type();
38+
switch (evalKind) {
39+
case clang_types.CXEvalResultKind.CXEval_Int:
40+
final value = clang.clang_EvalResult_getAsLongLong(evalResult);
41+
constant = Constant(
42+
usr: usr,
43+
originalName: name,
44+
name: config.globals.rename(decl),
45+
dartDoc: getCursorDocComment(context, cursor),
46+
rawType: 'int',
47+
rawValue: value.toString(),
48+
);
49+
break;
50+
case clang_types.CXEvalResultKind.CXEval_Float:
51+
final value = clang.clang_EvalResult_getAsDouble(evalResult);
52+
constant = Constant(
53+
usr: usr,
54+
originalName: name,
55+
name: config.globals.rename(decl),
56+
dartDoc: getCursorDocComment(context, cursor),
57+
rawType: 'double',
58+
rawValue: writeDoubleAsString(value),
59+
);
60+
break;
61+
case clang_types.CXEvalResultKind.CXEval_StrLiteral:
62+
final value = clang.clang_EvalResult_getAsStr(evalResult);
63+
final rawValue = getWrittenStringRepresentation(name, value, context);
64+
constant = Constant(
65+
usr: usr,
66+
originalName: name,
67+
name: config.globals.rename(decl),
68+
dartDoc: getCursorDocComment(context, cursor),
69+
rawType: 'String',
70+
rawValue: "'$rawValue'",
71+
);
72+
break;
73+
}
74+
clang.clang_EvalResult_dispose(evalResult);
75+
76+
if (constant != null) {
77+
logger.fine(
78+
'++++ Adding Constant from Global: ${cursor.completeStringRepr()}',
79+
);
80+
bindingsIndex.addVariableConstantToSeen(usr, constant);
81+
return constant;
82+
}
83+
}
84+
85+
logger.fine('++++ Adding Global: ${cursor.completeStringRepr()}');
2886

2987
final type = cType.toCodeGenType(
3088
context,

pkgs/ffigen/lib/src/header_parser/utils.dart

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// BSD-style license that can be found in the LICENSE file.
44

55
import 'dart:ffi';
6+
import 'dart:typed_data';
67

78
import 'package:ffi/ffi.dart';
89
import 'package:logging/logging.dart';
@@ -11,6 +12,7 @@ import '../code_generator.dart';
1112
import '../config_provider/config_types.dart';
1213
import '../context.dart';
1314
import '../strings.dart';
15+
import '../strings.dart' as strings;
1416
import 'clang_bindings/clang_bindings.dart' as clang_types;
1517
import 'type_extractor/extractor.dart';
1618

@@ -491,6 +493,7 @@ class BindingsIndex {
491493
final Map<String, Constant> _unnamedEnumConstants = {};
492494
final Map<String, String> _macros = {};
493495
final Map<String, Global> _globals = {};
496+
final Map<String, Constant> _variableConstants = {};
494497
final Map<String, Typealias> _typealiases = {};
495498
final Map<String, EnumClass> _enums = {};
496499
final Map<String, Compound> _compounds = {};
@@ -512,6 +515,11 @@ class BindingsIndex {
512515
bool isSeenGlobalVar(String usr) => _globals.containsKey(usr);
513516
void addGlobalVarToSeen(String usr, Global global) => _globals[usr] = global;
514517
Global? getSeenGlobalVar(String usr) => _globals[usr];
518+
bool isSeenVariableConstant(String usr) =>
519+
_variableConstants.containsKey(usr);
520+
void addVariableConstantToSeen(String usr, Constant constant) =>
521+
_variableConstants[usr] = constant;
522+
Constant? getSeenVariableConstant(String usr) => _variableConstants[usr];
515523
bool isSeenTypealias(String usr) => _typealiases.containsKey(usr);
516524
void addTypealiasToSeen(String usr, Typealias t) => _typealiases[usr] = t;
517525
Typealias? getSeenTypealias(String usr) => _typealiases[usr];
@@ -589,3 +597,108 @@ class CursorIndex {
589597
}
590598
}
591599
}
600+
601+
/// Converts a double to a string, handling cases like Infinity and NaN.
602+
String writeDoubleAsString(double d) {
603+
if (d.isFinite) {
604+
return d.toString();
605+
} else {
606+
// The only Non-Finite numbers are Infinity, NegativeInfinity and NaN.
607+
if (d.isInfinite) {
608+
return d.isNegative
609+
? strings.doubleNegativeInfinity
610+
: strings.doubleInfinity;
611+
}
612+
return strings.doubleNaN;
613+
}
614+
}
615+
616+
/// Gets a written representation string of a C string.
617+
///
618+
/// E.g- For a string "Hello\nWorld", The new line character is converted to \n.
619+
/// Note: The string is considered to be Utf8, but is treated as Extended ASCII,
620+
/// if the conversion fails.
621+
String getWrittenStringRepresentation(
622+
String varName,
623+
Pointer<Char> strPtr,
624+
Context context,
625+
) {
626+
final sb = StringBuffer();
627+
try {
628+
// Consider string to be Utf8 encoded by default.
629+
sb.clear();
630+
// This throws a Format Exception if string isn't Utf8 so that we handle it
631+
// in the catch block.
632+
final result = strPtr.cast<Utf8>().toDartString();
633+
for (final s in result.runes) {
634+
sb.write(_getWritableChar(s));
635+
}
636+
} catch (e) {
637+
// Handle string if it isn't Utf8. String is considered to be
638+
// Extended ASCII in this case.
639+
context.logger.warning(
640+
"Couldn't decode string value for '$varName' as Utf8, using "
641+
'ASCII instead.',
642+
);
643+
sb.clear();
644+
final length = strPtr.cast<Utf8>().length;
645+
final charList = Uint8List.view(
646+
strPtr.cast<Uint8>().asTypedList(length).buffer,
647+
0,
648+
length,
649+
);
650+
651+
for (final char in charList) {
652+
sb.write(_getWritableChar(char, utf8: false));
653+
}
654+
}
655+
656+
return sb.toString();
657+
}
658+
659+
/// Creates a writable char from [char] code.
660+
///
661+
/// E.g- `\` is converted to `\\`.
662+
String _getWritableChar(int char, {bool utf8 = true}) {
663+
/// Handle control characters.
664+
if (char >= 0 && char < 32 || char == 127) {
665+
/// Handle these - `\b \t \n \v \f \r` as special cases.
666+
switch (char) {
667+
case 8: // \b
668+
return r'\b';
669+
case 9: // \t
670+
return r'\t';
671+
case 10: // \n
672+
return r'\n';
673+
case 11: // \v
674+
return r'\v';
675+
case 12: // \f
676+
return r'\f';
677+
case 13: // \r
678+
return r'\r';
679+
default:
680+
final h = char.toRadixString(16).toUpperCase().padLeft(2, '0');
681+
return '\\x$h';
682+
}
683+
}
684+
685+
/// Handle characters - `$ ' \` these need to be escaped when writing to file.
686+
switch (char) {
687+
case 36: // $
688+
return r'\$';
689+
case 39: // '
690+
return r"\'";
691+
case 92: // \
692+
return r'\\';
693+
}
694+
695+
/// In case encoding is not Utf8, we know all characters will fall in [0..255]
696+
/// Print range [128..255] as `\xHH`.
697+
if (!utf8) {
698+
final h = char.toRadixString(16).toUpperCase().padLeft(2, '0');
699+
return '\\x$h';
700+
}
701+
702+
/// In all other cases, simply convert to string.
703+
return String.fromCharCode(char);
704+
}

pkgs/ffigen/lib/src/visitor/apply_config_filters.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ class ApplyConfigFiltersVisitation extends Visitation {
9191
@override
9292
void visitGlobal(Global node) => _visitImpl(node, config.globals);
9393

94+
@override
95+
void visitConstant(Constant node) {
96+
// MacroConstant and UnnamedEnumConstant have their own overrides, so this
97+
// only applies to base Constants (e.g. from static const variables).
98+
_visitImpl(node, config.globals);
99+
}
100+
94101
@override
95102
void visitTypealias(Typealias node) => _visitImpl(node, config.typedefs);
96103
}

0 commit comments

Comments
 (0)