package kr.ac.snu.selab.soot.analyzer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import kr.ac.snu.selab.soot.callgraph.MetaInfoCallGraph; import kr.ac.snu.selab.soot.graph.GraphPathCollector; import kr.ac.snu.selab.soot.graph.MetaInfo; import kr.ac.snu.selab.soot.graph.Path; import kr.ac.snu.selab.soot.graph.collectors.ReferenceFlowCollector; import kr.ac.snu.selab.soot.graph.pathcheckers.CallPathChecker; import kr.ac.snu.selab.soot.graph.refgraph.LocalInfoNode; import kr.ac.snu.selab.soot.graph.refgraph.ReferenceFlowGraph; import soot.Body; import soot.Hierarchy; import soot.Local; import soot.PointsToAnalysis; import soot.PointsToSet; import soot.RefType; import soot.Scene; import soot.SootClass; import soot.SootField; import soot.SootMethod; import soot.Type; import soot.Unit; import soot.Value; import soot.ValueBox; import soot.jimple.InvokeExpr; import soot.jimple.internal.JAssignStmt; import soot.jimple.internal.JInvokeStmt; import soot.jimple.internal.JReturnStmt; import soot.jimple.toolkits.callgraph.CallGraph; import soot.jimple.toolkits.callgraph.Edge; public class AnalysisUtil { public AnalysisUtil() { } public Map<String, MetaInfo> metaInfoMap(Collection<SootClass> classes) { Map<String, MetaInfo> metaInfoMap = new HashMap<String, MetaInfo>(); for (SootClass aClass : classes) { for (SootMethod aMethod : aClass.getMethods()) { MetaInfo metaInfo = new MetaInfo(aMethod); metaInfoMap.put(aMethod.getSignature(), metaInfo); } for (SootField aField : aClass.getFields()) { MetaInfo metaInfo = new MetaInfo(aField); metaInfoMap.put(aField.getSignature(), metaInfo); } } return metaInfoMap; } // Creation public Map<String, LocalInfo> creations(SootMethod aMethod) { Map<String, LocalInfo> creations = new HashMap<String, LocalInfo>(); Map<String, Local> locals = locals(aMethod); List<Unit> units = units(aMethod); String newExprClassStr = "class soot.jimple.internal.JNewExpr"; for (Unit unit : units) { if (unit instanceof JAssignStmt) { JAssignStmt stmt = (JAssignStmt)unit; Value rightVal = stmt.getRightOp(); if (rightVal.getClass().toString().equals(newExprClassStr)) { Value leftVal = stmt.getLeftOp(); Local local = locals.get(leftVal.toString()); LocalInfo localInfo = new Creation(); localInfo.setLocal(local); localInfo.setDeclaringMethod(aMethod); localInfo.setUnit(unit); creations.put(localInfo.toString(), localInfo); } } } return creations; } // Independent Role Analysis over a Type public void analyzeRole(SootClass aType, Map<String, MetaInfo> metaInfoMap, RoleRepository roles, Map<String, SootClass> classMap, Hierarchy hierarchy, CallGraph cg) { for (String key : metaInfoMap.keySet()) { MetaInfo metaInfo = metaInfoMap.get(key); if (metaInfo.getElement() instanceof SootMethod) { SootMethod method = (SootMethod)metaInfo.getElement(); Map<String, LocalInfo> callLocalInfos = calls(method, aType, classMap); Map<String, LocalInfo> creationLocalInfos = typeFilterOfLocalMap(creations(method), aType, hierarchy, classMap); for (LocalInfo callLocalInfo : callLocalInfos.values()) { checkCaller(aType, metaInfo, callLocalInfo, roles); } for (LocalInfo creationLocalInfo : creationLocalInfos.values()) { checkCreator(aType, metaInfo, creationLocalInfo, roles, classMap); } checkInjector(method, aType, metaInfoMap, roles, classMap, hierarchy, cg); } else if (metaInfo.getElement() instanceof SootField) { SootField field = (SootField)metaInfo.getElement(); SootClass fieldType = typeToClass(field.getType(), classMap); if (fieldType != null) { if (fieldType.equals(aType)) { Store store = new Store(); store.setInterfaceType(aType); metaInfo.addRole(store); roles.addStore(metaInfo); } } } } } public void checkInjector(SootMethod aMethod, SootClass aType, Map<String, MetaInfo> metaInfoMap, RoleRepository roles, Map<String, SootClass> classMap, Hierarchy hierarchy, CallGraph cg) { Map<String, LocalInfo> setterLocalInfos = typeFilterOfLocalMap(localsRightOfField(aMethod), aType, hierarchy, classMap); for (LocalInfo localInfo : setterLocalInfos.values()) { SootField field = localInfo.field(); SootClass fieldType = typeToClass(field.getType(), classMap); if (fieldType.equals(aType)) { SootMethod setterMethod = localInfo.declaringMethod(); if (metaInfoMap.containsKey(setterMethod.getSignature())) { MetaInfo setterMetaInfo = metaInfoMap.get(setterMethod.getSignature()); Set<SootMethod> injectorMethods = new HashSet<SootMethod>(); Iterator<Edge> edgeIter = cg.edgesInto((SootMethod)setterMetaInfo.getElement()); while(edgeIter.hasNext()) { injectorMethods.add(edgeIter.next().src()); } for (SootMethod injectorMethod : injectorMethods) { String key = injectorMethod.getSignature(); if (metaInfoMap.containsKey(key)) { Injector injector = new Injector(); injector.setInterfaceType(aType); metaInfoMap.get(key).addRole(injector); roles.addInjector(metaInfoMap.get(key)); } } } } } } // // Call without type constraint // public Map<String, LocalInfo> calls(SootMethod aMethod, Map<String, SootClass> classMap) { // Map<String, LocalInfo> calls = new HashMap<String, LocalInfo>(); // // Map<String, Local> locals = locals(aMethod); // // String virtualInvoke = "class soot.jimple.internal.JVirtualInvokeExpr"; // String interfaceInvoke = "class soot.jimple.internal.JInterfaceInvokeExpr"; // // List<Unit> units = units(aMethod); // // for (Unit unit : units) { // if (unit instanceof JAssignStmt) { // JAssignStmt stmt = (JAssignStmt)unit; // if (stmt.containsInvokeExpr()) { // String classString = stmt.getInvokeExpr().getClass().toString(); // if (classString.equals(virtualInvoke) || classString.equals(interfaceInvoke)) { // Value receiver = ((ValueBox)stmt.getInvokeExpr().getUseBoxes().get(0)).getValue(); // Local receiverLocal = locals.get(receiver.toString()); // SootClass receiverType = typeToClass(receiverLocal.getType(), classMap); // if (receiverType != null) { // LocalInfo localInfo = new Call(); // localInfo.setLocal(receiverLocal); // localInfo.setDeclaringMethod(aMethod); // localInfo.setMethod(stmt.getInvokeExpr().getMethod()); // localInfo.setUnit(unit); // // calls.put(localInfo.toString(), localInfo); // } // } // } // } // else if (unit instanceof JInvokeStmt) { // JInvokeStmt stmt = (JInvokeStmt)unit; // if (stmt.containsInvokeExpr()) { // String classString = stmt.getInvokeExpr().getClass().toString(); // if (classString.equals(virtualInvoke) || classString.equals(interfaceInvoke)) { // Value receiver = ((ValueBox)stmt.getInvokeExpr().getUseBoxes().get(0)).getValue(); // Local receiverLocal = locals.get(receiver.toString()); // SootClass receiverType = typeToClass(receiverLocal.getType(), classMap); // if (receiverType != null) { // LocalInfo localInfo = new Call(); // localInfo.setLocal(receiverLocal); // localInfo.setDeclaringMethod(aMethod); // localInfo.setMethod(stmt.getInvokeExpr().getMethod()); // localInfo.setUnit(unit); // // calls.put(localInfo.toString(), localInfo); // } // } // } // } // } // // return calls; // } // Call public Map<String, LocalInfo> calls(SootMethod aMethod, SootClass aType, Map<String, SootClass> classMap) { Map<String, LocalInfo> calls = new HashMap<String, LocalInfo>(); if (aType == null) { return calls; } Map<String, Local> locals = locals(aMethod); String virtualInvoke = "class soot.jimple.internal.JVirtualInvokeExpr"; String interfaceInvoke = "class soot.jimple.internal.JInterfaceInvokeExpr"; List<Unit> units = units(aMethod); for (Unit unit : units) { if (unit instanceof JAssignStmt) { JAssignStmt stmt = (JAssignStmt)unit; if (stmt.containsInvokeExpr()) { String classString = stmt.getInvokeExpr().getClass().toString(); if (classString.equals(virtualInvoke) || classString.equals(interfaceInvoke)) { Value receiver = ((ValueBox)stmt.getInvokeExpr().getUseBoxes().get(0)).getValue(); Local receiverLocal = locals.get(receiver.toString()); SootClass receiverType = typeToClass(receiverLocal.getType(), classMap); if (receiverType != null) { if (receiverType.equals(aType)) { LocalInfo localInfo = new Call(); localInfo.setLocal(receiverLocal); localInfo.setDeclaringMethod(aMethod); localInfo.setMethod(stmt.getInvokeExpr().getMethod()); localInfo.setUnit(unit); calls.put(localInfo.toString(), localInfo); } } } } } else if (unit instanceof JInvokeStmt) { JInvokeStmt stmt = (JInvokeStmt)unit; if (stmt.containsInvokeExpr()) { String classString = stmt.getInvokeExpr().getClass().toString(); if (classString.equals(virtualInvoke) || classString.equals(interfaceInvoke)) { Value receiver = ((ValueBox)stmt.getInvokeExpr().getUseBoxes().get(0)).getValue(); Local receiverLocal = locals.get(receiver.toString()); SootClass receiverType = typeToClass(receiverLocal.getType(), classMap); if (receiverType != null) { if (receiverType.equals(aType)) { LocalInfo localInfo = new Call(); localInfo.setLocal(receiverLocal); localInfo.setDeclaringMethod(aMethod); localInfo.setMethod(stmt.getInvokeExpr().getMethod()); localInfo.setUnit(unit); calls.put(localInfo.toString(), localInfo); } } } } } } return calls; } // In public Map<String, LocalInfo> localsOfMethodParam(SootMethod aMethod) { Map<String, LocalInfo> localsOfMethodParam = new HashMap<String, LocalInfo>(); if (aMethod.hasActiveBody()) { Body body = aMethod.getActiveBody(); int numOfParams = aMethod.getParameterCount(); for (int i = 0; i < numOfParams; i++) { Local local = body.getParameterLocal(i); LocalInfo localInfo = new MethodParamIn(); localInfo.setLocal(local); localInfo.setDeclaringMethod(aMethod); localInfo.setParamNum(i); localsOfMethodParam.put(localInfo.toString(), localInfo); } } return localsOfMethodParam; } // In public Map<String, LocalInfo> localsLeftOfField(SootMethod aMethod) { Map<String, LocalInfo> localsLeftOfField = new HashMap<String, LocalInfo>(); Map<String, Local> locals = locals(aMethod); List<Unit> units = units(aMethod); for (Unit unit : units) { if (isFieldInRightSide(unit)) { JAssignStmt stmt = (JAssignStmt)unit; Value leftVal = stmt.getLeftOp(); Local local = locals.get(leftVal.toString()); SootField field = stmt.getFieldRef().getField(); LocalInfo localInfo = new FieldIn(); localInfo.setLocal(local); localInfo.setDeclaringMethod(aMethod); localInfo.setField(field); localInfo.setUnit(unit); localsLeftOfField.put(localInfo.toString(), localInfo); } } return localsLeftOfField; } // In public Map<String, LocalInfo> localsLeftOfInvoke(SootMethod aMethod) { Map<String, LocalInfo> localsLeftOfInvoke = new HashMap<String, LocalInfo>(); Map<String, Local> locals = locals(aMethod); List<Unit> units = units(aMethod); for (Unit unit : units) { if (isInvokeInRightSide(unit)) { JAssignStmt stmt = (JAssignStmt)unit; Value leftVal = stmt.getLeftOp(); Local local = locals.get(leftVal.toString()); SootMethod method = stmt.getInvokeExpr().getMethod(); LocalInfo localInfo = new InvokeIn(); localInfo.setLocal(local); localInfo.setDeclaringMethod(aMethod); localInfo.setMethod(method); localInfo.setUnit(unit); localsLeftOfInvoke.put(localInfo.toString(), localInfo); } } return localsLeftOfInvoke; } // Out public Map<String, LocalInfo> localsOfInvokeParam(SootMethod aMethod) { Map<String, LocalInfo> localsOfInvokeParam = new HashMap<String, LocalInfo>(); Map<String, Local> locals = locals(aMethod); List<Unit> units = units(aMethod); for (Unit unit : units) { if (unit instanceof JAssignStmt) { JAssignStmt stmt = (JAssignStmt)unit; if (stmt.containsInvokeExpr()) { InvokeExpr invokeExpr = stmt.getInvokeExpr(); List<Value> args = invokeExpr.getArgs(); int argNum = 0; for (Value arg : args) { String key = arg.toString(); if (locals.containsKey(key)) { Local local = locals.get(key); LocalInfo localInfo = new InvokeParamOut(); localInfo.setLocal(local); localInfo.setDeclaringMethod(aMethod); localInfo.setMethod(invokeExpr.getMethod()); localInfo.setParamNum(argNum); localInfo.setUnit(unit); localsOfInvokeParam.put(localInfo.toString(), localInfo); } argNum++; } } } else if (unit instanceof JInvokeStmt) { JInvokeStmt stmt = (JInvokeStmt)unit; if (stmt.containsInvokeExpr()) { InvokeExpr invokeExpr = stmt.getInvokeExpr(); List<Value> args = invokeExpr.getArgs(); int argNum = 0; for (Value arg : args) { String key = arg.toString(); if (locals.containsKey(key)) { Local local = locals.get(key); LocalInfo localInfo = new InvokeParamOut(); localInfo.setLocal(local); localInfo.setDeclaringMethod(aMethod); localInfo.setMethod(invokeExpr.getMethod()); localInfo.setParamNum(argNum); localInfo.setUnit(unit); localsOfInvokeParam.put(localInfo.toString(), localInfo); } argNum++; } } } } return localsOfInvokeParam; } // Out public Map<String, LocalInfo> localsRightOfField(SootMethod aMethod) { Map<String, LocalInfo> localsRightOfField = new HashMap<String, LocalInfo>(); Map<String, Local> locals = locals(aMethod); List<Unit> units = units(aMethod); for (Unit unit : units) { if (isFieldInLeftSide(unit)) { JAssignStmt stmt = (JAssignStmt)unit; Value rightVal = stmt.getRightOp(); String key = rightVal.toString(); if (locals.containsKey(key)) { Local local = locals.get(key); SootField field = stmt.getFieldRef().getField(); LocalInfo localInfo = new FieldOut(); localInfo.setLocal(local); localInfo.setDeclaringMethod(aMethod); localInfo.setField(field); localInfo.setUnit(unit); localsRightOfField.put(localInfo.toString(), localInfo); } } } return localsRightOfField; } // Out public Map<String, LocalInfo> localOfReturn(SootMethod aMethod) { Map<String, LocalInfo> localOfReturn = new HashMap<String, LocalInfo>(); Map<String, Local> locals = locals(aMethod); List<Unit> units = units(aMethod); for (Unit unit : units) { if (unit instanceof JReturnStmt) { JReturnStmt stmt = (JReturnStmt)unit; Value returnVal = stmt.getOp(); String key = returnVal.toString(); if (locals.containsKey(key)) { Local local = locals.get(key); LocalInfo localInfo = new ReturnOut(); localInfo.setLocal(local); localInfo.setDeclaringMethod(aMethod); localInfo.setUnit(unit); localOfReturn.put(localInfo.toString(), localInfo); } } } return localOfReturn; } public Map<String, LocalInfo> typeFilterOfLocalMap(Map<String, LocalInfo> aLocalInfoMap, SootClass aType, Hierarchy hierarchy, Map<String, SootClass> classMap) { Map<String, LocalInfo> filteredMap = new HashMap<String, LocalInfo>(); for (String key : aLocalInfoMap.keySet()) { LocalInfo localInfo = aLocalInfoMap.get(key); Type localType = localInfo.local().getType(); SootClass localTypeClass = typeToClass(localType, classMap); if (isSubtypeIncluding(localTypeClass, aType, hierarchy)) { filteredMap.put(key, localInfo); } } return filteredMap; } public List<String> methodStrsInto(SootMethod aMethod, CallGraph cg) { List<String> callers = new ArrayList<String>(); Iterator<Edge> iter = cg.edgesInto(aMethod); while (iter.hasNext()) { callers.add(iter.next().src().getSignature()); } return callers; } // Map<String, LocalInfo> localsLeftOfFieldMap = typeFilterOfLocalMap(localsLeftOfField(aMethod), aType, hierarchy, classMap); // Map<String, LocalInfo> localsLeftOfInvokeMap = typeFilterOfLocalMap(localsLeftOfInvoke(aMethod), aType, hierarchy, classMap); // // Map<String, LocalInfo> localsOfInvokeParamMap = typeFilterOfLocalMap(localsOfInvokeParam(aMethod), aType, hierarchy, classMap); // Map<String, LocalInfo> localsRightOfFieldMap = typeFilterOfLocalMap(localsRightOfField(aMethod), aType, hierarchy, classMap); public MethodInternalPath analyzeMethodParamToReturn(SootMethod aMethod, SootClass aType, Hierarchy hierarchy, Map<String, SootClass> classMap) { MethodInternalPath mip = new MethodInternalPath(); PointsToAnalysis pta = Scene.v().getPointsToAnalysis(); Map<String, LocalInfo> localsOfMethodParamMap = typeFilterOfLocalMap(localsOfMethodParam(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> localOfReturnMap = typeFilterOfLocalMap(localOfReturn(aMethod), aType, hierarchy, classMap); if (!localOfReturnMap.isEmpty()) { LocalInfo localInfoOfReturn = localOfReturnMap.values().iterator().next(); Local localOfReturn = localInfoOfReturn.local(); PointsToSet set1 = pta.reachingObjects(localOfReturn); if (!localsOfMethodParamMap.isEmpty()) { for (LocalInfo localInfoOfMethodParam : localsOfMethodParamMap.values()) { Local localOfMethodParam = localInfoOfMethodParam.local(); PointsToSet set2 = pta.reachingObjects(localOfMethodParam); if (set1.hasNonEmptyIntersection(set2)) { mip.setMethodParamToReturn(localInfoOfMethodParam.paramNum()); break; } } } } return mip; } public SootClass typeToClass(Type aType, Map<String, SootClass> classMap) { SootClass result = null; String key = aType.toString(); if (classMap.containsKey(key)) { result = classMap.get(key); } return result; } public List<Pair<LocalInfo, LocalInfo>> internalEdges(SootMethod aMethod, SootClass aType, Hierarchy hierarchy, Map<String, SootClass> classMap) { List<Pair<LocalInfo, LocalInfo>> edges = new ArrayList<Pair<LocalInfo, LocalInfo>>(); List<LocalInfo> starts = new ArrayList<LocalInfo>(); List<LocalInfo> ends = new ArrayList<LocalInfo>(); Map<String, LocalInfo> methodParamInMap = typeFilterOfLocalMap(localsOfMethodParam(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> fieldInMap = typeFilterOfLocalMap(localsLeftOfField(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> invokeInMap = typeFilterOfLocalMap(localsLeftOfInvoke(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> creationMap = typeFilterOfLocalMap(creations(aMethod), aType, hierarchy, classMap); starts.addAll(methodParamInMap.values()); starts.addAll(fieldInMap.values()); starts.addAll(invokeInMap.values()); starts.addAll(creationMap.values()); Map<String, LocalInfo> returnOutMap = typeFilterOfLocalMap(localOfReturn(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> invokeParamOutMap = typeFilterOfLocalMap(localsOfInvokeParam(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> fieldOutMap = typeFilterOfLocalMap(localsRightOfField(aMethod), aType, hierarchy, classMap); ends.addAll(returnOutMap.values()); ends.addAll(invokeParamOutMap.values()); ends.addAll(fieldOutMap.values()); for (LocalInfo start : starts) { for (LocalInfo end : ends) { if (isConnected(start, end)) { Pair<LocalInfo, LocalInfo> pair = new Pair<LocalInfo, LocalInfo>(start, end); edges.add(pair); } } } return edges; } public MethodInfo analyzeMethod(SootMethod aMethod, SootClass aType, Hierarchy hierarchy, Map<String, SootClass> classMap) { MethodInfo info = new MethodInfo(); List<Pair<LocalInfo, LocalInfo>> edges = new ArrayList<Pair<LocalInfo, LocalInfo>>(); List<LocalInfo> starts = new ArrayList<LocalInfo>(); List<LocalInfo> ends = new ArrayList<LocalInfo>(); Map<String, LocalInfo> methodParamInMap = typeFilterOfLocalMap(localsOfMethodParam(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> fieldInMap = typeFilterOfLocalMap(localsLeftOfField(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> invokeInMap = typeFilterOfLocalMap(localsLeftOfInvoke(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> creationMap = typeFilterOfLocalMap(creations(aMethod), aType, hierarchy, classMap); starts.addAll(methodParamInMap.values()); starts.addAll(fieldInMap.values()); starts.addAll(invokeInMap.values()); starts.addAll(creationMap.values()); Map<String, LocalInfo> returnOutMap = typeFilterOfLocalMap(localOfReturn(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> invokeParamOutMap = typeFilterOfLocalMap(localsOfInvokeParam(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> fieldOutMap = typeFilterOfLocalMap(localsRightOfField(aMethod), aType, hierarchy, classMap); Map<String, LocalInfo> callMap = calls(aMethod, aType, classMap); ends.addAll(returnOutMap.values()); ends.addAll(invokeParamOutMap.values()); ends.addAll(fieldOutMap.values()); ends.addAll(callMap.values()); for (LocalInfo start : starts) { for (LocalInfo end : ends) { if (isConnected(start, end)) { Pair<LocalInfo, LocalInfo> pair = new Pair<LocalInfo, LocalInfo>(start, end); edges.add(pair); } } } info.setMethodParamIn(methodParamInMap); info.setFieldIn(fieldInMap); info.setInvokeIn(invokeInMap); info.setCreation(creationMap); info.setReturnOut(returnOutMap); info.setInvokeParamOut(invokeParamOutMap); info.setFieldOut(fieldOutMap); info.setCall(callMap); info.setInternalEdges(edges); return info; } public Map<SootMethod, MethodInfo> methodInfoMap(SootClass aType, Map<String, SootClass> classMap, Hierarchy hierarchy) { Map<SootMethod, MethodInfo> methodInfoMap = new HashMap<SootMethod, MethodInfo>(); for (SootClass aClass : classMap.values()) { for (SootMethod aMethod : aClass.getMethods()) { methodInfoMap.put(aMethod, analyzeMethod(aMethod, aType, hierarchy, classMap)); } } return methodInfoMap; } public Map<SootField, LocalInfo> fieldInfoMap(SootClass aType, Map<String, SootClass> classMap, Hierarchy hierarchy) { Map<SootField, LocalInfo> fieldInfoMap = new HashMap<SootField, LocalInfo>(); for (SootClass aClass : classMap.values()) { for (SootField aField : aClass.getFields()) { SootClass fieldType = null; String key = aField.getType().toString(); if (classMap.containsKey(key)) { fieldType = classMap.get(key); } if ((fieldType != null) && (isSubtypeIncluding(fieldType, aType, hierarchy))) { LocalInfo localInfo = new Field(); localInfo.setDeclaringField(aField); fieldInfoMap.put(aField, localInfo); } } } return fieldInfoMap; } public List<Pair<LocalInfo, LocalInfo>> invokeParamOut_to_methodParamIn(Map<SootMethod, MethodInfo> methodInfoMap, CallGraph cg) { List<Pair<LocalInfo, LocalInfo>> edges = new ArrayList<Pair<LocalInfo, LocalInfo>>(); for (MethodInfo methodInfo : methodInfoMap.values()) { Map<String, LocalInfo> methodParamInMap = methodInfo.methodParamIn(); for (LocalInfo methodParamIn : methodParamInMap.values()) { Iterator<Edge> edgeIter = cg.edgesInto(methodParamIn.declaringMethod()); while (edgeIter.hasNext()) { Edge edge = edgeIter.next(); SootMethod caller = edge.src(); MethodInfo methodInfoOfCaller = methodInfoMap.get(caller); Map<String, LocalInfo> invokeParamOutMap = methodInfoOfCaller.invokeParamOut(); for (LocalInfo invokeParamOut : invokeParamOutMap.values()) { if (invokeParamOut.method().equals(methodParamIn.declaringMethod()) && invokeParamOut.paramNum() == methodParamIn.paramNum()) { Pair<LocalInfo, LocalInfo> pair = new Pair<LocalInfo, LocalInfo>(invokeParamOut, methodParamIn); edges.add(pair); } } } } } return edges; } public List<Pair<LocalInfo, LocalInfo>> returnOut_to_InvokeIn(Map<SootMethod, MethodInfo> methodInfoMap) { List<Pair<LocalInfo, LocalInfo>> edges = new ArrayList<Pair<LocalInfo, LocalInfo>>(); for (MethodInfo methodInfo : methodInfoMap.values()) { Map<String, LocalInfo> invokeInMap = methodInfo.invokeIn(); for (LocalInfo invokeIn : invokeInMap.values()) { SootMethod callee = invokeIn.method(); MethodInfo methodInfoOfCallee = methodInfoMap.get(callee); for (LocalInfo returnOut : methodInfoOfCallee.returnOut().values()) { Pair<LocalInfo, LocalInfo> pair = new Pair<LocalInfo, LocalInfo>(returnOut, invokeIn); edges.add(pair); } } } return edges; } public List<Pair<LocalInfo, LocalInfo>> field_to_fieldIn(Map<SootMethod, MethodInfo> methodInfoMap, Map<SootField, LocalInfo> fieldInfoMap) { List<Pair<LocalInfo, LocalInfo>> edges = new ArrayList<Pair<LocalInfo, LocalInfo>>(); for (MethodInfo methodInfo : methodInfoMap.values()) { Map<String, LocalInfo> fieldInMap = methodInfo.fieldIn(); for (LocalInfo fieldIn : fieldInMap.values()) { SootField field = fieldIn.field(); if (fieldInfoMap.containsKey(field)) { LocalInfo fieldInfo = fieldInfoMap.get(field); Pair<LocalInfo, LocalInfo> pair = new Pair<LocalInfo, LocalInfo>(fieldInfo, fieldIn); edges.add(pair); } } } return edges; } public List<Pair<LocalInfo, LocalInfo>> fieldOut_to_field(Map<SootMethod, MethodInfo> methodInfoMap, Map<SootField, LocalInfo> fieldInfoMap) { List<Pair<LocalInfo, LocalInfo>> edges = new ArrayList<Pair<LocalInfo, LocalInfo>>(); for (MethodInfo methodInfo : methodInfoMap.values()) { Map<String, LocalInfo> fieldOutMap = methodInfo.fieldOut(); for (LocalInfo fieldOutInfo : fieldOutMap.values()) { SootField field = fieldOutInfo.field(); if (fieldInfoMap.containsKey(field)) { LocalInfo fieldInfo = fieldInfoMap.get(field); Pair<LocalInfo, LocalInfo> pair = new Pair<LocalInfo, LocalInfo>(fieldOutInfo, fieldInfo); edges.add(pair); } } } return edges; } public MetaInfoCallGraph metaInfoCallGraph(CallGraph cg, Map<String, MetaInfo> metaInfoMap) { MetaInfoCallGraph graph = new MetaInfoCallGraph(); for (MetaInfo metaInfo : metaInfoMap.values()) { if (metaInfo.getElement() instanceof SootMethod) { SootMethod method = (SootMethod)(metaInfo.getElement()); Iterator<Edge> edgesIntoIter = cg.edgesInto(method); while(edgesIntoIter.hasNext()) { Edge edge = edgesIntoIter.next(); SootMethod srcMethod = edge.src(); if (metaInfoMap.containsKey(srcMethod.getSignature())) { MetaInfo srcMetaInfo = metaInfoMap.get(srcMethod.getSignature()); graph.addEdge(srcMetaInfo, metaInfo); } } Iterator<Edge> edgesOutOfIter = cg.edgesOutOf(method); while(edgesOutOfIter.hasNext()) { Edge edge = edgesOutOfIter.next(); SootMethod tgtMethod = edge.tgt(); if (metaInfoMap.containsKey(tgtMethod.getSignature())) { MetaInfo tgtMethodInfo = metaInfoMap.get(tgtMethod.getSignature()); graph.addEdge(metaInfo, tgtMethodInfo); } } } } return graph; } public boolean doesCall(Set<MetaInfo> froms, Set<MetaInfo> tos, MetaInfoCallGraph metaInfoCallGraph) { boolean result = false; for (MetaInfo from : froms) { CallPathChecker pathChecker = new CallPathChecker(from, metaInfoCallGraph); pathChecker.setEndNodes(tos); if (pathChecker.check()) { result = true; break; } } return result; } public boolean isInterface(SootClass aClass) { boolean result = false; if (aClass.isInterface() || aClass.isAbstract()) { result = true; } return result; } public Set<SootClass> interfaceTypes(Map<String, SootClass> classMap) { Set<SootClass> result = new HashSet<SootClass>(); for (SootClass aClass : classMap.values()) { if (isInterface(aClass)) { result.add(aClass); } } return result; } public PatternAnalysisResult analyzePattern(PatternAnalysis analysis, Map<String, SootClass> classMap, Hierarchy hierarchy, CallGraph cg) { return analysis.perform(classMap, hierarchy, cg, this); } public ReferenceFlowGraph referenceFlowGraph(SootClass aType, Map<String, SootClass> classMap, Hierarchy hierarchy, CallGraph cg) { ReferenceFlowGraph graph = new ReferenceFlowGraph(); Map<SootMethod, MethodInfo> methodInfoMap = methodInfoMap(aType, classMap, hierarchy); Map<SootField, LocalInfo> fieldInfoMap = fieldInfoMap(aType, classMap, hierarchy); for (MethodInfo methodInfo : methodInfoMap.values()) { List<Pair<LocalInfo, LocalInfo>> internalEdges = methodInfo.internalEdges(); for (Pair<LocalInfo, LocalInfo> pair : internalEdges) { graph.addEdge(pair.first(), pair.second()); } } List<Pair<LocalInfo, LocalInfo>> invokeParamOut_to_methodParamIn = invokeParamOut_to_methodParamIn(methodInfoMap, cg); for (Pair<LocalInfo, LocalInfo> pair : invokeParamOut_to_methodParamIn) { graph.addEdge(pair.first(), pair.second()); } List<Pair<LocalInfo, LocalInfo>> returnOut_to_InvokeIn = returnOut_to_InvokeIn(methodInfoMap); for (Pair<LocalInfo, LocalInfo> pair : returnOut_to_InvokeIn) { graph.addEdge(pair.first(), pair.second()); } List<Pair<LocalInfo, LocalInfo>> field_to_fieldIn = field_to_fieldIn(methodInfoMap, fieldInfoMap); for (Pair<LocalInfo, LocalInfo> pair : field_to_fieldIn) { graph.addEdge(pair.first(), pair.second()); } List<Pair<LocalInfo, LocalInfo>> fieldOut_to_field = fieldOut_to_field(methodInfoMap, fieldInfoMap); for (Pair<LocalInfo, LocalInfo> pair : fieldOut_to_field) { graph.addEdge(pair.first(), pair.second()); } for (MethodInfo methodInfo : methodInfoMap.values()) { graph.addStartNodes(methodInfo.creation().values()); } for (MethodInfo methodInfo : methodInfoMap.values()) { graph.addEndNodes(methodInfo.call().values()); } return graph; } public Map<LocalInfoNode, List<Path<LocalInfoNode>>> referenceFlows(SootClass aType, Map<String, SootClass> classMap, Hierarchy hierarchy, CallGraph cg) { Map<LocalInfoNode, List<Path<LocalInfoNode>>> result = new HashMap<LocalInfoNode, List<Path<LocalInfoNode>>>(); ReferenceFlowGraph graph = referenceFlowGraph(aType, classMap, hierarchy, cg); List<LocalInfoNode> startNodes = graph.startNodes(); for (LocalInfoNode startNode : startNodes) { GraphPathCollector<LocalInfoNode> pathCollector = new ReferenceFlowCollector<LocalInfoNode>(startNode, graph); List<Path<LocalInfoNode>> pathList = pathCollector.run(); result.put(startNode, pathList); } return result; } public MetaInfo getMetaInfo(LocalInfo localInfo, Map<String, MetaInfo> metaInfoMap) { MetaInfo result = null; if (localInfo.declaringMethod() != null) { result = metaInfoMap.get(localInfo.declaringMethod().getSignature()); } else if (localInfo.declaringField() != null) { result = metaInfoMap.get(localInfo.declaringField().getSignature()); } return result; } public Set<Path<MetaInfo>> abstractReferenceFlows(SootClass aType, Map<String, SootClass> classMap, Hierarchy hierarchy, CallGraph cg, Map<String, MetaInfo> metaInfoMap, RoleRepository roles) { Set<Path<MetaInfo>> absFlowSet = new HashSet<Path<MetaInfo>>(); Map<LocalInfoNode, List<Path<LocalInfoNode>>> referenceFlows = referenceFlows(aType, classMap, hierarchy, cg); for (List<Path<LocalInfoNode>> list : referenceFlows.values()) { for (Path<LocalInfoNode> path : list) { Path<MetaInfo> newPath = new Path<MetaInfo>(); for (LocalInfoNode node : path.getNodeList()) { LocalInfo localInfo = (LocalInfo)node.getElement(); MetaInfo metaInfo = getMetaInfo(localInfo, metaInfoMap); if (newPath.isEmpty()) { // Creator newPath.add(metaInfo); checkCreator(aType, metaInfo, localInfo, roles, classMap); } else { // Store Check checkStore(aType, metaInfo, localInfo, classMap, roles); // Caller Check checkCaller(aType, metaInfo, localInfo, roles); if (!metaInfo.getElement().equals(newPath.last().getElement())) { newPath.add(metaInfo); } } } // Injector Check checkInjectorBasedOnFlow(aType, newPath, metaInfoMap, cg, roles); absFlowSet.add(newPath); } } return absFlowSet; } public void checkCaller(SootClass aType, MetaInfo metaInfo, LocalInfo localInfo, RoleRepository roles) { if (localInfo instanceof Call) { Caller caller = new Caller(); caller.setInterfaceType(aType); caller.setDeclaringClass(localInfo.declaringMethod().getDeclaringClass()); caller.setDeclaringMethod(localInfo.declaringMethod()); caller.setCalledMethod(localInfo.method()); metaInfo.addRole(caller); roles.addCaller(metaInfo); } } public void checkStore(SootClass aType, MetaInfo metaInfo, LocalInfo localInfo, Map<String, SootClass> classMap, RoleRepository roles) { if (localInfo.declaringField() != null) { SootClass fieldType = typeToClass(localInfo.declaringField().getType(), classMap); if (fieldType.equals(aType)) { Store store = new Store(); store.setInterfaceType(aType); metaInfo.addRole(store); roles.addStore(metaInfo); } } } public void checkCreator(SootClass aType, MetaInfo metaInfo, LocalInfo localInfo, RoleRepository roles, Map<String, SootClass> classMap) { Creator creator = new Creator(); creator.setInterfaceType(aType); creator.setDeclaringClass(((SootMethod)metaInfo.getElement()).getDeclaringClass()); creator.setConcreteType(typeToClass(localInfo.local().getType(), classMap)); metaInfo.addRole(creator); roles.addCreator(metaInfo); } public void checkInjectorBasedOnFlow(SootClass aType, Path<MetaInfo> absReferenceFlow, Map<String, MetaInfo> metaInfoMap, CallGraph cg, RoleRepository roles) { List<MetaInfo> metaInfoList = absReferenceFlow.getNodeList(); Set<MetaInfo> metaInfoSetOfFlow = new HashSet<MetaInfo>(); metaInfoSetOfFlow.addAll(metaInfoList); int index = 0; for (MetaInfo metaInfo : metaInfoList) { if (metaInfo.isStore()) { int setterIndex = index - 1; if (setterIndex >= 0) { MetaInfo setter = metaInfoList.get(setterIndex); Set<SootMethod> injectorMethods = new HashSet<SootMethod>(); Iterator<Edge> edgeIter = cg.edgesInto((SootMethod)setter.getElement()); while(edgeIter.hasNext()) { injectorMethods.add(edgeIter.next().src()); } for (SootMethod injectorMethod : injectorMethods) { String key = injectorMethod.getSignature(); if (metaInfoMap.containsKey(key)) { if (metaInfoSetOfFlow.contains(metaInfoMap.get(key))) { Injector injector = new Injector(); injector.setInterfaceType(aType); metaInfoMap.get(key).addRole(injector); roles.addInjector(metaInfoMap.get(key)); } } } } } index++; } } public boolean isCaller(LocalInfo localInfo, SootClass aType, Map<String, SootClass> classMap) { boolean result = false; String virtualInvoke = "class soot.jimple.internal.JVirtualInvokeExpr"; String interfaceInvoke = "class soot.jimple.internal.JInterfaceInvokeExpr"; String category = localInfo.category(); if ((category.equals("in_invoke")) || (category.equals("out_invokeParam"))) { SootMethod declaringMethod = localInfo.declaringMethod(); Map<String, Local> locals = locals(declaringMethod); Unit unit = localInfo.unit(); if (unit instanceof JAssignStmt) { JAssignStmt stmt = (JAssignStmt)unit; String classString = stmt.getInvokeExpr().getClass().toString(); if (classString.equals(virtualInvoke) || classString.equals(interfaceInvoke)) { Value receiver = ((ValueBox)stmt.getInvokeExpr().getUseBoxes().get(0)).getValue(); Local receiverLocal = locals.get(receiver.toString()); SootClass receiverType = typeToClass(receiverLocal.getType(), classMap); if (receiverType.equals(aType)) { result = true; } } } else if (unit instanceof JInvokeStmt) { JInvokeStmt stmt = (JInvokeStmt)unit; String classString = stmt.getInvokeExpr().getClass().toString(); if (classString.equals(virtualInvoke) || classString.equals(interfaceInvoke)) { Value receiver = ((ValueBox)stmt.getInvokeExpr().getUseBoxes().get(0)).getValue(); Local receiverLocal = locals.get(receiver.toString()); SootClass receiverType = typeToClass(receiverLocal.getType(), classMap); if (receiverType.equals(aType)) { result = true; } } } } return result; } public boolean isConnected(LocalInfo a, LocalInfo b) { boolean result = false; if ((a != null) && (b != null)) { PointsToAnalysis pta = Scene.v().getPointsToAnalysis(); PointsToSet set1 = pta.reachingObjects(a.local()); PointsToSet set2 = pta.reachingObjects(b.local()); if (set1.hasNonEmptyIntersection(set2)) { result = true; } } return result; } public Map<SootClass, List<Creator>> creators(SootMethod aMethod, SootClass aType, Map<String, SootClass> classMap, Hierarchy hierarchy) { Map<SootClass, List<Creator>> creators = new HashMap<SootClass, List<Creator>>(); List<Unit> units = units(aMethod); String newExprClassStr = "class soot.jimple.internal.JNewExpr"; for (Unit unit : units) { if (unit instanceof JAssignStmt) { JAssignStmt stmt = (JAssignStmt)unit; Value rightVal = stmt.getRightOp(); if (rightVal.getClass().toString().equals(newExprClassStr)) { Type type = rightVal.getType(); SootClass typeClass = classMap.get(type.toString()); if (isSubtypeIncluding(typeClass, aType, hierarchy)) { Creator creator = new Creator(unit, aType, aMethod.getDeclaringClass(), aMethod, typeClass); if (creators.containsKey(aType)) { List<Creator> creatorList = creators.get(aType); creatorList.add(creator); creators.put(aType, creatorList); } else { List<Creator> creatorList = new ArrayList<Creator>(); creatorList.add(creator); creators.put(aType, creatorList); } } } } } return creators; } public boolean isFieldInRightSide(Unit unit) { boolean result = false; if (unit instanceof JAssignStmt) { String instanceFieldRef = "class soot.jimple.internal.JInstanceFieldRef"; String staticFieldRef = "class soot.jimple.StaticFieldRef"; JAssignStmt stmt = (JAssignStmt)unit; String classString = stmt.getRightOp().getClass().toString(); if (classString.equals(instanceFieldRef) || classString.equals(staticFieldRef)) { result = true; } } return result; } public boolean isFieldInLeftSide(Unit unit) { boolean result = false; if (unit instanceof JAssignStmt) { String instanceFieldRef = "class soot.jimple.internal.JInstanceFieldRef"; String staticFieldRef = "class soot.jimple.StaticFieldRef"; JAssignStmt stmt = (JAssignStmt)unit; String classString = stmt.getLeftOp().getClass().toString(); if (classString.equals(instanceFieldRef) || classString.equals(staticFieldRef)) { result = true; } } return result; } public boolean isInvokeInRightSide(Unit unit) { boolean result = false; if (unit instanceof JAssignStmt) { String virtualInvoke = "class soot.jimple.internal.JVirtualInvokeExpr"; String interfaceInvoke = "class soot.jimple.internal.JInterfaceInvokeExpr"; String staticInvoke = "class soot.jimple.internal.JStaticInvokeExpr"; JAssignStmt stmt = (JAssignStmt)unit; String classString = stmt.getRightOp().getClass().toString(); if (classString.equals(virtualInvoke) || classString.equals(staticInvoke) || classString.equals(interfaceInvoke)) { result = true; } } return result; } public List<Unit> units(SootMethod aMethod) { List<Unit> units = new ArrayList<Unit>(); if (aMethod.hasActiveBody()) { Body body = aMethod.getActiveBody(); units.addAll(body.getUnits()); } return units; } public Map<String, Local> locals(SootMethod aMethod) { Map<String, Local> locals = new HashMap<String, Local>(); if (aMethod.hasActiveBody()) { Body body = aMethod.getActiveBody(); Iterator<Local> localIter = body.getLocals().iterator(); while(localIter.hasNext()) { Local local = localIter.next(); locals.put(local.toString(), local); } } return locals; } public boolean isSubtypeIncluding(SootClass a, SootClass b, Hierarchy hierarchy) { boolean result = false; if (!(a == null) && !(b == null)) { boolean isAInterface = a.isInterface(); boolean isBInterface = b.isInterface(); if (isAInterface && isBInterface) { if (hierarchy.isInterfaceSubinterfaceOf(a, b) || a.equals(b)) { result = true; } } else if (!isAInterface && isBInterface) { List<SootClass> implementers = hierarchy.getImplementersOf(b); if (implementers.contains(a)) { result = true; } } else if (isAInterface && !isBInterface) { // do nothing } else if (!isAInterface && !isBInterface) { if (hierarchy.isClassSubclassOfIncluding(a, b)) { result = true; } } } return result; } public boolean doesHaveParam(SootMethod aMethod, SootClass aType, Map<String, SootClass> classMap) { boolean result = false; List<Type> paramTypes = aMethod.getParameterTypes(); for (Type paramType : paramTypes) { if (aType.equals(typeToClass(paramType, classMap))) { result = true; break; } } return result; } public boolean doesHaveParamSubtypeIncluding(SootMethod aMethod, SootClass aType, Map<String, SootClass> classMap, Hierarchy hierarchy) { boolean result = false; List<Type> paramTypes = aMethod.getParameterTypes(); for (Type paramType : paramTypes) { if (isSubtypeIncluding(typeToClass(paramType, classMap), aType, hierarchy)) { result = true; break; } } return result; } public Set<SootClass> directSubClassesOf(SootClass aClass, Hierarchy hierarchy) { Set<SootClass> classes = new HashSet<SootClass>(); if (aClass.isInterface()) { List<SootClass> implementors = hierarchy.getDirectImplementersOf(aClass); classes.addAll(implementors); } else { List<SootClass> subclasses = hierarchy.getDirectSubclassesOf(aClass); classes.addAll(subclasses); } return classes; } public boolean isDelegateMethod(SootMethod aMethod, Map<String, SootClass> classMap, Map<SootClass, Set<SootClass>> superClassMap) { boolean result = false; Set<SootClass> fieldTypes = new HashSet<SootClass>(); Set<SootField> fields = allFields(aMethod.getDeclaringClass(), superClassMap); for (SootField field : fields) { SootClass fieldType = typeToClass(field.getType(), classMap); if (fieldType != null) { fieldTypes.add(fieldType); } } for (SootClass fieldType : fieldTypes) { if (!calls(aMethod, fieldType, classMap).isEmpty()) { result = true; break; } } return result; } public Set<LocalInfo> delegateInfos(SootMethod aMethod, Map<String, SootClass> classMap, Map<SootClass, Set<SootClass>> superClassMap) { Set<LocalInfo> delegateInfoSet = new HashSet<LocalInfo>(); Set<SootClass> fieldTypes = new HashSet<SootClass>(); Set<SootField> fields = allFields(aMethod.getDeclaringClass(), superClassMap); for (SootField field : fields) { SootClass fieldType = typeToClass(field.getType(), classMap); if (fieldType != null) { fieldTypes.add(fieldType); } } for (SootClass fieldType : fieldTypes) { Map<String, LocalInfo> callLocalInfoMap = calls(aMethod, fieldType, classMap); if (!callLocalInfoMap.isEmpty()) { delegateInfoSet.addAll(callLocalInfoMap.values()); } } return delegateInfoSet; } public boolean doesHaveCollection(SootClass aClass, Map<SootClass, Set<SootClass>> superClassMap) { if (aClass.isInterface()) { return false; } Set<SootClass> collectionTypes = new HashSet<SootClass>(); SootClass listClass = RefType.v("java.util.List").getSootClass(); collectionTypes.add(listClass); for (SootField aField : allFields(aClass, superClassMap)) { Type fieldType = aField.getType(); String fieldTypeStr = fieldType.toString(); for (SootClass collectionType : collectionTypes) { if (fieldTypeStr.equals(collectionType.toString())) { return true; } } } return false; } public Set<SootField> allFields(SootClass aClass, Map<SootClass, Set<SootClass>> superClassMap) { Set<SootField> fieldSet = new HashSet<SootField>(); if (aClass.isInterface()) { return fieldSet; } fieldSet.addAll(aClass.getFields()); if (superClassMap.containsKey(aClass)) { for (SootClass superClass : superClassMap.get(aClass)) { fieldSet.addAll(allFields(superClass, superClassMap)); } } return fieldSet; } public Map<SootClass, Set<SootClass>> superClassMap(Map<String, SootClass> classMap, Hierarchy hierarchy) { Map<SootClass, Set<SootClass>> superClassMap = new HashMap<SootClass, Set<SootClass>>(); for (SootClass aClass : classMap.values()) { if (!aClass.isInterface()) { List<SootClass> subClasses = hierarchy.getDirectSubclassesOf(aClass); for (SootClass subClass : subClasses) { if (superClassMap.containsKey(subClass)) { Set<SootClass> superClassSet = superClassMap.get(subClass); superClassSet.add(aClass); } else { Set<SootClass> superClassSet = new HashSet<SootClass>(); superClassSet.add(aClass); superClassMap.put(subClass, superClassSet); } } } else if (aClass.isInterface()) { List<SootClass> implementors = hierarchy.getDirectImplementersOf(aClass); for (SootClass implementor : implementors) { if (superClassMap.containsKey(implementor)) { Set<SootClass> superInterfaceSet = superClassMap.get(implementor); superInterfaceSet.add(aClass); } else { Set<SootClass> superInterfaceSet = new HashSet<SootClass>(); superInterfaceSet.add(aClass); superClassMap.put(implementor, superInterfaceSet); } } } } return superClassMap; } public Set<SootClass> topLevelClasses(Set<SootClass> classes, Hierarchy hierarchy) { Set<SootClass> topLevelClasses = new HashSet<SootClass>(); Set<SootClass> subClasses = new HashSet<SootClass>(); List<SootClass> allClasses = new ArrayList<SootClass>(); allClasses.addAll(classes); int size = allClasses.size(); for (int i = 0; i < size ; i++) { for (int j = i+1; j < size; j++) { SootClass iClass = allClasses.get(i); SootClass jClass = allClasses.get(j); if (isSubtypeIncluding(iClass, jClass, hierarchy)) { subClasses.add(iClass); } else if (isSubtypeIncluding(jClass, iClass, hierarchy)) { subClasses.add(jClass); } } } for (SootClass aClass : allClasses) { if (!subClasses.contains(aClass)) { topLevelClasses.add(aClass); } } int sizeAfterTrim = topLevelClasses.size(); if (size == sizeAfterTrim) { return topLevelClasses; } else { return topLevelClasses(topLevelClasses, hierarchy); } } }