github.com/taubyte/vm-wasm-utils@v1.0.2/binary/names.go (about)

     1  package binary
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  
     8  	wasm "github.com/taubyte/vm-wasm-utils"
     9  	"github.com/taubyte/vm-wasm-utils/leb128"
    10  )
    11  
    12  const (
    13  	// subsectionIDModuleName contains only the module name.
    14  	subsectionIDModuleName = uint8(0)
    15  	// subsectionIDFunctionNames is a map of indices to function names, in ascending order by function index
    16  	subsectionIDFunctionNames = uint8(1)
    17  	// subsectionIDLocalNames contain a map of function indices to a map of local indices to their names, in ascending
    18  	// order by function and local index
    19  	subsectionIDLocalNames = uint8(2)
    20  )
    21  
    22  // decodeNameSection deserializes the data associated with the "name" key in SectionIDCustom according to the
    23  // standard:
    24  //
    25  // * ModuleName decode from subsection 0
    26  // * FunctionNames decode from subsection 1
    27  // * LocalNames decode from subsection 2
    28  //
    29  // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec
    30  func decodeNameSection(r *bytes.Reader, limit uint64) (result *wasm.NameSection, err error) {
    31  	// TODO: add leb128 functions that work on []byte and offset. While using a reader allows us to reuse reader-based
    32  	// leb128 functions, it is less efficient, causes untestable code and in some cases more complex vs plain []byte.
    33  	result = &wasm.NameSection{}
    34  
    35  	// subsectionID is decoded if known, and skipped if not
    36  	var subsectionID uint8
    37  	// subsectionSize is the length to skip when the subsectionID is unknown
    38  	var subsectionSize uint32
    39  	var bytesRead uint64
    40  	for limit > 0 {
    41  		if subsectionID, err = r.ReadByte(); err != nil {
    42  			if err == io.EOF {
    43  				return result, nil
    44  			}
    45  			// TODO: untestable as this can't fail for a reason beside EOF reading a byte from a buffer
    46  			return nil, fmt.Errorf("failed to read a subsection ID: %w", err)
    47  		}
    48  		limit--
    49  
    50  		if subsectionSize, bytesRead, err = leb128.DecodeUint32(r); err != nil {
    51  			return nil, fmt.Errorf("failed to read the size of subsection[%d]: %w", subsectionID, err)
    52  		}
    53  		limit -= bytesRead
    54  
    55  		switch subsectionID {
    56  		case subsectionIDModuleName:
    57  			if result.ModuleName, _, err = decodeUTF8(r, "module name"); err != nil {
    58  				return nil, err
    59  			}
    60  		case subsectionIDFunctionNames:
    61  			if result.FunctionNames, err = decodeFunctionNames(r); err != nil {
    62  				return nil, err
    63  			}
    64  		case subsectionIDLocalNames:
    65  			if result.LocalNames, err = decodeLocalNames(r); err != nil {
    66  				return nil, err
    67  			}
    68  		default: // Skip other subsections.
    69  			// Note: Not Seek because it doesn't err when given an offset past EOF. Rather, it leads to undefined state.
    70  			if _, err = io.CopyN(io.Discard, r, int64(subsectionSize)); err != nil {
    71  				return nil, fmt.Errorf("failed to skip subsection[%d]: %w", subsectionID, err)
    72  			}
    73  		}
    74  		limit -= uint64(subsectionSize)
    75  	}
    76  	return
    77  }
    78  
    79  func decodeFunctionNames(r *bytes.Reader) (wasm.NameMap, error) {
    80  	functionCount, err := decodeFunctionCount(r, subsectionIDFunctionNames)
    81  	if err != nil {
    82  		return nil, err
    83  	}
    84  
    85  	result := make(wasm.NameMap, functionCount)
    86  	for i := uint32(0); i < functionCount; i++ {
    87  		functionIndex, err := decodeFunctionIndex(r, subsectionIDFunctionNames)
    88  		if err != nil {
    89  			return nil, err
    90  		}
    91  
    92  		name, _, err := decodeUTF8(r, "function[%d] name", functionIndex)
    93  		if err != nil {
    94  			return nil, err
    95  		}
    96  		result[i] = &wasm.NameAssoc{Index: functionIndex, Name: name}
    97  	}
    98  	return result, nil
    99  }
   100  
   101  func decodeLocalNames(r *bytes.Reader) (wasm.IndirectNameMap, error) {
   102  	functionCount, err := decodeFunctionCount(r, subsectionIDLocalNames)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	result := make(wasm.IndirectNameMap, functionCount)
   108  	for i := uint32(0); i < functionCount; i++ {
   109  		functionIndex, err := decodeFunctionIndex(r, subsectionIDLocalNames)
   110  		if err != nil {
   111  			return nil, err
   112  		}
   113  
   114  		localCount, _, err := leb128.DecodeUint32(r)
   115  		if err != nil {
   116  			return nil, fmt.Errorf("failed to read the local count for function[%d]: %w", functionIndex, err)
   117  		}
   118  
   119  		locals := make(wasm.NameMap, localCount)
   120  		for j := uint32(0); j < localCount; j++ {
   121  			localIndex, _, err := leb128.DecodeUint32(r)
   122  			if err != nil {
   123  				return nil, fmt.Errorf("failed to read a local index of function[%d]: %w", functionIndex, err)
   124  			}
   125  
   126  			name, _, err := decodeUTF8(r, "function[%d] local[%d] name", functionIndex, localIndex)
   127  			if err != nil {
   128  				return nil, err
   129  			}
   130  			locals[j] = &wasm.NameAssoc{Index: localIndex, Name: name}
   131  		}
   132  		result[i] = &wasm.NameMapAssoc{Index: functionIndex, NameMap: locals}
   133  	}
   134  	return result, nil
   135  }
   136  
   137  func decodeFunctionIndex(r *bytes.Reader, subsectionID uint8) (uint32, error) {
   138  	functionIndex, _, err := leb128.DecodeUint32(r)
   139  	if err != nil {
   140  		return 0, fmt.Errorf("failed to read a function index in subsection[%d]: %w", subsectionID, err)
   141  	}
   142  	return functionIndex, nil
   143  }
   144  
   145  func decodeFunctionCount(r *bytes.Reader, subsectionID uint8) (uint32, error) {
   146  	functionCount, _, err := leb128.DecodeUint32(r)
   147  	if err != nil {
   148  		return 0, fmt.Errorf("failed to read the function count of subsection[%d]: %w", subsectionID, err)
   149  	}
   150  	return functionCount, nil
   151  }
   152  
   153  // encodeNameSectionData serializes the data for the "name" key in wasm.SectionIDCustom according to the
   154  // standard:
   155  //
   156  // Note: The result can be nil because this does not encode empty subsections
   157  //
   158  // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namesec
   159  func encodeNameSectionData(n *wasm.NameSection) (data []byte) {
   160  	if n.ModuleName != "" {
   161  		data = append(data, encodeNameSubsection(subsectionIDModuleName, encodeSizePrefixed([]byte(n.ModuleName)))...)
   162  	}
   163  	if fd := encodeFunctionNameData(n); len(fd) > 0 {
   164  		data = append(data, encodeNameSubsection(subsectionIDFunctionNames, fd)...)
   165  	}
   166  	if ld := encodeLocalNameData(n); len(ld) > 0 {
   167  		data = append(data, encodeNameSubsection(subsectionIDLocalNames, ld)...)
   168  	}
   169  	return
   170  }
   171  
   172  // encodeFunctionNameData encodes the data for the function name subsection.
   173  // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-funcnamesec
   174  func encodeFunctionNameData(n *wasm.NameSection) []byte {
   175  	if len(n.FunctionNames) == 0 {
   176  		return nil
   177  	}
   178  
   179  	return encodeNameMap(n.FunctionNames)
   180  }
   181  
   182  func encodeNameMap(m wasm.NameMap) []byte {
   183  	count := uint32(len(m))
   184  	data := leb128.EncodeUint32(count)
   185  	for _, na := range m {
   186  		data = append(data, encodeNameAssoc(na)...)
   187  	}
   188  	return data
   189  }
   190  
   191  // encodeLocalNameData encodes the data for the local name subsection.
   192  // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-localnamesec
   193  func encodeLocalNameData(n *wasm.NameSection) []byte {
   194  	if len(n.LocalNames) == 0 {
   195  		return nil
   196  	}
   197  
   198  	funcNameCount := uint32(len(n.LocalNames))
   199  	subsection := leb128.EncodeUint32(funcNameCount)
   200  
   201  	for _, na := range n.LocalNames {
   202  		locals := encodeNameMap(na.NameMap)
   203  		subsection = append(subsection, append(leb128.EncodeUint32(na.Index), locals...)...)
   204  	}
   205  	return subsection
   206  }
   207  
   208  // encodeNameSubsection returns a buffer encoding the given subsection
   209  // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#subsections%E2%91%A0
   210  func encodeNameSubsection(subsectionID uint8, content []byte) []byte {
   211  	contentSizeInBytes := leb128.EncodeUint32(uint32(len(content)))
   212  	result := []byte{subsectionID}
   213  	result = append(result, contentSizeInBytes...)
   214  	result = append(result, content...)
   215  	return result
   216  }
   217  
   218  // encodeNameAssoc encodes the index and data prefixed by their size.
   219  // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#binary-namemap
   220  func encodeNameAssoc(na *wasm.NameAssoc) []byte {
   221  	return append(leb128.EncodeUint32(na.Index), encodeSizePrefixed([]byte(na.Name))...)
   222  }
   223  
   224  // encodeSizePrefixed encodes the data prefixed by their size.
   225  func encodeSizePrefixed(data []byte) []byte {
   226  	size := leb128.EncodeUint32(uint32(len(data)))
   227  	return append(size, data...)
   228  }