Skip to content

Commit

Permalink
Allow runtime-decompilation of exported classes using fernflower
Browse files Browse the repository at this point in the history
  • Loading branch information
Mumfrey committed May 22, 2015
1 parent 7717461 commit 7f3f7bf
Show file tree
Hide file tree
Showing 5 changed files with 242 additions and 5 deletions.
19 changes: 19 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ repositories {
name = 'renamed-packages'
dirs = files("build/renamed-packages/")
}
maven {
// For fernflower
name = 'sponge'
url = 'http://repo.spongepowered.org/maven'
}
}

configurations {
Expand All @@ -87,6 +92,9 @@ sourceSets {
ap {
compileClasspath += main.output
}
fernflower {
compileClasspath += main.output
}
}

// Dependencies for renamed ASM
Expand Down Expand Up @@ -145,6 +153,12 @@ dependencies {
// remapped ASM
compile tasks.renamedASM.outputs.files
apCompile tasks.renamedASM.outputs.files

// fernflower bridge
fernflowerCompile 'commons-io:commons-io:2.4'
fernflowerCompile 'org.apache.logging.log4j:log4j-core:2.0-beta9'
fernflowerCompile 'com.google.guava:guava:17.0'
fernflowerCompile 'org.jetbrains.java.decompiler:fernflower:sponge-SNAPSHOT'
}

// Filter, process, and include resources
Expand Down Expand Up @@ -199,6 +213,9 @@ jar {
// Include annotation processor
from sourceSets.ap.output

// Include fernflower bridge
from sourceSets.fernflower.output

// Renamed ASM
from zipTree(tasks.renamedASM.outputs.files[0])

Expand Down Expand Up @@ -243,6 +260,8 @@ task sourceJar(type: Jar) {
from sourceSets.main.resources
from sourceSets.ap.java
from sourceSets.ap.resources
from sourceSets.fernflower.java
from sourceSets.fernflower.resources
classifier = "sources"
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered.org <http://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.asm.mixin.transformer.debug;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.jar.Manifest;

import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.java.decompiler.main.Fernflower;
import org.jetbrains.java.decompiler.main.extern.IBytecodeProvider;
import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger;
import org.jetbrains.java.decompiler.main.extern.IResultSaver;
import org.jetbrains.java.decompiler.util.InterpreterUtil;

import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;

/**
* Wrapper for FernFlower to support runtime-decompilation of post-mixin classes
*/
public class RuntimeDecompiler extends IFernflowerLogger implements IDecompiler, IResultSaver {

private final Map<String, Object> options = ImmutableMap.<String, Object>builder()
.put("din", "0").put("rbr", "0").put("dgs", "1").put("asc", "1")
.put("den", "1").put("hdc", "1").put("ind", " ").build();

private final File outputPath;

private final Logger logger = LogManager.getLogger("fernflower");

public RuntimeDecompiler(File outputPath) {
this.outputPath = outputPath;
if (this.outputPath.exists()) {
try {
FileUtils.deleteDirectory(this.outputPath);
} catch (IOException ex) {
this.logger.warn("Error cleaning output directory", ex);
}
}
}

@Override
public void decompile(final File file) {
try {
Fernflower fernflower = new Fernflower(new IBytecodeProvider() {

private byte[] byteCode;

@Override
public byte[] getBytecode(String externalPath, String internalPath) throws IOException {
if (this.byteCode == null) {
this.byteCode = InterpreterUtil.getBytes(new File(externalPath));
}
return this.byteCode;
}

}, this, this.options, this);

fernflower.getStructContext().addSpace(file, true);
fernflower.decompileContext();
} catch (Throwable ex) {
this.logger.warn("Decompilation error while processing {}", file.getName());
}
}

@Override
public void saveFolder(String path) {
}

@Override
public void saveClassFile(String path, String qualifiedName, String entryName, String content, int[] mapping) {
File file = new File(this.outputPath, qualifiedName + ".java");
file.getParentFile().mkdirs();
try {
this.logger.info("Writing {}", file.getAbsolutePath());
Files.write(content, file, Charsets.UTF_8);
} catch (IOException ex) {
this.writeMessage("Cannot write source file " + file, ex);
}
}

@Override
public void startReadingClass(String className) {
this.logger.info("Decompiling {}", className);
}

@Override
public void writeMessage(String message, Severity severity) {
this.logger.info(message);
}

@Override
public void writeMessage(String message, Throwable t) {
this.logger.info(message, t);
}

@Override
public void copyFile(String source, String path, String entryName) {
}

@Override
public void createArchive(String path, String archiveName, Manifest manifest) {
}

@Override
public void saveDirEntry(String path, String archiveName, String entryName) {
}

@Override
public void copyEntry(String source, String path, String archiveName, String entry) {
}

@Override
public void saveClassEntry(String path, String archiveName, String qualifiedName, String entryName, String content) {
}

@Override
public void closeArchive(String path, String archiveName) {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
Expand Down Expand Up @@ -53,6 +54,7 @@
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinErrorHandler;
import org.spongepowered.asm.mixin.extensibility.IMixinErrorHandler.ErrorAction;
import org.spongepowered.asm.mixin.transformer.debug.IDecompiler;
import org.spongepowered.asm.transformers.TreeTransformer;
import org.spongepowered.asm.util.Constants;

Expand Down Expand Up @@ -215,6 +217,8 @@ ReEntranceState clear() {
}
}

static final File DEBUG_OUTPUT = new File(Constants.DEBUG_OUTPUT_PATH);

/**
* Log all the things
*/
Expand Down Expand Up @@ -262,6 +266,8 @@ ReEntranceState clear() {
*/
private boolean errorState = false;

private final IDecompiler decompiler;

/**
* ctor
*/
Expand All @@ -277,8 +283,28 @@ ReEntranceState clear() {
environment.setActiveTransformer(this);

TreeInfo.setLock(this.lock);

this.decompiler = this.initDecompiler(new File(MixinTransformer.DEBUG_OUTPUT, "java"));
}

private IDecompiler initDecompiler(File outputPath) {
if (!MixinEnvironment.getCurrentEnvironment().getOption(Option.DEBUG_EXPORT)) {
return null;
}
try {
this.logger.info("Attempting to load Fernflower decompiler");
@SuppressWarnings("unchecked")
Class<? extends IDecompiler> clazz =
(Class<? extends IDecompiler>)Class.forName("org.spongepowered.asm.mixin.transformer.debug.RuntimeDecompiler");
Constructor<? extends IDecompiler> ctor = clazz.getDeclaredConstructor(File.class);
return ctor.newInstance(outputPath);
} catch (Throwable th) {
this.logger.info("Fernflower could not be loaded, exported classes will not be decompiled. {}: {}",
th.getClass().getSimpleName(), th.getMessage());
}
return null;
}

/**
* Force-load all classes targetted by mixins but not yet applied
*/
Expand Down Expand Up @@ -619,17 +645,22 @@ private byte[] writeClass(String transformedName, ClassNode targetClass) {

// Export transformed class for debugging purposes
if (MixinEnvironment.getCurrentEnvironment().getOption(Option.DEBUG_EXPORT)) {
MixinTransformer.dumpClass(transformedName.replace('.', '/'), bytes);
File outputFile = MixinTransformer.dumpClass(transformedName.replace('.', '/'), bytes);
if (this.decompiler != null) {
this.decompiler.decompile(outputFile);
}
}

return bytes;
}

private static void dumpClass(String fileName, byte[] bytes) {
private static File dumpClass(String fileName, byte[] bytes) {
File outputFile = new File(MixinTransformer.DEBUG_OUTPUT, "class/" + fileName + ".class");
try {
FileUtils.writeByteArrayToFile(new File(Constants.DEBUG_OUTPUT_PATH + "/" + fileName + ".class"), bytes);
FileUtils.writeByteArrayToFile(outputFile, bytes);
} catch (IOException ex) {
// don't care
}
return outputFile;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
import org.spongepowered.asm.lib.tree.ClassNode;
import org.spongepowered.asm.mixin.transformer.ClassInfo.Method;
import org.spongepowered.asm.mixin.transformer.ClassInfo.Traversal;
import org.spongepowered.asm.util.Constants;
import org.spongepowered.asm.util.PrettyPrinter;
import org.spongepowered.asm.util.SignaturePrinter;

Expand All @@ -61,7 +60,7 @@ public class MixinTransformerModuleInterfaceChecker implements IMixinTransformer
private final File report;

public MixinTransformerModuleInterfaceChecker() {
File debugOutputFolder = new File(Constants.DEBUG_OUTPUT_PATH);
File debugOutputFolder = new File(MixinTransformer.DEBUG_OUTPUT, "audit");
debugOutputFolder.mkdirs();
this.csv = new File(debugOutputFolder, "mixin_implementation_report.csv");
this.report = new File(debugOutputFolder, "mixin_implementation_report.txt");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* This file is part of Sponge, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered.org <http://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.spongepowered.asm.mixin.transformer.debug;

import java.io.File;

/**
* Interface to allow the actual decompiler to be loaded on-demand
*/
public interface IDecompiler {

/**
* Decompile a class file
*
* @param file .class file to decompile
*/
public abstract void decompile(File file);

}

0 comments on commit 7f3f7bf

Please sign in to comment.