You've already forked go-sqlmock
mirror of
https://github.com/DATA-DOG/go-sqlmock.git
synced 2025-07-01 00:34:53 +02:00
Invalidate memory scanned into sql.RawBytes
The intention of sql.RawBytes is for it to hold memory owned by the database. When used, it's content is only valid until the `Next`, `Scan` or `Close` is called on the `Rows` To ensure that we meet this behaviour, when `[]byte` is used in a column, it's value is copied to a buffer that we keep track of for later invalidation. By doing this, incorrect use of `sql.RawBytes` values is exposed in tests that use go-sqlmock. Without this, when a real database is used and it's driver does share memory, then those issues would not be exposed until runtime (and in non-obvious ways)
This commit is contained in:
35
rows.go
35
rows.go
@ -1,6 +1,7 @@
|
||||
package sqlmock
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"database/sql/driver"
|
||||
"encoding/csv"
|
||||
"fmt"
|
||||
@ -8,6 +9,8 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
const invalidate = "☠☠☠ MEMORY OVERWRITTEN ☠☠☠ "
|
||||
|
||||
// CSVColumnParser is a function which converts trimmed csv
|
||||
// column string to a []byte representation. Currently
|
||||
// transforms NULL to nil
|
||||
@ -23,6 +26,7 @@ type rowSets struct {
|
||||
sets []*Rows
|
||||
pos int
|
||||
ex *ExpectedQuery
|
||||
raw [][]byte
|
||||
}
|
||||
|
||||
func (rs *rowSets) Columns() []string {
|
||||
@ -30,6 +34,7 @@ func (rs *rowSets) Columns() []string {
|
||||
}
|
||||
|
||||
func (rs *rowSets) Close() error {
|
||||
rs.invalidateRaw()
|
||||
rs.ex.rowsWereClosed = true
|
||||
return rs.sets[rs.pos].closeErr
|
||||
}
|
||||
@ -38,11 +43,17 @@ func (rs *rowSets) Close() error {
|
||||
func (rs *rowSets) Next(dest []driver.Value) error {
|
||||
r := rs.sets[rs.pos]
|
||||
r.pos++
|
||||
rs.invalidateRaw()
|
||||
if r.pos > len(r.rows) {
|
||||
return io.EOF // per interface spec
|
||||
}
|
||||
|
||||
for i, col := range r.rows[r.pos-1] {
|
||||
if b, ok := rawBytes(col); ok {
|
||||
rs.raw = append(rs.raw, b)
|
||||
dest[i] = b
|
||||
continue
|
||||
}
|
||||
dest[i] = col
|
||||
}
|
||||
|
||||
@ -80,6 +91,30 @@ func (rs *rowSets) empty() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func rawBytes(col driver.Value) (_ []byte, ok bool) {
|
||||
val, ok := col.([]byte)
|
||||
if !ok || len(val) == 0 {
|
||||
return nil, false
|
||||
}
|
||||
// Copy the bytes from the mocked row into a shared raw buffer, which we'll replace the content of later
|
||||
// This allows scanning into sql.RawBytes to correctly become invalid on subsequent calls to Next(), Scan() or Close()
|
||||
b := make([]byte, len(val))
|
||||
copy(b, val)
|
||||
return b, true
|
||||
}
|
||||
|
||||
// Bytes that could have been scanned as sql.RawBytes are only valid until the next call to Next, Scan or Close.
|
||||
// If those occur, we must replace their content to simulate the shared memory to expose misuse of sql.RawBytes
|
||||
func (rs *rowSets) invalidateRaw() {
|
||||
// Replace the content of slices previously returned
|
||||
b := []byte(invalidate)
|
||||
for _, r := range rs.raw {
|
||||
copy(r, bytes.Repeat(b, len(r)/len(b)+1))
|
||||
}
|
||||
// Start with new slices for the next scan
|
||||
rs.raw = nil
|
||||
}
|
||||
|
||||
// Rows is a mocked collection of rows to
|
||||
// return for Query result
|
||||
type Rows struct {
|
||||
|
Reference in New Issue
Block a user