Skip to content

Fix crashes when binding or reading blob values #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 13, 2025
Merged
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
5 changes: 5 additions & 0 deletions .changeset/six-moose-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@journeyapps/react-native-quick-sqlite": patch
---

Fix crash when binding or reading blobs.
17 changes: 10 additions & 7 deletions cpp/JSIHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

#include "JSIHelper.h"

#include <utility>

using namespace std;
using namespace facebook;

Expand All @@ -27,7 +29,7 @@ QuickValue createTextQuickValue(string value)
{
return QuickValue{
.dataType = TEXT,
.textValue = value};
.textValue = std::move(value)};
}

QuickValue createIntegerQuickValue(int value)
Expand Down Expand Up @@ -58,12 +60,13 @@ QuickValue createDoubleQuickValue(double value)
.doubleOrIntValue = value};
}

QuickValue createArrayBufferQuickValue(uint8_t *arrayBufferValue, size_t arrayBufferSize)
QuickValue createArrayBufferQuickValueByCopying(const uint8_t *arrayBufferValue, size_t arrayBufferSize)
{
vector<uint8_t> copy(arrayBufferValue, arrayBufferValue + arrayBufferSize);

return QuickValue{
.dataType = ARRAY_BUFFER,
.arrayBufferValue = shared_ptr<uint8_t>{arrayBufferValue},
.arrayBufferSize = arrayBufferSize};
.arrayBuffer = copy};
}

void jsiQueryArgumentsToSequelParam(jsi::Runtime &rt, jsi::Value const &params, vector<QuickValue> *target)
Expand Down Expand Up @@ -116,7 +119,7 @@ void jsiQueryArgumentsToSequelParam(jsi::Runtime &rt, jsi::Value const &params,
if (obj.isArrayBuffer(rt))
{
auto buf = obj.getArrayBuffer(rt);
target->push_back(createArrayBufferQuickValue(buf.data(rt), buf.size(rt)));
target->push_back(createArrayBufferQuickValueByCopying(buf.data(rt), buf.size(rt)));
}
}
else
Expand Down Expand Up @@ -171,10 +174,10 @@ jsi::Value createSequelQueryExecutionResult(jsi::Runtime &rt, SQLiteOPResult sta
} else if (value.dataType == ARRAY_BUFFER)
{
jsi::Function array_buffer_ctor = rt.global().getPropertyAsFunction(rt, "ArrayBuffer");
jsi::Object o = array_buffer_ctor.callAsConstructor(rt, (int) value.arrayBufferSize).getObject(rt);
jsi::Object o = array_buffer_ctor.callAsConstructor(rt, (int) value.arrayBuffer.size()).getObject(rt);
jsi::ArrayBuffer buf = o.getArrayBuffer(rt);
// It's a shame we have to copy here: see https://github.com/facebook/hermes/pull/419 and https://github.com/facebook/hermes/issues/564.
memcpy(buf.data(rt), value.arrayBufferValue.get(), value.arrayBufferSize);
memcpy(buf.data(rt), value.arrayBuffer.data(), value.arrayBuffer.size());
rowObject.setProperty(rt, columnName.c_str(), o);
} else
{
Expand Down
5 changes: 2 additions & 3 deletions cpp/JSIHelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ struct QuickValue
double doubleOrIntValue;
long long int64Value;
string textValue;
shared_ptr<uint8_t> arrayBufferValue;
size_t arrayBufferSize;
vector<uint8_t> arrayBuffer;
};

/**
Expand Down Expand Up @@ -108,7 +107,7 @@ QuickValue createIntegerQuickValue(int value);
QuickValue createIntegerQuickValue(double value);
QuickValue createInt64QuickValue(long long value);
QuickValue createDoubleQuickValue(double value);
QuickValue createArrayBufferQuickValue(uint8_t *arrayBufferValue, size_t arrayBufferSize);
QuickValue createArrayBufferQuickValueByCopying(const uint8_t *arrayBufferValue, size_t arrayBufferSize);
jsi::Value createSequelQueryExecutionResult(jsi::Runtime &rt, SQLiteOPResult status, vector<map<string, QuickValue>> *results, vector<QuickColumnMetadata> *metadata);

#endif /* JSIHelper_h */
10 changes: 4 additions & 6 deletions cpp/sqliteExecute.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ void bindStatement(sqlite3_stmt *statement, vector<QuickValue> *values) {

for (int ii = 0; ii < size; ii++) {
int sqIndex = ii + 1;
QuickValue value = values->at(ii);
const QuickValue& value = values->at(ii);
QuickDataType dataType = value.dataType;
if (dataType == NULL_VALUE) {
sqlite3_bind_null(statement, sqIndex);
Expand All @@ -24,8 +24,8 @@ void bindStatement(sqlite3_stmt *statement, vector<QuickValue> *values) {
sqlite3_bind_text(statement, sqIndex, value.textValue.c_str(),
value.textValue.length(), SQLITE_TRANSIENT);
} else if (dataType == ARRAY_BUFFER) {
sqlite3_bind_blob(statement, sqIndex, value.arrayBufferValue.get(),
value.arrayBufferSize, SQLITE_STATIC);
sqlite3_bind_blob(statement, sqIndex, value.arrayBuffer.data(),
value.arrayBuffer.size(), SQLITE_STATIC);
}
}
}
Expand Down Expand Up @@ -114,9 +114,7 @@ sqliteExecuteWithDB(sqlite3 *db, std::string const &query,
case SQLITE_BLOB: {
int blob_size = sqlite3_column_bytes(statement, i);
const void *blob = sqlite3_column_blob(statement, i);
uint8_t *data;
memcpy(data, blob, blob_size);
row[column_name] = createArrayBufferQuickValue(data, blob_size);
row[column_name] = createArrayBufferQuickValueByCopying(static_cast<const uint8_t*>(blob), blob_size);
break;
}

Expand Down
15 changes: 15 additions & 0 deletions tests/tests/sqlite/rawQueries.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,21 @@ export function registerBaseTests() {
]);
});

it('binding blobs', async () => {
const data = new TextEncoder().encode('hello blob');
const res = await db.execute('SELECT hex(?) AS r', [data.buffer]);

expect(res.rows?._array).to.eql([{ r: '68656C6C6F20626C6F62' }]);
});

it('reading blobs', async () => {
const res = await db.execute('SELECT unhex(1234) AS r');
const value = res.rows._array[0].r;

expect(value).to.be.instanceOf(ArrayBuffer);
expect([...new Uint8Array(value)]).to.eql([18, 52]);
});

it('Failed insert', async () => {
const id = chance.string(); // Setting the id to a string will throw an exception, it expects an int
const { name, age, networth } = generateUserInfo();
Expand Down
Loading