{"record":{"id":"81aa3ae3337ae6c4","repo":"vitessio/vitess","slug":"malformed-hex-literal-from-parser","errorCode":null,"errorMessage":"malformed hex literal from parser","messagePattern":"malformed hex literal from parser","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"go/vt/vtgate/evalengine/api_literal.go","lineNumber":146,"sourceCode":"func NewLiteralDatetimeFromBytes(val []byte) (*Literal, error) {\n\tt, err := parseDateTime(val)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Literal{t}, nil\n}\n\nfunc parseHexLiteral(val []byte) ([]byte, error) {\n\traw := make([]byte, hex.DecodedLen(val))\n\tif err := hex.DecodeBytes(raw, val); err != nil {\n\t\treturn nil, err\n\t}\n\treturn raw, nil\n}\n\nfunc parseHexNumber(val []byte) ([]byte, error) {\n\tif val[0] != '0' || val[1] != 'x' {\n\t\tpanic(\"malformed hex literal from parser\")\n\t}\n\tif len(val)%2 == 0 {\n\t\treturn parseHexLiteral(val[2:])\n\t}\n\t// If the hex literal doesn't have an even amount of hex digits, we need\n\t// to pad it with a '0' in the left. Instead of allocating a new slice\n\t// for padding pad in-place by replacing the 'x' in the original slice with\n\t// a '0', and clean it up after parsing.\n\tval[1] = '0'\n\tdefer func() {\n\t\tval[1] = 'x'\n\t}()\n\treturn parseHexLiteral(val[1:])\n}\n\nfunc parseBitNum(val []byte) ([]byte, error) {\n\tif val[0] != '0' || val[1] != 'b' {\n\t\treturn nil, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, \"malformed Bit literal: %q (missing 0b prefix)\", val)","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/vitessio/vitess/blob/01a25a7d176f94613b8d59d799f438380a8760e4/go/vt/vtgate/evalengine/api_literal.go#L128-L164","documentation":"parseHexNumber converts hex number literals ('0x...') produced by the SQL parser into binary bytes. It asserts the input actually starts with '0x'; if not, the parser and evalengine are inconsistent, which is an internal bug, so it panics rather than returning an error.","triggerScenarios":"Calling NewLiteralBinaryFromHexNum (directly or via push_hexnum/valueToEval) with a token that does not begin with the two bytes '0','x' — i.e. the parser classified the token as a hex number but the bytes say otherwise.","commonSituations":"Custom parser forks or modified lexer rules emitting hexnum tokens for non-hex text; hand-crafted calls to NewLiteralBinaryFromHexNum in tests with raw byte slices lacking the 0x prefix; version mismatches between parser and evalengine packages.","solutions":["Verify the input token starts with '0x' before calling NewLiteralBinaryFromHexNum (use parseHexLiteral for bare hex digits)","Regenerate the parser (make codegen) if lexer/parser rules were changed","Check for version skew between sqlparser and evalengine packages in your build"],"exampleFix":"// before\nlit, err := NewLiteralBinaryFromHexNum([]byte(\"ff\"))\n// after\nif len(b) >= 2 && b[0] == '0' && b[1] == 'x' {\n    lit, err = NewLiteralBinaryFromHexNum(b)\n} else {\n    lit, err = NewLiteralBinaryFromHexNum([]byte(\"0x\" + string(b)))\n}","handlingStrategy":"validation","validationCode":"func isHexNum(b []byte) bool {\n    return len(b) >= 3 && b[0] == '0' && b[1] == 'x'\n}","typeGuard":"func isHexNumberToken(val []byte) bool {\n    return len(val) >= 3 && val[0] == '0' && val[1] == 'x'\n}","tryCatchPattern":"defer func() {\n    if r := recover(); r != nil {\n        if strings.Contains(fmt.Sprint(r), \"malformed hex literal\") {\n            log.Errorf(\"bad hex token: %q\", tokenBytes)\n            return\n        }\n        panic(r)\n    }\n}()","preventionTips":["Only route tokens the lexer classified as hexnum to parseHexNumber","Regenerate the parser after lexer changes (make codegen)","Add fuzz/round-trip tests for hex literal parsing"],"tags":["go","panic","sql-parser","hex-literal","evalengine"],"backgroundTag":"internal-invariant-panic","analyzedSha":"01a25a7d176f94613b8d59d799f438380a8760e4","analyzedAt":"2026-09-01T17:28:30.605Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}