diff options
author | Max Dashouk <mdashouk@google.com> | 2021-06-30 17:43:54 +0000 |
---|---|---|
committer | Max Dashouk <mdashouk@google.com> | 2021-07-02 12:25:25 -0700 |
commit | 2c013dbf7bf922dd4eb63ae7c78b338048d151c6 (patch) | |
tree | 6a4b160e90a7a8665e10ebf0a37a8c11b22716a9 /cpp/telemetry | |
parent | 0c89bf8c456ca458eb88233a43a1530672c0ca9f (diff) | |
download | Car-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.bp | 15 | ||||
-rw-r--r-- | cpp/telemetry/script_executor/src/LuaEngine.cpp | 48 | ||||
-rw-r--r-- | cpp/telemetry/script_executor/src/LuaEngine.h | 20 | ||||
-rw-r--r-- | cpp/telemetry/script_executor/src/ScriptExecutorJni.cpp | 137 | ||||
-rw-r--r-- | cpp/telemetry/script_executor/src/ScriptExecutorListener.cpp | 12 | ||||
-rw-r--r-- | cpp/telemetry/script_executor/src/ScriptExecutorListener.h | 10 |
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 |