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 "JniUtils.h"
18 
19 #include "nativehelper/scoped_local_ref.h"
20 
21 namespace com {
22 namespace android {
23 namespace car {
24 namespace scriptexecutor {
25 
26 // TODO(b/199415783): Revisit the topic of limits to potentially move it to standalone file.
27 constexpr int MAX_ARRAY_SIZE = 1000;
28 
pushBundleToLuaTable(JNIEnv * env,lua_State * lua,jobject bundle)29 void pushBundleToLuaTable(JNIEnv* env, lua_State* lua, jobject bundle) {
30     lua_newtable(lua);
31     // null bundle object is allowed. We will treat it as an empty table.
32     if (bundle == nullptr) {
33         return;
34     }
35 
36     // TODO(b/188832769): Consider caching some of these JNI references for
37     // performance reasons.
38     ScopedLocalRef<jclass> persistableBundleClass(env,
39                                                   env->FindClass("android/os/PersistableBundle"));
40     jmethodID getKeySetMethod =
41             env->GetMethodID(persistableBundleClass.get(), "keySet", "()Ljava/util/Set;");
42     ScopedLocalRef<jobject> keys(env, env->CallObjectMethod(bundle, getKeySetMethod));
43     ScopedLocalRef<jclass> setClass(env, env->FindClass("java/util/Set"));
44     jmethodID iteratorMethod =
45             env->GetMethodID(setClass.get(), "iterator", "()Ljava/util/Iterator;");
46     ScopedLocalRef<jobject> keySetIteratorObject(env,
47                                                  env->CallObjectMethod(keys.get(), iteratorMethod));
48 
49     ScopedLocalRef<jclass> iteratorClass(env, env->FindClass("java/util/Iterator"));
50     jmethodID hasNextMethod = env->GetMethodID(iteratorClass.get(), "hasNext", "()Z");
51     jmethodID nextMethod = env->GetMethodID(iteratorClass.get(), "next", "()Ljava/lang/Object;");
52 
53     ScopedLocalRef<jclass> booleanClass(env, env->FindClass("java/lang/Boolean"));
54     ScopedLocalRef<jclass> integerClass(env, env->FindClass("java/lang/Integer"));
55     ScopedLocalRef<jclass> longClass(env, env->FindClass("java/lang/Long"));
56     ScopedLocalRef<jclass> numberClass(env, env->FindClass("java/lang/Number"));
57     ScopedLocalRef<jclass> stringClass(env, env->FindClass("java/lang/String"));
58     ScopedLocalRef<jclass> booleanArrayClass(env, env->FindClass("[Z"));
59     ScopedLocalRef<jclass> intArrayClass(env, env->FindClass("[I"));
60     ScopedLocalRef<jclass> longArrayClass(env, env->FindClass("[J"));
61     ScopedLocalRef<jclass> doubleArrayClass(env, env->FindClass("[D"));
62     ScopedLocalRef<jclass> stringArrayClass(env, env->FindClass("[Ljava/lang/String;"));
63 
64     jmethodID getMethod = env->GetMethodID(persistableBundleClass.get(), "get",
65                                            "(Ljava/lang/String;)Ljava/lang/Object;");
66 
67     // Iterate over key set of the bundle one key at a time.
68     while (env->CallBooleanMethod(keySetIteratorObject.get(), hasNextMethod)) {
69         // Read the value object that corresponds to this key.
70         ScopedLocalRef<jstring> key(env,
71                                     (jstring)env->CallObjectMethod(keySetIteratorObject.get(),
72                                                                    nextMethod));
73         ScopedLocalRef<jobject> value(env, env->CallObjectMethod(bundle, getMethod, key.get()));
74 
75         // Get the value of the type, extract it accordingly from the bundle and
76         // push the extracted value and the key to the Lua table.
77         if (env->IsInstanceOf(value.get(), booleanClass.get())) {
78             jmethodID boolMethod = env->GetMethodID(booleanClass.get(), "booleanValue", "()Z");
79             bool boolValue = static_cast<bool>(env->CallBooleanMethod(value.get(), boolMethod));
80             lua_pushboolean(lua, boolValue);
81         } else if (env->IsInstanceOf(value.get(), integerClass.get())) {
82             jmethodID intMethod = env->GetMethodID(integerClass.get(), "intValue", "()I");
83             lua_pushinteger(lua, env->CallIntMethod(value.get(), intMethod));
84         } else if (env->IsInstanceOf(value.get(), longClass.get())) {
85             jmethodID longMethod = env->GetMethodID(longClass.get(), "longValue", "()J");
86             lua_pushinteger(lua, env->CallLongMethod(value.get(), longMethod));
87         } else if (env->IsInstanceOf(value.get(), numberClass.get())) {
88             // Condense other numeric types using one class. Because lua supports only
89             // integer or double, and we handled integer in previous if clause.
90             jmethodID numberMethod = env->GetMethodID(numberClass.get(), "doubleValue", "()D");
91             /* Pushes a double onto the stack */
92             lua_pushnumber(lua, env->CallDoubleMethod(value.get(), numberMethod));
93         } else if (env->IsInstanceOf(value.get(), stringClass.get())) {
94             // Produces a string in Modified UTF-8 encoding. Any null character
95             // inside the original string is converted into two-byte encoding.
96             // This way we can directly use the output of GetStringUTFChars in C API that
97             // expects a null-terminated string.
98             const char* rawStringValue =
99                     env->GetStringUTFChars(static_cast<jstring>(value.get()), nullptr);
100             lua_pushstring(lua, rawStringValue);
101             env->ReleaseStringUTFChars(static_cast<jstring>(value.get()), rawStringValue);
102         } else if (env->IsInstanceOf(value.get(), booleanArrayClass.get())) {
103             jbooleanArray booleanArray = static_cast<jbooleanArray>(value.get());
104             const auto kLength = env->GetArrayLength(booleanArray);
105             // Arrays are represented as a table of sequential elements in Lua.
106             // We are creating a nested table to represent this array. We specify number of elements
107             // in the Java array to preallocate memory accordingly.
108             lua_createtable(lua, kLength, 0);
109             jboolean* rawBooleanArray = env->GetBooleanArrayElements(booleanArray, nullptr);
110             // Fills in the table at stack idx -2 with key value pairs, where key is a
111             // Lua index and value is an integer from the long array at that index
112             for (int i = 0; i < kLength; i++) {
113                 lua_pushboolean(lua, rawBooleanArray[i]);
114                 lua_rawseti(lua, /* idx= */ -2,
115                             i + 1);  // lua index starts from 1
116             }
117             // JNI_ABORT is used because we do not need to copy back elements.
118             env->ReleaseBooleanArrayElements(booleanArray, rawBooleanArray, JNI_ABORT);
119         } else if (env->IsInstanceOf(value.get(), intArrayClass.get())) {
120             jintArray intArray = static_cast<jintArray>(value.get());
121             const auto kLength = env->GetArrayLength(intArray);
122             // Arrays are represented as a table of sequential elements in Lua.
123             // We are creating a nested table to represent this array. We specify number of elements
124             // in the Java array to preallocate memory accordingly.
125             lua_createtable(lua, kLength, 0);
126             jint* rawIntArray = env->GetIntArrayElements(intArray, nullptr);
127             // Fills in the table at stack idx -2 with key value pairs, where key is a
128             // Lua index and value is an integer from the int array at that index
129             for (int i = 0; i < kLength; i++) {
130                 // Stack at index -1 is rawIntArray[i] after this push.
131                 lua_pushinteger(lua, rawIntArray[i]);
132                 lua_rawseti(lua, /* idx= */ -2,
133                             i + 1);  // lua index starts from 1
134             }
135             // JNI_ABORT is used because we do not need to copy back elements.
136             env->ReleaseIntArrayElements(intArray, rawIntArray, JNI_ABORT);
137         } else if (env->IsInstanceOf(value.get(), longArrayClass.get())) {
138             jlongArray longArray = static_cast<jlongArray>(value.get());
139             const auto kLength = env->GetArrayLength(longArray);
140             // Arrays are represented as a table of sequential elements in Lua.
141             // We are creating a nested table to represent this array. We specify number of elements
142             // in the Java array to preallocate memory accordingly.
143             lua_createtable(lua, kLength, 0);
144             jlong* rawLongArray = env->GetLongArrayElements(longArray, nullptr);
145             // Fills in the table at stack idx -2 with key value pairs, where key is a
146             // Lua index and value is an integer from the long array at that index
147             for (int i = 0; i < kLength; i++) {
148                 lua_pushinteger(lua, rawLongArray[i]);
149                 lua_rawseti(lua, /* idx= */ -2,
150                             i + 1);  // lua index starts from 1
151             }
152             // JNI_ABORT is used because we do not need to copy back elements.
153             env->ReleaseLongArrayElements(longArray, rawLongArray, JNI_ABORT);
154         } else if (env->IsInstanceOf(value.get(), doubleArrayClass.get())) {
155             jdoubleArray doubleArray = static_cast<jdoubleArray>(value.get());
156             const auto kLength = env->GetArrayLength(doubleArray);
157             // Arrays are represented as a table of sequential elements in Lua.
158             // We are creating a nested table to represent this array. We specify number of elements
159             // in the Java array to preallocate memory accordingly.
160             lua_createtable(lua, kLength, 0);
161             jdouble* rawDoubleArray = env->GetDoubleArrayElements(doubleArray, nullptr);
162             // Fills in the table at stack idx -2 with key value pairs, where key is a
163             // Lua index and value is an double from the double array at that index
164             for (int i = 0; i < kLength; i++) {
165                 lua_pushnumber(lua, rawDoubleArray[i]);
166                 lua_rawseti(lua, /* idx= */ -2,
167                             i + 1);  // lua index starts from 1
168             }
169             env->ReleaseDoubleArrayElements(doubleArray, rawDoubleArray, JNI_ABORT);
170         } else if (env->IsInstanceOf(value.get(), stringArrayClass.get())) {
171             jobjectArray stringArray = static_cast<jobjectArray>(value.get());
172             const auto kLength = env->GetArrayLength(stringArray);
173             // Arrays are represented as a table of sequential elements in Lua.
174             // We are creating a nested table to represent this array. We specify number of elements
175             // in the Java array to preallocate memory accordingly.
176             lua_createtable(lua, kLength, 0);
177             // Fills in the table at stack idx -2 with key value pairs, where key is a Lua index and
178             // value is an string value extracted from the object array at that index
179             for (int i = 0; i < kLength; i++) {
180                 ScopedLocalRef<jobject> localStringRef(env,
181                                                        env->GetObjectArrayElement(stringArray, i));
182                 jstring element = static_cast<jstring>(localStringRef.get());
183                 const char* rawStringValue = env->GetStringUTFChars(element, nullptr);
184                 lua_pushstring(lua, rawStringValue);
185                 env->ReleaseStringUTFChars(element, rawStringValue);
186                 // lua index starts from 1
187                 lua_rawseti(lua, /* idx= */ -2, i + 1);
188             }
189         } else if (env->IsInstanceOf(value.get(), persistableBundleClass.get())) {
190             jobject bundle = static_cast<jobject>(value.get());
191             // After this call, the lua stack will have 1 new item at the top of the stack: a table
192             // representing the PersistableBundle
193             pushBundleToLuaTable(env, lua, bundle);
194         } else {
195             // Other types are not implemented yet, skipping.
196             continue;
197         }
198 
199         const char* rawKey = env->GetStringUTFChars(key.get(), nullptr);
200         // table[rawKey] = value, where value is on top of the stack,
201         // and the table is the next element in the stack.
202         lua_setfield(lua, /* idx= */ -2, rawKey);
203         env->ReleaseStringUTFChars(key.get(), rawKey);
204     }
205 }
206 
pushBundleListToLuaTable(JNIEnv * env,lua_State * lua,jobject bundleList)207 void pushBundleListToLuaTable(JNIEnv* env, lua_State* lua, jobject bundleList) {
208     // Creates a new table as the encompassing array to contain the converted bundles.
209     // Pushed to top of stack.
210     lua_newtable(lua);
211 
212     ScopedLocalRef<jclass> listClass(env, env->FindClass("java/util/List"));
213     jmethodID sizeMethod = env->GetMethodID(listClass.get(), "size", "()I");
214     jmethodID getMethod = env->GetMethodID(listClass.get(), "get", "(I)Ljava/lang/Object;");
215 
216     const auto listSize = env->CallIntMethod(bundleList, sizeMethod);
217     // For each bundle in the bundleList set a converted Lua table into the table array.
218     for (int i = 0; i < listSize; i++) {
219         // Push to stack the index at which the next Lua table will be at. Lua index start at 1.
220         lua_pushnumber(lua, i + 1);
221         // Convert the bundle at i into Lua table and push to top of stack.
222         pushBundleToLuaTable(env, lua, env->CallObjectMethod(bundleList, getMethod, i));
223         // table[k] = v, table should be at the given index (-3), and expects v the value to be
224         // at the top of the stack, and k the key to be just below the top.
225         lua_settable(lua, /* idx= */ -3);
226     }
227 }
228 
convertLuaTableToBundle(JNIEnv * env,lua_State * lua,BundleWrapper * bundleWrapper)229 Result<void> convertLuaTableToBundle(JNIEnv* env, lua_State* lua, BundleWrapper* bundleWrapper) {
230     // Iterate over Lua table which is expected to be at the top of Lua stack.
231     // lua_next call pops the key from the top of the stack and finds the next
232     // key-value pair. It returns 0 if the next pair was not found.
233     // More on lua_next in: https://www.lua.org/manual/5.3/manual.html#lua_next
234     lua_pushnil(lua);  // First key is a null value, at index -1
235     while (lua_next(lua, /* table index = */ -2) != 0) {
236         //  'key' is at index -2 and 'value' is at index -1
237         // -1 index is the top of the stack.
238         // remove 'value' and keep 'key' for next iteration
239         // Process each key-value depending on a type and push it to Java PersistableBundle.
240         // TODO(b/199531928): Consider putting limits on key sizes as well.
241         const char* key = lua_tostring(lua, /* index = */ -2);
242         Result<void> bundleInsertionResult;
243         if (lua_isboolean(lua, /* index = */ -1)) {
244             bundleInsertionResult =
245                     bundleWrapper->putBoolean(key,
246                                               static_cast<bool>(
247                                                       lua_toboolean(lua, /* index = */ -1)));
248         } else if (lua_isinteger(lua, /* index = */ -1)) {
249             bundleInsertionResult =
250                     bundleWrapper->putLong(key,
251                                            static_cast<int64_t>(
252                                                    lua_tointeger(lua, /* index = */ -1)));
253         } else if (lua_isnumber(lua, /* index = */ -1)) {
254             bundleInsertionResult =
255                     bundleWrapper->putDouble(key,
256                                              static_cast<double>(
257                                                      lua_tonumber(lua, /* index = */ -1)));
258         } else if (lua_isstring(lua, /* index = */ -1)) {
259             // TODO(b/199415783): We need to have a limit on how long these strings could be.
260             bundleInsertionResult =
261                     bundleWrapper->putString(key, lua_tostring(lua, /* index = */ -1));
262         } else if (lua_istable(lua, /* index =*/-1) && lua_rawlen(lua, -1) > 0) {
263             // Lua uses tables to represent arrays and PersistableBundles.
264             // If lua_rawlen is greater than 0, this table is a sequence, which means it is an
265             // array.
266 
267             // TODO(b/199438375): Document to users that we expect tables to be either only indexed
268             // or keyed but not both. If the table contains consecutively indexed values starting
269             // from 1, we will treat it as an array. lua_rawlen call returns the size of the indexed
270             // part. We copy this part into an array, but any keyed values in this table are
271             // ignored. There is a test that documents this current behavior. If a user wants a
272             // nested table to be represented by a PersistableBundle object, they must make sure
273             // that the nested table does not contain indexed data, including no key=1.
274             const auto kTableLength = lua_rawlen(lua, -1);
275             if (kTableLength > MAX_ARRAY_SIZE) {
276                 return Error()
277                         << "Returned table " << key << " exceeds maximum allowed size of "
278                         << MAX_ARRAY_SIZE
279                         << " elements. This key-value cannot be unpacked successfully. This error "
280                            "is unrecoverable.";
281             }
282 
283             std::vector<unsigned char> boolArray;
284             std::vector<double> doubleArray;
285             std::vector<int64_t> longArray;
286             std::vector<std::string> stringArray;
287             int originalLuaType = LUA_TNIL;
288             for (int i = 0; i < kTableLength; i++) {
289                 lua_rawgeti(lua, -1, i + 1);
290                 // Lua allows arrays to have values of varying type. We need to force all Lua
291                 // arrays to stick to single type within the same array. We use the first value
292                 // in the array to determine the type of all values in the array that follow
293                 // after. If the second, third, etc element of the array does not match the type
294                 // of the first element we stop the extraction and return an error via a
295                 // callback.
296                 if (i == 0) {
297                     originalLuaType = lua_type(lua, /* index = */ -1);
298                 }
299                 int currentType = lua_type(lua, /* index= */ -1);
300                 if (currentType != originalLuaType) {
301                     return Error()
302                             << "Returned Lua arrays must have elements of the same type. Returned "
303                                "table with key="
304                             << key << " has the first element of type=" << originalLuaType
305                             << ", but the element at index=" << i + 1 << " has type=" << currentType
306                             << ". Integer type codes are defined in lua.h file. This error is "
307                                "unrecoverable.";
308                 }
309                 switch (currentType) {
310                     case LUA_TBOOLEAN:
311                         boolArray.push_back(
312                                 static_cast<unsigned char>(lua_toboolean(lua, /* index = */ -1)));
313                         break;
314                     case LUA_TNUMBER:
315                         if (lua_isinteger(lua, /* index = */ -1)) {
316                             longArray.push_back(lua_tointeger(lua, /* index = */ -1));
317                         } else {
318                             doubleArray.push_back(lua_tonumber(lua, /* index = */ -1));
319                         }
320                         break;
321                     case LUA_TSTRING:
322                         // TODO(b/200833728): Investigate optimizations to minimize string
323                         // copying. For example, populate JNI object array one element at a
324                         // time, as we go.
325                         stringArray.push_back(lua_tostring(lua, /* index = */ -1));
326                         break;
327                     default:
328                         return Error() << "Returned value for key=" << key
329                                        << " is an array with values of type="
330                                        << lua_typename(lua, lua_type(lua, /* index = */ -1))
331                                        << ", which is not supported yet.";
332                 }
333                 lua_pop(lua, 1);
334             }
335             switch (originalLuaType) {
336                 case LUA_TBOOLEAN:
337                     bundleInsertionResult = bundleWrapper->putBooleanArray(key, boolArray);
338                     break;
339                 case LUA_TNUMBER:
340                     if (longArray.size() > 0) {
341                         bundleInsertionResult = bundleWrapper->putLongArray(key, longArray);
342                     } else {
343                         bundleInsertionResult = bundleWrapper->putDoubleArray(key, doubleArray);
344                     }
345                     break;
346                 case LUA_TSTRING:
347                     bundleInsertionResult = bundleWrapper->putStringArray(key, stringArray);
348                     break;
349             }
350         } else if (lua_istable(lua, /* index =*/-1)) {
351             // If the Lua table is not a sequence, i.e., it is a table with string keys, then it is
352             // a PersistableBundle
353             BundleWrapper nestedBundleWrapper(env);
354             // After this line, the Lua stack is unchanged, so the top of the stack is still a
355             // table, but the nestedBundleWrapper will be populated
356             const auto status = convertLuaTableToBundle(env, lua, &nestedBundleWrapper);
357             if (!status.ok()) {
358                 return Error() << "Failed to parse nested tables into nested PersistableBundles";
359             }
360             bundleInsertionResult = bundleWrapper->putPersistableBundle(key, nestedBundleWrapper);
361         } else {
362             return Error() << "key=" << key << " has a Lua type="
363                            << lua_typename(lua, lua_type(lua, /* index = */ -1))
364                            << ", which is not supported yet.";
365         }
366         // Pop value from the stack, keep the key for the next iteration.
367         lua_pop(lua, 1);
368         // The key is at index -1, the table is at index -2 now.
369 
370         // Check if insertion of the current key-value into the bundle was successful. If not,
371         // fail-fast out of this extraction routine.
372         if (!bundleInsertionResult.ok()) {
373             return bundleInsertionResult;
374         }
375     }
376     return {};  // ok result
377 }
378 
379 }  // namespace scriptexecutor
380 }  // namespace car
381 }  // namespace android
382 }  // namespace com
383