1 /*
   2  * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package jdk.vm.ci.services;
  24 
  25 import java.io.ByteArrayInputStream;
  26 import java.io.ByteArrayOutputStream;
  27 import java.io.DataInputStream;
  28 import java.io.DataOutputStream;
  29 import java.io.IOException;
  30 import java.util.ArrayList;
  31 import java.util.Collections;
  32 import java.util.Formatter;
  33 import java.util.HashMap;
  34 import java.util.List;
  35 import java.util.Map;
  36 import java.util.Properties;
  37 import java.util.ServiceLoader;
  38 import java.util.Set;
  39 
  40 import jdk.internal.misc.VM;
  41 import jdk.internal.reflect.Reflection;
  42 
  43 /**
  44  * Provides utilities needed by JVMCI clients.
  45  */
  46 public final class Services {
  47 
  48     // This class must be compilable and executable on JDK 8 since it's used in annotation
  49     // processors while building JDK 9 so use of API added in JDK 9 is made via reflection.
  50 
  51     /**
  52      * Guards code that should be run when building an JVMCI shared library but should be excluded
  53      * from (being compiled into) the library. Such code must be directly guarded by an {@code if}
  54      * statement on this field - the guard cannot be behind a method call.
  55      */
  56     public static final boolean IS_BUILDING_NATIVE_IMAGE = Boolean.parseBoolean(VM.getSavedProperty("jdk.vm.ci.services.aot"));
  57 
  58     /**
  59      * Guards code that should only be run in a JVMCI shared library. Such code must be directly
  60      * guarded by an {@code if} statement on this field - the guard cannot be behind a method call.
  61      *
  62      * The value of this field in a JVMCI shared library runtime must be {@code true}.
  63      */
  64     public static final boolean IS_IN_NATIVE_IMAGE;
  65     static {
  66         /*
  67          * Prevents javac from constant folding use of this field. It is set to true by the process
  68          * that builds the shared library.
  69          */
  70         IS_IN_NATIVE_IMAGE = false;
  71     }
  72 
  73     private Services() {
  74     }
  75 
  76     private static volatile Map<String, String> savedProperties = VM.getSavedProperties();
  77     static final boolean JVMCI_ENABLED = Boolean.parseBoolean(savedProperties.get("jdk.internal.vm.ci.enabled"));
  78 
  79     /**
  80      * Checks that JVMCI is enabled in the VM and throws an error if it isn't.
  81      */
  82     static void checkJVMCIEnabled() {
  83         if (!JVMCI_ENABLED) {
  84             throw new Error("The EnableJVMCI VM option must be true (i.e., -XX:+EnableJVMCI) to use JVMCI");
  85         }
  86     }
  87 
  88     /**
  89      * Gets an unmodifiable copy of the system properties saved when {@link System} is initialized.
  90      */
  91     public static Map<String, String> getSavedProperties() {
  92         checkJVMCIEnabled();
  93         SecurityManager sm = System.getSecurityManager();
  94         if (sm != null) {
  95             sm.checkPermission(new JVMCIPermission());
  96         }
  97         return savedProperties;
  98     }
  99 
 100     /**
 101      * Helper method equivalent to {@link #getSavedProperties()}{@code .getOrDefault(name, def)}.
 102      */
 103     public static String getSavedProperty(String name, String def) {
 104         return Services.getSavedProperties().getOrDefault(name, def);
 105     }
 106 
 107     /**
 108      * Helper method equivalent to {@link #getSavedProperties()}{@code .get(name)}.
 109      */
 110     public static String getSavedProperty(String name) {
 111         return Services.getSavedProperties().get(name);
 112     }
 113 
 114     /**
 115      * Causes the JVMCI subsystem to be initialized if it isn't already initialized.
 116      */
 117     public static void initializeJVMCI() {
 118         checkJVMCIEnabled();
 119         try {
 120             Class.forName("jdk.vm.ci.runtime.JVMCI");
 121         } catch (ClassNotFoundException e) {
 122             throw new InternalError(e);
 123         }
 124     }
 125 
 126     private static boolean jvmciEnabled = true;
 127 
 128     /**
 129      * When {@code -XX:-UseJVMCIClassLoader} is in use, JVMCI classes are loaded via the boot class
 130      * loader. When {@code null} is the second argument to
 131      * {@link ServiceLoader#load(Class, ClassLoader)}, service lookup will use the system class
 132      * loader and thus find application classes which violates the API of {@link #load} and
 133      * {@link #loadSingle}. To avoid this, a class loader that simply delegates to the boot class
 134      * loader is used.
 135      */
 136     static class LazyBootClassPath {
 137         static final ClassLoader bootClassPath = new ClassLoader(null) {
 138         };
 139     }
 140 
 141     private static ClassLoader findBootClassLoaderChild(ClassLoader start) {
 142         ClassLoader cl = start;
 143         while (cl.getParent() != null) {
 144             cl = cl.getParent();
 145         }
 146         return cl;
 147     }
 148 
 149     private static final Map<Class<?>, List<?>> servicesCache = IS_BUILDING_NATIVE_IMAGE ? new HashMap<>() : null;
 150 
 151     @SuppressWarnings("unchecked")
 152     private static <S> Iterable<S> load0(Class<S> service) {
 153         if (IS_IN_NATIVE_IMAGE || IS_BUILDING_NATIVE_IMAGE) {
 154             List<?> list = servicesCache.get(service);
 155             if (list != null) {
 156                 return (Iterable<S>) list;
 157             }
 158             if (IS_IN_NATIVE_IMAGE) {
 159                 throw new InternalError(String.format("No %s providers found when building native image", service.getName()));
 160             }
 161         }
 162 
 163         Iterable<S> providers = Collections.emptyList();
 164         if (jvmciEnabled) {
 165             ClassLoader cl = null;
 166             try {
 167                 cl = getJVMCIClassLoader();
 168                 if (cl == null) {
 169                     cl = LazyBootClassPath.bootClassPath;
 170                     // JVMCI classes are loaded via the boot class loader.
 171                     // If we use null as the second argument to ServiceLoader.load,
 172                     // service loading will use the system class loader
 173                     // and find classes on the application class path. Since we
 174                     // don't want this, we use a loader that is as close to the
 175                     // boot class loader as possible (since it is impossible
 176                     // to force service loading to use only the boot class loader).
 177                     cl = findBootClassLoaderChild(ClassLoader.getSystemClassLoader());
 178                 }
 179                 providers = ServiceLoader.load(service, cl);
 180             } catch (UnsatisfiedLinkError e) {
 181                 jvmciEnabled = false;
 182             } catch (InternalError e) {
 183                 if (e.getMessage().equals("JVMCI is not enabled")) {
 184                     jvmciEnabled = false;
 185                 } else {
 186                     throw e;
 187                 }
 188             }
 189         }
 190         if (IS_BUILDING_NATIVE_IMAGE) {
 191             synchronized (servicesCache) {
 192                 ArrayList<S> providersList = new ArrayList<>();
 193                 for (S provider : providers) {
 194                     providersList.add(provider);
 195                 }
 196                 servicesCache.put(service, providersList);
 197                 providers = providersList;
 198             }
 199         }
 200         return providers;
 201     }
 202 
 203     /**
 204      * Opens all JVMCI packages to {@code otherModule}.
 205      */
 206     static void openJVMCITo(Module otherModule) {
 207         Module jvmci = Services.class.getModule();
 208         if (jvmci != otherModule) {
 209             Set<String> packages = jvmci.getPackages();
 210             for (String pkg : packages) {
 211                 boolean opened = jvmci.isOpen(pkg, otherModule);
 212                 if (!opened) {
 213                     jvmci.addOpens(pkg, otherModule);
 214                 }
 215             }
 216         }
 217     }
 218 
 219     /**
 220      * Gets an {@link Iterable} of the JVMCI providers available for a given service.
 221      *
 222      * @throws SecurityException if a security manager is present and it denies <tt>
 223      *             {@link RuntimePermission}("jvmci")</tt>
 224      */
 225     public static <S> Iterable<S> load(Class<S> service) {
 226         SecurityManager sm = System.getSecurityManager();
 227         if (sm != null) {
 228             sm.checkPermission(new JVMCIPermission());
 229         }
 230         return load0(service);
 231     }
 232 
 233     /**
 234      * Gets the JVMCI provider for a given service for which at most one provider must be available.
 235      *
 236      * @param service the service whose provider is being requested
 237      * @param required specifies if an {@link InternalError} should be thrown if no provider of
 238      *            {@code service} is available
 239      * @throws SecurityException if a security manager is present and it denies <tt>
 240      *             {@link RuntimePermission}("jvmci")</tt>
 241      */
 242     public static <S> S loadSingle(Class<S> service, boolean required) {
 243         SecurityManager sm = System.getSecurityManager();
 244         if (sm != null) {
 245             sm.checkPermission(new JVMCIPermission());
 246         }
 247         Iterable<S> providers = load0(service);
 248 
 249         S singleProvider = null;
 250         for (S provider : providers) {
 251             if (singleProvider != null) {
 252                 throw new InternalError(String.format("Multiple %s providers found: %s, %s", service.getName(), singleProvider.getClass().getName(), provider.getClass().getName()));
 253             }
 254             singleProvider = provider;
 255         }
 256         if (singleProvider == null && required) {
 257             String javaHome = Services.getSavedProperty("java.home");
 258             String vmName = Services.getSavedProperty("java.vm.name");
 259             Formatter errorMessage = new Formatter();
 260             errorMessage.format("The VM does not expose required service %s.%n", service.getName());
 261             errorMessage.format("Currently used Java home directory is %s.%n", javaHome);
 262             errorMessage.format("Currently used VM configuration is: %s", vmName);
 263             throw new UnsupportedOperationException(errorMessage.toString());
 264         }
 265         return singleProvider;
 266     }
 267 
 268     static {
 269         Reflection.registerMethodsToFilter(Services.class, Set.of("getJVMCIClassLoader"));
 270     }
 271 
 272     /**
 273      * Gets the JVMCI class loader.
 274      *
 275      * @throws InternalError with the {@linkplain Throwable#getMessage() message}
 276      *             {@code "JVMCI is not enabled"} iff JVMCI is not enabled
 277      */
 278     private static ClassLoader getJVMCIClassLoader() {
 279         if (IS_IN_NATIVE_IMAGE) {
 280             return null;
 281         }
 282         return ClassLoader.getSystemClassLoader();
 283     }
 284 
 285     /**
 286      * A Java {@code char} has a maximal UTF8 length of 3.
 287      */
 288     private static final int MAX_UNICODE_IN_UTF8_LENGTH = 3;
 289 
 290     /**
 291      * {@link DataOutputStream#writeUTF(String)} only supports values whose UTF8 encoding length is
 292      * less than 65535.
 293      */
 294     private static final int MAX_UTF8_PROPERTY_STRING_LENGTH = 65535 / MAX_UNICODE_IN_UTF8_LENGTH;
 295 
 296     /**
 297      * Serializes the {@linkplain #getSavedProperties() saved system properties} to a byte array for
 298      * the purpose of {@linkplain #initializeSavedProperties(byte[]) initializing} the initial
 299      * properties in the JVMCI shared library.
 300      */
 301     @VMEntryPoint
 302     private static byte[] serializeSavedProperties() throws IOException {
 303         if (IS_IN_NATIVE_IMAGE) {
 304             throw new InternalError("Can only serialize saved properties in HotSpot runtime");
 305         }
 306         return serializeProperties(Services.getSavedProperties());
 307     }
 308 
 309     private static byte[] serializeProperties(Map<String, String> props) throws IOException {
 310         // Compute size of output on the assumption that
 311         // all system properties have ASCII names and values
 312         int estimate = 4 + 4;
 313         int nonUtf8Props = 0;
 314         for (Map.Entry<String, String> e : props.entrySet()) {
 315             String name = e.getKey();
 316             String value = e.getValue();
 317             estimate += (2 + (name.length())) + (2 + (value.length()));
 318             if (name.length() > MAX_UTF8_PROPERTY_STRING_LENGTH || value.length() > MAX_UTF8_PROPERTY_STRING_LENGTH) {
 319                 nonUtf8Props++;
 320             }
 321         }
 322 
 323         ByteArrayOutputStream baos = new ByteArrayOutputStream(estimate);
 324         DataOutputStream out = new DataOutputStream(baos);
 325         out.writeInt(props.size() - nonUtf8Props);
 326         out.writeInt(nonUtf8Props);
 327         for (Map.Entry<String, String> e : props.entrySet()) {
 328             String name = e.getKey();
 329             String value = e.getValue();
 330             if (name.length() <= MAX_UTF8_PROPERTY_STRING_LENGTH && value.length() <= MAX_UTF8_PROPERTY_STRING_LENGTH) {
 331                 out.writeUTF(name);
 332                 out.writeUTF(value);
 333             }
 334         }
 335         if (nonUtf8Props != 0) {
 336             for (Map.Entry<String, String> e : props.entrySet()) {
 337                 String name = e.getKey();
 338                 String value = e.getValue();
 339                 if (name.length() > MAX_UTF8_PROPERTY_STRING_LENGTH || value.length() > MAX_UTF8_PROPERTY_STRING_LENGTH) {
 340                     byte[] utf8Name = name.getBytes("UTF-8");
 341                     byte[] utf8Value = value.getBytes("UTF-8");
 342                     out.writeInt(utf8Name.length);
 343                     out.write(utf8Name);
 344                     out.writeInt(utf8Value.length);
 345                     out.write(utf8Value);
 346                 }
 347             }
 348         }
 349         return baos.toByteArray();
 350     }
 351 
 352     /**
 353      * Initialized the {@linkplain #getSavedProperties() saved system properties} in the JVMCI
 354      * shared library from the {@linkplain #serializeSavedProperties() serialized saved properties}
 355      * in the HotSpot runtime.
 356      */
 357     @VMEntryPoint
 358     private static void initializeSavedProperties(byte[] serializedProperties) throws IOException {
 359         if (!IS_IN_NATIVE_IMAGE) {
 360             throw new InternalError("Can only initialize saved properties in JVMCI shared library runtime");
 361         }
 362         savedProperties = Collections.unmodifiableMap(deserializeProperties(serializedProperties));
 363     }
 364 
 365     private static Map<String, String> deserializeProperties(byte[] serializedProperties) throws IOException {
 366         DataInputStream in = new DataInputStream(new ByteArrayInputStream(serializedProperties));
 367         int utf8Props = in.readInt();
 368         int nonUtf8Props = in.readInt();
 369         Map<String, String> props = new HashMap<>(utf8Props + nonUtf8Props);
 370         int index = 0;
 371         while (in.available() != 0) {
 372             if (index < utf8Props) {
 373                 String name = in.readUTF();
 374                 String value = in.readUTF();
 375                 props.put(name, value);
 376             } else {
 377                 int nameLen = in.readInt();
 378                 byte[] nameBytes = new byte[nameLen];
 379                 in.read(nameBytes);
 380                 int valueLen = in.readInt();
 381                 byte[] valueBytes = new byte[valueLen];
 382                 in.read(valueBytes);
 383                 String name = new String(nameBytes, "UTF-8");
 384                 String value = new String(valueBytes, "UTF-8");
 385                 props.put(name, value);
 386             }
 387             index++;
 388         }
 389         return props;
 390     }
 391 }