001package daikon.split; 002 003import daikon.split.misc.CallerContextSplitter; 004import java.io.File; 005import java.io.IOException; 006import java.io.UncheckedIOException; 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.Collection; 010import java.util.HashMap; 011import java.util.LinkedHashMap; 012import java.util.List; 013import java.util.Map; 014import java.util.Set; 015import java.util.StringTokenizer; 016import java.util.TreeSet; 017import java.util.logging.Logger; 018import org.checkerframework.checker.lock.qual.GuardSatisfied; 019import org.checkerframework.checker.nullness.qual.KeyFor; 020import org.checkerframework.dataflow.qual.SideEffectFree; 021import org.plumelib.util.EntryReader; 022 023/** 024 * This factory creates Splitters from map files. The splitters partition the data based upon the 025 * the caller (i.e., which static callgraph edge was taken). 026 */ 027public class ContextSplitterFactory { 028 /** Debug tracer. */ 029 public static final Logger debug = Logger.getLogger("daikon.split.ContextSplitterFactory"); 030 031 /** Callsite granularity at the line level. */ 032 public static final int GRAIN_LINE = 0; 033 034 /** Callsite granularity at the method level. */ 035 public static final int GRAIN_METHOD = 1; 036 037 /** Callsite granularity at the class level. */ 038 public static final int GRAIN_CLASS = 2; 039 040 // Variables starting with dkconfig_ should only be set via the 041 // daikon.config.Configuration interface. 042 /** 043 * Enumeration (integer). Specifies the granularity to use for callsite splitter processing. (That 044 * is, for creating invariants for a method that are dependent on where the method was called 045 * from.) 0 is line-level granularity; 1 is method-level granularity; 2 is class-level 046 * granularity. 047 */ 048 public static int dkconfig_granularity = GRAIN_METHOD; 049 050 /** 051 * Read all the map files in the given collection, create callsite splitters from them, and put 052 * the splitters into SplitterList. 053 * 054 * @param files set of File objects to read from 055 * @param grain one of the GRAIN constants defined in this class 056 */ 057 public static void load_mapfiles_into_splitterlist(Collection<File> files, int grain) { 058 for (File file : files) { 059 String filename = file.getName(); 060 061 System.out.print("."); // show progress 062 debug.fine("Reading mapfile " + filename); 063 064 PptNameAndSplitters[] splitters; 065 try { 066 MapfileEntry[] entries = parse_mapfile(file); 067 splitters = make_context_splitters(entries, grain); 068 } catch (IOException e) { 069 throw new UncheckedIOException("problem reading " + file, e); 070 } 071 072 for (int j = 0; j < splitters.length; j++) { 073 PptNameAndSplitters nas = splitters[j]; 074 SplitterList.put(nas.ppt_name, nas.splitters); 075 } 076 } 077 } 078 079 /** Simple record type to store a map file entry. */ 080 public static final class MapfileEntry { 081 public final long id; 082 public final String fromclass; 083 public final String frommeth; 084 public final String fromfile; 085 public final long fromline; 086 public final long fromcol; 087 public final String toexpr; 088 public final String toargs; 089 public final String toclass; 090 public final String tometh; 091 092 public MapfileEntry( 093 long id, 094 String fromclass, 095 String frommeth, 096 String fromfile, 097 long fromline, 098 long fromcol, 099 String toexpr, 100 String toargs, 101 String toclass, 102 String tometh) { 103 this.id = id; 104 this.fromclass = fromclass; 105 this.frommeth = frommeth; 106 this.fromfile = fromfile; 107 this.fromline = fromline; 108 this.fromcol = fromcol; 109 this.toexpr = toexpr; 110 this.toargs = toargs; 111 this.toclass = toclass; 112 this.tometh = tometh; 113 } 114 } 115 116 /** Read and parse a map file. */ 117 public static MapfileEntry[] parse_mapfile(File mapfile) throws IOException { 118 ArrayList<MapfileEntry> result = new ArrayList<>(); 119 120 try (EntryReader er = new EntryReader(mapfile.toString())) { 121 for (String reader_line : er) { 122 String line = reader_line; 123 // Remove comments, skip blank lines 124 { 125 int hash = line.indexOf('#'); 126 if (hash >= 0) { 127 line = line.substring(0, hash); 128 } 129 line = line.trim(); 130 if (line.length() == 0) { 131 continue; 132 } 133 } 134 135 // Example line: 136 // 0x85c2e8c PC.RPStack get [PC/RPStack.java:156:29] -> "getCons" [(I)LPC/Cons;] PC.RP meth 137 // where this ^ is a tab and the rest are single spaces 138 long id; 139 String fromclass, frommeth, fromfile; 140 long fromline, fromcol; 141 String toexpr, toargs, toclass, tometh; 142 143 int tab = line.indexOf('\t'); 144 int arrow = line.indexOf(" -> "); 145 assert tab >= 0; 146 assert arrow >= tab; 147 148 id = Long.decode(line.substring(0, tab)).longValue(); 149 150 // parse "called from" data 151 { 152 StringTokenizer tok = new StringTokenizer(line.substring(tab + 1, arrow)); 153 fromclass = tok.nextToken(); 154 frommeth = tok.nextToken(); 155 String temp = tok.nextToken(); 156 assert temp.startsWith("["); 157 assert temp.endsWith("]"); 158 temp = temp.substring(1, temp.length() - 1); 159 int one = temp.indexOf(':'); 160 int two = temp.lastIndexOf(':'); 161 fromfile = temp.substring(0, one); 162 fromline = Integer.parseInt(temp.substring(one + 1, two)); 163 fromcol = Integer.parseInt(temp.substring(two + 1)); 164 assert !tok.hasMoreTokens(); 165 } 166 167 // parse "call into" data 168 { 169 String to = line.substring(arrow + 4); // 4: " -> " 170 assert to.startsWith("\"") : to; 171 int endquote = to.indexOf("\" ", 1); 172 toexpr = line.substring(1, endquote); 173 StringTokenizer tok = new StringTokenizer(to.substring(endquote + 1)); 174 toargs = tok.nextToken(); 175 toclass = tok.nextToken(); 176 tometh = tok.nextToken(); 177 assert !tok.hasMoreTokens(); 178 } 179 180 MapfileEntry entry = 181 new MapfileEntry( 182 id, fromclass, frommeth, fromfile, fromline, fromcol, toexpr, toargs, toclass, 183 tometh); 184 185 result.add(entry); 186 } 187 } catch (NumberFormatException e) { 188 throw (IOException) new IOException("Malformed number").initCause(e); 189 } 190 191 return result.toArray(new MapfileEntry[0]); 192 } 193 194 /** 195 * Given map file data, create splitters given the requested granularity. 196 * 197 * @param grain one of the GRAIN constants defined in this class 198 */ 199 public static PptNameAndSplitters[] make_context_splitters(MapfileEntry[] entries, int grain) { 200 // Use a 2-deep map structure. First key is an identifier 201 // (~pptname) for the callee. Second key is an idenfier for the 202 // caller (based on granularity). The value is a set of Integers 203 // giving the ids that are associated with that callgraph edge. 204 Map<String, Map<String, Set<Long>>> callee2caller2ids = new HashMap<>(); 205 206 // For each entry 207 for (int i = 0; i < entries.length; i++) { 208 MapfileEntry entry = entries[i]; 209 String callee_ppt_name = entry.toclass + "." + entry.tometh; 210 211 // Compute the caller based on granularity 212 String caller_condition; 213 switch (grain) { 214 case GRAIN_LINE: 215 caller_condition = 216 "<Called from " 217 + entry.fromclass 218 + "." 219 + entry.frommeth 220 + ":" 221 + entry.fromline 222 + ":" 223 + entry.fromcol 224 + ">"; 225 break; 226 case GRAIN_METHOD: 227 caller_condition = "<Called from " + entry.fromclass + "." + entry.frommeth + ">"; 228 break; 229 case GRAIN_CLASS: 230 caller_condition = "<Called from " + entry.fromclass + ">"; 231 break; 232 default: 233 throw new UnsupportedOperationException("Unknown grain " + grain); 234 } 235 236 // Place the ID into the mapping 237 Map<String, Set<Long>> caller2ids = 238 callee2caller2ids.computeIfAbsent(callee_ppt_name, __ -> new LinkedHashMap<>()); 239 Set<Long> ids = caller2ids.computeIfAbsent(caller_condition, __ -> new TreeSet<Long>()); 240 ids.add(entry.id); 241 } // for all entries 242 243 ArrayList<PptNameAndSplitters> result = new ArrayList<>(); 244 245 // For each callee 246 for (Map.Entry<@KeyFor("callee2caller2ids") String, Map<String, Set<Long>>> ipair : 247 callee2caller2ids.entrySet()) { 248 String callee_ppt_name = ipair.getKey(); 249 Map<String, Set<Long>> caller2ids = ipair.getValue(); 250 251 // 'splitters' collects all splitters for one callee_ppt_name 252 Collection<Splitter> splitters = new ArrayList<Splitter>(); 253 254 // For each caller of that callee 255 for (Map.Entry<@KeyFor("caller2ids") String, Set<Long>> jpair : caller2ids.entrySet()) { 256 String caller_condition = jpair.getKey(); 257 List<Long> ids = new ArrayList<>(jpair.getValue()); 258 259 // Make a splitter 260 long[] ids_array = new long[ids.size()]; 261 for (int k = 0; k < ids_array.length; k++) { 262 ids_array[k] = ids.get(k).longValue(); 263 } 264 265 debug.fine( 266 "Creating splitter for " 267 + callee_ppt_name 268 + " with ids " 269 + ids 270 + " named " 271 + caller_condition); 272 273 Splitter splitter = new CallerContextSplitter(ids_array, caller_condition); 274 splitters.add(splitter); 275 } 276 277 // Collect all splitters for one callee_ppt_name 278 Splitter[] splitters_array = splitters.toArray(new Splitter[0]); 279 result.add(new PptNameAndSplitters(callee_ppt_name, splitters_array)); 280 } 281 282 return result.toArray(new PptNameAndSplitters[0]); 283 } 284 285 /** Simple record type to store a PptName and Splitter array. */ 286 public static final class PptNameAndSplitters { 287 public final String ppt_name; // really more like a regexp 288 public final Splitter[] splitters; 289 290 public PptNameAndSplitters(String ppt_name, Splitter[] splitters) { 291 this.ppt_name = ppt_name; 292 this.splitters = splitters; 293 } 294 295 @SideEffectFree 296 @Override 297 public String toString(@GuardSatisfied PptNameAndSplitters this) { 298 return "PptNameAndSplitters<" + ppt_name + "," + Arrays.asList(splitters).toString() + ">"; 299 } 300 } 301}