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