summaryrefslogtreecommitdiffhomepage
path: root/tools/go_marshal/analysis/analysis_unsafe.go
blob: 7a3d6bbea53f502a5a19969ae7614baa8fb15837 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// Copyright 2019 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package analysis implements common functionality used by generated
// go_marshal tests.
package analysis

// All functions in this package are unsafe and are not intended for general
// consumption. They contain sharp edge cases and the caller is responsible for
// ensuring none of them are hit. Callers must be carefully to pass in only sane
// arguments. Failure to do so may cause panics at best and arbitrary memory
// corruption at worst.
//
// Never use outside of tests.

import (
	"fmt"
	"math/rand"
	"reflect"
	"testing"
	"unsafe"
)

// RandomizeValue assigns random value(s) to an abitrary type. This is intended
// for used with ABI structs from go_marshal, meaning the typical restrictions
// apply (fixed-size types, no pointers, maps, channels, etc), and should only
// be used on zeroed values to avoid overwriting pointers to active go objects.
//
// Internally, we populate the type with random data by doing an unsafe cast to
// access the underlying memory of the type and filling it as if it were a byte
// slice. This almost gets us what we want, but padding fields named "_" are
// normally not accessible, so we walk the type and recursively zero all "_"
// fields.
//
// Precondition: x must be a pointer. x must not contain any valid
// pointers to active go objects (pointer fields aren't allowed in ABI
// structs anyways), or we'd be violating the go runtime contract and
// the GC may malfunction.
func RandomizeValue(x interface{}) {
	v := reflect.Indirect(reflect.ValueOf(x))
	if !v.CanSet() {
		panic("RandomizeType() called with an unaddressable value. You probably need to pass a pointer to the argument")
	}

	// Cast the underlying memory for the type into a byte slice.
	var b []byte
	hdr := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	// Note: v.UnsafeAddr panics if x is passed by value. x should be a pointer.
	hdr.Data = v.UnsafeAddr()
	hdr.Len = int(v.Type().Size())
	hdr.Cap = hdr.Len

	// Fill the byte slice with random data, which in effect fills the type with
	// random values.
	n, err := rand.Read(b)
	if err != nil || n != len(b) {
		panic("unreachable")
	}

	// Normally, padding fields are not accessible, so zero them out.
	reflectZeroPaddingFields(v.Type(), b, false)
}

// reflectZeroPaddingFields assigns zero values to padding fields for the value
// of type r, represented by the memory in data. Padding fields are defined as
// fields with the name "_". If zero is true, the immediate value itself is
// zeroed. In addition, the type is recursively scanned for padding fields in
// inner types.
//
// This is used for zeroing padding fields after calling RandomizeValue.
func reflectZeroPaddingFields(r reflect.Type, data []byte, zero bool) {
	if zero {
		for i := range data {
			data[i] = 0
		}
	}
	switch r.Kind() {
	case reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64:
		// These types are explicitly allowed in an ABI type, but we don't need
		// to recurse further as they're scalar types.
	case reflect.Struct:
		for i, numFields := 0, r.NumField(); i < numFields; i++ {
			f := r.Field(i)
			off := f.Offset
			len := f.Type.Size()
			window := data[off : off+len]
			reflectZeroPaddingFields(f.Type, window, f.Name == "_")
		}
	case reflect.Array:
		eLen := int(r.Elem().Size())
		if int(r.Size()) != eLen*r.Len() {
			panic("Array has unexpected size?")
		}
		for i, n := 0, r.Len(); i < n; i++ {
			reflectZeroPaddingFields(r.Elem(), data[i*eLen:(i+1)*eLen], false)
		}
	default:
		panic(fmt.Sprintf("Type %v not allowed in ABI struct", r.Kind()))

	}
}

// AlignmentCheck ensures the definition of the type represented by typ doesn't
// cause the go compiler to emit implicit padding between elements of the type
// (i.e. fields in a struct).
//
// AlignmentCheck doesn't explicitly recurse for embedded structs because any
// struct present in an ABI struct must also be Marshallable, and therefore
// they're aligned by definition (or their alignment check would have failed).
func AlignmentCheck(t *testing.T, typ reflect.Type) (ok bool, delta uint64) {
	switch typ.Kind() {
	case reflect.Int8, reflect.Uint8, reflect.Int16, reflect.Uint16, reflect.Int32, reflect.Uint32, reflect.Int64, reflect.Uint64:
		// Primitive types are always considered well aligned. Primitive types
		// that are fields in structs are checked independently, this branch
		// exists to handle recursive calls to alignmentCheck.
	case reflect.Struct:
		xOff := 0
		nextXOff := 0
		skipNext := false
		for i, numFields := 0, typ.NumField(); i < numFields; i++ {
			xOff = nextXOff
			f := typ.Field(i)
			fmt.Printf("Checking alignment of %s.%s @ %d [+%d]...\n", typ.Name(), f.Name, f.Offset, f.Type.Size())
			nextXOff = int(f.Offset + f.Type.Size())

			if f.Name == "_" {
				// Padding fields need not be aligned.
				fmt.Printf("Padding field of type %v\n", f.Type)
				continue
			}

			if tag, ok := f.Tag.Lookup("marshal"); ok && tag == "unaligned" {
				skipNext = true
				continue
			}

			if skipNext {
				skipNext = false
				fmt.Printf("Skipping alignment check for field %s.%s explicitly marked as unaligned.\n", typ.Name(), f.Name)
				continue
			}

			if xOff != int(f.Offset) {
				implicitPad := int(f.Offset) - xOff
				t.Fatalf("Suspect offset for field %s.%s, detected an implicit %d byte padding from offset %d to %d; either add %d bytes of explicit padding before this field or tag it as `marshal:\"unaligned\"`.", typ.Name(), f.Name, implicitPad, xOff, f.Offset, implicitPad)
			}
		}

		// Ensure structs end on a byte explicitly defined by the type.
		if typ.NumField() > 0 && nextXOff != int(typ.Size()) {
			implicitPad := int(typ.Size()) - nextXOff
			f := typ.Field(typ.NumField() - 1) // Final field
			if tag, ok := f.Tag.Lookup("marshal"); ok && tag == "unaligned" {
				// Final field explicitly marked unaligned.
				break
			}
			t.Fatalf("Suspect offset for field %s.%s at the end of %s, detected an implicit %d byte padding from offset %d to %d at the end of the struct; either add %d bytes of explict padding at end of the struct or tag the final field %s as `marshal:\"unaligned\"`.",
				typ.Name(), f.Name, typ.Name(), implicitPad, nextXOff, typ.Size(), implicitPad, f.Name)
		}
	case reflect.Array:
		// Independent arrays are also always considered well aligned. We only
		// need to worry about their alignment when they're embedded in structs,
		// which we handle above.
	default:
		t.Fatalf("Unsupported type in ABI struct while checking for field alignment for type: %v", typ.Kind())
	}
	return true, uint64(typ.Size())
}