/****************************************************************************** * Copyright (c) 2009 - 2015 IBM Corporation. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *****************************************************************************/ /** * */ package com.ibm.wala.memsat.util; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import com.ibm.wala.cast.java.ipa.callgraph.AstJavaSSAPropagationCallGraphBuilder.EnclosingObjectReferenceKey; import com.ibm.wala.cast.loader.AstMethod; import com.ibm.wala.cast.tree.CAstSourcePositionMap.Position; import com.ibm.wala.classLoader.IField; import com.ibm.wala.classLoader.IMethod; import com.ibm.wala.classLoader.ShrikeBTMethod; import com.ibm.wala.ipa.callgraph.CGNode; import com.ibm.wala.ipa.callgraph.propagation.ArrayContentsKey; import com.ibm.wala.ipa.callgraph.propagation.InstanceFieldKey; import com.ibm.wala.ipa.callgraph.propagation.InstanceKey; import com.ibm.wala.ipa.callgraph.propagation.PointerKey; import com.ibm.wala.ipa.callgraph.propagation.StaticFieldKey; import com.ibm.wala.ipa.callgraph.propagation.rta.CallSite; import com.ibm.wala.ipa.modref.ArrayLengthKey; import com.ibm.wala.memsat.frontEnd.InlinedInstruction; import com.ibm.wala.shrikeCT.InvalidClassFileException; import com.ibm.wala.ssa.IR; import com.ibm.wala.types.TypeName; import com.ibm.wala.util.collections.Pair; import com.ibm.wala.util.graph.Graph; import kodkod.ast.Node; /** * A set of utility functions for string manipulation and pretty printing of Kodkod nodes, * IRs, etc. * * @author Emina Torlak */ public final class Strings { /** * Returns a pretty-printed string representation of the * given nodes, with each line offset with at least the given * number of whitespaces. The display parameter determines how * the mapped nodes are displayed; that is, a descendant d of the * given node is displayed as display.get(d.toString()) if * display.containsKey(d.toString()) is true. * @requires 0 <= offset < line * @return a pretty-printed string representation of the * given nodes */ public static String prettyPrint(Collection<Node> nodes, int offset, int line, Map<String,String> display) { return PrettyPrinter.print(nodes, offset, 80, display); } /** * Returns a pretty-printed string representation of the * given node, with each line offset with at least the given * number of whitespaces. The display parameter determines how * the mapped nodes are displayed; that is, a descendant d of the * given node is displayed as display.get(d.toString()) if * display.containsKey(d.toString()) is true. * @requires 0 <= offset < line * @return a pretty-printed string representation of the * given node */ public static String prettyPrint(Node node, int offset, Map<String, String> display) { return prettyPrint(node, offset, 80, display); } /** * Returns a pretty-printed string representation of the * given node, with each line offset with at least the given * number of whitespaces. The line parameter determines the * length of each pretty-printed line, including the offset. * The display parameter determines how * the mapped nodes are displayed; that is, a descendant d of the * given node is displayed as display.get(d.toString()) if * display.containsKey(d.toString()) is true. * @requires 0 <= offset < line * @return a pretty-printed string representation of the * given node */ public static String prettyPrint(Node node, int offset, int line, final Map<String, String> display) { return prettyPrint(Collections.singleton(node), offset, line, display); } /** * Returns a pretty-printed string representation of the * given node, with each line offset with at least the given * number of whitespaces. The line parameter determines the * length of each pretty-printed line, including the offset. * @requires 0 <= offset < line * @return a pretty-printed string representation of the * given node */ @SuppressWarnings("unchecked") public static String prettyPrint(Node node, int offset, int line) { assert offset >= 0 && offset < line && line > 0; return prettyPrint(node, offset, line, Collections.EMPTY_MAP); } /** * Returns a pretty-printed string representation of the * given node, with each line offset with at least the given * number of whitespaces. * @requires 0 <= offset < 80 * @return a pretty-printed string representation of the * given node */ public static String prettyPrint(Node node, int offset) { return prettyPrint(node,offset,80); } /** * Returns a string that consists of the given number of repetitions * of the specified string. * @return str^reps */ public static String repeat(String str, int reps) { final StringBuffer result = new StringBuffer(); for(int i = 0; i < reps; i++) { result.append(str); } return result.toString(); } /** * Returns the given string, with all new lines replaced with new lines indented * by the given number of spaces. * @return given string, with all new lines replaced with new lines indented * by the given number of spaces. */ public static String indent(String str, int offset) { assert offset >= 0; final String indent = repeat(" ", offset); return indent + str.replaceAll("\\n", "\n"+indent); } /** * Returns a pretty-print String representation * of the given graph. * @return pretty-print String representation * of the given graph */ public static String prettyPrint(Graph<?> graph) { return prettyPrint(graph,0); } /** * Returns a pretty-print String representation * of the given graph, with each new line starting * indented at least the given number of spaces. * @return pretty-print String representation * of the given graph */ public static <T> String prettyPrint(Graph<T> graph, int offset) { assert offset>=0; final StringBuffer result = new StringBuffer(); final String indent = repeat(" ", offset); for(T o : graph) { result.append("\n"); result.append(indent); result.append(o); for(Iterator<?> itr = graph.getSuccNodes(o); itr.hasNext(); ) { result.append("\n" + indent + " --> " + itr.next()); } result.append(",\n"); } result.delete(result.length()-2, result.length()); return result.toString(); } /** * Returns a pretty-print String representation * of the given IR, with each new line starting * indented at least the given number of spaces. * @return pretty-print String representation * of the given IR */ public static String prettyPrint(IR ir, int offset) { return indent(ir.toString(), offset); /* final StringBuffer result = new StringBuffer(); result.append("\n"+indent+"CFG:\n"); final SSACFG cfg = ir.getControlFlowGraph(); for (int i = 0; i <= cfg.getNumber(cfg.exit()); i++) { BasicBlock bb = cfg.getNode(i); result.append(indent+"BB").append(i).append("[").append(bb.getFirstInstructionIndex()).append("..").append(bb.getLastInstructionIndex()) .append("]\n"); Iterator<ISSABasicBlock> succNodes = cfg.getSuccNodes(bb); while (succNodes.hasNext()) { result.append(indent+" -> BB").append(((BasicBlock) succNodes.next()).getNumber()).append("\n"); } } result.append(indent+"Instructions:\n"); for (int i = 0; i <= cfg.getMaxNumber(); i++) { BasicBlock bb = cfg.getNode(i); int start = bb.getFirstInstructionIndex(); int end = bb.getLastInstructionIndex(); result.append(indent+"BB").append(bb.getNumber()); if (bb instanceof ExceptionHandlerBasicBlock) { result.append(indent+"<Handler>"); } result.append("\n"); final SymbolTable symbolTable = ir.getSymbolTable(); for (Iterator<SSAPhiInstruction> it = bb.iteratePhis(); it.hasNext();) { SSAPhiInstruction phi = it.next(); if (phi != null) { result.append(indent+" " + phi.toString(symbolTable)).append("\n"); } } if (bb instanceof ExceptionHandlerBasicBlock) { ExceptionHandlerBasicBlock ebb = (ExceptionHandlerBasicBlock) bb; SSAGetCaughtExceptionInstruction s = ebb.getCatchInstruction(); if (s != null) { result.append(indent+" " + s.toString(symbolTable)).append("\n"); } else { result.append(indent+" " + " No catch instruction. Unreachable?\n"); } } final SSAInstruction[] instructions = ir.getInstructions(); for (int j = start; j <= end; j++) { if (instructions[j] != null) { StringBuffer x = new StringBuffer(indent+j + " " + instructions[j].toString(symbolTable)); StringStuff.padWithSpaces(x, 45); result.append(indent+x); result.append("\n"); } } for (Iterator<SSAPiInstruction> it = bb.iteratePis(); it.hasNext();) { SSAPiInstruction pi = it.next(); if (pi != null) { result.append(indent+" " + pi.toString(symbolTable)).append("\n"); } } } return result.toString(); */ } /** * Returns a pretty-print String representation * of the given IR. * @return pretty-print String representation * of the given IR */ public static String prettyPrint(IR ir) { return prettyPrint(ir,0); } /** * Returns a pretty-print String representation * of the given collection. * @return pretty-print String representation * of the given collection */ public static String prettyPrint(Collection<?> c) { return prettyPrint(c,0); } /** * Returns a pretty-print String representation * of the given collection, with each new line starting * indented at least the given number of spaces. * @return pretty-print String representation * of the given collection */ public static String prettyPrint(Collection<?> c, int offset) { assert offset>=0; final StringBuffer result = new StringBuffer(); final String indent = repeat(" ", offset); for(Object o : c) { result.append(indent); result.append(o); result.append("\n"); } return result.toString(); } /** * Returns a String representation of the position in the source of the given method corresponding * to the instruction at the specified index, or the empty string if the line is unknown. * @return a String representation of the position in the source of the given method corresponding * to the instruction at the specified index, or the empty string if the line is unknown. */ public static final String line(IMethod method, int instructionIndex) { if (instructionIndex>=0) { if (method instanceof ShrikeBTMethod) { try { return String.valueOf(method.getLineNumber(((ShrikeBTMethod)method).getBytecodeIndex(instructionIndex))); } catch (InvalidClassFileException e) { } // ignore } else if (method instanceof AstMethod) { final Position pos = ((AstMethod)method).getSourcePosition(instructionIndex); if (pos!=null) return String.valueOf(pos.getFirstLine()); } } return ""; } /** * Returns a map from each InlinedInstruction in the given set to a unique name. * The names are constructed from the names of the concrete instructions wrapped * in each inlined instruction. Short type names are used whenever possible. * @return a map from each InlinedInstruction in the given set to a unique name. */ public static Map<InlinedInstruction, String> instructionNames(Set<InlinedInstruction> insts) { final Map<CGNode,String> methodNames = nodeNames(Programs.relevantMethods(insts)); final Map<String, List<InlinedInstruction>> name2Inst = new LinkedHashMap<String, List<InlinedInstruction>>(); final Map<InlinedInstruction, String> inst2Name = new LinkedHashMap<InlinedInstruction, String>(); for(InlinedInstruction inst : insts) { final String m = methodNames.get(inst.cgNode()); final String infix; final int idx = inst.instructionIndex(); if (idx==Integer.MIN_VALUE) { infix = "start"; } else if (idx==Integer.MAX_VALUE) { infix = "end"; } else { final String cname = "";//inst.instruction().getClass().getSimpleName().replaceAll("SSA", "").replaceAll("Instruction", ""); infix = cname+idx; } final String name = m + "[" + infix + "]"; // m+"_"+infix; List<InlinedInstruction> named = name2Inst.get(name); if (named==null) { named = new ArrayList<InlinedInstruction>(3); name2Inst.put(name, named); } named.add(inst); } for(Map.Entry<String, List<InlinedInstruction>> entry : name2Inst.entrySet()) { final List<InlinedInstruction> named = entry.getValue(); if (named.size()==1) { inst2Name.put(named.get(0), entry.getKey()); } else { for(InlinedInstruction inst : named) { final StringBuilder b = new StringBuilder(); assert !inst.callStack().empty(); final Iterator<CallSite> itr = inst.callStack().iterator(); b.append(methodNames.get(itr.next().getNode())); while(itr.hasNext()) { b.append("_" + methodNames.get(itr.next().getNode())); } b.append("_" + entry.getKey()); inst2Name.put(inst, b.toString()); } } } return inst2Name; } /** * Returns a map from each CGNode in the given set to a unique name derived * from the signature of that node. Short type names are used whenever possible. * @return a map from each CGNode in the given set to a unique name derived * from the signature of that node. */ public static Map<CGNode, String> nodeNames(Set<CGNode> nodes) { final Map<String, List<CGNode>> name2Node = new LinkedHashMap<String, List<CGNode>>(); final Map<CGNode,String> node2Name = new LinkedHashMap<CGNode, String>(); for(CGNode ref : nodes) { final String name = ref.getMethod().getName().toString(); List<CGNode> named = name2Node.get(name); if (named==null) { named = new ArrayList<CGNode>(3); name2Node.put(name, named); } named.add(ref); } for(Map.Entry<String,List<CGNode>> entry: name2Node.entrySet()) { final List<CGNode> named = entry.getValue(); if (named.size()==1) { node2Name.put(named.get(0), entry.getKey()); } else { for(CGNode ref : named) { node2Name.put(ref, ref.getMethod().getSignature()); } } } return node2Name; } /** * Returns a map from each InstanceKey in the given set to a unique name. * The names are constructed from the names of the concrete types represented * by each instance key. If there is more than one instance key for the same * type, unique suffixes are appended to make the names unique. Short type names * are used whenever possible. * @return a map from each InstanceKey in the given set to a unique name. */ public static Map<InstanceKey, String> instanceNames(Set<InstanceKey> instances) { final Map<TypeName, List<InstanceKey>> nameToKey = new LinkedHashMap<TypeName,List<InstanceKey>>(); final Map<String, Boolean> uniqueShort = new HashMap<String,Boolean>(); final Map<InstanceKey, String> keyToName = new LinkedHashMap<InstanceKey,String>(); for(InstanceKey key : instances) { final TypeName fullName = key.getConcreteType().getName(); List<InstanceKey> named = nameToKey.get(fullName); if (named==null) { named = new ArrayList<InstanceKey>(3); nameToKey.put(fullName, named); } named.add(key); } for(TypeName fullName : nameToKey.keySet()) { final String shortName = fullName.getClassName().toString(); final Boolean unique = uniqueShort.get(shortName); if (unique==null) { uniqueShort.put(shortName, Boolean.TRUE); } else { uniqueShort.put(shortName, Boolean.FALSE); } } for(Map.Entry<TypeName, List<InstanceKey>> entry : nameToKey.entrySet()) { final TypeName fullName = entry.getKey(); final List<InstanceKey> named = entry.getValue(); final String shortName = fullName.getClassName().toString(); final String name = uniqueShort.get(shortName) ? shortName : fullName.toString(); final int size = named.size(); if (size==1) { keyToName.put(named.get(0), name); } else { for(int i = 0; i < size; i++) { keyToName.put(named.get(i), name + i); } } } assert keyToName.size() == instances.size(); return keyToName; } /** * Returns a map from each IField in the given set to a unique name. * The names are constructed from the names of the fields represented * by IField. Short field names are used whenever possible. * @return a map from each IField in the given set to a unique name. */ public static Map<IField, String> fieldNames(Set<IField> fields) { final Map<String, List<IField>> name2Field = new LinkedHashMap<String, List<IField>>(); final Map<IField,String> field2Name = new LinkedHashMap<IField, String>(); for(IField field : fields) { final String name = field.getName().toString(); List<IField> named = name2Field.get(name); if (named==null) { named = new ArrayList<IField>(3); name2Field.put(name, named); } named.add(field); } for(Map.Entry<String,List<IField>> entry: name2Field.entrySet()) { final List<IField> named = entry.getValue(); if (named.size()==1) { field2Name.put(named.get(0), entry.getKey()); } else { for(IField field : named) { field2Name.put(field, field.getReference().getSignature()); } } } return field2Name; } /** * Returns a map from each PointerKey in the given set to a unique name. * The names are constructed from the names of the concrete fields represented * by each pointer key. If there is more than one pointer key for the same * field, unique suffixes are appended to make the names unique. Short field names * are used whenever possible. * @requires fields in InstanceFieldKey + StaticFieldKey + ArrayContentsKey + ArrayLengthKey + EnclosingObjectReferenceKey * @return a map from each PointerKey in the given set to a unique name. */ public static Map<PointerKey, String> pointerNames(Set<? extends PointerKey> fields) { final Map<Pair<TypeName,String>, List<PointerKey>> nameToKey = new LinkedHashMap<Pair<TypeName, String>,List<PointerKey>>(); final Map<String, Boolean> uniqueShort = new HashMap<String,Boolean>(); final Map<PointerKey, String> keyToName = new LinkedHashMap<PointerKey,String>(); for(PointerKey key : fields) { final TypeName typeName; final String fieldName; if (key instanceof InstanceFieldKey) { final IField field = ((InstanceFieldKey)key).getField(); typeName = field.getDeclaringClass().getName(); fieldName = field.getName().toString(); } else if (key instanceof StaticFieldKey) { final IField field = ((StaticFieldKey)key).getField(); typeName = field.getDeclaringClass().getName(); fieldName = field.getName().toString(); } else if (key instanceof ArrayContentsKey) { typeName = ((ArrayContentsKey)key).getInstanceKey().getConcreteType().getName(); fieldName = ""; } else if (key instanceof ArrayLengthKey) { typeName = ((ArrayLengthKey)key).getInstanceKey().getConcreteType().getName(); fieldName = "length"; } else { typeName = ((EnclosingObjectReferenceKey)key).getInstanceKey().getConcreteType().getName(); fieldName = "@enc"; } final Pair<TypeName,String> fullName = Pair.make(typeName, fieldName); List<PointerKey> named = nameToKey.get(fullName); if (named==null) { named = new ArrayList<PointerKey>(3); nameToKey.put(fullName, named); } named.add(key); } for(Pair<TypeName,String> fullName : nameToKey.keySet()) { final String shortName = fullName.fst.getClassName().toString() + "$" + fullName.snd; final Boolean unique = uniqueShort.get(shortName); if (unique==null) { uniqueShort.put(shortName, Boolean.TRUE); } else { uniqueShort.put(shortName, Boolean.FALSE); } } for(Map.Entry<Pair<TypeName,String>, List<PointerKey>> entry : nameToKey.entrySet()) { final Pair<TypeName,String> fullName = entry.getKey(); final List<PointerKey> named = entry.getValue(); final String shortName = fullName.fst.getClassName().toString() + "$" + fullName.snd; final String name = uniqueShort.get(shortName) ? shortName : fullName.fst + "$" + fullName.snd; final int size = named.size(); if (size==1) { keyToName.put(named.get(0), name); } else { for(int i = 0; i < size; i++) { keyToName.put(named.get(i), name + i); } } } assert keyToName.size() == fields.size(); return keyToName; } }