Skip to content
This repository has been archived by the owner on May 13, 2022. It is now read-only.

Implement dynamic memory and fix out of bounds issues #607

Merged
merged 1 commit into from
May 15, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions logging/loggers/burrow_format_logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/hyperledger/burrow/logging/structure"

kitlog "github.com/go-kit/kit/log"
"github.com/hyperledger/burrow/word256"
)

// Logger that implements some formatting conventions for burrow and burrow-client
Expand Down Expand Up @@ -50,7 +51,10 @@ func burrowFormatKeyValueMapper(key, value interface{}) (interface{}, interface{
switch v := value.(type) {
case []byte:
return key, fmt.Sprintf("%X", v)
case word256.Word256:
return burrowFormatKeyValueMapper(key, v.Bytes())
}

}
return key, value
}
Expand Down
2 changes: 1 addition & 1 deletion manager/burrow-mint/evm/log_event_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func TestLog4(t *testing.T) {
st.accounts[account1.Address.String()] = account1
st.accounts[account2.Address.String()] = account2

ourVm := NewVM(st, newParams(), Zero256, nil)
ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), Zero256, nil)

eventSwitch := events.NewEventSwitch()
_, err := eventSwitch.Start()
Expand Down
115 changes: 115 additions & 0 deletions manager/burrow-mint/evm/memory.go
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 (will not shrink).
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)
// 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
}
120 changes: 120 additions & 0 deletions manager/burrow-mint/evm/memory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package vm

import (
"testing"

"github.com/stretchr/testify/assert"
)

// Test static memory allocation with maximum == initial capacity - memory should not grow
func TestDynamicMemory_StaticAllocation(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")
}

// Test reading beyond the current capacity - memory should 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)
}

// Test writing beyond the current capacity - memory should grow
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_ZeroInitialMemory(t *testing.T) {
mem := NewDynamicMemory(0, 16).(*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)
}

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")

}
4 changes: 1 addition & 3 deletions manager/burrow-mint/evm/opcodes/opcodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,7 @@ var opCodeToString = map[OpCode]string{
EXTCODECOPY: "EXTCODECOPY",

// 0x50 range - 'storage' and execution
POP: "POP",
//DUP: "DUP",
//SWAP: "SWAP",
POP: "POP",
MLOAD: "MLOAD",
MSTORE: "MSTORE",
MSTORE8: "MSTORE8",
Expand Down
48 changes: 24 additions & 24 deletions manager/burrow-mint/evm/snative.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,10 @@ func SNativeContracts() map[string]*SNativeContractDescription {
`,
"addRole",
[]abi.Arg{
arg("_account", abi.AddressTypeName),
arg("_role", roleTypeName),
abiArg("_account", abi.AddressTypeName),
abiArg("_role", roleTypeName),
},
ret("result", abi.BoolTypeName),
abiReturn("result", abi.BoolTypeName),
ptypes.AddRole,
addRole},

Expand All @@ -102,10 +102,10 @@ func SNativeContracts() map[string]*SNativeContractDescription {
`,
"removeRole",
[]abi.Arg{
arg("_account", abi.AddressTypeName),
arg("_role", roleTypeName),
abiArg("_account", abi.AddressTypeName),
abiArg("_role", roleTypeName),
},
ret("result", abi.BoolTypeName),
abiReturn("result", abi.BoolTypeName),
ptypes.RmRole,
removeRole},

Expand All @@ -117,10 +117,10 @@ func SNativeContracts() map[string]*SNativeContractDescription {
`,
"hasRole",
[]abi.Arg{
arg("_account", abi.AddressTypeName),
arg("_role", roleTypeName),
abiArg("_account", abi.AddressTypeName),
abiArg("_role", roleTypeName),
},
ret("result", abi.BoolTypeName),
abiReturn("result", abi.BoolTypeName),
ptypes.HasRole,
hasRole},

Expand All @@ -133,11 +133,11 @@ func SNativeContracts() map[string]*SNativeContractDescription {
`,
"setBase",
[]abi.Arg{
arg("_account", abi.AddressTypeName),
arg("_permission", permFlagTypeName),
arg("_set", abi.BoolTypeName),
abiArg("_account", abi.AddressTypeName),
abiArg("_permission", permFlagTypeName),
abiArg("_set", abi.BoolTypeName),
},
ret("result", permFlagTypeName),
abiReturn("result", permFlagTypeName),
ptypes.SetBase,
setBase},

Expand All @@ -149,9 +149,9 @@ func SNativeContracts() map[string]*SNativeContractDescription {
`,
"unsetBase",
[]abi.Arg{
arg("_account", abi.AddressTypeName),
arg("_permission", permFlagTypeName)},
ret("result", permFlagTypeName),
abiArg("_account", abi.AddressTypeName),
abiArg("_permission", permFlagTypeName)},
abiReturn("result", permFlagTypeName),
ptypes.UnsetBase,
unsetBase},

Expand All @@ -163,9 +163,9 @@ func SNativeContracts() map[string]*SNativeContractDescription {
`,
"hasBase",
[]abi.Arg{
arg("_account", abi.AddressTypeName),
arg("_permission", permFlagTypeName)},
ret("result", abi.BoolTypeName),
abiArg("_account", abi.AddressTypeName),
abiArg("_permission", permFlagTypeName)},
abiReturn("result", abi.BoolTypeName),
ptypes.HasBase,
hasBase},

Expand All @@ -177,9 +177,9 @@ func SNativeContracts() map[string]*SNativeContractDescription {
`,
"setGlobal",
[]abi.Arg{
arg("_permission", permFlagTypeName),
arg("_set", abi.BoolTypeName)},
ret("result", permFlagTypeName),
abiArg("_permission", permFlagTypeName),
abiArg("_set", abi.BoolTypeName)},
abiReturn("result", permFlagTypeName),
ptypes.SetGlobal,
setGlobal},
),
Expand Down Expand Up @@ -324,14 +324,14 @@ func (function *SNativeFunctionDescription) NArgs() int {
return len(function.Args)
}

func arg(name string, abiTypeName abi.TypeName) abi.Arg {
func abiArg(name string, abiTypeName abi.TypeName) abi.Arg {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was renamed to be consistent with ret below

return abi.Arg{
Name: name,
TypeName: abiTypeName,
}
}

func ret(name string, abiTypeName abi.TypeName) abi.Return {
func abiReturn(name string, abiTypeName abi.TypeName) abi.Return {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was renamed so as to not clash with the local variable ret in the vm package

return abi.Return{
Name: name,
TypeName: abiTypeName,
Expand Down
Loading