Repository: tetratelabs/wazero (go)

Issue:
api.Memory.Size() overflows when the memory has the maximum 65536 pages
**Describe the bug**
Basically, when calling `memory.Grow` function passing a delta that grows the memory to the `MemoryLimitPages` (65536) returns successfully but when we call `memory.Size()` the number of pages mismatches the expected.

I think that occurs because uint32 cannot fit the new buffer len (since the current page is `65536` and the number of bytes per page is `65536` and the multiplication of these values overflows uint32)

**To Reproduce**
I've wrote a test that reproduces the error in this fork https://github.com/ChainSafe/wazero/pull/4

**Expected behavior**
Since the limit is `MemoryLimitPages` we should be able get the correct page size when growing up to that size

**Screenshots**
N/A

**Environment (please complete the relevant information):**
- Go version: 1.21.3
- wazero Version: 6a7e474e022e5d325261cf3073eb246cf4fb4238
- Host architecture: arm64
- Runtime mode:

**Additional context**
Add any other context about the problem here.


Pull request:
fix: workaround Memory.Size() overflow with 4GB memory
Fixes #1852.

Patch:
diff --git a/api/wasm.go b/api/wasm.go
index d6a0d796..ec4519a5 100644
--- a/api/wasm.go
+++ b/api/wasm.go
@@ -559,12 +559,20 @@ type Memory interface {
 	// Definition is metadata about this memory from its defining module.
 	Definition() MemoryDefinition
 
-	// Size returns the size in bytes available. e.g. If the underlying memory
-	// has 1 page: 65536
+	// Size returns the memory size in bytes available.
+	// e.g. If the underlying memory has 1 page: 65536
+	//
+	// # Notes
+	//
+	//   - This overflows (returns zero) if the memory has the maximum 65536 pages.
+	//   - Use PageSize() to handle this corner case.
 	//
 	// See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#-hrefsyntax-instr-memorymathsfmemorysize%E2%91%A0
 	Size() uint32
 
+	// Size returns the memory size in pages available.
+	PageSize() (pages uint32)
+
 	// Grow increases memory by the delta in pages (65536 bytes per page).
 	// The return val is the previous memory size in pages, or false if the
 	// delta was ignored as it exceeds MemoryDefinition.Max.
@@ -572,7 +580,7 @@ type Memory interface {
 	// # Notes
 	//
 	//   - This is the same as the "memory.grow" instruction defined in the
-	//	  WebAssembly Core Specification, except returns false instead of -1.
+	//	   WebAssembly Core Specification, except returns false instead of -1.
 	//   - When this returns true, any shared views via Read must be refreshed.
 	//
 	// See MemorySizer Read and https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#grow-mem
diff --git a/internal/wasm/memory.go b/internal/wasm/memory.go
index 4c9c51ec..6158ae27 100644
--- a/internal/wasm/memory.go
+++ b/internal/wasm/memory.go
@@ -95,12 +95,12 @@ func (m *MemoryInstance) Definition() api.MemoryDefinition {
 
 // Size implements the same method as documented on api.Memory.
 func (m *MemoryInstance) Size() uint32 {
-	return m.size()
+	return uint32(len(m.Buffer))
 }
 
 // ReadByte implements the same method as documented on api.Memory.
 func (m *MemoryInstance) ReadByte(offset uint32) (byte, bool) {
-	if offset >= m.size() {
+	if !m.hasSize(offset, 1) {
 		return 0, false
 	}
 	return m.Buffer[offset], true
@@ -152,7 +152,7 @@ func (m *MemoryInstance) Read(offset, byteCount uint32) ([]byte, bool) {
 
 // WriteByte implements the same method as documented on api.Memory.
 func (m *MemoryInstance) WriteByte(offset uint32, v byte) bool {
-	if offset >= m.size() {
+	if !m.hasSize(offset, 1) {
 		return false
 	}
 	m.Buffer[offset] = v
@@ -213,7 +213,7 @@ func MemoryPagesToBytesNum(pages uint32) (bytesNum uint64) {
 
 // Grow implements the same method as documented on api.Memory.
 func (m *MemoryInstance) Grow(delta uint32) (result uint32, ok bool) {
-	currentPages := memoryBytesNumToPages(uint64(len(m.Buffer)))
+	currentPages := m.PageSize()
 	if delta == 0 {
 		return currentPages, true
 	}
@@ -272,11 +272,6 @@ func memoryBytesNumToPages(bytesNum uint64) (pages uint32) {
 	return uint32(bytesNum >> MemoryPageSizeInBits)
 }
 
-// size returns the size in bytes of the buffer.
-func (m *MemoryInstance) size() uint32 {
-	return uint32(len(m.Buffer)) // We don't lock here because size can't become smaller.
-}
-
 // hasSize returns true if Len is sufficient for byteCount at the given offset.
 //
 // Note: This is always fine, because memory can grow, but never shrink.


Test patch:
diff --git a/experimental/wazerotest/wazerotest.go b/experimental/wazerotest/wazerotest.go
index bfbddf78..4bd8e1ec 100644
--- a/experimental/wazerotest/wazerotest.go
+++ b/experimental/wazerotest/wazerotest.go
@@ -511,8 +511,12 @@ func (m *Memory) Size() uint32 {
 	return uint32(len(m.Bytes))
 }
 
+func (m *Memory) PageSize() uint32 {
+	return uint32(len(m.Bytes) / PageSize)
+}
+
 func (m *Memory) Grow(deltaPages uint32) (previousPages uint32, ok bool) {
-	previousPages = uint32(len(m.Bytes) / PageSize)
+	previousPages = m.PageSize()
 	numPages := previousPages + deltaPages
 	if m.Max != 0 && numPages > m.Max {
 		return previousPages, false