We were seeing occasional crashes/SEGFAULTs from inside this ffi code (esp. on iOS/macOS). To combat this, I took a pass at fixing up the use-after-free bugs and memory leaks, along with some other correctness-focused cleanups.
Bug fixes:
Fix use-after-free in encodeResult. The fn was returning a pointer in a dropped std::vector<int8_t> via result.data = ToMatrix<int8_t>(bitMatrix).data().
Fix use-after-free bug in resultToCodeResult which was returning a pointer into a dropped ByteArray via code->bytes = result.bytes().data().
Fix memory leaks from Dart not freeing various owned pointers returned from the native code.
Correctness-focused cleanups:
Avoid zero-sized allocations, since they're semi-undefined and not portable.
Avoid ABI-dependent types for buffers, with the exception of C-strings (char*).
ffigen supports bools, so use that for bool things.
Use std::chrono::steady_clock and not std::chrono::system_clock to measure durations. steady_clock is monotonic, so taking durations will always return a positive value, even if the system clock changes.
Note for reviewer
This PR is easiest to review by-commit.
Example use-after-free
Use-after-free's are nasty as they only sometimes crash the app.
For an example of this bug, let's check out the use-after-free in native_zxing.cpp::encodeBarcode:
Here we're returning a pointer to the data backing a std::vector<int8_t>, which is dropped before the function scope returns. Across the FFI boundary, when dart tries to access the data behind this pointer, the runtime may SEGFAULT since this memory has already been freed and is no longer valid.
The fix here is to copy the data out of the std::vector for Dart to free once it's done with it. Sadly there doesn't seem to be any easy way to intentionally stop a std::vector from running its destructor, so we have to copy.
auto matrix = ToMatrix<int8_t>(bitMatrix);
result.data = new int8_t[matrix.size()];
std::copy(matrix.begin(), matrix.end(), result.data);
Example memory leak
Memory leaks are definitely less problematic since it won't crash the app, but leaking image buffers definitely adds up.
In this example, we're allocating an owned char* text but nobody ever frees it.
void resultToCodeResult(struct CodeResult *code, Result result)
{
string text = result.text();
code->text = new char[text.length() + 1]; // <<<<<<< allocation
strcpy(code->text, text.c_str());
// ...
}
extension CodeExt on CodeResult {
Code toCode() {
return Code(
// but no free
// vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
text: text == nullptr ? null : text.cast<Utf8>().toDartString(),
// ...
);
}
}
Overview
We were seeing occasional crashes/SEGFAULTs from inside this ffi code (esp. on iOS/macOS). To combat this, I took a pass at fixing up the use-after-free bugs and memory leaks, along with some other correctness-focused cleanups.
Bug fixes:
encodeResult
. The fn was returning a pointer in a droppedstd::vector<int8_t>
viaresult.data = ToMatrix<int8_t>(bitMatrix).data()
.resultToCodeResult
which was returning a pointer into a droppedByteArray
viacode->bytes = result.bytes().data()
.Correctness-focused cleanups:
char*
).ffigen
supportsbool
s, so use that for bool things.std::chrono::steady_clock
and notstd::chrono::system_clock
to measure durations.steady_clock
is monotonic, so taking durations will always return a positive value, even if the system clock changes.Note for reviewer
This PR is easiest to review by-commit.
Example use-after-free
Use-after-free's are nasty as they only sometimes crash the app.
For an example of this bug, let's check out the use-after-free in
native_zxing.cpp::encodeBarcode
:Here we're returning a pointer to the data backing a
std::vector<int8_t>
, which is dropped before the function scope returns. Across the FFI boundary, when dart tries to access the data behind this pointer, the runtime may SEGFAULT since this memory has already been freed and is no longer valid.The fix here is to copy the data out of the
std::vector
for Dart to free once it's done with it. Sadly there doesn't seem to be any easy way to intentionally stop astd::vector
from running its destructor, so we have to copy.Example memory leak
Memory leaks are definitely less problematic since it won't crash the app, but leaking image buffers definitely adds up.
In this example, we're allocating an owned
char* text
but nobody ever frees it.