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