KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > gov > nasa > jpf > jvm > NativePeer


1 //
2
// Copyright (C) 2005 United States Government as represented by the
3
// Administrator of the National Aeronautics and Space Administration
4
// (NASA). All Rights Reserved.
5
//
6
// This software is distributed under the NASA Open Source Agreement
7
// (NOSA), version 1.3. The NOSA has been approved by the Open Source
8
// Initiative. See the file NOSA-1.3-JPF at the top of the distribution
9
// directory tree for the complete NOSA document.
10
//
11
// THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
12
// KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
13
// LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
14
// SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
15
// A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
16
// THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
17
// DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
18
//
19
package gov.nasa.jpf.jvm;
20
21 import gov.nasa.jpf.Config;
22 import gov.nasa.jpf.JPFException;
23 import gov.nasa.jpf.jvm.bytecode.Instruction;
24 import gov.nasa.jpf.util.Debug;
25
26 import java.lang.reflect.*;
27
28 import java.util.HashMap JavaDoc;
29 import java.util.Map JavaDoc;
30
31
32 /**
33  * native peer classes are part of MJI and contain the code that is
34  * executed by the host VM (i.e. outside the state-tracked JPF JVM). Each
35  * class executed by JPF that has native mehods must have a native peer class
36  * (which is looked up and associated at class loadtime)
37  */

38 public class NativePeer {
39   static ClassLoader JavaDoc loader;
40   static HashMap JavaDoc peers;
41   static final String JavaDoc COND_EXEC_PREFIX = "$isExecutable_";
42   static final String JavaDoc COND_DETERM_PREFIX = "$isDeterministic_";
43   static final int MAX = 6;
44   static Object JavaDoc[][] argCache;
45
46   ClassInfo ci;
47   Class JavaDoc peerClass;
48   HashMap JavaDoc methods;
49
50   public static void init (Config config) {
51     // we don't do fancy things yet, but at some point, we might
52
// want to use a specific ClassLoader - just to make sure the
53
// peer classes are not colliding with other stuff
54
loader = ClassLoader.getSystemClassLoader();
55     peers = new HashMap JavaDoc();
56
57     argCache = new Object JavaDoc[MAX][];
58
59     for (int i = 0; i < MAX; i++) {
60       argCache[i] = new Object JavaDoc[i];
61     }
62   }
63   
64   NativePeer () {
65     // just here for our derived classes
66
}
67
68   NativePeer (Class JavaDoc peerClass, ClassInfo ci) {
69     initialize(peerClass, ci, true);
70   }
71
72   /**
73    * this becomes the factory method to load either a plain (slow)
74    * reflection-based peer (a NativePeer object), or some speed optimized
75    * derived class object.
76    * Watch out - this gets called before the ClassInfo is fully initialized
77    * (we shouldn't rely on more than just its name here)
78    */

79   static NativePeer getNativePeer (ClassInfo ci) {
80     String JavaDoc clsName = ci.getName();
81     NativePeer peer = (NativePeer) peers.get(clsName);
82     Class JavaDoc peerCls = null;
83     String JavaDoc pcn = null;
84
85     if (peer == null) {
86       // <2do> - we really should come up with our own classloader
87
// so that we don't have to rely on ClassNotFoundException for
88
// the lookup
89
try {
90         pcn = getSystemPeerClassName(clsName);
91         peerCls = loader.loadClass(pcn);
92       } catch (Throwable JavaDoc x) {
93         try {
94           pcn = getUserPeerClassName(clsName);
95           peerCls = loader.loadClass(pcn);
96         } catch (Throwable JavaDoc xx) {
97           try {
98             pcn = getGlobalPeerClassName(clsName);
99             peerCls = loader.loadClass(pcn);
100           } catch (Throwable JavaDoc xxx) {
101             // we could throw an exception in case there are native methods
102
// in ci, but a JVM would only hickup with UnsatisfiedLinkError
103
// if such a method is actually called
104
}
105         }
106       }
107
108       if (peerCls != null) {
109         try {
110           String JavaDoc dcn = getPeerDispatcherClassName(peerCls.getName());
111           Class JavaDoc dispatcherClass = loader.loadClass(dcn);
112
113           Debug.print(Debug.DEBUG, "load peer dispatcher: ");
114           Debug.println(Debug.DEBUG, dcn);
115
116           peer = (NativePeer) dispatcherClass.newInstance();
117           peer.initialize(peerCls, ci, false);
118         } catch (Throwable JavaDoc xxxx) {
119           if (!(xxxx instanceof ClassNotFoundException JavaDoc)) {
120             // maybe we should bail here
121
System.err.println("error loading dispatcher: " + xxxx);
122           }
123
124           Debug.print(Debug.DEBUG, "load peer: ");
125           Debug.println(Debug.DEBUG, pcn);
126
127           peer = new NativePeer(peerCls, ci);
128         }
129
130         if (peer != null) {
131           peers.put(clsName, peer);
132         }
133       }
134     }
135
136     return peer;
137   }
138
139   static String JavaDoc getPeerDispatcherClassName (String JavaDoc clsName) {
140     return (clsName + '$');
141   }
142
143   /**
144    * "system peer" classes reside in gov...jvm, i.e. they can do
145    * whatever JPF internal stuff they want to
146    */

147   static String JavaDoc getSystemPeerClassName (String JavaDoc clsName) {
148     // we still need the "JPF_" prefix in case of native methods w/o packages
149
return "gov.nasa.jpf.jvm.JPF_" + clsName.replace('.', '_');
150   }
151
152   /**
153    * look up peer classes in same package like the model class. This is kind
154    * of misleading since the model only gets seen by JPF, and the peer only
155    * by the host VM, but it makes it easier to distribute if this is an
156    * application specific combo. Otherwise the package of the native peer should
157    * be chosen so that it can access the host VM classes it needs
158    */

159   static String JavaDoc getUserPeerClassName (String JavaDoc clsName) {
160     int i = clsName.lastIndexOf('.');
161     String JavaDoc pkg = clsName.substring(0, i+1);
162     
163     return (pkg + "JPF_" + clsName.replace('.', '_'));
164   }
165   
166   /**
167    * lookup the native peer as a global (i.e. no package) class. This is still
168    * safe because the peer classname is fully qualified (has the model
169    * package in it)
170    * "user peer" classes are confined to what MJIEnv allows them to do
171    * (no package required, since the target class package is mangled into
172    * the name itself)
173    */

174   static String JavaDoc getGlobalPeerClassName (String JavaDoc clsName) {
175     return "JPF_" + clsName.replace('.', '_');
176   }
177
178   boolean isMethodCondDeterministic (ThreadInfo ti, MethodInfo mi) {
179     Boolean JavaDoc ret = Boolean.FALSE;
180     Object JavaDoc[] args = null;
181     Method mth;
182     MJIEnv env = ti.getMJIEnv();
183
184     env.setCallEnvironment(mi);
185
186     if ((mth = getDetermCondMethod(mi)) == null) {
187       // we should never get here, since existence of this method
188
// is used to determine the attribute value
189
throw new JPFException("no isDeterministic() condition: " +
190                                mi.getName());
191     }
192
193     try {
194       args = getArguments(env, ti, mi, mth);
195       ret = (Boolean JavaDoc) mth.invoke(peerClass, args);
196
197       // leave everything on the stack
198
} catch (Throwable JavaDoc x) {
199       // <2do> - BAD, should behave like executeMethod
200
x.printStackTrace();
201     }
202
203     return ret.booleanValue();
204   }
205
206   boolean isMethodCondExecutable (ThreadInfo ti, MethodInfo mi) {
207     Boolean JavaDoc ret = Boolean.FALSE;
208     Object JavaDoc[] args = null;
209     Method mth;
210     MJIEnv env = ti.getMJIEnv();
211
212     env.setCallEnvironment(mi);
213
214     if ((mth = getExecCondMethod(mi)) == null) {
215       // we should never get here, since existence of this method
216
// is used to determine the attribute value
217
throw new JPFException("no isExecutable() condition: " +
218                                mi.getName());
219     }
220
221     try {
222       args = getArguments(env, ti, mi, mth);
223       ret = (Boolean JavaDoc) mth.invoke(peerClass, args);
224
225       // leave everything on the stack
226
} catch (Throwable JavaDoc x) {
227       // <2do> - BAD, should behave like executeMethod
228
x.printStackTrace();
229     }
230
231     return ret.booleanValue();
232   }
233
234   boolean isMethodDeterministic (Method m) {
235     // Eeeek - we do the evil C++ thing here - we borrow a method modifier
236
// for the purpose of marking non-deterministic methods. 'final' seems
237
// to be the best bad choice (given that all methods are static).
238
// Note that we have to check this from the ClassInfo init (i.e. lookup
239
// via MethodInfo), because reverse lookup (Method->MethodInfo) is
240
// difficult (we don't have the exact return type info, which is
241
// part of the signature)
242
return ((m.getModifiers() & Modifier.FINAL) == 0);
243   }
244
245   Instruction executeMethod (ThreadInfo ti, MethodInfo mi) {
246     Object JavaDoc ret = null;
247     Object JavaDoc[] args = null;
248     Method mth;
249     String JavaDoc exception;
250     MJIEnv env = ti.getMJIEnv();
251     ElementInfo ei = null;
252
253     env.setCallEnvironment(mi);
254
255     if ((mth = getMethod(mi)) == null) {
256       return ti.createAndThrowException("java.lang.UnsatisfiedLinkError",
257                                         "cannot find native " + ci.getName() + '.' +
258                                         mi.getName());
259     }
260
261     try {
262       args = getArguments(env, ti, mi, mth);
263       
264       // we have to lock here in case a native method does sync stuff, so that
265
// we don't run into IllegalMonitorStateExceptions
266
if (mi.isSynchronized()){
267         ei = env.getElementInfo(((Integer JavaDoc)args[1]).intValue());
268         ei.lock(ti);
269       }
270       
271       ret = mth.invoke(peerClass, args);
272
273       // these are our non-standard returns
274
if ((exception = env.getException()) != null) {
275         String JavaDoc details = env.getExceptionDetails();
276
277         // even though we should prefer throwing normal exceptions,
278
// sometimes it might be better/required to explicitly throw
279
// something that's not wrapped into a InvocationTargetException
280
// (e.g. InterruptedException), which is why there still is a
281
// MJIEnv.throwException()
282
return ti.createAndThrowException(exception, details);
283       }
284
285       if (env.getRepeat()) {
286         // call it again
287
return ti.getPC();
288       }
289
290       // Ok, we did 'return', clean up the stack
291
// note that we don't have a stack frame for this
292
// sucker (for state and speed sake), so we just pop the arguments here
293
ti.removeArguments(mi);
294
295       pushReturnValue(ti, mi, ret);
296     } catch (IllegalArgumentException JavaDoc iax) {
297       System.out.println(iax);
298       return ti.createAndThrowException("java.lang.IllegalArgumentException",
299                                         "calling " + ci.getName() + '.' +
300                                         mi.getName());
301     } catch (IllegalAccessException JavaDoc ilax) {
302       return ti.createAndThrowException("java.lang.IllegalAccessException",
303                                         "calling " + ci.getName() + '.' +
304                                         mi.getName());
305     } catch (InvocationTargetException itx) {
306       // this will catch all exceptions thrown by the native method execution
307
// (automatically transformed by java.lang.reflect.Method.invoke())
308
return ti.createAndThrowException(
309                    "java.lang.reflect.InvocationTargetException",
310                    "in " + ci.getName() + '.' + mi.getName() + " : " + itx.getCause());
311      } finally {
312       // no matter what - if we grabbed the lock, we have to release it
313
// but the native method body might actually have given up the lock, so
314
// check first
315
if (mi.isSynchronized() && ei.isLocked()){
316         ei.unlock(ti);
317       }
318       
319       // bad native methods might keep references around
320
env.clearCallEnvironment();
321     }
322
323     Instruction pc = ti.getPC();
324
325     // <2do> - in case of the current System.exit() implementation, all the
326
// stackframes are gone and there is no pc anymore. Until we use a more
327
// explicit end condition, we have to check for this here (the return value
328
// should not matter)
329
if (pc == null) {
330       return null;
331     }
332     
333     // there is no RETURN for a native method, so we have to advance explicitly
334
return pc.getNext();
335   }
336
337   void initialize (Class JavaDoc peerClass, ClassInfo ci, boolean cacheMethods) {
338     if ((this.ci != null) || (this.peerClass != null)) {
339       throw new RuntimeException JavaDoc("cannot re-initialize NativePeer: " +
340                                  peerClass.getName());
341     }
342
343     this.ci = ci;
344     this.peerClass = peerClass;
345     
346     loadMethods(cacheMethods);
347   }
348
349   private static boolean isMJICandidate (Method mth) {
350     // only the public static ones are supposed to be native method impls
351
return ((mth.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) == (Modifier.PUBLIC | Modifier.STATIC));
352   }
353
354   private Object JavaDoc[] getArgArray (int n) {
355     // JPF BC execution is not multi-threaded, and argument conversion
356
// should NOT happen recursively (hint hint), so we are rather primitive here.
357
// (actually, argument array can happen recursive, but since they are
358
// only used to pass the values, we just care up to the invoke() call)
359
if (n < MAX) {
360       return argCache[n];
361     } else {
362       return new Object JavaDoc[n];
363     }
364   }
365
366   /**
367    * Get and convert the native method parameters off the ThreadInfo stack.
368    * Use the MethodInfo parameter type info for this (not the reflect.Method
369    * type array), or otherwise we won't have any type check
370    */

371   private Object JavaDoc[] getArguments (MJIEnv env, ThreadInfo ti, MethodInfo mi,
372                                  Method mth) {
373     int nArgs = mi.getNumberOfArguments();
374     Object JavaDoc[] a = getArgArray(nArgs + 2);
375     byte[] argTypes = mi.getArgumentTypes();
376     int stackOffset;
377     int i;
378     int j;
379     int k;
380     int ival;
381     long lval;
382
383     for (i = 0, stackOffset = 0, j = nArgs + 1, k = nArgs - 1;
384          i < nArgs;
385          i++, j--, k--) {
386       switch (argTypes[k]) {
387       case Types.T_BOOLEAN:
388         ival = ti.peek(stackOffset);
389         a[j] = new Boolean JavaDoc(Types.intToBoolean(ival));
390
391         break;
392
393       case Types.T_BYTE:
394         ival = ti.peek(stackOffset);
395         a[j] = new Byte JavaDoc((byte) ival);
396
397         break;
398
399       case Types.T_CHAR:
400         ival = ti.peek(stackOffset);
401         a[j] = new Character JavaDoc((char) ival);
402
403         break;
404
405       case Types.T_SHORT:
406         ival = ti.peek(stackOffset);
407         a[j] = new Short JavaDoc((short) ival);
408
409         break;
410
411       case Types.T_INT:
412         ival = ti.peek(stackOffset);
413         a[j] = new Integer JavaDoc(ival);
414
415         break;
416
417       case Types.T_LONG:
418         lval = ti.longPeek(stackOffset);
419         stackOffset++; // 2 stack words
420
a[j] = new Long JavaDoc(lval);
421
422         break;
423
424       case Types.T_FLOAT:
425         ival = ti.peek(stackOffset);
426         a[j] = new Float JavaDoc(Types.intToFloat(ival));
427
428         break;
429
430       case Types.T_DOUBLE:
431         lval = ti.longPeek(stackOffset);
432         stackOffset++; // 2 stack words
433
a[j] = new Double JavaDoc(Types.longToDouble(lval));
434
435         break;
436
437       default:
438         ival = ti.peek(stackOffset);
439         a[j] = new Integer JavaDoc(ival);
440       }
441
442       stackOffset++;
443     }
444
445     if (mi.isStatic()) {
446       a[1] = new Integer JavaDoc(ci.getClassObjectRef());
447     } else {
448       a[1] = new Integer JavaDoc(ti.getCalleeThis(mi));
449     }
450
451     a[0] = env;
452
453     return a;
454   }
455
456   private Method getDetermCondMethod (MethodInfo mi) {
457     return getMethod(COND_DETERM_PREFIX, mi);
458   }
459
460   private Method getExecCondMethod (MethodInfo mi) {
461     return getMethod(COND_EXEC_PREFIX, mi);
462   }
463
464   private void setMJIAttributes (MethodInfo mi, boolean isDeterministic,
465                                  boolean isCondDeterministic,
466                                  boolean isCondExecutable) {
467     mi.setMJI(true);
468
469     // don't overwrite explicitly set attributes with defaults
470
if (!isDeterministic) {
471       mi.setDeterministic(isDeterministic);
472     }
473
474     if (isCondDeterministic) {
475       mi.setCondDeterministic(isCondDeterministic);
476     }
477
478     if (isCondExecutable) {
479       mi.setCondExecutable(isCondExecutable);
480     }
481   }
482
483   private Method getMethod (MethodInfo mi) {
484     return getMethod(null, mi);
485   }
486
487   private Method getMethod (String JavaDoc prefix, MethodInfo mi) {
488     String JavaDoc name = mi.getUniqueName();
489
490     if (prefix != null) {
491       name = prefix + name;
492     }
493
494     return (Method) methods.get(name);
495   }
496
497   /**
498    * look at all public static methods in the peer and set their
499    * corresponding model class MethodInfo attributes
500    * <2do> pcm - this is too long, break it down
501    */

502   private void loadMethods (boolean cacheMethods) {
503     Method[] m = peerClass.getDeclaredMethods();
504     methods = new HashMap JavaDoc(m.length);
505
506     Map JavaDoc methodInfos = ci.getDeclaredMethods();
507     MethodInfo[] mis = null;
508
509     for (int i = 0; i < m.length; i++) {
510       boolean isDeterministic = true;
511       boolean isCondDeterministic = false;
512       boolean isCondExecutable = false;
513       String JavaDoc prefix = "";
514       Method mth = m[i];
515
516       if (isMJICandidate(mth)) {
517         // Note that we can't mangle the name automatically, since we loose the
518
// object type info (all mapped to int). This has to be handled
519
// the same way like with overloaded JNI methods - you have to
520
// mangle them manually
521
String JavaDoc mn = mth.getName();
522
523         // JNI doesn't allow <clinit> or <init> to be native, but MJI does
524
// (you should know what you are doing before you use that, really)
525
if (mn.equals("$clinit")) {
526           mn = "<clinit>";
527         } else if (mn.startsWith("$init")) {
528           mn = "<init>" + mn.substring(5);
529         }
530
531         // check if this is just a attribute method
532
if (mn.startsWith(COND_DETERM_PREFIX)) {
533           prefix = COND_DETERM_PREFIX;
534           mn = mn.substring(17);
535           isCondDeterministic = true;
536         } else if (mn.startsWith(COND_EXEC_PREFIX)) {
537           prefix = COND_EXEC_PREFIX;
538           mn = mn.substring(14);
539           isCondExecutable = true;
540         } else { // no, it's the real thing
541
isDeterministic = isMethodDeterministic(mth);
542         }
543
544         String JavaDoc mname = Types.getJNIMethodName(mn);
545         String JavaDoc argSig = Types.getJNIArgSignature(mn);
546
547         if (argSig != null) {
548           mname += argSig;
549         }
550
551         // now try to find a corresponding MethodInfo object and mark it
552
// as 'peer-ed'
553
// <2do> in case of <clinit>, it wouldn't be strictly required to
554
// have a MethodInfo upfront (we could create it). Might be handy
555
// for classes where we intercept just a few methods, but need
556
// to init before
557
MethodInfo mi = (MethodInfo) methodInfos.get(mname);
558
559         if ((mi == null) && (argSig == null)) {
560           // nothing found, we have to do it the hard way - check if there is
561
// a single method with this name (still unsafe, but JNI behavior)
562
// Note there's no point in doing that if we do have a signature
563
if (mis == null) { // cache it for subsequent lookup
564
mis = new MethodInfo[methodInfos.size()];
565             methodInfos.values().toArray(mis);
566           }
567
568           mi = searchMethod(mname, mis);
569         }
570
571         if (mi != null) {
572           Debug.print(Debug.DEBUG, " load MJI method: ");
573           Debug.print(Debug.DEBUG, prefix);
574           Debug.println(Debug.DEBUG, mname);
575
576           setMJIAttributes(mi, isDeterministic, isCondDeterministic,
577                            isCondExecutable);
578
579           if (cacheMethods) {
580             if (isCondDeterministic || isCondExecutable) {
581               // register the condition method
582
methods.put(prefix + mi.getUniqueName(), mth);
583             } else {
584               // register the real thing
585
methods.put(mi.getUniqueName(), mth); // no use to store unless it can be called!
586
}
587           } else {
588             // otherwise we are just interested in setting the MethodInfo attributes
589
}
590         } else {
591           // issue a warning if we have a NativePeer native method w/o a corresponding
592
// method in the model class (this might happen due to compiler optimizations
593
// silently skipping empty methods)
594
Debug.println(Debug.WARNING, "orphant NativePeer method: " + ci.getName() + '.' + mn);
595         }
596       }
597     }
598   }
599
600   private static MethodInfo searchMethod (String JavaDoc mname, MethodInfo[] methods) {
601     for (int j = 0; j < methods.length; j++) {
602       if (methods[j].getName().equals(mname)) {
603         // if this is actually a overloaded method, and the first one
604
// isn't the right choice, we will get a IllegalArgumentException
605
// later-on. Hence no need to spend more effort here - but
606
// could be optional.
607
// <2do> - actually, we should try harder and check if there's
608
// only one native candidate if we have a ambiguity (again JNI analogy)
609
return methods[j];
610       }
611     }
612
613     return null;
614   }
615
616   private void pushReturnValue (ThreadInfo ti, MethodInfo mi, Object JavaDoc ret) {
617     int ival;
618     long lval;
619
620     // in case of a return type mismatch, we get a ClassCastException, which
621
// is handled in executeMethod() and reported as a InvocationTargetException
622
// (not completely accurate, but we rather go with safety)
623
if (ret != null) {
624       switch (mi.getReturnType()) {
625       case Types.T_BOOLEAN:
626         ival = Types.booleanToInt(((Boolean JavaDoc) ret).booleanValue());
627         ti.push(ival, false);
628
629         break;
630
631       case Types.T_BYTE:
632         ti.push(((Byte JavaDoc) ret).byteValue(), false);
633
634         break;
635
636       case Types.T_CHAR:
637         ti.push(((Character JavaDoc) ret).charValue(), false);
638
639         break;
640
641       case Types.T_SHORT:
642         ti.push(((Short JavaDoc) ret).shortValue(), false);
643
644         break;
645
646       case Types.T_INT:
647         ti.push(((Integer JavaDoc) ret).intValue(), false);
648
649         break;
650
651       case Types.T_LONG:
652         ti.longPush(((Long JavaDoc) ret).longValue());
653
654         break;
655
656       case Types.T_FLOAT:
657         ival = Types.floatToInt(((Float JavaDoc) ret).floatValue());
658         ti.push(ival, false);
659
660         break;
661
662       case Types.T_DOUBLE:
663         lval = Types.doubleToLong(((Double JavaDoc) ret).doubleValue());
664         ti.longPush(lval);
665
666         break;
667
668       default:
669
670         // everything else is supposed to be a reference
671
ti.push(((Integer JavaDoc) ret).intValue(), true);
672       }
673     }
674   }
675 }
676
677
Popular Tags