Skip to content

Commit f9b9359

Browse files
Ensure that no pointer handles leak during the test (#712) (#716)
This change makes sure that pointer handles are correctly cleaned up during tests. (cherry picked from commit e28cce8) Co-authored-by: lhchavez <[email protected]>
1 parent f09f9c8 commit f9b9359

9 files changed

+80
-34
lines changed

clone.go

+12-14
Original file line numberDiff line numberDiff line change
@@ -55,38 +55,36 @@ func Clone(url string, path string, options *CloneOptions) (*Repository, error)
5555

5656
//export remoteCreateCallback
5757
func remoteCreateCallback(
58-
cremote unsafe.Pointer,
59-
crepo unsafe.Pointer,
58+
out **C.git_remote,
59+
crepo *C.git_repository,
6060
cname, curl *C.char,
61-
payload unsafe.Pointer,
61+
handle unsafe.Pointer,
6262
) C.int {
6363
name := C.GoString(cname)
6464
url := C.GoString(curl)
65-
repo := newRepositoryFromC((*C.git_repository)(crepo))
66-
// We don't own this repository, so make sure we don't try to free it
67-
runtime.SetFinalizer(repo, nil)
65+
repo := newRepositoryFromC(crepo)
66+
repo.weak = true
67+
defer repo.Free()
6868

69-
data, ok := pointerHandles.Get(payload).(*cloneCallbackData)
69+
data, ok := pointerHandles.Get(handle).(*cloneCallbackData)
7070
if !ok {
7171
panic("invalid remote create callback")
7272
}
7373

7474
remote, ret := data.options.RemoteCreateCallback(repo, name, url)
75-
// clear finalizer as the calling C function will
76-
// free the remote itself
77-
runtime.SetFinalizer(remote, nil)
78-
7975
if ret < 0 {
8076
*data.errorTarget = errors.New(ErrorCode(ret).String())
8177
return C.int(ErrorCodeUser)
8278
}
83-
8479
if remote == nil {
8580
panic("no remote created by callback")
8681
}
8782

88-
cptr := (**C.git_remote)(cremote)
89-
*cptr = remote.ptr
83+
*out = remote.ptr
84+
85+
// clear finalizer as the calling C function will
86+
// free the remote itself
87+
runtime.SetFinalizer(remote, nil)
9088

9189
return C.int(ErrorCodeOK)
9290
}

clone_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,5 @@ func TestCloneWithCallback(t *testing.T) {
7474
if err != nil || remote == nil {
7575
t.Fatal("Remote was not created properly")
7676
}
77+
defer remote.Free()
7778
}

git_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,33 @@
11
package git
22

33
import (
4+
"fmt"
45
"io/ioutil"
56
"os"
67
"path"
8+
"reflect"
79
"testing"
810
"time"
911
)
1012

13+
func TestMain(m *testing.M) {
14+
ret := m.Run()
15+
16+
// Ensure that we are not leaking any pointer handles.
17+
pointerHandles.Lock()
18+
if len(pointerHandles.handles) > 0 {
19+
for h, ptr := range pointerHandles.handles {
20+
fmt.Printf("%016p: %v %+v\n", h, reflect.TypeOf(ptr), ptr)
21+
}
22+
panic("pointer handle list not empty")
23+
}
24+
pointerHandles.Unlock()
25+
26+
Shutdown()
27+
28+
os.Exit(ret)
29+
}
30+
1131
func cleanupTestRepo(t *testing.T, r *Repository) {
1232
var err error
1333
if r.IsBare() {

object.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,14 @@ func (o *Object) Type() ObjectType {
7777
return ret
7878
}
7979

80-
// Owner returns a weak reference to the repository which owns this
81-
// object. This won't keep the underlying repository alive.
80+
// Owner returns a weak reference to the repository which owns this object.
81+
// This won't keep the underlying repository alive, but it should still be
82+
// Freed.
8283
func (o *Object) Owner() *Repository {
83-
ret := &Repository{
84-
ptr: C.git_object_owner(o.ptr),
85-
}
84+
repo := newRepositoryFromC(C.git_object_owner(o.ptr))
8685
runtime.KeepAlive(o)
87-
return ret
86+
repo.weak = true
87+
return repo
8888
}
8989

9090
func dupObject(obj *Object, kind ObjectType) (*C.git_object, error) {

push_test.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,18 @@ func TestRemotePush(t *testing.T) {
1414

1515
remote, err := localRepo.Remotes.Create("test_push", repo.Path())
1616
checkFatal(t, err)
17+
defer remote.Free()
1718

1819
seedTestRepo(t, localRepo)
1920

2021
err = remote.Push([]string{"refs/heads/master"}, nil)
2122
checkFatal(t, err)
2223

23-
_, err = localRepo.References.Lookup("refs/remotes/test_push/master")
24+
ref, err := localRepo.References.Lookup("refs/remotes/test_push/master")
2425
checkFatal(t, err)
26+
defer ref.Free()
2527

26-
_, err = repo.References.Lookup("refs/heads/master")
28+
ref, err = repo.References.Lookup("refs/heads/master")
2729
checkFatal(t, err)
30+
defer ref.Free()
2831
}

reference.go

+7-5
Original file line numberDiff line numberDiff line change
@@ -293,12 +293,14 @@ func (v *Reference) Peel(t ObjectType) (*Object, error) {
293293
return allocObject(cobj, v.repo), nil
294294
}
295295

296-
// Owner returns a weak reference to the repository which owns this
297-
// reference.
296+
// Owner returns a weak reference to the repository which owns this reference.
297+
// This won't keep the underlying repository alive, but it should still be
298+
// Freed.
298299
func (v *Reference) Owner() *Repository {
299-
return &Repository{
300-
ptr: C.git_reference_owner(v.ptr),
301-
}
300+
repo := newRepositoryFromC(C.git_reference_owner(v.ptr))
301+
runtime.KeepAlive(v)
302+
repo.weak = true
303+
return repo
302304
}
303305

304306
// Cmp compares v to ref2. It returns 0 on equality, otherwise a

remote.go

+2
Original file line numberDiff line numberDiff line change
@@ -432,10 +432,12 @@ func RemoteIsValidName(name string) bool {
432432
return C.git_remote_is_valid_name(cname) == 1
433433
}
434434

435+
// Free releases the resources of the Remote.
435436
func (r *Remote) Free() {
436437
runtime.SetFinalizer(r, nil)
437438
C.git_remote_free(r.ptr)
438439
r.ptr = nil
440+
r.repo = nil
439441
}
440442

441443
type RemoteCollection struct {

remote_test.go

+17-6
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,9 @@ func TestListRemotes(t *testing.T) {
2323
repo := createTestRepo(t)
2424
defer cleanupTestRepo(t, repo)
2525

26-
_, err := repo.Remotes.Create("test", "git://foo/bar")
27-
26+
remote, err := repo.Remotes.Create("test", "git://foo/bar")
2827
checkFatal(t, err)
28+
defer remote.Free()
2929

3030
expected := []string{
3131
"test",
@@ -53,6 +53,7 @@ func TestCertificateCheck(t *testing.T) {
5353

5454
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
5555
checkFatal(t, err)
56+
defer remote.Free()
5657

5758
options := FetchOptions{
5859
RemoteCallbacks: RemoteCallbacks{
@@ -73,6 +74,7 @@ func TestRemoteConnect(t *testing.T) {
7374

7475
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
7576
checkFatal(t, err)
77+
defer remote.Free()
7678

7779
err = remote.ConnectFetch(nil, nil, nil)
7880
checkFatal(t, err)
@@ -85,6 +87,7 @@ func TestRemoteLs(t *testing.T) {
8587

8688
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
8789
checkFatal(t, err)
90+
defer remote.Free()
8891

8992
err = remote.ConnectFetch(nil, nil, nil)
9093
checkFatal(t, err)
@@ -104,6 +107,7 @@ func TestRemoteLsFiltering(t *testing.T) {
104107

105108
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
106109
checkFatal(t, err)
110+
defer remote.Free()
107111

108112
err = remote.ConnectFetch(nil, nil, nil)
109113
checkFatal(t, err)
@@ -136,11 +140,13 @@ func TestRemotePruneRefs(t *testing.T) {
136140
err = config.SetBool("remote.origin.prune", true)
137141
checkFatal(t, err)
138142

139-
_, err = repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
143+
remote, err := repo.Remotes.Create("origin", "https://github.com/libgit2/TestGitRepository")
140144
checkFatal(t, err)
145+
defer remote.Free()
141146

142-
remote, err := repo.Remotes.Lookup("origin")
147+
remote, err = repo.Remotes.Lookup("origin")
143148
checkFatal(t, err)
149+
defer remote.Free()
144150

145151
if !remote.PruneRefs() {
146152
t.Fatal("Expected remote to be configured to prune references")
@@ -159,6 +165,7 @@ func TestRemotePrune(t *testing.T) {
159165

160166
remoteRef, err := remoteRepo.CreateBranch("test-prune", commit, true)
161167
checkFatal(t, err)
168+
defer remoteRef.Free()
162169

163170
repo := createTestRepo(t)
164171
defer cleanupTestRepo(t, repo)
@@ -170,12 +177,14 @@ func TestRemotePrune(t *testing.T) {
170177
remoteUrl := fmt.Sprintf("file://%s", remoteRepo.Workdir())
171178
remote, err := repo.Remotes.Create("origin", remoteUrl)
172179
checkFatal(t, err)
180+
defer remote.Free()
173181

174182
err = remote.Fetch([]string{"test-prune"}, nil, "")
175183
checkFatal(t, err)
176184

177-
_, err = repo.References.Create("refs/remotes/origin/test-prune", head, true, "remote reference")
185+
ref, err := repo.References.Create("refs/remotes/origin/test-prune", head, true, "remote reference")
178186
checkFatal(t, err)
187+
defer ref.Free()
179188

180189
err = remoteRef.Delete()
181190
checkFatal(t, err)
@@ -185,15 +194,17 @@ func TestRemotePrune(t *testing.T) {
185194

186195
rr, err := repo.Remotes.Lookup("origin")
187196
checkFatal(t, err)
197+
defer rr.Free()
188198

189199
err = rr.ConnectFetch(nil, nil, nil)
190200
checkFatal(t, err)
191201

192202
err = rr.Prune(nil)
193203
checkFatal(t, err)
194204

195-
_, err = repo.References.Lookup("refs/remotes/origin/test-prune")
205+
ref, err = repo.References.Lookup("refs/remotes/origin/test-prune")
196206
if err == nil {
207+
ref.Free()
197208
t.Fatal("Expected error getting a pruned reference")
198209
}
199210
}

repository.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ type Repository struct {
3535
// Stashes represents the collection of stashes and can be used to
3636
// save, apply and iterate over stash states in this repository.
3737
Stashes StashCollection
38+
39+
// weak indicates that a repository is a weak pointer and should not be
40+
// freed.
41+
weak bool
3842
}
3943

4044
func newRepositoryFromC(ptr *C.git_repository) *Repository {
@@ -136,8 +140,13 @@ func (v *Repository) SetRefdb(refdb *Refdb) {
136140
}
137141

138142
func (v *Repository) Free() {
143+
ptr := v.ptr
144+
v.ptr = nil
139145
runtime.SetFinalizer(v, nil)
140-
C.git_repository_free(v.ptr)
146+
if v.weak {
147+
return
148+
}
149+
C.git_repository_free(ptr)
141150
}
142151

143152
func (v *Repository) Config() (*Config, error) {

0 commit comments

Comments
 (0)