github.com/AlaxLee/go-forceexport@v1.0.3/README.md (about) 1 # go-forceexport 2 3 go-forceexport is a golang package that allows access to any module-level 4 function, even ones that are not exported. You give it the string name of a 5 function , like `"time.now"`, and gives you a function value that calls that 6 function. More generally, it can be used to achieve something like reflection on 7 top-level functions, whereas the `reflect` package only lets you access methods 8 by name. 9 10 As you might expect, this library is **unsafe** and **fragile** and probably 11 shouldn't be used in production. See "Use cases and pitfalls" below. 12 13 It has only been tested on Mac OS X with Go 1.14/1.15/1.16. If you find that it works or 14 breaks on other platforms, feel free to submit a pull request with a fix and/or 15 an update to this paragraph. 16 17 ## Installation 18 19 `$ go get github.com/AlaxLee/go-forceexport` 20 21 ## Usage 22 23 Here's how you can grab the `time.now` function, defined as 24 `func now() (sec int64, nsec int32)` 25 26 ```go 27 var timeNow func() (int64, int32) 28 err := forceexport.GetFunc(&timeNow, "time.now") 29 if err != nil { 30 // Handle errors if you care about name possibly being invalid. 31 } 32 // Calls the actual time.now function. 33 sec, nsec := timeNow() 34 ``` 35 36 The string you give should be the fully-qualified name. For example, here's 37 `GetFunc` getting itself. 38 39 ```go 40 var getFunc func(interface{}, string) error 41 GetFunc(&getFunc, "github.com/AlaxLee/go-forceexport.GetFunc") 42 ``` 43 44 **NOTICE:** Sometimes `GetFunc` could not find the wanted function. There are usually two reasons: 45 46 1. The function is optimized(inline). For example,to get the `time.(*Time).unixSec` function. 47 ```go 48 // unixSec returns the time's seconds since Jan 1 1970 (Unix time). 49 // func (t *Time) unixSec() int64 { return t.sec() + internalToUnix } 50 var unixSec func(time *time.Time) int64 51 err := forceexport.GetFunc(&unixSec, "time.(*Time).unixSec") 52 if err != nil { 53 // Handle errors if you care about name possibly being invalid. 54 panic(err) 55 } 56 usec := unixSec(&time.Time{}) 57 fmt.Println(usec) 58 fmt.Println(time.Time{}.Unix()) // time.Time.Unix is same as time.(*Time).unixSec 59 ``` 60 We will receive an error "Invalid function name: time.(*Time).unixSec" 61 ```text 62 AlaxdeMacBook-Pro:example alax$ go run main.go 63 panic: Invalid function name: time.(*Time).unixSec 64 ``` 65 But if we don’t use optimization with flag "-l", we will get it successfully. 66 ```text 67 AlaxdeMacBook-Pro:example alax$ go run -gcflags "all=-l" main.go 68 -62135596800 69 -62135596800 70 ``` 71 We can use "go build --gcflags=-m" to detect if optimized. 72 ```text 73 AlaxdeMacBook-Pro:example alax$ cd $GOROOT/src/time 74 AlaxdeMacBook-Pro:time alax$ go build --gcflags=-m 2>&1 |grep -i inline|grep -i unixSec 75 ./time.go:176:6: can inline (*Time).unixSec 76 ``` 77 78 2. The function is not used. For example,to get the `go/types.(*Checker).representable` function. 79 ```go 80 //must be kept in sync with operand in src/go/types/operand.go 81 type operandMode byte 82 type builtinId int 83 type operand struct { 84 mode operandMode 85 expr ast.Expr 86 typ types.Type 87 val constant.Value 88 id builtinId 89 } 90 //must same as method (*Checker).representable in src/go/types/expr.go 91 var _representable func(checker *types.Checker, x *operand, typ *types.Basic) 92 // 将 _representable 映射为 go/types.(*Checker).representable 93 err := forceexport.GetFunc(&_representable, "go/types.(*Checker).representable") 94 if err != nil { 95 panic(err) 96 } else { 97 fmt.Println("OK") 98 } 99 ``` 100 101 We will receive an error "Invalid function name: go/types.(*Checker).representable". 102 And it isn't because of optimized. 103 ```text 104 AlaxdeMacBook-Pro:example alax$ go run main.go 105 panic: Invalid function name: go/types.(*Checker).representable 106 AlaxdeMacBook-Pro:example alax$ go run -gcflags "all=-l" main.go 107 panic: Invalid function name: go/types.(*Checker).representable 108 AlaxdeMacBook-Pro:example alax$ cd $GOROOT/src/go/types 109 AlaxdeMacBook-Pro:types alax$ grep ' representable(' *.go 110 expr.go:func (check *Checker) representable(x *operand, typ *Basic) { 111 AlaxdeMacBook-Pro:types alax$ go build --gcflags=-m 2>&1 | grep -i representable 112 AlaxdeMacBook-Pro:types alax$ 113 ``` 114 Before `GetFunc`, we use it. 115 ```go 116 var packageName = "haha" 117 var code = ` 118 package haha 119 func main() {} 120 ` 121 var err error 122 fset := token.NewFileSet() 123 file, err := parser.ParseFile(fset, packageName+".go", code, 0) 124 if err != nil { 125 log.Panicf("parse code failed: %s", err) 126 } 127 c := new(types.Config) 128 c.Error = func(err error) {} 129 pkg := types.NewPackage(packageName, "") 130 checker := types.NewChecker(c, fset, pkg, nil) 131 err = checker.Files([]*ast.File{file}) // (* Checker).Files use the wanted function 132 if err != nil { 133 log.Panicf("check file failed: %s", err) 134 } 135 136 //must be kept in sync with operand in src/go/types/operand.go 137 type operandMode byte 138 type builtinId int 139 type operand struct { 140 mode operandMode 141 expr ast.Expr 142 typ types.Type 143 val constant.Value 144 id builtinId 145 } 146 //must same as method (*Checker).representable in src/go/types/expr.go 147 var _representable func(checker *types.Checker, x *operand, typ *types.Basic) 148 // 将 _representable 映射为 go/types.(*Checker).representable 149 err = forceexport.GetFunc(&_representable, "go/types.(*Checker).representable") 150 if err != nil { 151 panic(err) 152 } else { 153 fmt.Println("OK") 154 } 155 ``` 156 And we got it. 157 ```text 158 AlaxdeMacBook-Pro:example alax$ go run main.go 159 OK 160 ``` 161 Maybe the call link is as follows. 162 ```text 163 func (check *Checker) Files(files []*ast.File) error 164 -> func (check *Checker) checkFiles(files []*ast.File) (err error) 165 -> func (check *Checker) packageObjects() 166 -> func (check *Checker) objDecl(obj Object, def *Named) 167 -> func (check *Checker) funcDecl(obj *Func, decl *declInfo) 168 -> func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body *ast.BlockStmt, iota constant.Value) 169 -> func (check *Checker) stmtList(ctxt stmtContext, list []ast.Stmt) 170 -> func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) 171 -> func (check *Checker) rawExpr(x *operand, e ast.Expr, hint Type) exprKind 172 -> func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind 173 -> func (check *Checker) binary(x *operand, e *ast.BinaryExpr, lhs, rhs ast.Expr, op token.Token, opPos token.Pos) 174 -> func (check *Checker) shift(x, y *operand, e *ast.BinaryExpr, op token.Token) 175 -> func (check *Checker) representable(x *operand, typ *Basic) 176 ``` 177 178 ## Use cases and pitfalls 179 180 This library is most useful for development and hack projects. For example, you 181 might use it to track down why the standard library isn't behaving as you 182 expect, or you might use it to try out a standard library function to see if it 183 works, then later factor the code to be less fragile. You could also try using 184 it in production; just make sure you're aware of the risks. 185 186 There are lots of things to watch out for and ways to shoot yourself in 187 the foot: 188 * If you define the wrong function type, you'll get a function with undefined 189 behavior that will likely cause a runtime panic. The library makes no attempt 190 to warn you in this case. 191 * Calling unexported functions is inherently fragile because the function won't 192 have any stability guarantees. 193 * The implementation relies on the details of internal Go data structures, so 194 later versions of Go might break this library. 195 * Since the compiler doesn't expect unexported symbols to be used, it might not 196 create them at all, for example due to inlining or dead code analysis. This 197 means that functions may not show up like you expect, and new versions of the 198 compiler may cause functions to suddenly disappear. 199 * If the function you want to use relies on unexported types, you won't be able 200 to trivially use it. However, you can sometimes work around this by defining 201 equivalent copies of those types that you can use, but that approach has its 202 own set of dangers. 203 204 ## How it works 205 206 The [code](/forceexport.go) is pretty short, so you could just read it, but 207 here's a friendlier explanation: 208 209 The code uses the `go:linkname` compiler directive to get access to the 210 `runtime.firstmoduledata` symbol, which is an internal data structure created by 211 the linker that's used by functions like `runtime.FuncForPC`. (Using 212 `go:linkname` is an alternate way to access unexported functions/values, but it 213 has other gotchas and can't be used dynamically.) 214 215 Similar to the implementation of `runtime.FuncForPC`, the code walks the 216 function definitions until it finds one with a matching name, then gets its code 217 pointer. 218 219 From there, it creates a function object from the code pointer by calling 220 `reflect.MakeFunc` and using `unsafe.Pointer` to swap out the function object's 221 code pointer with the desired one. 222 223 Needless to say, it's a scary hack, but it seems to work! 224 225 ## Thanks 226 227 https://github.com/alangpierce/go-forceexport 228 229 https://github.com/linux4life798/go-forceexport 230 231 https://github.com/zhuzhengyang/go-forceexport 232 233 ## License 234 235 MIT