github.com/arnodel/golua@v0.0.0-20230215163904-e0b5347eaaa1/lib/debuglib/debuglib.go (about)

     1  package debuglib
     2  
     3  import (
     4  	"errors"
     5  	"strings"
     6  
     7  	"github.com/arnodel/golua/lib/packagelib"
     8  	rt "github.com/arnodel/golua/runtime"
     9  )
    10  
    11  // LibLoader can load the debug lib.
    12  var LibLoader = packagelib.Loader{
    13  	Load: load,
    14  	Name: "debug",
    15  }
    16  
    17  func load(r *rt.Runtime) (rt.Value, func()) {
    18  	pkg := rt.NewTable()
    19  	pkgVal := rt.TableValue(pkg)
    20  	r.SetEnv(r.GlobalEnv(), "debug", pkgVal)
    21  
    22  	rt.SolemnlyDeclareCompliance(
    23  		rt.ComplyCpuSafe|rt.ComplyMemSafe|rt.ComplyTimeSafe|rt.ComplyIoSafe,
    24  
    25  		r.SetEnvGoFunc(pkg, "gethook", gethook, 1, false),
    26  		r.SetEnvGoFunc(pkg, "getinfo", getinfo, 3, false),
    27  		r.SetEnvGoFunc(pkg, "getupvalue", getupvalue, 2, false),
    28  		r.SetEnvGoFunc(pkg, "setupvalue", setupvalue, 3, false),
    29  		r.SetEnvGoFunc(pkg, "upvaluejoin", upvaluejoin, 4, false),
    30  		r.SetEnvGoFunc(pkg, "setmetatable", setmetatable, 2, false),
    31  		r.SetEnvGoFunc(pkg, "sethook", sethook, 4, false),
    32  		r.SetEnvGoFunc(pkg, "traceback", traceback, 3, false),
    33  		r.SetEnvGoFunc(pkg, "upvalueid", upvalueid, 2, false),
    34  	)
    35  
    36  	return pkgVal, nil
    37  }
    38  
    39  func getinfo(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
    40  	if err := c.Check1Arg(); err != nil {
    41  		return nil, err
    42  	}
    43  	var (
    44  		thread *rt.Thread
    45  		idx    int64
    46  		cont   rt.Cont
    47  		what   string
    48  		fIdx   int
    49  	)
    50  	thread, ok := c.Arg(0).TryThread()
    51  	if !ok {
    52  		thread = t
    53  	} else {
    54  		fIdx = 1
    55  	}
    56  	if c.NArgs() < 1+fIdx {
    57  		return nil, errors.New("missing argument: f")
    58  	}
    59  	switch arg := c.Arg(fIdx); arg.Type() {
    60  	case rt.IntType:
    61  		idx = arg.AsInt()
    62  	case rt.FunctionType:
    63  		term := rt.NewTerminationWith(c, 0, false)
    64  		cont = arg.AsCallable().Continuation(t, term)
    65  	case rt.FloatType:
    66  		var tp rt.NumberType
    67  		idx, tp = rt.FloatToInt(arg.AsFloat())
    68  		if tp != rt.IsInt {
    69  			return nil, errors.New("f should be an integer or function")
    70  		}
    71  	default:
    72  		return nil, errors.New("f should be an integer or function")
    73  	}
    74  	if cont == nil {
    75  		cont = thread.CurrentCont()
    76  	}
    77  	for idx > 0 && cont != nil {
    78  		cont = cont.Parent()
    79  		idx--
    80  	}
    81  	// TODO: support what arg
    82  	_ = what
    83  	next := c.Next()
    84  	if cont == nil {
    85  		t.Push1(next, rt.NilValue)
    86  	} else if info := cont.DebugInfo(); info == nil {
    87  		t.Push1(next, rt.NilValue)
    88  	} else {
    89  		res := rt.NewTable()
    90  		t.SetEnv(res, "name", rt.StringValue(info.Name))
    91  		t.SetEnv(res, "currentline", rt.IntValue(int64(info.CurrentLine)))
    92  		t.SetEnv(res, "source", rt.StringValue(info.Source))
    93  		t.Push1(next, rt.TableValue(res))
    94  	}
    95  	return next, nil
    96  }
    97  
    98  func getupvalue(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
    99  	if err := c.CheckNArgs(2); err != nil {
   100  		return nil, err
   101  	}
   102  	f, err := c.ClosureArg(0)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	upv, err := c.IntArg(1)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  	up := int(upv) - 1
   111  	next := c.Next()
   112  	if up >= 0 && up < int(f.Code.UpvalueCount) {
   113  		t.Push(next, rt.StringValue(f.Code.UpNames[up]), f.GetUpvalue(up))
   114  	}
   115  	return next, nil
   116  }
   117  
   118  func setupvalue(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   119  	if err := c.CheckNArgs(3); err != nil {
   120  		return nil, err
   121  	}
   122  	f, err := c.ClosureArg(0)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  	upv, err := c.IntArg(1)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	up := int(upv) - 1
   131  	next := c.Next()
   132  	if up >= 0 && up < int(f.Code.UpvalueCount) {
   133  		t.Push1(next, rt.StringValue(f.Code.UpNames[up]))
   134  		f.SetUpvalue(up, c.Arg(2))
   135  	}
   136  	return next, nil
   137  }
   138  
   139  func upvaluejoin(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   140  	if err := c.CheckNArgs(4); err != nil {
   141  		return nil, err
   142  	}
   143  	f1, err := c.ClosureArg(0)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	upv1, err := c.IntArg(1)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  	f2, err := c.ClosureArg(2)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	upv2, err := c.IntArg(3)
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	up1 := int(upv1) - 1
   160  	up2 := int(upv2) - 1
   161  	if up1 < 0 || up1 >= int(f1.Code.UpvalueCount) || up2 < 0 || up2 >= int(f2.Code.UpvalueCount) {
   162  		return nil, errors.New("Invalid upvalue index")
   163  	}
   164  	f1.Upvalues[up1] = f2.Upvalues[up2]
   165  	return c.Next(), nil
   166  }
   167  
   168  func upvalueid(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   169  	if err := c.CheckNArgs(2); err != nil {
   170  		return nil, err
   171  	}
   172  	f, err := c.ClosureArg(0)
   173  	if err != nil {
   174  		return nil, err
   175  	}
   176  	upv, err := c.IntArg(1)
   177  	if err != nil {
   178  		return nil, err
   179  	}
   180  	up := int(upv) - 1
   181  	if up < 0 || up >= int(f.Code.UpvalueCount) {
   182  		return c.PushingNext1(t.Runtime, rt.NilValue), nil
   183  	}
   184  	return c.PushingNext1(t.Runtime, rt.LightUserDataValue(rt.LightUserData{Data: f.Upvalues[up]})), nil
   185  }
   186  
   187  func setmetatable(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   188  	var err error
   189  	if err = c.CheckNArgs(2); err != nil {
   190  		return nil, err
   191  	}
   192  	v := c.Arg(0)
   193  	var meta *rt.Table
   194  	if !c.Arg(1).IsNil() {
   195  		meta, err = c.TableArg(1)
   196  		if err != nil {
   197  			return nil, err
   198  		}
   199  	}
   200  	t.SetRawMetatable(v, meta)
   201  	return c.PushingNext1(t.Runtime, v), nil
   202  }
   203  
   204  func gethook(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   205  	var (
   206  		err    error
   207  		thread = t
   208  	)
   209  	if c.NArgs() > 0 {
   210  		thread, err = c.ThreadArg(0)
   211  		if err != nil {
   212  			return nil, err
   213  		}
   214  	}
   215  	hooks := thread.DebugHooks
   216  	return c.PushingNext(t.Runtime, hooks.Hook, rt.StringValue(getMaskString(hooks.DebugHookFlags)), rt.IntValue(int64(hooks.HookLineCount))), nil
   217  }
   218  
   219  func getMaskString(flags rt.DebugHookFlags) string {
   220  	var b strings.Builder
   221  	if flags&rt.HookFlagCall != 0 {
   222  		b.WriteByte('c')
   223  	}
   224  	if flags&rt.HookFlagReturn != 0 {
   225  		b.WriteByte('r')
   226  	}
   227  	if flags&rt.HookFlagLine != 0 {
   228  		b.WriteByte('l')
   229  	}
   230  	return b.String()
   231  }
   232  
   233  func sethook(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   234  	var (
   235  		argOffset int
   236  		err       error
   237  		thread    = t
   238  		hook      rt.Value
   239  		mask      string
   240  		count     int64
   241  	)
   242  
   243  	// Special case when resetting hook
   244  
   245  	if c.NArgs() == 1 {
   246  		thread, err = c.ThreadArg(0)
   247  		if err != nil {
   248  			return nil, err
   249  		}
   250  	}
   251  	if c.NArgs() < 2 {
   252  		thread.SetupHooks(rt.DebugHooks{})
   253  		return c.Next(), nil
   254  	}
   255  
   256  	// Get arguments (at least 2)
   257  
   258  	thread, err = c.ThreadArg(0)
   259  	if err != nil {
   260  		thread = t
   261  	} else {
   262  		if err = c.CheckNArgs(3); err != nil {
   263  			return nil, err
   264  		}
   265  		argOffset = 1
   266  	}
   267  	hook = c.Arg(argOffset)
   268  	mask, err = c.StringArg(argOffset + 1)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	if c.NArgs() > argOffset+2 {
   273  		count, err = c.IntArg(argOffset + 2)
   274  		if err != nil {
   275  			return nil, err
   276  		}
   277  	}
   278  
   279  	var flags rt.DebugHookFlags
   280  	for _, r := range mask {
   281  		switch r {
   282  		case 'c':
   283  			flags |= rt.HookFlagCall
   284  		case 'r':
   285  			flags |= rt.HookFlagReturn
   286  		case 'l':
   287  			flags |= rt.HookFlagLine
   288  		}
   289  	}
   290  	if count < 0 {
   291  		count = 0
   292  	}
   293  
   294  	thread.SetupHooks(rt.DebugHooks{
   295  		DebugHookFlags: flags,
   296  		HookLineCount:  int(count),
   297  		Hook:           hook,
   298  	})
   299  	return c.Next(), nil
   300  }
   301  
   302  func traceback(t *rt.Thread, c *rt.GoCont) (rt.Cont, error) {
   303  	var (
   304  		cont            = t.CurrentCont()
   305  		msgString       = ""
   306  		nArgs           = c.NArgs()
   307  		level     int64 = 1
   308  	)
   309  	if nArgs > 0 {
   310  		msgIndex := 0
   311  		arg0 := c.Arg(0)
   312  		if arg0.Type() == rt.ThreadType {
   313  			cont = arg0.AsThread().CurrentCont()
   314  			msgIndex = 1
   315  		}
   316  		if nArgs > msgIndex {
   317  			msg := c.Arg(msgIndex)
   318  			if !msg.IsNil() {
   319  				var ok bool
   320  				msgString, ok = msg.TryString()
   321  				if !ok {
   322  					return c.PushingNext1(t.Runtime, msg), nil
   323  				}
   324  			}
   325  		}
   326  		if nArgs > msgIndex+1 {
   327  			var err error
   328  			level, err = c.IntArg(msgIndex + 1)
   329  			if err != nil {
   330  				return nil, err
   331  			}
   332  		}
   333  	}
   334  	for level > 0 && cont != nil {
   335  		cont = cont.Next()
   336  		level--
   337  	}
   338  	tb := rt.StringValue(t.Traceback(msgString, cont))
   339  	return c.PushingNext1(t.Runtime, tb), nil
   340  }
   341  
   342  var Traceback = rt.NewGoFunction(traceback, "traceback", 3, false)