Skip to content

Commit 59d1d00

Browse files
committed
fuzz: draw the binary result format only for types whose bytes are valid utf8
1 parent 4867085 commit 59d1d00

4 files changed

Lines changed: 18 additions & 29 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,11 +101,11 @@ jobs:
101101
# into a test: replay it locally with `node fuzz/<tool>.js --seed N --rounds 1`.
102102
#
103103
# What the first runs found, each fixed in its own pull request: #3776 (a copyData chunk
104-
# changing under whoever kept it), #3777 (binary results losing every byte utf8 cannot
105-
# carry), #3778 (pg-native reporting NaN and '' for a missing row count or command), #3780
106-
# (native errors without detail and hint), #3781 (a named empty statement failing from its
107-
# second run). And one it cannot fix here: a Buffer parameter reaches libpq as a C string,
108-
# #980, so the native arm draws none.
104+
# changing under whoever kept it), #3778 (pg-native reporting NaN and '' for a missing row
105+
# count or command), #3780 (native errors without detail and hint), #3781 (a named empty
106+
# statement failing from its second run). And two it does not fix here: a binary result is
107+
# decoded as utf8 first, #3496, so the binary arm draws only types whose bytes are valid utf8;
108+
# a Buffer parameter reaches libpq as a C string, #980, so the native arm draws none.
109109
fuzz:
110110
timeout-minutes: 15
111111
needs: lint

‎fuzz/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ node fuzz/modes.js --no-shrink # print the round as drawn
2121
The queries come from `queries.js`: selects over `generate_series` with a column per type family,
2222
sometimes as a parameter instead of a literal, writes on a temp table, and statements that fail
2323
on purpose, some of them only after rows were sent. What the binary arm can compare is limited to
24-
the types pg-types has a binary parser for, the `binary` flag of each type says which. The native
24+
the types pg-types has a binary parser for and whose bytes are valid utf8, because the parser
25+
decodes a binary value as utf8 first (#3496). The `binary` flag of each type says which. The native
2526
arm draws no Buffer parameter: pg-native hands it to libpq as a C string, cut at its first zero
2627
byte (#980), and the fix for that is in node-libpq.

‎fuzz/queries.js‎

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,16 @@ const hex = (rng) => `\\x${bytes(rng, 8).toString('hex')}`
5959

6060
// one entry per type family. `literal` is a SQL expression, `param` a JS value pg sends for it,
6161
// `binary` says whether the binary result format gives the same JS value as the text one, which
62-
// is only true for the types pg-types has a binary parser for, or decodes as utf8 anyway
62+
// is only true for the types pg-types has a binary parser for, or decodes as utf8 anyway. The
63+
// parser decodes a binary value as utf8 before pg-types sees it (#3496), so a type whose bytes
64+
// can be invalid utf8 (numbers, oid, an array header) is false too
6365
const TYPES = [
64-
{ type: 'int2', binary: true, literal: (rng) => `${ints(rng, 16)}::int2`, param: (rng) => ints(rng, 16) },
65-
{ type: 'int4', binary: true, literal: (rng) => `${ints(rng, 32)}::int4`, param: (rng) => ints(rng, 32) },
66-
{ type: 'int8', binary: true, literal: (rng) => `${ints(rng, 53)}::int8`, param: (rng) => String(ints(rng, 53)) },
66+
{ type: 'int2', binary: false, literal: (rng) => `${ints(rng, 16)}::int2`, param: (rng) => ints(rng, 16) },
67+
{ type: 'int4', binary: false, literal: (rng) => `${ints(rng, 32)}::int4`, param: (rng) => ints(rng, 32) },
68+
{ type: 'int8', binary: false, literal: (rng) => `${ints(rng, 53)}::int8`, param: (rng) => String(ints(rng, 53)) },
6769
{
6870
type: 'float8',
69-
binary: true,
71+
binary: false,
7072
literal: (rng) => `${quote(String(floats(rng)))}::float8`,
7173
param: (rng) => floats(rng),
7274
},
@@ -136,7 +138,7 @@ const TYPES = [
136138
{ type: 'uuid', binary: false, literal: () => `${quote(uuid())}::uuid`, param: () => uuid() },
137139
{
138140
type: 'oid',
139-
binary: true,
141+
binary: false,
140142
literal: (rng) => `${int(rng, 0, 2147483647)}::oid`,
141143
param: (rng) => int(rng, 0, 2147483647),
142144
},
@@ -160,7 +162,7 @@ const TYPES = [
160162
},
161163
{
162164
type: 'text[]',
163-
binary: true,
165+
binary: false,
164166
literal: (rng) => `${quote(arrayLiteral(arrayOf(rng, texts)))}::text[]`,
165167
param: (rng) => arrayOf(rng, texts),
166168
},
@@ -199,7 +201,7 @@ const TYPES = [
199201
{ type: 'unknown', binary: true, literal: (rng) => quote(texts(rng)) },
200202
{ type: 'null', binary: true, literal: () => 'NULL', param: () => null },
201203
// something that changes with the row, so a result is not one value repeated
202-
{ type: 'row', binary: true, literal: () => 'g' },
204+
{ type: 'row', binary: false, literal: () => 'g' },
203205
{ type: 'rowtext', binary: true, literal: () => `'r' || g::text` },
204206
]
205207

‎fuzz/wire.js‎

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -245,20 +245,6 @@ const observed = (parsed, expect) => {
245245
return out
246246
}
247247

248-
// what each message must parse to, in the sequence it is in: a column the last row description
249-
// declared binary comes back as its bytes, the others as text
250-
const expectations = (messages) => {
251-
let binaryColumns = []
252-
return messages.map(({ expect }) => {
253-
if (expect.name === 'rowDescription') binaryColumns = expect.fields.map((f) => f.format === 'binary')
254-
if (expect.name !== 'dataRow') return expect
255-
const fields = expect.fields.map((field, i) =>
256-
field !== null && binaryColumns[i] ? Buffer.from(field, 'utf8') : field
257-
)
258-
return { ...expect, fields }
259-
})
260-
}
261-
262248
const parseAll = (chunks, expects) => {
263249
const parser = new Parser()
264250
const out = []
@@ -268,7 +254,7 @@ const parseAll = (chunks, expects) => {
268254

269255
const run = async (plan) => {
270256
const whole = Buffer.concat(plan.messages.map((m) => m.bytes))
271-
const expects = expectations(plan.messages)
257+
const expects = plan.messages.map((m) => m.expect)
272258
const chunks = []
273259
let from = 0
274260
for (const at of [...plan.cuts, whole.length]) {

0 commit comments

Comments
 (0)