You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
mysql2: a text column intermittently reads back as a wrong value (617 for 'hi') with a mysql_native_password user #11341
Found while validating #11335 on perrymaster (Linux x86_64) against a private MySQL 8.0.46, with mysql2 3.24.4. The oracle is Node 26.5.1.
A string column sometimes comes back as the wrong value.SELECT 1 + 1 AS two, 'hi' AS s through mysql2/promiseconnection.query sometimes prints 2 617 instead of 2 hi.
It happened in 11 of 15 runs with a mysql_native_password user and 0 of 15 with a caching_sha2_password user. Node was correct every time.
importmysqlfrom"mysql2/promise";constc=awaitmysql.createConnection({host: "127.0.0.1",port: 43306,user: "perry_native",password: "perry",database: "perry_test"});const[r]=(awaitc.query("SELECT 1 + 1 AS two, 'hi' AS s"))asany;console.log(r[0].two,r[0].s);// node: 2 hiawaitc.end();
The user was created with CREATE USER 'perry_native'@'%' IDENTIFIED WITH mysql_native_password BY 'perry'. The build was PERRY_NO_AUTO_OPTIMIZE=1, and the program was run in a loop. I have not reduced it further.
This no longer reproduces on current main. Bisected on perrymaster against a live MySQL 8.0.46 with mysql2 3.24.4 and a mysql_native_password user. Every arm carries #11366's patch, because without it mysql2 segfaults at connect.
The fix lies in the #11297 series ("class captures live in the class environment, not on instances"), in the range cef4301..3f6eb6c. The intermediate commits in that range do not compile on their own, so git bisect cannot narrow it further.
What the wrong value was: 617 is exactly what packet.parseLengthCodedInt makes of the bytes hi: (0x68-48)*10 + (0x69-48) = 617. So the s column was read with the integer reader. The generated TextRow parser source was correct (result["s"] = packet.readLengthCodedString(fields[1].encoding)), so the wrong method ran at execution time.
Before #11297, every class in a CommonJS module body stored its captured outer variables as hidden __perry_cap_* keys on each instance, and that shifted the instances' layout. That is consistent with a method or slot being resolved to the wrong entry, but I have not proven it.
I could not build a package-free regression test:
Any instrumentation inside mysql2 (logging from decodeShort, or recording the generated source) makes the bug disappear on the broken build.
A seeded GC schedule also makes it disappear, so it is not a collection-timing bug.
A hand-written copy of the Packet / generated-TextRow shape does not fail on the broken build.
While doing this I found a separate latent bug: under some seeded GC schedules, mysql2 throws TypeError: value is not a function in Command.execute. It is filed as #11400.
Correction: this is not fixed. On current main (e6ad5f3) the probe reads 2 617 in 4 of 10 runs. The build is PERRY_NO_AUTO_OPTIMIZE=1 with a mysql_native_password user, and #11366 is now on main. The bisect in my previous comment found a window, cef4301..3f6eb6c, in which the bug did not show on this program. That is a change in how often it shows, not a fix. I am continuing to investigate.
I found the cause and the fix is up as #11432. The VTABLE_IC inline cache matched an entry on the method-name address alone. The new Function interpreter, which runs mysql2's generated row parser, passes each call's method name in a short-lived buffer. When a freed parseLengthCodedInt buffer was reused at the same address for readLengthCodedString, the cache ran parseLengthCodedInt in its place. A debug build confirmed this with a backtrace.
Activity
proggeramlug commentedon Sep 26, 2026
This no longer reproduces on current main. Bisected on perrymaster against a live MySQL 8.0.46 with mysql2 3.24.4 and a
mysql_native_passworduser. Every arm carries #11366's patch, because without it mysql2 segfaults at connect.SELECT 1 + 1 AS two, 'hi' AS smatched NodeThe fix lies in the #11297 series ("class captures live in the class environment, not on instances"), in the range cef4301..3f6eb6c. The intermediate commits in that range do not compile on their own, so git bisect cannot narrow it further.
What the wrong value was:
617is exactly whatpacket.parseLengthCodedIntmakes of the byteshi:(0x68-48)*10 + (0x69-48) = 617. So thescolumn was read with the integer reader. The generatedTextRowparser source was correct (result["s"] = packet.readLengthCodedString(fields[1].encoding)), so the wrong method ran at execution time.Before #11297, every class in a CommonJS module body stored its captured outer variables as hidden
__perry_cap_*keys on each instance, and that shifted the instances' layout. That is consistent with a method or slot being resolved to the wrong entry, but I have not proven it.I could not build a package-free regression test:
decodeShort, or recording the generated source) makes the bug disappear on the broken build.Packet/ generated-TextRowshape does not fail on the broken build.While doing this I found a separate latent bug: under some seeded GC schedules, mysql2 throws
TypeError: value is not a functioninCommand.execute. It is filed as #11400.I suggest closing this issue as fixed by #11297.
proggeramlug commentedon Sep 26, 2026
Correction: this is not fixed. On current main (e6ad5f3) the probe reads
2 617in 4 of 10 runs. The build isPERRY_NO_AUTO_OPTIMIZE=1with amysql_native_passworduser, and #11366 is now on main. The bisect in my previous comment found a window, cef4301..3f6eb6c, in which the bug did not show on this program. That is a change in how often it shows, not a fix. I am continuing to investigate.proggeramlug commentedon Sep 26, 2026
I found the cause and the fix is up as #11432. The
VTABLE_ICinline cache matched an entry on the method-name address alone. Thenew Functioninterpreter, which runs mysql2's generated row parser, passes each call's method name in a short-lived buffer. When a freedparseLengthCodedIntbuffer was reused at the same address forreadLengthCodedString, the cache ranparseLengthCodedIntin its place. A debug build confirmed this with a backtrace.fix(runtime): a vtable IC hit also requires the method name bytes to …