xref: /aosp_15_r20/art/tools/hiddenapi/hiddenapi_test.cc (revision 795d594fd825385562da6b089ea9b2033f3abf5a)
1 /*
2  * Copyright (C) 2017 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 <fstream>
18 
19 #include "android-base/strings.h"
20 
21 #include "base/unix_file/fd_file.h"
22 #include "base/zip_archive.h"
23 #include "common_runtime_test.h"
24 #include "dex/art_dex_file_loader.h"
25 #include "dex/class_accessor-inl.h"
26 #include "dex/dex_file-inl.h"
27 #include "exec_utils.h"
28 
29 namespace art {
30 
31 class HiddenApiTest : public CommonRuntimeTest {
32  protected:
GetHiddenApiCmd()33   std::string GetHiddenApiCmd() {
34     std::string file_path = GetArtBinDir() + "/hiddenapi";
35     if (kIsDebugBuild) {
36       file_path += 'd';
37     }
38     if (!OS::FileExists(file_path.c_str())) {
39       LOG(FATAL) << "Could not find binary " << file_path;
40       UNREACHABLE();
41     }
42     return file_path;
43   }
44 
RunHiddenapiEncode(const ScratchFile & flags_csv,const std::vector<std::string> & extra_args,const ScratchFile & out_dex)45   std::unique_ptr<const DexFile> RunHiddenapiEncode(const ScratchFile& flags_csv,
46                                                     const std::vector<std::string>& extra_args,
47                                                     const ScratchFile& out_dex) {
48     std::string error;
49     ScratchFile in_dex;
50     std::unique_ptr<ZipArchive> jar(
51         ZipArchive::Open(GetTestDexFileName("HiddenApi").c_str(), &error));
52     if (jar == nullptr) {
53       LOG(FATAL) << "Could not open test file " << GetTestDexFileName("HiddenApi") << ": " << error;
54       UNREACHABLE();
55     }
56     std::unique_ptr<ZipEntry> jar_classes_dex(jar->Find("classes.dex", &error));
57     if (jar_classes_dex == nullptr) {
58       LOG(FATAL) << "Could not find classes.dex in test file " << GetTestDexFileName("HiddenApi")
59                  << ": " << error;
60       UNREACHABLE();
61     } else if (!jar_classes_dex->ExtractToFile(*in_dex.GetFile(), &error)) {
62       LOG(FATAL) << "Could not extract classes.dex from test file "
63                  << GetTestDexFileName("HiddenApi") << ": " << error;
64       UNREACHABLE();
65     }
66 
67     std::vector<std::string> argv_str;
68     argv_str.push_back(GetHiddenApiCmd());
69     argv_str.push_back("encode");
70     argv_str.insert(argv_str.end(), extra_args.begin(), extra_args.end());
71     argv_str.push_back("--input-dex=" + in_dex.GetFilename());
72     argv_str.push_back("--output-dex=" + out_dex.GetFilename());
73     argv_str.push_back("--api-flags=" + flags_csv.GetFilename());
74     argv_str.push_back("--no-force-assign-all");
75     int return_code = ExecAndReturnCode(argv_str, &error);
76     if (return_code == 0) {
77       return OpenDex(out_dex);
78     } else {
79       LOG(ERROR) << "HiddenApi binary exited with unexpected return code " << return_code;
80       return nullptr;
81     }
82   }
83 
RunHiddenapiList(const ScratchFile & out_flags_csv)84   bool RunHiddenapiList(const ScratchFile& out_flags_csv) {
85     std::string error;
86     std::string boot_jar = GetTestDexFileName("HiddenApi");
87     std::string stub_jar = GetTestDexFileName("HiddenApiStubs");
88     std::string boot_cp = android::base::Join(GetLibCoreDexFileNames(), ":");
89 
90     std::vector<std::string> argv_str;
91     argv_str.push_back(GetHiddenApiCmd());
92     argv_str.push_back("list");
93     for (const std::string& core_jar : GetLibCoreDexFileNames()) {
94       argv_str.push_back("--boot-dex=" + core_jar);
95     }
96     argv_str.push_back("--boot-dex=" + boot_jar);
97     argv_str.push_back("--public-stub-classpath=" + boot_cp + ":" + stub_jar);
98     argv_str.push_back("--out-api-flags=" + out_flags_csv.GetFilename());
99     int return_code = ExecAndReturnCode(argv_str, &error);
100     if (return_code == 0) {
101       return true;
102     } else {
103       LOG(ERROR) << "HiddenApi binary exited with unexpected return code " << return_code;
104       return false;
105     }
106   }
107 
OpenDex(const ScratchFile & file)108   std::unique_ptr<const DexFile> OpenDex(const ScratchFile& file) {
109     std::string error_msg;
110 
111     File fd(file.GetFilename(), O_RDONLY, /* check_usage= */ false);
112     if (fd.Fd() == -1) {
113       PLOG(FATAL) << "Unable to open file '" << file.GetFilename() << "'";
114       UNREACHABLE();
115     }
116 
117     ArtDexFileLoader dex_loader(&fd, file.GetFilename());
118     std::unique_ptr<const DexFile> dex_file(dex_loader.Open(
119         /*location_checksum=*/0,
120         /*verify=*/true,
121         /*verify_checksum=*/true,
122         &error_msg));
123     if (dex_file.get() == nullptr) {
124       LOG(FATAL) << "Open failed for '" << file.GetFilename() << "' " << error_msg;
125       UNREACHABLE();
126     } else if (!dex_file->IsStandardDexFile()) {
127       LOG(FATAL) << "Expected a standard dex file '" << file.GetFilename() << "'";
128       UNREACHABLE();
129     }
130 
131     return dex_file;
132   }
133 
OpenStream(const ScratchFile & file)134   std::ofstream OpenStream(const ScratchFile& file) {
135     std::ofstream ofs(file.GetFilename(), std::ofstream::out);
136     if (ofs.fail()) {
137       PLOG(FATAL) << "Open failed for '" << file.GetFilename() << "'";
138       UNREACHABLE();
139     }
140     return ofs;
141   }
142 
ReadFlagsCsvFile(const ScratchFile & file)143   std::map<std::string, std::string> ReadFlagsCsvFile(const ScratchFile& file) {
144     std::ifstream ifs(file.GetFilename());
145     std::map<std::string, std::string> flags;
146 
147     for (std::string line; std::getline(ifs, line);) {
148       std::size_t comma = line.find(',');
149       if (comma == std::string::npos) {
150         flags.emplace(line, "");
151       } else {
152         flags.emplace(line.substr(0, comma), line.substr(comma + 1));
153       }
154     }
155 
156     return flags;
157   }
158 
SafeMapGet(const std::string & key,const std::map<std::string,std::string> & map)159   std::string SafeMapGet(const std::string& key, const std::map<std::string, std::string>& map) {
160     auto it = map.find(key);
161     if (it == map.end()) {
162       LOG(FATAL) << "Key not found: " << key;
163       UNREACHABLE();
164     }
165     return it->second;
166   }
167 
FindClass(const char * desc,const DexFile & dex_file)168   const dex::ClassDef& FindClass(const char* desc, const DexFile& dex_file) {
169     const dex::TypeId* type_id = dex_file.FindTypeId(desc);
170     CHECK(type_id != nullptr) << "Could not find class " << desc;
171     const dex::ClassDef* found = dex_file.FindClassDef(dex_file.GetIndexForTypeId(*type_id));
172     CHECK(found != nullptr) << "Could not find class " << desc;
173     return *found;
174   }
175 
GetFieldHiddenFlags(const char * name,uint32_t expected_visibility,const dex::ClassDef & class_def,const DexFile & dex_file)176   hiddenapi::ApiList GetFieldHiddenFlags(const char* name,
177                                          uint32_t expected_visibility,
178                                          const dex::ClassDef& class_def,
179                                          const DexFile& dex_file) {
180     ClassAccessor accessor(dex_file, class_def, /* parse hiddenapi flags */ true);
181     CHECK(accessor.HasClassData()) << "Class " << accessor.GetDescriptor() << " has no data";
182 
183     if (!accessor.HasHiddenapiClassData()) {
184       return hiddenapi::ApiList::Sdk();
185     }
186 
187     for (const ClassAccessor::Field& field : accessor.GetFields()) {
188       const dex::FieldId& fid = dex_file.GetFieldId(field.GetIndex());
189       if (strcmp(name, dex_file.GetFieldName(fid)) == 0) {
190         const uint32_t actual_visibility = field.GetAccessFlags() & kAccVisibilityFlags;
191         CHECK_EQ(actual_visibility, expected_visibility)
192             << "Field " << name << " in class " << accessor.GetDescriptor();
193         return hiddenapi::ApiList(field.GetHiddenapiFlags());
194       }
195     }
196 
197     LOG(FATAL) << "Could not find field " << name << " in class "
198                << dex_file.GetClassDescriptor(class_def);
199     UNREACHABLE();
200   }
201 
GetMethodHiddenFlags(const char * name,uint32_t expected_visibility,bool expected_native,const dex::ClassDef & class_def,const DexFile & dex_file)202   hiddenapi::ApiList GetMethodHiddenFlags(const char* name,
203                                           uint32_t expected_visibility,
204                                           bool expected_native,
205                                           const dex::ClassDef& class_def,
206                                           const DexFile& dex_file) {
207     ClassAccessor accessor(dex_file, class_def, /* parse hiddenapi flags */ true);
208     CHECK(accessor.HasClassData()) << "Class " << accessor.GetDescriptor() << " has no data";
209 
210     if (!accessor.HasHiddenapiClassData()) {
211       return hiddenapi::ApiList::Sdk();
212     }
213 
214     for (const ClassAccessor::Method& method : accessor.GetMethods()) {
215       const dex::MethodId& mid = dex_file.GetMethodId(method.GetIndex());
216       if (strcmp(name, dex_file.GetMethodName(mid)) == 0) {
217         CHECK_EQ(expected_native, method.MemberIsNative())
218             << "Method " << name << " in class " << accessor.GetDescriptor();
219         const uint32_t actual_visibility = method.GetAccessFlags() & kAccVisibilityFlags;
220         CHECK_EQ(actual_visibility, expected_visibility)
221             << "Method " << name << " in class " << accessor.GetDescriptor();
222         return hiddenapi::ApiList(method.GetHiddenapiFlags());
223       }
224     }
225 
226     LOG(FATAL) << "Could not find method " << name << " in class "
227                << dex_file.GetClassDescriptor(class_def);
228     UNREACHABLE();
229   }
230 
GetIFieldHiddenFlags(const DexFile & dex_file)231   hiddenapi::ApiList GetIFieldHiddenFlags(const DexFile& dex_file) {
232     return GetFieldHiddenFlags("ifield", kAccPublic, FindClass("LMain;", dex_file), dex_file);
233   }
234 
GetSFieldHiddenFlags(const DexFile & dex_file)235   hiddenapi::ApiList GetSFieldHiddenFlags(const DexFile& dex_file) {
236     return GetFieldHiddenFlags("sfield", kAccPrivate, FindClass("LMain;", dex_file), dex_file);
237   }
238 
GetIMethodHiddenFlags(const DexFile & dex_file)239   hiddenapi::ApiList GetIMethodHiddenFlags(const DexFile& dex_file) {
240     return GetMethodHiddenFlags(
241         "imethod", 0, /* expected_native= */ false, FindClass("LMain;", dex_file), dex_file);
242   }
243 
GetSMethodHiddenFlags(const DexFile & dex_file)244   hiddenapi::ApiList GetSMethodHiddenFlags(const DexFile& dex_file) {
245     return GetMethodHiddenFlags("smethod",
246                                 kAccPublic,
247                                 /* expected_native= */ false,
248                                 FindClass("LMain;", dex_file),
249                                 dex_file);
250   }
251 
GetINMethodHiddenFlags(const DexFile & dex_file)252   hiddenapi::ApiList GetINMethodHiddenFlags(const DexFile& dex_file) {
253     return GetMethodHiddenFlags("inmethod",
254                                 kAccPublic,
255                                 /* expected_native= */ true,
256                                 FindClass("LMain;", dex_file),
257                                 dex_file);
258   }
259 
GetSNMethodHiddenFlags(const DexFile & dex_file)260   hiddenapi::ApiList GetSNMethodHiddenFlags(const DexFile& dex_file) {
261     return GetMethodHiddenFlags("snmethod",
262                                 kAccProtected,
263                                 /* expected_native= */ true,
264                                 FindClass("LMain;", dex_file),
265                                 dex_file);
266   }
267 };
268 
TEST_F(HiddenApiTest,InstanceFieldNoMatch)269 TEST_F(HiddenApiTest, InstanceFieldNoMatch) {
270   ScratchFile dex, flags_csv;
271   OpenStream(flags_csv)
272       << "LMain;->ifield:LBadType1;,unsupported" << std::endl
273       << "LMain;->ifield:LBadType2;,max-target-o" << std::endl
274       << "LMain;->ifield:LBadType3;,blocked" << std::endl;
275   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
276   ASSERT_NE(dex_file.get(), nullptr);
277   ASSERT_EQ(hiddenapi::ApiList::Sdk(), GetIFieldHiddenFlags(*dex_file));
278 }
279 
TEST_F(HiddenApiTest,InstanceFieldLightunsupportedMatch)280 TEST_F(HiddenApiTest, InstanceFieldLightunsupportedMatch) {
281   ScratchFile dex, flags_csv;
282   OpenStream(flags_csv)
283       << "LMain;->ifield:I,unsupported" << std::endl
284       << "LMain;->ifield:LBadType2;,max-target-o" << std::endl
285       << "LMain;->ifield:LBadType3;,blocked" << std::endl;
286   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
287   ASSERT_NE(dex_file.get(), nullptr);
288   ASSERT_EQ(hiddenapi::ApiList::Unsupported(), GetIFieldHiddenFlags(*dex_file));
289 }
290 
TEST_F(HiddenApiTest,InstanceFieldDarkunsupportedMatch)291 TEST_F(HiddenApiTest, InstanceFieldDarkunsupportedMatch) {
292   ScratchFile dex, flags_csv;
293   OpenStream(flags_csv)
294       << "LMain;->ifield:LBadType1;,unsupported" << std::endl
295       << "LMain;->ifield:I,max-target-o" << std::endl
296       << "LMain;->ifield:LBadType3;,blocked" << std::endl;
297   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
298   ASSERT_NE(dex_file.get(), nullptr);
299   ASSERT_EQ(hiddenapi::ApiList::MaxTargetO(), GetIFieldHiddenFlags(*dex_file));
300 }
301 
TEST_F(HiddenApiTest,InstanceFieldblockedMatch)302 TEST_F(HiddenApiTest, InstanceFieldblockedMatch) {
303   ScratchFile dex, flags_csv;
304   OpenStream(flags_csv)
305       << "LMain;->ifield:LBadType1;,unsupported" << std::endl
306       << "LMain;->ifield:LBadType2;,max-target-o" << std::endl
307       << "LMain;->ifield:I,blocked" << std::endl;
308   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
309   ASSERT_NE(dex_file.get(), nullptr);
310   ASSERT_EQ(hiddenapi::ApiList::Blocked(), GetIFieldHiddenFlags(*dex_file));
311 }
312 
TEST_F(HiddenApiTest,InstanceFieldTwoListsMatch1)313 TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch1) {
314   ScratchFile dex, flags_csv;
315   OpenStream(flags_csv)
316       << "LMain;->ifield:LBadType1;,unsupported" << std::endl
317       << "LMain;->ifield:I,blocked,max-target-o" << std::endl;
318   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
319   ASSERT_EQ(dex_file.get(), nullptr);
320 }
321 
TEST_F(HiddenApiTest,InstanceFieldTwoListsMatch2)322 TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch2) {
323   ScratchFile dex, flags_csv;
324   OpenStream(flags_csv)
325       << "LMain;->ifield:LBadType2;,max-target-o" << std::endl
326       << "LMain;->ifield:I,blocked,unsupported" << std::endl;
327   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
328   ASSERT_EQ(dex_file.get(), nullptr);
329 }
330 
TEST_F(HiddenApiTest,InstanceFieldTwoListsMatch3)331 TEST_F(HiddenApiTest, InstanceFieldTwoListsMatch3) {
332   ScratchFile dex, flags_csv;
333   OpenStream(flags_csv)
334       << "LMain;->ifield:I,unsupported,max-target-o" << std::endl
335       << "LMain;->ifield:LBadType3;,blocked" << std::endl;
336   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
337   ASSERT_EQ(dex_file.get(), nullptr);
338 }
339 
TEST_F(HiddenApiTest,StaticFieldNoMatch)340 TEST_F(HiddenApiTest, StaticFieldNoMatch) {
341   ScratchFile dex, flags_csv;
342   OpenStream(flags_csv)
343       << "LMain;->sfield:LBadType1;,unsupported" << std::endl
344       << "LMain;->sfield:LBadType2;,max-target-o" << std::endl
345       << "LMain;->sfield:LBadType3;,blocked" << std::endl;
346   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
347   ASSERT_NE(dex_file.get(), nullptr);
348   ASSERT_EQ(hiddenapi::ApiList::Sdk(), GetSFieldHiddenFlags(*dex_file));
349 }
350 
TEST_F(HiddenApiTest,StaticFieldLightunsupportedMatch)351 TEST_F(HiddenApiTest, StaticFieldLightunsupportedMatch) {
352   ScratchFile dex, flags_csv;
353   OpenStream(flags_csv)
354       << "LMain;->sfield:Ljava/lang/Object;,unsupported" << std::endl
355       << "LMain;->sfield:LBadType2;,max-target-o" << std::endl
356       << "LMain;->sfield:LBadType3;,blocked" << std::endl;
357   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
358   ASSERT_NE(dex_file.get(), nullptr);
359   ASSERT_EQ(hiddenapi::ApiList::Unsupported(), GetSFieldHiddenFlags(*dex_file));
360 }
361 
TEST_F(HiddenApiTest,StaticFieldDarkunsupportedMatch)362 TEST_F(HiddenApiTest, StaticFieldDarkunsupportedMatch) {
363   ScratchFile dex, flags_csv;
364   OpenStream(flags_csv)
365       << "LMain;->sfield:LBadType1;,unsupported" << std::endl
366       << "LMain;->sfield:Ljava/lang/Object;,max-target-o" << std::endl
367       << "LMain;->sfield:LBadType3;,blocked" << std::endl;
368   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
369   ASSERT_NE(dex_file.get(), nullptr);
370   ASSERT_EQ(hiddenapi::ApiList::MaxTargetO(), GetSFieldHiddenFlags(*dex_file));
371 }
372 
TEST_F(HiddenApiTest,StaticFieldblockedMatch)373 TEST_F(HiddenApiTest, StaticFieldblockedMatch) {
374   ScratchFile dex, flags_csv;
375   OpenStream(flags_csv)
376       << "LMain;->sfield:LBadType1;,unsupported" << std::endl
377       << "LMain;->sfield:LBadType2;,max-target-o" << std::endl
378       << "LMain;->sfield:Ljava/lang/Object;,blocked" << std::endl;
379   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
380   ASSERT_NE(dex_file.get(), nullptr);
381   ASSERT_EQ(hiddenapi::ApiList::Blocked(), GetSFieldHiddenFlags(*dex_file));
382 }
383 
TEST_F(HiddenApiTest,StaticFieldTwoListsMatch1)384 TEST_F(HiddenApiTest, StaticFieldTwoListsMatch1) {
385   ScratchFile dex, flags_csv;
386   OpenStream(flags_csv)
387       << "LMain;->sfield:LBadType1;,unsupported" << std::endl
388       << "LMain;->sfield:Ljava/lang/Object;,blocked,max-target-o" << std::endl;
389   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
390   ASSERT_EQ(dex_file.get(), nullptr);
391 }
392 
TEST_F(HiddenApiTest,StaticFieldTwoListsMatch2)393 TEST_F(HiddenApiTest, StaticFieldTwoListsMatch2) {
394   ScratchFile dex, flags_csv;
395   OpenStream(flags_csv)
396       << "LMain;->sfield:LBadType2;,max-target-o" << std::endl
397       << "LMain;->sfield:Ljava/lang/Object;,blocked,unsupported" << std::endl;
398   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
399   ASSERT_EQ(dex_file.get(), nullptr);
400 }
401 
TEST_F(HiddenApiTest,StaticFieldTwoListsMatch3)402 TEST_F(HiddenApiTest, StaticFieldTwoListsMatch3) {
403   ScratchFile dex, flags_csv;
404   OpenStream(flags_csv)
405       << "LMain;->sfield:Ljava/lang/Object;,unsupported,max-target-o" << std::endl
406       << "LMain;->sfield:LBadType3;,blocked" << std::endl;
407   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
408   ASSERT_EQ(dex_file.get(), nullptr);
409 }
410 
TEST_F(HiddenApiTest,InstanceMethodNoMatch)411 TEST_F(HiddenApiTest, InstanceMethodNoMatch) {
412   ScratchFile dex, flags_csv;
413   OpenStream(flags_csv)
414       << "LMain;->imethod(LBadType1;)V,unsupported" << std::endl
415       << "LMain;->imethod(LBadType2;)V,max-target-o" << std::endl
416       << "LMain;->imethod(LBadType3;)V,blocked" << std::endl;
417   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
418   ASSERT_NE(dex_file.get(), nullptr);
419   ASSERT_EQ(hiddenapi::ApiList::Sdk(), GetIMethodHiddenFlags(*dex_file));
420 }
421 
TEST_F(HiddenApiTest,InstanceMethodLightunsupportedMatch)422 TEST_F(HiddenApiTest, InstanceMethodLightunsupportedMatch) {
423   ScratchFile dex, flags_csv;
424   OpenStream(flags_csv)
425       << "LMain;->imethod(J)V,unsupported" << std::endl
426       << "LMain;->imethod(LBadType2;)V,max-target-o" << std::endl
427       << "LMain;->imethod(LBadType3;)V,blocked" << std::endl;
428   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
429   ASSERT_NE(dex_file.get(), nullptr);
430   ASSERT_EQ(hiddenapi::ApiList::Unsupported(), GetIMethodHiddenFlags(*dex_file));
431 }
432 
TEST_F(HiddenApiTest,InstanceMethodDarkunsupportedMatch)433 TEST_F(HiddenApiTest, InstanceMethodDarkunsupportedMatch) {
434   ScratchFile dex, flags_csv;
435   OpenStream(flags_csv)
436       << "LMain;->imethod(LBadType1;)V,unsupported" << std::endl
437       << "LMain;->imethod(J)V,max-target-o" << std::endl
438       << "LMain;->imethod(LBadType3;)V,blocked" << std::endl;
439   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
440   ASSERT_NE(dex_file.get(), nullptr);
441   ASSERT_EQ(hiddenapi::ApiList::MaxTargetO(), GetIMethodHiddenFlags(*dex_file));
442 }
443 
TEST_F(HiddenApiTest,InstanceMethodblockedMatch)444 TEST_F(HiddenApiTest, InstanceMethodblockedMatch) {
445   ScratchFile dex, flags_csv;
446   OpenStream(flags_csv)
447       << "LMain;->imethod(LBadType1;)V,unsupported" << std::endl
448       << "LMain;->imethod(LBadType2;)V,max-target-o" << std::endl
449       << "LMain;->imethod(J)V,blocked" << std::endl;
450   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
451   ASSERT_NE(dex_file.get(), nullptr);
452   ASSERT_EQ(hiddenapi::ApiList::Blocked(), GetIMethodHiddenFlags(*dex_file));
453 }
454 
TEST_F(HiddenApiTest,InstanceMethodTwoListsMatch1)455 TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch1) {
456   ScratchFile dex, flags_csv;
457   OpenStream(flags_csv)
458       << "LMain;->imethod(LBadType1;)V,unsupported" << std::endl
459       << "LMain;->imethod(J)V,blocked,max-target-o" << std::endl;
460   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
461   ASSERT_EQ(dex_file.get(), nullptr);
462 }
463 
TEST_F(HiddenApiTest,InstanceMethodTwoListsMatch2)464 TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch2) {
465   ScratchFile dex, flags_csv;
466   OpenStream(flags_csv)
467       << "LMain;->imethod(LBadType2;)V,max-target-o" << std::endl
468       << "LMain;->imethod(J)V,blocked,unsupported" << std::endl;
469   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
470   ASSERT_EQ(dex_file.get(), nullptr);
471 }
472 
TEST_F(HiddenApiTest,InstanceMethodTwoListsMatch3)473 TEST_F(HiddenApiTest, InstanceMethodTwoListsMatch3) {
474   ScratchFile dex, flags_csv;
475   OpenStream(flags_csv)
476       << "LMain;->imethod(J)V,unsupported,max-target-o" << std::endl
477       << "LMain;->imethod(LBadType3;)V,blocked" << std::endl;
478   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
479   ASSERT_EQ(dex_file.get(), nullptr);
480 }
481 
TEST_F(HiddenApiTest,StaticMethodNoMatch)482 TEST_F(HiddenApiTest, StaticMethodNoMatch) {
483   ScratchFile dex, flags_csv;
484   OpenStream(flags_csv)
485       << "LMain;->smethod(LBadType1;)V,unsupported" << std::endl
486       << "LMain;->smethod(LBadType2;)V,max-target-o" << std::endl
487       << "LMain;->smethod(LBadType3;)V,blocked" << std::endl;
488   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
489   ASSERT_NE(dex_file.get(), nullptr);
490   ASSERT_EQ(hiddenapi::ApiList::Sdk(), GetSMethodHiddenFlags(*dex_file));
491 }
492 
TEST_F(HiddenApiTest,StaticMethodLightunsupportedMatch)493 TEST_F(HiddenApiTest, StaticMethodLightunsupportedMatch) {
494   ScratchFile dex, flags_csv;
495   OpenStream(flags_csv)
496       << "LMain;->smethod(Ljava/lang/Object;)V,unsupported" << std::endl
497       << "LMain;->smethod(LBadType2;)V,max-target-o" << std::endl
498       << "LMain;->smethod(LBadType3;)V,blocked" << std::endl;
499   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
500   ASSERT_NE(dex_file.get(), nullptr);
501   ASSERT_EQ(hiddenapi::ApiList::Unsupported(), GetSMethodHiddenFlags(*dex_file));
502 }
503 
TEST_F(HiddenApiTest,StaticMethodDarkunsupportedMatch)504 TEST_F(HiddenApiTest, StaticMethodDarkunsupportedMatch) {
505   ScratchFile dex, flags_csv;
506   OpenStream(flags_csv)
507       << "LMain;->smethod(LBadType1;)V,unsupported" << std::endl
508       << "LMain;->smethod(Ljava/lang/Object;)V,max-target-o" << std::endl
509       << "LMain;->smethod(LBadType3;)V,blocked" << std::endl;
510   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
511   ASSERT_NE(dex_file.get(), nullptr);
512   ASSERT_EQ(hiddenapi::ApiList::MaxTargetO(), GetSMethodHiddenFlags(*dex_file));
513 }
514 
TEST_F(HiddenApiTest,StaticMethodblockedMatch)515 TEST_F(HiddenApiTest, StaticMethodblockedMatch) {
516   ScratchFile dex, flags_csv;
517   OpenStream(flags_csv)
518       << "LMain;->smethod(LBadType1;)V,unsupported" << std::endl
519       << "LMain;->smethod(LBadType2;)V,max-target-o" << std::endl
520       << "LMain;->smethod(Ljava/lang/Object;)V,blocked" << std::endl;
521   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
522   ASSERT_NE(dex_file.get(), nullptr);
523   ASSERT_EQ(hiddenapi::ApiList::Blocked(), GetSMethodHiddenFlags(*dex_file));
524 }
525 
TEST_F(HiddenApiTest,StaticMethodTwoListsMatch1)526 TEST_F(HiddenApiTest, StaticMethodTwoListsMatch1) {
527   ScratchFile dex, flags_csv;
528   OpenStream(flags_csv)
529       << "LMain;->smethod(LBadType1;)V,unsupported" << std::endl
530       << "LMain;->smethod(Ljava/lang/Object;)V,blocked,max-target-o" << std::endl;
531   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
532   ASSERT_EQ(dex_file.get(), nullptr);
533 }
534 
TEST_F(HiddenApiTest,StaticMethodTwoListsMatch2)535 TEST_F(HiddenApiTest, StaticMethodTwoListsMatch2) {
536   ScratchFile dex, flags_csv;
537   OpenStream(flags_csv)
538       << "LMain;->smethod(LBadType2;)V,max-target-o" << std::endl
539       << "LMain;->smethod(Ljava/lang/Object;)V,blocked,unsupported" << std::endl;
540   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
541   ASSERT_EQ(dex_file.get(), nullptr);
542 }
543 
TEST_F(HiddenApiTest,StaticMethodTwoListsMatch3)544 TEST_F(HiddenApiTest, StaticMethodTwoListsMatch3) {
545   ScratchFile dex, flags_csv;
546   OpenStream(flags_csv)
547       << "LMain;->smethod(Ljava/lang/Object;)V,unsupported,max-target-o" << std::endl
548       << "LMain;->smethod(LBadType3;)V,blocked" << std::endl;
549   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
550   ASSERT_EQ(dex_file.get(), nullptr);
551 }
552 
TEST_F(HiddenApiTest,InstanceNativeMethodNoMatch)553 TEST_F(HiddenApiTest, InstanceNativeMethodNoMatch) {
554   ScratchFile dex, flags_csv;
555   OpenStream(flags_csv)
556       << "LMain;->inmethod(LBadType1;)V,unsupported" << std::endl
557       << "LMain;->inmethod(LBadType2;)V,max-target-o" << std::endl
558       << "LMain;->inmethod(LBadType3;)V,blocked" << std::endl;
559   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
560   ASSERT_NE(dex_file.get(), nullptr);
561   ASSERT_EQ(hiddenapi::ApiList::Sdk(), GetINMethodHiddenFlags(*dex_file));
562 }
563 
TEST_F(HiddenApiTest,InstanceNativeMethodLightunsupportedMatch)564 TEST_F(HiddenApiTest, InstanceNativeMethodLightunsupportedMatch) {
565   ScratchFile dex, flags_csv;
566   OpenStream(flags_csv)
567       << "LMain;->inmethod(C)V,unsupported" << std::endl
568       << "LMain;->inmethod(LBadType2;)V,max-target-o" << std::endl
569       << "LMain;->inmethod(LBadType3;)V,blocked" << std::endl;
570   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
571   ASSERT_NE(dex_file.get(), nullptr);
572   ASSERT_EQ(hiddenapi::ApiList::Unsupported(), GetINMethodHiddenFlags(*dex_file));
573 }
574 
TEST_F(HiddenApiTest,InstanceNativeMethodDarkunsupportedMatch)575 TEST_F(HiddenApiTest, InstanceNativeMethodDarkunsupportedMatch) {
576   ScratchFile dex, flags_csv;
577   OpenStream(flags_csv)
578       << "LMain;->inmethod(LBadType1;)V,unsupported" << std::endl
579       << "LMain;->inmethod(C)V,max-target-o" << std::endl
580       << "LMain;->inmethod(LBadType3;)V,blocked" << std::endl;
581   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
582   ASSERT_NE(dex_file.get(), nullptr);
583   ASSERT_EQ(hiddenapi::ApiList::MaxTargetO(), GetINMethodHiddenFlags(*dex_file));
584 }
585 
TEST_F(HiddenApiTest,InstanceNativeMethodblockedMatch)586 TEST_F(HiddenApiTest, InstanceNativeMethodblockedMatch) {
587   ScratchFile dex, flags_csv;
588   OpenStream(flags_csv)
589       << "LMain;->inmethod(LBadType1;)V,unsupported" << std::endl
590       << "LMain;->inmethod(LBadType2;)V,max-target-o" << std::endl
591       << "LMain;->inmethod(C)V,blocked" << std::endl;
592   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
593   ASSERT_NE(dex_file.get(), nullptr);
594   ASSERT_EQ(hiddenapi::ApiList::Blocked(), GetINMethodHiddenFlags(*dex_file));
595 }
596 
TEST_F(HiddenApiTest,InstanceNativeMethodTwoListsMatch1)597 TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch1) {
598   ScratchFile dex, flags_csv;
599   OpenStream(flags_csv)
600       << "LMain;->inmethod(LBadType1;)V,unsupported" << std::endl
601       << "LMain;->inmethod(C)V,blocked,max-target-o" << std::endl;
602   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
603   ASSERT_EQ(dex_file.get(), nullptr);
604 }
605 
TEST_F(HiddenApiTest,InstanceNativeMethodTwoListsMatch2)606 TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch2) {
607   ScratchFile dex, flags_csv;
608   OpenStream(flags_csv)
609       << "LMain;->inmethod(C)V,blocked,unsupported" << std::endl
610       << "LMain;->inmethod(LBadType2;)V,max-target-o" << std::endl;
611   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
612   ASSERT_EQ(dex_file.get(), nullptr);
613 }
614 
TEST_F(HiddenApiTest,InstanceNativeMethodTwoListsMatch3)615 TEST_F(HiddenApiTest, InstanceNativeMethodTwoListsMatch3) {
616   ScratchFile dex, flags_csv;
617   OpenStream(flags_csv)
618       << "LMain;->inmethod(C)V,unsupported,max-target-o" << std::endl
619       << "LMain;->inmethod(LBadType3;)V,blocked" << std::endl;
620   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
621   ASSERT_EQ(dex_file.get(), nullptr);
622 }
623 
TEST_F(HiddenApiTest,StaticNativeMethodNoMatch)624 TEST_F(HiddenApiTest, StaticNativeMethodNoMatch) {
625   ScratchFile dex, flags_csv;
626   OpenStream(flags_csv)
627       << "LMain;->snmethod(LBadType1;)V,unsupported" << std::endl
628       << "LMain;->snmethod(LBadType2;)V,max-target-o" << std::endl
629       << "LMain;->snmethod(LBadType3;)V,blocked" << std::endl;
630   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
631   ASSERT_NE(dex_file.get(), nullptr);
632   ASSERT_EQ(hiddenapi::ApiList::Sdk(), GetSNMethodHiddenFlags(*dex_file));
633 }
634 
TEST_F(HiddenApiTest,StaticNativeMethodLightunsupportedMatch)635 TEST_F(HiddenApiTest, StaticNativeMethodLightunsupportedMatch) {
636   ScratchFile dex, flags_csv;
637   OpenStream(flags_csv)
638       << "LMain;->snmethod(Ljava/lang/Integer;)V,unsupported" << std::endl
639       << "LMain;->snmethod(LBadType2;)V,max-target-o" << std::endl
640       << "LMain;->snmethod(LBadType3;)V,blocked" << std::endl;
641   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
642   ASSERT_NE(dex_file.get(), nullptr);
643   ASSERT_EQ(hiddenapi::ApiList::Unsupported(), GetSNMethodHiddenFlags(*dex_file));
644 }
645 
TEST_F(HiddenApiTest,StaticNativeMethodDarkunsupportedMatch)646 TEST_F(HiddenApiTest, StaticNativeMethodDarkunsupportedMatch) {
647   ScratchFile dex, flags_csv;
648   OpenStream(flags_csv)
649       << "LMain;->snmethod(LBadType1;)V,unsupported" << std::endl
650       << "LMain;->snmethod(Ljava/lang/Integer;)V,max-target-o" << std::endl
651       << "LMain;->snmethod(LBadType3;)V,blocked" << std::endl;
652   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
653   ASSERT_NE(dex_file.get(), nullptr);
654   ASSERT_EQ(hiddenapi::ApiList::MaxTargetO(), GetSNMethodHiddenFlags(*dex_file));
655 }
656 
TEST_F(HiddenApiTest,StaticNativeMethodblockedMatch)657 TEST_F(HiddenApiTest, StaticNativeMethodblockedMatch) {
658   ScratchFile dex, flags_csv;
659   OpenStream(flags_csv)
660       << "LMain;->snmethod(LBadType1;)V,unsupported" << std::endl
661       << "LMain;->snmethod(LBadType2;)V,max-target-o" << std::endl
662       << "LMain;->snmethod(Ljava/lang/Integer;)V,blocked" << std::endl;
663   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
664   ASSERT_NE(dex_file.get(), nullptr);
665   ASSERT_EQ(hiddenapi::ApiList::Blocked(), GetSNMethodHiddenFlags(*dex_file));
666 }
667 
TEST_F(HiddenApiTest,StaticNativeMethodTwoListsMatch1)668 TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch1) {
669   ScratchFile dex, flags_csv;
670   OpenStream(flags_csv)
671       << "LMain;->snmethod(LBadType1;)V,unsupported" << std::endl
672       << "LMain;->snmethod(Ljava/lang/Integer;)V,blocked,max-target-o" << std::endl;
673   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
674   ASSERT_EQ(dex_file.get(), nullptr);
675 }
676 
TEST_F(HiddenApiTest,StaticNativeMethodTwoListsMatch2)677 TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch2) {
678   ScratchFile dex, flags_csv;
679   OpenStream(flags_csv)
680       << "LMain;->snmethod(Ljava/lang/Integer;)V,blocked,unsupported" << std::endl
681       << "LMain;->snmethod(LBadType2;)V,max-target-o" << std::endl;
682   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
683   ASSERT_EQ(dex_file.get(), nullptr);
684 }
685 
TEST_F(HiddenApiTest,StaticNativeMethodTwoListsMatch3)686 TEST_F(HiddenApiTest, StaticNativeMethodTwoListsMatch3) {
687   ScratchFile dex, flags_csv;
688   OpenStream(flags_csv)
689       << "LMain;->snmethod(Ljava/lang/Integer;)V,unsupported,max-target-o" << std::endl
690       << "LMain;->snmethod(LBadType3;)V,blocked" << std::endl;
691   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
692   ASSERT_EQ(dex_file.get(), nullptr);
693 }
694 
TEST_F(HiddenApiTest,InstanceFieldCorePlatformApiMatch)695 TEST_F(HiddenApiTest, InstanceFieldCorePlatformApiMatch) {
696   ScratchFile dex, flags_csv;
697   OpenStream(flags_csv)
698       << "LMain;->ifield:LBadType1;,unsupported" << std::endl
699       << "LMain;->ifield:LBadType2;,max-target-o" << std::endl
700       << "LMain;->ifield:I,unsupported,core-platform-api" << std::endl;
701   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
702   ASSERT_NE(dex_file.get(), nullptr);
703   ASSERT_EQ(hiddenapi::ApiList::CorePlatformApi() |
704   hiddenapi::ApiList::Unsupported(), GetIFieldHiddenFlags(*dex_file));
705 }
706 
TEST_F(HiddenApiTest,InstanceFieldTestApiMatch)707 TEST_F(HiddenApiTest, InstanceFieldTestApiMatch) {
708   ScratchFile dex, flags_csv;
709   OpenStream(flags_csv)
710       << "LMain;->ifield:LBadType1;,unsupported" << std::endl
711       << "LMain;->ifield:LBadType2;,max-target-o" << std::endl
712       << "LMain;->ifield:I,unsupported,test-api" << std::endl;
713   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
714   ASSERT_NE(dex_file.get(), nullptr);
715   ASSERT_EQ(hiddenapi::ApiList::TestApi()
716   | hiddenapi::ApiList::Unsupported(), GetIFieldHiddenFlags(*dex_file));
717 }
718 
TEST_F(HiddenApiTest,InstanceFieldUnknownFlagMatch)719 TEST_F(HiddenApiTest, InstanceFieldUnknownFlagMatch) {
720   ScratchFile dex, flags_csv;
721   OpenStream(flags_csv)
722       << "LMain;->ifield:LBadType1;,unsupported" << std::endl
723       << "LMain;->ifield:LBadType2;,max-target-o" << std::endl
724       << "LMain;->ifield:I,unsupported,unknown-flag" << std::endl;
725   auto dex_file = RunHiddenapiEncode(flags_csv, {}, dex);
726   ASSERT_EQ(dex_file.get(), nullptr);
727 }
728 
TEST_F(HiddenApiTest,InstanceFieldMaxSdkHigherThanMaxHiddenApiLevel)729 TEST_F(HiddenApiTest, InstanceFieldMaxSdkHigherThanMaxHiddenApiLevel) {
730   ScratchFile dex, flags_csv;
731   OpenStream(flags_csv)
732       << "LMain;->ifield:I,max-target-r" << std::endl;
733   auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-q"}, dex);
734   ASSERT_EQ(dex_file.get(), nullptr);
735 }
736 
TEST_F(HiddenApiTest,InstanceFieldMaxSdkEqualsMaxHiddenApiLevel)737 TEST_F(HiddenApiTest, InstanceFieldMaxSdkEqualsMaxHiddenApiLevel) {
738   ScratchFile dex, flags_csv;
739   OpenStream(flags_csv)
740       << "LMain;->ifield:I,max-target-r" << std::endl;
741   auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex);
742   ASSERT_NE(dex_file.get(), nullptr);
743   ASSERT_EQ(hiddenapi::ApiList::MaxTargetR(), GetIFieldHiddenFlags(*dex_file));
744 }
745 
TEST_F(HiddenApiTest,InstanceFieldMaxSdkLowerThanMaxHiddenApiLevel)746 TEST_F(HiddenApiTest, InstanceFieldMaxSdkLowerThanMaxHiddenApiLevel) {
747   ScratchFile dex, flags_csv;
748   OpenStream(flags_csv)
749       << "LMain;->ifield:I,max-target-q" << std::endl;
750   auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex);
751   ASSERT_NE(dex_file.get(), nullptr);
752   ASSERT_EQ(hiddenapi::ApiList::MaxTargetQ(), GetIFieldHiddenFlags(*dex_file));
753 }
754 
TEST_F(HiddenApiTest,InstanceFieldBlockedUnchangedByMaxHiddenApiLevel)755 TEST_F(HiddenApiTest, InstanceFieldBlockedUnchangedByMaxHiddenApiLevel) {
756   ScratchFile dex, flags_csv;
757   OpenStream(flags_csv)
758       << "LMain;->ifield:I,blocked" << std::endl;
759   auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex);
760   ASSERT_NE(dex_file.get(), nullptr);
761   ASSERT_EQ(hiddenapi::ApiList::Blocked(), GetIFieldHiddenFlags(*dex_file));
762 }
763 
TEST_F(HiddenApiTest,InstanceFieldUnsupportedUnchangedByMaxHiddenApiLevel)764 TEST_F(HiddenApiTest, InstanceFieldUnsupportedUnchangedByMaxHiddenApiLevel) {
765   ScratchFile dex, flags_csv;
766   OpenStream(flags_csv)
767       << "LMain;->ifield:I,unsupported" << std::endl;
768   auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex);
769   ASSERT_NE(dex_file.get(), nullptr);
770   ASSERT_EQ(hiddenapi::ApiList::Unsupported(), GetIFieldHiddenFlags(*dex_file));
771 }
772 
TEST_F(HiddenApiTest,InstanceFieldSdkUnchangedByMaxHiddenApiLevel)773 TEST_F(HiddenApiTest, InstanceFieldSdkUnchangedByMaxHiddenApiLevel) {
774   ScratchFile dex, flags_csv;
775   OpenStream(flags_csv)
776       << "LMain;->ifield:I,sdk" << std::endl;
777   auto dex_file = RunHiddenapiEncode(flags_csv, {"--max-hiddenapi-level=max-target-r"}, dex);
778   ASSERT_NE(dex_file.get(), nullptr);
779   ASSERT_EQ(hiddenapi::ApiList::Sdk(), GetIFieldHiddenFlags(*dex_file));
780 }
781 
782 // The following tests use this class hierarchy:
783 //
784 //    AbstractPackageClass  PublicInterface
785 //           |                     |
786 //           |    ┌----------------┘
787 //           |    |
788 //        PackageClass
789 //
790 // Only PublicInterface is in stubs.
791 
792 // Test a method declared in PublicInterface and defined in PackageClass.
TEST_F(HiddenApiTest,InterfaceMethodImplemented)793 TEST_F(HiddenApiTest, InterfaceMethodImplemented) {
794   ScratchFile flags_csv;
795   ASSERT_TRUE(RunHiddenapiList(flags_csv));
796   auto flags = ReadFlagsCsvFile(flags_csv);
797   ASSERT_EQ(SafeMapGet("LPackageClass;->publicMethod1()V", flags), "public-api");
798 }
799 
800 // Test a method declared in PublicInterface, defined in AbstractPackageClass and
801 // inherited by PackageClass.
TEST_F(HiddenApiTest,InterfaceMethodImplementedInParent)802 TEST_F(HiddenApiTest, InterfaceMethodImplementedInParent) {
803   ScratchFile flags_csv;
804   ASSERT_TRUE(RunHiddenapiList(flags_csv));
805   auto flags = ReadFlagsCsvFile(flags_csv);
806   ASSERT_EQ(SafeMapGet("LAbstractPackageClass;->publicMethod2()V", flags), "public-api");
807 }
808 
809 }  // namespace art
810