Fixed errors due to extended ASCII and control characters in macros (#106)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2ae8d46..a59311b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 1.0.3
+- Fixed errors due to extended ASCII and control characters in macro strings.
+
# 1.0.2
- Fix indentation for pub's readme.
diff --git a/lib/src/header_parser/sub_parsers/macro_parser.dart b/lib/src/header_parser/sub_parsers/macro_parser.dart
index 8884dd5..16cf4a1 100644
--- a/lib/src/header_parser/sub_parsers/macro_parser.dart
+++ b/lib/src/header_parser/sub_parsers/macro_parser.dart
@@ -4,6 +4,7 @@
import 'dart:ffi';
import 'dart:io';
+import 'dart:typed_data';
import 'package:path/path.dart' as p;
import 'package:ffi/ffi.dart';
@@ -131,17 +132,16 @@
);
break;
case clang_types.CXEvalResultKind.CXEval_StrLiteral:
- var value = Utf8.fromUtf8(clang.clang_EvalResult_getAsStr(e).cast());
- // Escape $ character.
- value = value.replaceAll(r'$', r'\$');
- // Escape ' character, because our strings are enclosed with '.
- value = value.replaceAll("'", r"\'");
+ final rawValue = _getWrittenRepresentation(
+ macroName,
+ clang.clang_EvalResult_getAsStr(e),
+ );
constant = Constant(
usr: savedMacros[macroName].usr,
originalName: savedMacros[macroName].originalName,
name: macroName,
rawType: 'String',
- rawValue: "'${value}'",
+ rawValue: "'${rawValue}'",
);
break;
}
@@ -241,3 +241,84 @@
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<Int8> strPtr) {
+ 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 = Utf8.fromUtf8(strPtr.cast());
+ 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.
+ _logger.warning(
+ "Couldn't decode Macro string '$macroName' as Utf8, using ASCII instead.");
+ sb.clear();
+ final length = Utf8.strlen(strPtr.cast());
+ 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);
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 04ca960..578efe9 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -3,7 +3,7 @@
# BSD-style license that can be found in the LICENSE file.
name: ffigen
-version: 1.0.2
+version: 1.0.3
homepage: https://github.com/dart-lang/ffigen
description: Experimental generator for FFI bindings, using LibClang to parse C header files.
diff --git a/test/header_parser_tests/macros.h b/test/header_parser_tests/macros.h
index d75bcca..e58d4ac 100644
--- a/test/header_parser_tests/macros.h
+++ b/test/header_parser_tests/macros.h
@@ -15,6 +15,11 @@
#define TEST8 5,2,3
-// These tests that special characters are escaped properly.
+// These test that special characters are escaped properly.
#define TEST9 "$dollar"
#define TEST10 "test's"
+
+// These test that extended ASCII and control characters are handled properly.
+#define TEST11 "\x80"
+#define TEST12 "hello\n\t\r\v\b"
+#define TEST13 "test\\"
diff --git a/test/header_parser_tests/macros_test.dart b/test/header_parser_tests/macros_test.dart
index e358873..9b279e7 100644
--- a/test/header_parser_tests/macros_test.dart
+++ b/test/header_parser_tests/macros_test.dart
@@ -72,6 +72,18 @@
expect(actual.getBindingAsString('TEST10'),
expected.getBindingAsString('TEST10'));
});
+ test('TEST11', () {
+ expect(actual.getBindingAsString('TEST11'),
+ expected.getBindingAsString('TEST11'));
+ });
+ test('TEST12', () {
+ expect(actual.getBindingAsString('TEST12'),
+ expected.getBindingAsString('TEST12'));
+ });
+ test('TEST13', () {
+ expect(actual.getBindingAsString('TEST13'),
+ expected.getBindingAsString('TEST13'));
+ });
});
}
@@ -88,6 +100,10 @@
Constant(name: 'TEST8', rawType: 'int', rawValue: '5'),
Constant(name: 'TEST9', rawType: 'String', rawValue: r"'\$dollar'"),
Constant(name: 'TEST10', rawType: 'String', rawValue: r"'test\'s'"),
+ Constant(name: 'TEST11', rawType: 'String', rawValue: r"'\x80'"),
+ Constant(
+ name: 'TEST12', rawType: 'String', rawValue: r"'hello\n\t\r\v\b'"),
+ Constant(name: 'TEST13', rawType: 'String', rawValue: r"'test\\'"),
],
);
}