github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/wasm/memory_test.go (about)

     1  package wasm
     2  
     3  import (
     4  	"math"
     5  	"reflect"
     6  	"strings"
     7  	"testing"
     8  	"unsafe"
     9  
    10  	"github.com/bananabytelabs/wazero/api"
    11  	"github.com/bananabytelabs/wazero/internal/testing/require"
    12  )
    13  
    14  func TestMemoryPageConsts(t *testing.T) {
    15  	require.Equal(t, MemoryPageSize, uint32(1)<<MemoryPageSizeInBits)
    16  	require.Equal(t, MemoryPageSize, uint32(1<<16))
    17  	require.Equal(t, MemoryLimitPages, uint32(1<<16))
    18  }
    19  
    20  func TestMemoryPagesToBytesNum(t *testing.T) {
    21  	for _, numPage := range []uint32{0, 1, 5, 10} {
    22  		require.Equal(t, uint64(numPage*MemoryPageSize), MemoryPagesToBytesNum(numPage))
    23  	}
    24  }
    25  
    26  func TestMemoryBytesNumToPages(t *testing.T) {
    27  	for _, numbytes := range []uint32{0, MemoryPageSize * 1, MemoryPageSize * 10} {
    28  		require.Equal(t, numbytes/MemoryPageSize, memoryBytesNumToPages(uint64(numbytes)))
    29  	}
    30  }
    31  
    32  func TestMemoryInstance_Grow_Size(t *testing.T) {
    33  	tests := []struct {
    34  		name         string
    35  		capEqualsMax bool
    36  	}{
    37  		{name: ""},
    38  		{name: "capEqualsMax", capEqualsMax: true},
    39  	}
    40  
    41  	for _, tt := range tests {
    42  		tc := tt
    43  
    44  		t.Run(tc.name, func(t *testing.T) {
    45  			max := uint32(10)
    46  			maxBytes := MemoryPagesToBytesNum(max)
    47  			var m *MemoryInstance
    48  			if tc.capEqualsMax {
    49  				m = &MemoryInstance{Cap: max, Max: max, Buffer: make([]byte, 0, maxBytes)}
    50  			} else {
    51  				m = &MemoryInstance{Max: max, Buffer: make([]byte, 0)}
    52  			}
    53  
    54  			res, ok := m.Grow(5)
    55  			require.True(t, ok)
    56  			require.Equal(t, uint32(0), res)
    57  			require.Equal(t, uint32(5), m.PageSize())
    58  
    59  			// Zero page grow is well-defined, should return the current page correctly.
    60  			res, ok = m.Grow(0)
    61  			require.True(t, ok)
    62  			require.Equal(t, uint32(5), res)
    63  			require.Equal(t, uint32(5), m.PageSize())
    64  
    65  			res, ok = m.Grow(4)
    66  			require.True(t, ok)
    67  			require.Equal(t, uint32(5), res)
    68  			require.Equal(t, uint32(9), m.PageSize())
    69  
    70  			// At this point, the page size equal 9,
    71  			// so trying to grow two pages should result in failure.
    72  			_, ok = m.Grow(2)
    73  			require.False(t, ok)
    74  			require.Equal(t, uint32(9), m.PageSize())
    75  
    76  			// But growing one page is still permitted.
    77  			res, ok = m.Grow(1)
    78  			require.True(t, ok)
    79  			require.Equal(t, uint32(9), res)
    80  
    81  			// Ensure that the current page size equals the max.
    82  			require.Equal(t, max, m.PageSize())
    83  
    84  			if tc.capEqualsMax { // Ensure the capacity isn't more than max.
    85  				require.Equal(t, maxBytes, uint64(cap(m.Buffer)))
    86  			} else { // Slice doubles, so it should have a higher capacity than max.
    87  				require.True(t, maxBytes < uint64(cap(m.Buffer)))
    88  			}
    89  		})
    90  	}
    91  }
    92  
    93  func TestMemoryInstance_ReadByte(t *testing.T) {
    94  	mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 0, 0, 0, 16}, Min: 1}
    95  	v, ok := mem.ReadByte(7)
    96  	require.True(t, ok)
    97  	require.Equal(t, byte(16), v)
    98  
    99  	_, ok = mem.ReadByte(8)
   100  	require.False(t, ok)
   101  
   102  	_, ok = mem.ReadByte(9)
   103  	require.False(t, ok)
   104  }
   105  
   106  func TestPagesToUnitOfBytes(t *testing.T) {
   107  	tests := []struct {
   108  		name     string
   109  		pages    uint32
   110  		expected string
   111  	}{
   112  		{
   113  			name:     "zero",
   114  			pages:    0,
   115  			expected: "0 Ki",
   116  		},
   117  		{
   118  			name:     "one",
   119  			pages:    1,
   120  			expected: "64 Ki",
   121  		},
   122  		{
   123  			name:     "megs",
   124  			pages:    100,
   125  			expected: "6 Mi",
   126  		},
   127  		{
   128  			name:     "max memory",
   129  			pages:    MemoryLimitPages,
   130  			expected: "4 Gi",
   131  		},
   132  		{
   133  			name:     "max uint32",
   134  			pages:    math.MaxUint32,
   135  			expected: "3 Ti",
   136  		},
   137  	}
   138  
   139  	for _, tt := range tests {
   140  		tc := tt
   141  
   142  		t.Run(tc.name, func(t *testing.T) {
   143  			require.Equal(t, tc.expected, PagesToUnitOfBytes(tc.pages))
   144  		})
   145  	}
   146  }
   147  
   148  func TestMemoryInstance_HasSize(t *testing.T) {
   149  	memory := &MemoryInstance{Buffer: make([]byte, MemoryPageSize)}
   150  
   151  	tests := []struct {
   152  		name        string
   153  		offset      uint32
   154  		sizeInBytes uint64
   155  		expected    bool
   156  	}{
   157  		{
   158  			name:        "simple valid arguments",
   159  			offset:      0, // arbitrary valid offset
   160  			sizeInBytes: 8, // arbitrary valid size
   161  			expected:    true,
   162  		},
   163  		{
   164  			name:        "maximum valid sizeInBytes",
   165  			offset:      memory.Size() - 8,
   166  			sizeInBytes: 8,
   167  			expected:    true,
   168  		},
   169  		{
   170  			name:        "sizeInBytes exceeds the valid size by 1",
   171  			offset:      100, // arbitrary valid offset
   172  			sizeInBytes: uint64(memory.Size() - 99),
   173  			expected:    false,
   174  		},
   175  		{
   176  			name:        "offset exceeds the memory size",
   177  			offset:      memory.Size(),
   178  			sizeInBytes: 1, // arbitrary size
   179  			expected:    false,
   180  		},
   181  		{
   182  			name:        "offset + sizeInBytes overflows in uint32",
   183  			offset:      math.MaxUint32 - 1, // invalid too large offset
   184  			sizeInBytes: 4,                  // if there's overflow, offset + sizeInBytes is 3, and it may pass the check
   185  			expected:    false,
   186  		},
   187  		{
   188  			name:        "address.wast:200",
   189  			offset:      4294967295,
   190  			sizeInBytes: 1,
   191  			expected:    false,
   192  		},
   193  	}
   194  
   195  	for _, tt := range tests {
   196  		tc := tt
   197  
   198  		t.Run(tc.name, func(t *testing.T) {
   199  			require.Equal(t, tc.expected, memory.hasSize(tc.offset, tc.sizeInBytes))
   200  		})
   201  	}
   202  }
   203  
   204  func TestMemoryInstance_ReadUint16Le(t *testing.T) {
   205  	tests := []struct {
   206  		name       string
   207  		memory     []byte
   208  		offset     uint32
   209  		expected   uint16
   210  		expectedOk bool
   211  	}{
   212  		{
   213  			name:       "valid offset with an endian-insensitive v",
   214  			memory:     []byte{0xff, 0xff},
   215  			offset:     0, // arbitrary valid offset.
   216  			expected:   math.MaxUint16,
   217  			expectedOk: true,
   218  		},
   219  		{
   220  			name:       "valid offset with an endian-sensitive v",
   221  			memory:     []byte{0xfe, 0xff},
   222  			offset:     0, // arbitrary valid offset.
   223  			expected:   math.MaxUint16 - 1,
   224  			expectedOk: true,
   225  		},
   226  		{
   227  			name:       "maximum boundary valid offset",
   228  			offset:     1,
   229  			memory:     []byte{0x00, 0x1, 0x00},
   230  			expected:   1, // arbitrary valid v
   231  			expectedOk: true,
   232  		},
   233  		{
   234  			name:   "offset exceeds the maximum valid offset by 1",
   235  			memory: []byte{0xff, 0xff},
   236  			offset: 1,
   237  		},
   238  	}
   239  
   240  	for _, tt := range tests {
   241  		tc := tt
   242  
   243  		t.Run(tc.name, func(t *testing.T) {
   244  			memory := &MemoryInstance{Buffer: tc.memory}
   245  
   246  			v, ok := memory.ReadUint16Le(tc.offset)
   247  			require.Equal(t, tc.expectedOk, ok)
   248  			require.Equal(t, tc.expected, v)
   249  		})
   250  	}
   251  }
   252  
   253  func TestMemoryInstance_ReadUint32Le(t *testing.T) {
   254  	tests := []struct {
   255  		name       string
   256  		memory     []byte
   257  		offset     uint32
   258  		expected   uint32
   259  		expectedOk bool
   260  	}{
   261  		{
   262  			name:       "valid offset with an endian-insensitive v",
   263  			memory:     []byte{0xff, 0xff, 0xff, 0xff},
   264  			offset:     0, // arbitrary valid offset.
   265  			expected:   math.MaxUint32,
   266  			expectedOk: true,
   267  		},
   268  		{
   269  			name:       "valid offset with an endian-sensitive v",
   270  			memory:     []byte{0xfe, 0xff, 0xff, 0xff},
   271  			offset:     0, // arbitrary valid offset.
   272  			expected:   math.MaxUint32 - 1,
   273  			expectedOk: true,
   274  		},
   275  		{
   276  			name:       "maximum boundary valid offset",
   277  			offset:     1,
   278  			memory:     []byte{0x00, 0x1, 0x00, 0x00, 0x00},
   279  			expected:   1, // arbitrary valid v
   280  			expectedOk: true,
   281  		},
   282  		{
   283  			name:   "offset exceeds the maximum valid offset by 1",
   284  			memory: []byte{0xff, 0xff, 0xff, 0xff},
   285  			offset: 1,
   286  		},
   287  	}
   288  
   289  	for _, tt := range tests {
   290  		tc := tt
   291  
   292  		t.Run(tc.name, func(t *testing.T) {
   293  			memory := &MemoryInstance{Buffer: tc.memory}
   294  
   295  			v, ok := memory.ReadUint32Le(tc.offset)
   296  			require.Equal(t, tc.expectedOk, ok)
   297  			require.Equal(t, tc.expected, v)
   298  		})
   299  	}
   300  }
   301  
   302  func TestMemoryInstance_ReadUint64Le(t *testing.T) {
   303  	tests := []struct {
   304  		name       string
   305  		memory     []byte
   306  		offset     uint32
   307  		expected   uint64
   308  		expectedOk bool
   309  	}{
   310  		{
   311  			name:       "valid offset with an endian-insensitive v",
   312  			memory:     []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
   313  			offset:     0, // arbitrary valid offset.
   314  			expected:   math.MaxUint64,
   315  			expectedOk: true,
   316  		},
   317  		{
   318  			name:       "valid offset with an endian-sensitive v",
   319  			memory:     []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
   320  			offset:     0, // arbitrary valid offset.
   321  			expected:   math.MaxUint64 - 1,
   322  			expectedOk: true,
   323  		},
   324  		{
   325  			name:       "maximum boundary valid offset",
   326  			offset:     1,
   327  			memory:     []byte{0x00, 0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
   328  			expected:   1, // arbitrary valid v
   329  			expectedOk: true,
   330  		},
   331  		{
   332  			name:   "offset exceeds the maximum valid offset by 1",
   333  			memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
   334  			offset: 1,
   335  		},
   336  	}
   337  
   338  	for _, tt := range tests {
   339  		tc := tt
   340  
   341  		t.Run(tc.name, func(t *testing.T) {
   342  			memory := &MemoryInstance{Buffer: tc.memory}
   343  
   344  			v, ok := memory.ReadUint64Le(tc.offset)
   345  			require.Equal(t, tc.expectedOk, ok)
   346  			require.Equal(t, tc.expected, v)
   347  		})
   348  	}
   349  }
   350  
   351  func TestMemoryInstance_ReadFloat32Le(t *testing.T) {
   352  	tests := []struct {
   353  		name       string
   354  		memory     []byte
   355  		offset     uint32
   356  		expected   float32
   357  		expectedOk bool
   358  	}{
   359  		{
   360  			name:       "valid offset with an endian-insensitive v",
   361  			memory:     []byte{0xff, 0x00, 0x00, 0xff},
   362  			offset:     0, // arbitrary valid offset.
   363  			expected:   math.Float32frombits(uint32(0xff0000ff)),
   364  			expectedOk: true,
   365  		},
   366  		{
   367  			name:       "valid offset with an endian-sensitive v",
   368  			memory:     []byte{0xfe, 0x00, 0x00, 0xff},
   369  			offset:     0, // arbitrary valid offset.
   370  			expected:   math.Float32frombits(uint32(0xff0000fe)),
   371  			expectedOk: true,
   372  		},
   373  		{
   374  			name:       "maximum boundary valid offset",
   375  			offset:     1,
   376  			memory:     []byte{0x00, 0xcd, 0xcc, 0xcc, 0x3d},
   377  			expected:   0.1, // arbitrary valid v
   378  			expectedOk: true,
   379  		},
   380  		{
   381  			name:   "offset exceeds the maximum valid offset by 1",
   382  			memory: []byte{0xff, 0xff, 0xff, 0xff},
   383  			offset: 1,
   384  		},
   385  	}
   386  
   387  	for _, tt := range tests {
   388  		tc := tt
   389  
   390  		t.Run(tc.name, func(t *testing.T) {
   391  			memory := &MemoryInstance{Buffer: tc.memory}
   392  
   393  			v, ok := memory.ReadFloat32Le(tc.offset)
   394  			require.Equal(t, tc.expectedOk, ok)
   395  			require.Equal(t, tc.expected, v)
   396  		})
   397  	}
   398  }
   399  
   400  func TestMemoryInstance_ReadFloat64Le(t *testing.T) {
   401  	tests := []struct {
   402  		name       string
   403  		memory     []byte
   404  		offset     uint32
   405  		expected   float64
   406  		expectedOk bool
   407  	}{
   408  		{
   409  			name:       "valid offset with an endian-insensitive v",
   410  			memory:     []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff},
   411  			offset:     0, // arbitrary valid offset.
   412  			expected:   math.Float64frombits(uint64(0xff000000000000ff)),
   413  			expectedOk: true,
   414  		},
   415  		{
   416  			name:       "valid offset with an endian-sensitive v",
   417  			memory:     []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
   418  			offset:     0,               // arbitrary valid offset.
   419  			expected:   math.MaxFloat64, // arbitrary valid v
   420  			expectedOk: true,
   421  		},
   422  		{
   423  			name:       "maximum boundary valid offset",
   424  			offset:     1,
   425  			memory:     []byte{0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
   426  			expected:   math.MaxFloat64, // arbitrary valid v
   427  			expectedOk: true,
   428  		},
   429  		{
   430  			name:   "offset exceeds the maximum valid offset by 1",
   431  			memory: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
   432  			offset: 1,
   433  		},
   434  	}
   435  
   436  	for _, tt := range tests {
   437  		tc := tt
   438  
   439  		t.Run(tc.name, func(t *testing.T) {
   440  			memory := &MemoryInstance{Buffer: tc.memory}
   441  
   442  			v, ok := memory.ReadFloat64Le(tc.offset)
   443  			require.Equal(t, tc.expectedOk, ok)
   444  			require.Equal(t, tc.expected, v)
   445  		})
   446  	}
   447  }
   448  
   449  func TestMemoryInstance_Read(t *testing.T) {
   450  	mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1}
   451  
   452  	buf, ok := mem.Read(4, 4)
   453  	require.True(t, ok)
   454  	require.Equal(t, []byte{16, 0, 0, 0}, buf)
   455  
   456  	// Test write-through
   457  	buf[3] = 4
   458  	require.Equal(t, []byte{16, 0, 0, 4}, buf)
   459  	require.Equal(t, []byte{0, 0, 0, 0, 16, 0, 0, 4}, mem.Buffer)
   460  
   461  	_, ok = mem.Read(5, 4)
   462  	require.False(t, ok)
   463  
   464  	_, ok = mem.Read(9, 4)
   465  	require.False(t, ok)
   466  }
   467  
   468  func TestMemoryInstance_WriteUint16Le(t *testing.T) {
   469  	memory := &MemoryInstance{Buffer: make([]byte, 100)}
   470  
   471  	tests := []struct {
   472  		name          string
   473  		offset        uint32
   474  		v             uint16
   475  		expectedOk    bool
   476  		expectedBytes []byte
   477  	}{
   478  		{
   479  			name:          "valid offset with an endian-insensitive v",
   480  			offset:        0, // arbitrary valid offset.
   481  			v:             math.MaxUint16,
   482  			expectedOk:    true,
   483  			expectedBytes: []byte{0xff, 0xff},
   484  		},
   485  		{
   486  			name:          "valid offset with an endian-sensitive v",
   487  			offset:        0, // arbitrary valid offset.
   488  			v:             math.MaxUint16 - 1,
   489  			expectedOk:    true,
   490  			expectedBytes: []byte{0xfe, 0xff},
   491  		},
   492  		{
   493  			name:          "maximum boundary valid offset",
   494  			offset:        memory.Size() - 2, // 2 is the size of uint16
   495  			v:             1,                 // arbitrary valid v
   496  			expectedOk:    true,
   497  			expectedBytes: []byte{0x1, 0x00},
   498  		},
   499  		{
   500  			name:          "offset exceeds the maximum valid offset by 1",
   501  			offset:        memory.Size() - 2 + 1, // 2 is the size of uint16
   502  			v:             1,                     // arbitrary valid v
   503  			expectedBytes: []byte{0xff, 0xff},
   504  		},
   505  	}
   506  
   507  	for _, tt := range tests {
   508  		tc := tt
   509  
   510  		t.Run(tc.name, func(t *testing.T) {
   511  			require.Equal(t, tc.expectedOk, memory.WriteUint16Le(tc.offset, tc.v))
   512  			if tc.expectedOk {
   513  				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+2]) // 2 is the size of uint16
   514  			}
   515  		})
   516  	}
   517  }
   518  
   519  func TestMemoryInstance_WriteUint32Le(t *testing.T) {
   520  	memory := &MemoryInstance{Buffer: make([]byte, 100)}
   521  
   522  	tests := []struct {
   523  		name          string
   524  		offset        uint32
   525  		v             uint32
   526  		expectedOk    bool
   527  		expectedBytes []byte
   528  	}{
   529  		{
   530  			name:          "valid offset with an endian-insensitive v",
   531  			offset:        0, // arbitrary valid offset.
   532  			v:             math.MaxUint32,
   533  			expectedOk:    true,
   534  			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
   535  		},
   536  		{
   537  			name:          "valid offset with an endian-sensitive v",
   538  			offset:        0, // arbitrary valid offset.
   539  			v:             math.MaxUint32 - 1,
   540  			expectedOk:    true,
   541  			expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff},
   542  		},
   543  		{
   544  			name:          "maximum boundary valid offset",
   545  			offset:        memory.Size() - 4, // 4 is the size of uint32
   546  			v:             1,                 // arbitrary valid v
   547  			expectedOk:    true,
   548  			expectedBytes: []byte{0x1, 0x00, 0x00, 0x00},
   549  		},
   550  		{
   551  			name:          "offset exceeds the maximum valid offset by 1",
   552  			offset:        memory.Size() - 4 + 1, // 4 is the size of uint32
   553  			v:             1,                     // arbitrary valid v
   554  			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
   555  		},
   556  	}
   557  
   558  	for _, tt := range tests {
   559  		tc := tt
   560  
   561  		t.Run(tc.name, func(t *testing.T) {
   562  			require.Equal(t, tc.expectedOk, memory.WriteUint32Le(tc.offset, tc.v))
   563  			if tc.expectedOk {
   564  				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of uint32
   565  			}
   566  		})
   567  	}
   568  }
   569  
   570  func TestMemoryInstance_WriteUint64Le(t *testing.T) {
   571  	memory := &MemoryInstance{Buffer: make([]byte, 100)}
   572  	tests := []struct {
   573  		name          string
   574  		offset        uint32
   575  		v             uint64
   576  		expectedOk    bool
   577  		expectedBytes []byte
   578  	}{
   579  		{
   580  			name:          "valid offset with an endian-insensitive v",
   581  			offset:        0, // arbitrary valid offset.
   582  			v:             math.MaxUint64,
   583  			expectedOk:    true,
   584  			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
   585  		},
   586  		{
   587  			name:          "valid offset with an endian-sensitive v",
   588  			offset:        0, // arbitrary valid offset.
   589  			v:             math.MaxUint64 - 1,
   590  			expectedOk:    true,
   591  			expectedBytes: []byte{0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
   592  		},
   593  		{
   594  			name:          "maximum boundary valid offset",
   595  			offset:        memory.Size() - 8, // 8 is the size of uint64
   596  			v:             1,                 // arbitrary valid v
   597  			expectedOk:    true,
   598  			expectedBytes: []byte{0x1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
   599  		},
   600  		{
   601  			name:       "offset exceeds the maximum valid offset by 1",
   602  			offset:     memory.Size() - 8 + 1, // 8 is the size of uint64
   603  			v:          1,                     // arbitrary valid v
   604  			expectedOk: false,
   605  		},
   606  	}
   607  
   608  	for _, tt := range tests {
   609  		tc := tt
   610  
   611  		t.Run(tc.name, func(t *testing.T) {
   612  			require.Equal(t, tc.expectedOk, memory.WriteUint64Le(tc.offset, tc.v))
   613  			if tc.expectedOk {
   614  				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of uint64
   615  			}
   616  		})
   617  	}
   618  }
   619  
   620  func TestMemoryInstance_WriteFloat32Le(t *testing.T) {
   621  	memory := &MemoryInstance{Buffer: make([]byte, 100)}
   622  
   623  	tests := []struct {
   624  		name          string
   625  		offset        uint32
   626  		v             float32
   627  		expectedOk    bool
   628  		expectedBytes []byte
   629  	}{
   630  		{
   631  			name:          "valid offset with an endian-insensitive v",
   632  			offset:        0, // arbitrary valid offset.
   633  			v:             math.Float32frombits(uint32(0xff0000ff)),
   634  			expectedOk:    true,
   635  			expectedBytes: []byte{0xff, 0x00, 0x00, 0xff},
   636  		},
   637  		{
   638  			name:          "valid offset with an endian-sensitive v",
   639  			offset:        0,                                        // arbitrary valid offset.
   640  			v:             math.Float32frombits(uint32(0xff0000fe)), // arbitrary valid v
   641  			expectedOk:    true,
   642  			expectedBytes: []byte{0xfe, 0x00, 0x00, 0xff},
   643  		},
   644  		{
   645  			name:          "maximum boundary valid offset",
   646  			offset:        memory.Size() - 4, // 4 is the size of float32
   647  			v:             0.1,               // arbitrary valid v
   648  			expectedOk:    true,
   649  			expectedBytes: []byte{0xcd, 0xcc, 0xcc, 0x3d},
   650  		},
   651  		{
   652  			name:          "offset exceeds the maximum valid offset by 1",
   653  			offset:        memory.Size() - 4 + 1, // 4 is the size of float32
   654  			v:             math.MaxFloat32,       // arbitrary valid v
   655  			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff},
   656  		},
   657  	}
   658  
   659  	for _, tt := range tests {
   660  		tc := tt
   661  
   662  		t.Run(tc.name, func(t *testing.T) {
   663  			require.Equal(t, tc.expectedOk, memory.WriteFloat32Le(tc.offset, tc.v))
   664  			if tc.expectedOk {
   665  				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+4]) // 4 is the size of float32
   666  			}
   667  		})
   668  	}
   669  }
   670  
   671  func TestMemoryInstance_WriteFloat64Le(t *testing.T) {
   672  	memory := &MemoryInstance{Buffer: make([]byte, 100)}
   673  	tests := []struct {
   674  		name          string
   675  		offset        uint32
   676  		v             float64
   677  		expectedOk    bool
   678  		expectedBytes []byte
   679  	}{
   680  		{
   681  			name:          "valid offset with an endian-insensitive v",
   682  			offset:        0, // arbitrary valid offset.
   683  			v:             math.Float64frombits(uint64(0xff000000000000ff)),
   684  			expectedOk:    true,
   685  			expectedBytes: []byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff},
   686  		},
   687  		{
   688  			name:          "valid offset with an endian-sensitive v",
   689  			offset:        0,               // arbitrary valid offset.
   690  			v:             math.MaxFloat64, // arbitrary valid v
   691  			expectedOk:    true,
   692  			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
   693  		},
   694  		{
   695  			name:          "maximum boundary valid offset",
   696  			offset:        memory.Size() - 8, // 8 is the size of float64
   697  			v:             math.MaxFloat64,   // arbitrary valid v
   698  			expectedOk:    true,
   699  			expectedBytes: []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0x7f},
   700  		},
   701  		{
   702  			name:       "offset exceeds the maximum valid offset by 1",
   703  			offset:     memory.Size() - 8 + 1, // 8 is the size of float64
   704  			v:          math.MaxFloat64,       // arbitrary valid v
   705  			expectedOk: false,
   706  		},
   707  	}
   708  
   709  	for _, tt := range tests {
   710  		tc := tt
   711  
   712  		t.Run(tc.name, func(t *testing.T) {
   713  			require.Equal(t, tc.expectedOk, memory.WriteFloat64Le(tc.offset, tc.v))
   714  			if tc.expectedOk {
   715  				require.Equal(t, tc.expectedBytes, memory.Buffer[tc.offset:tc.offset+8]) // 8 is the size of float64
   716  			}
   717  		})
   718  	}
   719  }
   720  
   721  func TestMemoryInstance_Write(t *testing.T) {
   722  	mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1}
   723  
   724  	buf := []byte{16, 0, 0, 4}
   725  	require.True(t, mem.Write(4, buf))
   726  	require.Equal(t, []byte{0, 0, 0, 0, 16, 0, 0, 4}, mem.Buffer)
   727  
   728  	// Test it isn't write-through
   729  	buf[3] = 0
   730  	require.Equal(t, []byte{16, 0, 0, 0}, buf)
   731  	require.Equal(t, []byte{0, 0, 0, 0, 16, 0, 0, 4}, mem.Buffer)
   732  
   733  	ok := mem.Write(5, buf)
   734  	require.False(t, ok)
   735  
   736  	ok = mem.Write(9, buf)
   737  	require.False(t, ok)
   738  }
   739  
   740  func TestMemoryInstance_Write_overflow(t *testing.T) {
   741  	mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1}
   742  
   743  	// Test overflow
   744  	huge := uint64(math.MaxUint32 + 1 + 4)
   745  	if huge != uint64(int(huge)) {
   746  		t.Skip("Skipping on 32-bit")
   747  	}
   748  
   749  	buf := []byte{16, 0, 0, 4}
   750  	header := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
   751  	header.Len = int(huge)
   752  	header.Cap = int(huge)
   753  
   754  	require.False(t, mem.Write(4, buf))
   755  }
   756  
   757  func TestMemoryInstance_WriteString(t *testing.T) {
   758  	mem := &MemoryInstance{Buffer: []byte{0, 0, 0, 0, 16, 0, 0, 0}, Min: 1}
   759  
   760  	s := "bear"
   761  	require.True(t, mem.WriteString(4, s))
   762  	require.Equal(t, []byte{0, 0, 0, 0, 'b', 'e', 'a', 'r'}, mem.Buffer)
   763  
   764  	ok := mem.WriteString(5, s)
   765  	require.False(t, ok)
   766  
   767  	ok = mem.WriteString(9, s)
   768  	require.False(t, ok)
   769  }
   770  
   771  func BenchmarkWriteString(b *testing.B) {
   772  	tests := []string{
   773  		"",
   774  		"bear",
   775  		"hello world",
   776  		strings.Repeat("hello ", 10),
   777  	}
   778  	//nolint intentionally testing interface access
   779  	var mem api.Memory
   780  	mem = &MemoryInstance{Buffer: make([]byte, 1000), Min: 1}
   781  	for _, tt := range tests {
   782  		b.Run("", func(b *testing.B) {
   783  			b.Run("Write", func(b *testing.B) {
   784  				for i := 0; i < b.N; i++ {
   785  					if !mem.Write(0, []byte(tt)) {
   786  						b.Fail()
   787  					}
   788  				}
   789  			})
   790  			b.Run("WriteString", func(b *testing.B) {
   791  				for i := 0; i < b.N; i++ {
   792  					if !mem.WriteString(0, tt) {
   793  						b.Fail()
   794  					}
   795  				}
   796  			})
   797  		})
   798  	}
   799  }