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