• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 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 package com.android.ravenwood.common;
17 
18 import android.annotation.NonNull;
19 import android.annotation.Nullable;
20 
21 import com.android.ravenwood.common.divergence.RavenwoodDivergence;
22 
23 import java.io.File;
24 import java.io.FileDescriptor;
25 import java.io.FileInputStream;
26 import java.io.PrintStream;
27 import java.io.PrintWriter;
28 import java.io.StringWriter;
29 import java.lang.reflect.InvocationTargetException;
30 import java.lang.reflect.Member;
31 import java.lang.reflect.Method;
32 import java.lang.reflect.Modifier;
33 import java.util.Arrays;
34 import java.util.Objects;
35 import java.util.function.Supplier;
36 
37 public class RavenwoodCommonUtils {
38     public static final String TAG = "Ravenwood";
39 
RavenwoodCommonUtils()40     private RavenwoodCommonUtils() {
41     }
42 
43     /**
44      * If set to "1", we enable the verbose logging.
45      *
46      * (See also InitLogging() in http://ac/system/libbase/logging.cpp)
47      */
48     public static final boolean RAVENWOOD_VERBOSE_LOGGING = "1".equals(System.getenv(
49             "RAVENWOOD_VERBOSE"));
50 
51     /** Directory name of `out/host/linux-x86/testcases/ravenwood-runtime` */
52     private static final String RAVENWOOD_RUNTIME_DIR_NAME = "ravenwood-runtime";
53 
54     private static boolean sEnableExtraRuntimeCheck =
55             "1".equals(System.getenv("RAVENWOOD_ENABLE_EXTRA_RUNTIME_CHECK"));
56 
57     private static final boolean IS_ON_RAVENWOOD = RavenwoodDivergence.isOnRavenwood();
58 
59     private static final String RAVENWOOD_RUNTIME_PATH = getRavenwoodRuntimePathInternal();
60 
61     public static final String RAVENWOOD_SYSPROP = "ro.is_on_ravenwood";
62 
63     public static final String RAVENWOOD_RESOURCE_APK = "ravenwood-res-apks/ravenwood-res.apk";
64     public static final String RAVENWOOD_INST_RESOURCE_APK =
65             "ravenwood-res-apks/ravenwood-inst-res.apk";
66 
67     public static final String RAVENWOOD_EMPTY_RESOURCES_APK =
68             RAVENWOOD_RUNTIME_PATH + "ravenwood-data/ravenwood-empty-res.apk";
69 
70     public static final String RAVENWOOD_VERSION_JAVA_SYSPROP = "android.ravenwood.version";
71     public static final String RAVENWOOD_RUNTIME_PATH_JAVA_SYSPROP =
72             "android.ravenwood.runtime_path";
73 
74     /**
75      * @return if we're running on Ravenwood.
76      */
isOnRavenwood()77     public static boolean isOnRavenwood() {
78         return IS_ON_RAVENWOOD;
79     }
80 
81     /**
82      * Throws if the runtime is not Ravenwood.
83      */
ensureOnRavenwood()84     public static void ensureOnRavenwood() {
85         if (!isOnRavenwood()) {
86             throw new RavenwoodRuntimeException("This is only supposed to be used on Ravenwood");
87         }
88     }
89 
90     /**
91      * @return if the various extra runtime check should be enabled.
92      */
shouldEnableExtraRuntimeCheck()93     public static boolean shouldEnableExtraRuntimeCheck() {
94         return sEnableExtraRuntimeCheck;
95     }
96 
97     /** Simple logging method. */
log(String tag, String message)98     public static void log(String tag, String message) {
99         // Avoid using Android's Log class, which could be broken for various reasons.
100         // (e.g. the JNI file doesn't exist for whatever reason)
101         System.out.print(tag + ": " + message + "\n");
102     }
103 
104     /** Simple logging method. */
log(String tag, String format, Object... args)105     private void log(String tag, String format, Object... args) {
106         log(tag, String.format(format, args));
107     }
108 
109     /**
110      * Internal implementation of
111      * {@link android.platform.test.ravenwood.RavenwoodUtils#loadJniLibrary(String)}
112      */
loadJniLibrary(String libname)113     public static void loadJniLibrary(String libname) {
114         if (RavenwoodCommonUtils.isOnRavenwood()) {
115             System.load(getJniLibraryPath(libname));
116         } else {
117             System.loadLibrary(libname);
118         }
119     }
120 
121     /**
122      * Find the shared library path from java.library.path.
123      */
getJniLibraryPath(String libname)124     public static String getJniLibraryPath(String libname) {
125         var path = System.getProperty("java.library.path");
126         var filename = "lib" + libname + ".so";
127 
128         System.out.println("Looking for library " + libname + ".so in java.library.path:" + path);
129 
130         try {
131             if (path == null) {
132                 throw new UnsatisfiedLinkError("Cannot find library " + libname + "."
133                         + " Property java.library.path not set!");
134             }
135             for (var dir : path.split(":")) {
136                 var file = new File(dir + "/" + filename);
137                 if (file.exists()) {
138                     return file.getAbsolutePath();
139                 }
140             }
141         } catch (Throwable e) {
142             dumpFiles(System.out);
143             throw e;
144         }
145         throw new UnsatisfiedLinkError("Library " + libname + " not found in "
146                 + "java.library.path: " + path);
147     }
148 
dumpFiles(PrintStream out)149     private static void dumpFiles(PrintStream out) {
150         try {
151             var path = System.getProperty("java.library.path");
152             out.println("# java.library.path=" + path);
153 
154             for (var dir : path.split(":")) {
155                 listFiles(out, new File(dir), "");
156 
157                 var gparent = new File((new File(dir)).getAbsolutePath() + "../../..")
158                         .getCanonicalFile();
159                 if (gparent.getName().contains("testcases")) {
160                     // Special case: if we found this directory, dump its contents too.
161                     listFiles(out, gparent, "");
162                 }
163             }
164 
165             var gparent = new File("../..").getCanonicalFile();
166             out.println("# ../..=" + gparent);
167             listFiles(out, gparent, "");
168         } catch (Throwable th) {
169             out.println("Error: " + th.toString());
170             th.printStackTrace(out);
171         }
172     }
173 
listFiles(PrintStream out, File dir, String prefix)174     private static void listFiles(PrintStream out, File dir, String prefix) {
175         if (!dir.isDirectory()) {
176             out.println(prefix + dir.getAbsolutePath() + " is not a directory!");
177             return;
178         }
179         out.println(prefix + ":" + dir.getAbsolutePath() + "/");
180         // First, list the files.
181         for (var file : Arrays.stream(dir.listFiles()).sorted().toList()) {
182             out.println(prefix + "  " + file.getName() + "" + (file.isDirectory() ? "/" : ""));
183         }
184 
185         // Then recurse.
186         if (dir.getAbsolutePath().startsWith("/usr") || dir.getAbsolutePath().startsWith("/lib")) {
187             // There would be too many files, so don't recurse.
188             return;
189         }
190         for (var file : Arrays.stream(dir.listFiles()).sorted().toList()) {
191             if (file.isDirectory()) {
192                 listFiles(out, file, prefix + "  ");
193             }
194         }
195     }
196 
197     /**
198      * @return the full directory path that contains the "ravenwood-runtime" files.
199      *
200      * This method throws if called on the device side.
201      */
getRavenwoodRuntimePath()202     public static String getRavenwoodRuntimePath() {
203         ensureOnRavenwood();
204         return RAVENWOOD_RUNTIME_PATH;
205     }
206 
getRavenwoodRuntimePathInternal()207     private static String getRavenwoodRuntimePathInternal() {
208         if (!isOnRavenwood()) {
209             return null;
210         }
211         var path = System.getProperty("java.library.path");
212 
213         System.out.println("Looking for " + RAVENWOOD_RUNTIME_DIR_NAME + " directory"
214                 + " in java.library.path:" + path);
215 
216         try {
217             if (path == null) {
218                 throw new IllegalStateException("java.library.path shouldn't be null");
219             }
220             for (var dir : path.split(":")) {
221 
222                 // For each path, see if the path contains RAVENWOOD_RUNTIME_DIR_NAME.
223                 var d = new File(dir);
224                 for (;;) {
225                     if (d.getParent() == null) {
226                         break; // Root dir, stop.
227                     }
228                     if (RAVENWOOD_RUNTIME_DIR_NAME.equals(d.getName())) {
229                         var ret = d.getAbsolutePath() + "/";
230                         System.out.println("Found: " + ret);
231                         return ret;
232                     }
233                     d = d.getParentFile();
234                 }
235             }
236             throw new IllegalStateException(RAVENWOOD_RUNTIME_DIR_NAME + " not found");
237         } catch (Throwable e) {
238             dumpFiles(System.out);
239             throw e;
240         }
241     }
242 
243     /** Close an {@link AutoCloseable}. */
closeQuietly(AutoCloseable c)244     public static void closeQuietly(AutoCloseable c) {
245         if (c != null) {
246             try {
247                 c.close();
248             } catch (Exception e) {
249                 // Ignore
250             }
251         }
252     }
253 
254     /** Close a {@link FileDescriptor}. */
closeQuietly(FileDescriptor fd)255     public static void closeQuietly(FileDescriptor fd) {
256         var is = new FileInputStream(fd);
257         RavenwoodCommonUtils.closeQuietly(is);
258     }
259 
ensureIsPublicVoidMethod(Method method, boolean isStatic)260     public static void ensureIsPublicVoidMethod(Method method, boolean isStatic) {
261         var ok = Modifier.isPublic(method.getModifiers())
262                 && (Modifier.isStatic(method.getModifiers()) == isStatic)
263                 && (method.getReturnType() == void.class);
264         if (ok) {
265             return; // okay
266         }
267         throw new AssertionError(String.format(
268                 "Method %s.%s() expected to be public %svoid",
269                 method.getDeclaringClass().getName(), method.getName(),
270                 (isStatic ? "static " : "")));
271     }
272 
ensureIsPublicMember(Member member, boolean isStatic)273     public static void ensureIsPublicMember(Member member, boolean isStatic) {
274         var ok = Modifier.isPublic(member.getModifiers())
275                 && (Modifier.isStatic(member.getModifiers()) == isStatic);
276         if (ok) {
277             return; // okay
278         }
279         throw new AssertionError(String.format(
280                 "%s.%s expected to be public %s",
281                 member.getDeclaringClass().getName(), member.getName(),
282                 (isStatic ? "static" : "")));
283     }
284 
285     /**
286      * Run a supplier and swallow the exception, if any.
287      *
288      * It's a dangerous function. Only use it in an exception handler where we don't want to crash.
289      */
290     @Nullable
runIgnoringException(@onNull Supplier<T> s)291     public static <T> T runIgnoringException(@NonNull Supplier<T> s) {
292         try {
293             return s.get();
294         } catch (Throwable th) {
295             log(TAG, "Warning: Exception detected! " + getStackTraceString(th));
296         }
297         return null;
298     }
299 
300     /**
301      * Run a runnable and swallow the exception, if any.
302      *
303      * It's a dangerous function. Only use it in an exception handler where we don't want to crash.
304      */
runIgnoringException(@onNull Runnable r)305     public static void runIgnoringException(@NonNull Runnable r) {
306         runIgnoringException(() -> {
307             r.run();
308             return null;
309         });
310     }
311 
312     @NonNull
getStackTraceString(@onNull Throwable th)313     public static String getStackTraceString(@NonNull Throwable th) {
314         StringWriter stringWriter = new StringWriter();
315         PrintWriter writer = new PrintWriter(stringWriter);
316         th.printStackTrace(writer);
317         return stringWriter.toString();
318     }
319 
320     /** Same as {@link Integer#parseInt(String)} but accepts null and returns null. */
321     @Nullable
parseNullableInt(@ullable String value)322     public static Integer parseNullableInt(@Nullable String value) {
323         if (value == null) {
324             return null;
325         }
326         return Integer.parseInt(value);
327     }
328 
329     /**
330      * @return {@code value} if it's non-null. Otherwise, returns {@code def}.
331      */
332     @Nullable
withDefault(@ullable T value, @Nullable T def)333     public static <T> T withDefault(@Nullable T value, @Nullable T def) {
334         return value != null ? value : def;
335     }
336 
337     /**
338      * Utility for calling a method with reflections. Used to call a method by name.
339      * Note, this intentionally does _not_ support non-public methods, as we generally
340      * shouldn't violate java visibility in ravenwood.
341      *
342      * @param <TTHIS> class owning the method.
343      */
344     public static class ReflectedMethod<TTHIS> {
345         private final Class<TTHIS> mThisClass;
346         private final Method mMethod;
347 
ReflectedMethod(Class<TTHIS> thisClass, Method method)348         private ReflectedMethod(Class<TTHIS> thisClass, Method method) {
349             mThisClass = thisClass;
350             mMethod = method;
351         }
352 
353         /** Factory method. */
354         @SuppressWarnings("unchecked")
reflectMethod( @onNull Class<TTHIS> clazz, @NonNull String methodName, @NonNull Class<?>... argTypes)355         public static <TTHIS> ReflectedMethod<TTHIS> reflectMethod(
356                 @NonNull Class<TTHIS> clazz, @NonNull String methodName,
357                 @NonNull Class<?>... argTypes) {
358             try {
359                 return new ReflectedMethod(clazz, clazz.getMethod(methodName, argTypes));
360             } catch (NoSuchMethodException e) {
361                 throw new RuntimeException(e);
362             }
363         }
364 
365         /** Factory method. */
366         @SuppressWarnings("unchecked")
reflectMethod( @onNull String className, @NonNull String methodName, @NonNull Class<?>... argTypes)367         public static <TTHIS> ReflectedMethod<TTHIS> reflectMethod(
368                 @NonNull String className, @NonNull String methodName,
369                 @NonNull Class<?>... argTypes) {
370             try {
371                 return reflectMethod((Class<TTHIS>) Class.forName(className), methodName, argTypes);
372             } catch (ClassNotFoundException e) {
373                 throw new RuntimeException(e);
374             }
375         }
376 
377         /** Call the instance method */
378         @SuppressWarnings("unchecked")
call(@onNull TTHIS thisObject, @NonNull Object... args)379         public <RET> RET call(@NonNull TTHIS thisObject, @NonNull Object... args) {
380             try {
381                 return (RET) mMethod.invoke(Objects.requireNonNull(thisObject), args);
382             } catch (InvocationTargetException | IllegalAccessException e) {
383                 throw new RuntimeException(e);
384             }
385         }
386 
387         /** Call the static method */
388         @SuppressWarnings("unchecked")
callStatic(@onNull Object... args)389         public <RET> RET callStatic(@NonNull Object... args) {
390             try {
391                 return (RET) mMethod.invoke(null, args);
392             } catch (InvocationTargetException | IllegalAccessException e) {
393                 throw new RuntimeException(e);
394             }
395         }
396     }
397 
398     /** Handy method to create an array */
arr(@onNull T... objects)399     public static <T> T[] arr(@NonNull T... objects) {
400         return objects;
401     }
402 }
403