├── .github └── workflows │ └── go.yml ├── .gitignore ├── .golangci.yaml ├── LICENSE ├── README.md ├── example_c_iovec_test.go ├── example_go_iovec_test.go ├── go.mod ├── go.sum ├── internal └── testhelper │ └── testhelper.go ├── ptrguard.go ├── ptrguard_leakpanic_test.go ├── ptrguard_test.go └── runtime_hacks.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | go: [ '1.13', '1.17' ] 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Set up Go 20 | uses: actions/setup-go@v2 21 | with: 22 | go-version: ${{ matrix.go }} 23 | 24 | - name: Run golangci-lint 25 | uses: golangci/golangci-lint-action@v2 26 | with: 27 | # version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 28 | version: latest 29 | # golangci-lint command line arguments 30 | #args: # optional, default is 31 | # golangci-lint working directory, default is project root 32 | #working-directory: # optional 33 | # the token is used for fetching patch of a pull request to show only new issues 34 | #github-token: # default is ${{ github.token }} 35 | # if set to true and the action runs on a pull request - the action outputs only newly found issues 36 | #only-new-issues: 37 | # if set to true then action uses pre-installed Go 38 | skip-go-installation: true 39 | # if set to true then the action don't cache or restore ~/go/pkg. 40 | #skip-pkg-cache: 41 | # if set to true then the action don't cache or restore ~/.cache/go-build. 42 | #skip-build-cache: 43 | 44 | - name: Build 45 | run: go build -v 46 | 47 | - name: Test 48 | run: go test -v 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable-all: true 3 | disable: 4 | # deprecated linters 5 | - golint 6 | - scopelint 7 | - maligned 8 | - interfacer 9 | # disabled linters 10 | - wsl 11 | - nlreturn 12 | - gochecknoglobals 13 | - paralleltest 14 | - exhaustivestruct 15 | 16 | issues: 17 | exclude-use-default: false 18 | exclude: 19 | - "^G103:" 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sven Anderson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PtrGuard 2 | [![Build Status](https://github.com/ansiwen/ptrguard/actions/workflows/go.yml/badge.svg)](https://github.com/ansiwen/ptrguard/actions) 3 | [![Go Reference](https://pkg.go.dev/badge/github.com/ansiwen/ptrguard.svg)](https://pkg.go.dev/github.com/ansiwen/ptrguard) 4 | 5 | PtrGuard is a small Go package that allows to pin objects referenced by a Go 6 | pointer (that is pointing to memory allocated by the Go runtime) so that it will 7 | not be touched by the garbage collector. This is done by creating a `Pinner` 8 | object that has a `Pin()` method, which accepts a pointer of any type and pins 9 | the referenced object, until the `Unpin()` method of the same `Pinner` is 10 | called. A `Pinner` can be used to pin more than one object, in which case 11 | `Unpin()` releases all the pinned objects of a `Pinner`. 12 | 13 | Go pointers to pinned objects can either be directly stored in C memory with the 14 | `Store()` method, or are allowed to be contained in Go memory that is passed to 15 | C functions, which both usually violates the [pointer passing 16 | rules](https://golang.org/cmd/cgo/#hdr-Passing_pointers). In the second case you 17 | might need the `NoCheck()` helper function to call the C function in a context, 18 | where the cgocheck debug feature is disabled. This is necessary because PtrGuard 19 | doesn't have any possibility to tell cgocheck, that certain pointers are pinned. 20 | 21 | ## Example 22 | Let's say we want to use a C API that uses [vectored 23 | I/O](https://en.wikipedia.org/wiki/Vectored_I/O), like the 24 | [`readv()`](https://pubs.opengroup.org/onlinepubs/000095399/functions/readv.html) 25 | POSIX system call, in order to read data into an array of buffers. Because we 26 | want to avoid making a copy of the data, we want to read directly into Go 27 | buffers. The pointer passing rules wouldn't allow that, because 28 | * either we can allocate the buffer array in C memory, but then we can't store 29 | the pointers of the Go buffers in it. (Storing Go pointers in C memory is 30 | forbidden.) 31 | * or we would allocate the buffer array in Go memory and store the Go buffers in 32 | it. But then we can't pass the pointer to that buffer array to a C function. 33 | (Passing a Go pointer that points to memory containing other Go pointers to a 34 | C function is forbidden.) 35 | 36 | With PtrGuard both is still possible. (See examples.) 37 | -------------------------------------------------------------------------------- /example_c_iovec_test.go: -------------------------------------------------------------------------------- 1 | package ptrguard_test 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | 7 | "github.com/ansiwen/ptrguard" 8 | C "github.com/ansiwen/ptrguard/internal/testhelper" 9 | ) 10 | 11 | // Example how to use PtrGuard with a C allocated iovec array. 12 | func Example_cAllocatedIovec() { 13 | var buffers [][]byte 14 | for i := 2; i < 12; i += 3 { 15 | buffers = append(buffers, make([]byte, i)) 16 | } 17 | numberOfBuffers := len(buffers) 18 | 19 | cPtr := C.Malloc(C.SizeOfIovec * uintptr(len(buffers))) 20 | defer C.Free(cPtr) 21 | // This is a trick to create a slice on top of the C allocated array, for 22 | // easier and safer access. 23 | iovec := (*[math.MaxInt32]C.Iovec)(cPtr)[:numberOfBuffers:numberOfBuffers] 24 | 25 | var pinner ptrguard.Pinner 26 | defer pinner.Unpin() 27 | for i := range iovec { 28 | bufferPtr := &buffers[i][0] 29 | pinner.Pin(bufferPtr).Store(&iovec[i].Base) 30 | iovec[i].Len = C.Int(len(buffers[i])) 31 | } 32 | 33 | C.FillBuffersWithX(&iovec[0], len(iovec)) 34 | 35 | for i := range buffers { 36 | fmt.Println(string(buffers[i])) 37 | } 38 | // Output: 39 | // XX 40 | // XXXXX 41 | // XXXXXXXX 42 | // XXXXXXXXXXX 43 | } 44 | -------------------------------------------------------------------------------- /example_go_iovec_test.go: -------------------------------------------------------------------------------- 1 | package ptrguard_test 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | 7 | "github.com/ansiwen/ptrguard" 8 | C "github.com/ansiwen/ptrguard/internal/testhelper" 9 | ) 10 | 11 | // Example how to use PtrGuard with a Go allocated iovec slice. 12 | func Example_goAllocatedIovec() { 13 | var buffers [][]byte 14 | for i := 2; i < 12; i += 3 { 15 | buffers = append(buffers, make([]byte, i)) 16 | } 17 | 18 | iovec := make([]C.Iovec, len(buffers)) 19 | 20 | var pinner ptrguard.Pinner 21 | defer pinner.Unpin() 22 | for i := range iovec { 23 | bufferPtr := &buffers[i][0] 24 | pinner.Pin(bufferPtr) 25 | iovec[i].Base = unsafe.Pointer(bufferPtr) 26 | iovec[i].Len = C.Int(len(buffers[i])) 27 | } 28 | 29 | ptrguard.NoCheck(func() { 30 | C.FillBuffersWithX(&iovec[0], len(iovec)) 31 | }) 32 | 33 | for i := range buffers { 34 | fmt.Println(string(buffers[i])) 35 | } 36 | // Output: 37 | // XX 38 | // XXXXX 39 | // XXXXXXXX 40 | // XXXXXXXXXXX 41 | } 42 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ansiwen/ptrguard 2 | 3 | go 1.13 4 | 5 | require github.com/stretchr/testify v1.7.0 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 6 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 7 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 8 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 10 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 11 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 12 | -------------------------------------------------------------------------------- /internal/testhelper/testhelper.go: -------------------------------------------------------------------------------- 1 | // package testhelper 2 | package testhelper 3 | 4 | /* 5 | #include 6 | 7 | void dummyCall(void* p) {} 8 | 9 | typedef struct { 10 | void* Base; 11 | int Len; 12 | } iovec; 13 | 14 | inline void fillBufsWithX(iovec* bufs, int n) { 15 | for (int i = 0; i