Repository navigation
[2.1] Keeps error logging on prepared statements for any error message - #9883
albertlast wants to merge 1 commit into
Conversation
The error log is written only through its prepared statement. When that cannot be prepared, mysqli_stmt_bind_param() was handed false and threw a TypeError; the error is now dropped instead of being sent through the regular query path, which may be what failed and whose own errors would come straight back here. What the statement binds is cleaned first. On a UTF-8 forum strings are cut to their column on a character boundary, invalid UTF-8 is replaced and, on MySQL, 4-byte characters are encoded the way the query path does for tables that are not utf8mb4. NUL bytes are removed. Without that a long message, or one holding bytes the database rejects, made the insert fail and the error was lost. PostgreSQL with pooling prepares the unnamed statement every time, since any other query may replace it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: albertlast <mathiaspapealbert@hotmail.com>
|
We already have |
|
from where you know that this function exists in the time of the error? |
|
Its defined in Load.php during a reloadSettings. So the first thing that SMF calls on any startup. If it doesn't exist, the forum was not loaded properly. |
|
so we are in a error state, provide in this most usable information in this moment instead of "no loaded proplerly" message. |
|
On Both show up in #9872, which uses it, against its current head on MySQL. Long 2-byte, 3-byte and emoji messages were not stored, and invalid UTF-8 threw
|
|
I don't think 2.1 required mb to be loaded, I can't recall right now. So I don't know if we can rely on it, but it should explain the hacky logic in the $smcFunc. I think we have to take the L here because of it and the low chance of having many multi byte characters in a error message. |
Description
The error log is written through a prepared statement so that error messages, whatever they hold, never pass through SMF's query parser. This keeps it that way and closes the two gaps that still lost errors.
When the statement cannot be prepared, the error is dropped.
mysqli_stmt_bind_param()was handedfalseand threw aTypeError. The error is not sent through$smcFunc['db_insert']instead. Whatever caused the error may have broken that path too, and its own errors would be logged by callingsmf_db_error_insert()again. #9872 does fall back todb_insert, and in testing that fallback breaks on both engines:$smcFuncwithout declaring it global, which is a fatal "Value of type null is not callable";{inet:...}rejects ("The database value you're trying to insert does not exist: ip"). On PostgreSQL that failure is logged through the same broken path and ends in "Error loop." A null IP is what every error from cron, the CLI or a background task has.What the statement binds is cleaned first. NUL bytes are removed. On a UTF-8 forum:
mb_strcut(), so the cut never ends inside a character;$smcFunc['fix_utf8mb4'], as they do on the query path. A table that is not utf8mb4 rejects them otherwise.Other character sets keep a plain
substr(). Without this, a message longer than its column, or one holding bytes the database rejects, made the insert fail and the error was lost.PostgreSQL with pooling prepares the unnamed statement every time, as it did before, since any other query may replace it.
Testing
2.1 has no test suite, so this was checked with a script calling
$smcFunc['db_error_insert']against an installed forum. It used 16 kinds of message (quotes, backslashes, SQL,{db_prefix}and%splaceholders, emoji, newlines, HTML, NUL bytes, invalid UTF-8, empty, and 70 KB cut inside 2-, 3- and 4-byte characters), each under an IPv4, an IPv6 and an invalid IP. The MySQL tables were not utf8mb4.$smcFuncis nullThe prepare was made to fail with
SET GLOBAL max_prepared_stmt_count = 0on MySQL, and on PostgreSQL by preparing another statement namedsmf_log_errorsfirst. The script checked that the prepare really failed before it ran.Issues References (Fixes|Related|Closes)
🤖 Generated with Claude Code