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

Commit

Permalink
Implement dynamic memory and fix out of bounds issues
Browse files Browse the repository at this point in the history
  • Loading branch information
Silas Davis committed May 9, 2017
1 parent 8f4e266 commit ad9d537
Show file tree
Hide file tree
Showing 11 changed files with 425 additions and 101 deletions.
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
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
mem.slice = mem.slice[:newCapacity]
return nil
}
112 changes: 112 additions & 0 deletions manager/burrow-mint/evm/memory_test.go
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")


}
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 {
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 {
return abi.Return{
Name: name,
TypeName: abiTypeName,
Expand Down
Loading

0 comments on commit ad9d537

Please sign in to comment.