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  }