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)