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 #include "LuaEngine.h"
19 #include "ScriptExecutorListener.h"
20 #include "jni.h"
21
22 #include <android-base/logging.h>
23
24 #include <cstdint>
25
26 namespace android {
27 namespace automotive {
28 namespace telemetry {
29 namespace script_executor {
30
31 extern "C" {
32
33 JNIEXPORT jlong JNICALL
Java_com_android_car_telemetry_ScriptExecutor_nativeInitLuaEngine(JNIEnv * env,jobject object)34 Java_com_android_car_telemetry_ScriptExecutor_nativeInitLuaEngine(JNIEnv* env, jobject object) {
35 // Cast first to intptr_t to ensure int can hold the pointer without loss.
36 return static_cast<jlong>(reinterpret_cast<intptr_t>(new LuaEngine()));
37 }
38
Java_com_android_car_telemetry_ScriptExecutor_nativeDestroyLuaEngine(JNIEnv * env,jobject object,jlong luaEnginePtr)39 JNIEXPORT void JNICALL Java_com_android_car_telemetry_ScriptExecutor_nativeDestroyLuaEngine(
40 JNIEnv* env, jobject object, jlong luaEnginePtr) {
41 delete reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
42 }
43
44 // Parses the inputs and loads them to Lua one at a time.
45 // Loading of data into Lua also triggers checks on Lua side to verify the
46 // inputs are valid. For example, pushing "functionName" into Lua stack verifies
47 // that the function name actually exists in the previously loaded body of the
48 // script.
49 //
50 // The steps are:
51 // Step 1: Parse the inputs for obvious programming errors.
52 // Step 2: Parse and load the body of the script.
53 // Step 3: Parse and push function name we want to execute in the provided
54 // script body to Lua stack. If the function name doesn't exist, we exit.
55 // Step 4: Parse publishedData, convert it into Lua table and push it to the
56 // stack.
57 // Step 5: Parse savedState Bundle object, convert it into Lua table and push it
58 // to the stack.
59 // Any errors that occur at the stage above result in quick exit or crash.
60 //
61 // All interaction with Lua happens via Lua stack. Therefore, order of how the
62 // inputs are parsed and processed is critical because Lua API methods such as
63 // lua_pcall assume specific order between function name and the input arguments
64 // on the stack.
65 // More information about how to work with Lua stack: https://www.lua.org/pil/24.2.html
66 // and how Lua functions are called via Lua API: https://www.lua.org/pil/25.2.html
67 //
68 // Finally, once parsing and pushing to Lua stack is complete, we do
69 //
70 // Step 6: attempt to run the provided function.
Java_com_android_car_telemetry_ScriptExecutor_nativeInvokeScript(JNIEnv * env,jobject object,jlong luaEnginePtr,jstring scriptBody,jstring functionName,jobject publishedData,jobject savedState,jobject listener)71 JNIEXPORT void JNICALL Java_com_android_car_telemetry_ScriptExecutor_nativeInvokeScript(
72 JNIEnv* env, jobject object, jlong luaEnginePtr, jstring scriptBody, jstring functionName,
73 jobject publishedData, jobject savedState, jobject listener) {
74 if (!luaEnginePtr) {
75 env->FatalError("luaEnginePtr parameter cannot be nil");
76 }
77 if (scriptBody == nullptr) {
78 env->FatalError("scriptBody parameter cannot be null");
79 }
80 if (functionName == nullptr) {
81 env->FatalError("functionName parameter cannot be null");
82 }
83 if (listener == nullptr) {
84 env->FatalError("listener parameter cannot be null");
85 }
86
87 LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
88
89 // Load and parse the script
90 const char* scriptStr = env->GetStringUTFChars(scriptBody, nullptr);
91 auto status = engine->LoadScript(scriptStr);
92 env->ReleaseStringUTFChars(scriptBody, scriptStr);
93 // status == 0 if the script loads successfully.
94 if (status) {
95 env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
96 "Failed to load the script.");
97 return;
98 }
99 engine->ResetListener(new ScriptExecutorListener(env, listener));
100
101 // Push the function name we want to invoke to Lua stack
102 const char* functionNameStr = env->GetStringUTFChars(functionName, nullptr);
103 status = engine->PushFunction(functionNameStr);
104 env->ReleaseStringUTFChars(functionName, functionNameStr);
105 // status == 1 if the name is indeed a function.
106 if (!status) {
107 env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
108 "symbol functionName does not correspond to a function.");
109 return;
110 }
111
112 // TODO(b/189241508): Provide implementation to parse publishedData input,
113 // convert it into Lua table and push into Lua stack.
114 if (publishedData) {
115 env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
116 "Parsing of publishedData is not implemented yet.");
117 return;
118 }
119
120 // Unpack bundle in savedState, convert to Lua table and push it to Lua
121 // stack.
122 PushBundleToLuaTable(env, engine, savedState);
123
124 // Execute the function. This will block until complete or error.
125 if (engine->Run()) {
126 env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
127 "Runtime error occurred while running the function.");
128 return;
129 }
130 }
131
132 } // extern "C"
133
134 } // namespace script_executor
135 } // namespace telemetry
136 } // namespace automotive
137 } // namespace android
138