Skip to content

Commit 91bbc00

Browse files
Merge pull request #3 from illuin-tech/panic-safe-invoke
fix: make invoke panic safe
2 parents be7a9a5 + 186dd59 commit 91bbc00

1 file changed

Lines changed: 33 additions & 7 deletions

File tree

injector.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,14 @@ func (injector *Injector) Shutdown() {
7878

7979
// Invoke will execute the parameter function (which must be a function that optionally can return an error).
8080
// argument of function will be resolved by the injector using configured providers & scope.
81-
func (injector *Injector) Invoke(ctx context.Context, function any) error {
81+
func (injector *Injector) Invoke(ctx context.Context, function any) (err error) {
82+
// recover from potential panics in user code or reflection
83+
defer func() {
84+
if r := recover(); r != nil {
85+
err = fmt.Errorf("panic during invocation: %v", r)
86+
}
87+
}()
88+
8289
if function == nil {
8390
return newInvalidInputError("can't invoke on nil")
8491
}
@@ -89,6 +96,11 @@ func (injector *Injector) Invoke(ctx context.Context, function any) error {
8996
fmt.Sprintf("can't invoke non-function %v (type %v)", function, ftype))
9097
}
9198

99+
// 2. check for Typed Nil, reflect.Call on a nil function value causes a panic.
100+
if fvalue.IsNil() {
101+
return newInvalidInputError(fmt.Sprintf("can't invoke nil function %v", ftype))
102+
}
103+
92104
if ftype.NumOut() > 1 || (ftype.NumOut() == 1 && !ftype.Out(0).AssignableTo(errorReflectType)) {
93105
return newInvalidInputError("can't invoke on function whose return type is not error or no return type")
94106
}
@@ -97,13 +109,27 @@ func (injector *Injector) Invoke(ctx context.Context, function any) error {
97109
if err != nil {
98110
return fmt.Errorf("failed to call invokation function: %w", err)
99111
}
100-
if ftype.NumOut() == 1 {
101-
invokationError := res[0].Interface().(error)
102-
if invokationError != nil {
103-
return fmt.Errorf("invokation returned error: %w", invokationError)
104-
}
112+
if ftype.NumOut() == 0 {
113+
return nil
105114
}
106-
return nil
115+
116+
// check slice access
117+
if len(res) == 0 {
118+
return fmt.Errorf("function expected to return error but reflection result was empty")
119+
}
120+
121+
val := res[0].Interface()
122+
// handle the case where the interface is nil (no error returned)
123+
if val == nil {
124+
return nil
125+
}
126+
127+
// Assert it is an error
128+
if invokationError, ok := val.(error); ok {
129+
return fmt.Errorf("invokation returned error: %w", invokationError)
130+
}
131+
// fallback for edge cases where AssignableTo passed but assertion failed
132+
return fmt.Errorf("return value was not an error interface: %v", val)
107133
}
108134

109135
func (injector *Injector) eagerlyCreateSingletons() error {

0 commit comments

Comments
 (0)