Path: blob/master/src/java.management.rmi/share/classes/javax/management/remote/rmi/NoCallStackClassLoader.java
41162 views
/*1* Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package javax.management.remote.rmi;2627import java.security.ProtectionDomain;2829/**30<p>A class loader that only knows how to define a limited number31of classes, and load a limited number of other classes through32delegation to another loader. It is used to get around a problem33with Serialization, in particular as used by RMI. The JMX Remote API34defines exactly what class loader must be used to deserialize arguments on35the server, and return values on the client. We communicate this class36loader to RMI by setting it as the context class loader. RMI uses the37context class loader to load classes as it deserializes, which is what we38want. However, before consulting the context class loader, it39looks up the call stack for a class with a non-null class loader,40and uses that if it finds one. So, in the standalone version of41javax.management.remote, if the class you're looking for is known42to the loader of jmxremote.jar (typically the system class loader)43then that loader will load it. This contradicts the class-loading44semantics required.4546<p>We get around the problem by ensuring that the search up the47call stack will find a non-null class loader that doesn't load any48classes of interest, namely this one. So even though this loader49is indeed consulted during deserialization, it never finds the50class being deserialized. RMI then proceeds to use the context51class loader, as we require.5253<p>This loader is constructed with the name and byte-code of one54or more classes that it defines, and a class-loader to which it55will delegate certain other classes required by that byte-code.56We construct the byte-code somewhat painstakingly, by compiling57the Java code directly, converting into a string, copying that58string into the class that needs this loader, and using the59stringToBytes method to convert it into the byte array. We60compile with -g:none because there's not much point in having61line-number information and the like in these directly-encoded62classes.6364<p>The referencedClassNames should contain the names of all65classes that are referenced by the classes defined by this loader.66It is not necessary to include standard J2SE classes, however.67Here, a class is referenced if it is the superclass or a68superinterface of a defined class, or if it is the type of a69field, parameter, or return value. A class is not referenced if70it only appears in the throws clause of a method or constructor.71Of course, referencedClassNames should not contain any classes72that the user might want to deserialize, because the whole point73of this loader is that it does not find such classes.74*/7576class NoCallStackClassLoader extends ClassLoader {77/** Simplified constructor when this loader only defines one class. */78public NoCallStackClassLoader(String className,79byte[] byteCode,80String[] referencedClassNames,81ClassLoader referencedClassLoader,82ProtectionDomain protectionDomain) {83this(new String[] {className}, new byte[][] {byteCode},84referencedClassNames, referencedClassLoader, protectionDomain);85}8687public NoCallStackClassLoader(String[] classNames,88byte[][] byteCodes,89String[] referencedClassNames,90ClassLoader referencedClassLoader,91ProtectionDomain protectionDomain) {92super(null);9394/* Validation. */95if (classNames == null || classNames.length == 096|| byteCodes == null || classNames.length != byteCodes.length97|| referencedClassNames == null || protectionDomain == null)98throw new IllegalArgumentException();99for (int i = 0; i < classNames.length; i++) {100if (classNames[i] == null || byteCodes[i] == null)101throw new IllegalArgumentException();102}103for (int i = 0; i < referencedClassNames.length; i++) {104if (referencedClassNames[i] == null)105throw new IllegalArgumentException();106}107108this.classNames = classNames;109this.byteCodes = byteCodes;110this.referencedClassNames = referencedClassNames;111this.referencedClassLoader = referencedClassLoader;112this.protectionDomain = protectionDomain;113}114115/* This method is called at most once per name. Define the name116* if it is one of the classes whose byte code we have, or117* delegate the load if it is one of the referenced classes.118*/119@Override120protected Class<?> findClass(String name) throws ClassNotFoundException {121// Note: classNames is guaranteed by the constructor to be non-null.122for (int i = 0; i < classNames.length; i++) {123if (name.equals(classNames[i])) {124return defineClass(classNames[i], byteCodes[i], 0,125byteCodes[i].length, protectionDomain);126}127}128129/* If the referencedClassLoader is null, it is the bootstrap130* class loader, and there's no point in delegating to it131* because it's already our parent class loader.132*/133if (referencedClassLoader != null) {134for (int i = 0; i < referencedClassNames.length; i++) {135if (name.equals(referencedClassNames[i]))136return referencedClassLoader.loadClass(name);137}138}139140throw new ClassNotFoundException(name);141}142143private final String[] classNames;144private final byte[][] byteCodes;145private final String[] referencedClassNames;146private final ClassLoader referencedClassLoader;147private final ProtectionDomain protectionDomain;148149/**150* <p>Construct a <code>byte[]</code> using the characters of the151* given <code>String</code>. Only the low-order byte of each152* character is used. This method is useful to reduce the153* footprint of classes that include big byte arrays (e.g. the154* byte code of other classes), because a string takes up much155* less space in a class file than the byte code to initialize a156* <code>byte[]</code> with the same number of bytes.</p>157*158* <p>We use just one byte per character even though characters159* contain two bytes. The resultant output length is much the160* same: using one byte per character is shorter because it has161* more characters in the optimal 1-127 range but longer because162* it has more zero bytes (which are frequent, and are encoded as163* two bytes in classfile UTF-8). But one byte per character has164* two key advantages: (1) you can see the string constants, which165* is reassuring, (2) you don't need to know whether the class166* file length is odd.</p>167*168* <p>This method differs from {@link String#getBytes()} in that169* it does not use any encoding. So it is guaranteed that each170* byte of the result is numerically identical (mod 256) to the171* corresponding character of the input.172*/173public static byte[] stringToBytes(String s) {174final int slen = s.length();175byte[] bytes = new byte[slen];176for (int i = 0; i < slen; i++)177bytes[i] = (byte) s.charAt(i);178return bytes;179}180}181182/*183184You can use the following Emacs function to convert class files into185strings to be used by the stringToBytes method above. Select the186whole (defun...) with the mouse and type M-x eval-region, or save it187to a file and do M-x load-file. Then visit the *.class file and do188M-x class-string.189190;; class-string.el191;; visit the *.class file with emacs, then invoke this function192193(defun class-string ()194"Construct a Java string whose bytes are the same as the current195buffer. The resultant string is put in a buffer called *string*,196possibly with a numeric suffix like <2>. From there it can be197insert-buffer'd into a Java program."198(interactive)199(let* ((s (buffer-string))200(slen (length s))201(i 0)202(buf (generate-new-buffer "*string*")))203(set-buffer buf)204(insert "\"")205(while (< i slen)206(if (> (current-column) 61)207(insert "\"+\n\""))208(let ((c (aref s i)))209(insert (cond210((> c 126) (format "\\%o" c))211((= c ?\") "\\\"")212((= c ?\\) "\\\\")213((< c 33)214(let ((nextc (if (< (1+ i) slen)215(aref s (1+ i))216?\0)))217(cond218((and (<= nextc ?7) (>= nextc ?0))219(format "\\%03o" c))220(t221(format "\\%o" c)))))222(t c))))223(setq i (1+ i)))224(insert "\"")225(switch-to-buffer buf)))226227Alternatively, the following class reads a class file and outputs a string228that can be used by the stringToBytes method above.229230import java.io.File;231import java.io.FileInputStream;232import java.io.IOException;233234public class BytesToString {235236public static void main(String[] args) throws IOException {237File f = new File(args[0]);238int len = (int)f.length();239byte[] classBytes = new byte[len];240241FileInputStream in = new FileInputStream(args[0]);242try {243int pos = 0;244for (;;) {245int n = in.read(classBytes, pos, (len-pos));246if (n < 0)247throw new RuntimeException("class file changed??");248pos += n;249if (pos >= n)250break;251}252} finally {253in.close();254}255256int pos = 0;257boolean lastWasOctal = false;258for (int i=0; i<len; i++) {259int value = classBytes[i];260if (value < 0)261value += 256;262String s = null;263if (value == '\\')264s = "\\\\";265else if (value == '\"')266s = "\\\"";267else {268if ((value >= 32 && value < 127) && ((!lastWasOctal ||269(value < '0' || value > '7')))) {270s = Character.toString((char)value);271}272}273if (s == null) {274s = "\\" + Integer.toString(value, 8);275lastWasOctal = true;276} else {277lastWasOctal = false;278}279if (pos > 61) {280System.out.print("\"");281if (i<len)282System.out.print("+");283System.out.println();284pos = 0;285}286if (pos == 0)287System.out.print(" \"");288System.out.print(s);289pos += s.length();290}291System.out.println("\"");292}293}294295*/296297298