1 /*
2 * Copyright (C) 2022 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "lua_engine.h"
18
19 #include <cstring>
20 #include <sstream>
21 #include <string>
22 #include <vector>
23
24 #include "lua.hpp"
25
26 namespace lua_interpreter {
27
28 std::vector<std::string> LuaEngine::output_;
29
30 // Represents the returns of the various CFunctions in the lua_engine.
31 // We explicitly must tell Lua how many results we return. More on the topic:
32 // https://www.lua.org/manual/5.4/manual.html#lua_CFunction
33 enum LuaNumReturnedResults {
34 ZERO_RETURNED_RESULTS = 0,
35 };
36
37 // Prefix for logging messages coming from lua script.
38 const char kLuaLogTag[] = "LUA: ";
39
40 // Key for retrieving saved state from the registry.
41 const char* const kSavedStateKey = "saved_state";
42
LuaEngine()43 LuaEngine::LuaEngine() {
44 lua_state_ = luaL_newstate();
45 luaL_openlibs(lua_state_);
46
47 // Register limited set of reserved methods for Lua to call native side.
48 lua_register(lua_state_, "log", LuaEngine::ScriptLog);
49 lua_register(lua_state_, "on_success", LuaEngine::OnSuccess);
50 lua_register(lua_state_, "on_script_finished", LuaEngine::OnScriptFinished);
51 lua_register(lua_state_, "on_error", LuaEngine::OnError);
52 lua_register(lua_state_, "on_metrics_report", LuaEngine::OnMetricsReport);
53 }
54
~LuaEngine()55 LuaEngine::~LuaEngine() { lua_close(lua_state_); }
56
57 // We need to insert error_handler (debug.traceback) before all arguments and function.
58 // Current indices (starting from top of stack):
59 // For example:
60 // debug.traceback (-1), arg2 (-2), arg1 (-3 == -n_args-1), function (-4 == -n_args-2)
61 // After insert (starting from top of stack): arg2 (-1), arg1 (-2 == -n_args),
62 // function (-3 == -n_args-1), debug.traceback (-4 == -n_args-2)
63 // so, we will insert error_handler at index : (-n_args - 2)
PushLuaErrorHandler(lua_State * mLuaState,int n_args)64 int PushLuaErrorHandler(lua_State* mLuaState, int n_args) {
65 lua_getglobal(mLuaState, "debug");
66 lua_getfield(mLuaState, -1, "traceback");
67 lua_remove(mLuaState, -2);
68 int error_handler_index = -n_args - 2;
69 lua_insert(mLuaState, error_handler_index);
70 return error_handler_index;
71 }
72
73 // Converts the published_data and saved_state JSON strings to Lua tables
74 // and pushes them onto the stack. If successful, the published_data table is at
75 // -2, and the saved_state table at -1. If unsuccessful, nothing is added to the
76 // stack.
77 // Returns true if both strings are successfully converted, appending
78 // any errors to output if not.
ConvertJsonToLuaTable(lua_State * lua_state,std::string published_data,std::string saved_state,std::vector<std::string> * output)79 bool ConvertJsonToLuaTable(lua_State* lua_state, std::string published_data,
80 std::string saved_state,
81 std::vector<std::string>* output) {
82 // After executing the file, the stack indices and its contents are:
83 // -1: the json.lua table
84 // the rest of the stack contents
85 luaL_dofile(lua_state, "json.lua");
86
87 // After obtaining "decode" function from the json.lua table, the stack
88 // indices and its contents are:
89 // -1: the decode function
90 // -2: the json.lua table
91 // the rest of the stack contents
92 lua_getfield(lua_state, /*index=*/-1, "decode");
93
94 // After pushing the published data argument, the stack indices and its
95 // contents are:
96 // -1: published_data string
97 // -2: the decode function
98 // -3: the json.lua table
99 // the rest of the stack contents
100 lua_pushstring(lua_state, published_data.c_str());
101
102 // After this pcall, the stack indices and its contents are:
103 // -1: converted published_data table
104 // -2: the json.lua table
105 // the rest of the stack contents
106 lua_pcall(lua_state, /*nargs=*/1, /*nresults=*/1, /*msgh=*/0);
107
108 // If the top element on the stack isn't a table, it's an error string from
109 // json.lua specifiyng any issues from decoding (e.g. syntax)
110 if (!lua_istable(lua_state, lua_gettop(lua_state))) {
111 std::string error =
112 std::string(lua_tostring(lua_state, lua_gettop(lua_state)));
113 lua_pop(lua_state, 2);
114 output->push_back("Error from parsing published data: " +
115 error.substr(error.find(' ')) + "\n");
116 return false;
117 }
118
119 // After this insert, the stack indices and its contents are:
120 // -1: the json.lua table
121 // -2: converted published_data table
122 // the rest of the stack contents
123 lua_insert(lua_state, /*index=*/-2);
124
125 // After obtaining "decode" function from the json.lua table, the stack
126 // indices and its contents are:
127 // -1: the decode function
128 // -2: the json.lua table
129 // -3: converted published_data table
130 // the rest of the stack contents
131 lua_getfield(lua_state, /*index=*/-1, "decode");
132
133 // After pushing the saved state argument, the stack indices and its
134 // contents are:
135 // -1: saved_state string
136 // -2: the decode function
137 // -3: the json.lua table
138 // -4: converted published_data table
139 // the rest of the stack contents
140 lua_pushstring(lua_state, saved_state.c_str());
141
142 // After this pcall, the stack indices and its contents are:
143 // -1: converted saved_state table
144 // -2: the json.lua table
145 // -3: converted published_data table
146 // the rest of the stack contents
147 lua_pcall(lua_state, /*nargs=*/1, /*nresults=*/1, /*msgh=*/0);
148
149 // If the top element on the stack isn't a table, it's an error string from
150 // json.lua specifiyng any issues from decoding (e.g. syntax)
151 if (!lua_istable(lua_state, lua_gettop(lua_state))) {
152 std::string error =
153 std::string(lua_tostring(lua_state, lua_gettop(lua_state)));
154 lua_pop(lua_state, 3);
155 output->push_back("Error from parsing saved state: " +
156 error.substr(error.find(' ')) + "\n");
157 return false;
158 }
159
160 // After this removal, the stack indices and its contents are:
161 // -1: converted saved_state table
162 // -2: converted published_data table
163 // the rest of the stack contents
164 lua_remove(lua_state, /*index=*/-2);
165
166 return true;
167 }
168
SaveSavedStateToRegistry(lua_State * lua_state,std::string saved_state)169 void LuaEngine::SaveSavedStateToRegistry(lua_State* lua_state,
170 std::string saved_state) {
171 // After this push, the stack indices and its contents are:
172 // -1: the saved state key string
173 // the rest of the stack contents
174 // The state is saved under the key kSavedStateKey
175 lua_pushstring(lua_state, kSavedStateKey);
176
177 // After this push, the stack indices and its contents are:
178 // -1: the saved state JSON string
179 // -2: the saved state key string
180 // the rest of the stack contents
181 lua_pushstring(lua_state, saved_state.c_str());
182
183 // After setting the key to the value in the registry, the stack is at it's
184 // original state.
185 lua_settable(lua_state, LUA_REGISTRYINDEX);
186 }
187
ClearSavedStateInRegistry(lua_State * lua_state)188 void LuaEngine::ClearSavedStateInRegistry(lua_State* lua_state) {
189 // After this push, the stack indices and its contents are:
190 // -1: the saved state key string
191 // the rest of the stack contents
192 lua_pushstring(lua_state, kSavedStateKey);
193
194 // After this push, the stack indices and its contents are:
195 // -1: nil
196 // -2: the saved state key string
197 // the rest of the stack contents
198 lua_pushnil(lua_state);
199
200 // After setting the key to the value in the registry, the stack is at it's
201 // original state.
202 lua_settable(lua_state, LUA_REGISTRYINDEX);
203 }
204
ExecuteScript(std::string script_body,std::string function_name,std::string published_data,std::string saved_state)205 std::vector<std::string> LuaEngine::ExecuteScript(std::string script_body,
206 std::string function_name,
207 std::string published_data,
208 std::string saved_state) {
209 output_.clear();
210 ClearSavedStateInRegistry(lua_state_);
211
212 const int load_status = luaL_dostring(lua_state_, script_body.data());
213 if (load_status != LUA_OK) {
214 const char* error = lua_tostring(lua_state_, lua_gettop(lua_state_));
215 lua_pop(lua_state_, lua_gettop(lua_state_));
216 output_.push_back(std::string("Error encountered while loading the "
217 "script. A possible cause could be "
218 "syntax errors in the script. Error: ") +
219 std::string(error));
220 return output_;
221 }
222
223 lua_getglobal(lua_state_, function_name.data());
224
225 const bool function_status =
226 lua_isfunction(lua_state_, lua_gettop(lua_state_));
227 if (!function_status) {
228 lua_pop(lua_state_, lua_gettop(lua_state_));
229 output_.push_back(std::string(
230 "Wrong function name. Provided function_name = " + function_name +
231 " does not correspond to any function in the provided script"));
232 return output_;
233 }
234
235 if (ConvertJsonToLuaTable(lua_state_, published_data, saved_state,
236 &output_)) {
237 // Push an error_handler to the stack (to get the stack_trace in case of any error).
238 // After this error_handler push, the stack indices and its contents are:
239 // -1: converted saved_state table
240 // -2: converted published_data table
241 // -3: the function corresponding to the function_name in the script
242 // -4: the error_handler
243 // the rest of the stack contents
244 int error_handler_index = PushLuaErrorHandler(lua_state_, 2);
245
246 // After lua_pcall, the function and all arguments are removed from the stack i.e. (n_args+1)
247 // If there is no error then lua_pcall pushes "n_results" elements to the stack.
248 // But in case of error, lua_pcall pushes exactly one element (error message).
249 // So, "error message" will be at top of the stack i.e. "-1".
250 // Therefore, we need to pop error_handler explicitly.
251 // error_handler will be at "-2" index from top of stack after lua_pcall,
252 // but once we pop error_message from top of stack, error_handler's new index will be "-1".
253 const int run_status =
254 lua_pcall(lua_state_, /*nargs=*/2, /*nresults=*/0, /*msgh=*/error_handler_index);
255 if (run_status != LUA_OK) {
256 const char* error = lua_tostring(lua_state_, lua_gettop(lua_state_));
257 lua_pop(lua_state_, lua_gettop(lua_state_)); // pop all the elements in stack.
258 output_.push_back(
259 std::string("Error encountered while running the script. "
260 "The returned error code = ") +
261 std::to_string(run_status) +
262 std::string(". Refer to lua.h file of Lua C API library "
263 "for error code definitions. Error: ") +
264 std::string(error));
265 }
266 }
267 lua_pop(lua_state_, lua_gettop(lua_state_)); // pop all the elements in stack.
268
269 return output_;
270 }
271
StringVectorToCharArray(std::vector<std::string> vector)272 char** LuaEngine::StringVectorToCharArray(std::vector<std::string> vector) {
273 if (vector.size() == 0) {
274 return nullptr;
275 }
276
277 char** array = new char*[vector.size()];
278 for (unsigned int i = 0; i < vector.size(); i++) {
279 // Size + 1 is for the null-terminating character.
280 array[i] = new char[vector[i].size() + 1];
281 strncpy(array[i], vector[i].c_str(), vector[i].size() + 1);
282 }
283
284 return array;
285 }
286
GetSavedState()287 std::string LuaEngine::GetSavedState() {
288 // After this push, the stack indices and its contents are:
289 // -1: the saved state key string
290 // the rest of the stack contents
291 lua_pushstring(lua_state_, kSavedStateKey);
292
293 // After obtaining the Lua value of the given key from the registry, the stack
294 // indices and its contents are:
295 // -1: the saved state JSON string (or nil if key is not assigned)
296 // the rest of the stack contents
297 lua_gettable(lua_state_, LUA_REGISTRYINDEX);
298
299 if (lua_isnil(lua_state_, lua_gettop(lua_state_))) {
300 // After popping the nil value from the stack, the stack is at it's
301 // original state.
302 lua_pop(lua_state_, 1);
303 return std::string();
304 }
305
306 const auto saved_state = lua_tostring(lua_state_, lua_gettop(lua_state_));
307 // After popping the saved state JSON string from the stack, the stack is at
308 // it's original state.
309 lua_pop(lua_state_, 1);
310
311 return saved_state;
312 }
313
314 // Converts the Lua table at the top of the stack to a JSON string.
315 // This function removes the table from the stack.
ConvertTableToJson(lua_State * lua_state)316 std::string ConvertTableToJson(lua_State* lua_state) {
317 // We re-insert the various items on the stack to keep the table
318 // on top (since lua function arguments must be at the top of the stack above
319 // the function itself).
320
321 // After executing the file, the stack indices and its contents are:
322 // -1: the json.lua table
323 // -2: the pre-converted table
324 // the rest of the stack contents
325 luaL_dofile(lua_state, "json.lua");
326
327 // After this insert, the stack indices and its contents are:
328 // -1: the pre-converted table
329 // -2: the json.lua table
330 // the rest of the stack contents
331 lua_insert(lua_state, /*index=*/-2);
332
333 // After obtaining the "encode" function from the json.lua table, the stack
334 // indices and its contents are:
335 // -1: the encode function
336 // -2: the pre-converted table
337 // -3: the json.lua table
338 // the rest of the stack contents
339 lua_getfield(lua_state, /*index=*/-2, "encode");
340
341 // After this insert, the stack indices and its contents are:
342 // -1: the pre-converted table
343 // -2: the encode function
344 // -3: the json.lua table
345 // the rest of the stack contents
346 lua_insert(lua_state, /*index=*/-2);
347
348 // After this pcall, the stack indices and its contents are:
349 // -1: the converted JSON string / json.lua error if encoding fails
350 // -2: the json.lua table
351 // the rest of the stack contents
352 lua_pcall(lua_state, /*nargs=*/1, /*nresults=*/1, /*msgh=*/0);
353 std::string json = lua_tostring(lua_state, lua_gettop(lua_state));
354
355 // After this pop, the stack contents are reverted back to it's original state
356 // without the pre-converted table that was previously at the top.
357 lua_pop(lua_state, /*num=*/2);
358
359 return json + "\n";
360 }
361
ScriptLog(lua_State * lua_state)362 int LuaEngine::ScriptLog(lua_State* lua_state) {
363 std::stringstream log;
364
365 for (int i = 1; i <= lua_gettop(lua_state); i++) {
366 // NIL lua type cannot be coerced to string so must be explicitly checked to
367 // prevent errors.
368 if (!lua_isstring(lua_state, i)) {
369 output_.push_back(
370 std::string(kLuaLogTag) +
371 "One of the log arguments cannot be coerced to a string; make "
372 "sure that this value exists\n");
373 return ZERO_RETURNED_RESULTS;
374 }
375 log << lua_tostring(lua_state, i);
376 }
377
378 output_.push_back(kLuaLogTag + log.str() + "\n");
379 return ZERO_RETURNED_RESULTS;
380 }
381
OnSuccess(lua_State * lua_state)382 int LuaEngine::OnSuccess(lua_State* lua_state) {
383 // Any script we run can call on_success only with a single argument of Lua
384 // table type.
385 if (lua_gettop(lua_state) != 1 ||
386 !lua_istable(lua_state, lua_gettop(lua_state))) {
387 output_.push_back(
388 "on_success can push only a single parameter from Lua - a Lua table\n");
389 return ZERO_RETURNED_RESULTS;
390 }
391
392 SaveSavedStateToRegistry(lua_state, ConvertTableToJson(lua_state));
393
394 return ZERO_RETURNED_RESULTS;
395 }
396
OnScriptFinished(lua_State * lua_state)397 int LuaEngine::OnScriptFinished(lua_State* lua_state) {
398 // Any script we run can call on_script_finished only with a single argument
399 // of Lua table type.
400 if (lua_gettop(lua_state) != 1 ||
401 !lua_istable(lua_state, lua_gettop(lua_state))) {
402 output_.push_back(
403 "on_script_finished can push only a single parameter from Lua - a Lua "
404 "table\n");
405 return ZERO_RETURNED_RESULTS;
406 }
407
408 output_.push_back(ConvertTableToJson(lua_state));
409
410 return ZERO_RETURNED_RESULTS;
411 }
412
OnError(lua_State * lua_state)413 int LuaEngine::OnError(lua_State* lua_state) {
414 // Any script we run can call on_error only with a single argument of Lua
415 // string type.
416 if (lua_gettop(lua_state) != 1 ||
417 !lua_isstring(lua_state, lua_gettop(lua_state))) {
418 output_.push_back(
419 "on_error can push only a single string parameter from Lua\n");
420 return ZERO_RETURNED_RESULTS;
421 }
422
423 std::string error = lua_tostring(lua_state, lua_gettop(lua_state));
424 output_.push_back(error + "\n");
425
426 return ZERO_RETURNED_RESULTS;
427 }
428
OnMetricsReport(lua_State * lua_state)429 int LuaEngine::OnMetricsReport(lua_State* lua_state) {
430 // Any script we run can call on_metrics_report with at most 2 arguments of
431 // Lua table type.
432 if (lua_gettop(lua_state) > 2 ||
433 !lua_istable(lua_state, lua_gettop(lua_state))) {
434 output_.push_back(
435 "on_metrics_report should push 1 to 2 parameters of Lua table type. "
436 "The first table is a metrics report and the second is an optional "
437 "state to save\n");
438 return ZERO_RETURNED_RESULTS;
439 }
440
441 const auto first_table = ConvertTableToJson(lua_state);
442
443 // If the script provided 1 argument, return now.
444 // gettop would be zero since the single argument is popped off the stack
445 // from ConvertTableToJson.
446 if (lua_gettop(lua_state) == 0) {
447 output_.push_back(first_table);
448 return ZERO_RETURNED_RESULTS;
449 }
450
451 if (!lua_istable(lua_state, lua_gettop(lua_state))) {
452 output_.push_back(
453 "on_metrics_report should push 1 to 2 parameters of Lua table type. "
454 "The first table is a metrics report and the second is an optional "
455 "state to save\n");
456 return ZERO_RETURNED_RESULTS;
457 }
458
459 const auto report = ConvertTableToJson(lua_state);
460 output_.push_back(report);
461
462 // If there's two tables, at index -1 would be the saved state table (since
463 // it's the second argument for on_metrics_report), so first_table is the
464 // saved state.
465 SaveSavedStateToRegistry(lua_state, first_table);
466
467 return ZERO_RETURNED_RESULTS;
468 }
469
470 extern "C" {
FreeLuaOutput(LuaOutput * lua_output)471 void FreeLuaOutput(LuaOutput* lua_output) {
472 for (int i = 0; i < lua_output->size; i++) {
473 delete[] lua_output->output[i];
474 }
475 delete[] lua_output->output;
476 delete[] lua_output->saved_state;
477 delete lua_output;
478 }
479
NewLuaEngine()480 LuaEngine* NewLuaEngine() { return new LuaEngine(); }
481
ExecuteScript(LuaEngine * l,char * script,char * function_name,char * published_data,char * saved_state)482 LuaOutput* ExecuteScript(LuaEngine* l, char* script, char* function_name,
483 char* published_data, char* saved_state) {
484 LuaOutput* lua_engine_output = new LuaOutput();
485 std::vector<std::string> script_execution_output =
486 l->ExecuteScript(script, function_name, published_data, saved_state);
487 lua_engine_output->size = script_execution_output.size();
488 lua_engine_output->output =
489 LuaEngine::StringVectorToCharArray(script_execution_output);
490
491 std::string new_saved_state = l->GetSavedState();
492 // Size + 1 is for the null-terminating character which is included in
493 // c_str().
494 lua_engine_output->saved_state = new char[new_saved_state.size() + 1];
495 strncpy(lua_engine_output->saved_state, new_saved_state.c_str(),
496 new_saved_state.size() + 1);
497 return lua_engine_output;
498 }
499 } // extern "C"
500 } // namespace lua_interpreter
501