Commit 798e0b38 authored by Russ Cox's avatar Russ Cox

misc/cgo/errors: consolidate test work

Build a single binary containing all the TestPointerChecks
instead of building many small binaries,
each with its own cgo+compile+link invocation.
This cuts 'go test -run=TestPointerChecks'
from 6.7r 35.5u 26.1s to 2.1r 2.1u 1.4s.

Move as many cgo checks as possible into fewer test files
for TestReportsTypeErrors too.
This cuts 'go test -run=TestReportsTypeErrors'
from 2.1r 6.7u 6.7s to 1.5r 2.5u 2.5s.

After this change, all.bash runs in ~4:30 on my laptop.

For #26473.

Change-Id: I3787448b03689a1f62dd810957ab6013bb75582f
Reviewed-on: https://go-review.googlesource.com/c/go/+/177599
Run-TryBot: Russ Cox <rsc@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarIan Lance Taylor <iant@golang.org>
parent b8648184
...@@ -63,7 +63,7 @@ func expect(t *testing.T, file string, errors []*regexp.Regexp) { ...@@ -63,7 +63,7 @@ func expect(t *testing.T, file string, errors []*regexp.Regexp) {
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
dst := filepath.Join(dir, strings.TrimSuffix(file, ".go")) dst := filepath.Join(dir, strings.TrimSuffix(file, ".go"))
cmd := exec.Command("go", "build", "-gcflags=-L", "-o="+dst, path(file)) // TODO(gri) no need for -gcflags=-L if go tool is adjusted cmd := exec.Command("go", "build", "-gcflags=-L -e", "-o="+dst, path(file)) // TODO(gri) no need for -gcflags=-L if go tool is adjusted
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err == nil { if err == nil {
t.Errorf("expected cgo to fail but it succeeded") t.Errorf("expected cgo to fail but it succeeded")
...@@ -107,21 +107,10 @@ func TestReportsTypeErrors(t *testing.T) { ...@@ -107,21 +107,10 @@ func TestReportsTypeErrors(t *testing.T) {
for _, file := range []string{ for _, file := range []string{
"err1.go", "err1.go",
"err2.go", "err2.go",
"err3.go",
"issue7757.go",
"issue8442.go",
"issue11097a.go", "issue11097a.go",
"issue11097b.go", "issue11097b.go",
"issue13129.go",
"issue13423.go",
"issue13467.go",
"issue13635.go",
"issue13830.go",
"issue16116.go",
"issue16591.go",
"issue18452.go", "issue18452.go",
"issue18889.go", "issue18889.go",
"issue26745.go",
"issue28721.go", "issue28721.go",
} { } {
check(t, file) check(t, file)
......
...@@ -7,21 +7,25 @@ ...@@ -7,21 +7,25 @@
package errorstest package errorstest
import ( import (
"bufio"
"bytes" "bytes"
"flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"sync/atomic"
"testing" "testing"
) )
var tmp = flag.String("tmp", "", "use `dir` for temporary files and do not clean up")
// ptrTest is the tests without the boilerplate. // ptrTest is the tests without the boilerplate.
type ptrTest struct { type ptrTest struct {
name string // for reporting name string // for reporting
c string // the cgo comment c string // the cgo comment
c1 string // cgo comment forced into non-export cgo file
imports []string // a list of imports imports []string // a list of imports
support string // supporting functions support string // supporting functions
body string // the body of the main function body string // the body of the main function
...@@ -39,253 +43,248 @@ var ptrTests = []ptrTest{ ...@@ -39,253 +43,248 @@ var ptrTests = []ptrTest{
{ {
// Passing a pointer to a struct that contains a Go pointer. // Passing a pointer to a struct that contains a Go pointer.
name: "ptr1", name: "ptr1",
c: `typedef struct s { int *p; } s; void f(s *ps) {}`, c: `typedef struct s1 { int *p; } s1; void f1(s1 *ps) {}`,
body: `C.f(&C.s{new(C.int)})`, body: `C.f1(&C.s1{new(C.int)})`,
fail: true, fail: true,
}, },
{ {
// Passing a pointer to a struct that contains a Go pointer. // Passing a pointer to a struct that contains a Go pointer.
name: "ptr2", name: "ptr2",
c: `typedef struct s { int *p; } s; void f(s *ps) {}`, c: `typedef struct s2 { int *p; } s2; void f2(s2 *ps) {}`,
body: `p := &C.s{new(C.int)}; C.f(p)`, body: `p := &C.s2{new(C.int)}; C.f2(p)`,
fail: true, fail: true,
}, },
{ {
// Passing a pointer to an int field of a Go struct // Passing a pointer to an int field of a Go struct
// that (irrelevantly) contains a Go pointer. // that (irrelevantly) contains a Go pointer.
name: "ok1", name: "ok1",
c: `struct s { int i; int *p; }; void f(int *p) {}`, c: `struct s3 { int i; int *p; }; void f3(int *p) {}`,
body: `p := &C.struct_s{i: 0, p: new(C.int)}; C.f(&p.i)`, body: `p := &C.struct_s3{i: 0, p: new(C.int)}; C.f3(&p.i)`,
fail: false, fail: false,
}, },
{ {
// Passing a pointer to a pointer field of a Go struct. // Passing a pointer to a pointer field of a Go struct.
name: "ptr-field", name: "ptrfield",
c: `struct s { int i; int *p; }; void f(int **p) {}`, c: `struct s4 { int i; int *p; }; void f4(int **p) {}`,
body: `p := &C.struct_s{i: 0, p: new(C.int)}; C.f(&p.p)`, body: `p := &C.struct_s4{i: 0, p: new(C.int)}; C.f4(&p.p)`,
fail: true, fail: true,
}, },
{ {
// Passing a pointer to a pointer field of a Go // Passing a pointer to a pointer field of a Go
// struct, where the field does not contain a Go // struct, where the field does not contain a Go
// pointer, but another field (irrelevantly) does. // pointer, but another field (irrelevantly) does.
name: "ptr-field-ok", name: "ptrfieldok",
c: `struct s { int *p1; int *p2; }; void f(int **p) {}`, c: `struct s5 { int *p1; int *p2; }; void f5(int **p) {}`,
body: `p := &C.struct_s{p1: nil, p2: new(C.int)}; C.f(&p.p1)`, body: `p := &C.struct_s5{p1: nil, p2: new(C.int)}; C.f5(&p.p1)`,
fail: false, fail: false,
}, },
{ {
// Passing the address of a slice with no Go pointers. // Passing the address of a slice with no Go pointers.
name: "slice-ok-1", name: "sliceok1",
c: `void f(void **p) {}`, c: `void f6(void **p) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
body: `s := []unsafe.Pointer{nil}; C.f(&s[0])`, body: `s := []unsafe.Pointer{nil}; C.f6(&s[0])`,
fail: false, fail: false,
}, },
{ {
// Passing the address of a slice with a Go pointer. // Passing the address of a slice with a Go pointer.
name: "slice-ptr-1", name: "sliceptr1",
c: `void f(void **p) {}`, c: `void f7(void **p) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
body: `i := 0; s := []unsafe.Pointer{unsafe.Pointer(&i)}; C.f(&s[0])`, body: `i := 0; s := []unsafe.Pointer{unsafe.Pointer(&i)}; C.f7(&s[0])`,
fail: true, fail: true,
}, },
{ {
// Passing the address of a slice with a Go pointer, // Passing the address of a slice with a Go pointer,
// where we are passing the address of an element that // where we are passing the address of an element that
// is not a Go pointer. // is not a Go pointer.
name: "slice-ptr-2", name: "sliceptr2",
c: `void f(void **p) {}`, c: `void f8(void **p) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
body: `i := 0; s := []unsafe.Pointer{nil, unsafe.Pointer(&i)}; C.f(&s[0])`, body: `i := 0; s := []unsafe.Pointer{nil, unsafe.Pointer(&i)}; C.f8(&s[0])`,
fail: true, fail: true,
}, },
{ {
// Passing the address of a slice that is an element // Passing the address of a slice that is an element
// in a struct only looks at the slice. // in a struct only looks at the slice.
name: "slice-ok-2", name: "sliceok2",
c: `void f(void **p) {}`, c: `void f9(void **p) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
support: `type S struct { p *int; s []unsafe.Pointer }`, support: `type S9 struct { p *int; s []unsafe.Pointer }`,
body: `i := 0; p := &S{p:&i, s:[]unsafe.Pointer{nil}}; C.f(&p.s[0])`, body: `i := 0; p := &S9{p:&i, s:[]unsafe.Pointer{nil}}; C.f9(&p.s[0])`,
fail: false, fail: false,
}, },
{ {
// Passing the address of a slice of an array that is // Passing the address of a slice of an array that is
// an element in a struct, with a type conversion. // an element in a struct, with a type conversion.
name: "slice-ok-3", name: "sliceok3",
c: `void f(void* p) {}`, c: `void f10(void* p) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
support: `type S struct { p *int; a [4]byte }`, support: `type S10 struct { p *int; a [4]byte }`,
body: `i := 0; p := &S{p:&i}; s := p.a[:]; C.f(unsafe.Pointer(&s[0]))`, body: `i := 0; p := &S10{p:&i}; s := p.a[:]; C.f10(unsafe.Pointer(&s[0]))`,
fail: false, fail: false,
}, },
{ {
// Passing the address of a slice of an array that is // Passing the address of a slice of an array that is
// an element in a struct, with a type conversion. // an element in a struct, with a type conversion.
name: "slice-ok-4", name: "sliceok4",
c: `typedef void* PV; void f(PV p) {}`, c: `typedef void* PV11; void f11(PV11 p) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
support: `type S struct { p *int; a [4]byte }`, support: `type S11 struct { p *int; a [4]byte }`,
body: `i := 0; p := &S{p:&i}; C.f(C.PV(unsafe.Pointer(&p.a[0])))`, body: `i := 0; p := &S11{p:&i}; C.f11(C.PV11(unsafe.Pointer(&p.a[0])))`,
fail: false, fail: false,
}, },
{ {
// Passing the address of a static variable with no // Passing the address of a static variable with no
// pointers doesn't matter. // pointers doesn't matter.
name: "varok", name: "varok",
c: `void f(char** parg) {}`, c: `void f12(char** parg) {}`,
support: `var hello = [...]C.char{'h', 'e', 'l', 'l', 'o'}`, support: `var hello12 = [...]C.char{'h', 'e', 'l', 'l', 'o'}`,
body: `parg := [1]*C.char{&hello[0]}; C.f(&parg[0])`, body: `parg := [1]*C.char{&hello12[0]}; C.f12(&parg[0])`,
fail: false, fail: false,
}, },
{ {
// Passing the address of a static variable with // Passing the address of a static variable with
// pointers does matter. // pointers does matter.
name: "var", name: "var1",
c: `void f(char*** parg) {}`, c: `void f13(char*** parg) {}`,
support: `var hello = [...]*C.char{new(C.char)}`, support: `var hello13 = [...]*C.char{new(C.char)}`,
body: `parg := [1]**C.char{&hello[0]}; C.f(&parg[0])`, body: `parg := [1]**C.char{&hello13[0]}; C.f13(&parg[0])`,
fail: true, fail: true,
}, },
{ {
// Storing a Go pointer into C memory should fail. // Storing a Go pointer into C memory should fail.
name: "barrier", name: "barrier",
c: `#include <stdlib.h> c: `#include <stdlib.h>
char **f1() { return malloc(sizeof(char*)); } char **f14a() { return malloc(sizeof(char*)); }
void f2(char **p) {}`, void f14b(char **p) {}`,
body: `p := C.f1(); *p = new(C.char); C.f2(p)`, body: `p := C.f14a(); *p = new(C.char); C.f14b(p)`,
fail: true, fail: true,
expensive: true, expensive: true,
}, },
{ {
// Storing a Go pointer into C memory by assigning a // Storing a Go pointer into C memory by assigning a
// large value should fail. // large value should fail.
name: "barrier-struct", name: "barrierstruct",
c: `#include <stdlib.h> c: `#include <stdlib.h>
struct s { char *a[10]; }; struct s15 { char *a[10]; };
struct s *f1() { return malloc(sizeof(struct s)); } struct s15 *f15() { return malloc(sizeof(struct s15)); }
void f2(struct s *p) {}`, void f15b(struct s15 *p) {}`,
body: `p := C.f1(); p.a = [10]*C.char{new(C.char)}; C.f2(p)`, body: `p := C.f15(); p.a = [10]*C.char{new(C.char)}; C.f15b(p)`,
fail: true, fail: true,
expensive: true, expensive: true,
}, },
{ {
// Storing a Go pointer into C memory using a slice // Storing a Go pointer into C memory using a slice
// copy should fail. // copy should fail.
name: "barrier-slice", name: "barrierslice",
c: `#include <stdlib.h> c: `#include <stdlib.h>
struct s { char *a[10]; }; struct s16 { char *a[10]; };
struct s *f1() { return malloc(sizeof(struct s)); } struct s16 *f16() { return malloc(sizeof(struct s16)); }
void f2(struct s *p) {}`, void f16b(struct s16 *p) {}`,
body: `p := C.f1(); copy(p.a[:], []*C.char{new(C.char)}); C.f2(p)`, body: `p := C.f16(); copy(p.a[:], []*C.char{new(C.char)}); C.f16b(p)`,
fail: true, fail: true,
expensive: true, expensive: true,
}, },
{ {
// A very large value uses a GC program, which is a // A very large value uses a GC program, which is a
// different code path. // different code path.
name: "barrier-gcprog-array", name: "barriergcprogarray",
c: `#include <stdlib.h> c: `#include <stdlib.h>
struct s { char *a[32769]; }; struct s17 { char *a[32769]; };
struct s *f1() { return malloc(sizeof(struct s)); } struct s17 *f17() { return malloc(sizeof(struct s17)); }
void f2(struct s *p) {}`, void f17b(struct s17 *p) {}`,
body: `p := C.f1(); p.a = [32769]*C.char{new(C.char)}; C.f2(p)`, body: `p := C.f17(); p.a = [32769]*C.char{new(C.char)}; C.f17b(p)`,
fail: true, fail: true,
expensive: true, expensive: true,
}, },
{ {
// Similar case, with a source on the heap. // Similar case, with a source on the heap.
name: "barrier-gcprog-array-heap", name: "barriergcprogarrayheap",
c: `#include <stdlib.h> c: `#include <stdlib.h>
struct s { char *a[32769]; }; struct s18 { char *a[32769]; };
struct s *f1() { return malloc(sizeof(struct s)); } struct s18 *f18() { return malloc(sizeof(struct s18)); }
void f2(struct s *p) {} void f18b(struct s18 *p) {}
void f3(void *p) {}`, void f18c(void *p) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
body: `p := C.f1(); n := &[32769]*C.char{new(C.char)}; p.a = *n; C.f2(p); n[0] = nil; C.f3(unsafe.Pointer(n))`, body: `p := C.f18(); n := &[32769]*C.char{new(C.char)}; p.a = *n; C.f18b(p); n[0] = nil; C.f18c(unsafe.Pointer(n))`,
fail: true, fail: true,
expensive: true, expensive: true,
}, },
{ {
// A GC program with a struct. // A GC program with a struct.
name: "barrier-gcprog-struct", name: "barriergcprogstruct",
c: `#include <stdlib.h> c: `#include <stdlib.h>
struct s { char *a[32769]; }; struct s19a { char *a[32769]; };
struct s2 { struct s f; }; struct s19b { struct s19a f; };
struct s2 *f1() { return malloc(sizeof(struct s2)); } struct s19b *f19() { return malloc(sizeof(struct s19b)); }
void f2(struct s2 *p) {}`, void f19b(struct s19b *p) {}`,
body: `p := C.f1(); p.f = C.struct_s{[32769]*C.char{new(C.char)}}; C.f2(p)`, body: `p := C.f19(); p.f = C.struct_s19a{[32769]*C.char{new(C.char)}}; C.f19b(p)`,
fail: true, fail: true,
expensive: true, expensive: true,
}, },
{ {
// Similar case, with a source on the heap. // Similar case, with a source on the heap.
name: "barrier-gcprog-struct-heap", name: "barriergcprogstructheap",
c: `#include <stdlib.h> c: `#include <stdlib.h>
struct s { char *a[32769]; }; struct s20a { char *a[32769]; };
struct s2 { struct s f; }; struct s20b { struct s20a f; };
struct s2 *f1() { return malloc(sizeof(struct s2)); } struct s20b *f20() { return malloc(sizeof(struct s20b)); }
void f2(struct s2 *p) {} void f20b(struct s20b *p) {}
void f3(void *p) {}`, void f20c(void *p) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
body: `p := C.f1(); n := &C.struct_s{[32769]*C.char{new(C.char)}}; p.f = *n; C.f2(p); n.a[0] = nil; C.f3(unsafe.Pointer(n))`, body: `p := C.f20(); n := &C.struct_s20a{[32769]*C.char{new(C.char)}}; p.f = *n; C.f20b(p); n.a[0] = nil; C.f20c(unsafe.Pointer(n))`,
fail: true, fail: true,
expensive: true, expensive: true,
}, },
{ {
// Exported functions may not return Go pointers. // Exported functions may not return Go pointers.
name: "export1", name: "export1",
c: `extern unsigned char *GoFn();`, c: `extern unsigned char *GoFn21();`,
support: `//export GoFn support: `//export GoFn21
func GoFn() *byte { return new(byte) }`, func GoFn21() *byte { return new(byte) }`,
body: `C.GoFn()`, body: `C.GoFn21()`,
fail: true, fail: true,
}, },
{ {
// Returning a C pointer is fine. // Returning a C pointer is fine.
name: "exportok", name: "exportok",
c: `#include <stdlib.h> c: `#include <stdlib.h>
extern unsigned char *GoFn();`, extern unsigned char *GoFn22();`,
support: `//export GoFn support: `//export GoFn22
func GoFn() *byte { return (*byte)(C.malloc(1)) }`, func GoFn22() *byte { return (*byte)(C.malloc(1)) }`,
body: `C.GoFn()`, body: `C.GoFn22()`,
}, },
{ {
// Passing a Go string is fine. // Passing a Go string is fine.
name: "pass-string", name: "passstring",
c: `#include <stddef.h> c: `#include <stddef.h>
typedef struct { const char *p; ptrdiff_t n; } gostring; typedef struct { const char *p; ptrdiff_t n; } gostring23;
gostring f(gostring s) { return s; }`, gostring23 f23(gostring23 s) { return s; }`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
body: `s := "a"; r := C.f(*(*C.gostring)(unsafe.Pointer(&s))); if *(*string)(unsafe.Pointer(&r)) != s { panic(r) }`, body: `s := "a"; r := C.f23(*(*C.gostring23)(unsafe.Pointer(&s))); if *(*string)(unsafe.Pointer(&r)) != s { panic(r) }`,
}, },
{ {
// Passing a slice of Go strings fails. // Passing a slice of Go strings fails.
name: "pass-string-slice", name: "passstringslice",
c: `void f(void *p) {}`, c: `void f24(void *p) {}`,
imports: []string{"strings", "unsafe"}, imports: []string{"strings", "unsafe"},
support: `type S struct { a [1]string }`, support: `type S24 struct { a [1]string }`,
body: `s := S{a:[1]string{strings.Repeat("a", 2)}}; C.f(unsafe.Pointer(&s.a[0]))`, body: `s := S24{a:[1]string{strings.Repeat("a", 2)}}; C.f24(unsafe.Pointer(&s.a[0]))`,
fail: true, fail: true,
}, },
{ {
// Exported functions may not return strings. // Exported functions may not return strings.
name: "ret-string", name: "retstring",
c: `extern void f();`, c: `extern void f25();`,
imports: []string{"strings"}, imports: []string{"strings"},
support: `//export GoStr support: `//export GoStr25
func GoStr() string { return strings.Repeat("a", 2) }`, func GoStr25() string { return strings.Repeat("a", 2) }`,
body: `C.f()`, body: `C.f25()`,
extra: []extra{ c1: `#include <stddef.h>
{ typedef struct { const char *p; ptrdiff_t n; } gostring25;
"call.c", extern gostring25 GoStr25();
`#include <stddef.h> void f25() { GoStr25(); }`,
typedef struct { const char *p; ptrdiff_t n; } gostring;
extern gostring GoStr();
void f() { GoStr(); }`,
},
},
fail: true, fail: true,
}, },
{ {
...@@ -296,37 +295,37 @@ var ptrTests = []ptrTest{ ...@@ -296,37 +295,37 @@ var ptrTests = []ptrTest{
// that is, we are testing something that is not unsafe. // that is, we are testing something that is not unsafe.
name: "ptrdata1", name: "ptrdata1",
c: `#include <stdlib.h> c: `#include <stdlib.h>
void f(void* p) {}`, void f26(void* p) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
support: `type S struct { p *int; a [8*8]byte; u uintptr }`, support: `type S26 struct { p *int; a [8*8]byte; u uintptr }`,
body: `i := 0; p := &S{u:uintptr(unsafe.Pointer(&i))}; q := (*S)(C.malloc(C.size_t(unsafe.Sizeof(*p)))); *q = *p; C.f(unsafe.Pointer(q))`, body: `i := 0; p := &S26{u:uintptr(unsafe.Pointer(&i))}; q := (*S26)(C.malloc(C.size_t(unsafe.Sizeof(*p)))); *q = *p; C.f26(unsafe.Pointer(q))`,
fail: false, fail: false,
}, },
{ {
// Like ptrdata1, but with a type that uses a GC program. // Like ptrdata1, but with a type that uses a GC program.
name: "ptrdata2", name: "ptrdata2",
c: `#include <stdlib.h> c: `#include <stdlib.h>
void f(void* p) {}`, void f27(void* p) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
support: `type S struct { p *int; a [32769*8]byte; q *int; u uintptr }`, support: `type S27 struct { p *int; a [32769*8]byte; q *int; u uintptr }`,
body: `i := 0; p := S{u:uintptr(unsafe.Pointer(&i))}; q := (*S)(C.malloc(C.size_t(unsafe.Sizeof(p)))); *q = p; C.f(unsafe.Pointer(q))`, body: `i := 0; p := S27{u:uintptr(unsafe.Pointer(&i))}; q := (*S27)(C.malloc(C.size_t(unsafe.Sizeof(p)))); *q = p; C.f27(unsafe.Pointer(q))`,
fail: false, fail: false,
}, },
{ {
// Check deferred pointers when they are used, not // Check deferred pointers when they are used, not
// when the defer statement is run. // when the defer statement is run.
name: "defer", name: "defer1",
c: `typedef struct s { int *p; } s; void f(s *ps) {}`, c: `typedef struct s28 { int *p; } s28; void f28(s28 *ps) {}`,
body: `p := &C.s{}; defer C.f(p); p.p = new(C.int)`, body: `p := &C.s28{}; defer C.f28(p); p.p = new(C.int)`,
fail: true, fail: true,
}, },
{ {
// Check a pointer to a union if the union has any // Check a pointer to a union if the union has any
// pointer fields. // pointer fields.
name: "union1", name: "union1",
c: `typedef union { char **p; unsigned long i; } u; void f(u *pu) {}`, c: `typedef union { char **p; unsigned long i; } u29; void f29(u29 *pu) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
body: `var b C.char; p := &b; C.f((*C.u)(unsafe.Pointer(&p)))`, body: `var b C.char; p := &b; C.f29((*C.u29)(unsafe.Pointer(&p)))`,
fail: true, fail: true,
}, },
{ {
...@@ -336,55 +335,55 @@ var ptrTests = []ptrTest{ ...@@ -336,55 +335,55 @@ var ptrTests = []ptrTest{
// integer that happens to have the same // integer that happens to have the same
// representation as a pointer. // representation as a pointer.
name: "union2", name: "union2",
c: `typedef union { unsigned long i; } u; void f(u *pu) {}`, c: `typedef union { unsigned long i; } u39; void f39(u39 *pu) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
body: `var b C.char; p := &b; C.f((*C.u)(unsafe.Pointer(&p)))`, body: `var b C.char; p := &b; C.f39((*C.u39)(unsafe.Pointer(&p)))`,
fail: false, fail: false,
}, },
{ {
// Test preemption while entering a cgo call. Issue #21306. // Test preemption while entering a cgo call. Issue #21306.
name: "preempt-during-call", name: "preemptduringcall",
c: `void f() {}`, c: `void f30() {}`,
imports: []string{"runtime", "sync"}, imports: []string{"runtime", "sync"},
body: `var wg sync.WaitGroup; wg.Add(100); for i := 0; i < 100; i++ { go func(i int) { for j := 0; j < 100; j++ { C.f(); runtime.GOMAXPROCS(i) }; wg.Done() }(i) }; wg.Wait()`, body: `var wg sync.WaitGroup; wg.Add(100); for i := 0; i < 100; i++ { go func(i int) { for j := 0; j < 100; j++ { C.f30(); runtime.GOMAXPROCS(i) }; wg.Done() }(i) }; wg.Wait()`,
fail: false, fail: false,
}, },
{ {
// Test poller deadline with cgocheck=2. Issue #23435. // Test poller deadline with cgocheck=2. Issue #23435.
name: "deadline", name: "deadline",
c: `#define US 10`, c: `#define US31 10`,
imports: []string{"os", "time"}, imports: []string{"os", "time"},
body: `r, _, _ := os.Pipe(); r.SetDeadline(time.Now().Add(C.US * time.Microsecond))`, body: `r, _, _ := os.Pipe(); r.SetDeadline(time.Now().Add(C.US31 * time.Microsecond))`,
fail: false, fail: false,
}, },
{ {
// Test for double evaluation of channel receive. // Test for double evaluation of channel receive.
name: "chan-recv", name: "chanrecv",
c: `void f(char** p) {}`, c: `void f32(char** p) {}`,
imports: []string{"time"}, imports: []string{"time"},
body: `c := make(chan []*C.char, 2); c <- make([]*C.char, 1); go func() { time.Sleep(10 * time.Second); panic("received twice from chan") }(); C.f(&(<-c)[0]);`, body: `c := make(chan []*C.char, 2); c <- make([]*C.char, 1); go func() { time.Sleep(10 * time.Second); panic("received twice from chan") }(); C.f32(&(<-c)[0]);`,
fail: false, fail: false,
}, },
{ {
// Test that converting the address of a struct field // Test that converting the address of a struct field
// to unsafe.Pointer still just checks that field. // to unsafe.Pointer still just checks that field.
// Issue #25941. // Issue #25941.
name: "struct-field", name: "structfield",
c: `void f(void* p) {}`, c: `void f33(void* p) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
support: `type S struct { p *int; a [8]byte; u uintptr }`, support: `type S33 struct { p *int; a [8]byte; u uintptr }`,
body: `s := &S{p: new(int)}; C.f(unsafe.Pointer(&s.a))`, body: `s := &S33{p: new(int)}; C.f33(unsafe.Pointer(&s.a))`,
fail: false, fail: false,
}, },
{ {
// Test that converting multiple struct field // Test that converting multiple struct field
// addresses to unsafe.Pointer still just checks those // addresses to unsafe.Pointer still just checks those
// fields. Issue #25941. // fields. Issue #25941.
name: "struct-field-2", name: "structfield2",
c: `void f(void* p, int r, void* s) {}`, c: `void f34(void* p, int r, void* s) {}`,
imports: []string{"unsafe"}, imports: []string{"unsafe"},
support: `type S struct { a [8]byte; p *int; b int64; }`, support: `type S34 struct { a [8]byte; p *int; b int64; }`,
body: `s := &S{p: new(int)}; C.f(unsafe.Pointer(&s.a), 32, unsafe.Pointer(&s.b))`, body: `s := &S34{p: new(int)}; C.f34(unsafe.Pointer(&s.a), 32, unsafe.Pointer(&s.b))`,
fail: false, fail: false,
}, },
{ {
...@@ -392,18 +391,18 @@ var ptrTests = []ptrTest{ ...@@ -392,18 +391,18 @@ var ptrTests = []ptrTest{
// evaluated when a deferred function is deferred, not // evaluated when a deferred function is deferred, not
// when it is run. // when it is run.
name: "defer2", name: "defer2",
c: `void f(char **pc) {}`, c: `void f35(char **pc) {}`,
support: `type S1 struct { s []*C.char }; type S2 struct { ps *S1 }`, support: `type S35a struct { s []*C.char }; type S35b struct { ps *S35a }`,
body: `p := &S2{&S1{[]*C.char{nil}}}; defer C.f(&p.ps.s[0]); p.ps = nil`, body: `p := &S35b{&S35a{[]*C.char{nil}}}; defer C.f35(&p.ps.s[0]); p.ps = nil`,
fail: false, fail: false,
}, },
{ {
// Test that indexing into a function call still // Test that indexing into a function call still
// examines only the slice being indexed. // examines only the slice being indexed.
name: "buffer", name: "buffer",
c: `void f(void *p) {}`, c: `void f36(void *p) {}`,
imports: []string{"bytes", "unsafe"}, imports: []string{"bytes", "unsafe"},
body: `var b bytes.Buffer; b.WriteString("a"); C.f(unsafe.Pointer(&b.Bytes()[0]))`, body: `var b bytes.Buffer; b.WriteString("a"); C.f36(unsafe.Pointer(&b.Bytes()[0]))`,
fail: false, fail: false,
}, },
{ {
...@@ -411,8 +410,8 @@ var ptrTests = []ptrTest{ ...@@ -411,8 +410,8 @@ var ptrTests = []ptrTest{
name: "finalizer", name: "finalizer",
c: `// Nothing to declare.`, c: `// Nothing to declare.`,
imports: []string{"os"}, imports: []string{"os"},
support: `func open() { os.Open(os.Args[0]) }; var G [][]byte`, support: `func open37() { os.Open(os.Args[0]) }; var G37 [][]byte`,
body: `for i := 0; i < 10000; i++ { G = append(G, make([]byte, 4096)); if i % 100 == 0 { G = nil; open() } }`, body: `for i := 0; i < 10000; i++ { G37 = append(G37, make([]byte, 4096)); if i % 100 == 0 { G37 = nil; open37() } }`,
fail: false, fail: false,
}, },
{ {
...@@ -420,103 +419,155 @@ var ptrTests = []ptrTest{ ...@@ -420,103 +419,155 @@ var ptrTests = []ptrTest{
name: "structof", name: "structof",
c: `// Nothing to declare.`, c: `// Nothing to declare.`,
imports: []string{"reflect"}, imports: []string{"reflect"},
support: `type MyInt int; func (i MyInt) Get() int { return int(i) }; type Getter interface { Get() int }`, support: `type MyInt38 int; func (i MyInt38) Get() int { return int(i) }; type Getter38 interface { Get() int }`,
body: `t := reflect.StructOf([]reflect.StructField{{Name: "MyInt", Type: reflect.TypeOf(MyInt(0)), Anonymous: true}}); v := reflect.New(t).Elem(); v.Interface().(Getter).Get()`, body: `t := reflect.StructOf([]reflect.StructField{{Name: "MyInt38", Type: reflect.TypeOf(MyInt38(0)), Anonymous: true}}); v := reflect.New(t).Elem(); v.Interface().(Getter38).Get()`,
fail: false, fail: false,
}, },
} }
func TestPointerChecks(t *testing.T) { func TestPointerChecks(t *testing.T) {
dir, exe := buildPtrTests(t)
// We (TestPointerChecks) return before the parallel subtest functions do,
// so we can't just defer os.RemoveAll(dir). Instead we have to wait for
// the parallel subtests to finish. This code looks racy but is not:
// the add +1 run in serial before testOne blocks. The -1 run in parallel
// after testOne finishes.
var pending int32
for _, pt := range ptrTests { for _, pt := range ptrTests {
pt := pt pt := pt
t.Run(pt.name, func(t *testing.T) { t.Run(pt.name, func(t *testing.T) {
testOne(t, pt) atomic.AddInt32(&pending, +1)
defer func() {
if atomic.AddInt32(&pending, -1) == 0 {
println("removing", dir)
os.RemoveAll(dir)
}
}()
testOne(t, pt, exe)
}) })
} }
} }
func testOne(t *testing.T, pt ptrTest) { func buildPtrTests(t *testing.T) (dir, exe string) {
t.Parallel() var gopath string
if *tmp != "" {
gopath, err := ioutil.TempDir("", filepath.Base(t.Name())) gopath = *tmp
dir = ""
} else {
d, err := ioutil.TempDir("", filepath.Base(t.Name()))
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer os.RemoveAll(gopath) dir = d
gopath = d
}
src := filepath.Join(gopath, "src", "ptrtest") src := filepath.Join(gopath, "src", "ptrtest")
if err := os.MkdirAll(src, 0777); err != nil { if err := os.MkdirAll(src, 0777); err != nil {
t.Fatal(err) t.Fatal(err)
} }
name := filepath.Join(src, fmt.Sprintf("%s.go", filepath.Base(t.Name()))) // Prepare two cgo inputs: one for standard cgo and one for //export cgo.
f, err := os.Create(name) // (The latter cannot have C definitions, only declarations.)
if err != nil { var cgo1, cgo2 bytes.Buffer
t.Fatal(err) fmt.Fprintf(&cgo1, "package main\n\n/*\n")
fmt.Fprintf(&cgo2, "package main\n\n/*\n")
// C code
for _, pt := range ptrTests {
cgo := &cgo1
if strings.Contains(pt.support, "//export") {
cgo = &cgo2
} }
fmt.Fprintf(cgo, "%s\n", pt.c)
fmt.Fprintf(&cgo1, "%s\n", pt.c1)
}
fmt.Fprintf(&cgo1, "*/\nimport \"C\"\n\n")
fmt.Fprintf(&cgo2, "*/\nimport \"C\"\n\n")
// Imports
did1 := make(map[string]bool)
did2 := make(map[string]bool)
did1["os"] = true // for ptrTestMain
fmt.Fprintf(&cgo1, "import \"os\"\n")
b := bufio.NewWriter(f) for _, pt := range ptrTests {
fmt.Fprintln(b, `package main`) did := did1
fmt.Fprintln(b) cgo := &cgo1
fmt.Fprintln(b, `/*`) if strings.Contains(pt.support, "//export") {
fmt.Fprintln(b, pt.c) did = did2
fmt.Fprintln(b, `*/`) cgo = &cgo2
fmt.Fprintln(b, `import "C"`) }
fmt.Fprintln(b)
for _, imp := range pt.imports { for _, imp := range pt.imports {
fmt.Fprintln(b, `import "`+imp+`"`) if !did[imp] {
did[imp] = true
fmt.Fprintf(cgo, "import %q\n", imp)
} }
if len(pt.imports) > 0 {
fmt.Fprintln(b)
} }
if len(pt.support) > 0 {
fmt.Fprintln(b, pt.support)
fmt.Fprintln(b)
} }
fmt.Fprintln(b, `func main() {`)
fmt.Fprintln(b, pt.body)
fmt.Fprintln(b, `}`)
if err := b.Flush(); err != nil { // Func support and bodies.
t.Fatalf("flushing %s: %v", name, err) for _, pt := range ptrTests {
cgo := &cgo1
if strings.Contains(pt.support, "//export") {
cgo = &cgo2
} }
if err := f.Close(); err != nil { fmt.Fprintf(cgo, "%s\nfunc %s() {\n%s\n}\n", pt.support, pt.name, pt.body)
t.Fatalf("closing %s: %v", name, err)
} }
for _, e := range pt.extra { // Func list and main dispatch.
if err := ioutil.WriteFile(filepath.Join(src, e.name), []byte(e.contents), 0644); err != nil { fmt.Fprintf(&cgo1, "var funcs = map[string]func() {\n")
t.Fatalf("writing %s: %v", e.name, err) for _, pt := range ptrTests {
} fmt.Fprintf(&cgo1, "\t%q: %s,\n", pt.name, pt.name)
} }
fmt.Fprintf(&cgo1, "}\n\n")
fmt.Fprintf(&cgo1, "%s\n", ptrTestMain)
gomod := fmt.Sprintf("module %s\n", filepath.Base(src)) if err := ioutil.WriteFile(filepath.Join(src, "cgo1.go"), cgo1.Bytes(), 0666); err != nil {
if err := ioutil.WriteFile(filepath.Join(src, "go.mod"), []byte(gomod), 0666); err != nil { t.Fatal(err)
t.Fatalf("writing go.mod: %v", err)
} }
if err := ioutil.WriteFile(filepath.Join(src, "cgo2.go"), cgo2.Bytes(), 0666); err != nil {
args := func(cmd *exec.Cmd) string { t.Fatal(err)
return strings.Join(cmd.Args, " ")
} }
cmd := exec.Command("go", "build") cmd := exec.Command("go", "build", "-o", "ptrtest.exe")
cmd.Dir = src cmd.Dir = src
cmd.Env = append(os.Environ(), "GOPATH="+gopath) cmd.Env = append(os.Environ(), "GOPATH="+gopath)
buf, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { if err != nil {
t.Logf("%#q:\n%s", args(cmd), buf) t.Fatalf("go build: %v\n%s", err, out)
t.Fatalf("failed to build: %v", err)
} }
exe := filepath.Join(src, filepath.Base(src)) return dir, filepath.Join(src, "ptrtest.exe")
cmd = exec.Command(exe) }
cmd.Dir = src
const ptrTestMain = `
func main() {
for _, arg := range os.Args[1:] {
f := funcs[arg]
if f == nil {
panic("missing func "+arg)
}
f()
}
}
`
func testOne(t *testing.T, pt ptrTest, exe string) {
t.Parallel()
newcmd := func(cgocheck string) *exec.Cmd {
cmd := exec.Command(exe, pt.name)
cmd.Env = append(os.Environ(), "GODEBUG=cgocheck="+cgocheck)
return cmd
}
if pt.expensive { if pt.expensive {
cmd.Env = cgocheckEnv("1") cmd := newcmd("1")
buf, err := cmd.CombinedOutput() buf, err := cmd.CombinedOutput()
if err != nil { if err != nil {
t.Logf("%#q:\n%s", args(cmd), buf) t.Logf("%s", buf)
if pt.fail { if pt.fail {
t.Fatalf("test marked expensive, but failed when not expensive: %v", err) t.Fatalf("test marked expensive, but failed when not expensive: %v", err)
} else { } else {
...@@ -524,54 +575,45 @@ func testOne(t *testing.T, pt ptrTest) { ...@@ -524,54 +575,45 @@ func testOne(t *testing.T, pt ptrTest) {
} }
} }
cmd = exec.Command(exe)
cmd.Dir = src
} }
cmd := newcmd("")
if pt.expensive { if pt.expensive {
cmd.Env = cgocheckEnv("2") cmd = newcmd("2")
} }
buf, err = cmd.CombinedOutput() buf, err := cmd.CombinedOutput()
if pt.fail { if pt.fail {
if err == nil { if err == nil {
t.Logf("%#q:\n%s", args(cmd), buf) t.Logf("%s", buf)
t.Fatalf("did not fail as expected") t.Fatalf("did not fail as expected")
} else if !bytes.Contains(buf, []byte("Go pointer")) { } else if !bytes.Contains(buf, []byte("Go pointer")) {
t.Logf("%#q:\n%s", args(cmd), buf) t.Logf("%s", buf)
t.Fatalf("did not print expected error (failed with %v)", err) t.Fatalf("did not print expected error (failed with %v)", err)
} }
} else { } else {
if err != nil { if err != nil {
t.Logf("%#q:\n%s", args(cmd), buf) t.Logf("%s", buf)
t.Fatalf("failed unexpectedly: %v", err) t.Fatalf("failed unexpectedly: %v", err)
} }
if !pt.expensive { if !pt.expensive {
// Make sure it passes with the expensive checks. // Make sure it passes with the expensive checks.
cmd := exec.Command(exe) cmd := newcmd("2")
cmd.Dir = src
cmd.Env = cgocheckEnv("2")
buf, err := cmd.CombinedOutput() buf, err := cmd.CombinedOutput()
if err != nil { if err != nil {
t.Logf("%#q:\n%s", args(cmd), buf) t.Logf("%s", buf)
t.Fatalf("failed unexpectedly with expensive checks: %v", err) t.Fatalf("failed unexpectedly with expensive checks: %v", err)
} }
} }
} }
if pt.fail { if pt.fail {
cmd = exec.Command(exe) cmd := newcmd("0")
cmd.Dir = src
cmd.Env = cgocheckEnv("0")
buf, err := cmd.CombinedOutput() buf, err := cmd.CombinedOutput()
if err != nil { if err != nil {
t.Logf("%#q:\n%s", args(cmd), buf) t.Logf("%s", buf)
t.Fatalf("failed unexpectedly with GODEBUG=cgocheck=0: %v", err) t.Fatalf("failed unexpectedly with GODEBUG=cgocheck=0: %v", err)
} }
} }
} }
func cgocheckEnv(val string) []string {
return append(os.Environ(), "GODEBUG=cgocheck="+val)
}
...@@ -10,6 +10,10 @@ package main ...@@ -10,6 +10,10 @@ package main
void test() { void test() {
xxx; // ERROR HERE xxx; // ERROR HERE
} }
// Issue 8442. Cgo output unhelpful error messages for
// invalid C preambles.
void issue8442foo(UNDEF*); // ERROR HERE
*/ */
import "C" import "C"
......
...@@ -4,10 +4,99 @@ ...@@ -4,10 +4,99 @@
package main package main
/*
#include <stdio.h>
typedef struct foo foo_t;
typedef struct bar bar_t;
foo_t *foop;
long double x = 0;
static int transform(int x) { return x; }
typedef void v;
void F(v** p) {}
void fvi(void *p, int x) {}
void fppi(int** p) {}
int i;
void fi(int i) {}
*/
import "C" import "C"
import (
"unsafe"
)
func main() { func main() {
s := "" s := ""
_ = s _ = s
C.malloc(s) // ERROR HERE C.malloc(s) // ERROR HERE
x := (*C.bar_t)(nil)
C.foop = x // ERROR HERE
// issue 13129: used to output error about C.unsignedshort with CC=clang
var x C.ushort
x = int(0) // ERROR HERE: C\.ushort
// issue 13423
_ = C.fopen() // ERROR HERE
// issue 13467
var x rune = '✈'
var _ rune = C.transform(x) // ERROR HERE: C\.int
// issue 13635: used to output error about C.unsignedchar.
// This test tests all such types.
var (
_ C.uchar = "uc" // ERROR HERE: C\.uchar
_ C.schar = "sc" // ERROR HERE: C\.schar
_ C.ushort = "us" // ERROR HERE: C\.ushort
_ C.uint = "ui" // ERROR HERE: C\.uint
_ C.ulong = "ul" // ERROR HERE: C\.ulong
_ C.longlong = "ll" // ERROR HERE: C\.longlong
_ C.ulonglong = "ull" // ERROR HERE: C\.ulonglong
_ C.complexfloat = "cf" // ERROR HERE: C\.complexfloat
_ C.complexdouble = "cd" // ERROR HERE: C\.complexdouble
)
// issue 13830
// cgo converts C void* to Go unsafe.Pointer, so despite appearances C
// void** is Go *unsafe.Pointer. This test verifies that we detect the
// problem at build time.
{
type v [0]byte
f := func(p **v) {
C.F((**C.v)(unsafe.Pointer(p))) // ERROR HERE
}
var p *v
f(&p)
}
// issue 16116
_ = C.fvi(1) // ERROR HERE
// Issue 16591: Test that we detect an invalid call that was being
// hidden by a type conversion inserted by cgo checking.
{
type x *C.int
var p *x
C.fppi(p) // ERROR HERE
}
// issue 26745
_ = func(i int) int {
return C.i + 1 // ERROR HERE: :13
}
_ = func(i int) {
C.fi(i) // ERROR HERE: :6
}
C.fi = C.fi // ERROR HERE
} }
// Copyright 2014 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
/*
typedef struct foo foo_t;
typedef struct bar bar_t;
foo_t *foop;
*/
import "C"
func main() {
x := (*C.bar_t)(nil)
C.foop = x // ERROR HERE
}
// 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.
// issue 13129: used to output error about C.unsignedshort with CC=clang
package main
import "C"
func main() {
var x C.ushort
x = int(0) // ERROR HERE: C\.ushort
}
// 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
// #include <stdio.h>
import "C"
func main() {
_ = C.fopen() // ERROR HERE
}
// Copyright 2017 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 p
/*
static int transform(int x) { return x; }
*/
import "C"
func F() {
var x rune = '✈'
var _ rune = C.transform(x) // ERROR HERE: C\.int
}
// 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.
// issue 13635: used to output error about C.unsignedchar.
// This test tests all such types.
package pkg
import "C"
func main() {
var (
_ C.uchar = "uc" // ERROR HERE: C\.uchar
_ C.schar = "sc" // ERROR HERE: C\.schar
_ C.ushort = "us" // ERROR HERE: C\.ushort
_ C.uint = "ui" // ERROR HERE: C\.uint
_ C.ulong = "ul" // ERROR HERE: C\.ulong
_ C.longlong = "ll" // ERROR HERE: C\.longlong
_ C.ulonglong = "ull" // ERROR HERE: C\.ulonglong
_ C.complexfloat = "cf" // ERROR HERE: C\.complexfloat
_ C.complexdouble = "cd" // ERROR HERE: C\.complexdouble
)
}
// Copyright 2016 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.
// cgo converts C void* to Go unsafe.Pointer, so despite appearances C
// void** is Go *unsafe.Pointer. This test verifies that we detect the
// problem at build time.
package main
// typedef void v;
// void F(v** p) {}
import "C"
import "unsafe"
type v [0]byte
func f(p **v) {
C.F((**C.v)(unsafe.Pointer(p))) // ERROR HERE
}
func main() {
var p *v
f(&p)
}
// Copyright 2016 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
// void f(void *p, int x) {}
import "C"
func main() {
_ = C.f(1) // ERROR HERE
}
// Copyright 2016 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.
// Issue 16591: Test that we detect an invalid call that was being
// hidden by a type conversion inserted by cgo checking.
package p
// void f(int** p) { }
import "C"
type x *C.int
func F(p *x) {
C.f(p) // ERROR HERE
}
// Copyright 2018 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
// int a;
// void CF(int i) {}
import "C"
func F1(i int) int {
return C.a + 1 // ERROR HERE: :13
}
func F2(i int) {
C.CF(i) // ERROR HERE: :6
}
// Copyright 2014 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
/*
void foo() {}
*/
import "C"
func main() {
C.foo = C.foo // ERROR HERE
}
// Copyright 2014 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
// Issue 8442. Cgo output unhelpful error messages for
// invalid C preambles.
/*
void issue8442foo(UNDEF*); // ERROR HERE
*/
import "C"
func main() {
C.issue8442foo(nil)
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment