Skip to content

Commit 160ec0e

Browse files
committed
internal/reflectlite: do not use the reflect package
This avoids a dependency on the reflect package. I've tried to deduplicate as much as reasonably possible, while keeping the code clean. Especially some tricky methods for the reflect.Type type have a shared implementation (using //go:linkname). In other cases, I've simply used the same type using a type alias, in the other direction (the reflect package now imports the internal/reflectlite package instead of the other way around).
1 parent 04c7057 commit 160ec0e

File tree

14 files changed

+1549
-1070
lines changed

14 files changed

+1549
-1070
lines changed

src/reflect/endian-big.go renamed to src/internal/reflectlite/endian-big.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//go:build mips
22

3-
package reflect
3+
package reflectlite
44

55
import "unsafe"
66

src/reflect/endian-little.go renamed to src/internal/reflectlite/endian-little.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//go:build !mips
22

3-
package reflect
3+
package reflectlite
44

55
import "unsafe"
66

src/internal/reflectlite/error.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package reflectlite
2+
3+
type TypeError struct {
4+
Method string
5+
}
6+
7+
func (e *TypeError) Error() string {
8+
return "reflect: call of reflect.Type." + e.Method + " on invalid type"
9+
}
10+
11+
var (
12+
errTypeElem = &TypeError{"Elem"}
13+
errTypeField = &TypeError{"Field"}
14+
)
15+
16+
type ValueError struct {
17+
Method string
18+
Kind Kind
19+
}
20+
21+
func (e *ValueError) Error() string {
22+
if e.Kind == 0 {
23+
return "reflect: call of " + e.Method + " on zero Value"
24+
}
25+
return "reflect: call of " + e.Method + " on " + e.Kind.String() + " Value"
26+
}

src/internal/reflectlite/reflect.go

Lines changed: 0 additions & 51 deletions
This file was deleted.
Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
// Copyright 2009 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+
5+
package reflectlite
6+
7+
import (
8+
"unicode/utf8"
9+
)
10+
11+
// errSyntax indicates that a value does not have the right syntax for the target type.
12+
var errSyntax = badSyntax{}
13+
14+
type badSyntax struct{}
15+
16+
func (badSyntax) Error() string {
17+
return "invalid syntax"
18+
}
19+
20+
func unhex(b byte) (v rune, ok bool) {
21+
c := rune(b)
22+
switch {
23+
case '0' <= c && c <= '9':
24+
return c - '0', true
25+
case 'a' <= c && c <= 'f':
26+
return c - 'a' + 10, true
27+
case 'A' <= c && c <= 'F':
28+
return c - 'A' + 10, true
29+
}
30+
return
31+
}
32+
33+
const (
34+
lowerhex = "0123456789abcef"
35+
)
36+
37+
// unquoteChar decodes the first character or byte in the escaped string
38+
// or character literal represented by the string s.
39+
// It returns four values:
40+
//
41+
// 1. value, the decoded Unicode code point or byte value;
42+
// 2. multibyte, a boolean indicating whether the decoded character requires a multibyte UTF-8 representation;
43+
// 3. tail, the remainder of the string after the character; and
44+
// 4. an error that will be nil if the character is syntactically valid.
45+
//
46+
// The second argument, quote, specifies the type of literal being parsed
47+
// and therefore which escaped quote character is permitted.
48+
// If set to a single quote, it permits the sequence \' and disallows unescaped '.
49+
// If set to a double quote, it permits \" and disallows unescaped ".
50+
// If set to zero, it does not permit either escape and allows both quote characters to appear unescaped.
51+
func unquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) {
52+
// easy cases
53+
if len(s) == 0 {
54+
err = errSyntax
55+
return
56+
}
57+
switch c := s[0]; {
58+
case c == quote && (quote == '\'' || quote == '"'):
59+
err = errSyntax
60+
return
61+
case c >= utf8.RuneSelf:
62+
r, size := utf8.DecodeRuneInString(s)
63+
return r, true, s[size:], nil
64+
case c != '\\':
65+
return rune(s[0]), false, s[1:], nil
66+
}
67+
68+
// hard case: c is backslash
69+
if len(s) <= 1 {
70+
err = errSyntax
71+
return
72+
}
73+
c := s[1]
74+
s = s[2:]
75+
76+
switch c {
77+
case 'a':
78+
value = '\a'
79+
case 'b':
80+
value = '\b'
81+
case 'f':
82+
value = '\f'
83+
case 'n':
84+
value = '\n'
85+
case 'r':
86+
value = '\r'
87+
case 't':
88+
value = '\t'
89+
case 'v':
90+
value = '\v'
91+
case 'x', 'u', 'U':
92+
n := 0
93+
switch c {
94+
case 'x':
95+
n = 2
96+
case 'u':
97+
n = 4
98+
case 'U':
99+
n = 8
100+
}
101+
var v rune
102+
if len(s) < n {
103+
err = errSyntax
104+
return
105+
}
106+
for j := 0; j < n; j++ {
107+
x, ok := unhex(s[j])
108+
if !ok {
109+
err = errSyntax
110+
return
111+
}
112+
v = v<<4 | x
113+
}
114+
s = s[n:]
115+
if c == 'x' {
116+
// single-byte string, possibly not UTF-8
117+
value = v
118+
break
119+
}
120+
if v > utf8.MaxRune {
121+
err = errSyntax
122+
return
123+
}
124+
value = v
125+
multibyte = true
126+
case '0', '1', '2', '3', '4', '5', '6', '7':
127+
v := rune(c) - '0'
128+
if len(s) < 2 {
129+
err = errSyntax
130+
return
131+
}
132+
for j := 0; j < 2; j++ { // one digit already; two more
133+
x := rune(s[j]) - '0'
134+
if x < 0 || x > 7 {
135+
err = errSyntax
136+
return
137+
}
138+
v = (v << 3) | x
139+
}
140+
s = s[2:]
141+
if v > 255 {
142+
err = errSyntax
143+
return
144+
}
145+
value = v
146+
case '\\':
147+
value = '\\'
148+
case '\'', '"':
149+
if c != quote {
150+
err = errSyntax
151+
return
152+
}
153+
value = rune(c)
154+
default:
155+
err = errSyntax
156+
return
157+
}
158+
tail = s
159+
return
160+
}
161+
162+
// unquote interprets s as a single-quoted, double-quoted,
163+
// or backquoted Go string literal, returning the string value
164+
// that s quotes. (If s is single-quoted, it would be a Go
165+
// character literal; unquote returns the corresponding
166+
// one-character string.)
167+
func unquote(s string) (string, error) {
168+
n := len(s)
169+
if n < 2 {
170+
return "", errSyntax
171+
}
172+
quote := s[0]
173+
if quote != s[n-1] {
174+
return "", errSyntax
175+
}
176+
s = s[1 : n-1]
177+
178+
if quote == '`' {
179+
if contains(s, '`') {
180+
return "", errSyntax
181+
}
182+
if contains(s, '\r') {
183+
// -1 because we know there is at least one \r to remove.
184+
buf := make([]byte, 0, len(s)-1)
185+
for i := 0; i < len(s); i++ {
186+
if s[i] != '\r' {
187+
buf = append(buf, s[i])
188+
}
189+
}
190+
return string(buf), nil
191+
}
192+
return s, nil
193+
}
194+
if quote != '"' && quote != '\'' {
195+
return "", errSyntax
196+
}
197+
if contains(s, '\n') {
198+
return "", errSyntax
199+
}
200+
201+
// Is it trivial? Avoid allocation.
202+
if !contains(s, '\\') && !contains(s, quote) {
203+
switch quote {
204+
case '"':
205+
if utf8.ValidString(s) {
206+
return s, nil
207+
}
208+
case '\'':
209+
r, size := utf8.DecodeRuneInString(s)
210+
if size == len(s) && (r != utf8.RuneError || size != 1) {
211+
return s, nil
212+
}
213+
}
214+
}
215+
216+
var runeTmp [utf8.UTFMax]byte
217+
buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
218+
for len(s) > 0 {
219+
c, multibyte, ss, err := unquoteChar(s, quote)
220+
if err != nil {
221+
return "", err
222+
}
223+
s = ss
224+
if c < utf8.RuneSelf || !multibyte {
225+
buf = append(buf, byte(c))
226+
} else {
227+
n := utf8.EncodeRune(runeTmp[:], c)
228+
buf = append(buf, runeTmp[:n]...)
229+
}
230+
if quote == '\'' && len(s) != 0 {
231+
// single-quoted must be single character
232+
return "", errSyntax
233+
}
234+
}
235+
return string(buf), nil
236+
}
237+
238+
// contains reports whether the string contains the byte c.
239+
func contains(s string, c byte) bool {
240+
return indexByteString(s, c) != -1
241+
}
242+
243+
// Index finds the index of the first instance of the specified byte in the string.
244+
// If the byte is not found, this returns -1.
245+
func indexByteString(s string, c byte) int {
246+
for i := 0; i < len(s); i++ {
247+
if s[i] == c {
248+
return i
249+
}
250+
}
251+
return -1
252+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package reflectlite
2+
3+
import "unsafe"
4+
5+
// Some of code here has been copied from the Go sources:
6+
// https://github.com/golang/go/blob/go1.15.2/src/reflect/swapper.go
7+
// It has the following copyright note:
8+
//
9+
// Copyright 2016 The Go Authors. All rights reserved.
10+
// Use of this source code is governed by a BSD-style
11+
// license that can be found in the LICENSE file.
12+
13+
func Swapper(slice interface{}) func(i, j int) {
14+
v := ValueOf(slice)
15+
if v.Kind() != Slice {
16+
panic(&ValueError{Method: "Swapper"})
17+
}
18+
19+
// Just return Nop func if nothing to swap.
20+
if v.Len() < 2 {
21+
return func(i, j int) {}
22+
}
23+
24+
typ := v.typecode.Elem()
25+
size := typ.Size()
26+
27+
header := (*sliceHeader)(v.value)
28+
tmp := unsafe.Pointer(&make([]byte, size)[0])
29+
30+
return func(i, j int) {
31+
if uint(i) >= uint(header.len) || uint(j) >= uint(header.len) {
32+
panic("reflect: slice index out of range")
33+
}
34+
val1 := unsafe.Add(header.data, uintptr(i)*size)
35+
val2 := unsafe.Add(header.data, uintptr(j)*size)
36+
memcpy(tmp, val1, size)
37+
memcpy(val1, val2, size)
38+
memcpy(val2, tmp, size)
39+
}
40+
}

0 commit comments

Comments
 (0)