1 /*
2 * Copyright (c) 2021, 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 "LuaEngine.h"
18
19 #include "BundleWrapper.h"
20 #include "JniUtils.h"
21 #include "jni.h"
22
23 #include <android-base/logging.h>
24
25 #include <sstream>
26 #include <string>
27 #include <utility>
28 #include <vector>
29
30 extern "C" {
31 #include "lauxlib.h"
32 #include "lua.h"
33 #include "lualib.h"
34 }
35
36 namespace com {
37 namespace android {
38 namespace car {
39 namespace scriptexecutor {
40
41 namespace {
42
43 enum LuaNumReturnedResults {
44 ZERO_RETURNED_RESULTS = 0,
45 };
46
47 // Prefix for logging messages coming from lua script.
48 const char kLuaLogTag[] = "LUA: ";
49
50 } // namespace
51
52 ScriptExecutorListener* LuaEngine::sListener = nullptr;
53
LuaEngine()54 LuaEngine::LuaEngine() {
55 // Instantiate Lua environment
56 mLuaState = luaL_newstate();
57 luaL_openlibs(mLuaState);
58 }
59
~LuaEngine()60 LuaEngine::~LuaEngine() {
61 lua_close(mLuaState);
62 }
63
getLuaState()64 lua_State* LuaEngine::getLuaState() {
65 return mLuaState;
66 }
67
resetListener(ScriptExecutorListener * listener)68 void LuaEngine::resetListener(ScriptExecutorListener* listener) {
69 if (sListener != nullptr) {
70 delete sListener;
71 }
72 sListener = listener;
73 }
74
loadScript(const char * scriptBody)75 int LuaEngine::loadScript(const char* scriptBody) {
76 // As the first step in Lua script execution we want to load
77 // the body of the script into Lua stack and have it processed by Lua
78 // to catch any errors.
79 // More on luaL_dostring: https://www.lua.org/manual/5.3/manual.html#lual_dostring
80 // If error, pushes the error object into the stack.
81 const auto status = luaL_dostring(mLuaState, scriptBody);
82 if (status) {
83 // Removes error object from the stack.
84 // Lua stack must be properly maintained due to its limited size,
85 // ~20 elements and its critical function because all interaction with
86 // Lua happens via the stack.
87 // Starting read about Lua stack: https://www.lua.org/pil/24.2.html
88 const char* error = lua_tostring(mLuaState, -1);
89 lua_pop(mLuaState, 1);
90 std::ostringstream out;
91 out << "Error encountered while loading the script. A possible cause could be syntax "
92 "errors in the script. Error: "
93 << error;
94 sListener->onError(ERROR_TYPE_LUA_RUNTIME_ERROR, out.str().c_str(), "");
95 return status;
96 }
97
98 // Register limited set of reserved methods for Lua to call native side.
99 lua_register(mLuaState, "log", LuaEngine::scriptLog);
100 lua_register(mLuaState, "on_success", LuaEngine::onSuccess);
101 lua_register(mLuaState, "on_script_finished", LuaEngine::onScriptFinished);
102 lua_register(mLuaState, "on_error", LuaEngine::onError);
103 lua_register(mLuaState, "on_metrics_report", LuaEngine::onMetricsReport);
104 return status;
105 }
106
pushFunction(const char * functionName)107 int LuaEngine::pushFunction(const char* functionName) {
108 // Interaction between native code and Lua happens via Lua stack.
109 // In such model, a caller first pushes the name of the function
110 // that needs to be called, followed by the function's input
111 // arguments, one input value pushed at a time.
112 // More info: https://www.lua.org/pil/24.2.html
113 lua_getglobal(mLuaState, functionName);
114 const auto status = lua_isfunction(mLuaState, /*idx= */ -1);
115 if (status == 0) {
116 lua_pop(mLuaState, 1);
117 std::ostringstream out;
118 out << "Wrong function name. Provided functionName=" << functionName
119 << " does not correspond to any function in the provided script";
120 sListener->onError(ERROR_TYPE_LUA_RUNTIME_ERROR, out.str().c_str(), "");
121 }
122 return status;
123 }
124
run()125 int LuaEngine::run() {
126 // Performs blocking call of the provided Lua function. Assumes all
127 // input arguments are in the Lua stack as well in proper order.
128 // On how to call Lua functions: https://www.lua.org/pil/25.2.html
129 // Doc on lua_pcall: https://www.lua.org/manual/5.3/manual.html#lua_pcall
130 int n_args = 2;
131 int n_results = 0;
132
133 // pushes the "debug" on top of the stack, so now "debug" is at index -1
134 lua_getglobal(mLuaState, "debug");
135
136 // pushes "traceback" as debug[traceback] because "debug" is the value at given index -1
137 lua_getfield(mLuaState, -1, "traceback");
138
139 // removing value "debug" from stack as we only need debug.traceback which is at index -1
140 lua_remove(mLuaState, -2);
141
142 // We need to insert err_handler (debug.traceback) before all arguments and function.
143 // Current indices (starting from top of stack): debug.traceback (-1), arg2 (-2), arg1 (-3 ==
144 // -n_args-1), function (-4 == -n_args-2) After insert (starting from top of stack): arg2 (-1),
145 // arg1 (-2 == -n_args), function (-3 == -n_args-1), debug.traceback (-4 == -n_args-2) so, we
146 // will insert error_handler at index : (-n_args - 2)
147 int err_handler_index = -n_args - 2;
148 lua_insert(mLuaState, err_handler_index);
149
150 // After lua_pcall, the function and all arguments are removed from the stack i.e. (n_args+1)
151 // If there is no error then lua_pcall pushes "n_results" elements to the stack.
152 // But in case of error, lua_pcall pushes exactly one element (error message).
153 // So, "error message" will be at top of the stack i.e. "-1".
154 // Therefore, we need to pop error_handler explicitly.
155 // error_handler will be at "-2" index from top of stack after lua_pcall,
156 // but once we pop error_message from top of stack, error_handler's new index will be "-1".
157 int status = lua_pcall(mLuaState, n_args, n_results, err_handler_index);
158 if (status) {
159 const char* error = lua_tostring(mLuaState, -1);
160 std::string s = error;
161
162 std::string delimiter = "stack traceback:";
163 // because actual delimiter is "\nstack traceback:\n\t"
164 // tried using \n and \t as part of the delimiter , but it did not work.
165 // also tried \\n and \\t in the delimiter, but it did not work.
166 // so to get error_msg string, avoided the \n in front by using dpos -1
167 // and for stack_traceback, avoided \n and \t in the tail by using delimiter.length() + 2.
168 int dpos = s.find(delimiter);
169 std::string error_msg = s.substr(0, dpos - 1);
170 std::string stack_traceback = s.substr(dpos + delimiter.length() + 2);
171
172 lua_pop(mLuaState, 2); // pop top 2 elements (error message & error handler) from the stack
173 std::ostringstream out;
174 out << "Error encountered while running the script. The returned error code=" << status
175 << ". Refer to lua.h file of Lua C API library for error code definitions. Error: "
176 << error_msg.c_str();
177 sListener->onError(ERROR_TYPE_LUA_RUNTIME_ERROR, out.str().c_str(),
178 stack_traceback.c_str());
179 }
180 lua_pop(mLuaState, 1); // pop top element (error handler) from the stack.
181 return status;
182 }
183
scriptLog(lua_State * lua)184 int LuaEngine::scriptLog(lua_State* lua) {
185 const auto n = lua_gettop(lua);
186 // Loop through each argument, Lua table indices range from [1 .. N] instead of [0 .. N-1].
187 // Negative indexes are stack positions and positive indexes are argument positions.
188 for (int i = 1; i <= n; i++) {
189 const char* message = lua_tostring(lua, i);
190 LOG(INFO) << kLuaLogTag << message;
191 }
192 return ZERO_RETURNED_RESULTS;
193 }
194
onSuccess(lua_State * lua)195 int LuaEngine::onSuccess(lua_State* lua) {
196 // Any script we run can call on_success only with a single argument of Lua table type.
197 if (lua_gettop(lua) != 1 || !lua_istable(lua, /* index =*/-1)) {
198 sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR,
199 "on_success can push only a single parameter from Lua - a Lua table",
200 "");
201 return ZERO_RETURNED_RESULTS;
202 }
203
204 // Helper object to create and populate Java PersistableBundle object.
205 BundleWrapper bundleWrapper(sListener->getCurrentJNIEnv());
206 const auto status = convertLuaTableToBundle(sListener->getCurrentJNIEnv(), lua, &bundleWrapper);
207 if (!status.ok()) {
208 sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR, status.error().message().c_str(), "");
209 // We explicitly must tell Lua how many results we return, which is 0 in this case.
210 // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
211 return ZERO_RETURNED_RESULTS;
212 }
213
214 // Forward the populated Bundle object to Java callback.
215 sListener->onSuccess(bundleWrapper.getBundle());
216
217 // We explicitly must tell Lua how many results we return, which is 0 in this case.
218 // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
219 return ZERO_RETURNED_RESULTS;
220 }
221
onScriptFinished(lua_State * lua)222 int LuaEngine::onScriptFinished(lua_State* lua) {
223 // Any script we run can call on_success only with a single argument of Lua table type.
224 if (lua_gettop(lua) != 1 || !lua_istable(lua, /* index =*/-1)) {
225 sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR,
226 "on_script_finished can push only a single parameter from Lua - a Lua "
227 "table",
228 "");
229 return ZERO_RETURNED_RESULTS;
230 }
231
232 // Helper object to create and populate Java PersistableBundle object.
233 BundleWrapper bundleWrapper(sListener->getCurrentJNIEnv());
234 const auto status = convertLuaTableToBundle(sListener->getCurrentJNIEnv(), lua, &bundleWrapper);
235 if (!status.ok()) {
236 sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR, status.error().message().c_str(), "");
237 // We explicitly must tell Lua how many results we return, which is 0 in this case.
238 // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
239 return ZERO_RETURNED_RESULTS;
240 }
241
242 // Forward the populated Bundle object to Java callback.
243 sListener->onScriptFinished(bundleWrapper.getBundle());
244
245 // We explicitly must tell Lua how many results we return, which is 0 in this case.
246 // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
247 return ZERO_RETURNED_RESULTS;
248 }
249
onError(lua_State * lua)250 int LuaEngine::onError(lua_State* lua) {
251 // Any script we run can call on_error only with a single argument of Lua string type.
252 if (lua_gettop(lua) != 1 || !lua_isstring(lua, /* index = */ -1)) {
253 sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR,
254 "on_error can push only a single string parameter from Lua", "");
255 return ZERO_RETURNED_RESULTS;
256 }
257 sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR, lua_tostring(lua, /* index = */ -1),
258 /* stackTrace =*/"");
259 return ZERO_RETURNED_RESULTS;
260 }
261
onMetricsReport(lua_State * lua)262 int LuaEngine::onMetricsReport(lua_State* lua) {
263 // Any script we run can call on_metrics_report with at most 2 arguments of Lua table type.
264 if (lua_gettop(lua) > 2 || !lua_istable(lua, /* index =*/-1)) {
265 sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR,
266 "on_metrics_report should push 1 to 2 parameters of Lua table type. "
267 "The first table is a metrics report and the second is an optional "
268 "state to save",
269 "");
270 return ZERO_RETURNED_RESULTS;
271 }
272
273 // stack with 2 items: stack with 1 item:
274 // index -1: state_to_persist index -1: report
275 // index -2: report
276 // If the stack has 2 items, top of the stack is the state.
277 // If the stack only has one item, top of the stack is the report.
278
279 // Process the top of the stack. Create helper object and populate Java PersistableBundle
280 // object.
281 BundleWrapper topBundleWrapper(sListener->getCurrentJNIEnv());
282 // If the helper function succeeds, it should not change the stack
283 const auto status =
284 convertLuaTableToBundle(sListener->getCurrentJNIEnv(), lua, &topBundleWrapper);
285 if (!status.ok()) {
286 sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR, status.error().message().c_str(), "");
287 // We explicitly must tell Lua how many results we return, which is 0 in this case.
288 // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
289 return ZERO_RETURNED_RESULTS;
290 }
291
292 // If the script provided 1 argument, return now
293 if (lua_gettop(lua) == 1) {
294 sListener->onMetricsReport(topBundleWrapper.getBundle(), nullptr);
295 return ZERO_RETURNED_RESULTS;
296 }
297
298 // Otherwise the script provided a report and a state
299 // pop the state_to_persist because it has already been processed in topBundleWrapper
300 lua_pop(lua, 1);
301
302 // check that the second argument is also a table
303 if (!lua_istable(lua, /* index =*/-1)) {
304 sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR,
305 "on_metrics_report should push 1 to 2 parameters of Lua table type. "
306 "The first table is a metrics report and the second is an optional "
307 "state to save",
308 "");
309 return ZERO_RETURNED_RESULTS;
310 }
311
312 // process the report
313 BundleWrapper bottomBundleWrapper(sListener->getCurrentJNIEnv());
314 const auto statusBottom =
315 convertLuaTableToBundle(sListener->getCurrentJNIEnv(), lua, &bottomBundleWrapper);
316 if (!statusBottom.ok()) {
317 sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR, statusBottom.error().message().c_str(), "");
318 // We explicitly must tell Lua how many results we return, which is 0 in this case.
319 // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
320 return ZERO_RETURNED_RESULTS;
321 }
322
323 // Top of the stack = state, bottom of the stack = report
324 sListener->onMetricsReport(bottomBundleWrapper.getBundle(), topBundleWrapper.getBundle());
325
326 // We explicitly must tell Lua how many results we return, which is 0 in this case.
327 // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
328 return ZERO_RETURNED_RESULTS;
329 }
330
331 } // namespace scriptexecutor
332 } // namespace car
333 } // namespace android
334 } // namespace com
335