compilationcache: improves error messages (#799)

Signed-off-by: Adrian Cole <adrian@tetrate.io>
This commit is contained in:
Crypt Keeper
2022-09-06 13:09:50 +08:00
committed by GitHub
parent 9ad8af121a
commit 2f56371078
2 changed files with 112 additions and 18 deletions

View File

@@ -125,11 +125,11 @@ func deserializeCodes(wazeroVersion string, reader io.Reader) (codes []*code, st
header := make([]byte, cacheHeaderSize)
n, err := reader.Read(header)
if err != nil {
return nil, false, err
return nil, false, fmt.Errorf("compilationcache: error reading header: %v", err)
}
if n != cacheHeaderSize {
return nil, false, fmt.Errorf("invalid header length: %d", n)
return nil, false, fmt.Errorf("compilationcache: invalid header length: %d", n)
}
// Check the version compatibility.
@@ -148,30 +148,27 @@ func deserializeCodes(wazeroVersion string, reader io.Reader) (codes []*code, st
codes = make([]*code, 0, functionsNum)
var eightBytes [8]byte
var nativeCodeLen uint64
for i := uint32(0); i < functionsNum; i++ {
c := &code{}
// Read the stack pointer ceil.
_, err = reader.Read(eightBytes[:])
if err != nil {
err = fmt.Errorf("reading stack pointer ceil: %v", err)
if c.stackPointerCeil, err = readUint64(reader, &eightBytes); err != nil {
err = fmt.Errorf("compilationcache: error reading func[%d] stack pointer ceil: %v", i, err)
break
}
c.stackPointerCeil = binary.LittleEndian.Uint64(eightBytes[:])
// Read (and mmap) the native code.
_, err = reader.Read(eightBytes[:])
if err != nil {
err = fmt.Errorf("reading native code size: %v", err)
if nativeCodeLen, err = readUint64(reader, &eightBytes); err != nil {
err = fmt.Errorf("compilationcache: error reading func[%d] reading native code size: %v", i, err)
break
}
c.codeSegment, err = platform.MmapCodeSegment(reader, int(binary.LittleEndian.Uint64(eightBytes[:])))
if err != nil {
err = fmt.Errorf("mmaping function: %v", err)
if c.codeSegment, err = platform.MmapCodeSegment(reader, int(nativeCodeLen)); err != nil {
err = fmt.Errorf("compilationcache: error mmapping func[%d] code (len=%d): %v", i, nativeCodeLen, err)
break
}
codes = append(codes, c)
}
@@ -186,3 +183,24 @@ func deserializeCodes(wazeroVersion string, reader io.Reader) (codes []*code, st
}
return
}
// readUint64 strictly reads a uint64 in little-endian byte order, using the
// given array as a buffer. This returns io.EOF if less than 8 bytes were read.
func readUint64(reader io.Reader, b *[8]byte) (uint64, error) {
s := b[0:8]
n, err := reader.Read(s)
if err != nil {
return 0, err
} else if n < 8 { // more strict than reader.Read
return 0, io.EOF
}
// read the u64 from the underlying buffer
ret := binary.LittleEndian.Uint64(s)
// clear the underlying array
for i := 0; i < 8; i++ {
b[i] = 0
}
return ret, nil
}

View File

@@ -2,9 +2,13 @@ package compiler
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"io"
"math"
"testing"
"testing/iotest"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/u32"
@@ -79,7 +83,7 @@ func TestDeserializeCodes(t *testing.T) {
name: "invalid header",
in: []byte{1},
expErr: "invalid header length: 1",
expErr: "compilationcache: invalid header length: 1",
},
{
@@ -156,7 +160,7 @@ func TestDeserializeCodes(t *testing.T) {
[]byte{1, 2, 3, 4, 5}, // code.
// Function index = 1.
),
expErr: "reading stack pointer ceil: EOF",
expErr: "compilationcache: error reading func[1] stack pointer ceil: EOF",
},
{
name: "reading native code size",
@@ -172,7 +176,7 @@ func TestDeserializeCodes(t *testing.T) {
// Function index = 1.
u64.LeBytes(12345), // stack pointer ceil.
),
expErr: "reading native code size: EOF",
expErr: "compilationcache: error reading func[1] reading native code size: EOF",
},
{
name: "mmapping",
@@ -190,7 +194,7 @@ func TestDeserializeCodes(t *testing.T) {
u64.LeBytes(5), // length of code.
// Lack of code here.
),
expErr: "mmaping function: EOF",
expErr: "compilationcache: error mmapping func[1] code (len=5): EOF",
},
}
@@ -233,7 +237,7 @@ func TestEngine_getCodesFromCache(t *testing.T) {
{
name: "error in deserialization",
ext: &testCache{caches: map[wasm.ModuleID][]byte{{}: {1, 2, 3}}},
expErr: "invalid header length: 3",
expErr: "compilationcache: invalid header length: 3",
},
{
name: "stale cache",
@@ -329,6 +333,78 @@ func TestEngine_addCodesToCache(t *testing.T) {
})
}
func Test_readUint64(t *testing.T) {
tests := []struct {
name string
input uint64
}{
{
name: "zero",
input: 0,
},
{
name: "half",
input: math.MaxUint32,
},
{
name: "max",
input: math.MaxUint64,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
input := make([]byte, 8)
binary.LittleEndian.PutUint64(input, tc.input)
var b [8]byte
n, err := readUint64(bytes.NewReader(input), &b)
require.NoError(t, err)
require.Equal(t, tc.input, n)
// ensure the buffer was cleared
var expectedB [8]byte
require.Equal(t, expectedB, b)
})
}
}
func Test_readUint64_errors(t *testing.T) {
tests := []struct {
name string
input io.Reader
expectedErr string
}{
{
name: "zero",
input: bytes.NewReader([]byte{}),
expectedErr: "EOF",
},
{
name: "not enough",
input: bytes.NewReader([]byte{1, 2}),
expectedErr: "EOF",
},
{
name: "error reading",
input: iotest.ErrReader(errors.New("ice cream")),
expectedErr: "ice cream",
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
var b [8]byte
_, err := readUint64(tc.input, &b)
require.EqualError(t, err, tc.expectedErr)
})
}
}
// testCache implements compilationcache.Cache
type testCache struct {
caches map[wasm.ModuleID][]byte