Accessing Private Functions, Methods, Types and Variables in Go
Have you ever found yourself cursing at your computer because you needed to access a private function, method, global variable or type in Go? Did it seem practically impossible? Well — it’s not, and this blog post is for you. Everything written below should NOT be considered a recommendation or an example of good Go code design.
Background
The way to distinguish between private and public in Go is by the first letter of the name. If the letter is lowercase, it is private. If it is uppercase, it is public. The scope of a private function/method/type is the package in which it is defined.
It’s possible to write large Go programs without resorting to using a hack to access private information. In fact, it’s advisable to avoid doing so. That said, there are extreme circumstances that require such a hack. That, along with pure curiosity, led me to write this blog post. Let’s begin!
//go:linkname
To access private functions, global variables and methods, you can use the linkname compiler directive. In a sentence, this directive allows you to link a declaration in one place to a definition in another. When the compiler sees the linkname comment, it makes the declaration a “link” (hence the name) to the original definition. The solution that allows access to private types is slightly less conventional, so we’ll dive into that later.
Go runtime
Let’s start with the most common use case: running a private function from the Go runtime. Many public functions in the standard library have private alternatives in the runtime that perform better but have certain limitations. An example of such a function is rand.Uint32
and its private runtime counterpart runtime.fastrand
. runtime.fastrand
does not allow seeding, but it performs much better than rand.Uint32
.
Let’s say we have a function that generates and uses a random number:
We can’t just swap out rand.Uint32
with an internal implementation:
Instead, we’ll copy the runtime.fastrand
function signature (the names here don’t matter, only the types):
|
|
To “link” our function declaration with the definition of runtime.fastrand
, we’ll add the linkname compiler directive in the format
|
|
In this case, that would look like:
Now, we can use runtime_fastrand
as we would use runtime.fastrand
:
User package
Similarly, we can link a private function from a user-written package to a function definition.
For this example, our module name will be github.com/YardenLaif/example. Under example
, we have the following directories and files:
In a.go
there is a private function named foo
:
We’d like to test this function in a_test.go
, so we’ll use the same format for the linkname directive and add the full path for the a
package:
Global Variables
Global variables can be linked in the same way. If our a
package had a global variable:
We can access globalVar
from the a_test
package using the linkname directive. I’ve found that importing the package the global variable is defined in is necessary when working with global variables.
This might look confusing, but the globalVar
defined in a
and the a_globalVar
defined in a_test
are the same variable. That means that its initial value is 55
and any changes in one will affect the other.
const
s or non-global variables.
Pointer Receiver Methods
We’re differentiating between pointer receiver methods: func (t *Type) foo()
, and value receiver methods: func (t Type) foo()
. For the latter, skip to the next section.
Methods deserve their own section since they require linking a function to a method. We’ll define a private method with a pointer receiver in the a
package:
In a_test
, instead of copying the method signature as-is, we’ll change it to a function signature and have the method’s receiver be the first parameter:
To specify the method we’re linking to, we’ll use the method receiver type along with the method’s name in the following format:
<method package>.(<method receiver type>)<method name>
We’ll put that in the linkname directive:
|
|
Value Receiver Methods
The way to link to value receiver methods is counterintuitive because it’s the same as linking to a pointer receiver. The function will have a pointer parameter even though the method has a value receiver. So, for this private method:
We’ll pass a pointer (not a value, as is passed in the original method!) as the first parameter:
|
|
Private Types
There is no compiler trick to access private types, but this trick will allow you to access private fields on private (or public) types. Combined with the linkname directive, this will enable you to access private methods on private types.
Private Fields
We’ll start by defining a private field in a type in the a
package, and a function that returns that type:
Next, we’ll copy the type in the a_test
package:
Finally, we’ll convert a
’s internalType
to a_test
’s a_internalType
wherever necessary. We’ll do this by playing with pointers and unsafe.Pointer
in the following way:
Now, we can use convertedI
and access internalField
. Once again, this is not a recommendation and must be used carefully.
|
|
Private Methods
Last but certainly not least is the ability to access private methods on private types.
We’ll define a private method of a private type in the a
package, and a function that returns that type:
Once again, we’ll copy the internal type and use pointers to convert an object of a
’s internalType
to a_test
’s internalType
:
|
|
At this point, we can use the linkname directive and pass a_test
’s internalType
as the first parameter:
|
|
This works because the go compiler doesn’t check that the function declaration matches the function definition. In this case, that works in our favor, but it’s also another reason using linkname is dangerous and can cause runtime crashes.
Closing Notes
There is a way to do almost anything in Go if you try hard enough and are willing to risk your code’s safety. Using the “hacks” written here lets you access practically anything regardless of the creator’s intentions. On a personal note, a small part of me hopes this doesn’t help you, as these practices genuinely aren’t recommended. However, I do hope this piqued your interest!