1 /*
2 * Copyright (C) 2018 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 "utils/intents/jni-lua.h"
18
19 #include "utils/hash/farmhash.h"
20 #include "utils/java/jni-helper.h"
21 #include "utils/strings/substitute.h"
22
23 #ifdef __cplusplus
24 extern "C" {
25 #endif
26 #include "lauxlib.h"
27 #include "lua.h"
28 #ifdef __cplusplus
29 }
30 #endif
31
32 namespace libtextclassifier3 {
33 namespace {
34
35 static constexpr const char* kHashKey = "hash";
36 static constexpr const char* kUrlSchemaKey = "url_schema";
37 static constexpr const char* kUrlHostKey = "url_host";
38 static constexpr const char* kUrlEncodeKey = "urlencode";
39 static constexpr const char* kPackageNameKey = "package_name";
40 static constexpr const char* kDeviceLocaleKey = "device_locales";
41 static constexpr const char* kFormatKey = "format";
42
43 } // namespace
44
JniLuaEnvironment(const Resources & resources,const JniCache * jni_cache,const jobject context,const std::vector<Locale> & device_locales)45 JniLuaEnvironment::JniLuaEnvironment(const Resources& resources,
46 const JniCache* jni_cache,
47 const jobject context,
48 const std::vector<Locale>& device_locales)
49 : LuaEnvironment(),
50 resources_(resources),
51 jenv_(jni_cache ? jni_cache->GetEnv() : nullptr),
52 jni_cache_(jni_cache),
53 context_(context),
54 device_locales_(device_locales),
55 usermanager_(/*object=*/nullptr,
56 /*jvm=*/(jni_cache ? jni_cache->jvm : nullptr)),
57 usermanager_retrieved_(false),
58 system_resources_(/*object=*/nullptr,
59 /*jvm=*/(jni_cache ? jni_cache->jvm : nullptr)),
60 system_resources_resources_retrieved_(false),
61 string_(/*object=*/nullptr,
62 /*jvm=*/(jni_cache ? jni_cache->jvm : nullptr)),
63 android_(/*object=*/nullptr,
64 /*jvm=*/(jni_cache ? jni_cache->jvm : nullptr)) {}
65
PreallocateConstantJniStrings()66 bool JniLuaEnvironment::PreallocateConstantJniStrings() {
67 TC3_ASSIGN_OR_RETURN_FALSE(ScopedLocalRef<jstring> string_value,
68 JniHelper::NewStringUTF(jenv_, "string"));
69 string_ = MakeGlobalRef(string_value.get(), jenv_, jni_cache_->jvm);
70 TC3_ASSIGN_OR_RETURN_FALSE(ScopedLocalRef<jstring> android_value,
71 JniHelper::NewStringUTF(jenv_, "android"));
72 android_ = MakeGlobalRef(android_value.get(), jenv_, jni_cache_->jvm);
73 if (string_ == nullptr || android_ == nullptr) {
74 TC3_LOG(ERROR) << "Could not allocate constant strings references.";
75 return false;
76 }
77 return true;
78 }
79
Initialize()80 bool JniLuaEnvironment::Initialize() {
81 if (!PreallocateConstantJniStrings()) {
82 return false;
83 }
84 return (RunProtected([this] {
85 LoadDefaultLibraries();
86 SetupExternalHook();
87 lua_setglobal(state_, "external");
88 return LUA_OK;
89 }) == LUA_OK);
90 }
91
SetupExternalHook()92 void JniLuaEnvironment::SetupExternalHook() {
93 // This exposes an `external` object with the following fields:
94 // * entity: the bundle with all information about a classification.
95 // * android: callbacks into specific android provided methods.
96 // * android.user_restrictions: callbacks to check user permissions.
97 // * android.R: callbacks to retrieve string resources.
98 PushLazyObject(&JniLuaEnvironment::HandleExternalCallback);
99
100 // android
101 PushLazyObject(&JniLuaEnvironment::HandleAndroidCallback);
102 {
103 // android.user_restrictions
104 PushLazyObject(&JniLuaEnvironment::HandleUserRestrictionsCallback);
105 lua_setfield(state_, /*idx=*/-2, "user_restrictions");
106
107 // android.R
108 // Callback to access android string resources.
109 PushLazyObject(&JniLuaEnvironment::HandleAndroidStringResources);
110 lua_setfield(state_, /*idx=*/-2, "R");
111 }
112 lua_setfield(state_, /*idx=*/-2, "android");
113 }
114
HandleExternalCallback()115 int JniLuaEnvironment::HandleExternalCallback() {
116 const StringPiece key = ReadString(kIndexStackTop);
117 if (key.Equals(kHashKey)) {
118 PushFunction(&JniLuaEnvironment::HandleHash);
119 return 1;
120 } else if (key.Equals(kFormatKey)) {
121 PushFunction(&JniLuaEnvironment::HandleFormat);
122 return 1;
123 } else {
124 TC3_LOG(ERROR) << "Undefined external access " << key;
125 lua_error(state_);
126 return 0;
127 }
128 }
129
HandleAndroidCallback()130 int JniLuaEnvironment::HandleAndroidCallback() {
131 const StringPiece key = ReadString(kIndexStackTop);
132 if (key.Equals(kDeviceLocaleKey)) {
133 // Provide the locale as table with the individual fields set.
134 lua_newtable(state_);
135 for (int i = 0; i < device_locales_.size(); i++) {
136 // Adjust index to 1-based indexing for Lua.
137 lua_pushinteger(state_, i + 1);
138 lua_newtable(state_);
139 PushString(device_locales_[i].Language());
140 lua_setfield(state_, -2, "language");
141 PushString(device_locales_[i].Region());
142 lua_setfield(state_, -2, "region");
143 PushString(device_locales_[i].Script());
144 lua_setfield(state_, -2, "script");
145 lua_settable(state_, /*idx=*/-3);
146 }
147 return 1;
148 } else if (key.Equals(kPackageNameKey)) {
149 if (context_ == nullptr) {
150 TC3_LOG(ERROR) << "Context invalid.";
151 lua_error(state_);
152 return 0;
153 }
154
155 StatusOr<ScopedLocalRef<jstring>> status_or_package_name_str =
156 JniHelper::CallObjectMethod<jstring>(
157 jenv_, context_, jni_cache_->context_get_package_name);
158
159 if (!status_or_package_name_str.ok()) {
160 TC3_LOG(ERROR) << "Error calling Context.getPackageName";
161 lua_error(state_);
162 return 0;
163 }
164 StatusOr<std::string> status_or_package_name_std_str = JStringToUtf8String(
165 jenv_, status_or_package_name_str.ValueOrDie().get());
166 if (!status_or_package_name_std_str.ok()) {
167 lua_error(state_);
168 return 0;
169 }
170 PushString(status_or_package_name_std_str.ValueOrDie());
171 return 1;
172 } else if (key.Equals(kUrlEncodeKey)) {
173 PushFunction(&JniLuaEnvironment::HandleUrlEncode);
174 return 1;
175 } else if (key.Equals(kUrlHostKey)) {
176 PushFunction(&JniLuaEnvironment::HandleUrlHost);
177 return 1;
178 } else if (key.Equals(kUrlSchemaKey)) {
179 PushFunction(&JniLuaEnvironment::HandleUrlSchema);
180 return 1;
181 } else {
182 TC3_LOG(ERROR) << "Undefined android reference " << key;
183 lua_error(state_);
184 return 0;
185 }
186 }
187
HandleUserRestrictionsCallback()188 int JniLuaEnvironment::HandleUserRestrictionsCallback() {
189 if (jni_cache_->usermanager_class == nullptr ||
190 jni_cache_->usermanager_get_user_restrictions == nullptr) {
191 // UserManager is only available for API level >= 17 and
192 // getUserRestrictions only for API level >= 18, so we just return false
193 // normally here.
194 lua_pushboolean(state_, false);
195 return 1;
196 }
197
198 // Get user manager if not previously retrieved.
199 if (!RetrieveUserManager()) {
200 TC3_LOG(ERROR) << "Error retrieving user manager.";
201 lua_error(state_);
202 return 0;
203 }
204
205 StatusOr<ScopedLocalRef<jobject>> status_or_bundle =
206 JniHelper::CallObjectMethod(
207 jenv_, usermanager_.get(),
208 jni_cache_->usermanager_get_user_restrictions);
209 if (!status_or_bundle.ok() || status_or_bundle.ValueOrDie() == nullptr) {
210 TC3_LOG(ERROR) << "Error calling getUserRestrictions";
211 lua_error(state_);
212 return 0;
213 }
214
215 const StringPiece key_str = ReadString(kIndexStackTop);
216 if (key_str.empty()) {
217 TC3_LOG(ERROR) << "Expected string, got null.";
218 lua_error(state_);
219 return 0;
220 }
221
222 const StatusOr<ScopedLocalRef<jstring>> status_or_key =
223 jni_cache_->ConvertToJavaString(key_str);
224 if (!status_or_key.ok()) {
225 lua_error(state_);
226 return 0;
227 }
228 const StatusOr<bool> status_or_permission = JniHelper::CallBooleanMethod(
229 jenv_, status_or_bundle.ValueOrDie().get(),
230 jni_cache_->bundle_get_boolean, status_or_key.ValueOrDie().get());
231 if (!status_or_permission.ok()) {
232 TC3_LOG(ERROR) << "Error getting bundle value";
233 lua_pushboolean(state_, false);
234 } else {
235 lua_pushboolean(state_, status_or_permission.ValueOrDie());
236 }
237 return 1;
238 }
239
HandleUrlEncode()240 int JniLuaEnvironment::HandleUrlEncode() {
241 const StringPiece input = ReadString(/*index=*/1);
242 if (input.empty()) {
243 TC3_LOG(ERROR) << "Expected string, got null.";
244 lua_error(state_);
245 return 0;
246 }
247
248 // Call Java Uri encode.
249 const StatusOr<ScopedLocalRef<jstring>> status_or_input_str =
250 jni_cache_->ConvertToJavaString(input);
251 if (!status_or_input_str.ok()) {
252 lua_error(state_);
253 return 0;
254 }
255 StatusOr<ScopedLocalRef<jstring>> status_or_encoded_str =
256 JniHelper::CallStaticObjectMethod<jstring>(
257 jenv_, jni_cache_->uri_class.get(), jni_cache_->uri_encode,
258 status_or_input_str.ValueOrDie().get());
259
260 if (!status_or_encoded_str.ok()) {
261 TC3_LOG(ERROR) << "Error calling Uri.encode";
262 lua_error(state_);
263 return 0;
264 }
265 const StatusOr<std::string> status_or_encoded_std_str =
266 JStringToUtf8String(jenv_, status_or_encoded_str.ValueOrDie().get());
267 if (!status_or_encoded_std_str.ok()) {
268 lua_error(state_);
269 return 0;
270 }
271 PushString(status_or_encoded_std_str.ValueOrDie());
272 return 1;
273 }
274
ParseUri(StringPiece url) const275 StatusOr<ScopedLocalRef<jobject>> JniLuaEnvironment::ParseUri(
276 StringPiece url) const {
277 if (url.empty()) {
278 return {Status::UNKNOWN};
279 }
280
281 // Call to Java URI parser.
282 TC3_ASSIGN_OR_RETURN(
283 const StatusOr<ScopedLocalRef<jstring>> status_or_url_str,
284 jni_cache_->ConvertToJavaString(url));
285
286 // Try to parse uri and get scheme.
287 TC3_ASSIGN_OR_RETURN(
288 ScopedLocalRef<jobject> uri,
289 JniHelper::CallStaticObjectMethod(jenv_, jni_cache_->uri_class.get(),
290 jni_cache_->uri_parse,
291 status_or_url_str.ValueOrDie().get()));
292 if (uri == nullptr) {
293 TC3_LOG(ERROR) << "Error calling Uri.parse";
294 return {Status::UNKNOWN};
295 }
296 return uri;
297 }
298
HandleUrlSchema()299 int JniLuaEnvironment::HandleUrlSchema() {
300 StringPiece url = ReadString(/*index=*/1);
301
302 const StatusOr<ScopedLocalRef<jobject>> status_or_parsed_uri = ParseUri(url);
303 if (!status_or_parsed_uri.ok()) {
304 lua_error(state_);
305 return 0;
306 }
307
308 const StatusOr<ScopedLocalRef<jstring>> status_or_scheme_str =
309 JniHelper::CallObjectMethod<jstring>(
310 jenv_, status_or_parsed_uri.ValueOrDie().get(),
311 jni_cache_->uri_get_scheme);
312 if (!status_or_scheme_str.ok()) {
313 TC3_LOG(ERROR) << "Error calling Uri.getScheme";
314 lua_error(state_);
315 return 0;
316 }
317 if (status_or_scheme_str.ValueOrDie() == nullptr) {
318 lua_pushnil(state_);
319 } else {
320 const StatusOr<std::string> status_or_scheme_std_str =
321 JStringToUtf8String(jenv_, status_or_scheme_str.ValueOrDie().get());
322 if (!status_or_scheme_std_str.ok()) {
323 lua_error(state_);
324 return 0;
325 }
326 PushString(status_or_scheme_std_str.ValueOrDie());
327 }
328 return 1;
329 }
330
HandleUrlHost()331 int JniLuaEnvironment::HandleUrlHost() {
332 const StringPiece url = ReadString(kIndexStackTop);
333
334 const StatusOr<ScopedLocalRef<jobject>> status_or_parsed_uri = ParseUri(url);
335 if (!status_or_parsed_uri.ok()) {
336 lua_error(state_);
337 return 0;
338 }
339
340 const StatusOr<ScopedLocalRef<jstring>> status_or_host_str =
341 JniHelper::CallObjectMethod<jstring>(
342 jenv_, status_or_parsed_uri.ValueOrDie().get(),
343 jni_cache_->uri_get_host);
344 if (!status_or_host_str.ok()) {
345 TC3_LOG(ERROR) << "Error calling Uri.getHost";
346 lua_error(state_);
347 return 0;
348 }
349
350 if (status_or_host_str.ValueOrDie() == nullptr) {
351 lua_pushnil(state_);
352 } else {
353 const StatusOr<std::string> status_or_host_std_str =
354 JStringToUtf8String(jenv_, status_or_host_str.ValueOrDie().get());
355 if (!status_or_host_std_str.ok()) {
356 lua_error(state_);
357 return 0;
358 }
359 PushString(status_or_host_std_str.ValueOrDie());
360 }
361 return 1;
362 }
363
HandleHash()364 int JniLuaEnvironment::HandleHash() {
365 const StringPiece input = ReadString(kIndexStackTop);
366 lua_pushinteger(state_, tc3farmhash::Hash32(input.data(), input.length()));
367 return 1;
368 }
369
HandleFormat()370 int JniLuaEnvironment::HandleFormat() {
371 const int num_args = lua_gettop(state_);
372 std::vector<StringPiece> args(num_args - 1);
373 for (int i = 0; i < num_args - 1; i++) {
374 args[i] = ReadString(/*index=*/i + 2);
375 }
376 PushString(strings::Substitute(ReadString(/*index=*/1), args));
377 return 1;
378 }
379
LookupModelStringResource() const380 bool JniLuaEnvironment::LookupModelStringResource() const {
381 // Handle only lookup by name.
382 if (lua_type(state_, kIndexStackTop) != LUA_TSTRING) {
383 return false;
384 }
385
386 const StringPiece resource_name = ReadString(kIndexStackTop);
387 std::string resource_content;
388 if (!resources_.GetResourceContent(device_locales_, resource_name,
389 &resource_content)) {
390 // Resource cannot be provided by the model.
391 return false;
392 }
393
394 PushString(resource_content);
395 return true;
396 }
397
HandleAndroidStringResources()398 int JniLuaEnvironment::HandleAndroidStringResources() {
399 // Check whether the requested resource can be served from the model data.
400 if (LookupModelStringResource()) {
401 return 1;
402 }
403
404 // Get system resources if not previously retrieved.
405 if (!RetrieveSystemResources()) {
406 TC3_LOG(ERROR) << "Error retrieving system resources.";
407 lua_error(state_);
408 return 0;
409 }
410
411 int resource_id;
412 switch (lua_type(state_, kIndexStackTop)) {
413 case LUA_TNUMBER:
414 resource_id = Read<int>(/*index=*/kIndexStackTop);
415 break;
416 case LUA_TSTRING: {
417 const StringPiece resource_name_str = ReadString(kIndexStackTop);
418 if (resource_name_str.empty()) {
419 TC3_LOG(ERROR) << "No resource name provided.";
420 lua_error(state_);
421 return 0;
422 }
423 const StatusOr<ScopedLocalRef<jstring>> status_or_resource_name =
424 jni_cache_->ConvertToJavaString(resource_name_str);
425 if (!status_or_resource_name.ok()) {
426 TC3_LOG(ERROR) << "Invalid resource name.";
427 lua_error(state_);
428 return 0;
429 }
430 StatusOr<int> status_or_resource_id = JniHelper::CallIntMethod(
431 jenv_, system_resources_.get(), jni_cache_->resources_get_identifier,
432 status_or_resource_name.ValueOrDie().get(), string_.get(),
433 android_.get());
434 if (!status_or_resource_id.ok()) {
435 TC3_LOG(ERROR) << "Error calling getIdentifier.";
436 lua_error(state_);
437 return 0;
438 }
439 resource_id = status_or_resource_id.ValueOrDie();
440 break;
441 }
442 default:
443 TC3_LOG(ERROR) << "Unexpected type for resource lookup.";
444 lua_error(state_);
445 return 0;
446 }
447 if (resource_id == 0) {
448 TC3_LOG(ERROR) << "Resource not found.";
449 lua_pushnil(state_);
450 return 1;
451 }
452 StatusOr<ScopedLocalRef<jstring>> status_or_resource_str =
453 JniHelper::CallObjectMethod<jstring>(jenv_, system_resources_.get(),
454 jni_cache_->resources_get_string,
455 resource_id);
456 if (!status_or_resource_str.ok()) {
457 TC3_LOG(ERROR) << "Error calling getString.";
458 lua_error(state_);
459 return 0;
460 }
461
462 if (status_or_resource_str.ValueOrDie() == nullptr) {
463 lua_pushnil(state_);
464 } else {
465 StatusOr<std::string> status_or_resource_std_str =
466 JStringToUtf8String(jenv_, status_or_resource_str.ValueOrDie().get());
467 if (!status_or_resource_std_str.ok()) {
468 lua_error(state_);
469 return 0;
470 }
471 PushString(status_or_resource_std_str.ValueOrDie());
472 }
473 return 1;
474 }
475
RetrieveSystemResources()476 bool JniLuaEnvironment::RetrieveSystemResources() {
477 if (system_resources_resources_retrieved_) {
478 return (system_resources_ != nullptr);
479 }
480 system_resources_resources_retrieved_ = true;
481 TC3_ASSIGN_OR_RETURN_FALSE(ScopedLocalRef<jobject> system_resources_ref,
482 JniHelper::CallStaticObjectMethod(
483 jenv_, jni_cache_->resources_class.get(),
484 jni_cache_->resources_get_system));
485 system_resources_ =
486 MakeGlobalRef(system_resources_ref.get(), jenv_, jni_cache_->jvm);
487 return (system_resources_ != nullptr);
488 }
489
RetrieveUserManager()490 bool JniLuaEnvironment::RetrieveUserManager() {
491 if (context_ == nullptr) {
492 return false;
493 }
494 if (usermanager_retrieved_) {
495 return (usermanager_ != nullptr);
496 }
497 usermanager_retrieved_ = true;
498 TC3_ASSIGN_OR_RETURN_FALSE(const ScopedLocalRef<jstring> service,
499 JniHelper::NewStringUTF(jenv_, "user"));
500 TC3_ASSIGN_OR_RETURN_FALSE(
501 const ScopedLocalRef<jobject> usermanager_ref,
502 JniHelper::CallObjectMethod(jenv_, context_,
503 jni_cache_->context_get_system_service,
504 service.get()));
505
506 usermanager_ = MakeGlobalRef(usermanager_ref.get(), jenv_, jni_cache_->jvm);
507 return (usermanager_ != nullptr);
508 }
509
ReadRemoteActionTemplateResult() const510 RemoteActionTemplate JniLuaEnvironment::ReadRemoteActionTemplateResult() const {
511 RemoteActionTemplate result;
512 // Read intent template.
513 lua_pushnil(state_);
514 while (Next(/*index=*/-2)) {
515 const StringPiece key = ReadString(/*index=*/-2);
516 if (key.Equals("title_without_entity")) {
517 result.title_without_entity = Read<std::string>(/*index=*/kIndexStackTop);
518 } else if (key.Equals("title_with_entity")) {
519 result.title_with_entity = Read<std::string>(/*index=*/kIndexStackTop);
520 } else if (key.Equals("description")) {
521 result.description = Read<std::string>(/*index=*/kIndexStackTop);
522 } else if (key.Equals("description_with_app_name")) {
523 result.description_with_app_name =
524 Read<std::string>(/*index=*/kIndexStackTop);
525 } else if (key.Equals("action")) {
526 result.action = Read<std::string>(/*index=*/kIndexStackTop);
527 } else if (key.Equals("data")) {
528 result.data = Read<std::string>(/*index=*/kIndexStackTop);
529 } else if (key.Equals("type")) {
530 result.type = Read<std::string>(/*index=*/kIndexStackTop);
531 } else if (key.Equals("flags")) {
532 result.flags = Read<int>(/*index=*/kIndexStackTop);
533 } else if (key.Equals("package_name")) {
534 result.package_name = Read<std::string>(/*index=*/kIndexStackTop);
535 } else if (key.Equals("request_code")) {
536 result.request_code = Read<int>(/*index=*/kIndexStackTop);
537 } else if (key.Equals("category")) {
538 result.category = ReadVector<std::string>(/*index=*/kIndexStackTop);
539 } else if (key.Equals("extra")) {
540 result.extra = ReadExtras();
541 } else {
542 TC3_LOG(INFO) << "Unknown entry: " << key;
543 }
544 lua_pop(state_, 1);
545 }
546 lua_pop(state_, 1);
547 return result;
548 }
549
ReadExtras() const550 std::map<std::string, Variant> JniLuaEnvironment::ReadExtras() const {
551 if (lua_type(state_, kIndexStackTop) != LUA_TTABLE) {
552 TC3_LOG(ERROR) << "Expected extras table, got: "
553 << lua_type(state_, kIndexStackTop);
554 lua_pop(state_, 1);
555 return {};
556 }
557 std::map<std::string, Variant> extras;
558 lua_pushnil(state_);
559 while (Next(/*index=*/-2)) {
560 // Each entry is a table specifying name and value.
561 // The value is specified via a type specific field as Lua doesn't allow
562 // to easily distinguish between different number types.
563 if (lua_type(state_, kIndexStackTop) != LUA_TTABLE) {
564 TC3_LOG(ERROR) << "Expected a table for an extra, got: "
565 << lua_type(state_, kIndexStackTop);
566 lua_pop(state_, 1);
567 return {};
568 }
569 std::string name;
570 Variant value;
571
572 lua_pushnil(state_);
573 while (Next(/*index=*/-2)) {
574 const StringPiece key = ReadString(/*index=*/-2);
575 if (key.Equals("name")) {
576 name = Read<std::string>(/*index=*/kIndexStackTop);
577 } else if (key.Equals("int_value")) {
578 value = Variant(Read<int>(/*index=*/kIndexStackTop));
579 } else if (key.Equals("long_value")) {
580 value = Variant(Read<int64>(/*index=*/kIndexStackTop));
581 } else if (key.Equals("float_value")) {
582 value = Variant(Read<float>(/*index=*/kIndexStackTop));
583 } else if (key.Equals("bool_value")) {
584 value = Variant(Read<bool>(/*index=*/kIndexStackTop));
585 } else if (key.Equals("string_value")) {
586 value = Variant(Read<std::string>(/*index=*/kIndexStackTop));
587 } else if (key.Equals("string_array_value")) {
588 value = Variant(ReadVector<std::string>(/*index=*/kIndexStackTop));
589 } else if (key.Equals("float_array_value")) {
590 value = Variant(ReadVector<float>(/*index=*/kIndexStackTop));
591 } else if (key.Equals("int_array_value")) {
592 value = Variant(ReadVector<int>(/*index=*/kIndexStackTop));
593 } else if (key.Equals("named_variant_array_value")) {
594 value = Variant(ReadExtras());
595 } else {
596 TC3_LOG(INFO) << "Unknown extra field: " << key;
597 }
598 lua_pop(state_, 1);
599 }
600 if (!name.empty()) {
601 extras[name] = value;
602 } else {
603 TC3_LOG(ERROR) << "Unnamed extra entry. Skipping.";
604 }
605 lua_pop(state_, 1);
606 }
607 return extras;
608 }
609
ReadRemoteActionTemplates(std::vector<RemoteActionTemplate> * result)610 int JniLuaEnvironment::ReadRemoteActionTemplates(
611 std::vector<RemoteActionTemplate>* result) {
612 // Read result.
613 if (lua_type(state_, kIndexStackTop) != LUA_TTABLE) {
614 TC3_LOG(ERROR) << "Unexpected result for snippet: "
615 << lua_type(state_, kIndexStackTop);
616 lua_error(state_);
617 return LUA_ERRRUN;
618 }
619
620 // Read remote action templates array.
621 lua_pushnil(state_);
622 while (Next(/*index=*/-2)) {
623 if (lua_type(state_, kIndexStackTop) != LUA_TTABLE) {
624 TC3_LOG(ERROR) << "Expected intent table, got: "
625 << lua_type(state_, kIndexStackTop);
626 lua_pop(state_, 1);
627 continue;
628 }
629 result->push_back(ReadRemoteActionTemplateResult());
630 }
631 lua_pop(state_, /*n=*/1);
632 return LUA_OK;
633 }
634
RunIntentGenerator(const std::string & generator_snippet,std::vector<RemoteActionTemplate> * remote_actions)635 bool JniLuaEnvironment::RunIntentGenerator(
636 const std::string& generator_snippet,
637 std::vector<RemoteActionTemplate>* remote_actions) {
638 int status;
639 status = luaL_loadbuffer(state_, generator_snippet.data(),
640 generator_snippet.size(),
641 /*name=*/nullptr);
642 if (status != LUA_OK) {
643 TC3_LOG(ERROR) << "Couldn't load generator snippet: " << status;
644 return false;
645 }
646 status = lua_pcall(state_, /*nargs=*/0, /*nresults=*/1, /*errfunc=*/0);
647 if (status != LUA_OK) {
648 TC3_LOG(ERROR) << "Couldn't run generator snippet: " << status;
649 return false;
650 }
651 if (RunProtected(
652 [this, remote_actions] {
653 return ReadRemoteActionTemplates(remote_actions);
654 },
655 /*num_args=*/1) != LUA_OK) {
656 TC3_LOG(ERROR) << "Could not read results.";
657 return false;
658 }
659 // Check that we correctly cleaned-up the state.
660 const int stack_size = lua_gettop(state_);
661 if (stack_size > 0) {
662 TC3_LOG(ERROR) << "Unexpected stack size.";
663 lua_settop(state_, 0);
664 return false;
665 }
666 return true;
667 }
668
669 } // namespace libtextclassifier3
670