forked from typetools/checker-framework-inference
-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathInferenceMain.java
490 lines (422 loc) · 19.4 KB
/
InferenceMain.java
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
package checkers.inference;
import checkers.inference.model.SourceVariableSlot;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.common.basetype.BaseAnnotatedTypeFactory;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.lang.model.element.AnnotationMirror;
import checkers.inference.InferenceOptions.InitStatus;
import checkers.inference.model.AnnotationLocation;
import checkers.inference.model.Constraint;
import checkers.inference.model.ConstraintManager;
import checkers.inference.model.VariableSlot;
import checkers.inference.qual.VarAnnot;
import checkers.inference.util.InferenceUtil;
import checkers.inference.util.JaifBuilder;
import org.checkerframework.javacutil.AnnotationUtils;
import org.checkerframework.javacutil.BugInCF;
import org.checkerframework.javacutil.SystemUtil;
/**
* InferenceMain is the central coordinator to the inference system.
*
* The main method creates an instance of InferenceMain to handle the rest of the inference process.
* This InferenceMain instance is made accessible by the rest of Checker-Framework-Inference through a static method
* getInstance.
* InferenceMain uses the InferrableChecker of the target checker to instantiate components and wire them together.
* It creates and holds instances to the InferenceVisitor, the InferenceAnnotatedTypeFactory, the InferrableChecker, etc.
*
* InferenceMain invokes the Checker-Framework programmatically using the javac api to run the InferenceChecker.
* Checker-Framework runs the InferenceChecker as a normal checker. Since javac is running in the same JVM
* and with the same classloader as InferenceMain, the InferenceChecker can access the static InferenceMain instance.
*
* During its initialization, InferenceChecker uses InferenceMain to get an instance of the InferenceVisitor.
* The Checker-Framework then uses this visitor to type-check the source code. For every compilation unit (source file)
* in the program, the InferenceVisitor scans the AST and generates constraints where each check would have occurred.
* InferenceMain manages a ConstraintManager instance to store all constraints generated.
*
* After the last compilation unit has been scanned by the visitor, the Checker-Framework call completes and
* control returns to InferenceMain. InferenceMain checks the return code of javac.
* The Checker-Framework will return an error if no source files were specified, if the specified source files did not exist,
* or if the source files fail to compile. Error codes for other reasons generally result from bugs in Checker-Framework-Inference;
* inference only generates constraints, it does not enforce type-checking rules.
*
* If the Checker-Framework call does not return an error, Checker-Framework-Inference will then process the generated constraints.
* The constraints are solved using an InferenceSolver and then a JAIF is created to allow insertion of inferred annotations back into the input program.
* InferenceSolver is an interface that all solvers must implement. Checker-Framework-Inference can also serialize the constraints for processing later (by a solver or by Verigames).
*
* In the future, Checker-Framework-Inference might be able to use the inferred annotations for type-checking without first inserting the annotations into the input program.
*
* @author mcarthur
*
*/
public class InferenceMain {
public final Logger logger = Logger.getLogger(InferenceMain.class.getName());
/**
* Return the single instance of this class.
*
* Consumers need an instance to look up
* Visitors/TypeFactories and to use the InferenceRunContext
*
*/
private static InferenceMain inferenceMainInstance;
private InferenceChecker inferenceChecker;
/**
* When we are inferring annotations we do not generate all constraints because
* a type may not yet have it's flow-refined type (and therefore RefinementVariable)
* applied to it. This flag is set to true while flow is being performed.
*
* It is queried with isPerformingFlow. Every location from which this method is
* called is a location we omit from generating constraints during flow.
*/
private boolean performingFlow;
private InferenceVisitor<?, ? extends BaseAnnotatedTypeFactory> visitor;
private InferrableChecker realChecker;
private BaseAnnotatedTypeFactory realTypeFactory;
private InferenceAnnotatedTypeFactory inferenceTypeFactory;
private ConstraintManager constraintManager;
private SlotManager slotManager;
// Hold the results of solving.
private InferenceResult solverResult;
// Turn off some of the checks so that more bodies of code pass.
// Eventually we will get rid of this.
private boolean hackMode;
private ResultHandler resultHandler;
public void setResultHandler(ResultHandler resultHandler) {
this.resultHandler = resultHandler;
}
public static void main(String [] args) {
InitStatus status = InferenceOptions.init(args, false);
status.validateOrExit();
InferenceMain inferenceMain = new InferenceMain();
inferenceMain.run();
}
/**
* Create an InferenceMain instance.
* Options are pulled from InferenceCli static fields.
*/
public InferenceMain() {
if (inferenceMainInstance != null) {
logger.warning("Only a single instance of InferenceMain should ever be created!");
}
inferenceMainInstance = this;
resultHandler = new DefaultResultHandler(logger);
}
public static InferenceMain resetInstance() {
inferenceMainInstance = null;
inferenceMainInstance = new InferenceMain();
return inferenceMainInstance;
}
/**
* Kick off the inference process.
*/
public void run() {
logger.finer("Starting InferenceMain");
// Start up javac
startCheckerFramework();
solve();
// solverResult = null covers case when debug solver is used, but in this case
// shouldn't exit
if (solverResult != null && !solverResult.hasSolution()) {
logger.info("No solution, exiting...");
System.exit(1);
}
writeJaif();
}
/**
* Run the Checker-Framework using InferenceChecker
*/
private void startCheckerFramework() {
List<String> checkerFrameworkArgs = new ArrayList<>(Arrays.asList(
"-processor", "checkers.inference.InferenceChecker",
"-Xmaxwarns", "1000",
"-Xmaxerrs", "1000",
"-XDignore.symbol.file",
"-Awarns"));
if (SystemUtil.getJreVersion() == 8) {
checkerFrameworkArgs.addAll(Arrays.asList("-source", "8", "-target", "8"));
}
if (InferenceOptions.cfArgs != null) {
checkerFrameworkArgs.addAll(parseCfArgs());
}
if (InferenceOptions.logLevel == null) {
InferenceUtil.setLoggingLevel(Level.FINE);
} else {
InferenceUtil.setLoggingLevel(Level.parse(InferenceOptions.logLevel));
}
if (InferenceOptions.hacks) {
hackMode = true;
}
if (InferenceOptions.javacOptions != null) {
checkerFrameworkArgs.addAll(InferenceOptions.javacOptions);
}
if (InferenceOptions.javaFiles != null) {
checkerFrameworkArgs.addAll(Arrays.asList(InferenceOptions.javaFiles));
}
logger.fine(String.format("Starting checker framework with options: %s", checkerFrameworkArgs));
StringWriter javacoutput = new StringWriter();
boolean success = CheckerFrameworkUtil.invokeCheckerFramework(checkerFrameworkArgs.toArray(new String[checkerFrameworkArgs.size()]),
new PrintWriter(javacoutput, true));
resultHandler.handleCompilerResult(success, javacoutput.toString());
}
/**
* Give the InferenceMain instance a reference to the InferenceChecker
* that is being run by Checker-Framework.
*
* @param inferenceChecker The InferenceChecker being run by Checker Framework.
*/
public void recordInferenceCheckerInstance(InferenceChecker inferenceChecker) {
this.inferenceChecker = inferenceChecker;
logger.finer("Received InferenceChecker callback");
}
/**
* Create a jaif file that records the mapping of VariableSlots to their code positions.
* The output file can be configured by the command-line argument jaiffile.
*/
private void writeJaif() {
try (PrintWriter writer
= new PrintWriter(new FileOutputStream(InferenceOptions.jaifFile))) {
List<VariableSlot> varSlots = slotManager.getVariableSlots();
Map<AnnotationLocation, String> values = new HashMap<>();
Set<Class<? extends Annotation>> annotationClasses = new HashSet<>();
if (solverResult == null) {
annotationClasses.add(VarAnnot.class);
} else {
for (Class<? extends Annotation> annotation : realTypeFactory.getSupportedTypeQualifiers()) {
annotationClasses.add(annotation);
}
// add any custom annotations that must be inserted to the JAIF header, such as alias annotations
for (Class<? extends Annotation> annotation : realChecker.additionalAnnotationsForJaifHeaderInsertion()) {
annotationClasses.add(annotation);
}
}
for (VariableSlot slot : varSlots) {
if (slot.getLocation() != null) {
// TODO: String serialization of annotations.
AnnotationMirror annotationToWrite = getAnnotationToWrite(slot);
if (annotationToWrite != null) {
values.put(slot.getLocation(), annotationToWrite.toString());
}
}
}
JaifBuilder builder = new JaifBuilder(values, annotationClasses, realChecker.isInsertMainModOfLocalVar());
String jaif = builder.createJaif();
writer.println(jaif);
} catch (Exception e) {
logger.log(Level.SEVERE, "Failed to write out jaif file!", e);
}
}
/**
* Solve the generated constraints using the solver specified on the command line.
*/
private void solve() {
// TODO: PERHAPS ALLOW SOLVERS TO DECIDE IF/HOW THEY WANT CONSTRAINTS NORMALIZED
final ConstraintNormalizer constraintNormalizer = new ConstraintNormalizer();
Set<Constraint> normalizedConstraints = constraintNormalizer.normalize(constraintManager.getConstraints());
// TODO: Support multiple solvers or serialize before or after solving
// TODO: Prune out unneeded variables
// TODO: Options to type-check after this.
if (InferenceOptions.solver != null) {
InferenceSolver solver = getSolver();
this.solverResult = solver.solve(
parseSolverArgs(),
slotManager.getSlots(),
normalizedConstraints,
getRealTypeFactory().getQualifierHierarchy(),
inferenceChecker.getProcessingEnvironment());
}
}
private @Nullable AnnotationMirror getAnnotationToWrite(VariableSlot slot) {
if (!slot.isInsertable()) {
return null;
} else if (solverResult == null) {
// Just use the VarAnnot in the jaif.
return slotManager.getAnnotation(slot);
}
// Not all VariableSlots will have an inferred value.
// This happens for VariableSlots that have no constraints.
AnnotationMirror result = solverResult.getSolutionForVariable(slot.getId());
if (result != null && slot instanceof SourceVariableSlot) {
AnnotationMirror defaultAnnotation = ((SourceVariableSlot) slot).getDefaultAnnotation();
if (defaultAnnotation != null && AnnotationUtils.areSame(defaultAnnotation, result)) {
// Don't need to write a solution that's equivalent to the default annotation.
result = null;
}
}
return result;
}
// ================================================================================
// Component Initialization
// ================================================================================
public InferenceVisitor<?, ? extends BaseAnnotatedTypeFactory> getVisitor() {
if (visitor == null) {
visitor = getRealChecker().createVisitor(inferenceChecker, getInferenceTypeFactory(), true);
logger.finer("Created InferenceVisitor");
}
return visitor;
}
private InferrableChecker getRealChecker() {
if (realChecker == null) {
try {
realChecker = (InferrableChecker) Class.forName(
InferenceOptions.checker, true, ClassLoader.getSystemClassLoader()).getDeclaredConstructor().newInstance();
realChecker.init(inferenceChecker.getProcessingEnvironment());
realChecker.initChecker();
logger.finer(String.format("Created real checker: %s", realChecker));
} catch (Throwable e) {
logger.log(Level.SEVERE, "Error instantiating checker class \"" + InferenceOptions.checker + "\".", e);
System.exit(5);
}
}
return realChecker;
}
private InferenceAnnotatedTypeFactory getInferenceTypeFactory() {
if (inferenceTypeFactory == null) {
inferenceTypeFactory = realChecker.createInferenceATF(inferenceChecker, getRealChecker(),
getRealTypeFactory(), getSlotManager(), getConstraintManager());
this.getConstraintManager().init(inferenceTypeFactory);
logger.finer("Created InferenceAnnotatedTypeFactory");
}
return inferenceTypeFactory;
}
/**
* This method is NOT deprecated but SHOULD NOT BE USED other than in getInferenceTypeFactory AND
* InferenceAnnotatedTypeFactory.getSupportedQualifierTypes. We have made it deprecated in order to bring
* this to the attention of future programmers. We would make it private if it weren't for the fact that
* we need the realTypeFactory qualifiers in getSupportedQualifierTypes and it is called in the super class.
*/
public BaseAnnotatedTypeFactory getRealTypeFactory() {
if (realTypeFactory == null) {
realTypeFactory = getRealChecker().createRealTypeFactory(true);
logger.finer(String.format("Created real type factory: %s", realTypeFactory));
}
return realTypeFactory;
}
public SlotManager getSlotManager() {
if (slotManager == null ) {
Set<? extends AnnotationMirror> tops = realTypeFactory.getQualifierHierarchy().getTopAnnotations();
if (tops.size() != 1) {
throw new BugInCF("Expected 1 real top qualifier, but received %d instead", tops.size());
}
slotManager = new DefaultSlotManager(
inferenceChecker.getProcessingEnvironment(),
tops.iterator().next(),
realTypeFactory.getSupportedTypeQualifiers(),
true
);
logger.finer("Created slot manager" + slotManager);
}
return slotManager;
}
protected InferenceSolver getSolver() {
try {
InferenceSolver solver = (InferenceSolver) Class.forName(
InferenceOptions.solver, true, ClassLoader.getSystemClassLoader()).getDeclaredConstructor().newInstance();
logger.finer("Created solver: " + solver);
return solver;
} catch (Throwable e) {
logger.log(Level.SEVERE, "Error instantiating solver class \"" + InferenceOptions.solver + "\".", e);
System.exit(5);
return null; // Dead code
}
}
/**
* Parse solver-args from a comma separated list of
* key=value pairs into a Map.
* @return Map of string keys and values
*/
private Map<String, String> parseSolverArgs() {
Map<String, String> processed = new HashMap<>();
if (InferenceOptions.solverArgs != null) {
String solverArgs = InferenceOptions.solverArgs;
String[] split = solverArgs.split(",");
for (String part : split) {
int index;
part = part.trim();
if ((index = part.indexOf("=")) > 0) {
processed.put(part.substring(0, index), part.substring(index + 1, part.length()));
} else {
processed.put(part, null);
}
}
}
return processed;
}
/**
* Parse checker framework args from a space separated list of
* -Axxx=xxx,y=y -Azzz=zzz
* @return List of Strings, each string is a checker framework argument
* in the format: -Axxx=xxx,y=y or -Azzz or -Azzz=zzz
*/
private List<String> parseCfArgs() {
List<String> argList = new ArrayList<>();
if (InferenceOptions.cfArgs != null) {
String cfArgs = InferenceOptions.cfArgs;
String[] split = cfArgs.split(" ");
argList.addAll(Arrays.asList(split));
}
return argList;
}
public static InferenceMain getInstance() {
return inferenceMainInstance;
}
public ConstraintManager getConstraintManager() {
if (this.constraintManager == null) {
this.constraintManager = new ConstraintManager();
}
return constraintManager;
}
public boolean isPerformingFlow() {
return performingFlow;
}
public void setPerformingFlow(boolean performingFlow) {
this.performingFlow = performingFlow;
}
public static boolean isHackMode() {
return isHackMode(true);
}
/**
* @param condition if some condition is true, do some sort of hack
*/
public static boolean isHackMode(boolean condition) {
// getInstance is null during type checking.
if (getInstance() != null && getInstance().hackMode && condition) {
StackTraceElement[] traces = Thread.currentThread().getStackTrace();
StackTraceElement hackLocation = traces[2];
if (traces[2].getMethodName().equals("isHackMode")) {
hackLocation = traces[3];
}
getInstance().logger.warning("Encountered hack: " + hackLocation);
return true;
} else {
return false;
}
}
public static abstract interface ResultHandler {
void handleCompilerResult(boolean success, String javacOutStr);
}
protected static class DefaultResultHandler implements ResultHandler {
private final Logger logger;
public DefaultResultHandler(Logger logger) {
this.logger = logger;
}
@Override
public void handleCompilerResult(boolean success, String javacOutStr) {
if (!success) {
logger.severe("Error return code from javac! Quitting.");
logger.info(javacOutStr);
System.exit(1);
}
}
}
}