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 
21 #include <sstream>
22 #include <string>
23 #include <utility>
24 #include <vector>
25 
26 extern "C" {
27 #include "lauxlib.h"
28 #include "lua.h"
29 #include "lualib.h"
30 }
31 
32 namespace com {
33 namespace android {
34 namespace car {
35 namespace scriptexecutor {
36 
37 using ::android::base::Error;
38 using ::android::base::Result;
39 
40 namespace {
41 
42 enum LuaNumReturnedResults {
43     ZERO_RETURNED_RESULTS = 0,
44 };
45 
46 // TODO(b/199415783): Revisit the topic of limits to potentially move it to standalone file.
47 constexpr int MAX_ARRAY_SIZE = 1000;
48 
49 // Helper method that goes over Lua table fields one by one and populates PersistableBundle
50 // object wrapped in BundleWrapper.
51 // It is assumed that Lua table is located on top of the Lua stack.
52 //
53 // Returns false if the conversion encountered unrecoverable error.
54 // Otherwise, returns true for success.
55 // In case of an error, there is no need to pop elements or clean the stack. When Lua calls C,
56 // the stack used to pass data between Lua and C is private for each call. According to
57 // https://www.lua.org/pil/26.1.html, after C function returns back to Lua, Lua
58 // removes everything that is in the stack below the returned results.
59 // TODO(b/200849134): Refactor this function.
convertLuaTableToBundle(lua_State * lua,BundleWrapper * bundleWrapper)60 Result<void> convertLuaTableToBundle(lua_State* lua, BundleWrapper* bundleWrapper) {
61     // Iterate over Lua table which is expected to be at the top of Lua stack.
62     // lua_next call pops the key from the top of the stack and finds the next
63     // key-value pair. It returns 0 if the next pair was not found.
64     // More on lua_next in: https://www.lua.org/manual/5.3/manual.html#lua_next
65     lua_pushnil(lua);  // First key is a null value.
66     while (lua_next(lua, /* index = */ -2) != 0) {
67         //  'key' is at index -2 and 'value' is at index -1
68         // -1 index is the top of the stack.
69         // remove 'value' and keep 'key' for next iteration
70         // Process each key-value depending on a type and push it to Java PersistableBundle.
71         // TODO(b/199531928): Consider putting limits on key sizes as well.
72         const char* key = lua_tostring(lua, /* index = */ -2);
73         Result<void> bundleInsertionResult;
74         if (lua_isboolean(lua, /* index = */ -1)) {
75             bundleInsertionResult =
76                     bundleWrapper->putBoolean(key,
77                                               static_cast<bool>(
78                                                       lua_toboolean(lua, /* index = */ -1)));
79         } else if (lua_isinteger(lua, /* index = */ -1)) {
80             bundleInsertionResult =
81                     bundleWrapper->putLong(key,
82                                            static_cast<int64_t>(
83                                                    lua_tointeger(lua, /* index = */ -1)));
84         } else if (lua_isnumber(lua, /* index = */ -1)) {
85             bundleInsertionResult =
86                     bundleWrapper->putDouble(key,
87                                              static_cast<double>(
88                                                      lua_tonumber(lua, /* index = */ -1)));
89         } else if (lua_isstring(lua, /* index = */ -1)) {
90             // TODO(b/199415783): We need to have a limit on how long these strings could be.
91             bundleInsertionResult =
92                     bundleWrapper->putString(key, lua_tostring(lua, /* index = */ -1));
93         } else if (lua_istable(lua, /* index =*/-1)) {
94             // Lua uses tables to represent an array.
95 
96             // TODO(b/199438375): Document to users that we expect tables to be either only indexed
97             // or keyed but not both. If the table contains consecutively indexed values starting
98             // from 1, we will treat it as an array. lua_rawlen call returns the size of the indexed
99             // part. We copy this part into an array, but any keyed values in this table are
100             // ignored. There is a test that documents this current behavior. If a user wants a
101             // nested table to be represented by a PersistableBundle object, they must make sure
102             // that the nested table does not contain indexed data, including no key=1.
103             const auto kTableLength = lua_rawlen(lua, -1);
104             if (kTableLength > MAX_ARRAY_SIZE) {
105                 return Error()
106                         << "Returned table " << key << " exceeds maximum allowed size of "
107                         << MAX_ARRAY_SIZE
108                         << " elements. This key-value cannot be unpacked successfully. This error "
109                            "is unrecoverable.";
110             }
111             if (kTableLength <= 0) {
112                 return Error() << "A value with key=" << key
113                                << " appears to be a nested table that does not represent an array "
114                                   "of data. "
115                                   "Such nested tables are not supported yet. This script error is "
116                                   "unrecoverable.";
117             }
118 
119             std::vector<int64_t> longArray;
120             std::vector<std::string> stringArray;
121             int originalLuaType = LUA_TNIL;
122             for (int i = 0; i < kTableLength; i++) {
123                 lua_rawgeti(lua, -1, i + 1);
124                 // Lua allows arrays to have values of varying type. We need to force all Lua
125                 // arrays to stick to single type within the same array. We use the first value
126                 // in the array to determine the type of all values in the array that follow
127                 // after. If the second, third, etc element of the array does not match the type
128                 // of the first element we stop the extraction and return an error via a
129                 // callback.
130                 if (i == 0) {
131                     originalLuaType = lua_type(lua, /* index = */ -1);
132                 }
133                 int currentType = lua_type(lua, /* index= */ -1);
134                 if (currentType != originalLuaType) {
135                     return Error()
136                             << "Returned Lua arrays must have elements of the same type. Returned "
137                                "table with key="
138                             << key << " has the first element of type=" << originalLuaType
139                             << ", but the element at index=" << i + 1 << " has type=" << currentType
140                             << ". Integer type codes are defined in lua.h file. This error is "
141                                "unrecoverable.";
142                 }
143                 switch (currentType) {
144                     case LUA_TNUMBER:
145                         if (!lua_isinteger(lua, /* index = */ -1)) {
146                             return Error() << "Returned value for key=" << key
147                                            << " contains a floating number array, which is not "
148                                               "supported yet.";
149                         } else {
150                             longArray.push_back(lua_tointeger(lua, /* index = */ -1));
151                         }
152                         break;
153                     case LUA_TSTRING:
154                         // TODO(b/200833728): Investigate optimizations to minimize string
155                         // copying. For example, populate JNI object array one element at a
156                         // time, as we go.
157                         stringArray.push_back(lua_tostring(lua, /* index = */ -1));
158                         break;
159                     default:
160                         return Error() << "Returned value for key=" << key
161                                        << " is an array with values of type="
162                                        << lua_typename(lua, lua_type(lua, /* index = */ -1))
163                                        << ", which is not supported yet.";
164                 }
165                 lua_pop(lua, 1);
166             }
167             switch (originalLuaType) {
168                 case LUA_TNUMBER:
169                     bundleInsertionResult = bundleWrapper->putLongArray(key, longArray);
170                     break;
171                 case LUA_TSTRING:
172                     bundleInsertionResult = bundleWrapper->putStringArray(key, stringArray);
173                     break;
174             }
175         } else {
176             return Error() << "key=" << key << " has a Lua type="
177                            << lua_typename(lua, lua_type(lua, /* index = */ -1))
178                            << ", which is not supported yet.";
179         }
180         // Pop value from the stack, keep the key for the next iteration.
181         lua_pop(lua, 1);
182         // The key is at index -1, the table is at index -2 now.
183 
184         // Check if insertion of the current key-value into the bundle was successful. If not,
185         // fail-fast out of this extraction routine.
186         if (!bundleInsertionResult.ok()) {
187             return bundleInsertionResult;
188         }
189     }
190     return {};  // ok result
191 }
192 
193 }  // namespace
194 
195 ScriptExecutorListener* LuaEngine::sListener = nullptr;
196 
LuaEngine()197 LuaEngine::LuaEngine() {
198     // Instantiate Lua environment
199     mLuaState = luaL_newstate();
200     luaL_openlibs(mLuaState);
201 }
202 
~LuaEngine()203 LuaEngine::~LuaEngine() {
204     lua_close(mLuaState);
205 }
206 
getLuaState()207 lua_State* LuaEngine::getLuaState() {
208     return mLuaState;
209 }
210 
resetListener(ScriptExecutorListener * listener)211 void LuaEngine::resetListener(ScriptExecutorListener* listener) {
212     if (sListener != nullptr) {
213         delete sListener;
214     }
215     sListener = listener;
216 }
217 
loadScript(const char * scriptBody)218 int LuaEngine::loadScript(const char* scriptBody) {
219     // As the first step in Lua script execution we want to load
220     // the body of the script into Lua stack and have it processed by Lua
221     // to catch any errors.
222     // More on luaL_dostring: https://www.lua.org/manual/5.3/manual.html#lual_dostring
223     // If error, pushes the error object into the stack.
224     const auto status = luaL_dostring(mLuaState, scriptBody);
225     if (status) {
226         // Removes error object from the stack.
227         // Lua stack must be properly maintained due to its limited size,
228         // ~20 elements and its critical function because all interaction with
229         // Lua happens via the stack.
230         // Starting read about Lua stack: https://www.lua.org/pil/24.2.html
231         lua_pop(mLuaState, 1);
232         std::ostringstream out;
233         out << "Error encountered while loading the script. A possible cause could be syntax "
234                "errors in the script.";
235         sListener->onError(ERROR_TYPE_LUA_RUNTIME_ERROR, out.str().c_str(), "");
236         return status;
237     }
238 
239     // Register limited set of reserved methods for Lua to call native side.
240     lua_register(mLuaState, "on_success", LuaEngine::onSuccess);
241     lua_register(mLuaState, "on_script_finished", LuaEngine::onScriptFinished);
242     lua_register(mLuaState, "on_error", LuaEngine::onError);
243     return status;
244 }
245 
pushFunction(const char * functionName)246 int LuaEngine::pushFunction(const char* functionName) {
247     // Interaction between native code and Lua happens via Lua stack.
248     // In such model, a caller first pushes the name of the function
249     // that needs to be called, followed by the function's input
250     // arguments, one input value pushed at a time.
251     // More info: https://www.lua.org/pil/24.2.html
252     lua_getglobal(mLuaState, functionName);
253     const auto status = lua_isfunction(mLuaState, /*idx= */ -1);
254     if (status == 0) {
255         lua_pop(mLuaState, 1);
256         std::ostringstream out;
257         out << "Wrong function name. Provided functionName=" << functionName
258             << " does not correspond to any function in the provided script";
259         sListener->onError(ERROR_TYPE_LUA_RUNTIME_ERROR, out.str().c_str(), "");
260     }
261     return status;
262 }
263 
run()264 int LuaEngine::run() {
265     // Performs blocking call of the provided Lua function. Assumes all
266     // input arguments are in the Lua stack as well in proper order.
267     // On how to call Lua functions: https://www.lua.org/pil/25.2.html
268     // Doc on lua_pcall: https://www.lua.org/manual/5.3/manual.html#lua_pcall
269     int status = lua_pcall(mLuaState, /* nargs= */ 2, /* nresults= */ 0, /*errfunc= */ 0);
270     if (status) {
271         lua_pop(mLuaState, 1);  // pop the error object from the stack.
272         std::ostringstream out;
273         out << "Error encountered while running the script. The returned error code=" << status
274             << ". Refer to lua.h file of Lua C API library for error code definitions.";
275         sListener->onError(ERROR_TYPE_LUA_RUNTIME_ERROR, out.str().c_str(), "");
276     }
277     return status;
278 }
279 
onSuccess(lua_State * lua)280 int LuaEngine::onSuccess(lua_State* lua) {
281     // Any script we run can call on_success only with a single argument of Lua table type.
282     if (lua_gettop(lua) != 1 || !lua_istable(lua, /* index =*/-1)) {
283         sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR,
284                            "on_success can push only a single parameter from Lua - a Lua table",
285                            "");
286         return ZERO_RETURNED_RESULTS;
287     }
288 
289     // Helper object to create and populate Java PersistableBundle object.
290     BundleWrapper bundleWrapper(sListener->getCurrentJNIEnv());
291     const auto status = convertLuaTableToBundle(lua, &bundleWrapper);
292     if (!status.ok()) {
293         sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR, status.error().message().c_str(), "");
294         // We explicitly must tell Lua how many results we return, which is 0 in this case.
295         // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
296         return ZERO_RETURNED_RESULTS;
297     }
298 
299     // Forward the populated Bundle object to Java callback.
300     sListener->onSuccess(bundleWrapper.getBundle());
301 
302     // We explicitly must tell Lua how many results we return, which is 0 in this case.
303     // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
304     return ZERO_RETURNED_RESULTS;
305 }
306 
onScriptFinished(lua_State * lua)307 int LuaEngine::onScriptFinished(lua_State* lua) {
308     // Any script we run can call on_success only with a single argument of Lua table type.
309     if (lua_gettop(lua) != 1 || !lua_istable(lua, /* index =*/-1)) {
310         sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR,
311                            "on_script_finished can push only a single parameter from Lua - a Lua "
312                            "table",
313                            "");
314         return ZERO_RETURNED_RESULTS;
315     }
316 
317     // Helper object to create and populate Java PersistableBundle object.
318     BundleWrapper bundleWrapper(sListener->getCurrentJNIEnv());
319     const auto status = convertLuaTableToBundle(lua, &bundleWrapper);
320     if (!status.ok()) {
321         sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR, status.error().message().c_str(), "");
322         // We explicitly must tell Lua how many results we return, which is 0 in this case.
323         // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
324         return ZERO_RETURNED_RESULTS;
325     }
326 
327     // Forward the populated Bundle object to Java callback.
328     sListener->onScriptFinished(bundleWrapper.getBundle());
329 
330     // We explicitly must tell Lua how many results we return, which is 0 in this case.
331     // More on the topic: https://www.lua.org/manual/5.3/manual.html#lua_CFunction
332     return ZERO_RETURNED_RESULTS;
333 }
334 
onError(lua_State * lua)335 int LuaEngine::onError(lua_State* lua) {
336     // Any script we run can call on_error only with a single argument of Lua string type.
337     if (lua_gettop(lua) != 1 || !lua_isstring(lua, /* index = */ -1)) {
338         sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR,
339                            "on_error can push only a single string parameter from Lua", "");
340         return ZERO_RETURNED_RESULTS;
341     }
342     sListener->onError(ERROR_TYPE_LUA_SCRIPT_ERROR, lua_tostring(lua, /* index = */ -1),
343                        /* stackTrace =*/"");
344     return ZERO_RETURNED_RESULTS;
345 }
346 
347 }  // namespace scriptexecutor
348 }  // namespace car
349 }  // namespace android
350 }  // namespace com
351