Documentation
¶
Overview ¶
Redefine Go functions at runtime
I wondered if it was possible to rewrite a Go function like some interpreted languages allow (Ruby being a prominent example). It turns out to be possible and this package is the proof-of-concept. You shouldn't use this.
Limitations:
- Only supports amd64 on Unix or Linux
- Relies on internal Go APIs that can break at any time
- Silently fails to redefine inline functions
- Silently fails to redefine generic functions
- Probably some bugs I don't know about.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Func ¶
Func redefines fn with newFn. An error will be returned if fn or newFn are not function pointers.
Note that Func only modifies non-inlined functions. Anywhere that fn has been inlined will continue with the old behavior. If possible, add a noinline directive:
//go:noinline
func myfunc() {
...
}
Other limitations that might be addressed one day:
- Generic functions cannot be redefined
- newFn cannot be a closure (anonymous functions are fine, but it will crash if you attempt to use data from the stack)
Example ¶
package main
import (
"fmt"
"time"
"github.com/pboyd/redefine"
)
func main() {
redefine.Func(time.Now, func() time.Time {
return time.Date(2000, 1, 1, 17, 0, 0, 0, time.FixedZone("somewhere", -5))
})
defer redefine.Restore(time.Now)
fmt.Printf("It's %s\n", time.Now().Format("3:04 PM MST"))
}
Output: It's 5:00 PM somewhere
func Method ¶
Method redefines a method of an object. The same caveats from Func apply here, with the new wrinkle that newFn must be a method on a type equivalent to the original type. For example:
type myCustomType otherpackage.Type
Any other type for the instance of newFn will likely lead to very troublesome bugs because the code compiled for newFn will be operating on the memory for the instance of fn.
Example ¶
package main
import (
"context"
"fmt"
"net"
"github.com/pboyd/redefine"
)
type myResolver net.Resolver
func (*myResolver) LookupHost(context.Context, string) ([]string, error) {
return []string{"127.0.0.1"}, nil
}
func main() {
redefine.Method((*net.Resolver).LookupHost, (*myResolver).LookupHost)
defer redefine.Restore((*net.Resolver).LookupHost)
addrs, _ := net.DefaultResolver.LookupHost(context.Background(), "www.google.com")
fmt.Printf("www.google.com has addresses %v", addrs)
}
Output: www.google.com has addresses [127.0.0.1]
func Original ¶
func Original[T any](fn T) T
Original returns a function with the same behavior as the original version of the function. If the function has not been redefined the original version if the passed function to that will be returned.
If the original function cannot be found for any reason Original returns nil.
Technically, this returns a copy of the original that's been relocated and had relative addresses adjusted. This process may introduce problems.
Example ¶
package main
import (
"encoding/json"
"fmt"
"github.com/pboyd/redefine"
)
func main() {
redefine.Func(json.Marshal, func(v any) ([]byte, error) {
// Pass strings through
if _, ok := v.(string); ok {
return redefine.Original(json.Marshal)(v)
}
return []byte(`{"nah": true}`), nil
})
defer redefine.Restore(json.Marshal)
buf, _ := json.Marshal("A string")
fmt.Println(string(buf))
buf, _ = json.Marshal(123)
fmt.Println(string(buf))
}
Output: "A string" {"nah": true}
Types ¶
This section is empty.