| /* |
| * Copyright 2000-2014 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.jetbrains.java.decompiler.main.rels; |
| |
| import org.jetbrains.java.decompiler.code.CodeConstants; |
| import org.jetbrains.java.decompiler.main.ClassesProcessor.ClassNode; |
| 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.IFernflowerPreferences; |
| import org.jetbrains.java.decompiler.modules.decompiler.exps.*; |
| import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectGraph; |
| import org.jetbrains.java.decompiler.modules.decompiler.sforms.DirectNode; |
| import org.jetbrains.java.decompiler.modules.decompiler.vars.VarVersionPaar; |
| import org.jetbrains.java.decompiler.struct.StructMethod; |
| import org.jetbrains.java.decompiler.struct.gen.MethodDescriptor; |
| import org.jetbrains.java.decompiler.util.InterpreterUtil; |
| |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.Map; |
| |
| public class NestedMemberAccess { |
| |
| private static final int METHOD_ACCESS_NORMAL = 1; |
| private static final int METHOD_ACCESS_FIELD_GET = 2; |
| private static final int METHOD_ACCESS_FIELD_SET = 3; |
| private static final int METHOD_ACCESS_METHOD = 4; |
| |
| private boolean noSynthFlag; |
| private Map<MethodWrapper, Integer> mapMethodType = new HashMap<MethodWrapper, Integer>(); |
| |
| |
| public void propagateMemberAccess(ClassNode root) { |
| if (root.nested.isEmpty()) { |
| return; |
| } |
| |
| noSynthFlag = DecompilerContext.getOption(IFernflowerPreferences.SYNTHETIC_NOT_SET); |
| |
| computeMethodTypes(root); |
| |
| eliminateStaticAccess(root); |
| } |
| |
| |
| private void computeMethodTypes(ClassNode node) { |
| if (node.type == ClassNode.CLASS_LAMBDA) { |
| return; |
| } |
| |
| for (ClassNode nd : node.nested) { |
| computeMethodTypes(nd); |
| } |
| |
| for (MethodWrapper method : node.wrapper.getMethods()) { |
| computeMethodType(node, method); |
| } |
| } |
| |
| private void computeMethodType(ClassNode node, MethodWrapper method) { |
| int type = METHOD_ACCESS_NORMAL; |
| |
| if (method.root != null) { |
| DirectGraph graph = method.getOrBuildGraph(); |
| |
| StructMethod mt = method.methodStruct; |
| if ((noSynthFlag || mt.isSynthetic()) && mt.hasModifier(CodeConstants.ACC_STATIC)) { |
| if (graph.nodes.size() == 2) { // incl. dummy exit node |
| if (graph.first.exprents.size() == 1) { |
| Exprent exprent = graph.first.exprents.get(0); |
| |
| MethodDescriptor mtdesc = MethodDescriptor.parseDescriptor(mt.getDescriptor()); |
| int parcount = mtdesc.params.length; |
| |
| Exprent exprCore = exprent; |
| |
| if (exprent.type == Exprent.EXPRENT_EXIT) { |
| ExitExprent exexpr = (ExitExprent)exprent; |
| if (exexpr.getExittype() == ExitExprent.EXIT_RETURN && exexpr.getValue() != null) { |
| exprCore = exexpr.getValue(); |
| } |
| } |
| |
| switch (exprCore.type) { |
| case Exprent.EXPRENT_FIELD: |
| FieldExprent fexpr = (FieldExprent)exprCore; |
| if ((parcount == 1 && !fexpr.isStatic()) || |
| (parcount == 0 && fexpr.isStatic())) { |
| if (fexpr.getClassname().equals(node.classStruct.qualifiedName)) { // FIXME: check for private flag of the field |
| if (fexpr.isStatic() || |
| (fexpr.getInstance().type == Exprent.EXPRENT_VAR && ((VarExprent)fexpr.getInstance()).getIndex() == 0)) { |
| type = METHOD_ACCESS_FIELD_GET; |
| } |
| } |
| } |
| break; |
| case Exprent.EXPRENT_VAR: // qualified this |
| if (parcount == 1) { |
| // this or final variable |
| if (((VarExprent)exprCore).getIndex() != 0) { |
| type = METHOD_ACCESS_FIELD_GET; |
| } |
| } |
| |
| break; |
| case Exprent.EXPRENT_INVOCATION: |
| type = METHOD_ACCESS_METHOD; |
| break; |
| case Exprent.EXPRENT_ASSIGNMENT: |
| AssignmentExprent asexpr = (AssignmentExprent)exprCore; |
| if (asexpr.getLeft().type == Exprent.EXPRENT_FIELD && asexpr.getRight().type == Exprent.EXPRENT_VAR) { |
| FieldExprent fexpras = (FieldExprent)asexpr.getLeft(); |
| if ((parcount == 2 && !fexpras.isStatic()) || |
| (parcount == 1 && fexpras.isStatic())) { |
| if (fexpras.getClassname().equals(node.classStruct.qualifiedName)) { // FIXME: check for private flag of the field |
| if (fexpras.isStatic() || |
| (fexpras.getInstance().type == Exprent.EXPRENT_VAR && ((VarExprent)fexpras.getInstance()).getIndex() == 0)) { |
| if (((VarExprent)asexpr.getRight()).getIndex() == parcount - 1) { |
| type = METHOD_ACCESS_FIELD_SET; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| |
| if (type == METHOD_ACCESS_METHOD) { // FIXME: check for private flag of the method |
| |
| type = METHOD_ACCESS_NORMAL; |
| |
| InvocationExprent invexpr = (InvocationExprent)exprCore; |
| |
| if ((invexpr.isStatic() && invexpr.getLstParameters().size() == parcount) || |
| (!invexpr.isStatic() && invexpr.getInstance().type == Exprent.EXPRENT_VAR |
| && ((VarExprent)invexpr.getInstance()).getIndex() == 0 && invexpr.getLstParameters().size() == parcount - 1)) { |
| |
| boolean equalpars = true; |
| |
| for (int i = 0; i < invexpr.getLstParameters().size(); i++) { |
| Exprent parexpr = invexpr.getLstParameters().get(i); |
| if (parexpr.type != Exprent.EXPRENT_VAR || |
| ((VarExprent)parexpr).getIndex() != i + (invexpr.isStatic() ? 0 : 1)) { |
| equalpars = false; |
| break; |
| } |
| } |
| |
| if (equalpars) { |
| type = METHOD_ACCESS_METHOD; |
| } |
| } |
| } |
| } |
| else if (graph.first.exprents.size() == 2) { |
| Exprent exprentFirst = graph.first.exprents.get(0); |
| Exprent exprentSecond = graph.first.exprents.get(1); |
| |
| if (exprentFirst.type == Exprent.EXPRENT_ASSIGNMENT && |
| exprentSecond.type == Exprent.EXPRENT_EXIT) { |
| |
| MethodDescriptor mtdesc = MethodDescriptor.parseDescriptor(mt.getDescriptor()); |
| int parcount = mtdesc.params.length; |
| |
| AssignmentExprent asexpr = (AssignmentExprent)exprentFirst; |
| if (asexpr.getLeft().type == Exprent.EXPRENT_FIELD && asexpr.getRight().type == Exprent.EXPRENT_VAR) { |
| FieldExprent fexpras = (FieldExprent)asexpr.getLeft(); |
| if ((parcount == 2 && !fexpras.isStatic()) || |
| (parcount == 1 && fexpras.isStatic())) { |
| if (fexpras.getClassname().equals(node.classStruct.qualifiedName)) { // FIXME: check for private flag of the field |
| if (fexpras.isStatic() || |
| (fexpras.getInstance().type == Exprent.EXPRENT_VAR && ((VarExprent)fexpras.getInstance()).getIndex() == 0)) { |
| if (((VarExprent)asexpr.getRight()).getIndex() == parcount - 1) { |
| |
| ExitExprent exexpr = (ExitExprent)exprentSecond; |
| if (exexpr.getExittype() == ExitExprent.EXIT_RETURN && exexpr.getValue() != null) { |
| if (exexpr.getValue().type == Exprent.EXPRENT_VAR && |
| ((VarExprent)asexpr.getRight()).getIndex() == parcount - 1) { |
| type = METHOD_ACCESS_FIELD_SET; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| if (type != METHOD_ACCESS_NORMAL) { |
| mapMethodType.put(method, type); |
| } |
| else { |
| mapMethodType.remove(method); |
| } |
| } |
| |
| |
| private void eliminateStaticAccess(ClassNode node) { |
| |
| if (node.type == ClassNode.CLASS_LAMBDA) { |
| return; |
| } |
| |
| for (MethodWrapper meth : node.wrapper.getMethods()) { |
| |
| if (meth.root != null) { |
| |
| boolean replaced = false; |
| |
| DirectGraph graph = meth.getOrBuildGraph(); |
| |
| HashSet<DirectNode> setVisited = new HashSet<DirectNode>(); |
| LinkedList<DirectNode> stack = new LinkedList<DirectNode>(); |
| stack.add(graph.first); |
| |
| while (!stack.isEmpty()) { // TODO: replace with interface iterator? |
| |
| DirectNode nd = stack.removeFirst(); |
| |
| if (setVisited.contains(nd)) { |
| continue; |
| } |
| setVisited.add(nd); |
| |
| for (int i = 0; i < nd.exprents.size(); i++) { |
| Exprent exprent = nd.exprents.get(i); |
| |
| replaced |= replaceInvocations(node, meth, exprent); |
| |
| if (exprent.type == Exprent.EXPRENT_INVOCATION) { |
| Exprent ret = replaceAccessExprent(node, meth, (InvocationExprent)exprent); |
| |
| if (ret != null) { |
| nd.exprents.set(i, ret); |
| replaced = true; |
| } |
| } |
| } |
| |
| for (DirectNode ndx : nd.succs) { |
| stack.add(ndx); |
| } |
| } |
| |
| if (replaced) { |
| computeMethodType(node, meth); |
| } |
| } |
| } |
| |
| for (ClassNode child : node.nested) { |
| eliminateStaticAccess(child); |
| } |
| } |
| |
| |
| private boolean replaceInvocations(ClassNode caller, MethodWrapper meth, Exprent exprent) { |
| |
| boolean res = false; |
| |
| for (Exprent expr : exprent.getAllExprents()) { |
| res |= replaceInvocations(caller, meth, expr); |
| } |
| |
| while (true) { |
| |
| boolean found = false; |
| |
| for (Exprent expr : exprent.getAllExprents()) { |
| if (expr.type == Exprent.EXPRENT_INVOCATION) { |
| Exprent newexpr = replaceAccessExprent(caller, meth, (InvocationExprent)expr); |
| if (newexpr != null) { |
| exprent.replaceExprent(expr, newexpr); |
| found = true; |
| res = true; |
| break; |
| } |
| } |
| } |
| |
| if (!found) { |
| break; |
| } |
| } |
| |
| return res; |
| } |
| |
| private static boolean sameTree(ClassNode caller, ClassNode callee) { |
| |
| if (caller.classStruct.qualifiedName.equals(callee.classStruct.qualifiedName)) { |
| return false; |
| } |
| |
| while (caller.parent != null) { |
| caller = caller.parent; |
| } |
| |
| while (callee.parent != null) { |
| callee = callee.parent; |
| } |
| |
| return caller == callee; |
| } |
| |
| private Exprent replaceAccessExprent(ClassNode caller, MethodWrapper methdest, InvocationExprent invexpr) { |
| |
| ClassNode node = DecompilerContext.getClassProcessor().getMapRootClasses().get(invexpr.getClassname()); |
| |
| MethodWrapper methsource = null; |
| if (node != null && node.wrapper != null) { |
| methsource = node.wrapper.getMethodWrapper(invexpr.getName(), invexpr.getStringDescriptor()); |
| } |
| |
| if (methsource == null || !mapMethodType.containsKey(methsource)) { |
| return null; |
| } |
| |
| // if same method, return |
| if (node.classStruct.qualifiedName.equals(caller.classStruct.qualifiedName) && |
| methsource.methodStruct.getName().equals(methdest.methodStruct.getName()) && |
| methsource.methodStruct.getDescriptor().equals(methdest.methodStruct.getDescriptor())) { |
| // no recursive invocations permitted! |
| return null; |
| } |
| |
| int type = mapMethodType.get(methsource); |
| |
| // // FIXME: impossible case. METHOD_ACCESS_NORMAL is not saved in the map |
| // if(type == METHOD_ACCESS_NORMAL) { |
| // return null; |
| // } |
| |
| if (!sameTree(caller, node)) { |
| return null; |
| } |
| |
| DirectGraph graph = methsource.getOrBuildGraph(); |
| Exprent source = graph.first.exprents.get(0); |
| |
| Exprent retexprent = null; |
| |
| switch (type) { |
| case METHOD_ACCESS_FIELD_GET: |
| ExitExprent exsource = (ExitExprent)source; |
| if (exsource.getValue().type == Exprent.EXPRENT_VAR) { // qualified this |
| VarExprent var = (VarExprent)exsource.getValue(); |
| String varname = methsource.varproc.getVarName(new VarVersionPaar(var)); |
| |
| if (!methdest.setOuterVarNames.contains(varname)) { |
| VarNamesCollector vnc = new VarNamesCollector(); |
| vnc.addName(varname); |
| |
| methdest.varproc.refreshVarNames(vnc); |
| methdest.setOuterVarNames.add(varname); |
| } |
| |
| int index = methdest.counter.getCounterAndIncrement(CounterContainer.VAR_COUNTER); |
| VarExprent ret = new VarExprent(index, var.getVartype(), methdest.varproc); |
| methdest.varproc.setVarName(new VarVersionPaar(index, 0), varname); |
| |
| retexprent = ret; |
| } |
| else { // field |
| FieldExprent ret = (FieldExprent)exsource.getValue().copy(); |
| if (!ret.isStatic()) { |
| ret.replaceExprent(ret.getInstance(), invexpr.getLstParameters().get(0)); |
| } |
| retexprent = ret; |
| } |
| break; |
| case METHOD_ACCESS_FIELD_SET: |
| AssignmentExprent ret; |
| if (source.type == Exprent.EXPRENT_EXIT) { |
| ExitExprent extex = (ExitExprent)source; |
| ret = (AssignmentExprent)extex.getValue().copy(); |
| } |
| else { |
| ret = (AssignmentExprent)source.copy(); |
| } |
| FieldExprent fexpr = (FieldExprent)ret.getLeft(); |
| |
| if (fexpr.isStatic()) { |
| ret.replaceExprent(ret.getRight(), invexpr.getLstParameters().get(0)); |
| } |
| else { |
| ret.replaceExprent(ret.getRight(), invexpr.getLstParameters().get(1)); |
| fexpr.replaceExprent(fexpr.getInstance(), invexpr.getLstParameters().get(0)); |
| } |
| retexprent = ret; |
| break; |
| case METHOD_ACCESS_METHOD: |
| if (source.type == Exprent.EXPRENT_EXIT) { |
| source = ((ExitExprent)source).getValue(); |
| } |
| |
| InvocationExprent invret = (InvocationExprent)source.copy(); |
| |
| int index = 0; |
| if (!invret.isStatic()) { |
| invret.replaceExprent(invret.getInstance(), invexpr.getLstParameters().get(0)); |
| index = 1; |
| } |
| |
| for (int i = 0; i < invret.getLstParameters().size(); i++) { |
| invret.replaceExprent(invret.getLstParameters().get(i), invexpr.getLstParameters().get(i + index)); |
| } |
| |
| retexprent = invret; |
| } |
| |
| |
| if (retexprent != null) { |
| // hide synthetic access method |
| boolean hide = true; |
| |
| if (node.type == ClassNode.CLASS_ROOT || (node.access & CodeConstants.ACC_STATIC) != 0) { |
| StructMethod mt = methsource.methodStruct; |
| if (!mt.isSynthetic()) { |
| hide = false; |
| } |
| } |
| if (hide) { |
| node.wrapper.getHiddenMembers().add(InterpreterUtil.makeUniqueKey(invexpr.getName(), invexpr.getStringDescriptor())); |
| } |
| } |
| |
| return retexprent; |
| } |
| } |