2 * Copyright (C) 2014, United States Government, as represented by the
3 * Administrator of the National Aeronautics and Space Administration.
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
10 * http://www.apache.org/licenses/LICENSE-2.0.
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.
19 package gov.nasa.jpf.listener;
21 import gov.nasa.jpf.Config;
22 import gov.nasa.jpf.JPF;
23 import gov.nasa.jpf.JPFConfigException;
24 import gov.nasa.jpf.ListenerAdapter;
25 import gov.nasa.jpf.jvm.bytecode.JVMInvokeInstruction;
26 import gov.nasa.jpf.vm.ClassInfo;
27 import gov.nasa.jpf.vm.ClassLoaderInfo;
28 import gov.nasa.jpf.vm.Instruction;
29 import gov.nasa.jpf.vm.VM;
30 import gov.nasa.jpf.vm.MethodInfo;
31 import gov.nasa.jpf.vm.ThreadInfo;
32 import gov.nasa.jpf.vm.Types;
34 import java.util.HashMap;
37 * listener to inject exceptions according to user specifications. This
38 * tool is meant to be used for exception handler verification, esp. if
39 * exceptions thrown by 3rd party code would be hard to produce.
41 * Exceptions are specified as a list of xSpec'@'location pairs.
43 * ExceptionSpec is specified as a class name, with optional details parameter. If no
44 * package is specified, either java.lang or default package are assumed
48 * - fully qualified method (callee that is supposed to throw, which is
49 * NOT executed in this case)
50 * - fully qualified method ':' lineOffset
52 * for line/offest based locations, either the first or last insn associated
53 * with this line (depending on ei.throwFirst=true|false) is not executed
54 * but replaced with throwing the exception.
56 * Method body line offsets count from the first statement line in the method body
59 * IOException@x.Foobar:42
60 * NullPointerException@x.SomeClass.computeSomething(Ljava/lang/String;I)
61 * y.MyException("something went wrong")@x.SomeClass.foo(D):10
64 public class ExceptionInjector extends ListenerAdapter {
66 boolean throwFirst; // for location targets, throw on first insn associated with line
68 static class ExceptionEntry {
73 ExceptionEntry next; // there might be more than one for one class
75 ExceptionEntry (ExceptionSpec xSpec, Location loc, ExceptionEntry next){
81 String getLocationClassName() {
93 ClassInfo getExceptionClassInfo(ThreadInfo ti) {
94 return ClassLoaderInfo.getCurrentResolvedClassInfo(xSpec.xClsName);
97 String getExceptionDetails() {
102 public String toString() {
103 return xSpec.toString() + '@' + loc.toString();
107 static class ExceptionSpec {
111 ExceptionSpec (String xClsName, String details){
112 this.xClsName = xClsName;
113 this.details = details;
117 public String toString() {
118 if (details == null){
121 StringBuilder sb = new StringBuilder(xClsName);
123 if (!details.isEmpty()){
129 return sb.toString();
134 static class Location {
136 String method; // name + signature
139 Location (String className, String method, int line){
140 this.className = className;
141 this.method = method;
146 public String toString() {
147 StringBuilder sb = new StringBuilder(className);
156 return sb.toString();
160 // these two are used to process classes at loadtime
161 HashMap<String,ExceptionEntry> targetClasses = new HashMap<String,ExceptionEntry>();
162 HashMap<String,ExceptionEntry> targetBases = new HashMap<String,ExceptionEntry>();
164 // methods and instructions to watch for at runtime will have ExceptionEntry attrs
167 public ExceptionInjector (Config config, JPF jpf){
168 throwFirst = config.getBoolean("ei.throw_first", false);
169 String[] xSpecs = config.getStringArray("ei.exception", new char[] {';'});
172 for (String xSpec : xSpecs){
173 if (!parseException(xSpec)){
174 throw new JPFConfigException("invalid exception spec: " + xSpec);
182 boolean parseException (String xSpec){
183 int i = xSpec.indexOf('@');
185 String typeSpec = xSpec.substring(0, i).trim();
186 String locSpec = xSpec.substring(i+1).trim();
188 ExceptionSpec type = parseType(typeSpec);
190 Location loc = parseLocation(locSpec);
192 String cls = loc.className;
195 targetClasses.put(cls, new ExceptionEntry(type,loc,targetClasses.get(cls)));
197 targetBases.put(cls, new ExceptionEntry(type,loc,targetBases.get(cls)));
207 ExceptionSpec parseType (String spec){
209 String details = null;
211 int i = spec.indexOf('(');
213 cls = spec.substring(0, i);
215 int j = spec.lastIndexOf(')');
216 if (spec.charAt(i+1) == '"'){
219 if (spec.charAt(j-1) == '"'){
222 details = spec.substring(i+1, j);
223 if (details.isEmpty()){
227 } else if (i < 0) { // no details
232 return new ExceptionSpec( cls,details);
238 Location parseLocation (String spec){
239 int i = spec.indexOf('(');
240 if (i > 0){ // we have a method name
241 int j = spec.lastIndexOf('.', i); // get class part
243 String cls = spec.substring(0, j).trim();
244 i = spec.indexOf(':');
247 String mth = Types.getSignatureName(spec.substring(j+1, i));
250 int line = Integer.parseInt(spec.substring(i + 1));
251 if (!cls.isEmpty() && !mth.isEmpty() && line >= 0){
252 return new Location(cls, mth, line);
254 } catch (NumberFormatException nfx) {
258 String mth = Types.getSignatureName(spec.substring(j+1));
259 return new Location(cls,mth, -1);
263 } else { // no method
264 i = spec.indexOf(':'); // but we need a line number
266 String cls = spec.substring(0, i).trim();
268 int line = Integer.parseInt(spec.substring(i+1));
269 if (!cls.isEmpty() && line >= 0){
270 return new Location (cls, null, line);
272 } catch (NumberFormatException nfx){
281 boolean checkTargetInsn (ExceptionEntry e, MethodInfo mi, int[] ln, int line){
282 if ((ln[0] <= line) && (ln[ln.length - 1] >= line)) {
283 for (int i = 0; i < ln.length; i++) {
286 while ((i++ < ln.length) && (ln[i] == line));
290 mi.getInstruction(i).addAttr(e);
300 * get the target insns/methods
303 public void classLoaded (VM vm, ClassInfo loadedClass){
306 for (ExceptionEntry e = targetClasses.get(loadedClass.getName()); e != null; e = e.next){
307 String method = e.getMethod();
308 int line = e.getLine();
310 if (method != null){ // method or method/line-offset
311 for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()){
312 if (mi.getUniqueName().startsWith(method)){
313 if (line >= 0){ // line offset
314 int[] ln = mi.getLineNumbers();
316 if (checkTargetInsn(e,mi,ln,line)){
317 continue nextClassEntry;
323 } else { // absolute line number
325 for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()) {
326 int[] ln = mi.getLineNumbers();
327 if (checkTargetInsn(e, mi, ln, line)) {
328 continue nextClassEntry;
335 if (targetBases != null){
336 for (; loadedClass != null; loadedClass = loadedClass.getSuperClass()) {
338 for (ExceptionEntry e = targetBases.get(loadedClass.getName()); e != null; e = e.next){
339 String method = e.getMethod();
340 for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()){
341 if (mi.getUniqueName().startsWith(method)){
343 continue nextBaseEntry;
352 public void executeInstruction (VM vm, ThreadInfo ti, Instruction insnToExecute){
354 ExceptionEntry e = insnToExecute.getAttr(ExceptionEntry.class);
355 if ((e == null) && insnToExecute instanceof JVMInvokeInstruction){
356 MethodInfo mi = ((JVMInvokeInstruction) insnToExecute).getInvokedMethod();
357 e = mi.getAttr(ExceptionEntry.class);
361 Instruction nextInsn = ti.createAndThrowException(e.getExceptionClassInfo(ti), e.getExceptionDetails());
362 ti.skipInstruction(nextInsn);
367 // for debugging purposes
368 void printEntries () {
369 for (ExceptionEntry e : targetClasses.values()){
370 System.out.println(e);
372 for (ExceptionEntry e : targetBases.values()){
373 System.out.println(e);
378 public static void main (String[] args){
379 Config conf = JPF.createConfig(args);
380 ExceptionInjector ei = new ExceptionInjector(conf,null);
382 ei.parseException("x.y.Zang(\"bang\")@z.Foo.doit(Ljava/lang/Object;I)");