Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/buildimage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ env:
jobs:
build-osx:
name: Build for Darwin x86_64
runs-on: macos-13
runs-on: macos-15-intel
steps:
- name: Install PostgreSQL@14
run: brew install --force postgresql@14
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## v2.1.5 [2026-02-06]
_Bug fixes_
- Fix memory leaks caused by unfreed `C.CString()` allocations across multiple functions. ([#618](https://github.com/turbot/steampipe-postgres-fdw/pull/618), [#620](https://github.com/turbot/steampipe-postgres-fdw/pull/620), [#622](https://github.com/turbot/steampipe-postgres-fdw/pull/622), [#624](https://github.com/turbot/steampipe-postgres-fdw/pull/624), [#631](https://github.com/turbot/steampipe-postgres-fdw/pull/631))

## v2.1.4 [2025-11-20]
_Dependencies_
- Upgraded dependencies to remediate vulnerabilities.
Expand Down
7 changes: 6 additions & 1 deletion explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package main
#include "fdw_helpers.h"
*/
import "C"
import "unsafe"

// Explainable is an optional interface for Iterator that can explain it's execution plan.
type Explainable interface {
Expand All @@ -20,5 +21,9 @@ type Explainer struct {

// Property adds a key-value property to results of EXPLAIN query.
func (e Explainer) Property(k, v string) {
C.ExplainPropertyText(C.CString(k), C.CString(v), e.ES)
ck := C.CString(k)
cv := C.CString(v)
defer C.free(unsafe.Pointer(ck))
defer C.free(unsafe.Pointer(cv))
C.ExplainPropertyText(ck, cv, e.ES)
}
5 changes: 4 additions & 1 deletion fdw.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,11 @@ func goFdwExplainForeignScan(node *C.ForeignScanState, es *C.ExplainState) {

//export goFdwBeginForeignScan
func goFdwBeginForeignScan(node *C.ForeignScanState, eflags C.int) {
// Outer recover: catches panics during early initialization (before inner defer is registered)
// This provides defense-in-depth - if the inner defer's recovery fails, this catches it
defer func() {
if r := recover(); r != nil {
log.Printf("[WARN] goFdwExplainForeignScan failed with panic: %v", r)
log.Printf("[WARN] goFdwBeginForeignScan failed with panic during early init: %v", r)
FdwError(fmt.Errorf("%v", r))
}
}()
Expand All @@ -285,6 +287,7 @@ func goFdwBeginForeignScan(node *C.ForeignScanState, eflags C.int) {

log.Printf("[INFO] goFdwBeginForeignScan, connection '%s', table '%s', explain: %v \n", opts["connection"], opts["table"], explain)

// Inner recover: catches panics during main scan processing
defer func() {
if r := recover(); r != nil {
log.Printf("[WARN] goFdwBeginForeignScan failed with panic: %v", r)
Expand Down
13 changes: 11 additions & 2 deletions helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ func ValToDatum(val interface{}, cinfo *C.ConversionInfo, buffer C.StringInfo) (
}
}()
// init an empty return result
datum := C.fdw_cStringGetDatum(C.CString(""))
// Allocate CString, use it, then immediately free to avoid memory leak
// Using explicit C.free() instead of defer because this is a hot path
emptyStr := C.CString("")
datum := C.fdw_cStringGetDatum(emptyStr)
C.free(unsafe.Pointer(emptyStr))

// write value into C buffer
if err := valToBuffer(val, cinfo.atttypoid, buffer); err != nil {
Expand Down Expand Up @@ -197,7 +201,12 @@ func valToBuffer(val interface{}, oid C.Oid, buffer C.StringInfo) (err error) {
}

C.resetStringInfo(buffer)
C.fdw_appendBinaryStringInfo(buffer, C.CString(valueString), C.int(len(valueString)))
// Allocate CString, use it, then immediately free to avoid memory leak
// Using explicit C.free() instead of defer because this is a hot path
// called for every string column in every row
cValueString := C.CString(valueString)
C.fdw_appendBinaryStringInfo(buffer, cValueString, C.int(len(valueString)))
C.free(unsafe.Pointer(cValueString))
return
}

Expand Down
8 changes: 7 additions & 1 deletion schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,13 @@ func SchemaToSql(schema map[string]*proto.TableSchema, stmt *C.ImportForeignSche
}

log.Printf("[TRACE] SQL %s", sql)
commands = C.lappend(commands, unsafe.Pointer(C.CString(sql)))
// Use pstrdup to allocate in PostgreSQL memory context.
// This ensures the string is freed when the memory context is destroyed,
// avoiding a memory leak from C.CString which allocates on the C heap.
cSql := C.CString(sql)
pSql := C.pstrdup(cSql)
C.free(unsafe.Pointer(cSql))
commands = C.lappend(commands, unsafe.Pointer(pSql))
}

return commands
Expand Down
Loading