unplugged-system/packages/services/Car/tools/telemetry/lua-interpreter/lua_engine.cc

501 lines
18 KiB
C++

/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "lua_engine.h"
#include <cstring>
#include <sstream>
#include <string>
#include <vector>
#include "lua.hpp"
namespace lua_interpreter {
std::vector<std::string> LuaEngine::output_;
// Represents the returns of the various CFunctions in the lua_engine.
// We explicitly must tell Lua how many results we return. More on the topic:
// https://www.lua.org/manual/5.4/manual.html#lua_CFunction
enum LuaNumReturnedResults {
ZERO_RETURNED_RESULTS = 0,
};
// Prefix for logging messages coming from lua script.
const char kLuaLogTag[] = "LUA: ";
// Key for retrieving saved state from the registry.
const char* const kSavedStateKey = "saved_state";
LuaEngine::LuaEngine() {
lua_state_ = luaL_newstate();
luaL_openlibs(lua_state_);
// Register limited set of reserved methods for Lua to call native side.
lua_register(lua_state_, "log", LuaEngine::ScriptLog);
lua_register(lua_state_, "on_success", LuaEngine::OnSuccess);
lua_register(lua_state_, "on_script_finished", LuaEngine::OnScriptFinished);
lua_register(lua_state_, "on_error", LuaEngine::OnError);
lua_register(lua_state_, "on_metrics_report", LuaEngine::OnMetricsReport);
}
LuaEngine::~LuaEngine() { lua_close(lua_state_); }
// We need to insert error_handler (debug.traceback) before all arguments and function.
// Current indices (starting from top of stack):
// For example:
// debug.traceback (-1), arg2 (-2), arg1 (-3 == -n_args-1), function (-4 == -n_args-2)
// After insert (starting from top of stack): arg2 (-1), arg1 (-2 == -n_args),
// function (-3 == -n_args-1), debug.traceback (-4 == -n_args-2)
// so, we will insert error_handler at index : (-n_args - 2)
int PushLuaErrorHandler(lua_State* mLuaState, int n_args) {
lua_getglobal(mLuaState, "debug");
lua_getfield(mLuaState, -1, "traceback");
lua_remove(mLuaState, -2);
int error_handler_index = -n_args - 2;
lua_insert(mLuaState, error_handler_index);
return error_handler_index;
}
// Converts the published_data and saved_state JSON strings to Lua tables
// and pushes them onto the stack. If successful, the published_data table is at
// -2, and the saved_state table at -1. If unsuccessful, nothing is added to the
// stack.
// Returns true if both strings are successfully converted, appending
// any errors to output if not.
bool ConvertJsonToLuaTable(lua_State* lua_state, std::string published_data,
std::string saved_state,
std::vector<std::string>* output) {
// After executing the file, the stack indices and its contents are:
// -1: the json.lua table
// the rest of the stack contents
luaL_dofile(lua_state, "json.lua");
// After obtaining "decode" function from the json.lua table, the stack
// indices and its contents are:
// -1: the decode function
// -2: the json.lua table
// the rest of the stack contents
lua_getfield(lua_state, /*index=*/-1, "decode");
// After pushing the published data argument, the stack indices and its
// contents are:
// -1: published_data string
// -2: the decode function
// -3: the json.lua table
// the rest of the stack contents
lua_pushstring(lua_state, published_data.c_str());
// After this pcall, the stack indices and its contents are:
// -1: converted published_data table
// -2: the json.lua table
// the rest of the stack contents
lua_pcall(lua_state, /*nargs=*/1, /*nresults=*/1, /*msgh=*/0);
// If the top element on the stack isn't a table, it's an error string from
// json.lua specifiyng any issues from decoding (e.g. syntax)
if (!lua_istable(lua_state, lua_gettop(lua_state))) {
std::string error =
std::string(lua_tostring(lua_state, lua_gettop(lua_state)));
lua_pop(lua_state, 2);
output->push_back("Error from parsing published data: " +
error.substr(error.find(' ')) + "\n");
return false;
}
// After this insert, the stack indices and its contents are:
// -1: the json.lua table
// -2: converted published_data table
// the rest of the stack contents
lua_insert(lua_state, /*index=*/-2);
// After obtaining "decode" function from the json.lua table, the stack
// indices and its contents are:
// -1: the decode function
// -2: the json.lua table
// -3: converted published_data table
// the rest of the stack contents
lua_getfield(lua_state, /*index=*/-1, "decode");
// After pushing the saved state argument, the stack indices and its
// contents are:
// -1: saved_state string
// -2: the decode function
// -3: the json.lua table
// -4: converted published_data table
// the rest of the stack contents
lua_pushstring(lua_state, saved_state.c_str());
// After this pcall, the stack indices and its contents are:
// -1: converted saved_state table
// -2: the json.lua table
// -3: converted published_data table
// the rest of the stack contents
lua_pcall(lua_state, /*nargs=*/1, /*nresults=*/1, /*msgh=*/0);
// If the top element on the stack isn't a table, it's an error string from
// json.lua specifiyng any issues from decoding (e.g. syntax)
if (!lua_istable(lua_state, lua_gettop(lua_state))) {
std::string error =
std::string(lua_tostring(lua_state, lua_gettop(lua_state)));
lua_pop(lua_state, 3);
output->push_back("Error from parsing saved state: " +
error.substr(error.find(' ')) + "\n");
return false;
}
// After this removal, the stack indices and its contents are:
// -1: converted saved_state table
// -2: converted published_data table
// the rest of the stack contents
lua_remove(lua_state, /*index=*/-2);
return true;
}
void LuaEngine::SaveSavedStateToRegistry(lua_State* lua_state,
std::string saved_state) {
// After this push, the stack indices and its contents are:
// -1: the saved state key string
// the rest of the stack contents
// The state is saved under the key kSavedStateKey
lua_pushstring(lua_state, kSavedStateKey);
// After this push, the stack indices and its contents are:
// -1: the saved state JSON string
// -2: the saved state key string
// the rest of the stack contents
lua_pushstring(lua_state, saved_state.c_str());
// After setting the key to the value in the registry, the stack is at it's
// original state.
lua_settable(lua_state, LUA_REGISTRYINDEX);
}
void LuaEngine::ClearSavedStateInRegistry(lua_State* lua_state) {
// After this push, the stack indices and its contents are:
// -1: the saved state key string
// the rest of the stack contents
lua_pushstring(lua_state, kSavedStateKey);
// After this push, the stack indices and its contents are:
// -1: nil
// -2: the saved state key string
// the rest of the stack contents
lua_pushnil(lua_state);
// After setting the key to the value in the registry, the stack is at it's
// original state.
lua_settable(lua_state, LUA_REGISTRYINDEX);
}
std::vector<std::string> LuaEngine::ExecuteScript(std::string script_body,
std::string function_name,
std::string published_data,
std::string saved_state) {
output_.clear();
ClearSavedStateInRegistry(lua_state_);
const int load_status = luaL_dostring(lua_state_, script_body.data());
if (load_status != LUA_OK) {
const char* error = lua_tostring(lua_state_, lua_gettop(lua_state_));
lua_pop(lua_state_, lua_gettop(lua_state_));
output_.push_back(std::string("Error encountered while loading the "
"script. A possible cause could be "
"syntax errors in the script. Error: ") +
std::string(error));
return output_;
}
lua_getglobal(lua_state_, function_name.data());
const bool function_status =
lua_isfunction(lua_state_, lua_gettop(lua_state_));
if (!function_status) {
lua_pop(lua_state_, lua_gettop(lua_state_));
output_.push_back(std::string(
"Wrong function name. Provided function_name = " + function_name +
" does not correspond to any function in the provided script"));
return output_;
}
if (ConvertJsonToLuaTable(lua_state_, published_data, saved_state,
&output_)) {
// Push an error_handler to the stack (to get the stack_trace in case of any error).
// After this error_handler push, the stack indices and its contents are:
// -1: converted saved_state table
// -2: converted published_data table
// -3: the function corresponding to the function_name in the script
// -4: the error_handler
// the rest of the stack contents
int error_handler_index = PushLuaErrorHandler(lua_state_, 2);
// After lua_pcall, the function and all arguments are removed from the stack i.e. (n_args+1)
// If there is no error then lua_pcall pushes "n_results" elements to the stack.
// But in case of error, lua_pcall pushes exactly one element (error message).
// So, "error message" will be at top of the stack i.e. "-1".
// Therefore, we need to pop error_handler explicitly.
// error_handler will be at "-2" index from top of stack after lua_pcall,
// but once we pop error_message from top of stack, error_handler's new index will be "-1".
const int run_status =
lua_pcall(lua_state_, /*nargs=*/2, /*nresults=*/0, /*msgh=*/error_handler_index);
if (run_status != LUA_OK) {
const char* error = lua_tostring(lua_state_, lua_gettop(lua_state_));
lua_pop(lua_state_, lua_gettop(lua_state_)); // pop all the elements in stack.
output_.push_back(
std::string("Error encountered while running the script. "
"The returned error code = ") +
std::to_string(run_status) +
std::string(". Refer to lua.h file of Lua C API library "
"for error code definitions. Error: ") +
std::string(error));
}
}
lua_pop(lua_state_, lua_gettop(lua_state_)); // pop all the elements in stack.
return output_;
}
char** LuaEngine::StringVectorToCharArray(std::vector<std::string> vector) {
if (vector.size() == 0) {
return nullptr;
}
char** array = new char*[vector.size()];
for (unsigned int i = 0; i < vector.size(); i++) {
// Size + 1 is for the null-terminating character.
array[i] = new char[vector[i].size() + 1];
strncpy(array[i], vector[i].c_str(), vector[i].size() + 1);
}
return array;
}
std::string LuaEngine::GetSavedState() {
// After this push, the stack indices and its contents are:
// -1: the saved state key string
// the rest of the stack contents
lua_pushstring(lua_state_, kSavedStateKey);
// After obtaining the Lua value of the given key from the registry, the stack
// indices and its contents are:
// -1: the saved state JSON string (or nil if key is not assigned)
// the rest of the stack contents
lua_gettable(lua_state_, LUA_REGISTRYINDEX);
if (lua_isnil(lua_state_, lua_gettop(lua_state_))) {
// After popping the nil value from the stack, the stack is at it's
// original state.
lua_pop(lua_state_, 1);
return std::string();
}
const auto saved_state = lua_tostring(lua_state_, lua_gettop(lua_state_));
// After popping the saved state JSON string from the stack, the stack is at
// it's original state.
lua_pop(lua_state_, 1);
return saved_state;
}
// Converts the Lua table at the top of the stack to a JSON string.
// This function removes the table from the stack.
std::string ConvertTableToJson(lua_State* lua_state) {
// We re-insert the various items on the stack to keep the table
// on top (since lua function arguments must be at the top of the stack above
// the function itself).
// After executing the file, the stack indices and its contents are:
// -1: the json.lua table
// -2: the pre-converted table
// the rest of the stack contents
luaL_dofile(lua_state, "json.lua");
// After this insert, the stack indices and its contents are:
// -1: the pre-converted table
// -2: the json.lua table
// the rest of the stack contents
lua_insert(lua_state, /*index=*/-2);
// After obtaining the "encode" function from the json.lua table, the stack
// indices and its contents are:
// -1: the encode function
// -2: the pre-converted table
// -3: the json.lua table
// the rest of the stack contents
lua_getfield(lua_state, /*index=*/-2, "encode");
// After this insert, the stack indices and its contents are:
// -1: the pre-converted table
// -2: the encode function
// -3: the json.lua table
// the rest of the stack contents
lua_insert(lua_state, /*index=*/-2);
// After this pcall, the stack indices and its contents are:
// -1: the converted JSON string / json.lua error if encoding fails
// -2: the json.lua table
// the rest of the stack contents
lua_pcall(lua_state, /*nargs=*/1, /*nresults=*/1, /*msgh=*/0);
std::string json = lua_tostring(lua_state, lua_gettop(lua_state));
// After this pop, the stack contents are reverted back to it's original state
// without the pre-converted table that was previously at the top.
lua_pop(lua_state, /*num=*/2);
return json + "\n";
}
int LuaEngine::ScriptLog(lua_State* lua_state) {
std::stringstream log;
for (int i = 1; i <= lua_gettop(lua_state); i++) {
// NIL lua type cannot be coerced to string so must be explicitly checked to
// prevent errors.
if (!lua_isstring(lua_state, i)) {
output_.push_back(
std::string(kLuaLogTag) +
"One of the log arguments cannot be coerced to a string; make "
"sure that this value exists\n");
return ZERO_RETURNED_RESULTS;
}
log << lua_tostring(lua_state, i);
}
output_.push_back(kLuaLogTag + log.str() + "\n");
return ZERO_RETURNED_RESULTS;
}
int LuaEngine::OnSuccess(lua_State* lua_state) {
// Any script we run can call on_success only with a single argument of Lua
// table type.
if (lua_gettop(lua_state) != 1 ||
!lua_istable(lua_state, lua_gettop(lua_state))) {
output_.push_back(
"on_success can push only a single parameter from Lua - a Lua table\n");
return ZERO_RETURNED_RESULTS;
}
SaveSavedStateToRegistry(lua_state, ConvertTableToJson(lua_state));
return ZERO_RETURNED_RESULTS;
}
int LuaEngine::OnScriptFinished(lua_State* lua_state) {
// Any script we run can call on_script_finished only with a single argument
// of Lua table type.
if (lua_gettop(lua_state) != 1 ||
!lua_istable(lua_state, lua_gettop(lua_state))) {
output_.push_back(
"on_script_finished can push only a single parameter from Lua - a Lua "
"table\n");
return ZERO_RETURNED_RESULTS;
}
output_.push_back(ConvertTableToJson(lua_state));
return ZERO_RETURNED_RESULTS;
}
int LuaEngine::OnError(lua_State* lua_state) {
// Any script we run can call on_error only with a single argument of Lua
// string type.
if (lua_gettop(lua_state) != 1 ||
!lua_isstring(lua_state, lua_gettop(lua_state))) {
output_.push_back(
"on_error can push only a single string parameter from Lua\n");
return ZERO_RETURNED_RESULTS;
}
std::string error = lua_tostring(lua_state, lua_gettop(lua_state));
output_.push_back(error + "\n");
return ZERO_RETURNED_RESULTS;
}
int LuaEngine::OnMetricsReport(lua_State* lua_state) {
// Any script we run can call on_metrics_report with at most 2 arguments of
// Lua table type.
if (lua_gettop(lua_state) > 2 ||
!lua_istable(lua_state, lua_gettop(lua_state))) {
output_.push_back(
"on_metrics_report should push 1 to 2 parameters of Lua table type. "
"The first table is a metrics report and the second is an optional "
"state to save\n");
return ZERO_RETURNED_RESULTS;
}
const auto first_table = ConvertTableToJson(lua_state);
// If the script provided 1 argument, return now.
// gettop would be zero since the single argument is popped off the stack
// from ConvertTableToJson.
if (lua_gettop(lua_state) == 0) {
output_.push_back(first_table);
return ZERO_RETURNED_RESULTS;
}
if (!lua_istable(lua_state, lua_gettop(lua_state))) {
output_.push_back(
"on_metrics_report should push 1 to 2 parameters of Lua table type. "
"The first table is a metrics report and the second is an optional "
"state to save\n");
return ZERO_RETURNED_RESULTS;
}
const auto report = ConvertTableToJson(lua_state);
output_.push_back(report);
// If there's two tables, at index -1 would be the saved state table (since
// it's the second argument for on_metrics_report), so first_table is the
// saved state.
SaveSavedStateToRegistry(lua_state, first_table);
return ZERO_RETURNED_RESULTS;
}
extern "C" {
void FreeLuaOutput(LuaOutput* lua_output) {
for (int i = 0; i < lua_output->size; i++) {
delete[] lua_output->output[i];
}
delete[] lua_output->output;
delete[] lua_output->saved_state;
delete lua_output;
}
LuaEngine* NewLuaEngine() { return new LuaEngine(); }
LuaOutput* ExecuteScript(LuaEngine* l, char* script, char* function_name,
char* published_data, char* saved_state) {
LuaOutput* lua_engine_output = new LuaOutput();
std::vector<std::string> script_execution_output =
l->ExecuteScript(script, function_name, published_data, saved_state);
lua_engine_output->size = script_execution_output.size();
lua_engine_output->output =
LuaEngine::StringVectorToCharArray(script_execution_output);
std::string new_saved_state = l->GetSavedState();
// Size + 1 is for the null-terminating character which is included in
// c_str().
lua_engine_output->saved_state = new char[new_saved_state.size() + 1];
strncpy(lua_engine_output->saved_state, new_saved_state.c_str(),
new_saved_state.size() + 1);
return lua_engine_output;
}
} // extern "C"
} // namespace lua_interpreter