Fixing bugs and cleaning up: Continuing sub-graph executions when there is no matched...
[jpf-core.git] / src / main / gov / nasa / jpf / listener / ExceptionInjector.java
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
19 package gov.nasa.jpf.listener;
20
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;
33
34 import java.util.HashMap;
35
36 /**
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.
40  *
41  * Exceptions are specified as a list of xSpec'@'location pairs.
42  *
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
45  *
46  * Location can be 
47  *   - class:line
48  *   - fully qualified method (callee that is supposed to throw, which is
49  *     NOT executed in this case)
50  *   - fully qualified method ':' lineOffset
51  *
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.
55  *
56  * Method body line offsets count from the first statement line in the method body
57  *
58  * Examples:
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
62  */
63
64 public class ExceptionInjector extends ListenerAdapter {
65
66   boolean throwFirst; // for location targets, throw on first insn associated with line
67
68   static class ExceptionEntry {
69     Instruction insn;
70     ExceptionSpec xSpec;
71     Location loc;
72
73     ExceptionEntry next;  // there might be more than one for one class
74
75     ExceptionEntry (ExceptionSpec xSpec, Location loc, ExceptionEntry next){
76       this.xSpec = xSpec;
77       this.loc = loc;
78       this.next = next;
79     }
80
81     String getLocationClassName() {
82       return loc.className;
83     }
84
85     String getMethod() {
86       return loc.method;
87     }
88
89     int getLine() {
90       return loc.line;
91     }
92
93     ClassInfo getExceptionClassInfo(ThreadInfo ti) {
94       return ClassLoaderInfo.getCurrentResolvedClassInfo(xSpec.xClsName);
95     }
96
97     String getExceptionDetails() {
98       return xSpec.details;
99     }
100
101     @Override
102         public String toString() {
103       return xSpec.toString() + '@' + loc.toString();
104     }
105   }
106
107   static class ExceptionSpec {
108     String xClsName;
109     String details;
110
111     ExceptionSpec (String xClsName, String details){
112       this.xClsName = xClsName;
113       this.details = details;
114     }
115     
116     @Override
117         public String toString() {
118       if (details == null){
119         return xClsName;
120       } else {
121         StringBuilder sb = new StringBuilder(xClsName);
122         sb.append('(');
123         if (!details.isEmpty()){
124           sb.append('"');
125           sb.append(details);
126           sb.append('"');
127         }
128         sb.append(')');
129         return sb.toString();
130       }
131     }
132   }
133
134   static class Location {
135     String className;
136     String method; // name + signature
137     int line;
138
139     Location (String className, String method, int line){
140       this.className = className;
141       this.method = method;
142       this.line = line;
143     }
144
145     @Override
146         public String toString() {
147       StringBuilder sb = new StringBuilder(className);
148       if (method != null){
149         sb.append('.');
150         sb.append(method);
151       }
152       if (line >= 0){
153         sb.append(':');
154         sb.append(line);
155       }
156       return sb.toString();
157     }
158   }
159
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>();
163   
164   // methods and instructions to watch for at runtime will have ExceptionEntry attrs
165
166
167   public ExceptionInjector (Config config, JPF jpf){
168     throwFirst = config.getBoolean("ei.throw_first", false);
169     String[] xSpecs = config.getStringArray("ei.exception", new char[] {';'});
170
171     if (xSpecs != null){
172       for (String xSpec : xSpecs){
173         if (!parseException(xSpec)){
174           throw new JPFConfigException("invalid exception spec: " + xSpec);
175         }
176       }
177     }
178
179     printEntries();
180   }
181
182   boolean parseException (String xSpec){
183     int i = xSpec.indexOf('@');
184     if (i > 0){
185       String typeSpec = xSpec.substring(0, i).trim();
186       String locSpec = xSpec.substring(i+1).trim();
187
188       ExceptionSpec type = parseType(typeSpec);
189       if (type != null){
190         Location loc = parseLocation(locSpec);
191         if (loc != null){
192           String cls = loc.className;
193           int line = loc.line;
194           if (line >= 0){
195             targetClasses.put(cls, new ExceptionEntry(type,loc,targetClasses.get(cls)));
196           } else {
197             targetBases.put(cls, new ExceptionEntry(type,loc,targetBases.get(cls)));
198           }
199           return true;
200         }
201       }
202     }
203
204     return false;
205   }
206
207   ExceptionSpec parseType (String spec){
208     String cls = null;
209     String details = null;
210
211     int i = spec.indexOf('(');
212     if (i > 0){
213       cls = spec.substring(0, i);
214
215       int j = spec.lastIndexOf(')');
216       if (spec.charAt(i+1) == '"'){
217         i++;
218       }
219       if (spec.charAt(j-1) == '"'){
220         j--;
221       }
222       details = spec.substring(i+1, j);
223       if (details.isEmpty()){
224         details = null;
225       }
226
227     } else if (i < 0) {  // no details
228       cls = spec;
229     }
230
231     if (cls != null){
232       return new ExceptionSpec( cls,details);
233     }
234
235     return null;
236   }
237
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
242       if (j > 0){
243         String cls = spec.substring(0, j).trim();
244         i = spec.indexOf(':');
245         if (i > 0){
246
247           String mth = Types.getSignatureName(spec.substring(j+1, i));
248
249           try {
250             int line = Integer.parseInt(spec.substring(i + 1));
251             if (!cls.isEmpty() && !mth.isEmpty() && line >= 0){
252               return new Location(cls, mth, line);
253             }
254           } catch (NumberFormatException nfx) {
255             return null;
256           }
257         } else {
258           String mth = Types.getSignatureName(spec.substring(j+1));
259           return new Location(cls,mth, -1);
260         }
261       }
262
263     } else { // no method
264       i = spec.indexOf(':');  // but we need a line number
265       if (i > 0){
266         String cls = spec.substring(0, i).trim();
267         try {
268           int line = Integer.parseInt(spec.substring(i+1));
269           if (!cls.isEmpty() && line >= 0){
270             return new Location (cls, null, line);
271           }
272         } catch (NumberFormatException nfx){
273           return null;
274         }
275       }
276     }
277
278     return null;
279   }
280
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++) {
284         if (ln[i] == line) {
285           if (!throwFirst) {
286             while ((i++ < ln.length) && (ln[i] == line));
287             i--;
288           }
289
290           mi.getInstruction(i).addAttr(e);
291           return true;
292         }
293       }
294     }
295
296     return false;
297   }
298
299   /**
300    * get the target insns/methods
301    */
302   @Override
303   public void classLoaded (VM vm, ClassInfo loadedClass){
304
305     nextClassEntry:
306     for (ExceptionEntry e = targetClasses.get(loadedClass.getName()); e != null; e = e.next){
307       String method = e.getMethod();
308       int line = e.getLine();
309
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();
315               line += ln[0];
316               if (checkTargetInsn(e,mi,ln,line)){
317                 continue nextClassEntry;
318               }
319             }
320           }
321         }
322
323       } else { // absolute line number
324         if (line >= 0){
325           for (MethodInfo mi : loadedClass.getDeclaredMethodInfos()) {
326             int[] ln = mi.getLineNumbers();
327             if (checkTargetInsn(e, mi, ln, line)) {
328               continue nextClassEntry;
329             }
330           }
331         }
332       }
333     }
334
335     if (targetBases != null){
336       for (; loadedClass != null; loadedClass = loadedClass.getSuperClass()) {
337         nextBaseEntry:
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)){
342               mi.addAttr(e);
343               continue nextBaseEntry;
344             }
345           }
346         }
347       }
348     }
349   }
350
351   @Override
352   public void executeInstruction (VM vm, ThreadInfo ti, Instruction insnToExecute){
353
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);
358     }
359
360     if (e != null){
361       Instruction nextInsn = ti.createAndThrowException(e.getExceptionClassInfo(ti), e.getExceptionDetails());
362       ti.skipInstruction(nextInsn);
363       return;
364     }
365   }
366   
367   // for debugging purposes
368   void printEntries () {
369     for (ExceptionEntry e : targetClasses.values()){
370       System.out.println(e);
371     }
372     for (ExceptionEntry e : targetBases.values()){
373       System.out.println(e);
374     }
375   }
376
377   /**
378   public static void main (String[] args){
379     Config conf = JPF.createConfig(args);
380     ExceptionInjector ei = new ExceptionInjector(conf,null);
381
382     ei.parseException("x.y.Zang(\"bang\")@z.Foo.doit(Ljava/lang/Object;I)");
383     ei.printEntries();
384   }
385   **/
386 }