4544c3f387
* java/lang/reflect/Proxy.java: New version from Classpath. * java/lang/Package.java: New version from Classpath. From-SVN: r65007
1625 lines
58 KiB
Java
1625 lines
58 KiB
Java
/* Proxy.java -- build a proxy class that implements reflected interfaces
|
|
Copyright (C) 2001, 2002 Free Software Foundation, Inc.
|
|
|
|
This file is part of GNU Classpath.
|
|
|
|
GNU Classpath is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
GNU Classpath is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with GNU Classpath; see the file COPYING. If not, write to the
|
|
Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
|
|
02111-1307 USA.
|
|
|
|
Linking this library statically or dynamically with other modules is
|
|
making a combined work based on this library. Thus, the terms and
|
|
conditions of the GNU General Public License cover the whole
|
|
combination.
|
|
|
|
As a special exception, the copyright holders of this library give you
|
|
permission to link this library with independent modules to produce an
|
|
executable, regardless of the license terms of these independent
|
|
modules, and to copy and distribute the resulting executable under
|
|
terms of your choice, provided that you also meet, for each linked
|
|
independent module, the terms and conditions of the license of that
|
|
module. An independent module is a module which is not derived from
|
|
or based on this library. If you modify this library, you may extend
|
|
this exception to your version of the library, but you are not
|
|
obligated to do so. If you do not wish to do so, delete this
|
|
exception statement from your version. */
|
|
|
|
|
|
package java.lang.reflect;
|
|
|
|
import java.io.Serializable;
|
|
import java.security.ProtectionDomain;
|
|
import java.util.Map;
|
|
import java.util.HashMap;
|
|
import java.util.Set;
|
|
import java.util.HashSet;
|
|
import java.util.Iterator;
|
|
import gnu.classpath.Configuration;
|
|
import gnu.java.lang.reflect.TypeSignature;
|
|
|
|
/**
|
|
* This class allows you to dynamically create an instance of any (or
|
|
* even multiple) interfaces by reflection, and decide at runtime
|
|
* how that instance will behave by giving it an appropriate
|
|
* {@link InvocationHandler}. Proxy classes serialize specially, so
|
|
* that the proxy object can be reused between VMs, without requiring
|
|
* a persistent copy of the generated class code.
|
|
*
|
|
* <h3>Creation</h3>
|
|
* To create a proxy for some interface Foo:
|
|
*
|
|
* <pre>
|
|
* InvocationHandler handler = new MyInvocationHandler(...);
|
|
* Class proxyClass = Proxy.getProxyClass(
|
|
* Foo.class.getClassLoader(), new Class[] { Foo.class });
|
|
* Foo f = (Foo) proxyClass
|
|
* .getConstructor(new Class[] { InvocationHandler.class })
|
|
* .newInstance(new Object[] { handler });
|
|
* </pre>
|
|
* or more simply:
|
|
* <pre>
|
|
* Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),
|
|
* new Class[] { Foo.class },
|
|
* handler);
|
|
* </pre>
|
|
*
|
|
* <h3>Dynamic Proxy Classes</h3>
|
|
* A dynamic proxy class is created at runtime, and has the following
|
|
* properties:
|
|
* <ul>
|
|
* <li>The class is <code>public</code> and <code>final</code>,
|
|
* and is neither <code>abstract</code> nor an inner class.</li>
|
|
* <li>The class has no canonical name (there is no formula you can use
|
|
* to determine or generate its name), but begins with the
|
|
* sequence "$Proxy". Abuse this knowledge at your own peril.
|
|
* (For now, '$' in user identifiers is legal, but it may not
|
|
* be that way forever. You weren't using '$' in your
|
|
* identifiers, were you?)</li>
|
|
* <li>The class extends Proxy, and explicitly implements all the
|
|
* interfaces specified at creation, in order (this is important
|
|
* for determining how method invocation is resolved). Note that
|
|
* a proxy class implements {@link Serializable}, at least
|
|
* implicitly, since Proxy does, but true serial behavior
|
|
* depends on using a serializable invocation handler as well.</li>
|
|
* <li>If at least one interface is non-public, the proxy class
|
|
* will be in the same package. Otherwise, the package is
|
|
* unspecified. This will work even if the package is sealed
|
|
* from user-generated classes, because Proxy classes are
|
|
* generated by a trusted source. Meanwhile, the proxy class
|
|
* belongs to the classloader you designated.</li>
|
|
* <li>Reflection works as expected: {@link Class#getInterfaces()} and
|
|
* {@link Class#getMethods()} work as they do on normal classes.</li>
|
|
* <li>The method {@link #isProxyClass()} will distinguish between
|
|
* true proxy classes and user extensions of this class. It only
|
|
* returns true for classes created by {@link #getProxyClass}.</li>
|
|
* <li>The {@link ProtectionDomain} of a proxy class is the same as for
|
|
* bootstrap classes, such as Object or Proxy, since it is created by
|
|
* a trusted source. This protection domain will typically be granted
|
|
* {@link java.security.AllPermission}. But this is not a security
|
|
* risk, since there are adequate permissions on reflection, which is
|
|
* the only way to create an instance of the proxy class.</li>
|
|
* <li>The proxy class contains a single constructor, which takes as
|
|
* its only argument an {@link InvocationHandler}. The method
|
|
* {@link #newInstance} is shorthand to do the necessary
|
|
* reflection.</li>
|
|
* </ul>
|
|
*
|
|
* <h3>Proxy Instances</h3>
|
|
* A proxy instance is an instance of a proxy class. It has the
|
|
* following properties, many of which follow from the properties of a
|
|
* proxy class listed above:
|
|
* <ul>
|
|
* <li>For a proxy class with Foo listed as one of its interfaces, the
|
|
* expression <code>proxy instanceof Foo</code> will return true,
|
|
* and the expression <code>(Foo) proxy</code> will succeed without
|
|
* a {@link ClassCastException}.</li>
|
|
* <li>Each proxy instance has an invocation handler, which can be
|
|
* accessed by {@link #getInvocationHandler(Object)}. Any call
|
|
* to an interface method, including {@link Object#hashcode()},
|
|
* {@link Object#equals(Object)}, or {@link Object#toString()},
|
|
* but excluding the public final methods of Object, will be
|
|
* encoded and passed to the {@link InvocationHandler#invoke}
|
|
* method of this handler.</li>
|
|
* </ul>
|
|
*
|
|
* <h3>Inheritance Issues</h3>
|
|
* A proxy class may inherit a method from more than one interface.
|
|
* The order in which interfaces are listed matters, because it determines
|
|
* which reflected {@link Method} object will be passed to the invocation
|
|
* handler. This means that the dynamically generated class cannot
|
|
* determine through which interface a method is being invoked.<p>
|
|
*
|
|
* In short, if a method is declared in Object (namely, hashCode,
|
|
* equals, or toString), then Object will be used; otherwise, the
|
|
* leftmost interface that inherits or declares a method will be used,
|
|
* even if it has a more permissive throws clause than what the proxy
|
|
* class is allowed. Thus, in the invocation handler, it is not always
|
|
* safe to assume that every class listed in the throws clause of the
|
|
* passed Method object can safely be thrown; fortunately, the Proxy
|
|
* instance is robust enough to wrap all illegal checked exceptions in
|
|
* {@link UndeclaredThrowableException}.
|
|
*
|
|
* @see InvocationHandler
|
|
* @see UndeclaredThrowableException
|
|
* @see Class
|
|
* @author Eric Blake <ebb9@email.byu.edu>
|
|
* @since 1.3
|
|
* @status updated to 1.4, except for the use of ProtectionDomain
|
|
*/
|
|
public class Proxy implements Serializable
|
|
{
|
|
/**
|
|
* Compatible with JDK 1.3+.
|
|
*/
|
|
private static final long serialVersionUID = -2222568056686623797L;
|
|
|
|
/**
|
|
* Map of ProxyType to proxy class.
|
|
*
|
|
* @XXX This prevents proxy classes from being garbage collected.
|
|
* java.util.WeakHashSet is not appropriate, because that collects the
|
|
* keys, but we are interested in collecting the elements.
|
|
*/
|
|
private static final Map proxyClasses = new HashMap();
|
|
|
|
/**
|
|
* The invocation handler for this proxy instance. For Proxy, this
|
|
* field is unused, but it appears here in order to be serialized in all
|
|
* proxy classes.
|
|
*
|
|
* <em>NOTE</em>: This implementation is more secure for proxy classes
|
|
* than what Sun specifies. Sun does not require h to be immutable, but
|
|
* this means you could change h after the fact by reflection. However,
|
|
* by making h immutable, we may break non-proxy classes which extend
|
|
* Proxy.
|
|
* @serial invocation handler associated with this proxy instance
|
|
*/
|
|
protected InvocationHandler h;
|
|
|
|
/**
|
|
* Constructs a new Proxy from a subclass (usually a proxy class),
|
|
* with the specified invocation handler.
|
|
*
|
|
* <em>NOTE</em>: This throws a NullPointerException if you attempt
|
|
* to create a proxy instance with a null handler using reflection.
|
|
* This behavior is not yet specified by Sun; see Sun Bug 4487672.
|
|
*
|
|
* @param handler the invocation handler, may be null if the subclass
|
|
* is not a proxy class
|
|
* @throws NullPointerException if handler is null and this is a proxy
|
|
* instance
|
|
*/
|
|
protected Proxy(InvocationHandler handler)
|
|
{
|
|
if (handler == null && isProxyClass(getClass()))
|
|
throw new NullPointerException("invalid handler");
|
|
h = handler;
|
|
}
|
|
|
|
/**
|
|
* Returns the proxy {@link Class} for the given ClassLoader and array
|
|
* of interfaces, dynamically generating it if necessary.
|
|
*
|
|
* There are several restrictions on this method, the violation of
|
|
* which will result in an IllegalArgumentException or
|
|
* NullPointerException:
|
|
* <ul>
|
|
* <li>All objects in `interfaces' must represent distinct interfaces.
|
|
* Classes, primitive types, null, and duplicates are forbidden.</li>
|
|
* <li>The interfaces must be visible in the specified ClassLoader.
|
|
* In other words, for each interface i:
|
|
* <code>Class.forName(i.getName(), false, loader) == i</code>
|
|
* must be true.</li>
|
|
* <li>All non-public interfaces (if any) must reside in the same
|
|
* package, or the proxy class would be non-instantiable. If
|
|
* there are no non-public interfaces, the package of the proxy
|
|
* class is unspecified.</li>
|
|
* <li>All interfaces must be compatible - if two declare a method
|
|
* with the same name and parameters, the return type must be
|
|
* the same and the throws clause of the proxy class will be
|
|
* the maximal subset of subclasses of the throws clauses for
|
|
* each method that is overridden.</li>
|
|
* <li>VM constraints limit the number of interfaces a proxy class
|
|
* may directly implement (however, the indirect inheritance
|
|
* of {@link Serializable} does not count against this limit).
|
|
* Even though most VMs can theoretically have 65535
|
|
* superinterfaces for a class, the actual limit is smaller
|
|
* because a class's constant pool is limited to 65535 entries,
|
|
* and not all entries can be interfaces.</li>
|
|
* </ul><p>
|
|
*
|
|
* Note that different orders of interfaces produce distinct classes.
|
|
*
|
|
* @param loader the class loader to define the proxy class in; null
|
|
* implies the bootstrap class loader
|
|
* @param interfaces the array of interfaces the proxy class implements,
|
|
* may be empty, but not null
|
|
* @return the Class object of the proxy class
|
|
* @throws IllegalArgumentException if the constraints above were
|
|
* violated, except for problems with null
|
|
* @throws NullPointerException if `interfaces' is null or contains
|
|
* a null entry
|
|
*/
|
|
// synchronized so that we aren't trying to build the same class
|
|
// simultaneously in two threads
|
|
public static synchronized Class getProxyClass(ClassLoader loader,
|
|
Class[] interfaces)
|
|
{
|
|
interfaces = (Class[]) interfaces.clone();
|
|
ProxyType pt = new ProxyType(loader, interfaces);
|
|
Class clazz = (Class) proxyClasses.get(pt);
|
|
if (clazz == null)
|
|
{
|
|
if (Configuration.HAVE_NATIVE_GET_PROXY_CLASS)
|
|
clazz = getProxyClass0(loader, interfaces);
|
|
else
|
|
{
|
|
ProxyData data = (Configuration.HAVE_NATIVE_GET_PROXY_DATA
|
|
? getProxyData0(loader, interfaces)
|
|
: ProxyData.getProxyData(pt));
|
|
|
|
// FIXME workaround for bug in gcj 3.0.x
|
|
// Not needed with the latest gcj from cvs
|
|
//clazz = (Configuration.HAVE_NATIVE_GENERATE_PROXY_CLASS
|
|
// ? generateProxyClass0(loader, data)
|
|
// : new ClassFactory(data).generate(loader));
|
|
if (Configuration.HAVE_NATIVE_GENERATE_PROXY_CLASS)
|
|
clazz = generateProxyClass0(loader, data);
|
|
else
|
|
{
|
|
ClassFactory cf = new ClassFactory(data);
|
|
clazz = cf.generate(loader);
|
|
}
|
|
}
|
|
|
|
Object check = proxyClasses.put(pt, clazz);
|
|
// assert check == null && clazz != null;
|
|
if (check != null || clazz == null)
|
|
throw new InternalError(/*"Fatal flaw in getProxyClass"*/);
|
|
}
|
|
return clazz;
|
|
}
|
|
|
|
/**
|
|
* Combines several methods into one. This is equivalent to:
|
|
* <pre>
|
|
* Proxy.getProxyClass(loader, interfaces)
|
|
* .getConstructor(new Class[] {InvocationHandler.class})
|
|
* .newInstance(new Object[] {handler});
|
|
* </pre>
|
|
* except that it will not fail with the normal problems caused
|
|
* by reflection. It can still fail for the same reasons documented
|
|
* in getProxyClass, or if handler is null.
|
|
*
|
|
* @param loader the class loader to define the proxy class in; null
|
|
* implies the bootstrap class loader
|
|
* @param interfaces the array of interfaces the proxy class implements,
|
|
* may be empty, but not null
|
|
* @param handler the invocation handler, may not be null
|
|
* @return a proxy instance implementing the specified interfaces
|
|
* @throws IllegalArgumentException if the constraints for getProxyClass
|
|
* were violated, except for problems with null
|
|
* @throws NullPointerException if `interfaces' is null or contains
|
|
* a null entry, or if handler is null
|
|
* @see #getProxyClass(ClassLoader, Class[])
|
|
* @see Class#getConstructor(Class[])
|
|
* @see Constructor#newInstance(Object[])
|
|
*/
|
|
public static Object newProxyInstance(ClassLoader loader,
|
|
Class[] interfaces,
|
|
InvocationHandler handler)
|
|
{
|
|
try
|
|
{
|
|
// getProxyClass() and Proxy() throw the necessary exceptions
|
|
return getProxyClass(loader, interfaces)
|
|
.getConstructor(new Class[] {InvocationHandler.class})
|
|
.newInstance(new Object[] {handler});
|
|
}
|
|
catch (RuntimeException e)
|
|
{
|
|
// Let IllegalArgumentException, NullPointerException escape.
|
|
// assert e instanceof IllegalArgumentException
|
|
// || e instanceof NullPointerException;
|
|
throw e;
|
|
}
|
|
catch (InvocationTargetException e)
|
|
{
|
|
// Let wrapped NullPointerException escape.
|
|
// assert e.getTargetException() instanceof NullPointerException
|
|
throw (NullPointerException) e.getCause();
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// Covers InstantiationException, IllegalAccessException,
|
|
// NoSuchMethodException, none of which should be generated
|
|
// if the proxy class was generated correctly.
|
|
// assert false;
|
|
throw (Error) new InternalError("Unexpected: " + e).initCause(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns true if and only if the Class object is a dynamically created
|
|
* proxy class (created by <code>getProxyClass</code> or by the
|
|
* syntactic sugar of <code>newProxyInstance</code>).
|
|
*
|
|
* <p>This check is secure (in other words, it is not simply
|
|
* <code>clazz.getSuperclass() == Proxy.class</code>), it will not
|
|
* be spoofed by non-proxy classes that extend Proxy.
|
|
*
|
|
* @param clazz the class to check, must not be null
|
|
* @return true if the class represents a proxy class
|
|
* @throws NullPointerException if clazz is null
|
|
*/
|
|
// This is synchronized on the off chance that another thread is
|
|
// trying to add a class to the map at the same time we read it.
|
|
public static synchronized boolean isProxyClass(Class clazz)
|
|
{
|
|
if (! Proxy.class.isAssignableFrom(clazz))
|
|
return false;
|
|
// This is a linear search, even though we could do an O(1) search
|
|
// using new ProxyType(clazz.getClassLoader(), clazz.getInterfaces()).
|
|
return proxyClasses.containsValue(clazz);
|
|
}
|
|
|
|
/**
|
|
* Returns the invocation handler for the given proxy instance.<p>
|
|
*
|
|
* <em>NOTE</em>: We guarantee a non-null result if successful,
|
|
* but Sun allows the creation of a proxy instance with a null
|
|
* handler. See the comments for {@link #Proxy(InvocationHandler)}.
|
|
*
|
|
* @param proxy the proxy instance, must not be null
|
|
* @return the invocation handler, guaranteed non-null.
|
|
* @throws IllegalArgumentException if
|
|
* <code>Proxy.isProxyClass(proxy.getClass())</code> returns false.
|
|
* @throws NullPointerException if proxy is null
|
|
*/
|
|
public static InvocationHandler getInvocationHandler(Object proxy)
|
|
{
|
|
if (! isProxyClass(proxy.getClass()))
|
|
throw new IllegalArgumentException("not a proxy instance");
|
|
return ((Proxy) proxy).h;
|
|
}
|
|
|
|
/**
|
|
* Optional native method to replace (and speed up) the pure Java
|
|
* implementation of getProxyClass. Only needed if
|
|
* Configuration.HAVE_NATIVE_GET_PROXY_CLASS is true, this does the
|
|
* work of both getProxyData0 and generateProxyClass0 with no
|
|
* intermediate form in Java. The native code may safely assume that
|
|
* this class must be created, and does not already exist.
|
|
*
|
|
* @param loader the class loader to define the proxy class in; null
|
|
* implies the bootstrap class loader
|
|
* @param interfaces the interfaces the class will extend
|
|
* @return the generated proxy class
|
|
* @throws IllegalArgumentException if the constraints for getProxyClass
|
|
* were violated, except for problems with null
|
|
* @throws NullPointerException if `interfaces' is null or contains
|
|
* a null entry, or if handler is null
|
|
* @see Configuration#HAVE_NATIVE_GET_PROXY_CLASS
|
|
* @see #getProxyClass(ClassLoader, Class[])
|
|
* @see #getProxyData0(ClassLoader, Class[])
|
|
* @see #generateProxyClass0(ProxyData)
|
|
*/
|
|
private static native Class getProxyClass0(ClassLoader loader,
|
|
Class[] interfaces);
|
|
|
|
/**
|
|
* Optional native method to replace (and speed up) the pure Java
|
|
* implementation of getProxyData. Only needed if
|
|
* Configuration.HAVE_NATIVE_GET_PROXY_DATA is true. The native code
|
|
* may safely assume that a new ProxyData object must be created which
|
|
* does not duplicate any existing ones.
|
|
*
|
|
* @param loader the class loader to define the proxy class in; null
|
|
* implies the bootstrap class loader
|
|
* @param interfaces the interfaces the class will extend
|
|
* @return all data that is required to make this proxy class
|
|
* @throws IllegalArgumentException if the constraints for getProxyClass
|
|
* were violated, except for problems with null
|
|
* @throws NullPointerException if `interfaces' is null or contains
|
|
* a null entry, or if handler is null
|
|
* @see Configuration.HAVE_NATIVE_GET_PROXY_DATA
|
|
* @see #getProxyClass(ClassLoader, Class[])
|
|
* @see #getProxyClass0(ClassLoader, Class[])
|
|
* @see ProxyType#getProxyData()
|
|
*/
|
|
private static native ProxyData getProxyData0(ClassLoader loader,
|
|
Class[] interfaces);
|
|
|
|
/**
|
|
* Optional native method to replace (and speed up) the pure Java
|
|
* implementation of generateProxyClass. Only needed if
|
|
* Configuration.HAVE_NATIVE_GENERATE_PROXY_CLASS is true. The native
|
|
* code may safely assume that a new Class must be created, and that
|
|
* the ProxyData object does not describe any existing class.
|
|
*
|
|
* @param loader the class loader to define the proxy class in; null
|
|
* implies the bootstrap class loader
|
|
* @param data the struct of information to convert to a Class. This
|
|
* has already been verified for all problems except exceeding
|
|
* VM limitations
|
|
* @return the newly generated class
|
|
* @throws IllegalArgumentException if VM limitations are exceeded
|
|
* @see #getProxyClass(ClassLoader, Class[])
|
|
* @see #getProxyClass0(ClassLoader, Class[])
|
|
* @see ProxyData#generateProxyClass(ClassLoader)
|
|
*/
|
|
private static native Class generateProxyClass0(ClassLoader loader,
|
|
ProxyData data);
|
|
|
|
/**
|
|
* Helper class for mapping unique ClassLoader and interface combinations
|
|
* to proxy classes.
|
|
*
|
|
* @author Eric Blake <ebb9@email.byu.edu>
|
|
*/
|
|
private static final class ProxyType
|
|
{
|
|
/**
|
|
* Store the class loader (may be null)
|
|
*/
|
|
final ClassLoader loader;
|
|
|
|
/**
|
|
* Store the interfaces (never null, all elements are interfaces)
|
|
*/
|
|
final Class[] interfaces;
|
|
|
|
/**
|
|
* Construct the helper object.
|
|
*
|
|
* @param loader the class loader to define the proxy class in; null
|
|
* implies the bootstrap class loader
|
|
* @param interfaces an array of interfaces
|
|
*/
|
|
ProxyType(ClassLoader loader, Class[] interfaces)
|
|
{
|
|
if (loader == null)
|
|
loader = ClassLoader.getSystemClassLoader();
|
|
this.loader = loader;
|
|
this.interfaces = interfaces;
|
|
}
|
|
|
|
/**
|
|
* Calculates the hash code.
|
|
*
|
|
* @return a combination of the classloader and interfaces hashcodes.
|
|
*/
|
|
public int hashCode()
|
|
{
|
|
//loader is always not null
|
|
int hash = loader.hashCode();
|
|
for (int i = 0; i < interfaces.length; i++)
|
|
hash = hash * 31 + interfaces[i].hashCode();
|
|
return hash;
|
|
}
|
|
|
|
// A more comprehensive comparison of two arrays,
|
|
// ignore array element order, and
|
|
// ignore redundant elements
|
|
private static boolean sameTypes(Class arr1[], Class arr2[]) {
|
|
if (arr1.length == 1 && arr2.length == 1) {
|
|
return arr1[0] == arr2[0];
|
|
}
|
|
|
|
// total occurrance of elements of arr1 in arr2
|
|
int total_occ_of_arr1_in_arr2 = 0;
|
|
each_type:
|
|
for (int i = arr1.length; --i >= 0; )
|
|
{
|
|
Class t = arr1[i];
|
|
for (int j = i; --j >= 0; )
|
|
{
|
|
if (t == arr1[j])
|
|
{ //found duplicate type
|
|
continue each_type;
|
|
}
|
|
}
|
|
|
|
// count c(a unique element of arr1)'s
|
|
// occurrences in arr2
|
|
int occ_in_arr2 = 0;
|
|
for (int j = arr2.length; --j >= 0; )
|
|
{
|
|
if (t == arr2[j])
|
|
{
|
|
++occ_in_arr2;
|
|
}
|
|
}
|
|
if (occ_in_arr2 == 0)
|
|
{ // t does not occur in arr2
|
|
return false;
|
|
}
|
|
|
|
total_occ_of_arr1_in_arr2 += occ_in_arr2;
|
|
}
|
|
// now, each element of arr2 must have been visited
|
|
return total_occ_of_arr1_in_arr2 == arr2.length;
|
|
}
|
|
|
|
/**
|
|
* Calculates equality.
|
|
*
|
|
* @param the object to compare to
|
|
* @return true if it is a ProxyType with same data
|
|
*/
|
|
public boolean equals(Object other)
|
|
{
|
|
ProxyType pt = (ProxyType) other;
|
|
if (loader != pt.loader || interfaces.length != pt.interfaces.length)
|
|
return false;
|
|
return sameTypes(interfaces, pt.interfaces);
|
|
}
|
|
} // class ProxyType
|
|
|
|
/**
|
|
* Helper class which allows hashing of a method name and signature
|
|
* without worrying about return type, declaring class, or throws clause,
|
|
* and which reduces the maximally common throws clause between two methods
|
|
*
|
|
* @author Eric Blake <ebb9@email.byu.edu>
|
|
*/
|
|
private static final class ProxySignature
|
|
{
|
|
/**
|
|
* The core signatures which all Proxy instances handle.
|
|
*/
|
|
static final HashMap coreMethods = new HashMap();
|
|
static
|
|
{
|
|
try
|
|
{
|
|
ProxySignature sig
|
|
= new ProxySignature(Object.class
|
|
.getMethod("equals",
|
|
new Class[] {Object.class}));
|
|
coreMethods.put(sig, sig);
|
|
sig = new ProxySignature(Object.class.getMethod("hashCode", null));
|
|
coreMethods.put(sig, sig);
|
|
sig = new ProxySignature(Object.class.getMethod("toString", null));
|
|
coreMethods.put(sig, sig);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// assert false;
|
|
throw (Error) new InternalError("Unexpected: " + e).initCause(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The underlying Method object, never null
|
|
*/
|
|
final Method method;
|
|
|
|
/**
|
|
* The set of compatible thrown exceptions, may be empty
|
|
*/
|
|
final Set exceptions = new HashSet();
|
|
|
|
/**
|
|
* Construct a signature
|
|
*
|
|
* @param method the Method this signature is based on, never null
|
|
*/
|
|
ProxySignature(Method method)
|
|
{
|
|
this.method = method;
|
|
Class[] exc = method.getExceptionTypes();
|
|
int i = exc.length;
|
|
while (--i >= 0)
|
|
{
|
|
// discard unchecked exceptions
|
|
if (Error.class.isAssignableFrom(exc[i])
|
|
|| RuntimeException.class.isAssignableFrom(exc[i]))
|
|
continue;
|
|
exceptions.add(exc[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a method, make sure it's return type is identical
|
|
* to this, and adjust this signature's throws clause appropriately
|
|
*
|
|
* @param other the signature to merge in
|
|
* @throws IllegalArgumentException if the return types conflict
|
|
*/
|
|
void checkCompatibility(ProxySignature other)
|
|
{
|
|
if (method.getReturnType() != other.method.getReturnType())
|
|
throw new IllegalArgumentException("incompatible return types: "
|
|
+ method + ", " + other.method);
|
|
|
|
// if you can think of a more efficient way than this O(n^2) search,
|
|
// implement it!
|
|
int size1 = exceptions.size();
|
|
int size2 = other.exceptions.size();
|
|
boolean[] valid1 = new boolean[size1];
|
|
boolean[] valid2 = new boolean[size2];
|
|
Iterator itr = exceptions.iterator();
|
|
int pos = size1;
|
|
while (--pos >= 0)
|
|
{
|
|
Class c1 = (Class) itr.next();
|
|
Iterator itr2 = other.exceptions.iterator();
|
|
int pos2 = size2;
|
|
while (--pos2 >= 0)
|
|
{
|
|
Class c2 = (Class) itr2.next();
|
|
if (c2.isAssignableFrom(c1))
|
|
valid1[pos] = true;
|
|
if (c1.isAssignableFrom(c2))
|
|
valid2[pos2] = true;
|
|
}
|
|
}
|
|
pos = size1;
|
|
itr = exceptions.iterator();
|
|
while (--pos >= 0)
|
|
{
|
|
itr.next();
|
|
if (! valid1[pos])
|
|
itr.remove();
|
|
}
|
|
pos = size2;
|
|
itr = other.exceptions.iterator();
|
|
while (--pos >= 0)
|
|
{
|
|
itr.next();
|
|
if (! valid2[pos])
|
|
itr.remove();
|
|
}
|
|
exceptions.addAll(other.exceptions);
|
|
}
|
|
|
|
/**
|
|
* Calculates the hash code.
|
|
*
|
|
* @return a combination of name and parameter types
|
|
*/
|
|
public int hashCode()
|
|
{
|
|
int hash = method.getName().hashCode();
|
|
Class[] types = method.getParameterTypes();
|
|
for (int i = 0; i < types.length; i++)
|
|
hash = hash * 31 + types[i].hashCode();
|
|
return hash;
|
|
}
|
|
|
|
/**
|
|
* Calculates equality.
|
|
*
|
|
* @param the object to compare to
|
|
* @return true if it is a ProxySignature with same data
|
|
*/
|
|
public boolean equals(Object other)
|
|
{
|
|
ProxySignature ps = (ProxySignature) other;
|
|
Class[] types1 = method.getParameterTypes();
|
|
Class[] types2 = ps.method.getParameterTypes();
|
|
if (! method.getName().equals(ps.method.getName())
|
|
|| types1.length != types2.length)
|
|
return false;
|
|
int i = types1.length;
|
|
while (--i >= 0)
|
|
if (types1[i] != types2[i])
|
|
return false;
|
|
return true;
|
|
}
|
|
} // class ProxySignature
|
|
|
|
/**
|
|
* A flat representation of all data needed to generate bytecode/instantiate
|
|
* a proxy class. This is basically a struct.
|
|
*
|
|
* @author Eric Blake <ebb9@email.byu.edu>
|
|
*/
|
|
private static final class ProxyData
|
|
{
|
|
/**
|
|
* The package this class is in. Possibly null, meaning the unnamed
|
|
* package.
|
|
*/
|
|
Package pack;
|
|
|
|
/**
|
|
* The interfaces this class implements. Non-null, but possibly empty.
|
|
*/
|
|
Class[] interfaces;
|
|
|
|
/**
|
|
* The Method objects this class must pass as the second argument to
|
|
* invoke (also useful for determining what methods this class has).
|
|
* Non-null, non-empty (includes at least Object.hashCode, Object.equals,
|
|
* and Object.toString).
|
|
*/
|
|
Method[] methods;
|
|
|
|
/**
|
|
* The exceptions that do not need to be wrapped in
|
|
* UndeclaredThrowableException. exceptions[i] is the same as, or a
|
|
* subset of subclasses, of methods[i].getExceptionTypes(), depending on
|
|
* compatible throws clauses with multiple inheritance. It is unspecified
|
|
* if these lists include or exclude subclasses of Error and
|
|
* RuntimeException, but excluding them is harmless and generates a
|
|
* smaller class.
|
|
*/
|
|
Class[][] exceptions;
|
|
|
|
/**
|
|
* For unique id's
|
|
*/
|
|
private static int count = 0;
|
|
|
|
/**
|
|
* The id of this proxy class
|
|
*/
|
|
final int id = count++;
|
|
|
|
/**
|
|
* Construct a ProxyData with uninitialized data members.
|
|
*/
|
|
ProxyData()
|
|
{
|
|
}
|
|
|
|
/**
|
|
* Verifies that the arguments are legal, and sets up remaining data
|
|
* This should only be called when a class must be generated, as
|
|
* it is expensive.
|
|
*
|
|
* @param pt the ProxyType to convert to ProxyData
|
|
* @return the flattened, verified ProxyData structure for use in
|
|
* class generation
|
|
* @throws IllegalArgumentException if `interfaces' contains
|
|
* non-interfaces or incompatible combinations, and verify is true
|
|
* @throws NullPointerException if interfaces is null or contains null
|
|
*/
|
|
static ProxyData getProxyData(ProxyType pt)
|
|
{
|
|
Map method_set = (Map) ProxySignature.coreMethods.clone();
|
|
boolean in_package = false; // true if we encounter non-public interface
|
|
|
|
ProxyData data = new ProxyData();
|
|
data.interfaces = pt.interfaces;
|
|
|
|
// if interfaces is too large, we croak later on when the constant
|
|
// pool overflows
|
|
int i = data.interfaces.length;
|
|
while (--i >= 0)
|
|
{
|
|
Class inter = data.interfaces[i];
|
|
if (! inter.isInterface())
|
|
throw new IllegalArgumentException("not an interface: " + inter);
|
|
try
|
|
{
|
|
if (Class.forName(inter.getName(), false, pt.loader) != inter)
|
|
throw new IllegalArgumentException("not accessible in "
|
|
+ "classloader: " + inter);
|
|
}
|
|
catch (ClassNotFoundException e)
|
|
{
|
|
throw new IllegalArgumentException("not accessible in "
|
|
+ "classloader: " + inter);
|
|
}
|
|
if (! Modifier.isPublic(inter.getModifiers()))
|
|
if (in_package)
|
|
{
|
|
Package p = inter.getPackage();
|
|
if (data.pack != inter.getPackage())
|
|
throw new IllegalArgumentException("non-public interfaces "
|
|
+ "from different "
|
|
+ "packages");
|
|
}
|
|
else
|
|
{
|
|
in_package = true;
|
|
data.pack = inter.getPackage();
|
|
}
|
|
for (int j = i-1; j >= 0; j--)
|
|
if (data.interfaces[j] == inter)
|
|
throw new IllegalArgumentException("duplicate interface: "
|
|
+ inter);
|
|
Method[] methods = inter.getMethods();
|
|
int j = methods.length;
|
|
while (--j >= 0)
|
|
{
|
|
ProxySignature sig = new ProxySignature(methods[j]);
|
|
ProxySignature old = (ProxySignature) method_set.put(sig, sig);
|
|
if (old != null)
|
|
sig.checkCompatibility(old);
|
|
}
|
|
}
|
|
|
|
i = method_set.size();
|
|
data.methods = new Method[i];
|
|
data.exceptions = new Class[i][];
|
|
Iterator itr = method_set.values().iterator();
|
|
while (--i >= 0)
|
|
{
|
|
ProxySignature sig = (ProxySignature) itr.next();
|
|
data.methods[i] = sig.method;
|
|
data.exceptions[i] = (Class[]) sig.exceptions
|
|
.toArray(new Class[sig.exceptions.size()]);
|
|
}
|
|
return data;
|
|
}
|
|
} // class ProxyData
|
|
|
|
/**
|
|
* Does all the work of building a class. By making this a nested class,
|
|
* this code is not loaded in memory if the VM has a native
|
|
* implementation instead.
|
|
*
|
|
* @author Eric Blake <ebb9@email.byu.edu>
|
|
*/
|
|
private static final class ClassFactory
|
|
{
|
|
/** Constants for assisting the compilation */
|
|
private static final byte POOL = 0;
|
|
private static final byte FIELD = 1;
|
|
private static final byte METHOD = 2;
|
|
private static final byte INTERFACE = 3;
|
|
private static final String CTOR_SIG
|
|
= "(Ljava/lang/reflect/InvocationHandler;)V";
|
|
private static final String INVOKE_SIG = "(Ljava/lang/Object;"
|
|
+ "Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;";
|
|
|
|
/** Bytecodes for insertion in the class definition byte[] */
|
|
private static final char ACONST_NULL = 1;
|
|
private static final char ICONST_0 = 3;
|
|
private static final char BIPUSH = 16;
|
|
private static final char SIPUSH = 17;
|
|
private static final char ILOAD = 21;
|
|
private static final char ILOAD_0 = 26;
|
|
private static final char ALOAD_0 = 42;
|
|
private static final char ALOAD_1 = 43;
|
|
private static final char AALOAD = 50;
|
|
private static final char AASTORE = 83;
|
|
private static final char DUP = 89;
|
|
private static final char DUP_X1 = 90;
|
|
private static final char SWAP = 95;
|
|
private static final char IRETURN = 172;
|
|
private static final char LRETURN = 173;
|
|
private static final char FRETURN = 174;
|
|
private static final char DRETURN = 175;
|
|
private static final char ARETURN = 176;
|
|
private static final char RETURN = 177;
|
|
private static final char GETSTATIC = 178;
|
|
private static final char GETFIELD = 180;
|
|
private static final char INVOKEVIRTUAL = 182;
|
|
private static final char INVOKESPECIAL = 183;
|
|
private static final char INVOKESTATIC = 184;
|
|
private static final char INVOKEINTERFACE = 185;
|
|
private static final char NEW = 187;
|
|
private static final char ANEWARRAY = 189;
|
|
private static final char ATHROW = 191;
|
|
private static final char CHECKCAST = 192;
|
|
|
|
// Implementation note: we use StringBuffers to hold the byte data, since
|
|
// they automatically grow. However, we only use the low 8 bits of
|
|
// every char in the array, so we are using twice the necessary memory
|
|
// for the ease StringBuffer provides.
|
|
|
|
/** The constant pool. */
|
|
private final StringBuffer pool = new StringBuffer();
|
|
/** The rest of the class data. */
|
|
private final StringBuffer stream = new StringBuffer();
|
|
|
|
/** Map of strings to byte sequences, to minimize size of pool. */
|
|
private final Map poolEntries = new HashMap();
|
|
|
|
/** The VM name of this proxy class. */
|
|
private final String qualName;
|
|
|
|
/**
|
|
* The Method objects the proxy class refers to when calling the
|
|
* invocation handler.
|
|
*/
|
|
private final Method[] methods;
|
|
|
|
/**
|
|
* Initializes the buffers with the bytecode contents for a proxy class.
|
|
*
|
|
* @param data the remainder of the class data
|
|
* @throws IllegalArgumentException if anything else goes wrong this
|
|
* late in the game; as far as I can tell, this will only happen
|
|
* if the constant pool overflows, which is possible even when
|
|
* the user doesn't exceed the 65535 interface limit
|
|
*/
|
|
ClassFactory(ProxyData data)
|
|
{
|
|
methods = data.methods;
|
|
|
|
// magic = 0xcafebabe
|
|
// minor_version = 0
|
|
// major_version = 46
|
|
// constant_pool_count: place-holder for now
|
|
pool.append("\u00ca\u00fe\u00ba\u00be\0\0\0\56\0\0");
|
|
// constant_pool[], filled in as we go
|
|
|
|
// access_flags
|
|
putU2(Modifier.SUPER | Modifier.FINAL | Modifier.PUBLIC);
|
|
// this_class
|
|
qualName = ((data.pack == null ? "" : data.pack.getName() + '.')
|
|
+ "$Proxy" + data.id);
|
|
putU2(classInfo(TypeSignature.getEncodingOfClass(qualName, false)));
|
|
// super_class
|
|
putU2(classInfo("java/lang/reflect/Proxy"));
|
|
|
|
// interfaces_count
|
|
putU2(data.interfaces.length);
|
|
// interfaces[]
|
|
for (int i = 0; i < data.interfaces.length; i++)
|
|
putU2(classInfo(data.interfaces[i]));
|
|
|
|
// Recall that Proxy classes serialize specially, so we do not need
|
|
// to worry about a <clinit> method for this field. Instead, we
|
|
// just assign it by reflection after the class is successfully loaded.
|
|
// fields_count - private static Method[] m;
|
|
putU2(1);
|
|
// fields[]
|
|
// m.access_flags
|
|
putU2(Modifier.PRIVATE | Modifier.STATIC);
|
|
// m.name_index
|
|
putU2(utf8Info("m"));
|
|
// m.descriptor_index
|
|
putU2(utf8Info("[Ljava/lang/reflect/Method;"));
|
|
// m.attributes_count
|
|
putU2(0);
|
|
// m.attributes[]
|
|
|
|
// methods_count - # handler methods, plus <init>
|
|
putU2(methods.length + 1);
|
|
// methods[]
|
|
// <init>.access_flags
|
|
putU2(Modifier.PUBLIC);
|
|
// <init>.name_index
|
|
putU2(utf8Info("<init>"));
|
|
// <init>.descriptor_index
|
|
putU2(utf8Info(CTOR_SIG));
|
|
// <init>.attributes_count - only Code is needed
|
|
putU2(1);
|
|
// <init>.Code.attribute_name_index
|
|
putU2(utf8Info("Code"));
|
|
// <init>.Code.attribute_length = 18
|
|
// <init>.Code.info:
|
|
// $Proxynn(InvocationHandler h) { super(h); }
|
|
// <init>.Code.max_stack = 2
|
|
// <init>.Code.max_locals = 2
|
|
// <init>.Code.code_length = 6
|
|
// <init>.Code.code[]
|
|
stream.append("\0\0\0\22\0\2\0\2\0\0\0\6" + ALOAD_0 + ALOAD_1
|
|
+ INVOKESPECIAL);
|
|
putU2(refInfo(METHOD, "java/lang/reflect/Proxy", "<init>", CTOR_SIG));
|
|
// <init>.Code.exception_table_length = 0
|
|
// <init>.Code.exception_table[]
|
|
// <init>.Code.attributes_count = 0
|
|
// <init>.Code.attributes[]
|
|
stream.append(RETURN + "\0\0\0\0");
|
|
|
|
for (int i = methods.length - 1; i >= 0; i--)
|
|
emitMethod(i, data.exceptions[i]);
|
|
|
|
// attributes_count
|
|
putU2(0);
|
|
// attributes[] - empty; omit SourceFile attribute
|
|
// XXX should we mark this with a Synthetic attribute?
|
|
}
|
|
|
|
/**
|
|
* Produce the bytecode for a single method.
|
|
*
|
|
* @param i the index of the method we are building
|
|
* @param e the exceptions possible for the method
|
|
*/
|
|
private void emitMethod(int i, Class[] e)
|
|
{
|
|
// First, we precalculate the method length and other information.
|
|
|
|
Method m = methods[i];
|
|
Class[] paramtypes = m.getParameterTypes();
|
|
int wrap_overhead = 0; // max words taken by wrapped primitive
|
|
int param_count = 1; // 1 for this
|
|
int code_length = 16; // aload_0, getfield, aload_0, getstatic, const,
|
|
// aaload, const/aconst_null, invokeinterface
|
|
if (i > 5)
|
|
{
|
|
if (i > Byte.MAX_VALUE)
|
|
code_length += 2; // sipush
|
|
else
|
|
code_length++; // bipush
|
|
}
|
|
if (paramtypes.length > 0)
|
|
{
|
|
code_length += 3; // anewarray
|
|
if (paramtypes.length > Byte.MAX_VALUE)
|
|
code_length += 2; // sipush
|
|
else if (paramtypes.length > 5)
|
|
code_length++; // bipush
|
|
for (int j = 0; j < paramtypes.length; j++)
|
|
{
|
|
code_length += 4; // dup, const, load, store
|
|
Class type = paramtypes[j];
|
|
if (j > 5)
|
|
{
|
|
if (j > Byte.MAX_VALUE)
|
|
code_length += 2; // sipush
|
|
else
|
|
code_length++; // bipush
|
|
}
|
|
if (param_count >= 4)
|
|
code_length++; // 2-byte load
|
|
param_count++;
|
|
if (type.isPrimitive())
|
|
{
|
|
code_length += 7; // new, dup, invokespecial
|
|
if (type == long.class || type == double.class)
|
|
{
|
|
wrap_overhead = 3;
|
|
param_count++;
|
|
}
|
|
else if (wrap_overhead < 2)
|
|
wrap_overhead = 2;
|
|
}
|
|
}
|
|
}
|
|
int end_pc = code_length;
|
|
Class ret_type = m.getReturnType();
|
|
if (ret_type == void.class)
|
|
code_length++; // return
|
|
else if (ret_type.isPrimitive())
|
|
code_length += 7; // cast, invokevirtual, return
|
|
else
|
|
code_length += 4; // cast, return
|
|
int exception_count = 0;
|
|
boolean throws_throwable = false;
|
|
for (int j = 0; j < e.length; j++)
|
|
if (e[j] == Throwable.class)
|
|
{
|
|
throws_throwable = true;
|
|
break;
|
|
}
|
|
if (! throws_throwable)
|
|
{
|
|
exception_count = e.length + 3; // Throwable, Error, RuntimeException
|
|
code_length += 9; // new, dup_x1, swap, invokespecial, athrow
|
|
}
|
|
int handler_pc = code_length - 1;
|
|
StringBuffer signature = new StringBuffer("(");
|
|
for (int j = 0; j < paramtypes.length; j++)
|
|
signature.append(TypeSignature.getEncodingOfClass(paramtypes[j]));
|
|
signature.append(")").append(TypeSignature.getEncodingOfClass(ret_type));
|
|
|
|
// Now we have enough information to emit the method.
|
|
|
|
// handler.access_flags
|
|
putU2(Modifier.PUBLIC | Modifier.FINAL);
|
|
// handler.name_index
|
|
putU2(utf8Info(m.getName()));
|
|
// handler.descriptor_index
|
|
putU2(utf8Info(signature.toString()));
|
|
// handler.attributes_count - Code is necessary, Exceptions possible
|
|
putU2(e.length > 0 ? 2 : 1);
|
|
|
|
// handler.Code.info:
|
|
// type name(args) {
|
|
// try {
|
|
// return (type) h.invoke(this, methods[i], new Object[] {args});
|
|
// } catch (<declared Exceptions> e) {
|
|
// throw e;
|
|
// } catch (Throwable t) {
|
|
// throw new UndeclaredThrowableException(t);
|
|
// }
|
|
// }
|
|
// Special cases:
|
|
// if arg_n is primitive, wrap it
|
|
// if method throws Throwable, try-catch is not needed
|
|
// if method returns void, return statement not needed
|
|
// if method returns primitive, unwrap it
|
|
// save space by sharing code for all the declared handlers
|
|
|
|
// handler.Code.attribute_name_index
|
|
putU2(utf8Info("Code"));
|
|
// handler.Code.attribute_length
|
|
putU4(12 + code_length + 8 * exception_count);
|
|
// handler.Code.max_stack
|
|
putU2(param_count == 1 ? 4 : 7 + wrap_overhead);
|
|
// handler.Code.max_locals
|
|
putU2(param_count);
|
|
// handler.Code.code_length
|
|
putU4(code_length);
|
|
// handler.Code.code[]
|
|
putU1(ALOAD_0);
|
|
putU1(GETFIELD);
|
|
putU2(refInfo(FIELD, "java/lang/reflect/Proxy", "h",
|
|
"Ljava/lang/reflect/InvocationHandler;"));
|
|
putU1(ALOAD_0);
|
|
putU1(GETSTATIC);
|
|
putU2(refInfo(FIELD, TypeSignature.getEncodingOfClass(qualName, false),
|
|
"m", "[Ljava/lang/reflect/Method;"));
|
|
putConst(i);
|
|
putU1(AALOAD);
|
|
if (paramtypes.length > 0)
|
|
{
|
|
putConst(paramtypes.length);
|
|
putU1(ANEWARRAY);
|
|
putU2(classInfo("java/lang/Object"));
|
|
param_count = 1;
|
|
for (int j = 0; j < paramtypes.length; j++, param_count++)
|
|
{
|
|
putU1(DUP);
|
|
putConst(j);
|
|
if (paramtypes[j].isPrimitive())
|
|
{
|
|
putU1(NEW);
|
|
putU2(classInfo(wrapper(paramtypes[j])));
|
|
putU1(DUP);
|
|
}
|
|
putLoad(param_count, paramtypes[j]);
|
|
if (paramtypes[j].isPrimitive())
|
|
{
|
|
putU1(INVOKESPECIAL);
|
|
putU2(refInfo(METHOD, wrapper(paramtypes[j]), "<init>",
|
|
'(' + (TypeSignature
|
|
.getEncodingOfClass(paramtypes[j])
|
|
+ ")V")));
|
|
if (paramtypes[j] == long.class
|
|
|| paramtypes[j] == double.class)
|
|
param_count++;
|
|
}
|
|
putU1(AASTORE);
|
|
}
|
|
}
|
|
else
|
|
putU1(ACONST_NULL);
|
|
putU1(INVOKEINTERFACE);
|
|
putU2(refInfo(INTERFACE, "java/lang/reflect/InvocationHandler",
|
|
"invoke", INVOKE_SIG));
|
|
putU1(4); // InvocationHandler, this, Method, Object[]
|
|
putU1(0);
|
|
if (ret_type == void.class)
|
|
putU1(RETURN);
|
|
else if (ret_type.isPrimitive())
|
|
{
|
|
putU1(CHECKCAST);
|
|
putU2(classInfo(wrapper(ret_type)));
|
|
putU1(INVOKEVIRTUAL);
|
|
putU2(refInfo(METHOD, wrapper(ret_type),
|
|
ret_type.getName() + "Value",
|
|
"()" + TypeSignature.getEncodingOfClass(ret_type)));
|
|
if (ret_type == long.class)
|
|
putU1(LRETURN);
|
|
else if (ret_type == float.class)
|
|
putU1(FRETURN);
|
|
else if (ret_type == double.class)
|
|
putU1(DRETURN);
|
|
else
|
|
putU1(IRETURN);
|
|
}
|
|
else
|
|
{
|
|
putU1(CHECKCAST);
|
|
putU2(classInfo(ret_type));
|
|
putU1(ARETURN);
|
|
}
|
|
if (! throws_throwable)
|
|
{
|
|
putU1(NEW);
|
|
putU2(classInfo("java/lang/reflect/UndeclaredThrowableException"));
|
|
putU1(DUP_X1);
|
|
putU1(SWAP);
|
|
putU1(INVOKESPECIAL);
|
|
putU2(refInfo(METHOD,
|
|
"java/lang/reflect/UndeclaredThrowableException",
|
|
"<init>", "(Ljava/lang/Throwable;)V"));
|
|
putU1(ATHROW);
|
|
}
|
|
|
|
// handler.Code.exception_table_length
|
|
putU2(exception_count);
|
|
// handler.Code.exception_table[]
|
|
if (! throws_throwable)
|
|
{
|
|
// handler.Code.exception_table.start_pc
|
|
putU2(0);
|
|
// handler.Code.exception_table.end_pc
|
|
putU2(end_pc);
|
|
// handler.Code.exception_table.handler_pc
|
|
putU2(handler_pc);
|
|
// handler.Code.exception_table.catch_type
|
|
putU2(classInfo("java/lang/Error"));
|
|
// handler.Code.exception_table.start_pc
|
|
putU2(0);
|
|
// handler.Code.exception_table.end_pc
|
|
putU2(end_pc);
|
|
// handler.Code.exception_table.handler_pc
|
|
putU2(handler_pc);
|
|
// handler.Code.exception_table.catch_type
|
|
putU2(classInfo("java/lang/RuntimeException"));
|
|
for (int j = 0; j < e.length; j++)
|
|
{
|
|
// handler.Code.exception_table.start_pc
|
|
putU2(0);
|
|
// handler.Code.exception_table.end_pc
|
|
putU2(end_pc);
|
|
// handler.Code.exception_table.handler_pc
|
|
putU2(handler_pc);
|
|
// handler.Code.exception_table.catch_type
|
|
putU2(classInfo(e[j]));
|
|
}
|
|
// handler.Code.exception_table.start_pc
|
|
putU2(0);
|
|
// handler.Code.exception_table.end_pc
|
|
putU2(end_pc);
|
|
// handler.Code.exception_table.handler_pc -
|
|
// -8 for undeclared handler, which falls thru to normal one
|
|
putU2(handler_pc - 8);
|
|
// handler.Code.exception_table.catch_type
|
|
putU2(0);
|
|
}
|
|
// handler.Code.attributes_count
|
|
putU2(0);
|
|
// handler.Code.attributes[]
|
|
|
|
if (e.length > 0)
|
|
{
|
|
// handler.Exceptions.attribute_name_index
|
|
putU2(utf8Info("Exceptions"));
|
|
// handler.Exceptions.attribute_length
|
|
putU4(2 * e.length + 2);
|
|
// handler.Exceptions.number_of_exceptions
|
|
putU2(e.length);
|
|
// handler.Exceptions.exception_index_table[]
|
|
for (int j = 0; j < e.length; j++)
|
|
putU2(classInfo(e[j]));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates the Class object that corresponds to the bytecode buffers
|
|
* built when this object was constructed.
|
|
*
|
|
* @param loader the class loader to define the proxy class in; null
|
|
* implies the bootstrap class loader
|
|
* @return the proxy class Class object
|
|
*/
|
|
final Class generate(ClassLoader loader)
|
|
{
|
|
byte[] bytecode = new byte[pool.length() + stream.length()];
|
|
// More efficient to bypass calling charAt() repetitively.
|
|
char[] c = pool.toString().toCharArray();
|
|
int i = c.length;
|
|
while (--i >= 0)
|
|
bytecode[i] = (byte) c[i];
|
|
c = stream.toString().toCharArray();
|
|
i = c.length;
|
|
int j = bytecode.length;
|
|
while (i > 0)
|
|
bytecode[--j] = (byte) c[--i];
|
|
|
|
// Patch the constant pool size, which we left at 0 earlier.
|
|
int count = poolEntries.size() + 1;
|
|
bytecode[8] = (byte) (count >> 8);
|
|
bytecode[9] = (byte) count;
|
|
|
|
try
|
|
{
|
|
// XXX Do we require more native support here?
|
|
|
|
// XXX Security hole - it is possible for another thread to grab the
|
|
// VMClassLoader.defineClass Method object, and abuse it while we
|
|
// have temporarily made it accessible. Do we need to add some
|
|
// synchronization lock to prevent user reflection while we use it?
|
|
|
|
// XXX This is waiting on VM support for protection domains.
|
|
|
|
Class vmClassLoader = Class.forName("java.lang.VMClassLoader");
|
|
Class[] types = {ClassLoader.class, String.class,
|
|
byte[].class, int.class, int.class,
|
|
/* ProtectionDomain.class */ };
|
|
Method m = vmClassLoader.getDeclaredMethod("defineClass", types);
|
|
|
|
// Bypass the security check of setAccessible(true), since this
|
|
// is trusted code. But note the comment above about the security
|
|
// risk of doing this outside a synchronized block.
|
|
m.flag = true;
|
|
Object[] args = {loader, qualName, bytecode, new Integer(0),
|
|
new Integer(bytecode.length),
|
|
/* Object.class.getProtectionDomain() */ };
|
|
Class clazz = (Class) m.invoke(null, args);
|
|
m.flag = false;
|
|
|
|
// Finally, initialize the m field of the proxy class, before
|
|
// returning it.
|
|
|
|
// No security risk here, since clazz has not been exposed yet,
|
|
// so user code cannot grab the same reflection object.
|
|
Field f = clazz.getDeclaredField("m");
|
|
f.flag = true;
|
|
// we can share the array, because it is not publicized
|
|
f.set(null, methods);
|
|
f.flag = false;
|
|
|
|
return clazz;
|
|
}
|
|
catch (Throwable e)
|
|
{
|
|
// assert false;
|
|
throw (Error) new InternalError("Unexpected: " + e).initCause(e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Put a single byte on the stream.
|
|
*
|
|
* @param i the information to add (only lowest 8 bits are used)
|
|
*/
|
|
private void putU1(int i)
|
|
{
|
|
stream.append((char) i);
|
|
}
|
|
|
|
/**
|
|
* Put two bytes on the stream.
|
|
*
|
|
* @param i the information to add (only lowest 16 bits are used)
|
|
*/
|
|
private void putU2(int i)
|
|
{
|
|
stream.append((char) (i >> 8)).append((char) i);
|
|
}
|
|
|
|
/**
|
|
* Put four bytes on the stream.
|
|
*
|
|
* @param i the information to add (treated as unsigned)
|
|
*/
|
|
private void putU4(int i)
|
|
{
|
|
stream.append((char) (i >> 24)).append((char) (i >> 16));
|
|
stream.append((char) (i >> 8)).append((char) i);
|
|
}
|
|
|
|
/**
|
|
* Put bytecode to load a constant integer on the stream. This only
|
|
* needs to work for values less than Short.MAX_VALUE.
|
|
*
|
|
* @param i the int to add
|
|
*/
|
|
private void putConst(int i)
|
|
{
|
|
if (i >= -1 && i <= 5)
|
|
putU1(ICONST_0 + i);
|
|
else if (i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE)
|
|
{
|
|
putU1(BIPUSH);
|
|
putU1(i);
|
|
}
|
|
else
|
|
{
|
|
putU1(SIPUSH);
|
|
putU2(i);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Put bytecode to load a given local variable on the stream.
|
|
*
|
|
* @param i the slot to load
|
|
* @param type the base type of the load
|
|
*/
|
|
private void putLoad(int i, Class type)
|
|
{
|
|
int offset = 0;
|
|
if (type == long.class)
|
|
offset = 1;
|
|
else if (type == float.class)
|
|
offset = 2;
|
|
else if (type == double.class)
|
|
offset = 3;
|
|
else if (! type.isPrimitive())
|
|
offset = 4;
|
|
if (i < 4)
|
|
putU1(ILOAD_0 + 4 * offset + i);
|
|
else
|
|
{
|
|
putU1(ILOAD + offset);
|
|
putU1(i);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Given a primitive type, return its wrapper class name.
|
|
*
|
|
* @param clazz the primitive type (but not void.class)
|
|
* @return the internal form of the wrapper class name
|
|
*/
|
|
private String wrapper(Class clazz)
|
|
{
|
|
if (clazz == boolean.class)
|
|
return "java/lang/Boolean";
|
|
if (clazz == byte.class)
|
|
return "java/lang/Byte";
|
|
if (clazz == short.class)
|
|
return "java/lang/Short";
|
|
if (clazz == char.class)
|
|
return "java/lang/Character";
|
|
if (clazz == int.class)
|
|
return "java/lang/Integer";
|
|
if (clazz == long.class)
|
|
return "java/lang/Long";
|
|
if (clazz == float.class)
|
|
return "java/lang/Float";
|
|
if (clazz == double.class)
|
|
return "java/lang/Double";
|
|
// assert false;
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns the entry of this String in the Constant pool, adding it
|
|
* if necessary.
|
|
*
|
|
* @param str the String to resolve
|
|
* @return the index of the String in the constant pool
|
|
*/
|
|
private char utf8Info(String str)
|
|
{
|
|
String utf8 = toUtf8(str);
|
|
int len = utf8.length();
|
|
return poolIndex("\1" + (char) (len >> 8) + (char) (len & 0xff) + utf8);
|
|
}
|
|
|
|
/**
|
|
* Returns the entry of the appropriate class info structure in the
|
|
* Constant pool, adding it if necessary.
|
|
*
|
|
* @param name the class name, in internal form
|
|
* @return the index of the ClassInfo in the constant pool
|
|
*/
|
|
private char classInfo(String name)
|
|
{
|
|
char index = utf8Info(name);
|
|
char[] c = {7, (char) (index >> 8), (char) (index & 0xff)};
|
|
return poolIndex(new String(c));
|
|
}
|
|
|
|
/**
|
|
* Returns the entry of the appropriate class info structure in the
|
|
* Constant pool, adding it if necessary.
|
|
*
|
|
* @param clazz the class type
|
|
* @return the index of the ClassInfo in the constant pool
|
|
*/
|
|
private char classInfo(Class clazz)
|
|
{
|
|
return classInfo(TypeSignature.getEncodingOfClass(clazz.getName(),
|
|
false));
|
|
}
|
|
|
|
/**
|
|
* Returns the entry of the appropriate fieldref, methodref, or
|
|
* interfacemethodref info structure in the Constant pool, adding it
|
|
* if necessary.
|
|
*
|
|
* @param structure FIELD, METHOD, or INTERFACE
|
|
* @param clazz the class name, in internal form
|
|
* @param name the simple reference name
|
|
* @param type the type of the reference
|
|
* @return the index of the appropriate Info structure in the constant pool
|
|
*/
|
|
private char refInfo(byte structure, String clazz, String name,
|
|
String type)
|
|
{
|
|
char cindex = classInfo(clazz);
|
|
char ntindex = nameAndTypeInfo(name, type);
|
|
// relies on FIELD == 1, METHOD == 2, INTERFACE == 3
|
|
char[] c = {(char) (structure + 8),
|
|
(char) (cindex >> 8), (char) (cindex & 0xff),
|
|
(char) (ntindex >> 8), (char) (ntindex & 0xff)};
|
|
return poolIndex(new String(c));
|
|
}
|
|
|
|
/**
|
|
* Returns the entry of the appropriate nameAndTyperef info structure
|
|
* in the Constant pool, adding it if necessary.
|
|
*
|
|
* @param name the simple name
|
|
* @param type the reference type
|
|
* @return the index of the NameAndTypeInfo structure in the constant pool
|
|
*/
|
|
private char nameAndTypeInfo(String name, String type)
|
|
{
|
|
char nindex = utf8Info(name);
|
|
char tindex = utf8Info(type);
|
|
char[] c = {12, (char) (nindex >> 8), (char) (nindex & 0xff),
|
|
(char) (tindex >> 8), (char) (tindex & 0xff)};
|
|
return poolIndex(new String(c));
|
|
}
|
|
|
|
/**
|
|
* Converts a regular string to a UTF8 string, where the upper byte
|
|
* of every char is 0, and '\\u0000' is not in the string. This is
|
|
* basically to use a String as a fancy byte[], and while it is less
|
|
* efficient in memory use, it is easier for hashing.
|
|
*
|
|
* @param str the original, in straight unicode
|
|
* @return a modified string, in UTF8 format in the low bytes
|
|
*/
|
|
private String toUtf8(String str)
|
|
{
|
|
final char[] ca = str.toCharArray();
|
|
final int len = ca.length;
|
|
|
|
// Avoid object creation, if str is already fits UTF8.
|
|
int i;
|
|
for (i = 0; i < len; i++)
|
|
if (ca[i] == 0 || ca[i] > '\u007f')
|
|
break;
|
|
if (i == len)
|
|
return str;
|
|
|
|
final StringBuffer sb = new StringBuffer(str);
|
|
sb.setLength(i);
|
|
for ( ; i < len; i++)
|
|
{
|
|
final char c = ca[i];
|
|
if (c > 0 && c <= '\u007f')
|
|
sb.append(c);
|
|
else if (c <= '\u07ff') // includes '\0'
|
|
{
|
|
sb.append((char) (0xc0 | (c >> 6)));
|
|
sb.append((char) (0x80 | (c & 0x6f)));
|
|
}
|
|
else
|
|
{
|
|
sb.append((char) (0xe0 | (c >> 12)));
|
|
sb.append((char) (0x80 | ((c >> 6) & 0x6f)));
|
|
sb.append((char) (0x80 | (c & 0x6f)));
|
|
}
|
|
}
|
|
return sb.toString();
|
|
}
|
|
|
|
/**
|
|
* Returns the location of a byte sequence (conveniently wrapped in
|
|
* a String with all characters between \u0001 and \u00ff inclusive)
|
|
* in the constant pool, adding it if necessary.
|
|
*
|
|
* @param sequence the byte sequence to look for
|
|
* @return the index of the sequence
|
|
* @throws IllegalArgumentException if this would make the constant
|
|
* pool overflow
|
|
*/
|
|
private char poolIndex(String sequence)
|
|
{
|
|
Integer i = (Integer) poolEntries.get(sequence);
|
|
if (i == null)
|
|
{
|
|
// pool starts at index 1
|
|
int size = poolEntries.size() + 1;
|
|
if (size >= 65535)
|
|
throw new IllegalArgumentException("exceeds VM limitations");
|
|
i = new Integer(size);
|
|
poolEntries.put(sequence, i);
|
|
pool.append(sequence);
|
|
}
|
|
return (char) i.intValue();
|
|
}
|
|
} // class ClassFactory
|
|
}
|