1// Copyright (c) 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package field
6
7import (
8	"testing"
9	"testing/quick"
10)
11
12func checkAliasingOneArg(f func(v, x *Element) *Element) func(v, x Element) bool {
13	return func(v, x Element) bool {
14		x1, v1 := x, x
15
16		// Calculate a reference f(x) without aliasing.
17		if out := f(&v, &x); out != &v && isInBounds(out) {
18			return false
19		}
20
21		// Test aliasing the argument and the receiver.
22		if out := f(&v1, &v1); out != &v1 || v1 != v {
23			return false
24		}
25
26		// Ensure the arguments was not modified.
27		return x == x1
28	}
29}
30
31func checkAliasingTwoArgs(f func(v, x, y *Element) *Element) func(v, x, y Element) bool {
32	return func(v, x, y Element) bool {
33		x1, y1, v1 := x, y, Element{}
34
35		// Calculate a reference f(x, y) without aliasing.
36		if out := f(&v, &x, &y); out != &v && isInBounds(out) {
37			return false
38		}
39
40		// Test aliasing the first argument and the receiver.
41		v1 = x
42		if out := f(&v1, &v1, &y); out != &v1 || v1 != v {
43			return false
44		}
45		// Test aliasing the second argument and the receiver.
46		v1 = y
47		if out := f(&v1, &x, &v1); out != &v1 || v1 != v {
48			return false
49		}
50
51		// Calculate a reference f(x, x) without aliasing.
52		if out := f(&v, &x, &x); out != &v {
53			return false
54		}
55
56		// Test aliasing the first argument and the receiver.
57		v1 = x
58		if out := f(&v1, &v1, &x); out != &v1 || v1 != v {
59			return false
60		}
61		// Test aliasing the second argument and the receiver.
62		v1 = x
63		if out := f(&v1, &x, &v1); out != &v1 || v1 != v {
64			return false
65		}
66		// Test aliasing both arguments and the receiver.
67		v1 = x
68		if out := f(&v1, &v1, &v1); out != &v1 || v1 != v {
69			return false
70		}
71
72		// Ensure the arguments were not modified.
73		return x == x1 && y == y1
74	}
75}
76
77// TestAliasing checks that receivers and arguments can alias each other without
78// leading to incorrect results. That is, it ensures that it's safe to write
79//
80//     v.Invert(v)
81//
82// or
83//
84//     v.Add(v, v)
85//
86// without any of the inputs getting clobbered by the output being written.
87func TestAliasing(t *testing.T) {
88	type target struct {
89		name     string
90		oneArgF  func(v, x *Element) *Element
91		twoArgsF func(v, x, y *Element) *Element
92	}
93	for _, tt := range []target{
94		{name: "Absolute", oneArgF: (*Element).Absolute},
95		{name: "Invert", oneArgF: (*Element).Invert},
96		{name: "Negate", oneArgF: (*Element).Negate},
97		{name: "Set", oneArgF: (*Element).Set},
98		{name: "Square", oneArgF: (*Element).Square},
99		{name: "Multiply", twoArgsF: (*Element).Multiply},
100		{name: "Add", twoArgsF: (*Element).Add},
101		{name: "Subtract", twoArgsF: (*Element).Subtract},
102		{
103			name: "Select0",
104			twoArgsF: func(v, x, y *Element) *Element {
105				return (*Element).Select(v, x, y, 0)
106			},
107		},
108		{
109			name: "Select1",
110			twoArgsF: func(v, x, y *Element) *Element {
111				return (*Element).Select(v, x, y, 1)
112			},
113		},
114	} {
115		var err error
116		switch {
117		case tt.oneArgF != nil:
118			err = quick.Check(checkAliasingOneArg(tt.oneArgF), &quick.Config{MaxCountScale: 1 << 8})
119		case tt.twoArgsF != nil:
120			err = quick.Check(checkAliasingTwoArgs(tt.twoArgsF), &quick.Config{MaxCountScale: 1 << 8})
121		}
122		if err != nil {
123			t.Errorf("%v: %v", tt.name, err)
124		}
125	}
126}
127