From 429b66703122958bebddbeaf04462b33ec3c3073 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Tue, 6 Mar 2018 17:17:29 +0100 Subject: [PATCH] [java decompiler] restores thread-conscious context access (EA-116270) --- .../decompiler/main/DecompilerContext.java | 82 ++++++++----------- .../java/decompiler/main/Fernflower.java | 74 +++++++++++------ .../main/decompiler/BaseDecompiler.java | 14 ++-- .../main/decompiler/ConsoleDecompiler.java | 36 ++++---- .../decompiler/main/rels/ClassWrapper.java | 4 +- .../main/rels/MethodProcessorRunnable.java | 13 ++- .../decompiler/BulkDecompilationTest.java | 6 +- .../java/decompiler/SingleClassesTest.java | 8 +- 8 files changed, 128 insertions(+), 109 deletions(-) diff --git a/src/org/jetbrains/java/decompiler/main/DecompilerContext.java b/src/org/jetbrains/java/decompiler/main/DecompilerContext.java index f75d751..e293635 100644 --- a/src/org/jetbrains/java/decompiler/main/DecompilerContext.java +++ b/src/org/jetbrains/java/decompiler/main/DecompilerContext.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.main; import org.jetbrains.java.decompiler.main.collectors.BytecodeSourceMapper; @@ -10,8 +10,6 @@ import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; import org.jetbrains.java.decompiler.modules.renamer.PoolInterceptor; import org.jetbrains.java.decompiler.struct.StructContext; -import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.Objects; @@ -21,8 +19,6 @@ public class DecompilerContext { public static final String CURRENT_CLASS_NODE = "CURRENT_CLASS_NODE"; public static final String CURRENT_METHOD_WRAPPER = "CURRENT_METHOD_WRAPPER"; - private static volatile DecompilerContext currentContext = null; - private final Map properties; private final IFernflowerLogger logger; private final StructContext structContext; @@ -33,11 +29,16 @@ public class DecompilerContext { private CounterContainer counterContainer; private BytecodeSourceMapper bytecodeSourceMapper; - private DecompilerContext(Map properties, - IFernflowerLogger logger, - StructContext structContext, - ClassesProcessor classProcessor, - PoolInterceptor interceptor) { + public DecompilerContext(Map properties, + IFernflowerLogger logger, + StructContext structContext, + ClassesProcessor classProcessor, + PoolInterceptor interceptor) { + Objects.requireNonNull(properties); + Objects.requireNonNull(logger); + Objects.requireNonNull(structContext); + Objects.requireNonNull(classProcessor); + this.properties = properties; this.logger = logger; this.structContext = structContext; @@ -50,48 +51,31 @@ public class DecompilerContext { // context setup and update // ***************************************************************************** - public static void initContext(Map customProperties, - IFernflowerLogger logger, - StructContext structContext, - ClassesProcessor classProcessor, - PoolInterceptor interceptor) { - Objects.requireNonNull(logger); - Objects.requireNonNull(structContext); - Objects.requireNonNull(classProcessor); - - Map properties = new HashMap<>(IFernflowerPreferences.DEFAULTS); - if (customProperties != null) { - properties.putAll(customProperties); - } - - String level = (String)properties.get(IFernflowerPreferences.LOG_LEVEL); - if (level != null) { - try { - logger.setSeverity(IFernflowerLogger.Severity.valueOf(level.toUpperCase(Locale.US))); - } - catch (IllegalArgumentException ignore) { } - } + private static final ThreadLocal currentContext = new ThreadLocal<>(); - currentContext = new DecompilerContext(properties, logger, structContext, classProcessor, interceptor); + public static DecompilerContext getCurrentContext() { + return currentContext.get(); } - public static void clearContext() { - currentContext = null; + public static void setCurrentContext(DecompilerContext context) { + currentContext.set(context); } public static void setProperty(String key, Object value) { - currentContext.properties.put(key, value); + getCurrentContext().properties.put(key, value); } public static void startClass(ImportCollector importCollector) { - currentContext.importCollector = importCollector; - currentContext.counterContainer = new CounterContainer(); - currentContext.bytecodeSourceMapper = new BytecodeSourceMapper(); + DecompilerContext context = getCurrentContext(); + context.importCollector = importCollector; + context.counterContainer = new CounterContainer(); + context.bytecodeSourceMapper = new BytecodeSourceMapper(); } public static void startMethod(VarProcessor varProcessor) { - currentContext.varProcessor = varProcessor; - currentContext.counterContainer = new CounterContainer(); + DecompilerContext context = getCurrentContext(); + context.varProcessor = varProcessor; + context.counterContainer = new CounterContainer(); } // ***************************************************************************** @@ -99,7 +83,7 @@ public class DecompilerContext { // ***************************************************************************** public static Object getProperty(String key) { - return currentContext.properties.get(key); + return getCurrentContext().properties.get(key); } public static boolean getOption(String key) { @@ -112,34 +96,34 @@ public class DecompilerContext { } public static IFernflowerLogger getLogger() { - return currentContext.logger; + return getCurrentContext().logger; } public static StructContext getStructContext() { - return currentContext.structContext; + return getCurrentContext().structContext; } public static ClassesProcessor getClassProcessor() { - return currentContext.classProcessor; + return getCurrentContext().classProcessor; } public static PoolInterceptor getPoolInterceptor() { - return currentContext.poolInterceptor; + return getCurrentContext().poolInterceptor; } public static ImportCollector getImportCollector() { - return currentContext.importCollector; + return getCurrentContext().importCollector; } public static VarProcessor getVarProcessor() { - return currentContext.varProcessor; + return getCurrentContext().varProcessor; } public static CounterContainer getCounterContainer() { - return currentContext.counterContainer; + return getCurrentContext().counterContainer; } public static BytecodeSourceMapper getBytecodeSourceMapper() { - return currentContext.bytecodeSourceMapper; + return getCurrentContext().bytecodeSourceMapper; } } \ No newline at end of file diff --git a/src/org/jetbrains/java/decompiler/main/Fernflower.java b/src/org/jetbrains/java/decompiler/main/Fernflower.java index 1ad0080..b7ca4a8 100644 --- a/src/org/jetbrains/java/decompiler/main/Fernflower.java +++ b/src/org/jetbrains/java/decompiler/main/Fernflower.java @@ -1,6 +1,4 @@ -/* - * Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.main; import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; @@ -14,57 +12,83 @@ import org.jetbrains.java.decompiler.struct.StructContext; import org.jetbrains.java.decompiler.struct.lazy.LazyLoader; import org.jetbrains.java.decompiler.util.TextBuffer; +import java.io.File; +import java.util.HashMap; +import java.util.Locale; import java.util.Map; public class Fernflower implements IDecompiledData { private final StructContext structContext; private final ClassesProcessor classProcessor; - private IIdentifierRenamer helper; - private IdentifierConverter converter; + private final IIdentifierRenamer helper; + private final IdentifierConverter converter; + + public Fernflower(IBytecodeProvider provider, IResultSaver saver, Map customProperties, IFernflowerLogger logger) { + Map properties = new HashMap<>(IFernflowerPreferences.DEFAULTS); + if (customProperties != null) { + properties.putAll(customProperties); + } + + String level = (String)properties.get(IFernflowerPreferences.LOG_LEVEL); + if (level != null) { + try { + logger.setSeverity(IFernflowerLogger.Severity.valueOf(level.toUpperCase(Locale.US))); + } + catch (IllegalArgumentException ignore) { } + } - public Fernflower(IBytecodeProvider provider, IResultSaver saver, Map options, IFernflowerLogger logger) { structContext = new StructContext(saver, this, new LazyLoader(provider)); classProcessor = new ClassesProcessor(structContext); PoolInterceptor interceptor = null; - Object rename = options.get(IFernflowerPreferences.RENAME_ENTITIES); - if ("1".equals(rename) || rename == null && "1".equals(IFernflowerPreferences.DEFAULTS.get(IFernflowerPreferences.RENAME_ENTITIES))) { - helper = loadHelper((String)options.get(IFernflowerPreferences.USER_RENAMER_CLASS)); + if ("1".equals(properties.get(IFernflowerPreferences.RENAME_ENTITIES))) { + helper = loadHelper((String)properties.get(IFernflowerPreferences.USER_RENAMER_CLASS), logger); interceptor = new PoolInterceptor(); converter = new IdentifierConverter(structContext, helper, interceptor); } - - DecompilerContext.initContext(options, logger, structContext, classProcessor, interceptor); - } - - public void decompileContext() { - if (converter != null) { - converter.rename(); + else { + helper = null; + converter = null; } - classProcessor.loadClasses(helper); - - structContext.saveContext(); + DecompilerContext context = new DecompilerContext(properties, logger, structContext, classProcessor, interceptor); + DecompilerContext.setCurrentContext(context); } - private static IIdentifierRenamer loadHelper(String className) { + private static IIdentifierRenamer loadHelper(String className, IFernflowerLogger logger) { if (className != null) { try { Class renamerClass = Fernflower.class.getClassLoader().loadClass(className); return (IIdentifierRenamer) renamerClass.getDeclaredConstructor().newInstance(); } - catch (Exception ignored) { } + catch (Exception e) { + logger.writeMessage("Cannot load renamer '" + className + "'", IFernflowerLogger.Severity.WARN, e); + } } return new ConverterHelper(); } - public void clearContext() { - DecompilerContext.clearContext(); + public void addSource(File source) { + structContext.addSpace(source, true); + } + + public void addLibrary(File library) { + structContext.addSpace(library, false); } - public StructContext getStructContext() { - return structContext; + public void decompileContext() { + if (converter != null) { + converter.rename(); + } + + classProcessor.loadClasses(helper); + + structContext.saveContext(); + } + + public void clearContext() { + DecompilerContext.setCurrentContext(null); } @Override diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java b/src/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java index fb33f49..6d9152f 100644 --- a/src/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java +++ b/src/org/jetbrains/java/decompiler/main/decompiler/BaseDecompiler.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.main.decompiler; import org.jetbrains.java.decompiler.main.Fernflower; @@ -11,22 +11,22 @@ import java.util.Map; @SuppressWarnings("unused") public class BaseDecompiler { - private final Fernflower fernflower; + private final Fernflower engine; public BaseDecompiler(IBytecodeProvider provider, IResultSaver saver, Map options, IFernflowerLogger logger) { - fernflower = new Fernflower(provider, saver, options, logger); + engine = new Fernflower(provider, saver, options, logger); } - public void addSpace(File file, boolean isOwn) { - fernflower.getStructContext().addSpace(file, isOwn); + public void addSource(File source) { + engine.addSource(source); } public void decompileContext() { try { - fernflower.decompileContext(); + engine.decompileContext(); } finally { - fernflower.clearContext(); + engine.clearContext(); } } } \ No newline at end of file diff --git a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java index 7eaf57b..3b3819d 100644 --- a/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java +++ b/src/org/jetbrains/java/decompiler/main/decompiler/ConsoleDecompiler.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.main.decompiler; import org.jetbrains.java.decompiler.main.DecompilerContext; @@ -27,8 +27,8 @@ public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver { } Map mapOptions = new HashMap<>(); - List lstSources = new ArrayList<>(); - List lstLibraries = new ArrayList<>(); + List sources = new ArrayList<>(); + List libraries = new ArrayList<>(); boolean isOption = true; for (int i = 0; i < args.length - 1; ++i) { // last parameter - destination @@ -49,15 +49,15 @@ public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver { isOption = false; if (arg.startsWith("-e=")) { - addPath(lstLibraries, arg.substring(3)); + addPath(libraries, arg.substring(3)); } else { - addPath(lstSources, arg); + addPath(sources, arg); } } } - if (lstSources.isEmpty()) { + if (sources.isEmpty()) { System.out.println("error: no sources given"); return; } @@ -71,11 +71,11 @@ public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver { PrintStreamLogger logger = new PrintStreamLogger(System.out); ConsoleDecompiler decompiler = new ConsoleDecompiler(destination, mapOptions, logger); - for (File source : lstSources) { - decompiler.addSpace(source, true); + for (File source : sources) { + decompiler.addSource(source); } - for (File library : lstLibraries) { - decompiler.addSpace(library, false); + for (File library : libraries) { + decompiler.addLibrary(library); } decompiler.decompileContext(); @@ -97,25 +97,29 @@ public class ConsoleDecompiler implements IBytecodeProvider, IResultSaver { // ******************************************************************* private final File root; - private final Fernflower fernflower; + private final Fernflower engine; private final Map mapArchiveStreams = new HashMap<>(); private final Map> mapArchiveEntries = new HashMap<>(); protected ConsoleDecompiler(File destination, Map options, IFernflowerLogger logger) { root = destination; - fernflower = new Fernflower(this, this, options, logger); + engine = new Fernflower(this, this, options, logger); } - public void addSpace(File file, boolean isOwn) { - fernflower.getStructContext().addSpace(file, isOwn); + public void addSource(File source) { + engine.addSource(source); + } + + public void addLibrary(File library) { + engine.addLibrary(library); } public void decompileContext() { try { - fernflower.decompileContext(); + engine.decompileContext(); } finally { - fernflower.clearContext(); + engine.clearContext(); } } diff --git a/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java b/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java index 5b84ab3..9f41a37 100644 --- a/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java +++ b/src/org/jetbrains/java/decompiler/main/rels/ClassWrapper.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.main.rels; import org.jetbrains.java.decompiler.code.CodeConstants; @@ -62,7 +62,7 @@ public class ClassWrapper { root = MethodProcessorRunnable.codeToJava(mt, md, varProc); } else { - MethodProcessorRunnable mtProc = new MethodProcessorRunnable(mt, md, varProc); + MethodProcessorRunnable mtProc = new MethodProcessorRunnable(mt, md, varProc, DecompilerContext.getCurrentContext()); Thread mtThread = new Thread(mtProc, "Java decompiler"); long stopAt = System.currentTimeMillis() + maxSec * 1000; diff --git a/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorRunnable.java b/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorRunnable.java index 9f047d4..935589f 100644 --- a/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorRunnable.java +++ b/src/org/jetbrains/java/decompiler/main/rels/MethodProcessorRunnable.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler.main.rels; import org.jetbrains.java.decompiler.code.CodeConstants; @@ -25,15 +25,20 @@ public class MethodProcessorRunnable implements Runnable { private final StructMethod method; private final MethodDescriptor methodDescriptor; private final VarProcessor varProc; + private final DecompilerContext parentContext; private volatile RootStatement root; private volatile Throwable error; private volatile boolean finished = false; - public MethodProcessorRunnable(StructMethod method, MethodDescriptor methodDescriptor, VarProcessor varProc) { + public MethodProcessorRunnable(StructMethod method, + MethodDescriptor methodDescriptor, + VarProcessor varProc, + DecompilerContext parentContext) { this.method = method; this.methodDescriptor = methodDescriptor; this.varProc = varProc; + this.parentContext = parentContext; } @Override @@ -42,6 +47,7 @@ public class MethodProcessorRunnable implements Runnable { root = null; try { + DecompilerContext.setCurrentContext(parentContext); root = codeToJava(method, methodDescriptor, varProc); } catch (ThreadDeath ex) { @@ -50,6 +56,9 @@ public class MethodProcessorRunnable implements Runnable { catch (Throwable ex) { error = ex; } + finally { + DecompilerContext.setCurrentContext(null); + } finished = true; synchronized (lock) { diff --git a/test/org/jetbrains/java/decompiler/BulkDecompilationTest.java b/test/org/jetbrains/java/decompiler/BulkDecompilationTest.java index bd39a8a..810c9ec 100644 --- a/test/org/jetbrains/java/decompiler/BulkDecompilationTest.java +++ b/test/org/jetbrains/java/decompiler/BulkDecompilationTest.java @@ -1,4 +1,4 @@ -// Copyright 2000-2017 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler; import org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler; @@ -36,7 +36,7 @@ public class BulkDecompilationTest { unpack(new File(fixture.getTestDataDir(), "bulk.jar"), classes); ConsoleDecompiler decompiler = fixture.getDecompiler(); - decompiler.addSpace(classes, true); + decompiler.addSource(classes); decompiler.decompileContext(); assertFilesEqual(new File(fixture.getTestDataDir(), "bulk"), fixture.getTargetDir()); @@ -55,7 +55,7 @@ public class BulkDecompilationTest { private void doTestJar(String name) { ConsoleDecompiler decompiler = fixture.getDecompiler(); String jarName = name + ".jar"; - decompiler.addSpace(new File(fixture.getTestDataDir(), jarName), true); + decompiler.addSource(new File(fixture.getTestDataDir(), jarName)); decompiler.decompileContext(); File unpacked = new File(fixture.getTempDir(), "unpacked"); diff --git a/test/org/jetbrains/java/decompiler/SingleClassesTest.java b/test/org/jetbrains/java/decompiler/SingleClassesTest.java index 1808d14..22d6cb4 100644 --- a/test/org/jetbrains/java/decompiler/SingleClassesTest.java +++ b/test/org/jetbrains/java/decompiler/SingleClassesTest.java @@ -1,6 +1,4 @@ -/* - * Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. - */ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.java.decompiler; import org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler; @@ -125,14 +123,14 @@ public class SingleClassesTest { File classFile = new File(fixture.getTestDataDir(), "/classes/" + testFile + ".class"); assertTrue(classFile.isFile()); for (File file : collectClasses(classFile)) { - decompiler.addSpace(file, true); + decompiler.addSource(file); } for (String companionFile : companionFiles) { File companionClassFile = new File(fixture.getTestDataDir(), "/classes/" + companionFile + ".class"); assertTrue(companionClassFile.isFile()); for (File file : collectClasses(companionClassFile)) { - decompiler.addSpace(file, true); + decompiler.addSource(file); } }