0f5ef77944
02/11/2015 - Added ZStringArray String Decrypter. (Thanks Righteous) 02/20/2015 - Moved the decompilers/disassemblers around. 02/20/2015 - Fixed a resource leak with Krakatau Decompiler/Disassembler/Assembler. 02/21/2015 - Fixed regex searching if your regex search contained a syntax error. 02/21/2015 - Added the compiler/decompiler instances to the BytecodeViewer API class. 02/21/2015 - Sped up the decompilers, each view pane runs its own decompiler thread. 02/21/2015 - Added Janino compiler, you can now compile the decompiled source code inside of BCV. 02/21/2015 - Added the editable option for almost all of the decompilers/disassemblers. 02/21/2015 - Cached the next/previous icons and added a resources class for all resources. 01/21/2015 - Renamed EZ-Injection as File-Run, however kept the plugin named EZ-Injection. 02/21/2015 - Dropped Groovy support, added .Java plugin compilation instead (now only 10mb). 02/21/2015 - Added support for reading resources, including displaying images, detecting pure ascii files and more. 02/21/2015 - Fixed an issue with loading an already selected node in the file navigation pane. 02/22/2015 - Added an error console to the Java compiler 02/22/2015 - Ensured the spawned Python/Krakatau processes are killed when closing BCV. 02/22/2015 - Made it more beginner friendly. 02/22/2015 - Fixed? The file navigation search. 02/22/2015 - Added a shit ton more comments to non-api related classes. 02/23/2015 - Added APK resources. 02/23/2015 - MORE ANDROID LOVE! Added APKTool.jar's decode. (Takes a while so it's a setting, also pumped the jar back to 16MB) 02/23/2015 - Added close all but this tab menu. 02/23/2015 - Not really code related, but added _install.bat and _uninstall.bat for the exe version of BCV. 02/23/2015 - Back to ASM5, packed dex2jar in its own obfuscated jar. 02/23/2015 - Added the annotations back to the Bytecode Decompiler. (Once again, thanks Bibl) 02/23/2015 - It once again works with Java 8 Jars.
425 lines
17 KiB
Java
425 lines
17 KiB
Java
|
|
/*
|
|
* Janino - An embedded Java[TM] compiler
|
|
*
|
|
* Copyright (c) 2001-2010, Arno Unkrig
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
|
* following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
|
|
* following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
|
|
* following disclaimer in the documentation and/or other materials provided with the distribution.
|
|
* 3. The name of the author may not be used to endorse or promote products derived from this software without
|
|
* specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
|
|
* THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
package org.codehaus.janino;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.FileInputStream;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStreamWriter;
|
|
import java.io.Reader;
|
|
import java.lang.reflect.Method;
|
|
import java.security.AccessController;
|
|
import java.security.PrivilegedAction;
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
import org.codehaus.commons.compiler.CompileException;
|
|
import org.codehaus.commons.compiler.Cookable;
|
|
import org.codehaus.commons.compiler.ErrorHandler;
|
|
import org.codehaus.commons.compiler.ICookable;
|
|
import org.codehaus.commons.compiler.ISimpleCompiler;
|
|
import org.codehaus.commons.compiler.Location;
|
|
import org.codehaus.commons.compiler.WarningHandler;
|
|
import org.codehaus.janino.Java.Type;
|
|
import org.codehaus.janino.Visitor.AtomVisitor;
|
|
import org.codehaus.janino.Visitor.TypeVisitor;
|
|
import org.codehaus.janino.util.ClassFile;
|
|
|
|
/**
|
|
* To set up a {@link SimpleCompiler} object, proceed as described for {@link ISimpleCompiler}.
|
|
* Alternatively, a number of "convenience constructors" exist that execute the described steps
|
|
* instantly.
|
|
*/
|
|
@SuppressWarnings({ "rawtypes", "unchecked" }) public
|
|
class SimpleCompiler extends Cookable implements ISimpleCompiler {
|
|
private static final boolean DEBUG = false;
|
|
|
|
private ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
|
|
|
|
// Set when "cook()"ing.
|
|
private ClassLoaderIClassLoader classLoaderIClassLoader;
|
|
|
|
private ClassLoader result;
|
|
private ErrorHandler optionalCompileErrorHandler;
|
|
private WarningHandler optionalWarningHandler;
|
|
|
|
private boolean debugSource = Boolean.getBoolean(ICookable.SYSTEM_PROPERTY_SOURCE_DEBUGGING_ENABLE);
|
|
private boolean debugLines = this.debugSource;
|
|
private boolean debugVars = this.debugSource;
|
|
|
|
public static void // SUPPRESS CHECKSTYLE JavadocMethod
|
|
main(String[] args) throws Exception {
|
|
if (args.length >= 1 && "-help".equals(args[0])) {
|
|
System.out.println("Usage:");
|
|
System.out.println(" org.codehaus.janino.SimpleCompiler <source-file> <class-name> { <argument> }");
|
|
System.out.println("Reads a compilation unit from the given <source-file> and invokes method");
|
|
System.out.println("\"public static void main(String[])\" of class <class-name>, passing the");
|
|
System.out.println("given <argument>s.");
|
|
System.exit(1);
|
|
}
|
|
|
|
if (args.length < 2) {
|
|
System.err.println("Source file and/or class name missing; try \"-help\".");
|
|
System.exit(1);
|
|
}
|
|
|
|
// Get source file.
|
|
String sourceFileName = args[0];
|
|
|
|
// Get class name.
|
|
String className = args[1];
|
|
|
|
// Get arguments.
|
|
String[] arguments = new String[args.length - 2];
|
|
System.arraycopy(args, 2, arguments, 0, arguments.length);
|
|
|
|
// Compile the source file.
|
|
ClassLoader cl = new SimpleCompiler(sourceFileName, new FileInputStream(sourceFileName)).getClassLoader();
|
|
|
|
// Load the class.
|
|
Class c = cl.loadClass(className);
|
|
|
|
// Invoke the "public static main(String[])" method.
|
|
Method m = c.getMethod("main", new Class[] { String[].class });
|
|
m.invoke(null, new Object[] { arguments });
|
|
}
|
|
|
|
/**
|
|
* Equivalent to<pre>
|
|
* SimpleCompiler sc = new SimpleCompiler();
|
|
* sc.cook(optionalFileName, in);</pre>
|
|
*
|
|
* @see #SimpleCompiler()
|
|
* @see Cookable#cook(String, Reader)
|
|
*/
|
|
public
|
|
SimpleCompiler(String optionalFileName, Reader in) throws IOException, CompileException {
|
|
this.cook(optionalFileName, in);
|
|
}
|
|
|
|
/**
|
|
* Equivalent to<pre>
|
|
* SimpleCompiler sc = new SimpleCompiler();
|
|
* sc.cook(optionalFileName, is);</pre>
|
|
*
|
|
* @see #SimpleCompiler()
|
|
* @see Cookable#cook(String, InputStream)
|
|
*/
|
|
public
|
|
SimpleCompiler(String optionalFileName, InputStream is) throws IOException, CompileException {
|
|
this.cook(optionalFileName, is);
|
|
}
|
|
|
|
/**
|
|
* Equivalent to<pre>
|
|
* SimpleCompiler sc = new SimpleCompiler();
|
|
* sc.cook(fileName);</pre>
|
|
*
|
|
* @see #SimpleCompiler()
|
|
* @see Cookable#cookFile(String)
|
|
*/
|
|
public
|
|
SimpleCompiler(String fileName) throws IOException, CompileException {
|
|
this.cookFile(fileName);
|
|
}
|
|
|
|
/**
|
|
* Equivalent to<pre>
|
|
* SimpleCompiler sc = new SimpleCompiler();
|
|
* sc.setParentClassLoader(optionalParentClassLoader);
|
|
* sc.cook(scanner);</pre>
|
|
*
|
|
* @see #SimpleCompiler()
|
|
* @see #setParentClassLoader(ClassLoader)
|
|
* @see Cookable#cook(Reader)
|
|
*/
|
|
public
|
|
SimpleCompiler(Scanner scanner, ClassLoader optionalParentClassLoader) throws IOException, CompileException {
|
|
this.setParentClassLoader(optionalParentClassLoader);
|
|
this.cook(scanner);
|
|
}
|
|
|
|
public SimpleCompiler() {}
|
|
|
|
@Override public void
|
|
setParentClassLoader(ClassLoader optionalParentClassLoader) {
|
|
this.assertNotCooked();
|
|
this.parentClassLoader = (
|
|
optionalParentClassLoader != null
|
|
? optionalParentClassLoader
|
|
: Thread.currentThread().getContextClassLoader()
|
|
);
|
|
}
|
|
|
|
@Override public void
|
|
setDebuggingInformation(boolean debugSource, boolean debugLines, boolean debugVars) {
|
|
this.debugSource = debugSource;
|
|
this.debugLines = debugLines;
|
|
this.debugVars = debugVars;
|
|
}
|
|
|
|
/**
|
|
* Scans, parses and compiles a given compilation unit from the given {@link Reader}. After completion, {@link
|
|
* #getClassLoader()} returns a {@link ClassLoader} that allows for access to the compiled classes.
|
|
*/
|
|
@Override public final void
|
|
cook(String optionalFileName, Reader r) throws CompileException, IOException {
|
|
this.cook(new Scanner(optionalFileName, r));
|
|
}
|
|
|
|
/**
|
|
* Scans, parses and ompiles a given compilation unit from the given scanner. After completion, {@link
|
|
* #getClassLoader()} returns a {@link ClassLoader} that allows for access to the compiled classes.
|
|
*/
|
|
public void
|
|
cook(Scanner scanner) throws CompileException, IOException {
|
|
this.compileToClassLoader(new Parser(scanner).parseCompilationUnit());
|
|
}
|
|
|
|
/**
|
|
* Cooks this compilation unit directly.
|
|
*
|
|
* @see Cookable#cook(Reader)
|
|
*/
|
|
public void
|
|
cook(Java.CompilationUnit compilationUnit) throws CompileException {
|
|
|
|
// Compile the classes and load them.
|
|
this.compileToClassLoader(compilationUnit);
|
|
}
|
|
|
|
@Override public ClassLoader
|
|
getClassLoader() {
|
|
if (this.getClass() != SimpleCompiler.class) {
|
|
throw new IllegalStateException("Must not be called on derived instances");
|
|
}
|
|
if (this.result == null) throw new IllegalStateException("Must only be called after \"cook()\"");
|
|
return this.result;
|
|
}
|
|
|
|
/**
|
|
* Two {@link SimpleCompiler}s are regarded equal iff
|
|
* <ul>
|
|
* <li>Both are objects of the same class (e.g. both are {@link ScriptEvaluator}s)
|
|
* <li>Both generated functionally equal classes as seen by {@link ByteArrayClassLoader#equals(Object)}
|
|
* </ul>
|
|
*/
|
|
@Override public boolean
|
|
equals(Object o) {
|
|
if (!(o instanceof SimpleCompiler)) return false;
|
|
SimpleCompiler that = (SimpleCompiler) o;
|
|
if (this.getClass() != that.getClass()) return false;
|
|
if (this.result == null || that.result == null) {
|
|
throw new IllegalStateException("Equality can only be checked after cooking");
|
|
}
|
|
return this.result.equals(that.result);
|
|
}
|
|
|
|
@Override public int
|
|
hashCode() { return this.parentClassLoader.hashCode(); }
|
|
|
|
@Override public void
|
|
setCompileErrorHandler(ErrorHandler optionalCompileErrorHandler) {
|
|
this.optionalCompileErrorHandler = optionalCompileErrorHandler;
|
|
}
|
|
|
|
@Override public void
|
|
setWarningHandler(WarningHandler optionalWarningHandler) {
|
|
this.optionalWarningHandler = optionalWarningHandler;
|
|
}
|
|
|
|
/** Wraps a reflection {@link Class} in a {@link Java.Type} object. */
|
|
protected Java.Type
|
|
classToType(final Location location, final Class clazz) {
|
|
if (clazz == null) return null;
|
|
|
|
// IClass iClass;
|
|
// synchronized (this.classes) {
|
|
// iClass = (IClass) this.classes.get(clazz);
|
|
// if (iClass == null) {
|
|
// if (clazz.isPrimitive()) {
|
|
// if (clazz == byte.class) { iClass = IClass.BYTE; } else
|
|
// if (clazz == short.class) { iClass = IClass.SHORT; } else
|
|
// if (clazz == int.class) { iClass = IClass.INT; } else
|
|
// if (clazz == long.class) { iClass = IClass.LONG; } else
|
|
// if (clazz == float.class) { iClass = IClass.FLOAT; } else
|
|
// if (clazz == double.class) { iClass = IClass.DOUBLE; } else
|
|
// if (clazz == char.class) { iClass = IClass.CHAR; } else
|
|
// if (clazz == boolean.class) { iClass = IClass.BOOLEAN; } else
|
|
// if (clazz == void.class) { iClass = IClass.VOID; } else
|
|
// { throw new AssertionError(clazz); }
|
|
// } else {
|
|
// iClass = new ReflectionIClass(clazz, null);
|
|
// }
|
|
// this.classes.put(clazz, iClass);
|
|
// }
|
|
// }
|
|
// return new Java.SimpleType(location, iClass);
|
|
|
|
// Can't use a SimpleType here because the classLoaderIClassLoader is not yet set up. Instead, create a
|
|
// Type that lazily creates a delegate Type at COMPILE TIME.
|
|
return new Java.Type(location) {
|
|
|
|
private Java.SimpleType delegate;
|
|
|
|
@Override public String toString() { return this.getDelegate().toString(); }
|
|
@Override public void accept(AtomVisitor visitor) { this.getDelegate().accept((TypeVisitor) visitor); }
|
|
@Override public void accept(TypeVisitor visitor) { this.getDelegate().accept(visitor); }
|
|
|
|
private Type
|
|
getDelegate() {
|
|
if (this.delegate == null) {
|
|
IClass iClass;
|
|
try {
|
|
iClass = SimpleCompiler.this.classLoaderIClassLoader.loadIClass(
|
|
Descriptor.fromClassName(clazz.getName())
|
|
);
|
|
} catch (ClassNotFoundException ex) {
|
|
throw new JaninoRuntimeException("Loading IClass \"" + clazz.getName() + "\": " + ex);
|
|
}
|
|
if (iClass == null) {
|
|
throw new JaninoRuntimeException(
|
|
"Cannot load class '"
|
|
+ clazz.getName()
|
|
+ "' through the parent loader"
|
|
);
|
|
}
|
|
|
|
// Verify that the class loaders match.
|
|
IClass iClass2 = iClass;
|
|
Class class2 = clazz;
|
|
for (;;) {
|
|
IClass ct = iClass2.getComponentType();
|
|
if (ct == null) {
|
|
if (class2.getComponentType() != null) {
|
|
throw new JaninoRuntimeException("Array type/class inconsistency");
|
|
}
|
|
break;
|
|
}
|
|
iClass2 = ct;
|
|
class2 = class2.getComponentType();
|
|
if (class2 == null) throw new JaninoRuntimeException("Array type/class inconsistency");
|
|
}
|
|
if (class2.isPrimitive()) {
|
|
if (!iClass2.isPrimitive()) {
|
|
throw new JaninoRuntimeException("Primitive type/class inconsistency");
|
|
}
|
|
} else {
|
|
if (iClass2.isPrimitive()) {
|
|
throw new JaninoRuntimeException("Primitive type/class inconsistency");
|
|
}
|
|
if (((ReflectionIClass) iClass2).getClazz() != class2) {
|
|
throw new JaninoRuntimeException(
|
|
"Class '"
|
|
+ class2.getName()
|
|
+ "' was loaded through a different loader"
|
|
);
|
|
}
|
|
}
|
|
this.delegate = new Java.SimpleType(location, iClass);
|
|
}
|
|
|
|
return this.delegate;
|
|
}
|
|
};
|
|
}
|
|
// private final Map<Class, IClass> classes = new HashMap();
|
|
|
|
/** Converts an array of {@link Class}es into an array of{@link Java.Type}s. */
|
|
protected Java.Type[]
|
|
classesToTypes(Location location, Class[] classes) {
|
|
Java.Type[] types = new Java.Type[classes.length];
|
|
for (int i = 0; i < classes.length; ++i) {
|
|
types[i] = this.classToType(location, classes[i]);
|
|
}
|
|
return types;
|
|
}
|
|
|
|
/**
|
|
* Compile the given compilation unit. (A "compilation unit" is typically the contents
|
|
* of a Java™ source file.)
|
|
*
|
|
* @param compilationUnit The parsed compilation unit
|
|
* @return The {@link ClassLoader} into which the compiled classes were defined
|
|
* @throws CompileException
|
|
*/
|
|
protected final ClassLoader
|
|
compileToClassLoader(Java.CompilationUnit compilationUnit) throws CompileException {
|
|
if (SimpleCompiler.DEBUG) {
|
|
UnparseVisitor.unparse(compilationUnit, new OutputStreamWriter(System.out));
|
|
}
|
|
|
|
this.classLoaderIClassLoader = new ClassLoaderIClassLoader(this.parentClassLoader);
|
|
|
|
// Compile compilation unit to class files.
|
|
UnitCompiler unitCompiler = new UnitCompiler(compilationUnit, this.classLoaderIClassLoader);
|
|
unitCompiler.setCompileErrorHandler(this.optionalCompileErrorHandler);
|
|
unitCompiler.setWarningHandler(this.optionalWarningHandler);
|
|
ClassFile[] classFiles = unitCompiler.compileUnit(this.debugSource, this.debugLines, this.debugVars);
|
|
|
|
// Convert the class files to bytes and store them in a Map.
|
|
final Map<String /*className*/, byte[] /*bytecode*/> classes = new HashMap();
|
|
for (ClassFile cf : classFiles) {
|
|
byte[] contents = cf.toByteArray();
|
|
if (SimpleCompiler.DEBUG) {
|
|
try {
|
|
Class disassemblerClass = Class.forName("de.unkrig.jdisasm.Disassembler");
|
|
disassemblerClass.getMethod(
|
|
"disasm",
|
|
new Class[] { InputStream.class }
|
|
).invoke(
|
|
disassemblerClass.newInstance(),
|
|
new Object[] { new ByteArrayInputStream(contents) }
|
|
);
|
|
} catch (Exception e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
classes.put(cf.getThisClassName(), contents);
|
|
}
|
|
|
|
// Create a ClassLoader that loads the generated classes.
|
|
this.result = (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
|
|
|
|
@Override public Object
|
|
run() {
|
|
return new ByteArrayClassLoader(
|
|
classes, // classes
|
|
SimpleCompiler.this.parentClassLoader // parent
|
|
);
|
|
}
|
|
});
|
|
return this.result;
|
|
}
|
|
|
|
/** @throws IllegalStateException This {@link Cookable} is already cooked */
|
|
protected void
|
|
assertNotCooked() {
|
|
if (this.classLoaderIClassLoader != null) throw new IllegalStateException("Already cooked");
|
|
}
|
|
}
|