Skip to content

Commit 43b7af5

Browse files
committed
Replace shim's printf with a SQLite-compatible formatter
pdo_sqlite's quote() calls sqlite3_mprintf("'%q'", ...). Our libc- backed shim didn't know about %q / %Q / %w, so glibc's vsnprintf produced garbage like 'DEFAULT '%' (the root cause of 343/496 test errors). Replace the shim's printf family with a small parser that handles SQLite's extensions explicitly and delegates standard specifiers to libc snprintf one-at-a-time.
1 parent 59d0225 commit 43b7af5

1 file changed

Lines changed: 160 additions & 26 deletions

File tree

.github/workflows/phpunit-tests-turso.yml

Lines changed: 160 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -181,45 +181,179 @@ jobs:
181181
extern void sqlite3_result_int64(sqlite3_context *ctx, long long v);
182182
void sqlite3_result_int(sqlite3_context *ctx, int v) { sqlite3_result_int64(ctx, (long long)v); }
183183
184-
// SQLite's own formatting API. Turso doesn't export it, so without a
185-
// shim pdo_sqlite falls through to system libsqlite3, where these
186-
// functions interact badly with Turso's malloc/free and crash.
187-
// This implementation is libc-based and ignores SQLite's %q / %Q
188-
// format specifiers (used for SQL-quoting); pdo_sqlite uses the
189-
// plain %s family for error message formatting, which is handled.
190-
char *sqlite3_vsnprintf(int n, char *dst, const char *fmt, va_list ap) {
191-
if (!dst || n <= 0) return dst;
192-
vsnprintf(dst, (size_t)n, fmt, ap);
193-
return dst;
184+
// SQLite's own formatting API. Turso doesn't export it. A naive
185+
// libc-only wrapper breaks because SQLite defines extra conversion
186+
// specifiers (%q, %Q, %w) that libc vsnprintf doesn't understand —
187+
// in particular, pdo_sqlite's quote() uses sqlite3_mprintf("'%q'",s),
188+
// so without %q support every quoted value becomes garbled. This
189+
// implementation parses the format itself, handles the SQLite
190+
// extensions explicitly, and delegates standard specifiers to libc.
191+
struct buf { char *p; size_t len, cap; };
192+
193+
static int buf_append(struct buf *b, const char *s, size_t n) {
194+
if (b->len + n + 1 > b->cap) {
195+
size_t new_cap = b->cap ? b->cap * 2 : 64;
196+
while (new_cap < b->len + n + 1) new_cap *= 2;
197+
char *np = (char *)realloc(b->p, new_cap);
198+
if (!np) return -1;
199+
b->p = np;
200+
b->cap = new_cap;
201+
}
202+
memcpy(b->p + b->len, s, n);
203+
b->len += n;
204+
b->p[b->len] = '\0';
205+
return 0;
194206
}
195207
196-
char *sqlite3_snprintf(int n, char *dst, const char *fmt, ...) {
197-
va_list ap;
198-
va_start(ap, fmt);
199-
sqlite3_vsnprintf(n, dst, fmt, ap);
200-
va_end(ap);
201-
return dst;
208+
static int buf_append_c(struct buf *b, char c) { return buf_append(b, &c, 1); }
209+
210+
static int buf_append_quoted(struct buf *b, const char *s, char q) {
211+
for (; *s; s++) {
212+
if (*s == q && buf_append_c(b, q) < 0) return -1;
213+
if (buf_append_c(b, *s) < 0) return -1;
214+
}
215+
return 0;
216+
}
217+
218+
// Build a result string by parsing fmt and consuming ap as needed.
219+
static char *vmprintf_impl(const char *fmt, va_list ap) {
220+
struct buf b = {0};
221+
222+
while (*fmt) {
223+
if (*fmt != '%') {
224+
if (buf_append_c(&b, *fmt++) < 0) { free(b.p); return NULL; }
225+
continue;
226+
}
227+
const char *spec_start = fmt;
228+
fmt++;
229+
// Flags, width, precision, length — collected for libc fallback.
230+
while (*fmt && strchr("-+ #0'", *fmt)) fmt++;
231+
while (*fmt == '*') { va_arg(ap, int); fmt++; }
232+
while (*fmt && *fmt >= '0' && *fmt <= '9') fmt++;
233+
if (*fmt == '.') {
234+
fmt++;
235+
if (*fmt == '*') { va_arg(ap, int); fmt++; }
236+
while (*fmt && *fmt >= '0' && *fmt <= '9') fmt++;
237+
}
238+
int is_long = 0, is_long_long = 0;
239+
while (*fmt && strchr("hljztL", *fmt)) {
240+
if (*fmt == 'l') { if (is_long) is_long_long = 1; else is_long = 1; }
241+
if (*fmt == 'j' || *fmt == 'z') is_long_long = 1;
242+
fmt++;
243+
}
244+
char conv = *fmt;
245+
if (!conv) break;
246+
fmt++;
247+
248+
if (conv == 'q' || conv == 'Q' || conv == 'w') {
249+
const char *arg = va_arg(ap, const char *);
250+
if (conv == 'Q' && !arg) {
251+
if (buf_append(&b, "NULL", 4) < 0) { free(b.p); return NULL; }
252+
} else {
253+
char qchar = (conv == 'w') ? '"' : '\'';
254+
if (conv == 'Q' && buf_append_c(&b, qchar) < 0) { free(b.p); return NULL; }
255+
if (arg && buf_append_quoted(&b, arg, qchar) < 0) { free(b.p); return NULL; }
256+
if (conv == 'Q' && buf_append_c(&b, qchar) < 0) { free(b.p); return NULL; }
257+
}
258+
continue;
259+
}
260+
261+
// Standard conversion — rebuild the spec and call libc snprintf
262+
// for the single specifier, then append the result.
263+
size_t speclen = (size_t)(fmt - spec_start);
264+
char spec[64];
265+
if (speclen + 1 > sizeof spec) speclen = sizeof spec - 1;
266+
memcpy(spec, spec_start, speclen);
267+
spec[speclen] = '\0';
268+
269+
char tmp[128];
270+
char *out = tmp;
271+
int n = 0;
272+
if (conv == 's' || conv == 'z') {
273+
const char *a = va_arg(ap, const char *);
274+
n = snprintf(tmp, sizeof tmp, spec, a ? a : "(null)");
275+
if (n >= (int)sizeof tmp) {
276+
out = (char *)malloc((size_t)n + 1);
277+
if (!out) { free(b.p); return NULL; }
278+
snprintf(out, (size_t)n + 1, spec, a ? a : "(null)");
279+
}
280+
if (conv == 'z' && a) free((void *)a);
281+
} else if (conv == 'c') {
282+
int a = va_arg(ap, int);
283+
n = snprintf(tmp, sizeof tmp, spec, a);
284+
} else if (conv == 'd' || conv == 'i' || conv == 'u' || conv == 'x' ||
285+
conv == 'X' || conv == 'o') {
286+
if (is_long_long) {
287+
long long a = va_arg(ap, long long);
288+
n = snprintf(tmp, sizeof tmp, spec, a);
289+
} else if (is_long) {
290+
long a = va_arg(ap, long);
291+
n = snprintf(tmp, sizeof tmp, spec, a);
292+
} else {
293+
int a = va_arg(ap, int);
294+
n = snprintf(tmp, sizeof tmp, spec, a);
295+
}
296+
} else if (conv == 'e' || conv == 'E' || conv == 'f' || conv == 'F' ||
297+
conv == 'g' || conv == 'G') {
298+
double a = va_arg(ap, double);
299+
n = snprintf(tmp, sizeof tmp, spec, a);
300+
} else if (conv == 'p') {
301+
void *a = va_arg(ap, void *);
302+
n = snprintf(tmp, sizeof tmp, spec, a);
303+
} else if (conv == '%') {
304+
tmp[0] = '%'; tmp[1] = '\0'; n = 1;
305+
} else {
306+
// Unknown specifier — pass through literally.
307+
memcpy(tmp, spec_start, speclen);
308+
tmp[speclen] = '\0';
309+
n = (int)speclen;
310+
}
311+
312+
if (n > 0 && buf_append(&b, out, (size_t)n) < 0) {
313+
if (out != tmp) free(out);
314+
free(b.p);
315+
return NULL;
316+
}
317+
if (out != tmp) free(out);
318+
}
319+
if (!b.p) {
320+
b.p = (char *)malloc(1);
321+
if (b.p) b.p[0] = '\0';
322+
}
323+
return b.p;
202324
}
203325
204326
char *sqlite3_vmprintf(const char *fmt, va_list ap) {
205-
va_list ap2;
206-
va_copy(ap2, ap);
207-
int len = vsnprintf(NULL, 0, fmt, ap2);
208-
va_end(ap2);
209-
if (len < 0) return NULL;
210-
char *buf = (char *)malloc((size_t)len + 1);
211-
if (!buf) return NULL;
212-
vsnprintf(buf, (size_t)len + 1, fmt, ap);
213-
return buf;
327+
return vmprintf_impl(fmt, ap);
214328
}
215329
216330
char *sqlite3_mprintf(const char *fmt, ...) {
217331
va_list ap;
218332
va_start(ap, fmt);
219-
char *s = sqlite3_vmprintf(fmt, ap);
333+
char *s = vmprintf_impl(fmt, ap);
220334
va_end(ap);
221335
return s;
222336
}
337+
338+
char *sqlite3_vsnprintf(int n, char *dst, const char *fmt, va_list ap) {
339+
if (!dst || n <= 0) return dst;
340+
char *s = vmprintf_impl(fmt, ap);
341+
if (!s) { dst[0] = '\0'; return dst; }
342+
size_t copy = strlen(s);
343+
if (copy > (size_t)(n - 1)) copy = (size_t)(n - 1);
344+
memcpy(dst, s, copy);
345+
dst[copy] = '\0';
346+
free(s);
347+
return dst;
348+
}
349+
350+
char *sqlite3_snprintf(int n, char *dst, const char *fmt, ...) {
351+
va_list ap;
352+
va_start(ap, fmt);
353+
sqlite3_vsnprintf(n, dst, fmt, ap);
354+
va_end(ap);
355+
return dst;
356+
}
223357
C
224358
225359
SHIM=/tmp/libturso-compat-shim.so

0 commit comments

Comments
 (0)