Skip to content

Commit

Permalink
cmd/cgo: add hooks for thread sanitizer
Browse files Browse the repository at this point in the history
When Go code is used with C code compiled with -fsanitize=thread, adds
thread sanitizer calls so that correctly synchronized Go code does not
cause spurious failure reports from the thread sanitizer.  This may
cause some false negatives, but for the thread sanitizer what is most
important is avoiding false positives.

Change-Id: If670e4a6f2874c7a2be2ff7db8728c6036340a52
Reviewed-on: https://go-review.googlesource.com/17421
Reviewed-by: Dmitry Vyukov <[email protected]>
  • Loading branch information
ianlancetaylor committed Feb 28, 2016
1 parent c86dbbe commit c8ef0df
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 36 deletions.
105 changes: 71 additions & 34 deletions misc/cgo/testsanitizers/test.bash
Original file line number Diff line number Diff line change
Expand Up @@ -15,75 +15,112 @@ if test -x "$(type -p clang)"; then
fi
export CC

msan=yes

TMPDIR=${TMPDIR:-/tmp}
echo > ${TMPDIR}/testsanitizers$$.c
if $CC -fsanitize=memory -c ${TMPDIR}/testsanitizers$$.c -o ${TMPDIR}/testsanitizers$$.o 2>&1 | grep "unrecognized" >& /dev/null; then
echo "skipping msan test: -fsanitize=memory not supported"
rm -f ${TMPDIR}/testsanitizers$$.*
exit 0
echo "skipping msan tests: -fsanitize=memory not supported"
msan=no
fi
rm -f ${TMPDIR}/testsanitizers$$.*

# The memory sanitizer in versions of clang before 3.6 don't work with Go.
if $CC --version | grep clang >& /dev/null; then
if test "$msan" = "yes" && $CC --version | grep clang >& /dev/null; then
ver=$($CC --version | sed -e 's/.* version \([0-9.-]*\).*/\1/')
major=$(echo $ver | sed -e 's/\([0-9]*\).*/\1/')
minor=$(echo $ver | sed -e 's/[0-9]*\.\([0-9]*\).*/\1/')
if test "$major" -lt 3 || test "$major" -eq 3 -a "$minor" -lt 6; then
echo "skipping msan test; clang version $major.$minor (older than 3.6)"
exit 0
echo "skipping msan tests: clang version $major.$minor (older than 3.6)"
msan=no
fi

# Clang before 3.8 does not work with Linux at or after 4.1.
# golang.org/issue/12898.
if test "$major" -lt 3 || test "$major" -eq 3 -a "$minor" -lt 8; then
if test "$msan" = "yes" -a "$major" -lt 3 || test "$major" -eq 3 -a "$minor" -lt 8; then
if test "$(uname)" = Linux; then
linuxver=$(uname -r)
linuxmajor=$(echo $linuxver | sed -e 's/\([0-9]*\).*/\1/')
linuxminor=$(echo $linuxver | sed -e 's/[0-9]*\.\([0-9]*\).*/\1/')
if test "$linuxmajor" -gt 4 || test "$linuxmajor" -eq 4 -a "$linuxminor" -ge 1; then
echo "skipping msan test; clang version $major.$minor (older than 3.8) incompatible with linux version $linuxmajor.$linuxminor (4.1 or newer)"
exit 0
echo "skipping msan tests: clang version $major.$minor (older than 3.8) incompatible with linux version $linuxmajor.$linuxminor (4.1 or newer)"
msan=no
fi
fi
fi
fi

status=0

if ! go build -msan std; then
echo "FAIL: build -msan std"
status=1
fi
if test "$msan" = "yes"; then
if ! go build -msan std; then
echo "FAIL: build -msan std"
status=1
fi

if ! go run -msan msan.go; then
echo "FAIL: msan"
status=1
fi
if ! go run -msan msan.go; then
echo "FAIL: msan"
status=1
fi

if ! CGO_LDFLAGS="-fsanitize=memory" CGO_CPPFLAGS="-fsanitize=memory" go run -msan -a msan2.go; then
echo "FAIL: msan2 with -fsanitize=memory"
status=1
fi
if ! CGO_LDFLAGS="-fsanitize=memory" CGO_CPPFLAGS="-fsanitize=memory" go run -msan -a msan2.go; then
echo "FAIL: msan2 with -fsanitize=memory"
status=1
fi

if ! go run -msan -a msan2.go; then
echo "FAIL: msan2"
status=1
fi
if ! go run -msan -a msan2.go; then
echo "FAIL: msan2"
status=1
fi

if ! go run -msan msan3.go; then
echo "FAIL: msan3"
status=1
if ! go run -msan msan3.go; then
echo "FAIL: msan3"
status=1
fi

if ! go run -msan msan4.go; then
echo "FAIL: msan4"
status=1
fi

if go run -msan msan_fail.go 2>/dev/null; then
echo "FAIL: msan_fail"
status=1
fi
fi

if ! go run -msan msan4.go; then
echo "FAIL: msan4"
status=1
tsan=yes

TMPDIR=${TMPDIR:-/tmp}
echo > ${TMPDIR}/testsanitizers$$.c
if $CC -fsanitize=thread -c ${TMPDIR}/testsanitizers$$.c -o ${TMPDIR}/testsanitizers$$.o 2>&1 | grep "unrecognized" >& /dev/null; then
echo "skipping tsan tests: -fsanitize=thread not supported"
tsan=no
fi
rm -f ${TMPDIR}/testsanitizers$$.*

if test "$tsan" = "yes"; then
err=${TMPDIR}/tsanerr$$.out

if ! go run tsan.go 2>$err; then
echo "FAIL: tsan"
status=1
elif grep -i warning $err >/dev/null 2>&1; then
cat $err
echo "FAIL: tsan"
status=1
fi

if ! go run tsan2.go 2>$err; then
echo "FAIL: tsan2"
status=1
elif grep -i warning $err >/dev/null 2>&1; then
cat $err
echo "FAIL: tsan2"
status=1
fi

if go run -msan msan_fail.go 2>/dev/null; then
echo "FAIL: msan_fail"
status=1
rm -f $err
fi

exit $status
44 changes: 44 additions & 0 deletions misc/cgo/testsanitizers/tsan.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

// This program produced false race reports when run under the C/C++
// ThreadSanitizer, as it did not understand the synchronization in
// the Go code.

/*
#cgo CFLAGS: -fsanitize=thread
#cgo LDFLAGS: -fsanitize=thread
int val;
int getVal() {
return val;
}
void setVal(int i) {
val = i;
}
*/
import "C"

import (
"runtime"
)

func main() {
runtime.LockOSThread()
C.setVal(1)
c := make(chan bool)
go func() {
runtime.LockOSThread()
C.setVal(2)
c <- true
}()
<-c
if v := C.getVal(); v != 2 {
panic(v)
}
}
55 changes: 55 additions & 0 deletions misc/cgo/testsanitizers/tsan2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

// This program produced false race reports when run under the C/C++
// ThreadSanitizer, as it did not understand the synchronization in
// the Go code.

/*
#cgo CFLAGS: -fsanitize=thread
#cgo LDFLAGS: -fsanitize=thread
extern void GoRun(void);
// Yes, you can have definitions if you use //export, as long as they are weak.
int val __attribute__ ((weak));
int run(void) __attribute__ ((weak));
int run() {
val = 1;
GoRun();
return val;
}
void setVal(int) __attribute__ ((weak));
void setVal(int i) {
val = i;
}
*/
import "C"

import "runtime"

//export GoRun
func GoRun() {
runtime.LockOSThread()
c := make(chan bool)
go func() {
runtime.LockOSThread()
C.setVal(2)
c <- true
}()
<-c
}

func main() {
if v := C.run(); v != 2 {
panic(v)
}
}
Loading

0 comments on commit c8ef0df

Please sign in to comment.