[Fix] Defensive infinite loop guard and UTF-8 check in C API - #20348
Conversation
|
After thinking some more about this I have doubts about adding the check there without a |
Acceptable and necessary. DuckDB expects any If we don't add the check here ourselves, any application developer that uses DuckDB and handles user data will have to add it themselves, as they wouldn't ever want DuckDB to exhibit unexpected behaviour. Just as an example: strings in Parquet are required to be valid UTF-8 as per the Parquet spec. Despite this, we still run UTF-8 validation upon reading strings from Parquet, because user input cannot be trusted. It's not free, but it's also not prohibitively expensive - out Parquet reader performance is fine. EDIT: you changes look good to me, but let's discuss |
lorenzowritescode
left a comment
There was a problem hiding this comment.
Do you have any benchmarks that would alert you if this change has significant performance impact?
In general, I would err on the side of safety here. Bugs caused by issues like this are very hard to track down.
Imagine we only added the assertion in the GetResultLength function but still allowed invalid UTF-8 VARCHARs, a similar issue could pop up later in a different string function.
Looking at the source here https://github.com/duckdb/duckdb/blob/main/third_party/utf8proc/utf8proc_wrapper.cpp#L80-L87 it looks like this function has an optimisation to quickly skip over ASCII chars so for ASCII-only strings the impact should be minimal (correct me if I am wrong here - not exactly the sharpest C/C++ programmer 😆 )
I added this a while ago. The optimisation specifically improves performance for longer strings by checking 8 bytes at a time. The performance of verifying UTF-8 strings should be relatively OK, but above all it's about correctness and safety. |
|
nice! I also prefer throwing an exception there :) |
@lnkuiper - I think in this case application developers still have to add an extra check because, as-of now, I propose that in this PR I'll elaborate more in the function comment that when using
@lorenzowritescode - agreed, we need to both prevent the loop and ensure it is much harder to enter invalid input.
@EtgarDev - thanks! I'll have a look :) |
|
@taniabogatsch Maybe we can return the error in |
|
Hmm, we could do that - however, this function can be used in many places, for example also when executing custom UDFs. The original issue was found during scalar UDF execution. We've had some issues w.r.t. when and where to properly return errors in the C API. @Maxxen has been working on more context-awareness and ways to pass and inspect contexts, which can then contain errors (next to other things). However, we don't have that (yet). We could also refactor the |
|
I see. I understand that it's difficult right now to get this to throw, so I am fine with improving our docs, but this really should always throw in the future. We could consider performing this check after custom UDF execution too. We don't want user strings to be a potential gateway to introducing unexpected behaviour. |
|
Thanks a lot for the input + discussion! Coming back to this after thinking about it a bit further. The C API is kind of different from, e.g., parquet files. It is not directly exposed to end users, but other developers. For example, we hand out raw That being said, preventing each client + extension from having to implement these valid UTF-8 guards themselves is very convenient and a sensible choice. And I fully agree with us throwing on incorrect input, plus that we should try our best to prevent (accidental) incorrect input through the C API. Since we cannot throw in C API functions and Once we merge this PR + |
|
This PR is of interest to us! We have seen instances of queries never finishes (symptoms are one or two CPU cores consumed at 100% and remaining that way forever or the query just never returns). We know some of our usage has invalid unicode strings. We evaluated doing a pretest for invalid unicode in Golang but deemed it very expensive. We would be very interested if you all exposed a high-speed unicode validator or a separate API in the C-appender that does unicode validation of its inputs (also for the variant that accepts a full datachunk). |
The current plan is to expose a new C API function for writing UTF-8-validated strings to data chunks, which should then cover all cases where we create (and later ingest) such data chunks. |
|
Thanks! |
[Fix] Defensive infinite loop guard and UTF-8 check in C API (duckdb/duckdb#20348) Fixup BRANCHES_TO_BE_CACHED, vars are not available on PRs, so env it is (duckdb/duckdb#20411) Don't add a semicolon to final query when splitting statements (duckdb/duckdb#20401)
[Fix] Defensive infinite loop guard and UTF-8 check in C API (duckdb/duckdb#20348) Fixup BRANCHES_TO_BE_CACHED, vars are not available on PRs, so env it is (duckdb/duckdb#20411) Don't add a semicolon to final query when splitting statements (duckdb/duckdb#20401) Co-authored-by: krlmlr <krlmlr@users.noreply.github.com>
|
Here's the follow-up PR with the new safe C API function: #20467 |
Follow-up to #20348. Related issue: https://github.com/duckdblabs/duckdb-internal/issues/7002
Related issue: https://github.com/duckdblabs/duckdb-internal/issues/7002, where invalid UTF-8 strings can cause hangs during scalar function execution.
The hang is due to incorrect API usage, but we did not sufficiently enforce it. Thus, it was possible for invalid UTF-8 to "slip through" and later on cause a hang. All vectors with a logical type
VARCHARare expected to contain valid UTF-8, which is now enforced in the C API.@lnkuiper - do you think the performance implication of calling
auto utf_type = duckdb::Utf8Proc::Analyze(str, str_len, &reason, &pos);for each string here is acceptable, or should we expose an additional function? Or maybe only perform the check behind a#ifdef DEBUGguard?