void (ClassNode parent, ClassNode child) { // enclosing method, is null iff member class MethodWrapper enclosingMethod = parent.getWrapper().getMethods().getWithKey(child.enclosingMethod); // iterate all child methods for (MethodWrapper method : child.getWrapper().getMethods()) { if (method.root != null) { // neither abstract nor native Map<VarVersionPair, String> mapNewNames = new HashMap<>(); // local var names Map<VarVersionPair, VarType> mapNewTypes = new HashMap<>(); // local var types Map<Integer, VarVersionPair> mapParamsToNewVars = new HashMap<>(); if (method.synthParameters != null) { int index = 0, varIndex = 1; MethodDescriptor md = MethodDescriptor.parseDescriptor(method.methodStruct.getDescriptor()); for (VarVersionPair pair : method.synthParameters) { if (pair != null) { VarVersionPair newVar = new VarVersionPair(method.counter.getCounterAndIncrement(CounterContainer.VAR_COUNTER), 0); mapParamsToNewVars.put(varIndex, newVar); String varName = null; VarType varType = null; if (child.type != ClassNode.CLASS_MEMBER) { varName = enclosingMethod.varproc.getVarName(pair); varType = enclosingMethod.varproc.getVarType(pair); enclosingMethod.varproc.setVarFinal(pair, VarProcessor.VAR_EXPLICIT_FINAL); } if (pair.var == -1 || "this".equals(varName)) { if (parent.simpleName == null) { // anonymous enclosing class, no access to this varName = VarExprent.VAR_NAMELESS_ENCLOSURE; } else { varName = parent.simpleName + ".this"; } method.varproc.getThisVars().put(newVar, parent.classStruct.qualifiedName); } mapNewNames.put(newVar, varName); mapNewTypes.put(newVar, varType); } varIndex += md.params[index++].getStackSize(); } } Map<String, VarVersionPair> mapFieldsToNewVars = new HashMap<>(); for (ClassNode classNode = child; classNode != null; classNode = classNode.parent) { for (Entry<String, VarVersionPair> entry : classNode.mapFieldsToVars.entrySet()) { VarVersionPair newVar = new VarVersionPair(method.counter.getCounterAndIncrement(CounterContainer.VAR_COUNTER), 0); mapFieldsToNewVars.put(InterpreterUtil.makeUniqueKey(classNode.classStruct.qualifiedName, entry.getKey()), newVar); String varName = null; VarType varType = null; if (classNode.type != ClassNode.CLASS_MEMBER) { MethodWrapper enclosing_method = classNode.parent.getWrapper().getMethods().getWithKey(classNode.enclosingMethod); varName = enclosing_method.varproc.getVarName(entry.getValue()); varType = enclosing_method.varproc.getVarType(entry.getValue()); enclosing_method.varproc.setVarFinal(entry.getValue(), VarProcessor.VAR_EXPLICIT_FINAL); } if (entry.getValue().var == -1 || "this".equals(varName)) { if (classNode.parent.simpleName == null) { // anonymous enclosing class, no access to this varName = VarExprent.VAR_NAMELESS_ENCLOSURE; } else { varName = classNode.parent.simpleName + ".this"; } method.varproc.getThisVars().put(newVar, classNode.parent.classStruct.qualifiedName); } mapNewNames.put(newVar, varName); mapNewTypes.put(newVar, varType); // hide synthetic field if (classNode == child) { // fields higher up the chain were already handled with their classes StructField fd = child.classStruct.getFields().getWithKey(entry.getKey()); child.getWrapper().getHiddenMembers().add(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor())); } } } Set<String> setNewOuterNames = new HashSet<>(mapNewNames.values()); setNewOuterNames.removeAll(method.setOuterVarNames); method.varproc.refreshVarNames(new VarNamesCollector(setNewOuterNames)); method.setOuterVarNames.addAll(setNewOuterNames); for (Entry<VarVersionPair, String> entry : mapNewNames.entrySet()) { VarVersionPair pair = entry.getKey(); VarType type = mapNewTypes.get(pair); method.varproc.setVarName(pair, entry.getValue()); if (type != null) { method.varproc.setVarType(pair, type); } } method.getOrBuildGraph().iterateExprents(new DirectGraph.ExprentIterator() { @Override public int processExprent(Exprent exprent) { if (exprent.type == Exprent.EXPRENT_ASSIGNMENT) { AssignmentExprent assignExpr = (AssignmentExprent)exprent; if (assignExpr.getLeft().type == Exprent.EXPRENT_FIELD) { FieldExprent fExpr = (FieldExprent)assignExpr.getLeft(); String qName = child.classStruct.qualifiedName; if (fExpr.getClassname().equals(qName) && // process this class only mapFieldsToNewVars.containsKey(InterpreterUtil.makeUniqueKey(qName, fExpr.getName(), fExpr.getDescriptor().descriptorString))) { return 2; } } } if (child.type == ClassNode.CLASS_ANONYMOUS && CodeConstants.INIT_NAME.equals(method.methodStruct.getName()) && exprent.type == Exprent.EXPRENT_INVOCATION) { InvocationExprent invokeExpr = (InvocationExprent)exprent; if (invokeExpr.getFuncType() == InvocationExprent.TYPE_INIT) { // invocation of the super constructor in an anonymous class child.superInvocation = invokeExpr; // FIXME: save original names of parameters return 2; } } replaceExprent(exprent); return 0; } private Exprent replaceExprent(Exprent exprent) { if (exprent.type == Exprent.EXPRENT_VAR) { int varIndex = ((VarExprent)exprent).getIndex(); if (mapParamsToNewVars.containsKey(varIndex)) { VarVersionPair newVar = mapParamsToNewVars.get(varIndex); method.varproc.getExternalVars().add(newVar); return new VarExprent(newVar.var, method.varproc.getVarType(newVar), method.varproc); } } else if (exprent.type == Exprent.EXPRENT_FIELD) { FieldExprent fExpr = (FieldExprent)exprent; String key = InterpreterUtil.makeUniqueKey(fExpr.getClassname(), fExpr.getName(), fExpr.getDescriptor().descriptorString); if (mapFieldsToNewVars.containsKey(key)) { //if(fExpr.getClassname().equals(child.classStruct.qualifiedName) && // mapFieldsToNewVars.containsKey(key)) { VarVersionPair newVar = mapFieldsToNewVars.get(key); method.varproc.getExternalVars().add(newVar); return new VarExprent(newVar.var, method.varproc.getVarType(newVar), method.varproc); } } boolean replaced = true; while (replaced) { replaced = false; for (Exprent expr : exprent.getAllExprents()) { Exprent retExpr = replaceExprent(expr); if (retExpr != null) { exprent.replaceExprent(expr, retExpr); replaced = true; break; } } } return null; } }); } } } private static Map<String, List<VarFieldPair>> getMaskLocalVars(ClassWrapper wrapper) { Map<String, List<VarFieldPair>> mapMasks = new HashMap<>(); StructClass cl = wrapper.getClassStruct(); // iterate over constructors for (StructMethod mt : cl.getMethods()) { if (CodeConstants.INIT_NAME.equals(mt.getName())) { MethodDescriptor md = MethodDescriptor.parseDescriptor(mt.getDescriptor()); MethodWrapper method = wrapper.getMethodWrapper(CodeConstants.INIT_NAME, mt.getDescriptor()); DirectGraph graph = method.getOrBuildGraph(); if (graph != null) { // something gone wrong, should not be null List<VarFieldPair> fields = new ArrayList<>(md.params.length); int varIndex = 1; for (int i = 0; i < md.params.length; i++) { // no static methods allowed String keyField = getEnclosingVarField(cl, method, graph, varIndex); fields.add(keyField == null ? null : new VarFieldPair(keyField, new VarVersionPair(-1, 0))); // TODO: null? varIndex += md.params[i].getStackSize(); } mapMasks.put(mt.getDescriptor(), fields); } } } return mapMasks; } private static String getEnclosingVarField(StructClass cl, MethodWrapper method, DirectGraph graph, int index) { String field = ""; // parameter variable final if (method.varproc.getVarFinal(new VarVersionPair(index, 0)) == VarProcessor.VAR_NON_FINAL) { return null; } boolean noSynthFlag = DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET); // no loop at the begin DirectNode firstNode = graph.first; if (firstNode.predecessors.isEmpty()) { // assignment to a synthetic field? for (Exprent exprent : firstNode.exprents) { if (exprent.type == Exprent.EXPRENT_ASSIGNMENT) { AssignmentExprent assignExpr = (AssignmentExprent)exprent; if (assignExpr.getRight().type == Exprent.EXPRENT_VAR && ((VarExprent)assignExpr.getRight()).getIndex() == index && assignExpr.getLeft().type == Exprent.EXPRENT_FIELD) { FieldExprent left = (FieldExprent)assignExpr.getLeft(); StructField fd = cl.getField(left.getName(), left.getDescriptor().descriptorString); if (fd != null && cl.qualifiedName.equals(left.getClassname()) && (fd.isSynthetic() || noSynthFlag && possiblySyntheticField(fd))) { // local (== not inherited) field field = InterpreterUtil.makeUniqueKey(left.getName(), left.getDescriptor().descriptorString); break; } } } } } return field; } private static boolean possiblySyntheticField(StructField fd) { return fd.getName().contains("$") && fd.hasModifier(CodeConstants.ACC_FINAL) && fd.hasModifier(CodeConstants.ACC_PRIVATE); } private static void mergeListSignatures(List<VarFieldPair> first, List<VarFieldPair> second, boolean both) { int i = 1; while (first.size() > i && second.size() > i) { VarFieldPair fObj = first.get(first.size() - i); VarFieldPair sObj = second.get(second.size() - i); if (!isEqual(both, fObj, sObj)) { first.set(first.size() - i, null); if (both) { second.set(second.size() - i, null); } } else if (fObj != null) { if (fObj.varPair.var == -1) { fObj.varPair = sObj.varPair; } else { sObj.varPair = fObj.varPair; } } i++; } for (int j = 1; j <= first.size() - i; j++) { first.set(j, null); } if (both) { for (int j = 1; j <= second.size() - i; j++) { second.set(j, null); } } // first if (first.isEmpty()) { if (!second.isEmpty() && both) { second.set(0, null); } } else if (second.isEmpty()) { first.set(0, null); } else { VarFieldPair fObj = first.get(0); VarFieldPair sObj = second.get(0); if (!isEqual(both, fObj, sObj)) { first.set(0, null); if (both) { second.set(0, null); } } else if (fObj != null) { if (fObj.varPair.var == -1) { fObj.varPair = sObj.varPair; } else { sObj.varPair = fObj.varPair; } } } } private static boolean isEqual(boolean both, VarFieldPair fObj, VarFieldPair sObj) { boolean eq; if (fObj == null || sObj == null) { eq = (fObj == sObj); } else { eq = true; if (fObj.fieldKey.length() == 0) { fObj.fieldKey = sObj.fieldKey; } else if (sObj.fieldKey.length() == 0) { if (both) { sObj.fieldKey = fObj.fieldKey; } } else { eq = fObj.fieldKey.equals(sObj.fieldKey); } } return eq; } private static void setLocalClassDefinition(MethodWrapper method, ClassNode node) { RootStatement root = method.root; Set<Statement> setStats = new HashSet<>(); VarType classType = new VarType(node.classStruct.qualifiedName, true); Statement statement = getDefStatement(root, classType, setStats); if (statement == null) { // unreferenced local class statement = root.getFirst(); } Statement first = findFirstBlock(statement, setStats); List<Exprent> lst; if (first == null) { lst = statement.getVarDefinitions(); } else if (first.getExprents() == null) { lst = first.getVarDefinitions(); } else { lst = first.getExprents(); } int addIndex = 0; for (Exprent expr : lst) { if (searchForClass(expr, classType)) { break; } addIndex++; } VarExprent var = new VarExprent(method.counter.getCounterAndIncrement(CounterContainer.VAR_COUNTER), classType, method.varproc); var.setDefinition(true); var.setClassDef(true); lst.add(addIndex, var); } private static Statement findFirstBlock(Statement stat, Set<Statement> setStats) { LinkedList<Statement> stack = new LinkedList<>(); stack.add(stat); while (!stack.isEmpty()) { Statement st = stack.remove(0); if (stack.isEmpty() || setStats.contains(st)) { if (st.isLabeled() && !stack.isEmpty() || st.getExprents() != null) { return st; } stack.clear(); switch (st.type) { case SEQUENCE -> stack.addAll(0, st.getStats()); case IF, ROOT, SWITCH, SYNCHRONIZED -> stack.add(st.getFirst()); default -> { return st; } } } } return null; } private static Statement getDefStatement(Statement stat, VarType classType, Set<? super Statement> setStats) { List<Exprent> lst = new ArrayList<>(); Statement retStat = null; if (stat.getExprents() == null) { int counter = 0; for (Object obj : stat.getSequentialObjects()) { if (obj instanceof Statement st) { Statement stTemp = getDefStatement(st, classType, setStats); if (stTemp != null) { if (counter == 1) { retStat = stat; break; } retStat = stTemp; counter++; } if (st.type == StatementType.DO) { DoStatement dost = (DoStatement)st; lst.addAll(dost.getInitExprentList()); lst.addAll(dost.getConditionExprentList()); } } else if (obj instanceof Exprent) { lst.add((Exprent)obj); } } } else { lst = stat.getExprents(); } if (retStat != stat) { for (Exprent exprent : lst) { if (exprent != null && searchForClass(exprent, classType)) { retStat = stat; break; } } } if (retStat != null) { setStats.add(stat); } return retStat; } private static boolean searchForClass(Exprent exprent, VarType classType) { List<Exprent> lst = exprent.getAllExprents(true); lst.add(exprent); String classname = classType.getValue(); for (Exprent expr : lst) { boolean res = false; switch (expr.type) { case Exprent.EXPRENT_CONST -> { ConstExprent constExpr = (ConstExprent)expr; res = (VarType.VARTYPE_CLASS.equals(constExpr.getConstType()) && classname.equals(constExpr.getValue()) || classType.equals(constExpr.getConstType())); } case Exprent.EXPRENT_FIELD -> res = classname.equals(((FieldExprent)expr).getClassname()); case Exprent.EXPRENT_INVOCATION -> res = classname.equals(((InvocationExprent)expr).getClassName()); case Exprent.EXPRENT_NEW -> { VarType newType = expr.getExprType(); res = newType.getType() == CodeConstants.TYPE_OBJECT && classname.equals(newType.getValue()); } case Exprent.EXPRENT_VAR -> { VarExprent varExpr = (VarExprent)expr; if (varExpr.isDefinition()) { VarType varType = varExpr.getVarType(); if (classType.equals(varType) || (varType.getArrayDim() > 0 && classType.getValue().equals(varType.getValue()))) { res = true; } } } } if (res) { return true; } } return false; } private static class VarFieldPair { public String fieldKey; public VarVersionPair varPair; VarFieldPair(String field, VarVersionPair varPair) { this.fieldKey = field; this.varPair = varPair; } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof VarFieldPair pair)) return false; return fieldKey.equals(pair.fieldKey) && varPair.equals(pair.varPair); } @Override public int hashCode() { return fieldKey.hashCode() + varPair.hashCode(); } } }