Copy SQLite callback memory through pinned runtime buffers
5 files changed,
+217
−38
internal/vfsspike/locks_linux.go+6 −6internal/vfsspike/memory_linux.go+52 −0internal/vfsspike/memory_linux_test.go+124 −0internal/vfsspike/vfs_linux.go+33 −29internal/vfsspike/vfs_linux_test.go+2 −3
CI passed — run details
diff --git a/internal/vfsspike/locks_linux.go b/internal/vfsspike/locks_linux.go| index 4aca9b967..5d1282499 100644 |
| --- a/internal/vfsspike/locks_linux.go |
| +++ b/internal/vfsspike/locks_linux.go |
| @@ -105,9 +105,9 @@ func reservedFile(t *libc.TLS, p, out uintptr) int32 { |
| f := getFile(p) |
| f.v.mu.Lock() |
| defer f.v.mu.Unlock() |
| - *(*int32)(unsafe.Pointer(out)) = 0 |
| + foreignStore(t, out, int32(0)) |
| if f.lock >= sqlite.SQLITE_LOCK_RESERVED { |
| - *(*int32)(unsafe.Pointer(out)) = 1 |
| + foreignStore(t, out, int32(1)) |
| return sqlite.SQLITE_OK |
| } |
| l := unix.Flock_t{Type: unix.F_WRLCK, Whence: 0, Start: pending + 1, Len: 1} |
| @@ -115,7 +115,7 @@ func reservedFile(t *libc.TLS, p, out uintptr) int32 { |
| return sqlite.SQLITE_IOERR_CHECKRESERVEDLOCK |
| } |
| if l.Type != unix.F_UNLCK { |
| - *(*int32)(unsafe.Pointer(out)) = 1 |
| + foreignStore(t, out, int32(1)) |
| } |
| return sqlite.SQLITE_OK |
| } |
| @@ -158,7 +158,7 @@ func ensureSHM(f *file) int32 { |
| return sqlite.SQLITE_OK |
| } |
| func shmMap(t *libc.TLS, p uintptr, page, size, extend int32, out uintptr) int32 { |
| - *(*uintptr)(unsafe.Pointer(out)) = 0 |
| + foreignStore(t, out, uintptr(0)) |
| f := getFile(p) |
| f.v.mu.Lock() |
| defer f.v.mu.Unlock() |
| @@ -172,7 +172,7 @@ func shmMap(t *libc.TLS, p uintptr, page, size, extend int32, out uintptr) int32 |
| if len(b) != int(size) { |
| return sqlite.SQLITE_IOERR_SHMMAP |
| } |
| - *(*uintptr)(unsafe.Pointer(out)) = uintptr(unsafe.Pointer(&b[0])) |
| + foreignStore(t, out, uintptr(unsafe.Pointer(&b[0]))) |
| return sqlite.SQLITE_OK |
| } |
| var s unix.Stat_t |
| @@ -193,7 +193,7 @@ func shmMap(t *libc.TLS, p uintptr, page, size, extend int32, out uintptr) int32 |
| return sqlite.SQLITE_IOERR_SHMMAP |
| } |
| f.regions[page] = b |
| - *(*uintptr)(unsafe.Pointer(out)) = uintptr(unsafe.Pointer(&b[0])) |
| + foreignStore(t, out, uintptr(unsafe.Pointer(&b[0]))) |
| return sqlite.SQLITE_OK |
| } |
| func shmLock(t *libc.TLS, p uintptr, offset, n, flags int32) int32 { |
diff --git a/internal/vfsspike/memory_linux.go b/internal/vfsspike/memory_linux.go| new file mode 100644 |
| index 000000000..4b38109ce |
| --- /dev/null |
| +++ b/internal/vfsspike/memory_linux.go |
| @@ -0,0 +1,52 @@ |
| +//go:build linux && (amd64 || arm64) |
| + |
| +package vfsspike |
| + |
| +import ( |
| + "runtime" |
| + "unsafe" |
| + |
| + "modernc.org/libc" |
| + sqlite "modernc.org/sqlite/lib" |
| +) |
| + |
| +type foreignValue interface { |
| + byte | int32 | int64 | uintptr | float64 | sqlite.Tsqlite3_file | sqlite.Tsqlite3_io_methods | sqlite.Tsqlite3_vfs |
| +} |
| + |
| +// SQLite owns callback addresses. Copy through its libc bridge while the Go |
| +// staging object is pinned; these types contain no Go pointers. |
| +func foreignStore[T foreignValue](tls *libc.TLS, dst uintptr, value T) { |
| + var pin runtime.Pinner |
| + pin.Pin(&value) |
| + defer pin.Unpin() |
| + libc.Xmemcpy(tls, dst, uintptr(unsafe.Pointer(&value)), uint64(unsafe.Sizeof(value))) |
| +} |
| + |
| +func foreignLoad[T foreignValue](tls *libc.TLS, src uintptr) (value T) { |
| + var pin runtime.Pinner |
| + pin.Pin(&value) |
| + defer pin.Unpin() |
| + libc.Xmemcpy(tls, uintptr(unsafe.Pointer(&value)), src, uint64(unsafe.Sizeof(value))) |
| + return value |
| +} |
| + |
| +func copyToForeign(tls *libc.TLS, dst uintptr, value []byte) { |
| + if len(value) == 0 { |
| + return |
| + } |
| + var pin runtime.Pinner |
| + pin.Pin(&value[0]) |
| + defer pin.Unpin() |
| + libc.Xmemcpy(tls, dst, uintptr(unsafe.Pointer(&value[0])), uint64(len(value))) |
| +} |
| + |
| +func copyFromForeign(tls *libc.TLS, value []byte, src uintptr) { |
| + if len(value) == 0 { |
| + return |
| + } |
| + var pin runtime.Pinner |
| + pin.Pin(&value[0]) |
| + defer pin.Unpin() |
| + libc.Xmemcpy(tls, uintptr(unsafe.Pointer(&value[0])), src, uint64(len(value))) |
| +} |
diff --git a/internal/vfsspike/memory_linux_test.go b/internal/vfsspike/memory_linux_test.go| new file mode 100644 |
| index 000000000..d05218cb0 |
| --- /dev/null |
| +++ b/internal/vfsspike/memory_linux_test.go |
| @@ -0,0 +1,124 @@ |
| +//go:build linux && (amd64 || arm64) |
| + |
| +package vfsspike |
| + |
| +import ( |
| + "bytes" |
| + "testing" |
| + "unsafe" |
| + |
| + "modernc.org/libc" |
| + sqlite "modernc.org/sqlite/lib" |
| +) |
| + |
| +func sqliteBuffer(t *testing.T, tls *libc.TLS, n uint64) uintptr { |
| + t.Helper() |
| + p := sqlite.Xsqlite3_malloc64(tls, n) |
| + if p == 0 { |
| + t.Fatalf("sqlite3_malloc64(%d) returned nil", n) |
| + } |
| + t.Cleanup(func() { sqlite.Xsqlite3_free(tls, p) }) |
| + return p |
| +} |
| + |
| +func openMainCallback(t *testing.T, v *VFS) uintptr { |
| + t.Helper() |
| + z, err := libc.CString(v.main) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + t.Cleanup(func() { libc.Xfree(v.tls, z) }) |
| + p := sqliteBuffer(t, v.tls, uint64(unsafe.Sizeof(sqlite.Tsqlite3_file{}))) |
| + flags := int32(sqlite.SQLITE_OPEN_MAIN_DB | sqlite.SQLITE_OPEN_READWRITE) |
| + if rc := openFile(v.tls, v.ptr, z, p, flags, 0); rc != sqlite.SQLITE_OK { |
| + t.Fatalf("openFile = %d, want SQLITE_OK", rc) |
| + } |
| + t.Cleanup(func() { |
| + if getFile(p) != nil { |
| + if rc := closeFile(v.tls, p); rc != sqlite.SQLITE_OK { |
| + t.Errorf("closeFile = %d, want SQLITE_OK", rc) |
| + } |
| + } |
| + }) |
| + return p |
| +} |
| + |
| +func TestForeignMemoryRoundTrip(t *testing.T) { |
| + v, _ := setup(t) |
| + p := sqliteBuffer(t, v.tls, 16) |
| + |
| + foreignStore(v.tls, p, int64(0x102030405060708)) |
| + if got := foreignLoad[int64](v.tls, p); got != 0x102030405060708 { |
| + t.Fatalf("foreignLoad = %#x", got) |
| + } |
| + |
| + want := []byte{0, 1, 2, 0xfe, 0xff} |
| + copyToForeign(v.tls, p, want) |
| + got := make([]byte, len(want)) |
| + copyFromForeign(v.tls, got, p) |
| + if !bytes.Equal(got, want) { |
| + t.Fatalf("copied bytes = %v, want %v", got, want) |
| + } |
| + |
| + copyToForeign(v.tls, 0, nil) |
| + copyFromForeign(v.tls, nil, 0) |
| +} |
| + |
| +func TestCallbackReadWriteAndShortReadZeroFill(t *testing.T) { |
| + v, _ := setup(t) |
| + f := openMainCallback(t, v) |
| + src := sqliteBuffer(t, v.tls, 3) |
| + copyToForeign(v.tls, src, []byte("abc")) |
| + if rc := writeFile(v.tls, f, src, 3, 4); rc != sqlite.SQLITE_OK { |
| + t.Fatalf("writeFile = %d, want SQLITE_OK", rc) |
| + } |
| + |
| + out := sqliteBuffer(t, v.tls, 6) |
| + copyToForeign(v.tls, out, bytes.Repeat([]byte{0xa5}, 6)) |
| + if rc := readFile(v.tls, f, out, 6, 4); rc != sqlite.SQLITE_IOERR_SHORT_READ { |
| + t.Fatalf("readFile = %d, want SQLITE_IOERR_SHORT_READ", rc) |
| + } |
| + got := make([]byte, 6) |
| + copyFromForeign(v.tls, got, out) |
| + if want := []byte{'a', 'b', 'c', 0, 0, 0}; !bytes.Equal(got, want) { |
| + t.Fatalf("short read buffer = %v, want %v", got, want) |
| + } |
| +} |
| + |
| +func TestCallbackForeignOutputs(t *testing.T) { |
| + v, _ := setup(t) |
| + f := openMainCallback(t, v) |
| + z, err := libc.CString(v.main) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + defer libc.Xfree(v.tls, z) |
| + |
| + path := sqliteBuffer(t, v.tls, 16) |
| + copyToForeign(v.tls, path, bytes.Repeat([]byte{0xa5}, 16)) |
| + if rc := fullPath(v.tls, v.ptr, z, 16, path); rc != sqlite.SQLITE_OK { |
| + t.Fatalf("fullPath = %d, want SQLITE_OK", rc) |
| + } |
| + gotPath := make([]byte, 16) |
| + copyFromForeign(v.tls, gotPath, path) |
| + wantPrefix := append([]byte(v.main), 0) |
| + if !bytes.Equal(gotPath[:len(wantPrefix)], wantPrefix) { |
| + t.Fatalf("full path prefix = %v, want %v", gotPath[:len(wantPrefix)], wantPrefix) |
| + } |
| + if !bytes.Equal(gotPath[len(wantPrefix):], bytes.Repeat([]byte{0xa5}, 16-len(wantPrefix))) { |
| + t.Fatalf("fullPath changed bytes beyond NUL: %v", gotPath) |
| + } |
| + |
| + scalar := sqliteBuffer(t, v.tls, 8) |
| + foreignStore(v.tls, scalar, int32(-1)) |
| + if rc := accessFile(v.tls, v.ptr, z, sqlite.SQLITE_ACCESS_EXISTS, scalar); rc != sqlite.SQLITE_OK || foreignLoad[int32](v.tls, scalar) != 1 { |
| + t.Fatalf("accessFile = %d, out %d", rc, foreignLoad[int32](v.tls, scalar)) |
| + } |
| + if rc := currentTime64(v.tls, v.ptr, scalar); rc != sqlite.SQLITE_OK || foreignLoad[int64](v.tls, scalar) <= 0 { |
| + t.Fatalf("currentTime64 = %d, out %d", rc, foreignLoad[int64](v.tls, scalar)) |
| + } |
| + foreignStore(v.tls, scalar, int32(-1)) |
| + if rc := controlFile(v.tls, f, sqlite.SQLITE_FCNTL_LOCKSTATE, scalar); rc != sqlite.SQLITE_OK || foreignLoad[int32](v.tls, scalar) != 0 { |
| + t.Fatalf("controlFile = %d, out %d", rc, foreignLoad[int32](v.tls, scalar)) |
| + } |
| +} |
diff --git a/internal/vfsspike/vfs_linux.go b/internal/vfsspike/vfs_linux.go| index 896cde8f6..4a6ffeeda 100644 |
| --- a/internal/vfsspike/vfs_linux.go |
| +++ b/internal/vfsspike/vfs_linux.go |
| @@ -60,10 +60,10 @@ var registry = struct { |
| }{vfs: make(map[uintptr]*VFS), files: make(map[uintptr]*file), inodes: make(map[identity]bool)} |
| var barrier atomic.Uint64 |
| -func fp[T any](f T) uintptr { return *(*uintptr)(unsafe.Pointer(&f)) } |
| -func bytesAt(p uintptr, n int) []byte { return unsafe.Slice((*byte)(unsafe.Pointer(p)), n) } |
| -func getVFS(p uintptr) *VFS { registry.RLock(); defer registry.RUnlock(); return registry.vfs[p] } |
| -func getFile(p uintptr) *file { registry.RLock(); defer registry.RUnlock(); return registry.files[p] } |
| +func fp[T any](f T) uintptr { return *(*uintptr)(unsafe.Pointer(&f)) } |
| + |
| +func getVFS(p uintptr) *VFS { registry.RLock(); defer registry.RUnlock(); return registry.vfs[p] } |
| +func getFile(p uintptr) *file { registry.RLock(); defer registry.RUnlock(); return registry.files[p] } |
| func same(s *unix.Stat_t, id identity) bool { |
| return s.Dev == id.dev && s.Ino == id.ino && s.Mode&unix.S_IFMT == unix.S_IFREG |
| } |
| @@ -122,17 +122,17 @@ func Register(data, meta *os.File, name string) (*VFS, error) { |
| err = unix.ENOMEM |
| return nil, err |
| } |
| - *(*sqlite.Tsqlite3_io_methods)(unsafe.Pointer(v.methods)) = sqlite.Tsqlite3_io_methods{ |
| + foreignStore(v.tls, v.methods, sqlite.Tsqlite3_io_methods{ |
| FiVersion: 2, FxClose: fp(closeFile), FxRead: fp(readFile), FxWrite: fp(writeFile), FxTruncate: fp(truncateFile), FxSync: fp(syncFile), FxFileSize: fp(sizeFile), |
| FxLock: fp(lockFile), FxUnlock: fp(unlockFile), FxCheckReservedLock: fp(reservedFile), FxFileControl: fp(controlFile), FxSectorSize: fp(sectorSize), FxDeviceCharacteristics: fp(characteristics), |
| FxShmMap: fp(shmMap), FxShmLock: fp(shmLock), FxShmBarrier: fp(shmBarrier), FxShmUnmap: fp(shmUnmap), |
| - } |
| - *(*sqlite.Tsqlite3_vfs)(unsafe.Pointer(v.ptr)) = sqlite.Tsqlite3_vfs{ |
| + }) |
| + foreignStore(v.tls, v.ptr, sqlite.Tsqlite3_vfs{ |
| FiVersion: 2, FszOsFile: int32(unsafe.Sizeof(sqlite.Tsqlite3_file{})), FmxPathname: 255, FzName: v.cname, |
| FxOpen: fp(openFile), FxDelete: fp(deleteFile), FxAccess: fp(accessFile), FxFullPathname: fp(fullPath), |
| FxDlOpen: fp(dlOpen), FxDlError: fp(dlError), FxDlSym: fp(dlSym), FxDlClose: fp(dlClose), |
| FxRandomness: fp(randomness), FxSleep: fp(sleep), FxCurrentTime: fp(currentTime), FxCurrentTimeInt64: fp(currentTime64), |
| - } |
| + }) |
| registry.Lock() |
| registry.vfs[v.ptr] = v |
| registry.Unlock() |
| @@ -194,7 +194,7 @@ func (v *VFS) recheck() error { |
| return nil |
| } |
| func openFile(tls *libc.TLS, p, z, out uintptr, flags int32, outFlags uintptr) int32 { |
| - (*sqlite.Tsqlite3_file)(unsafe.Pointer(out)).FpMethods = 0 |
| + foreignStore(tls, out, sqlite.Tsqlite3_file{}) |
| v := getVFS(p) |
| if v == nil { |
| return sqlite.SQLITE_CANTOPEN |
| @@ -262,9 +262,9 @@ func openFile(tls *libc.TLS, p, z, out uintptr, flags int32, outFlags uintptr) i |
| registry.Lock() |
| registry.files[out] = f |
| registry.Unlock() |
| - (*sqlite.Tsqlite3_file)(unsafe.Pointer(out)).FpMethods = v.methods |
| + foreignStore(tls, out, sqlite.Tsqlite3_file{FpMethods: v.methods}) |
| if outFlags != 0 { |
| - *(*int32)(unsafe.Pointer(outFlags)) = flags |
| + foreignStore(tls, outFlags, flags) |
| } |
| return sqlite.SQLITE_OK |
| } |
| @@ -286,7 +286,7 @@ func closeFile(tls *libc.TLS, p uintptr) int32 { |
| registry.Lock() |
| delete(registry.files, p) |
| registry.Unlock() |
| - (*sqlite.Tsqlite3_file)(unsafe.Pointer(p)).FpMethods = 0 |
| + foreignStore(tls, p, sqlite.Tsqlite3_file{}) |
| if err != nil { |
| return sqlite.SQLITE_IOERR_CLOSE |
| } |
| @@ -294,20 +294,22 @@ func closeFile(tls *libc.TLS, p uintptr) int32 { |
| } |
| func readFile(tls *libc.TLS, p, b uintptr, n int32, off int64) int32 { |
| f := getFile(p) |
| - buf := bytesAt(b, int(n)) |
| + buf := make([]byte, int(n)) |
| got, err := unix.Pread(f.fd, buf, off) |
| if err != nil && err != io.EOF { |
| return sqlite.SQLITE_IOERR_READ |
| } |
| + clear(buf[got:]) |
| + copyToForeign(tls, b, buf) |
| if got < len(buf) { |
| - clear(buf[got:]) |
| return sqlite.SQLITE_IOERR_SHORT_READ |
| } |
| return sqlite.SQLITE_OK |
| } |
| func writeFile(tls *libc.TLS, p, b uintptr, n int32, off int64) int32 { |
| f := getFile(p) |
| - buf := bytesAt(b, int(n)) |
| + buf := make([]byte, int(n)) |
| + copyFromForeign(tls, buf, b) |
| for len(buf) > 0 { |
| got, err := unix.Pwrite(f.fd, buf, off) |
| if err != nil || got == 0 { |
| @@ -339,7 +341,7 @@ func sizeFile(tls *libc.TLS, p, out uintptr) int32 { |
| if unix.Fstat(getFile(p).fd, &s) != nil { |
| return sqlite.SQLITE_IOERR_FSTAT |
| } |
| - *(*int64)(unsafe.Pointer(out)) = s.Size |
| + foreignStore(tls, out, s.Size) |
| return sqlite.SQLITE_OK |
| } |
| func fullPath(tls *libc.TLS, p, z uintptr, n int32, out uintptr) int32 { |
| @@ -348,13 +350,14 @@ func fullPath(tls *libc.TLS, p, z uintptr, n int32, out uintptr) int32 { |
| if !v.allowed(name) || len(name) >= int(n) { |
| return sqlite.SQLITE_CANTOPEN |
| } |
| - buf := bytesAt(out, int(n)) |
| + buf := make([]byte, len(name)+1) |
| copy(buf, name) |
| buf[len(name)] = 0 |
| + copyToForeign(tls, out, buf) |
| return sqlite.SQLITE_OK |
| } |
| func accessFile(tls *libc.TLS, p, z uintptr, flags int32, out uintptr) int32 { |
| - *(*int32)(unsafe.Pointer(out)) = 0 |
| + foreignStore(tls, out, int32(0)) |
| v := getVFS(p) |
| name := libc.GoString(z) |
| if !v.allowed(name) { |
| @@ -374,7 +377,7 @@ func accessFile(tls *libc.TLS, p, z uintptr, flags int32, out uintptr) int32 { |
| if name == v.main && !same(&s, v.expected) { |
| return sqlite.SQLITE_CANTOPEN |
| } |
| - *(*int32)(unsafe.Pointer(out)) = 1 |
| + foreignStore(tls, out, int32(1)) |
| return sqlite.SQLITE_OK |
| } |
| func deleteFile(tls *libc.TLS, p, z uintptr, syncDir int32) int32 { |
| @@ -394,16 +397,17 @@ func deleteFile(tls *libc.TLS, p, z uintptr, syncDir int32) int32 { |
| func dlOpen(*libc.TLS, uintptr, uintptr) uintptr { return 0 } |
| func dlError(tls *libc.TLS, p uintptr, n int32, out uintptr) { |
| if n > 0 { |
| - *(*byte)(unsafe.Pointer(out)) = 0 |
| + foreignStore(tls, out, byte(0)) |
| } |
| } |
| func dlSym(*libc.TLS, uintptr, uintptr, uintptr) uintptr { return 0 } |
| func dlClose(*libc.TLS, uintptr, uintptr) {} |
| func randomness(tls *libc.TLS, p uintptr, n int32, out uintptr) int32 { |
| - b := bytesAt(out, int(n)) |
| + b := make([]byte, int(n)) |
| if _, err := rand.Read(b); err != nil { |
| return 0 |
| } |
| + copyToForeign(tls, out, b) |
| return n |
| } |
| func sleep(tls *libc.TLS, p uintptr, n int32) int32 { |
| @@ -411,11 +415,11 @@ func sleep(tls *libc.TLS, p uintptr, n int32) int32 { |
| return n |
| } |
| func currentTime64(tls *libc.TLS, p, out uintptr) int32 { |
| - *(*int64)(unsafe.Pointer(out)) = time.Now().UnixMilli() + 210866760000000 |
| + foreignStore(tls, out, time.Now().UnixMilli()+210866760000000) |
| return sqlite.SQLITE_OK |
| } |
| func currentTime(tls *libc.TLS, p, out uintptr) int32 { |
| - *(*float64)(unsafe.Pointer(out)) = float64(time.Now().UnixMilli())/86400000 + 2440587.5 |
| + foreignStore(tls, out, float64(time.Now().UnixMilli())/86400000+2440587.5) |
| return sqlite.SQLITE_OK |
| } |
| func sectorSize(*libc.TLS, uintptr) int32 { return 4096 } |
| @@ -428,24 +432,24 @@ func controlFile(tls *libc.TLS, p uintptr, op int32, arg uintptr) int32 { |
| if f.v.recheck() != nil { |
| moved = 1 |
| } |
| - *(*int32)(unsafe.Pointer(arg)) = moved |
| + foreignStore(tls, arg, moved) |
| return sqlite.SQLITE_OK |
| case sqlite.SQLITE_FCNTL_LOCKSTATE: |
| - *(*int32)(unsafe.Pointer(arg)) = f.lock |
| + foreignStore(tls, arg, f.lock) |
| return sqlite.SQLITE_OK |
| case sqlite.SQLITE_FCNTL_PERSIST_WAL: |
| - v := *(*int32)(unsafe.Pointer(arg)) |
| + v := foreignLoad[int32](tls, arg) |
| if v >= 0 { |
| f.persistent = v != 0 |
| } |
| if f.persistent { |
| - *(*int32)(unsafe.Pointer(arg)) = 1 |
| + foreignStore(tls, arg, int32(1)) |
| } else { |
| - *(*int32)(unsafe.Pointer(arg)) = 0 |
| + foreignStore(tls, arg, int32(0)) |
| } |
| return sqlite.SQLITE_OK |
| case sqlite.SQLITE_FCNTL_POWERSAFE_OVERWRITE: |
| - *(*int32)(unsafe.Pointer(arg)) = 0 |
| + foreignStore(tls, arg, int32(0)) |
| return sqlite.SQLITE_OK |
| default: |
| return sqlite.SQLITE_NOTFOUND |
diff --git a/internal/vfsspike/vfs_linux_test.go b/internal/vfsspike/vfs_linux_test.go| index 8b73c432c..efdfa386e 100644 |
| --- a/internal/vfsspike/vfs_linux_test.go |
| +++ b/internal/vfsspike/vfs_linux_test.go |
| @@ -286,12 +286,11 @@ func TestRefusedOpenClearsMethods(t *testing.T) { |
| t.Fatal("allocation") |
| } |
| defer sqlite.Xsqlite3_free(v.tls, out) |
| - output := (*sqlite.Tsqlite3_file)(unsafe.Pointer(out)) |
| - output.FpMethods = v.methods |
| + foreignStore(v.tls, out, sqlite.Tsqlite3_file{FpMethods: v.methods}) |
| if rc := openFile(v.tls, tc.ptr, z, out, sqlite.SQLITE_OPEN_MAIN_DB|sqlite.SQLITE_OPEN_READWRITE, 0); rc == sqlite.SQLITE_OK { |
| t.Fatal("open accepted") |
| } |
| - if output.FpMethods != 0 { |
| + if foreignLoad[sqlite.Tsqlite3_file](v.tls, out).FpMethods != 0 { |
| t.Fatal("refused open retained methods") |
| } |
| }) |