lin
2025-07-31 065ea569db06206874bbfa18eb25ff6121aec09b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
 
import dalvik.system.PathClassLoader;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.util.Arrays;
 
public class Main {
  // This needs to be kept in sync with DexDomain in ChildClass.
  enum DexDomain {
    CorePlatform,
    Platform,
    Application
  }
 
  public static void main(String[] args) throws Exception {
    System.loadLibrary(args[0]);
    prepareNativeLibFileName(args[0]);
 
    // Enable hidden API checks in case they are disabled by default.
    init();
 
    // TODO there are sequential depencies between these test cases, and bugs
    // in the production code may lead to subsequent tests to erroneously pass,
    // or test the wrong thing. We rely on not deduping hidden API warnings
    // here for the same reasons), meaning the code under test and production
    // code are running in different configurations. Each test should be run in
    // a fresh process to ensure that they are working correctly and not
    // accidentally interfering with each other.
    // As a side effect, we also cannot test Platform->Platform and later
    // Platform->CorePlatform as the former succeeds in verifying linkage usages
    // that should fail in the latter.
    // We also cannot use InMemoryDexClassLoader because it runs verification in
    // a background thread and being able to dynamically change the configuration
    // (like list of exemptions) would require proper thread synchronization.
 
    // Run test with both parent and child dex files loaded with class loaders.
    // The expectation is that hidden members in parent should be visible to
    // the child.
    doTest(DexDomain.Application, DexDomain.Application, false);
    doUnloading();
 
    // Now append parent dex file to boot class path and run again. This time
    // the child dex file should not be able to access private APIs of the
    // parent.
    int parentIdx = appendToBootClassLoader(DEX_PARENT_BOOT, /* isCorePlatform */ false);
    doTest(DexDomain.Platform, DexDomain.Application, false);
    doUnloading();
 
    // Now run the same test again, but with the blacklist exmemptions list set
    // to "L" which matches everything.
    doTest(DexDomain.Platform, DexDomain.Application, true);
    doUnloading();
 
    // Repeat the two tests above, only with parent being a core-platform dex file.
    setDexDomain(parentIdx, /* isCorePlatform */ true);
    doTest(DexDomain.CorePlatform, DexDomain.Application, false);
    doUnloading();
    doTest(DexDomain.CorePlatform, DexDomain.Application, true);
    doUnloading();
 
    // Append child to boot class path, first as a platform dex file.
    // It should not be allowed to access non-public, non-core platform API members.
    int childIdx = appendToBootClassLoader(DEX_CHILD, /* isCorePlatform */ false);
    doTest(DexDomain.CorePlatform, DexDomain.Platform, false);
    doUnloading();
 
    // And finally change child to core-platform dex. With both in the boot classpath
    // and both core-platform, access should be granted.
    setDexDomain(childIdx, /* isCorePlatform */ true);
    doTest(DexDomain.CorePlatform, DexDomain.CorePlatform, false);
    doUnloading();
  }
 
  private static void doTest(DexDomain parentDomain, DexDomain childDomain,
      boolean whitelistAllApis) throws Exception {
    // Load parent dex if it is not in boot class path.
    ClassLoader parentLoader = null;
    if (parentDomain == DexDomain.Application) {
      parentLoader = new PathClassLoader(DEX_PARENT, ClassLoader.getSystemClassLoader());
    } else {
      parentLoader = BOOT_CLASS_LOADER;
    }
 
    // Load child dex if it is not in boot class path.
    ClassLoader childLoader = null;
    if (childDomain == DexDomain.Application) {
      childLoader = new PathClassLoader(DEX_CHILD, parentLoader);
    } else {
      if (parentLoader != BOOT_CLASS_LOADER) {
        throw new IllegalStateException(
            "DeclaringClass must be in parent class loader of CallingClass");
      }
      childLoader = BOOT_CLASS_LOADER;
    }
 
    // Create a unique copy of the native library. Each shared library can only
    // be loaded once, but for some reason even classes from a class loader
    // cannot register their native methods against symbols in a shared library
    // loaded by their parent class loader.
    String nativeLibCopy = createNativeLibCopy(parentDomain, childDomain, whitelistAllApis);
 
    // Set exemptions to "L" (matches all classes) if we are testing whitelisting.
    setWhitelistAll(whitelistAllApis);
 
    // Invoke ChildClass.runTest
    Class<?> childClass = Class.forName("ChildClass", true, childLoader);
    Method runTestMethod = childClass.getDeclaredMethod(
        "runTest", String.class, Integer.TYPE, Integer.TYPE, Boolean.TYPE);
    runTestMethod.invoke(null, nativeLibCopy, parentDomain.ordinal(), childDomain.ordinal(),
        whitelistAllApis);
  }
 
  // Routine which tries to figure out the absolute path of our native library.
  private static void prepareNativeLibFileName(String arg) throws Exception {
    String libName = System.mapLibraryName(arg);
    Method libPathsMethod = Runtime.class.getDeclaredMethod("getLibPaths");
    libPathsMethod.setAccessible(true);
    String[] libPaths = (String[]) libPathsMethod.invoke(Runtime.getRuntime());
    nativeLibFileName = null;
    for (String p : libPaths) {
      String candidate = p + libName;
      if (new File(candidate).exists()) {
        nativeLibFileName = candidate;
        break;
      }
    }
    if (nativeLibFileName == null) {
      throw new IllegalStateException("Didn't find " + libName + " in " +
          Arrays.toString(libPaths));
    }
  }
 
  // Copy native library to a new file with a unique name so it does not
  // conflict with other loaded instance of the same binary file.
  private static String createNativeLibCopy(DexDomain parentDomain, DexDomain childDomain,
      boolean whitelistAllApis) throws Exception {
    String tempFileName = System.mapLibraryName(
        "hiddenapitest_" + (parentDomain.ordinal()) + (childDomain.ordinal()) +
        (whitelistAllApis ? "1" : "0"));
    File tempFile = new File(System.getenv("DEX_LOCATION"), tempFileName);
    Files.copy(new File(nativeLibFileName).toPath(), tempFile.toPath());
    return tempFile.getAbsolutePath();
  }
 
  private static void doUnloading() {
    // Do multiple GCs to prevent rare flakiness if some other thread is
    // keeping the classloader live.
    for (int i = 0; i < 5; ++i) {
       Runtime.getRuntime().gc();
    }
  }
 
  private static String nativeLibFileName;
 
  private static final String DEX_PARENT =
      new File(System.getenv("DEX_LOCATION"), "674-hiddenapi.jar").getAbsolutePath();
  private static final String DEX_PARENT_BOOT =
      new File(new File(System.getenv("DEX_LOCATION"), "res"), "boot.jar").getAbsolutePath();
  private static final String DEX_CHILD =
      new File(System.getenv("DEX_LOCATION"), "674-hiddenapi-ex.jar").getAbsolutePath();
 
  private static ClassLoader BOOT_CLASS_LOADER = Object.class.getClassLoader();
 
  private static native int appendToBootClassLoader(String dexPath, boolean isCorePlatform);
  private static native void setDexDomain(int index, boolean isCorePlatform);
  private static native void init();
  private static native void setWhitelistAll(boolean value);
}