This repository has been archived by the owner on May 13, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 346
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement dynamic memory and fix out of bounds issues
- Loading branch information
Silas Davis
committed
May 9, 2017
1 parent
8f4e266
commit ffb8cb3
Showing
11 changed files
with
425 additions
and
101 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package vm | ||
|
||
import ( | ||
"fmt" | ||
"math" | ||
) | ||
|
||
const ( | ||
defaultInitialMemoryCapacity = 0x100000 // 1 MiB | ||
defaultMaximumMemoryCapacity = 0x1000000 // 16 MiB | ||
) | ||
|
||
// Change the length of this zero array to tweak the size of the block of zeros | ||
// written to the backing slice at a time when it is grown. A larger number may | ||
// lead to less calls to append to achieve the desired capacity although it is | ||
// unlikely to make a lot of difference. | ||
var zeroBlock []byte = make([]byte, 32) | ||
|
||
// Interface for a bounded linear memory indexed by a single int64 parameter | ||
// for each byte in the memory. | ||
type Memory interface { | ||
// Read a value from the memory store starting at offset | ||
// (index of first byte will equal offset). The value will be returned as a | ||
// length-bytes byte slice. Returns an error if the memory cannot be read or | ||
// is not allocated. | ||
// | ||
// The value returned should be copy of any underlying memory, not a reference | ||
// to the underlying store. | ||
Read(offset, length int64) ([]byte, error) | ||
// Write a value to the memory starting at offset (the index of the first byte | ||
// written will equal offset). The value is provided as bytes to be written | ||
// consecutively to the memory store. Return an error if the memory cannot be | ||
// written or allocated. | ||
Write(offset int64, value []byte) error | ||
// Returns the current capacity of the memory. For dynamically allocating | ||
// memory this capacity can be used as a write offset that is guaranteed to be | ||
// unused. Solidity in particular makes this assumption when using MSIZE to | ||
// get the current allocated memory. | ||
Capacity() int64 | ||
} | ||
|
||
func NewDynamicMemory(initialCapacity, maximumCapacity int64) Memory { | ||
return &dynamicMemory{ | ||
slice: make([]byte, initialCapacity), | ||
maximumCapacity: maximumCapacity, | ||
} | ||
} | ||
|
||
func DefaultDynamicMemoryProvider() Memory { | ||
return NewDynamicMemory(defaultInitialMemoryCapacity, defaultMaximumMemoryCapacity) | ||
} | ||
|
||
// Implements a bounded dynamic memory that relies on Go's (pretty good) dynamic | ||
// array allocation via a backing slice | ||
type dynamicMemory struct { | ||
slice []byte | ||
maximumCapacity int64 | ||
} | ||
|
||
func (mem *dynamicMemory) Read(offset, length int64) ([]byte, error) { | ||
capacity := offset + length | ||
err := mem.ensureCapacity(capacity) | ||
if err != nil { | ||
return nil, err | ||
} | ||
value := make([]byte, length) | ||
copy(value, mem.slice[offset:capacity]) | ||
return value, nil | ||
} | ||
|
||
func (mem *dynamicMemory) Write(offset int64, value []byte) error { | ||
capacity := offset + int64(len(value)) | ||
err := mem.ensureCapacity(capacity) | ||
if err != nil { | ||
return err | ||
} | ||
copy(mem.slice[offset:capacity], value) | ||
return nil | ||
} | ||
|
||
func (mem *dynamicMemory) Capacity() int64 { | ||
return int64(len(mem.slice)) | ||
} | ||
|
||
// Ensures the current memory store can hold newCapacity. Will only grow the | ||
// memory. | ||
func (mem *dynamicMemory) ensureCapacity(newCapacity int64) error { | ||
if newCapacity > math.MaxInt32 { | ||
// If we ever did want to then we would need to maintain multiple pages | ||
// of memory | ||
return fmt.Errorf("Cannot address memory beyond a maximum index "+ | ||
"of Int32 type (%v bytes)", math.MaxInt32) | ||
} | ||
newCapacityInt := int(newCapacity) | ||
// If we're already big enough so return | ||
if newCapacityInt <= len(mem.slice) { | ||
return nil | ||
} | ||
if newCapacity > mem.maximumCapacity { | ||
return fmt.Errorf("Cannot grow memory because it would exceed the "+ | ||
"current maximum limit of %v bytes", mem.maximumCapacity) | ||
} | ||
// Ensure the backing array of slice is big enough | ||
// Grow the memory one word at time using the pre-allocated zeroBlock to avoid | ||
// unnecessary allocations. Use append to make use of any spare capacity in | ||
// the slice's backing array. | ||
for newCapacityInt > cap(mem.slice) { | ||
// We'll trust Go exponentially grow our arrays (at first). | ||
mem.slice = append(mem.slice, zeroBlock...) | ||
} | ||
// Now we've ensured the backing array of the slice is big enough we can | ||
// just re-slice (even if len(mem.slice) < newCapacity) | ||
mem.slice = mem.slice[:newCapacity] | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
package vm | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestDynamicMemory_static_allocation(t *testing.T) { | ||
mem := NewDynamicMemory(4, 4).(*dynamicMemory) | ||
mem.Write(0, []byte{1}) | ||
mem.Write(1, []byte{0, 0, 1}) | ||
assert.Equal(t, []byte{1, 0, 0, 1}, mem.slice) | ||
assert.Equal(t, 4, cap(mem.slice), "Slice capacity should not grow") | ||
} | ||
|
||
func TestDynamicMemory_ReadAhead(t *testing.T) { | ||
mem := NewDynamicMemory(4, 8).(*dynamicMemory) | ||
value, err := mem.Read(2, 4) | ||
assert.NoError(t, err) | ||
// Value should be size requested | ||
assert.Equal(t, []byte{0, 0, 0, 0}, value) | ||
// Slice should have grown to that plus offset | ||
assert.Equal(t, []byte{0, 0, 0, 0, 0, 0}, mem.slice) | ||
|
||
value, err = mem.Read(2, 6) | ||
assert.NoError(t, err) | ||
assert.Equal(t, []byte{0, 0, 0, 0, 0, 0}, value) | ||
assert.Equal(t, []byte{0, 0, 0, 0, 0, 0, 0, 0}, mem.slice) | ||
|
||
// Check cannot read out of bounds | ||
_, err = mem.Read(2, 7) | ||
assert.Error(t, err) | ||
} | ||
|
||
func TestDynamicMemory_WriteAhead(t *testing.T) { | ||
mem := NewDynamicMemory(4, 8).(*dynamicMemory) | ||
err := mem.Write(4, []byte{1, 2, 3, 4}) | ||
assert.NoError(t, err) | ||
assert.Equal(t, []byte{0, 0, 0, 0, 1, 2, 3, 4}, mem.slice) | ||
|
||
err = mem.Write(4, []byte{1, 2, 3, 4, 5}) | ||
assert.Error(t, err) | ||
} | ||
|
||
func TestDynamicMemory_WriteRead(t *testing.T) { | ||
mem := NewDynamicMemory(1, 0x10000000).(*dynamicMemory) | ||
// Text is out of copyright | ||
bytesToWrite := []byte(`He paused. He felt the rhythm of the verse about him in the room. | ||
How melancholy it was! Could he, too, write like that, express the | ||
melancholy of his soul in verse? There were so many things he wanted | ||
to describe: his sensation of a few hours before on Grattan Bridge, for | ||
example. If he could get back again into that mood....`) | ||
|
||
// Write the bytes | ||
offset := 0x1000000 | ||
err := mem.Write(int64(offset), bytesToWrite) | ||
assert.NoError(t, err) | ||
assert.Equal(t, append(make([]byte, offset), bytesToWrite...), mem.slice) | ||
assert.Equal(t, offset+len(bytesToWrite), len(mem.slice)) | ||
|
||
// Read them back | ||
value, err := mem.Read(int64(offset), int64(len(bytesToWrite))) | ||
assert.NoError(t, err) | ||
assert.Equal(t, bytesToWrite, value) | ||
|
||
} | ||
|
||
func TestDynamicMemory_Capacity(t *testing.T) { | ||
mem := NewDynamicMemory(1, 0x10000000).(*dynamicMemory) | ||
|
||
assert.Equal(t, int64(1), mem.Capacity()) | ||
|
||
capacity := int64(1234) | ||
err := mem.ensureCapacity(capacity) | ||
assert.NoError(t, err) | ||
assert.Equal(t, capacity, mem.Capacity()) | ||
|
||
capacity = int64(123456789) | ||
err = mem.ensureCapacity(capacity) | ||
assert.NoError(t, err) | ||
assert.Equal(t, capacity, mem.Capacity()) | ||
|
||
// Check doesn't shrink or err | ||
err = mem.ensureCapacity(12) | ||
assert.NoError(t, err) | ||
assert.Equal(t, capacity, mem.Capacity()) | ||
} | ||
|
||
func TestDynamicMemory_ensureCapacity(t *testing.T) { | ||
mem := NewDynamicMemory(4, 16).(*dynamicMemory) | ||
// Check we can grow within bounds | ||
err := mem.ensureCapacity(8) | ||
assert.NoError(t, err) | ||
expected := make([]byte, 8) | ||
assert.Equal(t, expected, mem.slice) | ||
|
||
// Check we can grow to bounds | ||
err = mem.ensureCapacity(16) | ||
assert.NoError(t, err) | ||
expected = make([]byte, 16) | ||
assert.Equal(t, expected, mem.slice) | ||
|
||
err = mem.ensureCapacity(1) | ||
assert.NoError(t, err) | ||
assert.Equal(t, 16, len(mem.slice)) | ||
|
||
err = mem.ensureCapacity(17) | ||
assert.Error(t, err, "Should not be possible to grow over capacity") | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.