Перейти к содержимому

Динамическое выполнение кода

Выполнение кода во время работы приложения в изолированном окружении с контролем доступа к модулям.

Wippy предоставляет две системы выполнения:

СистемаНазначениеПрименение
exprВычисление выраженийКонфиги, шаблоны, простые вычисления
eval_runnerПолноценное выполнение LuaПлагины, пользовательские скрипты

Вычисление простых выражений на синтаксисе expr-lang:

local expr = require("expr")
local result, err = expr.eval("x + y * 2", {x = 10, y = 5})
-- result = 20

Скомпилировав выражение один раз, можно выполнять его многократно:

local program, err = expr.compile("price * quantity")
local total1 = program:run({price = 10, quantity = 5})
local total2 = program:run({price = 20, quantity = 3})
-- Арифметика
expr.eval("1 + 2 * 3") -- 7
expr.eval("10 / 2 - 1") -- 4
expr.eval("10 % 3") -- 1
-- Сравнение
expr.eval("x > 5", {x = 10}) -- true
expr.eval("x == y", {x = 1, y = 1}) -- true
-- Логические операции
expr.eval("a && b", {a = true, b = false}) -- false
expr.eval("a || b", {a = true, b = false}) -- true
expr.eval("!a", {a = false}) -- true
-- Тернарный оператор
expr.eval("x > 0 ? 'positive' : 'negative'", {x = 5})
-- Функции
expr.eval("max(1, 5, 3)") -- 5
expr.eval("min(1, 5, 3)") -- 1
expr.eval("len([1, 2, 3])") -- 3
-- Массивы
expr.eval("[1, 2, 3][0]") -- 1
-- Конкатенация строк
expr.eval("'hello' + ' ' + 'world'")

Полноценное выполнение Lua с контролем безопасности:

local runner = require("eval_runner")
local result, err = runner.run({
source = [[
local function double(x)
return x * 2
end
return double(input)
]],
args = {21}
})
-- result = 42
ПараметрТипОписание
sourcestringИсходный код Lua (обязательно)
methodstringФункция для вызова из возвращаемой таблицы
argsany[]Аргументы для функции
modulesstring[]Разрешённые встроенные модули
importstableЗаписи реестра для импорта
contexttableЗначения, доступные как ctx
allow_classesstring[]Дополнительные классы модулей
custom_modulestableПользовательские таблицы как модули

Укажите белый список разрешённых модулей:

runner.run({
source = [[
local json = require("json")
return json.encode({hello = "world"})
]],
modules = {"json"}
})

Модули, не указанные в списке, недоступны.

Импорт записей из реестра:

runner.run({
source = [[
local utils = require("utils")
return utils.format(data)
]],
imports = {
utils = "app.lib:utilities"
},
args = {{key = "value"}}
})

Передача собственных таблиц:

runner.run({
source = [[
return sdk.version
]],
custom_modules = {
sdk = {version = "1.0.0", api_key = "xxx"}
}
})

Передача данных, доступных через ctx:

runner.run({
source = [[
return "Привет, " .. ctx.user
]],
context = {user = "Алексей"}
})

runner.compile проверяет исходный код и сообщает его точку входа и модули, не выполняя его:

local program, err = runner.compile([[
local function process(x)
return x * 2
end
return { process = process }
]], "process", {modules = {"json"}})
program:method() -- "process" (string)
program:modules() -- {"json"} (string[])

Скомпилированная программа носит информационный характер; для выполнения вызовите runner.run с исходным кодом и методом.

Модули классифицированы по возможностям:

КлассОписаниеПо умолчанию
deterministicЧистые функцииРазрешён
encodingКодирование данныхРазрешён
timeОперации со временемРазрешён
nondeterministicСлучайные числа и т.п.Разрешён
processSpawn, реестрЗапрещён
storageФайлы, БДЗапрещён
networkHTTP, сокетыЗапрещён
runner.run({
source = [[
local http = require("http_client")
return http.get("https://api.example.com")
]],
modules = {"http_client"},
allow_classes = {"network"}
})

Система проверяет права на:

  • eval.compile — перед компиляцией
  • eval.run — перед выполнением
  • eval.module — для каждого модуля в белом списке
  • eval.import — для каждого импорта из реестра
  • eval.class — для каждого разрешённого класса

Настройка в политиках безопасности.

local result, err = runner.run({...})
if err then
if err:kind() == errors.PERMISSION_DENIED then
-- Доступ запрещён политикой безопасности
elseif err:kind() == errors.INVALID then
-- Неверный код или настройки
elseif err:kind() == errors.INTERNAL then
-- Ошибка выполнения или компиляции
end
end
local plugins = registry.find({meta = {type = "plugin"}})
for _, plugin in ipairs(plugins) do
local source = plugin:data().source
runner.run({
source = source,
method = "init",
modules = {"json", "time"},
context = {config = app_config}
})
end
local template = "Hello, {{name}}! You have {{count}} messages."
local compiled = expr.compile("name")
-- Быстрое многократное вычисление
for _, user in ipairs(users) do
local greeting = compiled:run({name = user.name})
end
local user_code = request:body()
local result, err = runner.run({
source = user_code,
modules = {"json", "text"}, -- Только безопасные модули
context = {data = input_data}
})