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}