Skip to content

Commit 7df088a

Browse files
dylanahsmithGenevieve
and
Genevieve
authored
Add Leak Check workflow to CI that uses Leak Sanitizer (rogchap#226)
* Add support for using Leak Sanitizer through a leakcheck build tag * Exclude ExampleFunctionTemplate_fetch when leak checking on macOS * Add leak check github action * Add documentation for doing leak checking locally * Improve backtrace to help with debugging reported leaks The sanitizers are a part of LLVM, so using clang seems to result in more complete backtraces. Doing a separate `go test -c` from running the test executable was also needed to actually get line numbers in the backtrace in CI. Disabling dwarf compression was also needed on macOS Co-authored-by: Genevieve <[email protected]>
1 parent 0e32bb0 commit 7df088a

File tree

6 files changed

+157
-38
lines changed

6 files changed

+157
-38
lines changed

.github/workflows/leakcheck.yml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: Leak Check
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
workflow_dispatch:
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
14+
steps:
15+
- name: Install Go
16+
uses: actions/setup-go@v2
17+
with:
18+
go-version: 1.17.2
19+
- name: Checkout
20+
uses: actions/checkout@v2
21+
- name: Go Test
22+
env:
23+
CC: clang
24+
CXX: clang++
25+
run: |
26+
go test -c --tags leakcheck
27+
./v8go.test

README.md

+31
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,37 @@ standard output being fully buffered.
241241
A simple way to avoid this problem is to flush the standard output stream after printing with the `fflush(stdout);` statement.
242242
Not relying on the flushing at exit can also help ensure the output is printed before a crash.
243243

244+
### Local leak checking
245+
246+
Leak checking is automatically done in CI, but it can be useful to do locally to debug leaks.
247+
248+
Leak checking is done using the [Leak Sanitizer](https://clang.llvm.org/docs/LeakSanitizer.html) which
249+
is a part of LLVM. As such, compiling with clang as the C/C++ compiler seems to produce more complete
250+
backtraces (unfortunately still only of the system stack at the time of writing).
251+
252+
For instance, on a Debian-based Linux system, you can use `sudo apt-get install clang-12` to install a
253+
recent version of clang. Then CC and CXX environment variables are needed to use that compiler. With
254+
that compiler, the tests can be run as follows
255+
256+
```
257+
CC=clang-12 CXX=clang++-12 go test -c --tags leakcheck && ./v8go.test
258+
```
259+
260+
The separate compile and link commands are currently needed to get line numbers in the backtrace.
261+
262+
On macOS, leak checking isn't available with the version of clang that comes with Xcode, so a separate
263+
compiler installation is needed. For example, with homebrew, `brew install llvm` will install a version
264+
of clang with support for this. The ASAN_OPTIONS environment variable will also be needed to run the code
265+
with leak checking enabled, since it isn't enabled by default on macOS. E.g. with the homebrew
266+
installation of llvm, the tests can be run with
267+
268+
```
269+
CXX=/usr/local/opt/llvm/bin/clang++ CC=/usr/local/opt/llvm/bin/clang go test -c --tags leakcheck -ldflags=-compressdwarf=false
270+
ASAN_OPTIONS=detect_leaks=1 ./v8go.test
271+
```
272+
273+
The `-ldflags=-compressdwarf=false` is currently (with clang 13) needed to get line numbers in the backtrace.
274+
244275
### Formatting
245276

246277
Go has `go fmt`, C has `clang-format`. Any changes to the `v8go.h|cc` should be formated with `clang-format` with the

function_template_fetch_test.go

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright 2021 Roger Chapman and the v8go contributors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// Ignore leaks within Go standard libraries http/https support code.
6+
// The getaddrinfo detected leaks can be avoided using GODEBUG=netdns=go but
7+
// currently there are more for loading system root certificates on macOS.
8+
//go:build !leakcheck || !darwin
9+
// +build !leakcheck !darwin
10+
11+
package v8go_test
12+
13+
import (
14+
"fmt"
15+
"io/ioutil"
16+
"net/http"
17+
"strings"
18+
19+
v8 "rogchap.com/v8go"
20+
)
21+
22+
func ExampleFunctionTemplate_fetch() {
23+
iso := v8.NewIsolate()
24+
defer iso.Dispose()
25+
global := v8.NewObjectTemplate(iso)
26+
27+
fetchfn := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value {
28+
args := info.Args()
29+
url := args[0].String()
30+
31+
resolver, _ := v8.NewPromiseResolver(info.Context())
32+
33+
go func() {
34+
res, _ := http.Get(url)
35+
body, _ := ioutil.ReadAll(res.Body)
36+
val, _ := v8.NewValue(iso, string(body))
37+
resolver.Resolve(val)
38+
}()
39+
return resolver.GetPromise().Value
40+
})
41+
global.Set("fetch", fetchfn, v8.ReadOnly)
42+
43+
ctx := v8.NewContext(iso, global)
44+
defer ctx.Close()
45+
val, _ := ctx.RunScript("fetch('https://rogchap.com/v8go')", "")
46+
prom, _ := val.AsPromise()
47+
48+
// wait for the promise to resolve
49+
for prom.State() == v8.Pending {
50+
continue
51+
}
52+
fmt.Printf("%s\n", strings.Split(prom.Result().String(), "\n")[0])
53+
// Output:
54+
// <!DOCTYPE html>
55+
}

function_template_test.go

-38
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,6 @@ package v8go_test
66

77
import (
88
"fmt"
9-
"io/ioutil"
10-
"net/http"
11-
"strings"
129
"testing"
1310

1411
v8 "rogchap.com/v8go"
@@ -127,38 +124,3 @@ func ExampleFunctionTemplate() {
127124
// Output:
128125
// [foo bar 0 1]
129126
}
130-
131-
func ExampleFunctionTemplate_fetch() {
132-
iso := v8.NewIsolate()
133-
defer iso.Dispose()
134-
global := v8.NewObjectTemplate(iso)
135-
136-
fetchfn := v8.NewFunctionTemplate(iso, func(info *v8.FunctionCallbackInfo) *v8.Value {
137-
args := info.Args()
138-
url := args[0].String()
139-
140-
resolver, _ := v8.NewPromiseResolver(info.Context())
141-
142-
go func() {
143-
res, _ := http.Get(url)
144-
body, _ := ioutil.ReadAll(res.Body)
145-
val, _ := v8.NewValue(iso, string(body))
146-
resolver.Resolve(val)
147-
}()
148-
return resolver.GetPromise().Value
149-
})
150-
global.Set("fetch", fetchfn, v8.ReadOnly)
151-
152-
ctx := v8.NewContext(iso, global)
153-
defer ctx.Close()
154-
val, _ := ctx.RunScript("fetch('https://rogchap.com/v8go')", "")
155-
prom, _ := val.AsPromise()
156-
157-
// wait for the promise to resolve
158-
for prom.State() == v8.Pending {
159-
continue
160-
}
161-
fmt.Printf("%s\n", strings.Split(prom.Result().String(), "\n")[0])
162-
// Output:
163-
// <!DOCTYPE html>
164-
}

leak_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2021 the v8go contributors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
//go:build leakcheck
6+
// +build leakcheck
7+
8+
package v8go_test
9+
10+
import (
11+
"testing"
12+
"os"
13+
14+
"rogchap.com/v8go"
15+
)
16+
17+
func TestMain(m *testing.M) {
18+
exitCode := m.Run()
19+
v8go.DoLeakSanitizerCheck()
20+
os.Exit(exitCode)
21+
}

leakcheck.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright 2021 the v8go contributors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
//go:build leakcheck
6+
// +build leakcheck
7+
8+
package v8go
9+
10+
// #cgo CPPFLAGS: -fsanitize=address
11+
// #cgo LDFLAGS: -fsanitize=address
12+
//
13+
// #include <sanitizer/lsan_interface.h>
14+
import "C"
15+
16+
import "runtime"
17+
18+
// Call LLVM Leak Sanitizer's at-exit hook that doesn't
19+
// get called automatically by Go.
20+
func DoLeakSanitizerCheck() {
21+
runtime.GC()
22+
C.__lsan_do_leak_check()
23+
}

0 commit comments

Comments
 (0)