github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/pkg/executor/wasm/validator.go (about)

     1  package wasm
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/filecoin-project/bacalhau/pkg/model"
     7  	"github.com/tetratelabs/wazero"
     8  	"github.com/tetratelabs/wazero/api"
     9  	"golang.org/x/exp/maps"
    10  )
    11  
    12  // ValidateModuleAgainstJob will return an error if the passed job does not
    13  // represent a valid WASM executor job or the passed module is not able to be
    14  // run to fulfill the job.
    15  func ValidateModuleAgainstJob(
    16  	module wazero.CompiledModule,
    17  	job model.Spec,
    18  	importModules ...wazero.CompiledModule,
    19  ) error {
    20  	err := ValidateModuleImports(module, importModules...)
    21  	if err != nil {
    22  		return err
    23  	}
    24  
    25  	return ValidateModuleAsEntryPoint(module, job.Wasm.EntryPoint)
    26  }
    27  
    28  // ValidateModuleImports will return an error if the passed module requires
    29  // imports that are not found in any of the passed importModules. Imports have
    30  // to match exactly, i.e. function names and signatures must be an exact match.
    31  func ValidateModuleImports(
    32  	module wazero.CompiledModule,
    33  	importModules ...wazero.CompiledModule,
    34  ) error {
    35  	availableImports := make(map[string]api.FunctionDefinition)
    36  	for _, importModule := range importModules {
    37  		maps.Copy(importModule.ExportedFunctions(), availableImports)
    38  	}
    39  
    40  	for _, requiredImport := range module.ImportedFunctions() {
    41  		importNamespace, funcName, _ := requiredImport.Import()
    42  		exists := false
    43  		for _, importModule := range importModules {
    44  			_, exists = importModule.ExportedFunctions()[funcName]
    45  			if exists {
    46  				err := ValidateModuleHasFunction(
    47  					importModule,
    48  					funcName,
    49  					requiredImport.ParamTypes(),
    50  					requiredImport.ResultTypes(),
    51  				)
    52  
    53  				// If the module has the import but the signature doesn't match,
    54  				// as we enforce that imports are unique, this will break even
    55  				// if there is another import with correct name and signature.
    56  				if err != nil {
    57  					return err
    58  				}
    59  			}
    60  		}
    61  
    62  		if !exists {
    63  			// We didn't find an export from any module.
    64  			return fmt.Errorf("no export found for '%s::%s' required by module", importNamespace, funcName)
    65  		}
    66  	}
    67  
    68  	return nil
    69  }
    70  
    71  // ValidateModuleAsEntryPoint returns an error if the passed module is not
    72  // capable of being an entry point to a job, i.e. that it contains a function of
    73  // the passed name that meets the specification of:
    74  //
    75  // - the named function exists and is exported
    76  // - the function takes no parameters
    77  // - the function returns one i32 value (exit code)
    78  func ValidateModuleAsEntryPoint(
    79  	module wazero.CompiledModule,
    80  	name string,
    81  ) error {
    82  	return ValidateModuleHasFunction(
    83  		module,
    84  		name,
    85  		[]api.ValueType{},
    86  		[]api.ValueType{},
    87  	)
    88  }
    89  
    90  // ValidateModuleHasFunction returns an error if the passed module does not
    91  // contain an exported function with the passed name, parameters and return
    92  // values.
    93  func ValidateModuleHasFunction(
    94  	module wazero.CompiledModule,
    95  	name string,
    96  	parameters []api.ValueType,
    97  	results []api.ValueType,
    98  ) error {
    99  	function, ok := module.ExportedFunctions()[name]
   100  	if !ok {
   101  		return fmt.Errorf("function '%s' required but no WASM export with that name was found", name)
   102  	}
   103  
   104  	if len(function.ParamTypes()) != len(parameters) {
   105  		return fmt.Errorf("function '%s' should take %d parameters", name, len(parameters))
   106  	}
   107  	for i := range parameters {
   108  		expectedType := parameters[i]
   109  		actualType := function.ParamTypes()[i]
   110  		if expectedType != actualType {
   111  			return fmt.Errorf("function '%s': expected param %d to have type %v", name, i, expectedType)
   112  		}
   113  	}
   114  
   115  	if len(function.ResultTypes()) != len(results) {
   116  		return fmt.Errorf("function '%s' should return %d results", name, len(results))
   117  	}
   118  	for i := range results {
   119  		expectedType := results[i]
   120  		actualType := function.ResultTypes()[i]
   121  		if expectedType != actualType {
   122  			return fmt.Errorf("function '%s': expected result %d to have type %v", name, i, expectedType)
   123  		}
   124  	}
   125  
   126  	return nil
   127  }