just in case you really really really want to call a private methods with an argument of a private class which is inaccessible, like … :
i was looking on [icode]Throwable.printStackTrace(PrintStream s)[/icode] and found that there is a much lighter method next to it
[icode]private void printStackTrace(PrintStreamOrWriter s)[/icode], fair enough. to access it we can use reflections :
final Class<?> throwableClazz = Class.forName("java.lang.Throwable");
final Class<?> printStreamOrWriterClazz = Class.forName("java.lang.Throwable$PrintStreamOrWriter");
final Method printStackTraceMethod = throwableClazz.getDeclaredMethod("printStackTrace",printStreamOrWriterClazz);
printStackTraceMethod.setAccessible(true);
now we cannot invoke [icode]printStackTraceMethod[/icode] cos’ the argument needs to be a [icode]PrintStreamOrWriter[/icode] object which looks like this :
private abstract static class PrintStreamOrWriter
{
abstract Object lock();
abstract void println(Object o);
}
afaik, there is no way to implement it. a private abstract (inner) class. but it’s just what is required for [icode]Throwable.printStackTrace()[/icode], not a clumsy [icode]PrintStream[/icode]!.
so we can just write something that looks similar, no [icode]extends[/icode] or [icode]implements[/icode], just a simple class but its methods match (mostly) the signatures/modifiers of [icode]PrintStreamOrWriter[/icode], even if that is abstract :
class MyPrintStreamOrWriter
{
public final StringBuilder str;
public MyPrintStreamOrWriter(final StringBuilder str) { this.str = str; }
public Object lock() { return str; }
public void println(final Object o) { str.append(o.toString()).append('\n'); }
}
obviously that will not work :
final Error someError = new Error(new RuntimeException());
final StringBuilder str = new StringBuilder(32);
final MyPrintStreamOrWriter wrapper = new MyPrintStreamOrWriter(str);
printStackTraceMethod.invoke(someError,wrapper);
= java.lang.IllegalArgumentException: argument type mismatch
to make that work we can use sun.misc.Unsafe to make [icode]MyPrintStreamOrWriter[/icode] castable to [icode]PrintStreamOrWriter[/icode].
thanks to Serkan Özal who wrote down all the offsets required for that (http://zeroturnaround.com/rebellabs/dangerous-code-how-to-be-unsafe-with-java-classes-objects-in-memory/4/) :
private final static sun.misc.Unsafe unsafe = [...] // if you do not know how to obtain unsafe you should not use it :P
public static void makeCastable(final Class<?> from,final Class<?> to)
{
final long pt_from = getPointer(from);
final long pt_to = getPointer(to);
switch(Unsafe.ADDRESS_SIZE)
{
case 4 :
unsafe.putAddress(pt_from + 32 + 4, pt_to); // write to "primary supers" array index 1
break;
case 8 :
unsafe.putAddress(pt_from + 56 + 8, pt_to);
break;
default :
throw new IllegalStateException();
}
}
private final static boolean compressedOOPS = Unsafe.ADDRESS_SIZE == 8 && Unsafe.ARRAY_OBJECT_INDEX_SCALE == 4;
public static long getPointer(final Class<?> o)
{
switch(Unsafe.ADDRESS_SIZE)
{
case 4 :
return unsafe.getInt(o,80L);
case 8 :
if(compressedOOPS)
return int2ulong(unsafe.getInt(o,84L));
else
return unsafe.getLong(o,160L);
default :
throw new IllegalStateException();
}
}
public static long int2ulong(final int x)
{
if(x >= 0) return x;
return( ~0L>>>32 ) & x;
}
using this :
makeCastable(MyPrintStreamOrWriter.class, printStreamOrWriterClazz);
final Error someError = new Error(new RuntimeException());
final StringBuilder str = new StringBuilder(32);
final MyPrintStreamOrWriter wrapper = new MyPrintStreamOrWriter(str);
printStackTraceMethod.invoke(someError,wrapper);
final String stackTraceString = str.toString();
actually works even if illegal and prints into str as desired.
\o/
- create a class which looks like another class you cannot implement
- use unsafe to make it castable to that class, even if illegal
- use reflections to invoke methods as usual
- win