// Copyright 2000-2021 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; import org.jetbrains.java.decompiler.main.DecompilerContext; import org.jetbrains.java.decompiler.main.collectors.CounterContainer; import org.jetbrains.java.decompiler.main.collectors.VarNamesCollector; import org.jetbrains.java.decompiler.main.extern.IFernflowerLogger; import org.jetbrains.java.decompiler.main.extern.IFernflowerPreferences; import org.jetbrains.java.decompiler.modules.decompiler.exps.Exprent; import org.jetbrains.java.decompiler.modules.decompiler.exps.VarExprent; import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarProcessor; import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPair; import org.jetbrains.java.decompiler.struct.StructClass; import org.jetbrains.java.decompiler.struct.StructMethod; import org.jetbrains.java.decompiler.struct.attr.StructGeneralAttribute; import org.jetbrains.java.decompiler.struct.attr.StructLocalVariableTableAttribute; import org.jetbrains.java.decompiler.struct.attr.StructMethodParametersAttribute; import org.jetbrains.java.decompiler.struct.attr.StructOriginalPcTableAttribute; import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; import org.jetbrains.java.decompiler.util.InterpreterUtil; import org.jetbrains.java.decompiler.util.VBStyleCollection; import java.util.*; public class ClassWrapper { private final StructClass classStruct; private final Set hiddenMembers = new HashSet<>(); private final VBStyleCollection staticFieldInitializers = new VBStyleCollection<>(); private final VBStyleCollection dynamicFieldInitializers = new VBStyleCollection<>(); private final VBStyleCollection methods = new VBStyleCollection<>(); public ClassWrapper(StructClass classStruct) { this.classStruct = classStruct; } public void init() { DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS, classStruct); DecompilerContext.setProperty(DecompilerContext.CURRENT_CLASS_WRAPPER, this); DecompilerContext.getLogger().startClass(classStruct.qualifiedName); int maxSec = Integer.parseInt(DecompilerContext.getProperty(IFernflowerPreferences.MAX_PROCESSING_METHOD).toString()); boolean testMode = DecompilerContext.getOption(IFernflowerPreferences.UNIT_TEST_MODE); for (StructMethod mt : classStruct.getMethods()) { DecompilerContext.getLogger().startMethod(mt.getName() + " " + mt.getDescriptor()); MethodDescriptor md = MethodDescriptor.parseDescriptor(mt.getDescriptor()); VarProcessor varProc = new VarProcessor(mt, md); DecompilerContext.startMethod(varProc); VarNamesCollector vc = varProc.getVarNamesCollector(); CounterContainer counter = DecompilerContext.getCounterContainer(); RootStatement root = null; boolean isError = false; try { if (mt.containsCode()) { if (maxSec == 0 || testMode) { root = MethodProcessorRunnable.codeToJava(classStruct, mt, md, varProc); } else { MethodProcessorRunnable mtProc = new MethodProcessorRunnable(classStruct, mt, md, varProc, DecompilerContext.getCurrentContext()); Thread mtThread = new Thread(mtProc, "Java decompiler"); long stopAt = System.currentTimeMillis() + maxSec * 1000L; mtThread.start(); while (!mtProc.isFinished()) { try { synchronized (mtProc.lock) { mtProc.lock.wait(200); } } catch (InterruptedException e) { killThread(mtThread); throw e; } if (System.currentTimeMillis() >= stopAt) { String message = "Processing time limit exceeded for method " + mt.getName() + ", execution interrupted."; DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.ERROR); killThread(mtThread); isError = true; break; } } if (!isError) { root = mtProc.getResult(); } } } else { boolean thisVar = !mt.hasModifier(CodeConstants.ACC_STATIC); int paramCount = 0; if (thisVar) { varProc.getThisVars().put(new VarVersionPair(0, 0), classStruct.qualifiedName); paramCount = 1; } paramCount += md.params.length; int varIndex = 0; for (int i = 0; i < paramCount; i++) { varProc.setVarName(new VarVersionPair(varIndex, 0), vc.getFreeName(varIndex)); if (thisVar) { if (i == 0) { varIndex++; } else { varIndex += md.params[i - 1].stackSize; } } else { varIndex += md.params[i].stackSize; } } } } catch (Throwable t) { String message = "Method " + mt.getName() + " " + mt.getDescriptor() + " couldn't be decompiled."; DecompilerContext.getLogger().writeMessage(message, IFernflowerLogger.Severity.WARN, t); isError = true; } MethodWrapper methodWrapper = new MethodWrapper(root, varProc, mt, counter); methodWrapper.decompiledWithErrors = isError; methods.addWithKey(methodWrapper, InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor())); if (!isError) { // rename vars so that no one has the same name as a field VarNamesCollector namesCollector = new VarNamesCollector(); classStruct.getFields().forEach(f -> namesCollector.addName(f.getName())); int index = mt.hasModifier(CodeConstants.ACC_STATIC) ? 0 : 1; for (int i = 0; i < md.params.length; i++) { varProc.setVarName(new VarVersionPair(index, 0), "arg" + i); index += md.params[i].stackSize; } Map paramNames = new HashMap<>(); if (DecompilerContext.getOption(IFernflowerPreferences.USE_METHOD_PARAMETERS)) { StructMethodParametersAttribute attr = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_METHOD_PARAMETERS); if (attr != null) { List entries = attr.getEntries(); index = mt.hasModifier(CodeConstants.ACC_STATIC) ? 0 : 1; for (int i = 0; i < md.params.length && i < entries.size(); i++) { String myName = entries.get(i).myName; if (myName != null) { paramNames.put(index, myName); } index += md.params[i].stackSize; } } } // if debug information present and should be used if (DecompilerContext.getOption(IFernflowerPreferences.USE_DEBUG_VAR_NAMES)) { StructLocalVariableTableAttribute attr = mt.getLocalVariableAttr(); if (attr != null) { // only param names here paramNames.putAll(attr.getMapParamNames()); } } varProc.setDebugVarNames(paramNames); if (DecompilerContext.getOption(IFernflowerPreferences.USE_DEBUG_VAR_NAMES)) { StructLocalVariableTableAttribute attr = mt.getLocalVariableAttr(); if (attr != null) { // the rest is here methodWrapper.getOrBuildGraph().iterateExprents(exprent -> { List lst = exprent.getAllExprents(true); lst.add(exprent); lst.stream() .filter(e -> e.type == Exprent.EXPRENT_VAR) .forEach(e -> { VarExprent varExprent = (VarExprent)e; String name = varExprent.getDebugName(mt); if (name != null) { varProc.setVarName(varExprent.getVarVersionPair(), name); } }); return 0; }, true); } } StructOriginalPcTableAttribute originalPcTable = mt.getAttribute(StructGeneralAttribute.ATTRIBUTE_ORIGINAL_PC_TABLE); if (originalPcTable != null) { methodWrapper.getOrBuildGraph().iterateExprents(rootExpr -> { List exprs = rootExpr.getAllExprents(true); exprs.add(rootExpr); for (Exprent expr : exprs) { if (expr.type != Exprent.EXPRENT_VAR) { continue; } VarExprent varExpr = (VarExprent) expr; if (!varExpr.isDefinition()) { continue; } int bytecodeOffset = varExpr.getBytecodeOffset(); if (bytecodeOffset == -1 || !originalPcTable.hasOriginalPc(bytecodeOffset)) { continue; } int pc = originalPcTable.getOriginalPc(bytecodeOffset); String name; if (originalPcTable.hasName(pc)) { name = originalPcTable.getName(pc); } else { name = "local" + pc; } varProc.setVarName(varExpr.getVarVersionPair(), name); } return 0; }, true); } varProc.refreshVarNames(namesCollector); } DecompilerContext.getLogger().endMethod(); } DecompilerContext.getLogger().endClass(); } @SuppressWarnings("deprecation") private static void killThread(Thread thread) { thread.stop(); } public MethodWrapper getMethodWrapper(String name, String descriptor) { return methods.getWithKey(InterpreterUtil.makeUniqueKey(name, descriptor)); } public StructClass getClassStruct() { return classStruct; } public VBStyleCollection getMethods() { return methods; } public Set getHiddenMembers() { return hiddenMembers; } public VBStyleCollection getStaticFieldInitializers() { return staticFieldInitializers; } public VBStyleCollection getDynamicFieldInitializers() { return dynamicFieldInitializers; } @Override public String toString() { return classStruct.qualifiedName; } }