diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-19 08:40:55 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-19 08:40:55 +0300 |
| commit | 42013b111a3dadd603b423a0bcc4bcbd02da2add (patch) | |
| tree | fd1c3dbdccc0298ee11961f115a14fe18a1be9c5 /internal | |
| parent | 8dbec6969ceff32662c41bd267210c34b90d8810 (diff) | |
Harden action handler registration for nk0
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/hexaiaction/action_handler.go | 20 | ||||
| -rw-r--r-- | internal/hexaiaction/action_handler_test.go | 15 |
2 files changed, 34 insertions, 1 deletions
diff --git a/internal/hexaiaction/action_handler.go b/internal/hexaiaction/action_handler.go index 8da90ea..c01edbd 100644 --- a/internal/hexaiaction/action_handler.go +++ b/internal/hexaiaction/action_handler.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io" + "reflect" "sync" "codeberg.org/snonux/hexai/internal/appconfig" @@ -28,6 +29,9 @@ type CodeActionHandler = ActionHandler type actionHandlerFunc func(context.Context, actionRequest) (string, error) func (f actionHandlerFunc) Execute(ctx context.Context, req actionRequest) (string, error) { + if f == nil { + return "", fmt.Errorf("hexaiaction: nil action handler") + } return f(ctx, req) } @@ -44,7 +48,7 @@ func (r *actionHandlerRegistry) register(kind ActionKind, handler ActionHandler) if kind == "" { panic("hexaiaction: cannot register empty action kind") } - if handler == nil { + if isNilActionHandler(handler) { panic(fmt.Sprintf("hexaiaction: cannot register nil handler for %q", kind)) } @@ -56,6 +60,20 @@ func (r *actionHandlerRegistry) register(kind ActionKind, handler ActionHandler) r.handlers[kind] = handler } +func isNilActionHandler(handler ActionHandler) bool { + if handler == nil { + return true + } + + value := reflect.ValueOf(handler) + switch value.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: + return value.IsNil() + default: + return false + } +} + func (r *actionHandlerRegistry) lookup(kind ActionKind) (ActionHandler, bool) { r.mu.RLock() defer r.mu.RUnlock() diff --git a/internal/hexaiaction/action_handler_test.go b/internal/hexaiaction/action_handler_test.go index 1d811fe..92df3b1 100644 --- a/internal/hexaiaction/action_handler_test.go +++ b/internal/hexaiaction/action_handler_test.go @@ -55,6 +55,10 @@ func TestActionHandlerRegistryRejectsInvalidRegistrations(t *testing.T) { "nil handler": func(registry *actionHandlerRegistry) { registry.register(ActionKind("nil"), nil) }, + "typed nil handler": func(registry *actionHandlerRegistry) { + var handler actionHandlerFunc + registry.register(ActionKind("typed-nil"), handler) + }, "duplicate kind": func(registry *actionHandlerRegistry) { registry.register(ActionKind("dup"), actionHandlerFunc(handleSkipAction)) registry.register(ActionKind("dup"), actionHandlerFunc(handleSkipAction)) @@ -85,3 +89,14 @@ func TestActionHandlerRegistryNilPanicNamesKind(t *testing.T) { }() newActionHandlerRegistry().register(ActionCustom, nil) } + +func TestActionHandlerFuncNilExecuteReturnsError(t *testing.T) { + var handler actionHandlerFunc + _, err := handler.Execute(context.Background(), actionRequest{}) + if err == nil { + t.Fatal("expected error") + } + if !strings.Contains(err.Error(), "nil action handler") { + t.Fatalf("expected nil handler error, got %v", err) + } +} |
