Skip to content
Open
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
4 changes: 4 additions & 0 deletions statement.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ func (stmt *Statement) AddVar(writer clause.Writer, vars ...interface{}) {
writer.WriteString(subdb.Statement.SQL.String())
stmt.Vars = subdb.Statement.Vars
default:
if valuer := GetDynamicGormValuer(reflect.TypeOf(v)); valuer != nil {
Comment thread
iseki0 marked this conversation as resolved.
stmt.AddVar(writer, valuer(stmt.Context, stmt.DB, v))
Comment thread
iseki0 marked this conversation as resolved.
return
}
switch rv := reflect.ValueOf(v); rv.Kind() {
case reflect.Slice, reflect.Array:
if rv.Len() == 0 {
Expand Down
19 changes: 19 additions & 0 deletions valuer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package gorm

import (
"context"
"reflect"

"gorm.io/gorm/clause"
)

var dynamicRegisteredGormValuer = make(map[reflect.Type]func(context.Context, *DB, any) clause.Expr)

// RegisterDynamicGormValuerInit shouldn't be called outside the init function
func RegisterDynamicGormValuerInit(valueType reflect.Type, valuer func(context.Context, *DB, any) clause.Expr) {
dynamicRegisteredGormValuer[valueType] = valuer
Comment on lines +10 to +14
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CriticalError]

Race condition: The global map dynamicRegisteredGormValuer is written to without synchronization. If multiple packages call RegisterDynamicGormValuerInit concurrently during init() (which can happen when Go initializes packages in parallel), this will cause a data race. While the comment says "shouldn't be called outside the init function", Go doesn't guarantee sequential execution of different packages' init() functions.

Fix: Use sync.Map or protect with sync.RWMutex:

var (
    dynamicRegisteredGormValuer = make(map[reflect.Type]func(context.Context, *DB, any) clause.Expr)
    dynamicValuerMutex sync.RWMutex
)

func RegisterDynamicGormValuerInit(valueType reflect.Type, valuer func(context.Context, *DB, any) clause.Expr) {
    dynamicValuerMutex.Lock()
    defer dynamicValuerMutex.Unlock()
    dynamicRegisteredGormValuer[valueType] = valuer
}

func GetDynamicGormValuer(valueType reflect.Type) func(context.Context, *DB, any) clause.Expr {
    dynamicValuerMutex.RLock()
    defer dynamicValuerMutex.RUnlock()
    return dynamicRegisteredGormValuer[valueType]
}
Context for Agents
**Race condition**: The global map `dynamicRegisteredGormValuer` is written to without synchronization. If multiple packages call `RegisterDynamicGormValuerInit` concurrently during `init()` (which can happen when Go initializes packages in parallel), this will cause a data race. While the comment says "shouldn't be called outside the init function", Go doesn't guarantee sequential execution of different packages' `init()` functions.

**Fix**: Use `sync.Map` or protect with `sync.RWMutex`:

```go
var (
    dynamicRegisteredGormValuer = make(map[reflect.Type]func(context.Context, *DB, any) clause.Expr)
    dynamicValuerMutex sync.RWMutex
)

func RegisterDynamicGormValuerInit(valueType reflect.Type, valuer func(context.Context, *DB, any) clause.Expr) {
    dynamicValuerMutex.Lock()
    defer dynamicValuerMutex.Unlock()
    dynamicRegisteredGormValuer[valueType] = valuer
}

func GetDynamicGormValuer(valueType reflect.Type) func(context.Context, *DB, any) clause.Expr {
    dynamicValuerMutex.RLock()
    defer dynamicValuerMutex.RUnlock()
    return dynamicRegisteredGormValuer[valueType]
}
```

File: valuer.go
Line: 14

}

func GetDynamicGormValuer(valueType reflect.Type) func(context.Context, *DB, any) clause.Expr {
return dynamicRegisteredGormValuer[valueType]
}
Loading