github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/pkg/actions/lua/stdlib.go (about) 1 package lua 2 3 import ( 4 "context" 5 "io" 6 "runtime" 7 "strconv" 8 "strings" 9 10 glua "github.com/Shopify/go-lua" 11 ) 12 13 // This file is an adaptation of https://github.com/Shopify/go-lua/blob/main/base.go 14 // Original MIT license with copyright (as appears in https://github.com/Shopify/go-lua/blob/main/LICENSE.md): 15 /* 16 The MIT License (MIT) 17 18 Copyright (c) 2014 Shopify Inc. 19 20 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 21 22 The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 23 24 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 25 */ 26 27 func next(l *glua.State) int { 28 glua.CheckType(l, 1, glua.TypeTable) 29 l.SetTop(2) 30 if l.Next(1) { 31 return 2 32 } 33 l.PushNil() 34 return 1 35 } 36 37 func pairs(method string, isZero bool, iter glua.Function) glua.Function { 38 return func(l *glua.State) int { 39 if hasMetamethod := glua.MetaField(l, 1, method); !hasMetamethod { 40 glua.CheckType(l, 1, glua.TypeTable) // argument must be a table 41 l.PushGoFunction(iter) // will return generator, 42 l.PushValue(1) // state, 43 if isZero { // and initial value 44 l.PushInteger(0) 45 } else { 46 l.PushNil() 47 } 48 } else { 49 l.PushValue(1) // argument 'self' to metamethod 50 l.Call(1, 3) // get 3 values from metamethod 51 } 52 return 3 53 } 54 } 55 56 func intPairs(l *glua.State) int { 57 i := glua.CheckInteger(l, 2) 58 glua.CheckType(l, 1, glua.TypeTable) 59 i++ // next value 60 l.PushInteger(i) 61 l.RawGetInt(1, i) 62 if l.IsNil(-1) { 63 return 1 64 } 65 return 2 66 } 67 68 func finishProtectedCall(l *glua.State, status bool) int { 69 if !l.CheckStack(1) { 70 l.SetTop(0) // create space for return values 71 l.PushBoolean(false) 72 l.PushString("stack overflow") 73 return 2 // return false, message 74 } 75 l.PushBoolean(status) // first result (status) 76 l.Replace(1) // put first result in the first slot 77 return l.Top() 78 } 79 80 func protectedCallContinuation(l *glua.State) int { 81 _, shouldYield, _ := l.Context() 82 return finishProtectedCall(l, shouldYield) 83 } 84 85 func loadHelper(l *glua.State, s error, e int) int { 86 if s == nil { 87 if e != 0 { 88 l.PushValue(e) 89 if _, ok := glua.SetUpValue(l, -2, 1); !ok { 90 l.Pop(1) 91 } 92 } 93 return 1 94 } 95 l.PushNil() 96 l.Insert(-2) 97 return 2 98 } 99 100 type genericReader struct { 101 l *glua.State 102 r *strings.Reader 103 e error 104 } 105 106 func (r *genericReader) Read(b []byte) (n int, err error) { 107 if r.e != nil { 108 return 0, r.e 109 } 110 if l := r.l; r.r == nil { 111 glua.CheckStackWithMessage(l, 2, "too many nested functions") 112 l.PushValue(1) 113 if l.Call(0, 1); l.IsNil(-1) { 114 l.Pop(1) 115 return 0, io.EOF 116 } else if !l.IsString(-1) { 117 glua.Errorf(l, "reader function must return a string") 118 } 119 if s, ok := l.ToString(-1); ok { 120 r.r = strings.NewReader(s) 121 } else { 122 return 0, io.EOF 123 } 124 } 125 if n, err = r.r.Read(b); err == io.EOF { 126 r.r, err = nil, nil 127 } else if err != nil { 128 r.e = err 129 } 130 return 131 } 132 133 func getBaseLibrary(output io.StringWriter) []glua.RegistryFunction { 134 return []glua.RegistryFunction{ 135 {Name: "assert", Function: func(l *glua.State) int { 136 if !l.ToBoolean(1) { 137 glua.Errorf(l, "%s", glua.OptString(l, 2, "assertion failed!")) 138 panic("unreachable") 139 } 140 return l.Top() 141 }}, 142 {Name: "collectgarbage", Function: func(l *glua.State) int { 143 switch opt, _ := glua.OptString(l, 1, "collect"), glua.OptInteger(l, 2, 0); opt { 144 case "collect": 145 runtime.GC() 146 l.PushInteger(0) 147 case "step": 148 runtime.GC() 149 l.PushBoolean(true) 150 case "count": 151 var stats runtime.MemStats 152 runtime.ReadMemStats(&stats) 153 l.PushNumber(float64(stats.HeapAlloc >> 10)) 154 l.PushInteger(int(stats.HeapAlloc & 0x3ff)) 155 return 2 156 default: 157 l.PushInteger(-1) 158 } 159 return 1 160 }}, 161 // {"dofile", func(l *glua.State) int { 162 // f := glua.OptString(l, 1, "") 163 // if l.SetTop(1); glua.LoadFile(l, f, "") != nil { 164 // l.Error() 165 // panic("unreachable") 166 // } 167 // continuation := func(l *glua.State) int { return l.Top() - 1 } 168 // l.CallWithContinuation(0, glua.MultipleReturns, 0, continuation) 169 // return continuation(l) 170 // }}, 171 {Name: "error", Function: func(l *glua.State) int { 172 level := glua.OptInteger(l, 2, 1) 173 l.SetTop(1) 174 if l.IsString(1) && level > 0 { 175 glua.Where(l, level) 176 l.PushValue(1) 177 l.Concat(2) 178 } 179 l.Error() 180 panic("unreachable") 181 }}, 182 {Name: "getmetatable", Function: func(l *glua.State) int { 183 glua.CheckAny(l, 1) 184 if !l.MetaTable(1) { 185 l.PushNil() 186 return 1 187 } 188 glua.MetaField(l, 1, "__metatable") 189 return 1 190 }}, 191 {Name: "ipairs", Function: pairs("__ipairs", true, intPairs)}, 192 // {"loadfile", func(l *glua.State) int { 193 // f, m, e := glua.OptString(l, 1, ""), glua.OptString(l, 2, ""), 3 194 // if l.IsNone(e) { 195 // e = 0 196 // } 197 // return loadHelper(l, glua.LoadFile(l, f, m), e) 198 // }}, 199 {Name: "load", Function: func(l *glua.State) int { 200 m, e := glua.OptString(l, 3, "bt"), 4 201 if l.IsNone(e) { 202 e = 0 203 } 204 var err error 205 if s, ok := l.ToString(1); ok { 206 err = glua.LoadBuffer(l, s, glua.OptString(l, 2, s), m) 207 } else { 208 chunkName := glua.OptString(l, 2, "=(load)") 209 glua.CheckType(l, 1, glua.TypeFunction) 210 err = l.Load(&genericReader{l: l}, chunkName, m) 211 } 212 return loadHelper(l, err, e) 213 }}, 214 {Name: "next", Function: next}, 215 {Name: "pairs", Function: pairs("__pairs", false, next)}, 216 {Name: "pcall", Function: func(l *glua.State) int { 217 glua.CheckAny(l, 1) 218 l.PushNil() 219 l.Insert(1) // create space for status result 220 return finishProtectedCall(l, nil == l.ProtectedCallWithContinuation(l.Top()-2, glua.MultipleReturns, 0, 0, protectedCallContinuation)) 221 }}, 222 {Name: "print", Function: func(l *glua.State) int { 223 n := l.Top() 224 l.Global("tostring") 225 for i := 1; i <= n; i++ { 226 l.PushValue(-1) // function to be called 227 l.PushValue(i) // value to print 228 l.Call(1, 1) 229 s, ok := l.ToString(-1) 230 if !ok { 231 glua.Errorf(l, "'tostring' must return a string to 'print'") 232 panic("unreachable") 233 } 234 if i > 1 { 235 _, _ = output.WriteString("\t") 236 } 237 _, _ = output.WriteString(s) 238 l.Pop(1) // pop result 239 } 240 _, _ = output.WriteString("\n") 241 return 0 242 }}, 243 {Name: "rawequal", Function: func(l *glua.State) int { 244 glua.CheckAny(l, 1) 245 glua.CheckAny(l, 2) 246 l.PushBoolean(l.RawEqual(1, 2)) 247 return 1 248 }}, 249 {Name: "rawlen", Function: func(l *glua.State) int { 250 t := l.TypeOf(1) 251 glua.ArgumentCheck(l, t == glua.TypeTable || t == glua.TypeString, 1, "table or string expected") 252 l.PushInteger(l.RawLength(1)) 253 return 1 254 }}, 255 {Name: "rawget", Function: func(l *glua.State) int { 256 glua.CheckType(l, 1, glua.TypeTable) 257 glua.CheckAny(l, 2) 258 l.SetTop(2) 259 l.RawGet(1) 260 return 1 261 }}, 262 {Name: "rawset", Function: func(l *glua.State) int { 263 glua.CheckType(l, 1, glua.TypeTable) 264 glua.CheckAny(l, 2) 265 glua.CheckAny(l, 3) 266 l.SetTop(3) 267 l.RawSet(1) 268 return 1 269 }}, 270 {Name: "select", Function: func(l *glua.State) int { 271 n := l.Top() 272 if l.TypeOf(1) == glua.TypeString { 273 if s, _ := l.ToString(1); s[0] == '#' { 274 l.PushInteger(n - 1) 275 return 1 276 } 277 } 278 i := glua.CheckInteger(l, 1) 279 if i < 0 { 280 i = n + i 281 } else if i > n { 282 i = n 283 } 284 glua.ArgumentCheck(l, 1 <= i, 1, "index out of range") 285 return n - i 286 }}, 287 {Name: "setmetatable", Function: func(l *glua.State) int { 288 t := l.TypeOf(2) 289 glua.CheckType(l, 1, glua.TypeTable) 290 glua.ArgumentCheck(l, t == glua.TypeNil || t == glua.TypeTable, 2, "nil or table expected") 291 if glua.MetaField(l, 1, "__metatable") { 292 glua.Errorf(l, "cannot change a protected metatable") 293 } 294 l.SetTop(2) 295 l.SetMetaTable(1) 296 return 1 297 }}, 298 {Name: "tonumber", Function: func(l *glua.State) int { 299 if l.IsNoneOrNil(2) { // standard conversion 300 if n, ok := l.ToNumber(1); ok { 301 l.PushNumber(n) 302 return 1 303 } 304 glua.CheckAny(l, 1) 305 } else { 306 s := glua.CheckString(l, 1) 307 base := glua.CheckInteger(l, 2) 308 glua.ArgumentCheck(l, 2 <= base && base <= 36, 2, "base out of range") 309 if i, err := strconv.ParseInt(strings.TrimSpace(s), base, 64); err == nil { 310 l.PushNumber(float64(i)) 311 return 1 312 } 313 } 314 l.PushNil() 315 return 1 316 }}, 317 {Name: "tostring", Function: func(l *glua.State) int { 318 glua.CheckAny(l, 1) 319 glua.ToStringMeta(l, 1) 320 return 1 321 }}, 322 {Name: "type", Function: func(l *glua.State) int { 323 glua.CheckAny(l, 1) 324 l.PushString(glua.TypeNameOf(l, 1)) 325 return 1 326 }}, 327 {Name: "xpcall", Function: func(l *glua.State) int { 328 n := l.Top() 329 glua.ArgumentCheck(l, n >= 2, 2, "value expected") 330 l.PushValue(1) // exchange function and error handler 331 l.Copy(2, 1) 332 l.Replace(2) 333 return finishProtectedCall(l, nil == l.ProtectedCallWithContinuation(n-2, glua.MultipleReturns, 1, 0, protectedCallContinuation)) 334 }}, 335 } 336 } 337 338 // BaseOpen opens the basic library. Usually passed to Require. 339 func BaseOpen(buf io.StringWriter) glua.Function { 340 return func(l *glua.State) int { 341 l.PushGlobalTable() 342 l.PushGlobalTable() 343 l.SetField(-2, "_G") 344 glua.SetFunctions(l, getBaseLibrary(buf), 0) 345 l.PushString(glua.VersionString) 346 l.SetField(-2, "_VERSION") 347 return 1 348 } 349 } 350 351 type OpenSafeConfig struct { 352 NetHTTPEnabled bool 353 LakeFSAddr string // The domain (or "authority:port") that lakeFS listens to 354 } 355 356 func OpenSafe(l *glua.State, ctx context.Context, cfg OpenSafeConfig, buf io.StringWriter) { 357 // a thin version of the standard library that doesn't include 'io' 358 // along with a set of globals that omit anything that loads something external or reaches out to OS. 359 libs := []glua.RegistryFunction{ 360 {Name: "_G", Function: BaseOpen(buf)}, 361 {Name: "package", Function: PackageOpen}, // using our own PackageOpen which disable loading sources from files 362 {Name: "table", Function: glua.TableOpen}, 363 {Name: "string", Function: glua.StringOpen}, 364 {Name: "bit32", Function: glua.Bit32Open}, 365 {Name: "math", Function: glua.MathOpen}, 366 {Name: "debug", Function: glua.DebugOpen}, 367 } 368 for _, lib := range libs { 369 glua.Require(l, lib.Name, lib.Function, true) 370 l.Pop(1) 371 } 372 373 // utils adapted from goluago, skipping anything that allows network or storage access. 374 // additionally, the "goluago" namespace is removed from import tokens 375 Open(l, ctx, cfg) 376 }