Mercurial > hg > Members > kono > jpf-core
comparison src/main/gov/nasa/jpf/search/Search.java @ 0:61d41facf527
initial v8 import (history reset)
author | Peter Mehlitz <Peter.C.Mehlitz@nasa.gov> |
---|---|
date | Fri, 23 Jan 2015 10:14:01 -0800 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:61d41facf527 |
---|---|
1 /* | |
2 * Copyright (C) 2014, United States Government, as represented by the | |
3 * Administrator of the National Aeronautics and Space Administration. | |
4 * All rights reserved. | |
5 * | |
6 * The Java Pathfinder core (jpf-core) platform is licensed under the | |
7 * Apache License, Version 2.0 (the "License"); you may not use this file except | |
8 * in compliance with the License. You may obtain a copy of the License at | |
9 * | |
10 * http://www.apache.org/licenses/LICENSE-2.0. | |
11 * | |
12 * Unless required by applicable law or agreed to in writing, software | |
13 * distributed under the License is distributed on an "AS IS" BASIS, | |
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
15 * See the License for the specific language governing permissions and | |
16 * limitations under the License. | |
17 */ | |
18 package gov.nasa.jpf.search; | |
19 | |
20 import gov.nasa.jpf.Config; | |
21 import gov.nasa.jpf.ConfigChangeListener; | |
22 import gov.nasa.jpf.Error; | |
23 import gov.nasa.jpf.JPF; | |
24 import gov.nasa.jpf.JPFException; | |
25 import gov.nasa.jpf.JPFListenerException; | |
26 import gov.nasa.jpf.Property; | |
27 import gov.nasa.jpf.State; | |
28 import gov.nasa.jpf.report.Reporter; | |
29 import gov.nasa.jpf.util.IntVector; | |
30 import gov.nasa.jpf.util.JPFLogger; | |
31 import gov.nasa.jpf.util.Misc; | |
32 import gov.nasa.jpf.vm.VM; | |
33 import gov.nasa.jpf.vm.Path; | |
34 import gov.nasa.jpf.vm.ThreadList; | |
35 import gov.nasa.jpf.vm.Transition; | |
36 | |
37 import java.util.ArrayList; | |
38 import java.util.List; | |
39 import java.util.concurrent.atomic.AtomicBoolean; | |
40 | |
41 /** | |
42 * the mother of all search classes. Mostly takes care of listeners, keeping | |
43 * track of state attributes and errors. This class mainly keeps the | |
44 * general search info like depth, configured properties etc. | |
45 */ | |
46 public abstract class Search { | |
47 | |
48 protected static JPFLogger log = JPF.getLogger("gov.nasa.jpf.search"); | |
49 | |
50 /** error encountered during last transition, null otherwise */ | |
51 protected Error currentError = null; | |
52 protected ArrayList<Error> errors = new ArrayList<Error>(); | |
53 | |
54 protected int depth = 0; | |
55 protected VM vm; | |
56 | |
57 protected ArrayList<Property> properties; | |
58 | |
59 protected boolean matchDepth; | |
60 protected long minFreeMemory; | |
61 protected int depthLimit; | |
62 protected boolean getAllErrors; | |
63 | |
64 // message explaining the last search constraint hit | |
65 protected String lastSearchConstraint; | |
66 | |
67 // these states control the search loop | |
68 protected boolean done = false; | |
69 protected boolean doBacktrack = false; | |
70 | |
71 // do we have a probe request | |
72 protected AtomicBoolean notifyProbeListeners = new AtomicBoolean(false); | |
73 | |
74 /** search listeners. We keep them in a simple array to avoid | |
75 creating objects on each notification */ | |
76 protected SearchListener[] listeners = new SearchListener[0]; | |
77 | |
78 /** this is a special SearchListener that is always notified last, so that | |
79 * PublisherExtensions can be sure the notification has been processed by all listeners */ | |
80 protected Reporter reporter; | |
81 | |
82 protected final Config config; // to later-on access settings that are only used once (not ideal) | |
83 | |
84 // don't forget to unregister or we have a HUGE memory leak if the same Config object is | |
85 // reused for several JPF runs | |
86 class ConfigListener implements ConfigChangeListener { | |
87 | |
88 @Override | |
89 public void propertyChanged(Config config, String key, String oldValue, String newValue) { | |
90 // Different Config instance | |
91 if (!config.equals(Search.this.config)) { | |
92 return; | |
93 } | |
94 | |
95 // Check if Search configuration changed | |
96 if (key.startsWith("search.")){ | |
97 String k = key.substring(7); | |
98 if ("match_depth".equals(k) || | |
99 "min_free".equals(k) || | |
100 "multiple_errors".equals(k)){ | |
101 initialize(config); | |
102 } | |
103 } | |
104 } | |
105 | |
106 @Override | |
107 public void jpfRunTerminated (Config config){ | |
108 config.removeChangeListener(this); | |
109 } | |
110 } | |
111 | |
112 /** storage to keep track of state depths */ | |
113 protected final IntVector stateDepth = new IntVector(); | |
114 | |
115 protected Search (Config config, VM vm) { | |
116 this.vm = vm; | |
117 this.config = config; | |
118 | |
119 initialize( config); | |
120 | |
121 properties = getProperties(config); | |
122 if (properties.isEmpty()) { | |
123 log.severe("no property"); | |
124 } | |
125 | |
126 config.addChangeListener( new ConfigListener()); | |
127 } | |
128 | |
129 protected void initialize( Config conf){ | |
130 depthLimit = conf.getInt("search.depth_limit", Integer.MAX_VALUE); | |
131 matchDepth = conf.getBoolean("search.match_depth"); | |
132 minFreeMemory = conf.getMemorySize("search.min_free", 1024<<10); | |
133 getAllErrors = conf.getBoolean("search.multiple_errors"); | |
134 } | |
135 | |
136 /** | |
137 * called after the JPF run is finished. Shouldn't be public, but is called by JPF | |
138 */ | |
139 public void cleanUp(){ | |
140 // nothing here, the ConfigListener removes itself | |
141 } | |
142 | |
143 public Config getConfig() { | |
144 return config; | |
145 } | |
146 | |
147 public abstract void search (); | |
148 | |
149 public void setReporter(Reporter reporter){ | |
150 this.reporter = reporter; | |
151 } | |
152 | |
153 public void addListener (SearchListener newListener) { | |
154 log.info("SearchListener added: ", newListener); | |
155 listeners = Misc.appendElement(listeners, newListener); | |
156 } | |
157 | |
158 public boolean hasListenerOfType (Class<?> listenerCls) { | |
159 return Misc.hasElementOfType(listeners, listenerCls); | |
160 } | |
161 | |
162 public <T> T getNextListenerOfType(Class<T> type, T prev){ | |
163 return Misc.getNextElementOfType(listeners, type, prev); | |
164 } | |
165 | |
166 | |
167 public void removeListener (SearchListener removeListener) { | |
168 listeners = Misc.removeElement(listeners, removeListener); | |
169 } | |
170 | |
171 | |
172 public void addProperty (Property newProperty) { | |
173 properties.add(newProperty); | |
174 } | |
175 | |
176 public void removeProperty (Property oldProperty) { | |
177 properties.remove(oldProperty); | |
178 } | |
179 | |
180 /** | |
181 * return set of configured properties | |
182 * note there is a name clash here - JPF 'properties' have nothing to do with | |
183 * Java properties (java.util.Properties) | |
184 */ | |
185 protected ArrayList<Property> getProperties (Config config) { | |
186 Class<?>[] argTypes = { Config.class, Search.class }; | |
187 Object[] args = { config, this }; | |
188 | |
189 ArrayList<Property> list = config.getInstances("search.properties", Property.class, | |
190 argTypes, args); | |
191 | |
192 return list; | |
193 } | |
194 | |
195 // check for property violation, return true if not done | |
196 protected boolean hasPropertyTermination () { | |
197 if (currentError != null){ | |
198 if (done){ | |
199 return true; | |
200 } else { // we search for multiple errors, so we ignore and go on | |
201 doBacktrack = true; | |
202 } | |
203 } | |
204 | |
205 return false; | |
206 } | |
207 | |
208 // this should only be called once per transition, otherwise it keeps adding the same error | |
209 protected boolean checkPropertyViolation () { | |
210 for (Property p : properties) { | |
211 if (!p.check(this, vm)) { | |
212 error(p, vm.getClonedPath(), vm.getThreadList()); | |
213 return true; | |
214 } | |
215 } | |
216 | |
217 return false; | |
218 } | |
219 | |
220 public List<Error> getErrors () { | |
221 return errors; | |
222 } | |
223 | |
224 public int getNumberOfErrors(){ | |
225 return errors.size(); | |
226 } | |
227 | |
228 public String getLastSearchConstraint() { | |
229 return lastSearchConstraint; | |
230 } | |
231 | |
232 /** | |
233 * request a probe | |
234 * | |
235 * This does not do the actual listener notification, it only stores | |
236 * the request, which is then processed from within JPFs inner execution loop. | |
237 * As a consequence, probeSearch() can be called async, and searchProbed() listeners | |
238 * don't have to bother with synchronization or inconsistent JPF states (notification | |
239 * happens from within JPFs main thread after a completed Instruction execution) | |
240 */ | |
241 public void probeSearch(){ | |
242 notifyProbeListeners.set(true); | |
243 } | |
244 | |
245 /** | |
246 * this does the actual notification and resets the request, hence this call | |
247 * should only happen from within JPFs main thread | |
248 */ | |
249 public void checkAndResetProbeRequest(){ | |
250 if (notifyProbeListeners.compareAndSet(true, false)){ | |
251 notifySearchProbed(); | |
252 } | |
253 } | |
254 | |
255 /** | |
256 * @return error encountered during *last* transition (null otherwise) | |
257 */ | |
258 public Error getCurrentError(){ | |
259 return currentError; | |
260 } | |
261 | |
262 public Error getLastError() { | |
263 int i=errors.size()-1; | |
264 if (i >=0) { | |
265 return errors.get(i); | |
266 } else { | |
267 return null; | |
268 } | |
269 } | |
270 | |
271 public boolean hasErrors(){ | |
272 return !errors.isEmpty(); | |
273 } | |
274 | |
275 public VM getVM() { | |
276 return vm; | |
277 } | |
278 | |
279 public boolean isEndState () { | |
280 return vm.isEndState(); | |
281 } | |
282 | |
283 public boolean isErrorState(){ | |
284 return (currentError != null); | |
285 } | |
286 | |
287 public boolean hasNextState () { | |
288 return !isEndState(); | |
289 } | |
290 | |
291 public boolean transitionOccurred(){ | |
292 return vm.transitionOccurred(); | |
293 } | |
294 | |
295 public boolean isNewState () { | |
296 boolean isNew = vm.isNewState(); | |
297 | |
298 if (matchDepth) { | |
299 int id = vm.getStateId(); | |
300 | |
301 if (isNew) { | |
302 setStateDepth(id, depth); | |
303 } else { | |
304 return depth < getStateDepth(id); | |
305 } | |
306 } | |
307 | |
308 return isNew; | |
309 } | |
310 | |
311 public boolean isVisitedState () { | |
312 return !isNewState(); | |
313 } | |
314 | |
315 public boolean isIgnoredState(){ | |
316 return vm.isIgnoredState(); | |
317 } | |
318 | |
319 public boolean isProcessedState(){ | |
320 return vm.getChoiceGenerator().isProcessed(); | |
321 } | |
322 | |
323 public boolean isDone(){ | |
324 return done; | |
325 } | |
326 | |
327 public int getDepth () { | |
328 return depth; | |
329 } | |
330 | |
331 public String getSearchConstraint () { | |
332 return lastSearchConstraint; | |
333 } | |
334 | |
335 public Transition getTransition () { | |
336 return vm.getLastTransition(); | |
337 } | |
338 | |
339 public int getStateId () { | |
340 return vm.getStateId(); | |
341 } | |
342 | |
343 public int getPurgedStateId () { | |
344 return -1; // a lot of Searches don't purge any states | |
345 } | |
346 | |
347 | |
348 /** | |
349 * this is somewhat redundant to SystemState.setIgnored(), but we don't | |
350 * want to mix the case of overriding state matching with backtracking when | |
351 * searching for multiple errors | |
352 */ | |
353 public boolean requestBacktrack () { | |
354 return doBacktrack = true; | |
355 } | |
356 | |
357 | |
358 protected boolean checkAndResetBacktrackRequest() { | |
359 if (doBacktrack){ | |
360 doBacktrack = false; | |
361 return true; | |
362 } else { | |
363 return false; | |
364 } | |
365 } | |
366 | |
367 public boolean supportsBacktrack () { | |
368 return true; | |
369 } | |
370 | |
371 public boolean supportsRestoreState () { | |
372 // not supported by default | |
373 return false; | |
374 } | |
375 | |
376 public int getDepthLimit () { | |
377 return depthLimit; | |
378 } | |
379 | |
380 public void setDepthLimit(int limit){ | |
381 depthLimit = limit; | |
382 } | |
383 | |
384 protected SearchState getSearchState () { | |
385 return new SearchState(this); | |
386 } | |
387 | |
388 // can be used by SearchListeners to create path-less errors (liveness) | |
389 public void error (Property property) { | |
390 error(property, null, null); | |
391 } | |
392 | |
393 public void error (Property property, Path path, ThreadList threadList) { | |
394 | |
395 if (getAllErrors) { | |
396 // otherwise we are going to overwrite it if we go on | |
397 try { | |
398 property = property.clone(); | |
399 path = path.clone(); | |
400 threadList = (ThreadList) threadList.clone(); // this makes it a snapshot (deep) clone | |
401 } catch (CloneNotSupportedException cnsx){ | |
402 throw new JPFException("failed to clone error information: " + cnsx); | |
403 } | |
404 done = false; | |
405 | |
406 } else { | |
407 done = true; | |
408 } | |
409 | |
410 currentError = new Error(errors.size()+1, property, path, threadList); | |
411 | |
412 errors.add(currentError); | |
413 | |
414 // we should not reset the property until listeners have been notified | |
415 // (the listener might be the property itself, in which case it could get | |
416 // confused if propertyViolated() notifications happen after a reset) | |
417 } | |
418 | |
419 public void resetProperties(){ | |
420 for (Property p : properties) { | |
421 p.reset(); | |
422 } | |
423 } | |
424 | |
425 protected void notifyStateAdvanced () { | |
426 try { | |
427 for (int i = 0; i < listeners.length; i++) { | |
428 listeners[i].stateAdvanced(this); | |
429 } | |
430 if (reporter != null){ | |
431 // reporter always comes last to ensure all listeners have been notified | |
432 reporter.stateAdvanced(this); | |
433 } | |
434 } catch (Throwable t) { | |
435 throw new JPFListenerException("exception during stateAdvanced() notification", t); | |
436 } | |
437 } | |
438 | |
439 protected void notifyStateProcessed() { | |
440 try { | |
441 for (int i = 0; i < listeners.length; i++) { | |
442 listeners[i].stateProcessed(this); | |
443 } | |
444 if (reporter != null){ | |
445 reporter.stateProcessed(this); | |
446 } | |
447 } catch (Throwable t) { | |
448 throw new JPFListenerException("exception during stateProcessed() notification", t); | |
449 } | |
450 } | |
451 | |
452 protected void notifyStateStored() { | |
453 try { | |
454 for (int i = 0; i < listeners.length; i++) { | |
455 listeners[i].stateStored(this); | |
456 } | |
457 if (reporter != null){ | |
458 reporter.stateStored(this); | |
459 } | |
460 } catch (Throwable t) { | |
461 throw new JPFListenerException("exception during stateStored() notification", t); | |
462 } | |
463 } | |
464 | |
465 protected void notifyStateRestored() { | |
466 try { | |
467 for (int i = 0; i < listeners.length; i++) { | |
468 listeners[i].stateRestored(this); | |
469 } | |
470 if (reporter != null){ | |
471 reporter.stateRestored(this); | |
472 } | |
473 } catch (Throwable t) { | |
474 throw new JPFListenerException("exception during stateRestored() notification", t); | |
475 } | |
476 } | |
477 | |
478 protected void notifyStateBacktracked() { | |
479 try { | |
480 for (int i = 0; i < listeners.length; i++) { | |
481 listeners[i].stateBacktracked(this); | |
482 } | |
483 if (reporter != null){ | |
484 reporter.stateBacktracked(this); | |
485 } | |
486 } catch (Throwable t) { | |
487 throw new JPFListenerException("exception during stateBacktracked() notification", t); | |
488 } | |
489 } | |
490 | |
491 protected void notifyStatePurged() { | |
492 try { | |
493 for (int i = 0; i < listeners.length; i++) { | |
494 listeners[i].statePurged(this); | |
495 } | |
496 if (reporter != null){ | |
497 reporter.statePurged(this); | |
498 } | |
499 } catch (Throwable t) { | |
500 throw new JPFListenerException("exception during statePurged() notification", t); | |
501 } | |
502 } | |
503 | |
504 public void notifySearchProbed() { | |
505 try { | |
506 for (int i = 0; i < listeners.length; i++) { | |
507 listeners[i].searchProbed(this); | |
508 } | |
509 if (reporter != null){ | |
510 reporter.searchProbed(this); | |
511 } | |
512 } catch (Throwable t) { | |
513 throw new JPFListenerException("exception during searchProbed() notification", t); | |
514 } | |
515 } | |
516 | |
517 | |
518 protected void notifyPropertyViolated() { | |
519 try { | |
520 for (int i = 0; i < listeners.length; i++) { | |
521 listeners[i].propertyViolated(this); | |
522 } | |
523 if (reporter != null){ | |
524 reporter.propertyViolated(this); | |
525 } | |
526 } catch (Throwable t) { | |
527 throw new JPFListenerException("exception during propertyViolated() notification", t); | |
528 } | |
529 | |
530 // reset properties if getAllErrors is set | |
531 if (getAllErrors){ | |
532 resetProperties(); | |
533 } | |
534 } | |
535 | |
536 protected void notifySearchStarted() { | |
537 try { | |
538 for (int i = 0; i < listeners.length; i++) { | |
539 listeners[i].searchStarted(this); | |
540 } | |
541 if (reporter != null){ | |
542 reporter.searchStarted(this); | |
543 } | |
544 } catch (Throwable t) { | |
545 throw new JPFListenerException("exception during searchStarted() notification", t); | |
546 } | |
547 } | |
548 | |
549 public void notifySearchConstraintHit(String details) { | |
550 try { | |
551 lastSearchConstraint = details; | |
552 for (int i = 0; i < listeners.length; i++) { | |
553 listeners[i].searchConstraintHit(this); | |
554 } | |
555 if (reporter != null){ | |
556 reporter.searchConstraintHit(this); | |
557 } | |
558 } catch (Throwable t) { | |
559 throw new JPFListenerException("exception during searchConstraintHit() notification", t); | |
560 } | |
561 } | |
562 | |
563 protected void notifySearchFinished() { | |
564 try { | |
565 for (int i = 0; i < listeners.length; i++) { | |
566 listeners[i].searchFinished(this); | |
567 } | |
568 if (reporter != null){ | |
569 reporter.searchFinished(this); | |
570 } | |
571 } catch (Throwable t) { | |
572 throw new JPFListenerException("exception during searchFinished() notification", t); | |
573 } | |
574 } | |
575 | |
576 protected boolean forward () { | |
577 currentError = null; | |
578 | |
579 boolean ret = vm.forward(); | |
580 | |
581 checkPropertyViolation(); | |
582 return ret; | |
583 } | |
584 | |
585 protected boolean backtrack () { | |
586 return vm.backtrack(); | |
587 } | |
588 | |
589 public void setIgnoredState (boolean cond) { | |
590 vm.ignoreState(cond); | |
591 } | |
592 | |
593 protected void restoreState (State state) { | |
594 // not supported by default | |
595 } | |
596 | |
597 /** this can be used by listeners to terminate the search */ | |
598 public void terminate () { | |
599 done = true; | |
600 } | |
601 | |
602 protected void setStateDepth (int stateId, int depth) { | |
603 stateDepth.set(stateId, depth + 1); | |
604 } | |
605 | |
606 public int getStateDepth (int stateId) { | |
607 int depthPlusOne = stateDepth.get(stateId); | |
608 if (depthPlusOne <= 0) { | |
609 throw new JPFException("Asked for depth of unvisited state"); | |
610 } else { | |
611 return depthPlusOne - 1; | |
612 } | |
613 } | |
614 | |
615 /** | |
616 * check if we have a minimum amount of free memory left. If not, we rather want to stop in time | |
617 * (with a threshold amount left) so that we can report something useful, and not just die silently | |
618 * with a OutOfMemoryError (which isn't handled too gracefully by most VMs) | |
619 */ | |
620 public boolean checkStateSpaceLimit () { | |
621 Runtime rt = Runtime.getRuntime(); | |
622 | |
623 long avail = rt.freeMemory(); | |
624 | |
625 // we could also just check for a max number of states, but what really | |
626 // limits us is the memory required to store states | |
627 | |
628 if (avail < minFreeMemory) { | |
629 // try to collect first | |
630 rt.gc(); | |
631 avail = rt.freeMemory(); | |
632 | |
633 if (avail < minFreeMemory) { | |
634 // Ok, we give up, threshold reached | |
635 return false; | |
636 } | |
637 } | |
638 | |
639 return true; | |
640 } | |
641 } | |
642 |