wa-lang.org/wazero@v1.0.2/internal/wasm/memory_test.go (about)

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