// // Copyright (C) 2008 United States Government as represented by the // Administrator of the National Aeronautics and Space Administration // (NASA). All Rights Reserved. // // This software is distributed under the NASA Open Source Agreement // (NOSA), version 1.3. The NOSA has been approved by the Open Source // Initiative. See the file NOSA-1.3-JPF at the top of the distribution // directory tree for the complete NOSA document. // // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. // package gov.nasa.jpf.listener; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import gov.nasa.jpf.Config; import gov.nasa.jpf.JPF; import gov.nasa.jpf.PropertyListenerAdapter; import gov.nasa.jpf.report.ConsolePublisher; import gov.nasa.jpf.report.Publisher; import gov.nasa.jpf.search.Search; import gov.nasa.jpf.vm.Path; import gov.nasa.jpf.vm.Transition; import gov.nasa.jpf.vm.VM; /** * listener that monitors path output, matching it against specifications * supplied as text files. Per default, this uses simple line-by-line * regular expression matching, also supporting prefixes by means of * special ". . ." ellipsis patterns. Each file can contain a number of * path output specs, separated by "~~~~~" lines. * * The major purpose of this listener is to verify JPF state spaces, * but it can also be used as a functional property */ public class PathOutputMonitor extends PropertyListenerAdapter { static final String SEPARATOR = "~~~~~"; static final String ELLIPSIS = ". . ."; static Logger log = JPF.getLogger("gov.nasa.jpf.listener.PathOutputMonitor"); public interface PathOutputSpec { boolean add (String spec); boolean matches (String[] output); void printOn (PrintWriter pw); } // simple regular expression matchers (could be a more sophisticated parser) static class RegexOutputSpec implements PathOutputSpec { ArrayList<Pattern> patterns = new ArrayList<Pattern>(); public boolean add (String spec) { try { Pattern p = Pattern.compile(spec); patterns.add(p); } catch (PatternSyntaxException psx) { return false; } return true; } public boolean matches (String[] output) { if ((output != null) && (output.length > 0)) { Iterator<Pattern> it = patterns.iterator(); for (String line : output) { if (it.hasNext()) { Pattern p = it.next(); if (p.toString().equals(ELLIPSIS)) { return true; } Matcher m = p.matcher(line); if (!m.matches()) { return false; } } else { return false; } } return (!it.hasNext() || it.next().toString().equals(ELLIPSIS)); } else { // no output return patterns.isEmpty(); } } public void printOn (PrintWriter pw) { for (Pattern p : patterns) { pw.println(p.toString()); } } } //---- our instance data VM vm; //--- this is where we store the outputs (line-wise) // <2do> not very space efficient List<String[]> pathOutputs = new ArrayList<String[]>(); //--- config options Class<? extends PathOutputSpec> psClass; boolean printOutput; boolean deferOutput; List<PathOutputSpec> anySpecs, allSpecs, noneSpecs; //--- keep track of property violations String errorMsg; List<PathOutputSpec> violatedSpecs; String[] offendingOutput; public PathOutputMonitor (Config config, JPF jpf) { vm = jpf.getVM(); vm.storePathOutput(); jpf.addPublisherExtension(ConsolePublisher.class, this); printOutput = config.getBoolean("pom.print_output", true); deferOutput = config.getBoolean("pom.defer_output", true); psClass = config.getClass("pom.output_spec.class", PathOutputSpec.class); if (psClass == null) { psClass = RegexOutputSpec.class; } anySpecs = loadSpecs(config, "pom.any"); allSpecs = loadSpecs(config, "pom.all"); noneSpecs = loadSpecs(config, "pom.none"); violatedSpecs = new ArrayList<PathOutputSpec>(); } List<PathOutputSpec> loadSpecs(Config conf, String key) { String fname = conf.getString(key); if (fname != null) { File file = new File(fname); if (file.exists()) { return readPathPatterns(file); } else { log.warning("pattern file not found: " + fname); } } return null; } PathOutputSpec createPathOutputSpec() { try { return psClass.newInstance(); } catch (Throwable t) { log.severe("cannot instantiate PathoutputSpec class: " + t.getMessage()); return null; } } List<PathOutputSpec> readPathPatterns (File f){ ArrayList<PathOutputSpec> results = new ArrayList<PathOutputSpec>(); // prefix pattern goes into file try { FileReader fr = new FileReader(f); BufferedReader br = new BufferedReader(fr); PathOutputSpec ps = createPathOutputSpec(); for (String line=br.readLine(); true; line = br.readLine()) { if (line == null) { results.add(ps); break; } if (line.startsWith(SEPARATOR)) { results.add(ps); ps = createPathOutputSpec(); } else { ps.add(line); } } br.close(); } catch (FileNotFoundException fnfx) { return null; } catch (IOException e) { e.printStackTrace(); } return results; } String[] getLines (String output) { ArrayList<String> lines = new ArrayList<String>(); BufferedReader br = new BufferedReader(new StringReader(output)); try { for (String line = br.readLine(); line != null; line = br.readLine()) { lines.add(line); } } catch (IOException iox) { iox.printStackTrace(); } return lines.toArray(new String[lines.size()]); } boolean matchesAny (List<PathOutputSpec> outputSpecs, String[] lines) { for (PathOutputSpec ps : outputSpecs) { if (ps.matches(lines)) { return true; } } errorMsg = "unmatched output"; offendingOutput = lines; return false; } boolean matchesNone (List<PathOutputSpec> outputSpecs, String[] lines) { for (PathOutputSpec ps : outputSpecs) { if (ps.matches(lines)) { errorMsg = "illegal output (matching inverse spec)"; offendingOutput = lines; violatedSpecs.add(ps); return false; } } return true; } boolean matchesAll (List<PathOutputSpec> outputSpecs, List<String[]> outputs) { HashSet<PathOutputSpec> unmatched = new HashSet<PathOutputSpec>(); unmatched.addAll(outputSpecs); for (String[] lines : outputs) { for (PathOutputSpec ps : outputSpecs) { if (ps.matches(lines)) { unmatched.remove(ps); if (unmatched.isEmpty()) { return true; } } } } errorMsg = "unmatched specs (" + unmatched.size() + ')'; for (PathOutputSpec ps : unmatched) { violatedSpecs.add(ps); } return false; } //----------- the listener interface @Override public boolean check(Search search, VM vm) { return (errorMsg == null); } public String getErrorMessage () { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); pw.println(errorMsg); if (offendingOutput != null) { pw.println("offending output:"); for (String line : offendingOutput) { pw.println(line); } } if (!violatedSpecs.isEmpty()) { pw.println("violated specs:"); for (PathOutputSpec ps : violatedSpecs) { ps.printOn(pw); pw.println(SEPARATOR); } } String s = sw.toString(); pw.close(); return s; } @Override public void reset () { errorMsg = null; violatedSpecs.clear(); offendingOutput = null; } @Override public void stateAdvanced(Search search) { if (search.isEndState()) { Path path = vm.getPath(); if (path.hasOutput()) { StringBuilder sb = null; if (deferOutput || (noneSpecs != null)) { sb = new StringBuilder(); for (Transition t : path) { String s = t.getOutput(); if (s != null){ sb.append(s); } } } String[] lines = getLines(sb.toString()); if (deferOutput) { pathOutputs.add(lines); } else if (printOutput){ for (Transition t : path) { String s = t.getOutput(); if (s != null){ System.out.print(s); // <2do> don't use System.out } } } // check safety properties if (noneSpecs != null && !matchesNone(noneSpecs, lines)) { log.warning("pom.none violated"); } if (anySpecs != null && !matchesAny(anySpecs, lines)) { log.warning("pom.any violated"); } } } } @Override public void searchFinished (Search search) { if (allSpecs != null && !matchesAll(allSpecs, pathOutputs)) { log.warning("pom.all violated"); search.error(this); } } @Override public void publishFinished (Publisher publisher) { if (printOutput) { PrintWriter pw = publisher.getOut(); publisher.publishTopicStart("path outputs"); for (String[] output : pathOutputs) { for (String line : output) { pw.println(line); } pw.println(SEPARATOR); } } } }