aboutsummaryrefslogtreecommitdiff
path: root/cpp/telemetry
diff options
context:
space:
mode:
authorMax Dashouk <mdashouk@google.com>2021-06-30 17:43:54 +0000
committerMax Dashouk <mdashouk@google.com>2021-07-02 12:25:25 -0700
commit2c013dbf7bf922dd4eb63ae7c78b338048d151c6 (patch)
tree6a4b160e90a7a8665e10ebf0a37a8c11b22716a9 /cpp/telemetry
parent0c89bf8c456ca458eb88233a43a1530672c0ca9f (diff)
downloadCar-2c013dbf7bf922dd4eb63ae7c78b338048d151c6.tar.gz
Revert "Revert "Create minimal ScriptExecutor java class to invoke script.""
This reverts commit e59944126c6df9fc9d4fe8a09b691c292d285939. Reason for revert: Changing build rules Test: manual build of git_sc-dev-plus-aosp that failed Bug: b/183422851#comment19 Change-Id: Ic7d5323254f3e908b04a3d5ce0573f8a14977646
Diffstat (limited to 'cpp/telemetry')
-rw-r--r--cpp/telemetry/script_executor/Android.bp15
-rw-r--r--cpp/telemetry/script_executor/src/LuaEngine.cpp48
-rw-r--r--cpp/telemetry/script_executor/src/LuaEngine.h20
-rw-r--r--cpp/telemetry/script_executor/src/ScriptExecutorJni.cpp137
-rw-r--r--cpp/telemetry/script_executor/src/ScriptExecutorListener.cpp12
-rw-r--r--cpp/telemetry/script_executor/src/ScriptExecutorListener.h10
6 files changed, 242 insertions, 0 deletions
diff --git a/cpp/telemetry/script_executor/Android.bp b/cpp/telemetry/script_executor/Android.bp
index 78b639a44a..6eaec62261 100644
--- a/cpp/telemetry/script_executor/Android.bp
+++ b/cpp/telemetry/script_executor/Android.bp
@@ -39,6 +39,7 @@ cc_library {
"src/ScriptExecutorListener.cpp",
],
shared_libs: [
+ "libandroid_runtime",
"libnativehelper",
],
// Allow dependents to use the header files.
@@ -60,3 +61,17 @@ cc_library_shared {
"libscriptexecutor",
],
}
+
+cc_library {
+ name: "libscriptexecutorjni",
+ defaults: [
+ "scriptexecutor_defaults",
+ ],
+ srcs: [
+ "src/ScriptExecutorJni.cpp",
+ ],
+ shared_libs: [
+ "libnativehelper",
+ "libscriptexecutor",
+ ],
+}
diff --git a/cpp/telemetry/script_executor/src/LuaEngine.cpp b/cpp/telemetry/script_executor/src/LuaEngine.cpp
index cc1d0b8f62..1a074f2b3e 100644
--- a/cpp/telemetry/script_executor/src/LuaEngine.cpp
+++ b/cpp/telemetry/script_executor/src/LuaEngine.cpp
@@ -29,6 +29,7 @@ namespace telemetry {
namespace script_executor {
LuaEngine::LuaEngine() {
+ // Instantiate Lua environment
mLuaState = luaL_newstate();
luaL_openlibs(mLuaState);
}
@@ -41,6 +42,53 @@ lua_State* LuaEngine::GetLuaState() {
return mLuaState;
}
+void LuaEngine::ResetListener(ScriptExecutorListener* listener) {
+ mListener.reset(listener);
+}
+
+int LuaEngine::LoadScript(const char* scriptBody) {
+ // As the first step in Lua script execution we want to load
+ // the body of the script into Lua stack and have it processed by Lua
+ // to catch any errors.
+ // More on luaL_dostring: https://www.lua.org/manual/5.3/manual.html#lual_dostring
+ // If error, pushes the error object into the stack.
+ const auto status = luaL_dostring(mLuaState, scriptBody);
+ if (status) {
+ // Removes error object from the stack.
+ // Lua stack must be properly maintained due to its limited size,
+ // ~20 elements and its critical function because all interaction with
+ // Lua happens via the stack.
+ // Starting read about Lua stack: https://www.lua.org/pil/24.2.html
+ // TODO(b/192284232): add test case to trigger this.
+ lua_pop(mLuaState, 1);
+ }
+ return status;
+}
+
+bool LuaEngine::PushFunction(const char* functionName) {
+ // Interaction between native code and Lua happens via Lua stack.
+ // In such model, a caller first pushes the name of the function
+ // that needs to be called, followed by the function's input
+ // arguments, one input value pushed at a time.
+ // More info: https://www.lua.org/pil/24.2.html
+ lua_getglobal(mLuaState, functionName);
+ const auto status = lua_isfunction(mLuaState, /*idx= */ -1);
+ // TODO(b/192284785): add test case for wrong function name in Lua.
+ if (status == 0) lua_pop(mLuaState, 1);
+ return status;
+}
+
+int LuaEngine::Run() {
+ // Performs blocking call of the provided Lua function. Assumes all
+ // input arguments are in the Lua stack as well in proper order.
+ // On how to call Lua functions: https://www.lua.org/pil/25.2.html
+ // Doc on lua_pcall: https://www.lua.org/manual/5.3/manual.html#lua_pcall
+ // TODO(b/189241508): Once we implement publishedData parsing, nargs should
+ // change from 1 to 2.
+ // TODO(b/192284612): add test case for failed call.
+ return lua_pcall(mLuaState, /* nargs= */ 1, /* nresults= */ 0, /*errfunc= */ 0);
+}
+
} // namespace script_executor
} // namespace telemetry
} // namespace automotive
diff --git a/cpp/telemetry/script_executor/src/LuaEngine.h b/cpp/telemetry/script_executor/src/LuaEngine.h
index a0f3978079..a1d6e48c4e 100644
--- a/cpp/telemetry/script_executor/src/LuaEngine.h
+++ b/cpp/telemetry/script_executor/src/LuaEngine.h
@@ -40,8 +40,28 @@ public:
// Returns pointer to Lua state object.
lua_State* GetLuaState();
+ // Loads Lua script provided as scriptBody string.
+ // Returns 0 if successful. Otherwise returns non-zero Lua error code.
+ int LoadScript(const char* scriptBody);
+
+ // Pushes a Lua function under provided name into the stack.
+ // Returns true if successful.
+ bool PushFunction(const char* functionName);
+
+ // Invokes function with the inputs provided in the stack.
+ // Assumes that the script body has been already loaded and successully
+ // compiled and run, and all input arguments, and the function have been
+ // pushed to the stack.
+ // Returns 0 if successful. Otherwise returns non-zero Lua error code.
+ int Run();
+
+ // Updates stored listener and destroys the previous one.
+ void ResetListener(ScriptExecutorListener* listener);
+
private:
lua_State* mLuaState; // owned
+
+ std::unique_ptr<ScriptExecutorListener> mListener;
};
} // namespace script_executor
diff --git a/cpp/telemetry/script_executor/src/ScriptExecutorJni.cpp b/cpp/telemetry/script_executor/src/ScriptExecutorJni.cpp
new file mode 100644
index 0000000000..500b8e2384
--- /dev/null
+++ b/cpp/telemetry/script_executor/src/ScriptExecutorJni.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "JniUtils.h"
+#include "LuaEngine.h"
+#include "ScriptExecutorListener.h"
+#include "jni.h"
+
+#include <android-base/logging.h>
+
+#include <cstdint>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+namespace script_executor {
+
+extern "C" {
+
+JNIEXPORT jlong JNICALL
+Java_com_android_car_telemetry_ScriptExecutor_nativeInitLuaEngine(JNIEnv* env, jobject object) {
+ // Cast first to intptr_t to ensure int can hold the pointer without loss.
+ return static_cast<jlong>(reinterpret_cast<intptr_t>(new LuaEngine()));
+}
+
+JNIEXPORT void JNICALL Java_com_android_car_telemetry_ScriptExecutor_nativeDestroyLuaEngine(
+ JNIEnv* env, jobject object, jlong luaEnginePtr) {
+ delete reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+}
+
+// Parses the inputs and loads them to Lua one at a time.
+// Loading of data into Lua also triggers checks on Lua side to verify the
+// inputs are valid. For example, pushing "functionName" into Lua stack verifies
+// that the function name actually exists in the previously loaded body of the
+// script.
+//
+// The steps are:
+// Step 1: Parse the inputs for obvious programming errors.
+// Step 2: Parse and load the body of the script.
+// Step 3: Parse and push function name we want to execute in the provided
+// script body to Lua stack. If the function name doesn't exist, we exit.
+// Step 4: Parse publishedData, convert it into Lua table and push it to the
+// stack.
+// Step 5: Parse savedState Bundle object, convert it into Lua table and push it
+// to the stack.
+// Any errors that occur at the stage above result in quick exit or crash.
+//
+// All interaction with Lua happens via Lua stack. Therefore, order of how the
+// inputs are parsed and processed is critical because Lua API methods such as
+// lua_pcall assume specific order between function name and the input arguments
+// on the stack.
+// More information about how to work with Lua stack: https://www.lua.org/pil/24.2.html
+// and how Lua functions are called via Lua API: https://www.lua.org/pil/25.2.html
+//
+// Finally, once parsing and pushing to Lua stack is complete, we do
+//
+// Step 6: attempt to run the provided function.
+JNIEXPORT void JNICALL Java_com_android_car_telemetry_ScriptExecutor_nativeInvokeScript(
+ JNIEnv* env, jobject object, jlong luaEnginePtr, jstring scriptBody, jstring functionName,
+ jobject publishedData, jobject savedState, jobject listener) {
+ if (!luaEnginePtr) {
+ env->FatalError("luaEnginePtr parameter cannot be nil");
+ }
+ if (scriptBody == nullptr) {
+ env->FatalError("scriptBody parameter cannot be null");
+ }
+ if (functionName == nullptr) {
+ env->FatalError("functionName parameter cannot be null");
+ }
+ if (listener == nullptr) {
+ env->FatalError("listener parameter cannot be null");
+ }
+
+ LuaEngine* engine = reinterpret_cast<LuaEngine*>(static_cast<intptr_t>(luaEnginePtr));
+
+ // Load and parse the script
+ const char* scriptStr = env->GetStringUTFChars(scriptBody, nullptr);
+ auto status = engine->LoadScript(scriptStr);
+ env->ReleaseStringUTFChars(scriptBody, scriptStr);
+ // status == 0 if the script loads successfully.
+ if (status) {
+ env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
+ "Failed to load the script.");
+ return;
+ }
+ engine->ResetListener(new ScriptExecutorListener(env, listener));
+
+ // Push the function name we want to invoke to Lua stack
+ const char* functionNameStr = env->GetStringUTFChars(functionName, nullptr);
+ status = engine->PushFunction(functionNameStr);
+ env->ReleaseStringUTFChars(functionName, functionNameStr);
+ // status == 1 if the name is indeed a function.
+ if (!status) {
+ env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
+ "symbol functionName does not correspond to a function.");
+ return;
+ }
+
+ // TODO(b/189241508): Provide implementation to parse publishedData input,
+ // convert it into Lua table and push into Lua stack.
+ if (publishedData) {
+ env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
+ "Parsing of publishedData is not implemented yet.");
+ return;
+ }
+
+ // Unpack bundle in savedState, convert to Lua table and push it to Lua
+ // stack.
+ PushBundleToLuaTable(env, engine, savedState);
+
+ // Execute the function. This will block until complete or error.
+ if (engine->Run()) {
+ env->ThrowNew(env->FindClass("java/lang/RuntimeException"),
+ "Runtime error occurred while running the function.");
+ return;
+ }
+}
+
+} // extern "C"
+
+} // namespace script_executor
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/script_executor/src/ScriptExecutorListener.cpp b/cpp/telemetry/script_executor/src/ScriptExecutorListener.cpp
index 8a46253bf5..8c10aa4e78 100644
--- a/cpp/telemetry/script_executor/src/ScriptExecutorListener.cpp
+++ b/cpp/telemetry/script_executor/src/ScriptExecutorListener.cpp
@@ -17,12 +17,24 @@
#include "ScriptExecutorListener.h"
#include <android-base/logging.h>
+#include <android_runtime/AndroidRuntime.h>
namespace android {
namespace automotive {
namespace telemetry {
namespace script_executor {
+ScriptExecutorListener::~ScriptExecutorListener() {
+ if (mScriptExecutorListener != NULL) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(mScriptExecutorListener);
+ }
+}
+
+ScriptExecutorListener::ScriptExecutorListener(JNIEnv* env, jobject script_executor_listener) {
+ mScriptExecutorListener = env->NewGlobalRef(script_executor_listener);
+}
+
void ScriptExecutorListener::onError(const int errorType, const std::string& message,
const std::string& stackTrace) {
LOG(INFO) << "errorType: " << errorType << ", message: " << message
diff --git a/cpp/telemetry/script_executor/src/ScriptExecutorListener.h b/cpp/telemetry/script_executor/src/ScriptExecutorListener.h
index d9d8213afe..1e5c7d702a 100644
--- a/cpp/telemetry/script_executor/src/ScriptExecutorListener.h
+++ b/cpp/telemetry/script_executor/src/ScriptExecutorListener.h
@@ -17,6 +17,8 @@
#ifndef CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_SCRIPTEXECUTORLISTENER_H_
#define CPP_TELEMETRY_SCRIPT_EXECUTOR_SRC_SCRIPTEXECUTORLISTENER_H_
+#include "jni.h"
+
#include <string>
namespace android {
@@ -27,11 +29,19 @@ namespace script_executor {
// Wrapper class for IScriptExecutorListener.aidl.
class ScriptExecutorListener {
public:
+ ScriptExecutorListener(JNIEnv* jni, jobject script_executor_listener);
+
+ virtual ~ScriptExecutorListener();
+
void onScriptFinished() {}
void onSuccess() {}
void onError(const int errorType, const std::string& message, const std::string& stackTrace);
+
+private:
+ // Stores a jni global reference to Java Script Executor listener object.
+ jobject mScriptExecutorListener;
};
} // namespace script_executor