github.com/zeebo/goof@v0.0.0-20230907150950-e9457bc94477/troop_call.go (about)

     1  package goof
     2  
     3  import (
     4  	"debug/dwarf"
     5  	"reflect"
     6  	"sort"
     7  	"unsafe"
     8  
     9  	"github.com/zeebo/errs"
    10  )
    11  
    12  func (t *Troop) addFunctions() error {
    13  	reader := t.data.Reader()
    14  
    15  	for {
    16  		entry, err := reader.Next()
    17  		if err != nil {
    18  			return errs.Wrap(err)
    19  		}
    20  		if entry == nil {
    21  			break
    22  		}
    23  
    24  		if entry.Tag != dwarf.TagSubprogram {
    25  			continue
    26  		}
    27  
    28  		name, ok := entry.Val(dwarf.AttrName).(string)
    29  		if !ok {
    30  			continue
    31  		}
    32  
    33  		pc, ok := entry.Val(dwarf.AttrLowpc).(uint64)
    34  		if !ok {
    35  			continue
    36  		}
    37  
    38  		dtypes, err := getFunctionArgTypes(t.data, entry)
    39  		if err != nil {
    40  			continue
    41  		}
    42  
    43  		_, err = t.findDwarfTypes(dtypes)
    44  		if err != nil {
    45  			continue
    46  		}
    47  
    48  		t.functions[name] = functionCacheEntry{
    49  			pc:     pc,
    50  			dtypes: dtypes,
    51  		}
    52  	}
    53  
    54  	return nil
    55  }
    56  
    57  func (t *Troop) Functions() ([]string, error) {
    58  	if err := t.check(); err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	out := make([]string, 0, len(t.functions))
    63  	for name := range t.functions {
    64  		out = append(out, name)
    65  	}
    66  	sort.Strings(out)
    67  	return out, nil
    68  }
    69  
    70  func (t *Troop) Call(name string, args ...interface{}) ([]interface{}, error) {
    71  	if err := t.check(); err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	// and so it begins. find the function, the pc, and the types of the args
    76  	// and return values. we don't know which is which, but we assume the
    77  	// caller passed the appropriate things.
    78  	entry, ok := t.functions[name]
    79  	if !ok {
    80  		return nil, errs.New("call %s: unknown or uncallable function", name)
    81  	}
    82  	pc, dtypes := entry.pc, entry.dtypes
    83  
    84  	// make sure they didn't pass more arguments than total types
    85  	num_results := len(dtypes) - len(args)
    86  	if num_results < 0 {
    87  		return nil, errs.New("call %s: too many args", name)
    88  	}
    89  
    90  	// build the arguments, checking consistency and doing hacks to make it
    91  	// nice to use.
    92  	in, in_types, err := t.buildArguments(args, dtypes[:len(args)])
    93  	if err != nil {
    94  		return nil, errs.New("call %s: %v", name, err)
    95  	}
    96  
    97  	// the rest must be the return values, RIGHT? LOL
    98  	out_types, err := t.findDwarfTypes(dtypes[len(args):])
    99  	if err != nil {
   100  		return nil, errs.New("call %s: %v", name, err)
   101  	}
   102  
   103  	// make it happen
   104  	fn_typ := reflect.FuncOf(in_types, out_types, false)
   105  	fn := reflect.ValueOf(makeInterface(dataPtr(fn_typ), unsafe.Pointer(&pc)))
   106  	return ifaces(fn.Call(in)), nil
   107  }
   108  
   109  func (t *Troop) buildArguments(args []interface{}, dtypes []dwarf.Type) (
   110  	[]reflect.Value, []reflect.Type, error) {
   111  
   112  	if len(args) != len(dtypes) {
   113  		return nil, nil, errs.New("number of arguments does not match")
   114  	}
   115  
   116  	// so I want the Call signatrue to have args passed as ...interface{}
   117  	// because that makes the api nice: taking a reflect.Value is hard for the
   118  	// consumer.
   119  	//
   120  	// Unfortunately, that means that if you pass an interface type in, they
   121  	// get down-casted to just interface{}. Doubly unfortunately, the itab
   122  	// pointer was lost when the value was converted to an interface{} instead
   123  	// of whatever interface it was in the first place.
   124  	//
   125  	// So let's just look and see if we can find the interface type in the
   126  	// types map based on the dwarf type. If not, shoot. Hopefully that's
   127  	// rare! Maybe we can expose a CallReflect api that can get around this.
   128  	//
   129  	// Heh.
   130  
   131  	in_types := make([]reflect.Type, 0, len(args))
   132  	in := make([]reflect.Value, 0, len(args))
   133  
   134  	for i, arg := range args {
   135  		dtyp := dtypes[i]
   136  
   137  		val, err := t.constructValue(arg, dtyp)
   138  		if err != nil {
   139  			return nil, nil, errs.New("arg %d: %v", i, err)
   140  		}
   141  
   142  		in_types = append(in_types, val.Type())
   143  		in = append(in, val)
   144  	}
   145  
   146  	return in, in_types, nil
   147  }
   148  
   149  func (t *Troop) constructValue(arg interface{}, dtyp dwarf.Type) (
   150  	val reflect.Value, err error) {
   151  
   152  	rtyp, err := t.consistentValue(arg, dtyp)
   153  	if err != nil {
   154  		return val, err
   155  	}
   156  	if arg == nil {
   157  		return reflect.New(rtyp).Elem(), nil
   158  	}
   159  	return reflect.ValueOf(arg).Convert(rtyp), nil
   160  }
   161  
   162  func (t *Troop) consistentValue(arg interface{}, dtyp dwarf.Type) (
   163  	reflect.Type, error) {
   164  
   165  	rtyp, err := t.findDwarfType(dtyp)
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  	if arg == nil {
   170  		if !reflectCanBeNil(rtyp) {
   171  			return nil, errs.New("passing nil to non-nillable type: %v",
   172  				rtyp)
   173  		}
   174  		return rtyp, nil
   175  	}
   176  	if !reflect.TypeOf(arg).ConvertibleTo(rtyp) {
   177  		return nil, errs.New("cannot convert %v to %T", rtyp, arg)
   178  	}
   179  	return rtyp, nil
   180  }