-
Notifications
You must be signed in to change notification settings - Fork 63
Expand file tree
/
Copy pathChicory.java
More file actions
613 lines (507 loc) · 21.3 KB
/
Chicory.java
File metadata and controls
613 lines (507 loc) · 21.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
package daikon;
import static java.nio.charset.StandardCharsets.UTF_8;
import daikon.chicory.*;
import daikon.util.RegexUtil;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import org.plumelib.bcelutil.SimpleLog;
import org.plumelib.options.Option;
import org.plumelib.options.Options;
/*>>>
import org.checkerframework.checker.nullness.qual.*;
import org.checkerframework.dataflow.qual.*;
*/
/**
* This is the main class for Chicory which transforms the class files of a program to instrument it
* for Daikon. The instrumentation uses the javaagent switch to java (which allows classes to be
* instrumented as they are loaded). This class parses the command line arguments, starts java with
* the javaagent switch on the target program and if requested starts Daikon on the result.
*/
public class Chicory {
@Option("File in which to put dtrace output")
public static /*@MonotonicNonNull*/ File dtrace_file = null;
/** Also see Daikon's {@code --var-omit-pattern} command-line argument. */
@Option("Omit variables that match this regular expression.")
public static /*@Nullable*/ Pattern omit_var = null;
@Option("Directory in which to create output files")
public static File output_dir = new File(".");
@Option("Depth to examine structure components")
public static int nesting_depth = 2;
@Option("Omit all program points that match")
public static List<Pattern> ppt_omit_pattern = new ArrayList<Pattern>();
@Option("Include only program points that match")
public static List<Pattern> ppt_select_pattern = new ArrayList<Pattern>();
@Option("Decl formatted file containing comparability information")
public static /*@Nullable*/ File comparability_file = null;
/**
* If true, no variable values are printed. Static variables are not initialized yet when the
* routine is entered, and static variable are not necessarily initialized to their final values
* when the routine is exited. These .dtrace entries are purely for the benefit of tools that use
* Chicory for program tracing, to determine when methods are entered and exited.
*/
@Option("Write static initialzer program points")
public static boolean instrument_clinit = false;
@Option("Include variables that are visible under normal java access rules")
public static boolean std_visibility = false;
@Option("Print progress information")
public static boolean verbose = false;
@Option("Print debug information and save instrumented classes")
public static boolean debug = false;
@Option("Print detailed information on which classes are transformed")
public static boolean debug_transform = false;
@Option("Print detailed information on variables being observed")
public static boolean debug_decl_print = false;
@Option("Treat classes that match the regex as boot classes (do not instrument)")
public static /*@Nullable*/ Pattern boot_classes = null;
// Should perhaps permit specifying the heap for the target program and
// for Daikon separately.
@Option("Size of the heap for the target program, and for Daikon if it is run")
public static String heap_size = "1000m";
@Option("Print information about each ppt name as it is created")
public static boolean debug_ppt_names = false;
@Option("Create the new declaration record format")
public static boolean new_decl_format = true;
/**
* Path to java agent jar file that performs the transformation. The "main" procedure is {@link
* ChicoryPremain#premain}.
*/
@Option("Path to the Chicory agent jar file")
public static /*@MonotonicNonNull*/ File premain = null;
/**
* The name of the file to read for a list of pure methods. Should be 1 method per line. Each
* method should be in the same format as format ouput by the purity analysis.
*/
@Option("File of pure methods to use as additional Daikon variables")
public static /*@Nullable*/ File purity_file;
@Option("Directory in which to find configuration files")
public static /*@Nullable*/ File config_dir = null;
// Daikon is run in a separate process
@Option("Run Daikon on the generated data trace file")
public static boolean daikon = false;
@Option("Send trace information to Daikon over a socket")
public static boolean daikon_online = false;
/**
* Specifies Daikon arguments to be used if Daikon is run on a generated trace file or online via
* a socket. If neither {@code --daikon} or {@code --daikon-online} is chosen, this option will
* select {@code --daikon}.
*/
@Option("Specify Daikon arguments for either --daikon or --daikon-online")
public static String daikon_args = "";
@Option("Render linked lists as vectors")
public static boolean linked_lists = true;
@Option("Number of calls after which sampling will begin")
public static int sample_start = 0;
@Option("Enable remote debug")
public static boolean remote_debug = false;
/** Daikon port number. Daikon writes this to stdout when it is started in online mode. */
private static int daikon_port = -1;
/** Thread that copies output from target to our output */
public static /*@MonotonicNonNull*/ StreamRedirectThread out_thread;
/** Thread that copies stderr from target to our stderr */
public static /*@MonotonicNonNull*/ StreamRedirectThread err_thread;
/** starting time (msecs) */
public static long start = System.currentTimeMillis();
/** daikon process for {@code --daikon} command-line option */
// non-null if either daikon==true or daikon_online==true
public static /*@MonotonicNonNull*/ Process daikon_proc;
private static final String traceLimTermString = "DTRACELIMITTERMINATE";
private static final String traceLimString = "DTRACELIMIT";
/** flag to use if we want to turn on the static initialization checks */
public static final boolean checkStaticInit = true;
/** Flag to initiate a purity analysis and use results to create add vars */
private static boolean purityAnalysis = false;
private static final SimpleLog basic = new SimpleLog(false);
/** Synopsis for the chicory command line */
public static final String synopsis = "daikon.Chicory [options] target [target-args]";
/**
* Entry point of Chicory.
*
* @param args see usage for argument descriptions
*/
public static void main(String[] args) {
// Parse our arguments
Options options = new Options(synopsis, Chicory.class);
options.setParseAfterArg(false);
String[] target_args = options.parse(true, args);
boolean ok = check_args(options, target_args);
if (!ok) System.exit(1);
// Turn on basic logging if the debug was selected
basic.enabled = debug;
basic.log("target_args = %s%n", Arrays.toString(target_args));
// Start the target. Pass the same options to the premain as
// were passed here.
Chicory chicory = new Chicory();
chicory.start_target(getOptionsString(options), target_args);
}
// Gross hack, undo when Options package makes the `getOptionsString` method public.
@SuppressWarnings("nullness")
private static String getOptionsString(Options options) {
try {
Method method = options.getClass().getDeclaredMethod("getOptionsString");
method.setAccessible(true);
return (String) method.invoke(options);
} catch (Throwable e) {
throw new Error(e);
}
}
/**
* Check the resulting arguments for legality. Prints a message and returns false if there was an
* error.
*/
public static boolean check_args(Options options, String[] target_args) {
// Make sure arguments have legal values
if (nesting_depth < 0) {
System.out.printf("nesting depth (%d) must not be negative%n", nesting_depth);
options.printUsage();
return false;
}
if (target_args.length == 0) {
System.out.println("target program must be specified");
options.printUsage();
return false;
}
return true;
}
/**
* Return true iff argument was given to run a purity analysis.
*
* <p>You should only call this after parsing arguments.
*/
public static boolean doPurity() {
return purityAnalysis;
}
/** Return true iff a file name was specified to supply pure method names. */
/*@Pure*/
public static /*@Nullable*/ File get_purity_file() {
return purity_file;
}
/**
* Starts the target program with the java agent setup to do the transforms. All java agent
* arguments are passed to it. Our classpath is passed to the new JVM.
*/
void start_target(String premain_args, String[] target_args) {
// Default the trace file name to the <target-program-name>.dtrace.gz
if (dtrace_file == null) {
String target_class = target_args[0].replaceFirst(".*[/.]", "");
dtrace_file = new File(String.format("%s.dtrace.gz", target_class));
premain_args += " --dtrace-file=" + dtrace_file;
}
// Get the current classpath
String cp = System.getProperty("java.class.path");
basic.log("classpath = '%s'\n", cp);
if (cp == null) cp = ".";
// The the separator for items in the class path
String path_separator = System.getProperty("path.separator");
basic.log("path_separator = %s\n", path_separator);
if (path_separator == null) {
path_separator = ";"; // should work for windows at least...
} else if (!RegexUtil.isRegex(path_separator)) {
throw new Daikon.UserError(
"Bad regexp "
+ path_separator
+ " for path.separator: "
+ RegexUtil.regexError(path_separator));
}
// Look for ChicoryPremain.jar along the classpath
if (premain == null) {
String[] cpath = cp.split(path_separator);
for (String path : cpath) {
File poss_premain = new File(path, "ChicoryPremain.jar");
if (poss_premain.canRead()) {
premain = poss_premain;
break;
}
}
}
// If not on the classpath look in ${DAIKONDIR}/java
String daikon_dir = System.getenv("DAIKONDIR");
if (premain == null) {
if (daikon_dir != null) {
String file_separator = System.getProperty("file.separator");
File poss_premain = new File(daikon_dir + file_separator + "java", "ChicoryPremain.jar");
if (poss_premain.canRead()) {
premain = poss_premain;
}
}
}
// If not found, try the daikon.jar file itself
if (premain == null) {
for (String path : cp.split(path_separator)) {
File poss_premain = new File(path);
if (poss_premain.getName().equals("daikon.jar")) {
if (poss_premain.canRead()) {
premain = poss_premain;
}
}
}
}
// If we didn't find a premain, give up
if (premain == null) {
System.err.printf("Can't find ChicoryPremain.jar on the classpath");
if (daikon_dir == null) {
System.err.printf(" and $DAIKONDIR is not set.\n");
} else {
System.err.printf(" or in $DAIKONDIR/java .\n");
}
System.err.printf("It should be found in the directory where Daikon was installed.\n");
System.err.printf("Use the --premain switch to specify its location,\n");
System.err.printf("or change your classpath to include it.\n");
System.exit(1);
}
String dtraceLim, terminate;
dtraceLim = System.getProperty(traceLimString);
terminate = System.getProperty(traceLimTermString);
// Run Daikon if we're in online mode
StreamRedirectThread daikon_err = null;
StreamRedirectThread daikon_out = null;
if (daikon_online) {
runDaikon();
@SuppressWarnings("nullness") // didn't redirect stream, so getter returns non-null
StreamRedirectThread tmp_daikon_err =
new StreamRedirectThread("stderr", daikon_proc.getErrorStream(), System.err);
daikon_err = tmp_daikon_err;
daikon_err.start();
@SuppressWarnings("nullness") // didn't redirect stream, so getter returns non-null
/*@NonNull*/ InputStream daikonStdOut = daikon_proc.getInputStream();
// daikonReader escapes, so it is not closed in this method.
BufferedReader daikonReader = new BufferedReader(new InputStreamReader(daikonStdOut, UTF_8));
// Examine up to 100 lines of Daikon output, looking for
// the "DaikonChicoryOnlinePort=" line. Note that if file progress
// is turned on in Daikon, it may be preceded by a timestamp.
for (int i = 0; i < 100; i++) {
String line;
try {
line = daikonReader.readLine();
} catch (IOException e1) {
System.out.printf("Exception reading output from Daikon: %s%n", e1);
line = null;
}
if (line == null) {
throw new RuntimeException("Did not receive socket port from Daikon!");
} else {
System.out.println(line);
if (line.contains("DaikonChicoryOnlinePort=")) {
String portStr = line.replaceFirst(".*DaikonChicoryOnlinePort=", "");
daikon_port = Integer.decode(portStr);
System.out.println("GOT PORT STRING " + daikon_port);
break;
}
}
}
if (daikon_port == -1) {
throw new RuntimeException("After 100 lines of output, Daikon port not received");
}
// continue reading daikon output in separate thread
daikon_out = new StreamRedirectThread("stdout", daikonStdOut, System.out);
daikon_out.start();
}
// Build the command line to execute the target with the javaagent
List<String> cmdlist = new ArrayList<String>();
cmdlist.add("java");
if (remote_debug) {
// -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=4142,suspend=n
cmdlist.add("-agentlib:jdwp=transport=dt_socket,server=y,address=8000,suspend=y");
// cmdlist.add("-Xdebug -Xrunjdwp:server=n,transport=dt_socket,address=8000,suspend=y");
// cmdlist.add("-Xdebug -Xnoagent
// -Xrunjdwp:transport=dt_socket,server=n,suspend=n,address=8000 -Djava.compiler=NONE");
}
cmdlist.add("-cp");
cmdlist.add(cp);
cmdlist.add("-ea");
cmdlist.add("-Xmx" + heap_size);
// cmdlist.add ("-verbose");
if (dtraceLim != null) cmdlist.add("-D" + traceLimString + "=" + dtraceLim);
if (terminate != null) cmdlist.add("-D" + traceLimTermString + "=" + terminate);
// Specify the port to use to talk to Daikon if in online mode
if (daikon_online) {
assert daikon_port != -1 : daikon_port;
premain_args += " --daikon-port " + daikon_port;
}
cmdlist.add(String.format("-javaagent:%s=%s", premain, premain_args));
for (String target_arg : target_args) {
cmdlist.add(target_arg);
}
if (verbose) System.out.printf("\nExecuting target program: %s\n", args_to_string(cmdlist));
String[] cmdline = cmdlist.toArray(new String[cmdlist.size()]);
// Execute the command, sending all output to our streams
java.lang.Runtime rt = java.lang.Runtime.getRuntime();
Process chicory_proc = null;
try {
chicory_proc = rt.exec(cmdline);
} catch (Exception e) {
System.out.printf("Exception '%s' while executing '%s'\n", e, cmdline);
System.exit(1);
}
@SuppressWarnings("nullness") // didn't redirect stream, so getter returns non-null
StreamRedirectThread stdin_thread =
new StreamRedirectThread("stdin", System.in, chicory_proc.getOutputStream(), false);
stdin_thread.start();
int targetResult = redirect_wait(chicory_proc);
if (daikon) {
// Terminate if target didn't end properly
if (targetResult != 0) {
System.out.printf(
"Warning: Did not run Daikon because target exited with %d status\n", targetResult);
System.exit(targetResult);
}
runDaikon();
int daikonResult = waitForDaikon();
System.exit(daikonResult);
} else if (daikon_online) {
assert daikon_proc != null
: "@AssumeAssertion(nullness): conditional: just tested daikon_online, and ran runDaikon() earlier in this method";
if (targetResult != 0) {
System.out.printf("Warning: Target exited with %d status\n", targetResult);
}
// Wait for the process to terminate and return the results
int daikonResult = 0; // initialized to nonsense value to suppress compiler warning
while (true) {
try {
daikonResult = daikon_proc.waitFor();
break;
} catch (InterruptedException e) {
System.out.printf("unexpected interrupt %s while waiting for target to finish", e);
}
}
// Make sure all output is forwarded before we finish
try {
assert daikon_err != null
: "@AssumeAssertion(nullness): dependent: because daikon_online is true";
assert daikon_out != null
: "@AssumeAssertion(nullness): dependent: because daikon_online is true";
daikon_err.join();
daikon_out.join();
} catch (InterruptedException e) {
System.out.printf("unexpected interrupt %s while waiting for threads to join", e);
}
if (daikonResult != 0) {
System.out.printf("Warning: Daikon exited with %d status\n", daikonResult);
}
System.exit(daikonResult);
} else {
// No daikon command specified, so just exit
if (targetResult != 0) {
System.out.printf("Warning: Target exited with %d status\n", targetResult);
}
System.exit(targetResult);
}
}
/** Runs daikon either online or on the generated trace file. */
/*@EnsuresNonNull("daikon_proc")*/
public void runDaikon() {
java.lang.Runtime rt = java.lang.Runtime.getRuntime();
// Get the current classpath
String cp = System.getProperty("java.class.path");
if (cp == null) cp = ".";
String cmdstr;
if (daikon_online) {
cmdstr =
String.format("java -Xmx%s -cp %s -ea daikon.Daikon %s +", heap_size, cp, daikon_args);
} else {
cmdstr =
String.format(
"java -Xmx%s -cp %s -ea daikon.Daikon %s %s/%s",
heap_size, cp, daikon_args, output_dir, dtrace_file);
}
if (remote_debug) {
cmdstr =
cmdstr.replace(
"java", "java -agentlib:jdwp=transport=dt_socket,server=y,address=8001,suspend=y");
}
// System.out.println("daikon command is " + daikon_cmd);
// System.out.println("daikon command cmdstr " + cmdstr);
if (verbose) System.out.printf("\nExecuting daikon: %s\n", cmdstr);
try {
daikon_proc = rt.exec(cmdstr);
} catch (Exception e) {
System.out.printf("Exception '%s' while executing '%s'\n", e, cmdstr);
System.exit(1);
}
}
/** Wait for daikon to complete and return its exit status */
/*@RequiresNonNull("daikon_proc")*/
private int waitForDaikon() {
int result = redirect_wait(daikon_proc);
return result;
}
/** Wait for stream redirect threads to complete and return its exit status */
public int redirect_wait(Process p) {
// Create the redirect theads and start them
@SuppressWarnings("nullness") // didn't redirect stream, so getter returns non-null
StreamRedirectThread err_thread =
new StreamRedirectThread("stderr", p.getErrorStream(), System.err);
@SuppressWarnings("nullness") // didn't redirect stream, so getter returns non-null
StreamRedirectThread out_thread =
new StreamRedirectThread("stdout", p.getInputStream(), System.out);
err_thread.start();
out_thread.start();
// Wait for the process to terminate and return the results
int result = -1;
while (true) {
try {
result = p.waitFor();
break;
} catch (InterruptedException e) {
System.out.printf("unexpected interrupt %s while waiting for target to finish", e);
}
}
// Make sure all output is forwarded before we finish
try {
err_thread.join();
out_thread.join();
} catch (InterruptedException e) {
System.out.printf("unexpected interrupt %s while waiting for threads to join", e);
}
return result;
}
public PrintWriter openFileInDirectory(String fileName, String dirName) {
PrintWriter outFile = null;
try {
if (dirName != null) {
File directory = new File(dirName);
// make the output directory if non-existent
if (!directory.exists()) directory.mkdir();
}
outFile = new PrintWriter(new File(dirName, fileName), UTF_8.name());
} catch (IOException e) {
if (outFile == null) {
throw new RuntimeException("This can't happen.");
}
outFile.close();
throw new Error("File creation of file " + fileName + " failed", e);
}
return outFile;
}
/** Returns elapsed time as a String since the start of the program */
public static String elapsed() {
return ("[" + (System.currentTimeMillis() - start) + " msec]");
}
public static long elapsed_msecs() {
return (System.currentTimeMillis() - start);
}
/** Convert a list of arguments into a command-line string. Only used for debugging output. */
public String args_to_string(List<String> args) {
String str = "";
for (String arg : args) {
if (arg.indexOf(" ") != -1) {
str = "'" + str + "'";
}
str += arg + " ";
}
return (str.trim());
}
// parses the single string into arguments
public String[] parseDaikonArgs(String arg) {
// TODO deal with quotation marks...
return arg.split(" ");
}
}