Skip to content

fix(plugin-mysql): read MARIADB_CONST_STRING by length to stop JSON detection flicker#1216

Merged
datlechin merged 1 commit into
mainfrom
fix/mariadb-json-attr-buffer-overread
May 11, 2026
Merged

fix(plugin-mysql): read MARIADB_CONST_STRING by length to stop JSON detection flicker#1216
datlechin merged 1 commit into
mainfrom
fix/mariadb-json-attr-buffer-overread

Conversation

@datlechin
Copy link
Copy Markdown
Member

Summary

User reported audit_log.payload chevron flickering on rapid Cmd+R refresh. Inspector pane confirmed the column type was alternating between json and string across refreshes — same query, same column, intermittent classification.

Root cause

MariaDBPluginConnection.mysqlTypeToString recognizes JSON-stored-as-LONGTEXT by reading MariaDB's extended metadata:

var attr = MARIADB_CONST_STRING()
if mariadb_field_attr(&attr, fieldPtr, MARIADB_FIELD_ATTR_FORMAT_NAME) == 0,
   let str = attr.str, attr.length > 0,
   String(cString: str) == "json" {
    return "JSON"
}

MARIADB_CONST_STRING is a length-prefixed buffer ({ const char *str; size_t length; }) — not null-terminated. String(cString:) scans bytes until it hits the next \0, which means it reads past the four bytes of "json" into whatever memory happens to live next in the result-set buffer.

When that adjacent memory contained a null byte → String(cString:) returned "json" → comparison passed → column tagged JSON → JSON kind → chevron visible.
When that adjacent memory contained non-null bytes → String(cString:) returned "json…garbage" → comparison failed → fell through to the type switch → column tagged LONGTEXT → text kind → no chevron.

The contents of the adjacent memory are non-deterministic (depends on what the connector library wrote there for previous rows / fields, allocator alignment padding, etc.), which is why the bug presented as a flicker rather than a consistent failure.

Fix

Read exactly attr.length bytes:

if mariadb_field_attr(&attr, fieldPtr, MARIADB_FIELD_ATTR_FORMAT_NAME) == 0,
   let str = attr.str, attr.length > 0,
   let value = String(data: Data(bytes: str, count: Int(attr.length)), encoding: .utf8),
   value == "json" {
    return "JSON"
}

Data(bytes:count:) copies exactly count bytes and stops, so there is no over-read. String(bytes:encoding:) (which SwiftLint prefers over the non-failable String(decoding:as:)) returns nil on invalid UTF-8, which we want anyway — anything that isn't valid UTF-8 isn't going to be the literal "json" we're looking for.

Test plan

  • Open MySQL tablepro_demoaudit_log.
  • Cmd+R 20+ times in rapid succession.
  • Inspector pane shows payload: json on every refresh, never string.
  • Chevron accessory visible on every payload cell every refresh.
  • Click chevron / double-click cell opens JSON editor (Text + Tree tabs).
  • Other LONGTEXT columns (not declared as JSON) still classify as LONGTEXT and use the plain text editor.

@datlechin datlechin force-pushed the fix/mariadb-json-attr-buffer-overread branch from 70c6476 to 621c0e5 Compare May 11, 2026 09:19
@datlechin datlechin merged commit ca45984 into main May 11, 2026
2 checks passed
@datlechin datlechin deleted the fix/mariadb-json-attr-buffer-overread branch May 11, 2026 09:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant