// // Copyright (C) 2009 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.util.HashMap; import de.fosd.typechef.featureexpr.FeatureExpr; import de.fosd.typechef.featureexpr.FeatureExprFactory; import gov.nasa.jpf.Config; import gov.nasa.jpf.JPF; import gov.nasa.jpf.JPFConfigException; import gov.nasa.jpf.ListenerAdapter; import gov.nasa.jpf.jvm.bytecode.InvokeInstruction; import gov.nasa.jpf.vm.ClassInfo; import gov.nasa.jpf.vm.ClassLoaderInfo; import gov.nasa.jpf.vm.Instruction; import gov.nasa.jpf.vm.MethodInfo; import gov.nasa.jpf.vm.ThreadInfo; import gov.nasa.jpf.vm.Types; import gov.nasa.jpf.vm.VM; /** * listener to inject exceptions according to user specifications. This * tool is meant to be used for exception handler verification, esp. if * exceptions thrown by 3rd party code would be hard to produce. * * Exceptions are specified as a list of xSpec'@'location pairs. * * ExceptionSpec is specified as a class name, with optional details parameter. If no * package is specified, either java.lang or default package are assumed * * Location can be * - class:line * - fully qualified method (callee that is supposed to throw, which is * NOT executed in this case) * - fully qualified method ':' lineOffset * * for line/offest based locations, either the first or last insn associated * with this line (depending on ei.throwFirst=true|false) is not executed * but replaced with throwing the exception. * * Method body line offsets count from the first statement line in the method body * * Examples: * IOException@x.Foobar:42 * NullPointerException@x.SomeClass.computeSomething(Ljava/lang/String;I) * y.MyException("something went wrong")@x.SomeClass.foo(D):10 */ public class ExceptionInjector extends ListenerAdapter { boolean throwFirst; // for location targets, throw on first insn associated with line static class ExceptionEntry { Instruction insn; ExceptionSpec xSpec; Location loc; ExceptionEntry next; // there might be more than one for one class ExceptionEntry (ExceptionSpec xSpec, Location loc, ExceptionEntry next){ this.xSpec = xSpec; this.loc = loc; this.next = next; } String getLocationClassName() { return loc.className; } String getMethod() { return loc.method; } int getLine() { return loc.line; } ClassInfo getExceptionClassInfo(ThreadInfo ti) { return ClassLoaderInfo.getCurrentResolvedClassInfo(xSpec.xClsName); } String getExceptionDetails() { return xSpec.details; } public String toString() { return xSpec.toString() + '@' + loc.toString(); } } static class ExceptionSpec { String xClsName; String details; ExceptionSpec (String xClsName, String details){ this.xClsName = xClsName; this.details = details; } public String toString() { if (details == null){ return xClsName; } else { StringBuilder sb = new StringBuilder(xClsName); sb.append('('); if (!details.isEmpty()){ sb.append('"'); sb.append(details); sb.append('"'); } sb.append(')'); return sb.toString(); } } } static class Location { String className; String method; // name + signature int line; Location (String className, String method, int line){ this.className = className; this.method = method; this.line = line; } public String toString() { StringBuilder sb = new StringBuilder(className); if (method != null){ sb.append('.'); sb.append(method); } if (line >= 0){ sb.append(':'); sb.append(line); } return sb.toString(); } } // these two are used to process classes at loadtime HashMap<String,ExceptionEntry> targetClasses = new HashMap<String,ExceptionEntry>(); HashMap<String,ExceptionEntry> targetBases = new HashMap<String,ExceptionEntry>(); // methods and instructions to watch for at runtime will have ExceptionEntry attrs public ExceptionInjector (Config config, JPF jpf){ throwFirst = config.getBoolean("ei.throw_first", false); String[] xSpecs = config.getStringArray("ei.exception", new char[] {';'}); if (xSpecs != null){ for (String xSpec : xSpecs){ if (!parseException(xSpec)){ throw new JPFConfigException("invalid exception spec: " + xSpec); } } } printEntries(); } boolean parseException (String xSpec){ int i = xSpec.indexOf('@'); if (i > 0){ String typeSpec = xSpec.substring(0, i).trim(); String locSpec = xSpec.substring(i+1).trim(); ExceptionSpec type = parseType(typeSpec); if (type != null){ Location loc = parseLocation(locSpec); if (loc != null){ String cls = loc.className; int line = loc.line; if (line >= 0){ targetClasses.put(cls, new ExceptionEntry(type,loc,targetClasses.get(cls))); } else { targetBases.put(cls, new ExceptionEntry(type,loc,targetBases.get(cls))); } return true; } } } return false; } ExceptionSpec parseType (String spec){ String cls = null; String details = null; int i = spec.indexOf('('); if (i > 0){ cls = spec.substring(0, i); int j = spec.lastIndexOf(')'); if (spec.charAt(i+1) == '"'){ i++; } if (spec.charAt(j-1) == '"'){ j--; } details = spec.substring(i+1, j); if (details.isEmpty()){ details = null; } } else if (i < 0) { // no details cls = spec; } if (cls != null){ return new ExceptionSpec( cls,details); } return null; } Location parseLocation (String spec){ int i = spec.indexOf('('); if (i > 0){ // we have a method name int j = spec.lastIndexOf('.', i); // get class part if (j > 0){ String cls = spec.substring(0, j).trim(); i = spec.indexOf(':'); if (i > 0){ String mth = Types.getSignatureName(spec.substring(j+1, i)); try { int line = Integer.parseInt(spec.substring(i + 1)); if (!cls.isEmpty() && !mth.isEmpty() && line >= 0){ return new Location(cls, mth, line); } } catch (NumberFormatException nfx) { return null; } } else { String mth = Types.getSignatureName(spec.substring(j+1)); return new Location(cls,mth, -1); } } } else { // no method i = spec.indexOf(':'); // but we need a line number if (i > 0){ String cls = spec.substring(0, i).trim(); try { int line = Integer.parseInt(spec.substring(i+1)); if (!cls.isEmpty() && line >= 0){ return new Location (cls, null, line); } } catch (NumberFormatException nfx){ return null; } } } return null; } boolean checkTargetInsn (ExceptionEntry e, MethodInfo mi, int[] ln, int line){ if ((ln[0] <= line) && (ln[ln.length - 1] >= line)) { for (int i = 0; i < ln.length; i++) { if (ln[i] == line) { if (!throwFirst) { while ((i++ < ln.length) && (ln[i] == line)); i--; } mi.getInstruction(i).addAttr(e); return true; } } } return false; } /** * get the target insns/methods */ @Override public void classLoaded (VM vm, ClassInfo loadedClass){ nextClassEntry: for (ExceptionEntry e = targetClasses.get(loadedClass.getName()); e != null; e = e.next){ String method = e.getMethod(); int line = e.getLine(); if (method != null){ // method or method/line-offset for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()){ if (mi.getUniqueName().startsWith(method)){ if (line >= 0){ // line offset int[] ln = mi.getLineNumbers(); line += ln[0]; if (checkTargetInsn(e,mi,ln,line)){ continue nextClassEntry; } } } } } else { // absolute line number if (line >= 0){ for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()) { int[] ln = mi.getLineNumbers(); if (checkTargetInsn(e, mi, ln, line)) { continue nextClassEntry; } } } } } if (targetBases != null){ for (; loadedClass != null; loadedClass = loadedClass.getSuperClass()) { nextBaseEntry: for (ExceptionEntry e = targetBases.get(loadedClass.getName()); e != null; e = e.next){ String method = e.getMethod(); for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()){ if (mi.getUniqueName().startsWith(method)){ mi.addAttr(e); continue nextBaseEntry; } } } } } } @Override public void executeInstruction (FeatureExpr ctx, VM vm, ThreadInfo ti, Instruction insnToExecute){ ExceptionEntry e = insnToExecute.getAttr(ExceptionEntry.class); if ((e == null) && insnToExecute instanceof InvokeInstruction){ MethodInfo mi = ((InvokeInstruction) insnToExecute).getInvokedMethod(); e = mi.getAttr(ExceptionEntry.class); } if (e != null){ Instruction nextInsn = ti.createAndThrowException(FeatureExprFactory.True(), e.getExceptionClassInfo(ti), e.getExceptionDetails()); ti.skipInstruction(nextInsn); return; } } // for debugging purposes void printEntries () { for (ExceptionEntry e : targetClasses.values()){ System.out.println(e); } for (ExceptionEntry e : targetBases.values()){ System.out.println(e); } } /** public static void main (String[] args){ Config conf = JPF.createConfig(args); ExceptionInjector ei = new ExceptionInjector(conf,null); ei.parseException("x.y.Zang(\"bang\")@z.Foo.doit(Ljava/lang/Object;I)"); ei.printEntries(); } **/ }