diff --git a/frameworks/native/ability/native/etc/extension_blocklist_config.json b/frameworks/native/ability/native/etc/extension_blocklist_config.json index 6c331eeb29159d661036d7ab7549d02911175021..0f7fd479f3fb1c2456657c6b08980f41893acb3e 100644 --- a/frameworks/native/ability/native/etc/extension_blocklist_config.json +++ b/frameworks/native/ability/native/etc/extension_blocklist_config.json @@ -545,5 +545,8 @@ "AppServiceExtension": [ "window" ] + }, + "replaceModuleList": { + "bundle": "bundle.bundle" } } diff --git a/frameworks/native/ability/native/extension_config_mgr.cpp b/frameworks/native/ability/native/extension_config_mgr.cpp index c430ca741dfc64375562d6edf08faa5b5cbb18a0..2a0c04e23bee6454efb6aae849ad2d5bd6d652b7 100644 --- a/frameworks/native/ability/native/extension_config_mgr.cpp +++ b/frameworks/native/ability/native/extension_config_mgr.cpp @@ -15,6 +15,7 @@ #include "extension_config_mgr.h" +#include #include #include "app_module_checker.h" @@ -75,6 +76,21 @@ void ExtensionConfigMgr::Init() childItem = childItem->next; } + cJSON *replaceModuleItem = cJSON_GetObjectItem(extensionConfig, ExtensionConfigItem::ITEM_NAME_REPLACEMODULELIST); + if (replaceModuleItem == nullptr) { + TAG_LOGE(AAFwkTag::EXT, "extension config file have no replace module list node"); + return; + } + childItem = replaceModuleItem->child; + while (childItem != nullptr) { + if (!cJSON_IsString(childItem)) { + continue; + } + std::string key = childItem->string == nullptr ? "" : childItem->string; + std::string value = childItem->valuestring; + replaceModuleListConfig_.emplace(key, value); + childItem = childItem->next; + } cJSON_Delete(extensionConfig); TAG_LOGD(AAFwkTag::EXT, "Init end"); } @@ -97,8 +113,86 @@ void ExtensionConfigMgr::UpdateRuntimeModuleChecker(const std::unique_ptr(extensionType_, extensionBlocklist_); - runtime->SetModuleLoadChecker(moduleChecker); - extensionBlocklist_.clear(); + GenerateExtensionEtsBlocklists(); + if (runtime->GetLanguage() == AbilityRuntime::Runtime::Language::ETS) { + TAG_LOGD(AAFwkTag::EXT, "ets runtime"); + SetExtensionEtsCheckCallback(runtime); + } else { + TAG_LOGD(AAFwkTag::EXT, "not ets runtime"); + auto moduleChecker = std::make_shared(extensionType_, extensionBlocklist_); + runtime->SetModuleLoadChecker(moduleChecker); + extensionBlocklist_.clear(); + } +} + +void ExtensionConfigMgr::GenerateExtensionEtsBlocklists() +{ + if (!extensionEtsBlocklist_.empty()) { + TAG_LOGD(AAFwkTag::EXT, "extension ets block list not empty."); + return; + } + auto iter = extensionBlocklist_.find(extensionType_); + if (iter == extensionBlocklist_.end()) { + TAG_LOGD(AAFwkTag::EXT, "null extension block, extensionType: %{public}d.", extensionType_); + return; + } + for (const auto& module: iter->second) { + auto replaceIter = replaceModuleListConfig_.find(module); + if (replaceIter == replaceModuleListConfig_.end()) { + std::string lowermodule; + lowermodule.reserve(module.size()); + std::transform(module.cbegin(), module.cend(), std::back_inserter(lowermodule), + [](unsigned char c) { return std::tolower(c); }); + extensionEtsBlocklist_.emplace(std::move(lowermodule)); + } else { + std::string lowerReplaceModule; + lowerReplaceModule.reserve(replaceIter->second.size()); + std::transform(replaceIter->second.cbegin(), replaceIter->second.cend(), + std::back_inserter(lowerReplaceModule), [](unsigned char c) { return std::tolower(c); }); + extensionEtsBlocklist_.emplace(std::move(lowerReplaceModule)); + } + } +} + +bool ExtensionConfigMgr::CheckEtsModuleLoadable(const std::string &className) +{ + std::string classNameAfterRemovePreFix; + if (className.length() >= ExtensionConfigItem::ITEM_LENGTH_OHOSPREFIX && + (className.compare(0, ExtensionConfigItem::ITEM_LENGTH_OHOSPREFIX, ExtensionConfigItem::ITEM_NAME_OHOSPREFIX) + == 0)) { + classNameAfterRemovePreFix = className.substr(ExtensionConfigItem::ITEM_LENGTH_OHOSPREFIX); + } else if (className.length() >= ExtensionConfigItem::ITEM_LENGTH_HMSPREFIX && + (className.compare(0, ExtensionConfigItem::ITEM_LENGTH_HMSPREFIX, ExtensionConfigItem::ITEM_NAME_HMSPREFIX) + == 0)) { + classNameAfterRemovePreFix = className.substr(ExtensionConfigItem::ITEM_LENGTH_HMSPREFIX); + } else { + classNameAfterRemovePreFix = className; + } + std::string lowerClassName; + lowerClassName.reserve(classNameAfterRemovePreFix.size()); + std::transform(classNameAfterRemovePreFix.cbegin(), classNameAfterRemovePreFix.cend(), + std::back_inserter(lowerClassName), [](unsigned char c) { return std::tolower(c); }); + TAG_LOGD(AAFwkTag::EXT, "extensionType: %{public}d, className is %{public}s.", extensionType_, className.c_str()); + for (const auto& blockClassName: extensionEtsBlocklist_) { + if (lowerClassName.compare(0, blockClassName.length(), blockClassName) == 0) { + TAG_LOGD(AAFwkTag::EXT, "className is %{public}s in blocklist.", className.c_str()); + return false; + } + } + TAG_LOGD(AAFwkTag::EXT, "className is %{public}s in not blocklist.", className.c_str()); + return true; +} + +void ExtensionConfigMgr::SetExtensionEtsCheckCallback(const std::unique_ptr &runtime) +{ + auto callback = [extensionConfigMgrWeak = weak_from_this()](const std::string &className) -> bool { + auto extensionConfigMgr = extensionConfigMgrWeak.lock(); + if (extensionConfigMgr == nullptr) { + TAG_LOGD(AAFwkTag::EXT, "null extensionConfigMgr"); + return false; + } + return extensionConfigMgr->CheckEtsModuleLoadable(className); + }; + runtime->SetExtensionApiCheckCallback(callback); } } \ No newline at end of file diff --git a/frameworks/native/appkit/app/main_thread.cpp b/frameworks/native/appkit/app/main_thread.cpp index f5b40c21cedb5345d35304d5f55acced9763ab76..671c9b7d6f35344e56e38b43cf66354301a4c87e 100644 --- a/frameworks/native/appkit/app/main_thread.cpp +++ b/frameworks/native/appkit/app/main_thread.cpp @@ -2838,7 +2838,7 @@ void MainThread::Init(const std::shared_ptr &runner) TAG_LOGD(AAFwkTag::APPKIT, "Start"); mainHandler_ = std::make_shared(runner, this); watchdog_ = std::make_shared(); - extensionConfigMgr_ = std::make_unique(); + extensionConfigMgr_ = std::make_shared(); wptr weak = this; auto task = [weak]() { auto appThread = weak.promote(); diff --git a/frameworks/native/runtime/ets_runtime.cpp b/frameworks/native/runtime/ets_runtime.cpp index f32bb200bbfe90aff6d33b4138ff2437e5c05f2d..281eaa9fa480de71500a5f9228302d39eb88629a 100644 --- a/frameworks/native/runtime/ets_runtime.cpp +++ b/frameworks/native/runtime/ets_runtime.cpp @@ -258,6 +258,12 @@ void ETSRuntime::SetAppLibPath(const AppLibPathMap &appLibPaths) g_etsEnvFuncs->InitETSSysNS(ETS_SYSLIB_PATH); } +void ETSRuntime::SetExtensionApiCheckCallback(const std::function &cb) +{ + TAG_LOGD(AAFwkTag::ETSRUNTIME, "called"); + // ark::ets::EtsNamespaceManager::SetExtensionApiCheckCallback(std::move(cb)); +} + bool ETSRuntime::Initialize(const Options &options, std::unique_ptr &jsRuntime) { TAG_LOGD(AAFwkTag::ETSRUNTIME, "Initialize called"); diff --git a/interfaces/inner_api/runtime/include/cj_runtime.h b/interfaces/inner_api/runtime/include/cj_runtime.h index 07ecd75405b42959b50c3d030665106ab0ad0972..b32f29953d6868c9ae5ace988e3ba51e6a42a95b 100644 --- a/interfaces/inner_api/runtime/include/cj_runtime.h +++ b/interfaces/inner_api/runtime/include/cj_runtime.h @@ -16,6 +16,7 @@ #ifndef OHOS_ABILITY_RUNTIME_CJ_RUNTIME_H #define OHOS_ABILITY_RUNTIME_CJ_RUNTIME_H +#include #include #include #include @@ -63,6 +64,7 @@ public: bool UnLoadRepairPatch(const std::string& patchFile) override { return false; } void RegisterQuickFixQueryFunc(const std::map& moduleAndPath) override {}; void StartProfiler(const DebugOption dOption) override; + void SetExtensionApiCheckCallback(const std::function &cb) override {} void SetModuleLoadChecker(const std::shared_ptr moduleCheckerDelegate) const override {} void SetDeviceDisconnectCallback(const std::function &cb) override {}; bool IsAppLibLoaded() const { return appLibLoaded_; } diff --git a/interfaces/inner_api/runtime/include/ets_runtime.h b/interfaces/inner_api/runtime/include/ets_runtime.h index 2b332eee8a3e45ffd233dcde20d3b1f52c0c6b25..66513d603ee84fee904d1143b955f4f8661410fb 100644 --- a/interfaces/inner_api/runtime/include/ets_runtime.h +++ b/interfaces/inner_api/runtime/include/ets_runtime.h @@ -71,6 +71,7 @@ public: bool UnLoadRepairPatch(const std::string &patchFile) override { return false; } void RegisterQuickFixQueryFunc(const std::map &moduleAndPath) override {}; void StartProfiler(const DebugOption debugOption) override {}; + void SetExtensionApiCheckCallback(const std::function &cb) override; void SetModuleLoadChecker(const std::shared_ptr moduleCheckerDelegate) const override {} void SetDeviceDisconnectCallback(const std::function &cb) override {}; void DestroyHeapProfiler() override {}; diff --git a/interfaces/inner_api/runtime/include/js_runtime.h b/interfaces/inner_api/runtime/include/js_runtime.h index 7e514e2ee6aab14d32d145805c09d183e055fd1c..a48fa12e3526f7ec2340dc23ead4fb49ccb9d9b6 100644 --- a/interfaces/inner_api/runtime/include/js_runtime.h +++ b/interfaces/inner_api/runtime/include/js_runtime.h @@ -136,6 +136,7 @@ public: void FreeNativeReference(std::unique_ptr reference); void FreeNativeReference(std::shared_ptr&& reference); void StartProfiler(const DebugOption debugOption) override; + void SetExtensionApiCheckCallback(const std::function &cb) override {} void DebuggerConnectionManager(bool isDebugApp, bool isStartWithDebug, const DebugOption dOption); void ReloadFormComponent(); // Reload ArkTS-Card component diff --git a/interfaces/inner_api/runtime/include/runtime.h b/interfaces/inner_api/runtime/include/runtime.h index 457106ae905d73728812cd8a2a3186d34adc9c92..91c6b4d513cf70100fbf4968a85d9ba464138bbf 100644 --- a/interfaces/inner_api/runtime/include/runtime.h +++ b/interfaces/inner_api/runtime/include/runtime.h @@ -126,6 +126,7 @@ public: virtual bool UnLoadRepairPatch(const std::string& patchFile) = 0; virtual void RegisterQuickFixQueryFunc(const std::map& moduleAndPath) = 0; virtual void StartProfiler(const DebugOption debugOption) = 0; + virtual void SetExtensionApiCheckCallback(const std::function &cb) {} virtual void DoCleanWorkAfterStageCleaned() {} virtual void SetModuleLoadChecker(const std::shared_ptr moduleCheckerDelegate) const {} virtual void SetDeviceDisconnectCallback(const std::function &cb) = 0; diff --git a/interfaces/kits/native/ability/native/extension_config_mgr.h b/interfaces/kits/native/ability/native/extension_config_mgr.h index 8aa210647d9b9234cf08ce9972e36a3f37c46018..281a6785f1ad5d0867d8b4bbcf4dbddeaab928d2 100644 --- a/interfaces/kits/native/ability/native/extension_config_mgr.h +++ b/interfaces/kits/native/ability/native/extension_config_mgr.h @@ -28,6 +28,11 @@ namespace OHOS::AbilityRuntime { namespace ExtensionConfigItem { constexpr char ITEM_NAME_BLOCKLIST[] = "blocklist"; + constexpr char ITEM_NAME_REPLACEMODULELIST[] = "replaceModuleList"; + constexpr char ITEM_NAME_OHOSPREFIX[] = "@ohos."; + constexpr char ITEM_NAME_HMSPREFIX[] = "@hms."; + constexpr size_t ITEM_LENGTH_OHOSPREFIX = sizeof(ITEM_NAME_OHOSPREFIX) - 1; + constexpr size_t ITEM_LENGTH_HMSPREFIX = sizeof(ITEM_NAME_HMSPREFIX) - 1; } namespace { constexpr int32_t EXTENSION_TYPE_UNKNOWN = 255; @@ -36,7 +41,7 @@ namespace { /** * @brief Manage extension configuration. */ -class ExtensionConfigMgr { +class ExtensionConfigMgr : public std::enable_shared_from_this { public: ExtensionConfigMgr() = default; @@ -73,9 +78,30 @@ public: */ void UpdateRuntimeModuleChecker(const std::unique_ptr &runtime); + /** + * @brief Check Whether the ets module can be loaded + * + * @param className the className of the ets module + * @return Return true if the module can be loaded, false if the module can not be loaded. + */ + bool CheckEtsModuleLoadable(const std::string &className); + private: + /** + * @brief Generate ets blocklist be extension_blocklist_config + * + */ + void GenerateExtensionEtsBlocklists(); + /** + * @brief set ets runtime module checker + * + * @param runtime the runtime pointer + */ + void SetExtensionEtsCheckCallback(const std::unique_ptr &runtime); std::unordered_map> blocklistConfig_; std::unordered_map> extensionBlocklist_; + std::unordered_map replaceModuleListConfig_; + std::unordered_set extensionEtsBlocklist_; int32_t extensionType_ = EXTENSION_TYPE_UNKNOWN; }; } // namespace OHOS::AbilityRuntime diff --git a/interfaces/kits/native/appkit/app/main_thread.h b/interfaces/kits/native/appkit/app/main_thread.h index 06141a7fdf4ad23cc1afec1ba63192b05c74df91..c5c1800efd6fea80d013b04406db9ceaf3648c1e 100644 --- a/interfaces/kits/native/appkit/app/main_thread.h +++ b/interfaces/kits/native/appkit/app/main_thread.h @@ -730,7 +730,7 @@ private: static std::shared_ptr mainHandler_; std::shared_ptr abilityRecordMgr_ = nullptr; std::shared_ptr watchdog_ = nullptr; - std::unique_ptr extensionConfigMgr_ = nullptr; + std::shared_ptr extensionConfigMgr_ = nullptr; MainThreadState mainThreadState_ = MainThreadState::INIT; sptr appMgr_ = nullptr; // appMgrService Handler sptr deathRecipient_ = nullptr; diff --git a/test/mock/frameworks_kits_runtime_test/mock_runtime.h b/test/mock/frameworks_kits_runtime_test/mock_runtime.h index 4540ee79a64da9c0ad24bd5bf36b79e3331a7570..9142fb409f95a419aab3ae400a445972372377cd 100644 --- a/test/mock/frameworks_kits_runtime_test/mock_runtime.h +++ b/test/mock/frameworks_kits_runtime_test/mock_runtime.h @@ -126,7 +126,7 @@ public: } void StartProfiler(const DebugOption debugOption) override {} - + void SetExtensionApiCheckCallback(const std::function &cb) override {} void DumpHeapSnapshot(uint32_t tid, bool isFullGC, bool isBinary = false) override {} void ForceFullGC(uint32_t tid) override {} public: diff --git a/test/unittest/extension_config_mgr_test/extension_config_mgr_test.cpp b/test/unittest/extension_config_mgr_test/extension_config_mgr_test.cpp index e751fd2a1780f4b78976523fc18ad11afa1ba7e6..805167c5995fb6472bf2a8115b5bfc117b3ec5af 100644 --- a/test/unittest/extension_config_mgr_test/extension_config_mgr_test.cpp +++ b/test/unittest/extension_config_mgr_test/extension_config_mgr_test.cpp @@ -15,6 +15,7 @@ #include +#include "cJSON.h" #define private public #define protected public #include "extension_config_mgr.h" @@ -39,6 +40,7 @@ namespace { constexpr int32_t EXTENSION_TYPE_WINDOW = 10; constexpr int32_t EXTENSION_TYPE_ENTERPRISE_ADMIN = 11; constexpr int32_t EXTENSION_TYPE_FILE_ACCESS = 12; + constexpr int32_t EXTENSION_TYPE_DRIVER = 18; constexpr char BLOCK_LIST_ITEM_SERVICE_EXTENSION[] = "ServiceExtension"; constexpr char BLOCK_LIST_ITEM_FORM_EXTENSION[] = "FormExtension"; constexpr char BLOCK_LIST_ITEM_FILE_ACCESS_EXTENSION[] = "FileAccessExtension"; @@ -51,6 +53,9 @@ namespace { constexpr char BLOCK_LIST_ITEM_INPUT_METHOD_EXTENSION_ABILITY[] = "InputMethodExtensionAbility"; constexpr char BLOCK_LIST_ITEM_WORK_SCHEDULER_EXTENSION[] = "WorkSchedulerExtension"; constexpr char BLOCK_LIST_ITEM_DATA_SHARE_EXTENSION[] = "DataShareExtension"; + constexpr char BLOCK_LIST_ITEM_DRIVER_EXTENSION[] = "DriverExtension"; + constexpr char REPLACE_MODULE_LIST_ITEM_BUNDLE_KEY[] = "bundle"; + constexpr char REPLACE_MODULE_LIST_ITEM_BUNDLE_VALUE[] = "bundle.bundle"; constexpr char INVAILD_BLOCK_LIST_ITEM[] = "InvaildExtension"; } @@ -174,5 +179,92 @@ HWTEST_F(ExtensionConfigMgrTest, AddBlockListItem_0200, TestSize.Level1) bool result = (mgr.extensionBlocklist_.find(EXTENSION_TYPE_FORM) != mgr.extensionBlocklist_.end()); EXPECT_FALSE(result); } + +/** + * @tc.name: ParseReplaceModuleLists_init_0100 + * @tc.desc: Init And parse success. + * @tc.type: FUNC + */ +HWTEST_F(ExtensionConfigMgrTest, ParseReplaceModuleLists_init_0100, TestSize.Level0) +{ + ExtensionConfigMgr mgr; + mgr.Init(); + auto iter = mgr.replaceModuleListConfig_.find(REPLACE_MODULE_LIST_ITEM_BUNDLE_KEY); + bool result = (iter != mgr.replaceModuleListConfig_.end()); + EXPECT_TRUE(result); + + EXPECT_EQ(iter->second, REPLACE_MODULE_LIST_ITEM_BUNDLE_VALUE); +} + +/** + * @tc.name: GenerateExtensionEtsBlocklists_0100 + * @tc.desc: Generate Extension ets block list Test + * @tc.type: FUNC + */ +HWTEST_F(ExtensionConfigMgrTest, GenerateExtensionEtsBlocklists_0100, TestSize.Level1) +{ + ExtensionConfigMgr mgr; + std::unordered_set set1 = { "111", "222", "333" }; + mgr.extensionBlocklist_.emplace(EXTENSION_TYPE_FORM, set1); + mgr.extensionType_ = EXTENSION_TYPE_WORK_SCHEDULER; + mgr.GenerateExtensionEtsBlocklists(); + EXPECT_TRUE(mgr.extensionEtsBlocklist_.empty()); + + mgr.extensionBlocklist_.clear(); + mgr.extensionBlocklist_.emplace(EXTENSION_TYPE_FORM, set1); + mgr.extensionType_ = EXTENSION_TYPE_FORM; + mgr.GenerateExtensionEtsBlocklists(); + EXPECT_FALSE(mgr.extensionEtsBlocklist_.empty()); + for (const auto& ele: set1) { + EXPECT_TRUE(mgr.extensionEtsBlocklist_.find(ele) != mgr.extensionEtsBlocklist_.end()); + } + + mgr.replaceModuleListConfig_.emplace("222", "222_1"); + mgr.extensionBlocklist_.clear(); + mgr.extensionEtsBlocklist_.clear(); + mgr.extensionBlocklist_.emplace(EXTENSION_TYPE_FORM, set1); + mgr.extensionType_ = EXTENSION_TYPE_FORM; + mgr.GenerateExtensionEtsBlocklists(); + EXPECT_FALSE(mgr.extensionEtsBlocklist_.empty()); + EXPECT_FALSE(mgr.extensionEtsBlocklist_.find("222") != mgr.extensionEtsBlocklist_.end()); + EXPECT_TRUE(mgr.extensionEtsBlocklist_.find("222_1") != mgr.extensionEtsBlocklist_.end()); + + std::unordered_set set2 = { "BbB" }; + mgr.replaceModuleListConfig_.clear(); + mgr.extensionBlocklist_.clear(); + mgr.extensionEtsBlocklist_.clear(); + mgr.extensionBlocklist_.emplace(EXTENSION_TYPE_FORM, set2); + mgr.extensionType_ = EXTENSION_TYPE_FORM; + mgr.GenerateExtensionEtsBlocklists(); + EXPECT_FALSE(mgr.extensionEtsBlocklist_.empty()); + EXPECT_FALSE(mgr.extensionEtsBlocklist_.find("BbB") != mgr.extensionEtsBlocklist_.end()); + EXPECT_TRUE(mgr.extensionEtsBlocklist_.find("bbb") != mgr.extensionEtsBlocklist_.end()); +} + +/** + * @tc.name: CheckEtsModuleLoadable_0100 + * @tc.desc: CheckEtsModuleLoadable Test + * @tc.type: FUNC + */ +HWTEST_F(ExtensionConfigMgrTest, CheckEtsModuleLoadable_0100, TestSize.Level1) +{ + ExtensionConfigMgr mgr; + mgr.Init(); + mgr.AddBlockListItem(BLOCK_LIST_ITEM_DRIVER_EXTENSION, EXTENSION_TYPE_DRIVER); + mgr.extensionType_ = EXTENSION_TYPE_DRIVER; + mgr.GenerateExtensionEtsBlocklists(); + + EXPECT_FALSE(mgr.CheckEtsModuleLoadable("abilityAccessCtrl")); + EXPECT_FALSE(mgr.CheckEtsModuleLoadable("ABILITYACCESSCTRL")); + EXPECT_FALSE(mgr.CheckEtsModuleLoadable("abilityAccessCtrl.XXX")); + EXPECT_TRUE(mgr.CheckEtsModuleLoadable("bundle")); + EXPECT_FALSE(mgr.CheckEtsModuleLoadable("bundle.bundle")); + EXPECT_FALSE(mgr.CheckEtsModuleLoadable("app.ability.appManager")); + EXPECT_FALSE(mgr.CheckEtsModuleLoadable("@ohos.app.ability.appManager")); + EXPECT_FALSE(mgr.CheckEtsModuleLoadable("@hms.app.ability.appManager")); + EXPECT_TRUE(mgr.CheckEtsModuleLoadable("@hms.app.app.appManager")); + EXPECT_TRUE(mgr.CheckEtsModuleLoadable("@ohos.app.app.appManager")); + EXPECT_TRUE(mgr.CheckEtsModuleLoadable("1111")); +} } // namespace AbilityRuntime } // namespace OHOS diff --git a/test/unittest/insight_intent/mock/mock_runtime.h b/test/unittest/insight_intent/mock/mock_runtime.h index 2348284d14dc23784c3cdb15663941b40f3534a1..b3b6ca82caf1e49516fd0ec796e91b7e6b49d65b 100644 --- a/test/unittest/insight_intent/mock/mock_runtime.h +++ b/test/unittest/insight_intent/mock/mock_runtime.h @@ -56,6 +56,7 @@ public: bool UnLoadRepairPatch(const std::string& patchFile) override { return false; } void RegisterQuickFixQueryFunc(const std::map& moduleAndPath) override {} void StartProfiler(const DebugOption debugOption) override {} + void SetExtensionApiCheckCallback(const std::function &cb) override {} void SetDeviceDisconnectCallback(const std::function &cb) override {} void SetLanguage(const Runtime::Language& language) { language_ = language; } private: