Use curried lua function as custom complete list in neovim - lua

I'm trying to provide a curried lua function as method to call for autocomplete a command. However, it sometimes does nothing and sometimes fails with a memory segmentation error.
This is a minimal reproduction example:
Given this init.lua
vim.cmd(string.format([[set rtp=%s]], vim.fn.getcwd()))
local custom_complete = [[customlist,v:lua.require'split_later'.split_later('a b')]]
local options = { complete = custom_complete, desc = "Test to break", nargs = 1, force = true }
vim.api.nvim_create_user_command("Test", "echo <args>", options)
And this file called split_later under the correct lua/ subfolder:
local M = {}
function M.split_later(str)
print("called split")
return function()
return vim.fn.split(str)
end
end
return M
The idea is to have a generic utility that is able to produce auto-complete functions from lua, but it is not working as expected.
I found a workaround using a lookup table that stores the command name and then uses it, but I don't like it that much.

Related

Getting "attempt to index a nil value error" when attempting to create objects in Lua

I'm putting some code into a module so I can draw and maintain multiple copies. I'm getting this common error but I can't see why. I understand what it's saying to a basic level, but as I'm able to see a print out from the table being created, I don't understand why calling a function that module contains would throw this error.
I've read through all the answers on SO, but I'm still at a loss. I've tried printing out at various stages to see where the issue is, everything works as if I had created an instance of the module, but the error persists.
Code below is cleaned of extraneous stuff.
local orbitalCircle = include('lib/orbital_circle')
function init()
c1 = orbitalCircle.new(20, 42, 18, 1.7, 16, 62, 15, c1Sequence)
<-- at this point print code from the module's init function works
c1:doFunc(param) <-- this will call the error
The module:
local Orbital_Circle = {}
-- set up variables
local some Vars Are here
function Orbital_Circle.new(x, y, diameter, scale_factor, number_of_notes, beats_per_second, frames_per_second, sequence_data)
print("Orbital_Circle running")
end
function Orbital_Circle:doFunc(param)
self.var = param <-- I update a local var here
print("self.var") <-- I then print the updated number for sanity checking
end
return Orbital_Circle
I expect the var in my instance of this module to update and the functions code to run, but... no joy. I get the error.
Cheers.
I'm putting some code into a module so I can draw and maintain multiple copies.
I think there's a bit of a misunderstanding about how Lua modules work here. It's an easy mistake to make.
When you require a module in Lua, each subsequent require of the same file refers to the same code. So (eg) these two variables contain exactly the same code:
local orbitalCircle1 = require('lib/orbital_circle')
local orbitalCircle2 = require('lib/orbital_circle')
Which means that you can't use Lua modules by themselves to create OOP type objects as you are trying to do. Your new function must return something that can be used like an instance of a class, a unique table for each call:
local Orbital_Circle = {}
local shared_variable = 1
function Orbital_Circle.new(x, y)
-- create unique table
local obj = {}
-- access these from table/object methods with self.xxx
obj.x = x or 0
obj.y = y or 0
obj.var = "initial value"
-- now define functions with an explicit 'self' parameter...
function obj.doFunc(self, param)
self.var = self.var .. " " .. param
shared_variable = shared_variable + 1
end
-- ... or with the syntactic 'self' sugar, ':'
function obj:printVars()
print("self.var = " .. self.var)
print("shared_variable = " .. shared_variable)
print("self.x = " .. self.x)
end
return obj
end
return Orbital_Circle
You can also define the methods as local functions outside the new function that have self parameter and have a list of entries such as:
obj.anotherMethod = functionDeclaredAtTopOfFile
… to keep things tidier, if you like.
Your code is completely messed up.
<-- will cause an error for unexpected symbol.
c1 = orbitalCircle.new(20, 42, 18, 1.7, 16, 62, 15, c1Sequence)
will give you an error for indexing a global nil value c1 because orbitalCircle.new has no return value.
your init function is incomplete and you don't call it so the provided code does not do anything even if you fix the above errors.
The reported error is not caused by any line of code you provided here.
Code below is cleaned of extraneous stuff.
I'm afraid you removed too much.
The error message tells you that you're indexing local n, a nil value from within a local function that has been defined in n's scope.
This code for example:
local n
function test()
local b = n.a
end
test()
would result in the error message:
input:3: attempt to index a nil value (upvalue 'n')
n is an upvalue for test because it is a local variable defined outside the functions body, but not a global variable.

sanitizing a Lua table input

Let's say I want a Lua table that will be provided from a third party, not totally reliable, from a file or other IO source.
I get the table as a string, like "{['valid'] = 10}" and I can load it as
externalTable = loadstring("return " .. txtTable)()
But this opens a breach to code injection, ie.: txtTable = os.execute('rm -rf /')
So I did this sanitizing function:
function safeLoadTable(txtTable)
txtTable = tostring(txtTable)
if (string.find(txtTable, "(", 1, true))
then return nil end
local _start = string.find(txtTable, "{", 1, true)
local _end = string.find(string.reverse(txtTable), "}", 1, true)
if (_start == nil or _end == nil)
then return nil end
txtTable = string.sub(txtTable, _start, #txtTable - _end + 1)
print("cropped to ", txtTable)
local pFunc = loadstring("return " .. txtTable)
if (pFunc) then
local _, aTable = pcall(pFunc)
return aTable
end
end
In the worst case it should return nil.
Can this be considered safe against a "regular bad-intentioned person" :)
You could run the unsafe code in a sandbox.
Here is how a simple sandbox could look in Lua 5.1 (error handling omitted for brevity):
local script = [[os.execute("rm -rf /")]]
local env = { print=print, table=table, string=string }
local f, err = loadstring(script)
if err then
-- handle syntax error
end
setfenv(f, env)
local status, err = pcall(f)
if not status then
-- handle runtime error
end
In Lua 5.2 you can load the script into it's own environment using the load function.
The result would be a runtime error returned from pcall:
attempt to index global 'os' (a nil value)
EDIT
As Lorenzo Donati pointed out in the comments this is not a complete solution to stop rogue scripts. It essentially allows you to white-list functions and tables that are approved for user scripts.
For more info about handling rogue scripts I would suggest this SO question:
Embedded Lua - timing out rogue scripts (e.g. infinite loop) - an example anyone?
I don't think it is safe. Try this:
print(safeLoadTable [[{ foo = (function() print"yahoo" end)() } ]])
EDIT
or this, for more fun:
print(safeLoadTable [[{ foo = (function() print(os.getenv "PATH") end)() } ]])
I won't suggest the alternative of replacing that os.getenv with os.execute, though. :-)
The problem is not easy to solve. Code injection avoidance is not at all simple in this case because you are executing a piece of Lua code when doing that loadstring. No simple string matching technique is really safe. The only secure way would be to implement a parser for a subset of the Lua table syntax and use that parser on the string.
BTW, even Lua team stripped off the bytecode verifier from Lua 5.2 since they discovered that it was amenable to attacks, and bytecode is a far simpler language than Lua source code.
I created sandbox.lua for exactly this purpose. It'll handle both insecure stuff as well as DOS-type attacks, assuming that your environment has access to the debug facility.
https://github.com/kikito/sandbox.lua
Note that for now it is Lua 5.1-compatible only.
Running in sandbox isn't safe, inspecting source code is not very simple. An idea: inspect bytecode!
Emmm, actually that's not very simple either, but here is a lazy implementation: http://codepad.org/mGqQ0Y8q

Error while trying to call a class method: attempt to index local 'self' (a nil value) - Lua

I'm creating a lua script that should run on the TI-Nspire calculator. The problem is that while running my script I get the error Attempt to index local 'self' (a nil value) when the button:activate() method is called. The parser says the error is in the 8th line in the code below. The problematic code is as follows:
button = class(view)
function button:init()
self.selected = false
end
function button:activate()
self.selected = true
end
I call the activate function like this:
item = button()
local action = "activate"
local arguments = {}
item[action](unpack(arguments))
I am aware the class() function doesn't exist in "stock" Lua, it's a function available in the TI-Nspire Lua implementation. You can find its definition and usage here.
obj:methodname(args) is sugar for obj.methodname(obj,args). So, if you want to use the syntax item[action](unpack(arguments)), you need to use item[action](item,unpack(arguments)). Otherwise, try item:activate(unpack(arguments)) if you can use method explicitly.

Making global environment access-only (Lua)

I embedded Lua and want scripts to be able to read the global table but not automatically write to it so two scripts can write variables with the same name without overwriting eachother but still being able to add stuff to the global table. I can't really explain it better then this:
Script 1
var1 = "foo"
_G.var2 = "bar"
Script 2
print(var1) -- Prints nil
print(var2) -- Prints 'bar'
How I tried to accomplish this is by doing something like this (The 'scripts' being a function)
newScript = function(content)
Script = loadstring(content)()
env = setmetatable({},{__index = _G})
setfenv(Script,env)
return Script
end
My Lua binding is LuaJ, for the sake of giving all information here is that code too:
private LuaValue newScript(String content){
LuaTable envMt = new LuaTable();
envMt.set(INDEX, _G);
LuaTable env = new LuaTable();
env.setmetatable(envMt);
LuaClosure func = (LuaClosure) _G.get("loadstring").call(valueOf(content));
thread = new LuaThread(func,env);
thread.resume(NIL);
return thread;
}
It's not __index that you want to change, it's __newindex. In addition, you can't use __index to catch access to keys that do exist in the table. The only way to make a table read-only in all situations is to defer all reads to a proxy table and throw an error on writes.
Here's a function I use to return a read-only table:
function ro_table (t)
local t = t
if t then
return setmetatable({},
{ __index=t,
__newindex= function(_,_,_) error ("Attempt to modify read-only table") end,
})
else
return nil
end
end
So for your code, you'd have the following:
newScript = function(content)
Script = loadstring(content)()
setfenv(Script,ro_table(_G))
return Script
end
Note that this does not work recursively, so if you have any table defined as a global (or even any of the built-in functions) the contents can be changed, but the table itself cannot be replaced.

lua how require works

I'm using a graphics library that lets you program in Lua. I have a need for the A* pathfinding library so I found one online. It's just 1 lua file that does the pathfinding and 1 example file. In the example file it uses the object like:
-- Loading the library
local Astar = require 'Astar'
Astar(map,1) -- Inits the library, sets the OBST_VALUE to 1
I run the script and everything works. So now I add the Astar.lua file to the path location where my graphics engine is running and do the same thing and I get the error on the Astar(map, 1) line:
"attempt to call local 'AStar' (a number value)
Any ideas why I would be getting that error when I'm doing the same thing as the example that comes with this AStar lib?
Here is a little of the AStar file
-- The Astar class
local Astar = {}
setmetatable(Astar, {__call = function(self,...) return self:init(...) end})
Astar.__index = Astar
-- Loads the map, sets the unwalkable value, inits pathfinding
function Astar:init(map,obstvalue)
self.map = map
self.OBST_VALUE = obstvalue or 1
self.cList = {}
self.oList = {}
self.initialNode = false
self.finalNode = false
self.currentNode = false
self.path = {}
self.mapSizeX = #self.map[1]
self.mapSizeY = #self.map
end
So note that when I run this from my graphics engine it's returning 1, but when run from the example that it came with it's returning a table, which is what it should be returning. So not sure why it would only be returning 1.
How is Astar getting added to the package.loaded table for the example script, as opposed to your code?
QUICK LUA SYNTACTIC SUGAR REVIEW:
func 'string' is equivalent to func('string')
tabl.ident is equivalent to tabl['ident']
When you run a script using require('Astar'), this is what it does:
checks if package.loaded['Astar'] is a non-nil value.
If it is, it returns this value. Otherwise it continues down this list.
Runs through filenames of the patterns listed in package.path (and package.cpath), with '?' replaced with 'Astar', until it finds the first file matching the pattern.
Sets package.loaded['Astar'] to true.
Runs the module script (found via path search above- for the sake of this example we'll assume it's not a C module) with 'Astar' as an argument (accessible as ... in the module script).
If the script returns a value, this value is placed into package.loaded['Astar'].
The contents of package.loaded['Astar'] are returned.
Note that the script can load the package into package.loaded['Astar'] as part of its execution and return nothing.
As somebody noted in the comments above, your problem may come from loading the module using 'AStar' instead of 'Astar'. It's possible that Lua is loading this script using this string (since, on the case-insensitive Windows, a search for a file named "AStar.lua" will open a file called "Astar.lua"), but the script isn't operating with that (by using a hard-coded "Astar" instead of the "AStar" Lua is loading the script under).
You need to add return Astar at the end of Astar.lua.

Resources