001: //
002: // Copyright (C) 2005 United States Government as represented by the
003: // Administrator of the National Aeronautics and Space Administration
004: // (NASA). All Rights Reserved.
005: //
006: // This software is distributed under the NASA Open Source Agreement
007: // (NOSA), version 1.3. The NOSA has been approved by the Open Source
008: // Initiative. See the file NOSA-1.3-JPF at the top of the distribution
009: // directory tree for the complete NOSA document.
010: //
011: // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY
012: // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT
013: // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO
014: // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR
015: // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT
016: // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT
017: // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE.
018: //
019: package gov.nasa.jpf.jvm;
020:
021: import gov.nasa.jpf.Config;
022: import gov.nasa.jpf.JPFException;
023: import gov.nasa.jpf.jvm.bytecode.Instruction;
024: import gov.nasa.jpf.util.Debug;
025:
026: import java.lang.reflect.*;
027:
028: import java.util.HashMap;
029: import java.util.Map;
030:
031: /**
032: * native peer classes are part of MJI and contain the code that is
033: * executed by the host VM (i.e. outside the state-tracked JPF JVM). Each
034: * class executed by JPF that has native mehods must have a native peer class
035: * (which is looked up and associated at class loadtime)
036: */
037: public class NativePeer {
038: static ClassLoader loader;
039: static HashMap peers;
040: static final String COND_EXEC_PREFIX = "$isExecutable_";
041: static final String COND_DETERM_PREFIX = "$isDeterministic_";
042: static final int MAX = 6;
043: static Object[][] argCache;
044:
045: ClassInfo ci;
046: Class peerClass;
047: HashMap methods;
048:
049: public static void init(Config config) {
050: // we don't do fancy things yet, but at some point, we might
051: // want to use a specific ClassLoader - just to make sure the
052: // peer classes are not colliding with other stuff
053: loader = ClassLoader.getSystemClassLoader();
054: peers = new HashMap();
055:
056: argCache = new Object[MAX][];
057:
058: for (int i = 0; i < MAX; i++) {
059: argCache[i] = new Object[i];
060: }
061: }
062:
063: NativePeer() {
064: // just here for our derived classes
065: }
066:
067: NativePeer(Class peerClass, ClassInfo ci) {
068: initialize(peerClass, ci, true);
069: }
070:
071: /**
072: * this becomes the factory method to load either a plain (slow)
073: * reflection-based peer (a NativePeer object), or some speed optimized
074: * derived class object.
075: * Watch out - this gets called before the ClassInfo is fully initialized
076: * (we shouldn't rely on more than just its name here)
077: */
078: static NativePeer getNativePeer(ClassInfo ci) {
079: String clsName = ci.getName();
080: NativePeer peer = (NativePeer) peers.get(clsName);
081: Class peerCls = null;
082: String pcn = null;
083:
084: if (peer == null) {
085: // <2do> - we really should come up with our own classloader
086: // so that we don't have to rely on ClassNotFoundException for
087: // the lookup
088: try {
089: pcn = getSystemPeerClassName(clsName);
090: peerCls = loader.loadClass(pcn);
091: } catch (Throwable x) {
092: try {
093: pcn = getUserPeerClassName(clsName);
094: peerCls = loader.loadClass(pcn);
095: } catch (Throwable xx) {
096: try {
097: pcn = getGlobalPeerClassName(clsName);
098: peerCls = loader.loadClass(pcn);
099: } catch (Throwable xxx) {
100: // we could throw an exception in case there are native methods
101: // in ci, but a JVM would only hickup with UnsatisfiedLinkError
102: // if such a method is actually called
103: }
104: }
105: }
106:
107: if (peerCls != null) {
108: try {
109: String dcn = getPeerDispatcherClassName(peerCls
110: .getName());
111: Class 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 xxxx) {
119: if (!(xxxx instanceof ClassNotFoundException)) {
120: // maybe we should bail here
121: System.err.println("error loading dispatcher: "
122: + xxxx);
123: }
124:
125: Debug.print(Debug.DEBUG, "load peer: ");
126: Debug.println(Debug.DEBUG, pcn);
127:
128: peer = new NativePeer(peerCls, ci);
129: }
130:
131: if (peer != null) {
132: peers.put(clsName, peer);
133: }
134: }
135: }
136:
137: return peer;
138: }
139:
140: static String getPeerDispatcherClassName(String clsName) {
141: return (clsName + '$');
142: }
143:
144: /**
145: * "system peer" classes reside in gov...jvm, i.e. they can do
146: * whatever JPF internal stuff they want to
147: */
148: static String getSystemPeerClassName(String clsName) {
149: // we still need the "JPF_" prefix in case of native methods w/o packages
150: return "gov.nasa.jpf.jvm.JPF_" + clsName.replace('.', '_');
151: }
152:
153: /**
154: * look up peer classes in same package like the model class. This is kind
155: * of misleading since the model only gets seen by JPF, and the peer only
156: * by the host VM, but it makes it easier to distribute if this is an
157: * application specific combo. Otherwise the package of the native peer should
158: * be chosen so that it can access the host VM classes it needs
159: */
160: static String getUserPeerClassName(String clsName) {
161: int i = clsName.lastIndexOf('.');
162: String pkg = clsName.substring(0, i + 1);
163:
164: return (pkg + "JPF_" + clsName.replace('.', '_'));
165: }
166:
167: /**
168: * lookup the native peer as a global (i.e. no package) class. This is still
169: * safe because the peer classname is fully qualified (has the model
170: * package in it)
171: * "user peer" classes are confined to what MJIEnv allows them to do
172: * (no package required, since the target class package is mangled into
173: * the name itself)
174: */
175: static String getGlobalPeerClassName(String clsName) {
176: return "JPF_" + clsName.replace('.', '_');
177: }
178:
179: boolean isMethodCondDeterministic(ThreadInfo ti, MethodInfo mi) {
180: Boolean ret = Boolean.FALSE;
181: Object[] args = null;
182: Method mth;
183: MJIEnv env = ti.getMJIEnv();
184:
185: env.setCallEnvironment(mi);
186:
187: if ((mth = getDetermCondMethod(mi)) == null) {
188: // we should never get here, since existence of this method
189: // is used to determine the attribute value
190: throw new JPFException("no isDeterministic() condition: "
191: + mi.getName());
192: }
193:
194: try {
195: args = getArguments(env, ti, mi, mth);
196: ret = (Boolean) mth.invoke(peerClass, args);
197:
198: // leave everything on the stack
199: } catch (Throwable x) {
200: // <2do> - BAD, should behave like executeMethod
201: x.printStackTrace();
202: }
203:
204: return ret.booleanValue();
205: }
206:
207: boolean isMethodCondExecutable(ThreadInfo ti, MethodInfo mi) {
208: Boolean ret = Boolean.FALSE;
209: Object[] args = null;
210: Method mth;
211: MJIEnv env = ti.getMJIEnv();
212:
213: env.setCallEnvironment(mi);
214:
215: if ((mth = getExecCondMethod(mi)) == null) {
216: // we should never get here, since existence of this method
217: // is used to determine the attribute value
218: throw new JPFException("no isExecutable() condition: "
219: + mi.getName());
220: }
221:
222: try {
223: args = getArguments(env, ti, mi, mth);
224: ret = (Boolean) mth.invoke(peerClass, args);
225:
226: // leave everything on the stack
227: } catch (Throwable x) {
228: // <2do> - BAD, should behave like executeMethod
229: x.printStackTrace();
230: }
231:
232: return ret.booleanValue();
233: }
234:
235: boolean isMethodDeterministic(Method m) {
236: // Eeeek - we do the evil C++ thing here - we borrow a method modifier
237: // for the purpose of marking non-deterministic methods. 'final' seems
238: // to be the best bad choice (given that all methods are static).
239: // Note that we have to check this from the ClassInfo init (i.e. lookup
240: // via MethodInfo), because reverse lookup (Method->MethodInfo) is
241: // difficult (we don't have the exact return type info, which is
242: // part of the signature)
243: return ((m.getModifiers() & Modifier.FINAL) == 0);
244: }
245:
246: Instruction executeMethod(ThreadInfo ti, MethodInfo mi) {
247: Object ret = null;
248: Object[] args = null;
249: Method mth;
250: String exception;
251: MJIEnv env = ti.getMJIEnv();
252: ElementInfo ei = null;
253:
254: env.setCallEnvironment(mi);
255:
256: if ((mth = getMethod(mi)) == null) {
257: return ti.createAndThrowException(
258: "java.lang.UnsatisfiedLinkError",
259: "cannot find native " + ci.getName() + '.'
260: + mi.getName());
261: }
262:
263: try {
264: args = getArguments(env, ti, mi, mth);
265:
266: // we have to lock here in case a native method does sync stuff, so that
267: // we don't run into IllegalMonitorStateExceptions
268: if (mi.isSynchronized()) {
269: ei = env.getElementInfo(((Integer) args[1]).intValue());
270: ei.lock(ti);
271: }
272:
273: ret = mth.invoke(peerClass, args);
274:
275: // these are our non-standard returns
276: if ((exception = env.getException()) != null) {
277: String details = env.getExceptionDetails();
278:
279: // even though we should prefer throwing normal exceptions,
280: // sometimes it might be better/required to explicitly throw
281: // something that's not wrapped into a InvocationTargetException
282: // (e.g. InterruptedException), which is why there still is a
283: // MJIEnv.throwException()
284: return ti.createAndThrowException(exception, details);
285: }
286:
287: if (env.getRepeat()) {
288: // call it again
289: return ti.getPC();
290: }
291:
292: // Ok, we did 'return', clean up the stack
293: // note that we don't have a stack frame for this
294: // sucker (for state and speed sake), so we just pop the arguments here
295: ti.removeArguments(mi);
296:
297: pushReturnValue(ti, mi, ret);
298: } catch (IllegalArgumentException iax) {
299: System.out.println(iax);
300: return ti.createAndThrowException(
301: "java.lang.IllegalArgumentException", "calling "
302: + ci.getName() + '.' + mi.getName());
303: } catch (IllegalAccessException ilax) {
304: return ti.createAndThrowException(
305: "java.lang.IllegalAccessException", "calling "
306: + ci.getName() + '.' + mi.getName());
307: } catch (InvocationTargetException itx) {
308: // this will catch all exceptions thrown by the native method execution
309: // (automatically transformed by java.lang.reflect.Method.invoke())
310: return ti.createAndThrowException(
311: "java.lang.reflect.InvocationTargetException",
312: "in " + ci.getName() + '.' + mi.getName() + " : "
313: + itx.getCause());
314: } finally {
315: // no matter what - if we grabbed the lock, we have to release it
316: // but the native method body might actually have given up the lock, so
317: // check first
318: if (mi.isSynchronized() && ei.isLocked()) {
319: ei.unlock(ti);
320: }
321:
322: // bad native methods might keep references around
323: env.clearCallEnvironment();
324: }
325:
326: Instruction pc = ti.getPC();
327:
328: // <2do> - in case of the current System.exit() implementation, all the
329: // stackframes are gone and there is no pc anymore. Until we use a more
330: // explicit end condition, we have to check for this here (the return value
331: // should not matter)
332: if (pc == null) {
333: return null;
334: }
335:
336: // there is no RETURN for a native method, so we have to advance explicitly
337: return pc.getNext();
338: }
339:
340: void initialize(Class peerClass, ClassInfo ci, boolean cacheMethods) {
341: if ((this .ci != null) || (this .peerClass != null)) {
342: throw new RuntimeException(
343: "cannot re-initialize NativePeer: "
344: + peerClass.getName());
345: }
346:
347: this .ci = ci;
348: this .peerClass = peerClass;
349:
350: loadMethods(cacheMethods);
351: }
352:
353: private static boolean isMJICandidate(Method mth) {
354: // only the public static ones are supposed to be native method impls
355: return ((mth.getModifiers() & (Modifier.PUBLIC | Modifier.STATIC)) == (Modifier.PUBLIC | Modifier.STATIC));
356: }
357:
358: private Object[] getArgArray(int n) {
359: // JPF BC execution is not multi-threaded, and argument conversion
360: // should NOT happen recursively (hint hint), so we are rather primitive here.
361: // (actually, argument array can happen recursive, but since they are
362: // only used to pass the values, we just care up to the invoke() call)
363: if (n < MAX) {
364: return argCache[n];
365: } else {
366: return new Object[n];
367: }
368: }
369:
370: /**
371: * Get and convert the native method parameters off the ThreadInfo stack.
372: * Use the MethodInfo parameter type info for this (not the reflect.Method
373: * type array), or otherwise we won't have any type check
374: */
375: private Object[] getArguments(MJIEnv env, ThreadInfo ti,
376: MethodInfo mi, Method mth) {
377: int nArgs = mi.getNumberOfArguments();
378: Object[] a = getArgArray(nArgs + 2);
379: byte[] argTypes = mi.getArgumentTypes();
380: int stackOffset;
381: int i;
382: int j;
383: int k;
384: int ival;
385: long lval;
386:
387: for (i = 0, stackOffset = 0, j = nArgs + 1, k = nArgs - 1; i < nArgs; i++, j--, k--) {
388: switch (argTypes[k]) {
389: case Types.T_BOOLEAN:
390: ival = ti.peek(stackOffset);
391: a[j] = new Boolean(Types.intToBoolean(ival));
392:
393: break;
394:
395: case Types.T_BYTE:
396: ival = ti.peek(stackOffset);
397: a[j] = new Byte((byte) ival);
398:
399: break;
400:
401: case Types.T_CHAR:
402: ival = ti.peek(stackOffset);
403: a[j] = new Character((char) ival);
404:
405: break;
406:
407: case Types.T_SHORT:
408: ival = ti.peek(stackOffset);
409: a[j] = new Short((short) ival);
410:
411: break;
412:
413: case Types.T_INT:
414: ival = ti.peek(stackOffset);
415: a[j] = new Integer(ival);
416:
417: break;
418:
419: case Types.T_LONG:
420: lval = ti.longPeek(stackOffset);
421: stackOffset++; // 2 stack words
422: a[j] = new Long(lval);
423:
424: break;
425:
426: case Types.T_FLOAT:
427: ival = ti.peek(stackOffset);
428: a[j] = new Float(Types.intToFloat(ival));
429:
430: break;
431:
432: case Types.T_DOUBLE:
433: lval = ti.longPeek(stackOffset);
434: stackOffset++; // 2 stack words
435: a[j] = new Double(Types.longToDouble(lval));
436:
437: break;
438:
439: default:
440: ival = ti.peek(stackOffset);
441: a[j] = new Integer(ival);
442: }
443:
444: stackOffset++;
445: }
446:
447: if (mi.isStatic()) {
448: a[1] = new Integer(ci.getClassObjectRef());
449: } else {
450: a[1] = new Integer(ti.getCalleeThis(mi));
451: }
452:
453: a[0] = env;
454:
455: return a;
456: }
457:
458: private Method getDetermCondMethod(MethodInfo mi) {
459: return getMethod(COND_DETERM_PREFIX, mi);
460: }
461:
462: private Method getExecCondMethod(MethodInfo mi) {
463: return getMethod(COND_EXEC_PREFIX, mi);
464: }
465:
466: private void setMJIAttributes(MethodInfo mi,
467: boolean isDeterministic, boolean isCondDeterministic,
468: boolean isCondExecutable) {
469: mi.setMJI(true);
470:
471: // don't overwrite explicitly set attributes with defaults
472: if (!isDeterministic) {
473: mi.setDeterministic(isDeterministic);
474: }
475:
476: if (isCondDeterministic) {
477: mi.setCondDeterministic(isCondDeterministic);
478: }
479:
480: if (isCondExecutable) {
481: mi.setCondExecutable(isCondExecutable);
482: }
483: }
484:
485: private Method getMethod(MethodInfo mi) {
486: return getMethod(null, mi);
487: }
488:
489: private Method getMethod(String prefix, MethodInfo mi) {
490: String name = mi.getUniqueName();
491:
492: if (prefix != null) {
493: name = prefix + name;
494: }
495:
496: return (Method) methods.get(name);
497: }
498:
499: /**
500: * look at all public static methods in the peer and set their
501: * corresponding model class MethodInfo attributes
502: * <2do> pcm - this is too long, break it down
503: */
504: private void loadMethods(boolean cacheMethods) {
505: Method[] m = peerClass.getDeclaredMethods();
506: methods = new HashMap(m.length);
507:
508: Map methodInfos = ci.getDeclaredMethods();
509: MethodInfo[] mis = null;
510:
511: for (int i = 0; i < m.length; i++) {
512: boolean isDeterministic = true;
513: boolean isCondDeterministic = false;
514: boolean isCondExecutable = false;
515: String prefix = "";
516: Method mth = m[i];
517:
518: if (isMJICandidate(mth)) {
519: // Note that we can't mangle the name automatically, since we loose the
520: // object type info (all mapped to int). This has to be handled
521: // the same way like with overloaded JNI methods - you have to
522: // mangle them manually
523: String mn = mth.getName();
524:
525: // JNI doesn't allow <clinit> or <init> to be native, but MJI does
526: // (you should know what you are doing before you use that, really)
527: if (mn.equals("$clinit")) {
528: mn = "<clinit>";
529: } else if (mn.startsWith("$init")) {
530: mn = "<init>" + mn.substring(5);
531: }
532:
533: // check if this is just a attribute method
534: if (mn.startsWith(COND_DETERM_PREFIX)) {
535: prefix = COND_DETERM_PREFIX;
536: mn = mn.substring(17);
537: isCondDeterministic = true;
538: } else if (mn.startsWith(COND_EXEC_PREFIX)) {
539: prefix = COND_EXEC_PREFIX;
540: mn = mn.substring(14);
541: isCondExecutable = true;
542: } else { // no, it's the real thing
543: isDeterministic = isMethodDeterministic(mth);
544: }
545:
546: String mname = Types.getJNIMethodName(mn);
547: String argSig = Types.getJNIArgSignature(mn);
548:
549: if (argSig != null) {
550: mname += argSig;
551: }
552:
553: // now try to find a corresponding MethodInfo object and mark it
554: // as 'peer-ed'
555: // <2do> in case of <clinit>, it wouldn't be strictly required to
556: // have a MethodInfo upfront (we could create it). Might be handy
557: // for classes where we intercept just a few methods, but need
558: // to init before
559: MethodInfo mi = (MethodInfo) methodInfos.get(mname);
560:
561: if ((mi == null) && (argSig == null)) {
562: // nothing found, we have to do it the hard way - check if there is
563: // a single method with this name (still unsafe, but JNI behavior)
564: // Note there's no point in doing that if we do have a signature
565: if (mis == null) { // cache it for subsequent lookup
566: mis = new MethodInfo[methodInfos.size()];
567: methodInfos.values().toArray(mis);
568: }
569:
570: mi = searchMethod(mname, mis);
571: }
572:
573: if (mi != null) {
574: Debug.print(Debug.DEBUG, " load MJI method: ");
575: Debug.print(Debug.DEBUG, prefix);
576: Debug.println(Debug.DEBUG, mname);
577:
578: setMJIAttributes(mi, isDeterministic,
579: isCondDeterministic, isCondExecutable);
580:
581: if (cacheMethods) {
582: if (isCondDeterministic || isCondExecutable) {
583: // register the condition method
584: methods.put(prefix + mi.getUniqueName(),
585: mth);
586: } else {
587: // register the real thing
588: methods.put(mi.getUniqueName(), mth); // no use to store unless it can be called!
589: }
590: } else {
591: // otherwise we are just interested in setting the MethodInfo attributes
592: }
593: } else {
594: // issue a warning if we have a NativePeer native method w/o a corresponding
595: // method in the model class (this might happen due to compiler optimizations
596: // silently skipping empty methods)
597: Debug.println(Debug.WARNING,
598: "orphant NativePeer method: "
599: + ci.getName() + '.' + mn);
600: }
601: }
602: }
603: }
604:
605: private static MethodInfo searchMethod(String mname,
606: MethodInfo[] methods) {
607: for (int j = 0; j < methods.length; j++) {
608: if (methods[j].getName().equals(mname)) {
609: // if this is actually a overloaded method, and the first one
610: // isn't the right choice, we will get a IllegalArgumentException
611: // later-on. Hence no need to spend more effort here - but
612: // could be optional.
613: // <2do> - actually, we should try harder and check if there's
614: // only one native candidate if we have a ambiguity (again JNI analogy)
615: return methods[j];
616: }
617: }
618:
619: return null;
620: }
621:
622: private void pushReturnValue(ThreadInfo ti, MethodInfo mi,
623: Object ret) {
624: int ival;
625: long lval;
626:
627: // in case of a return type mismatch, we get a ClassCastException, which
628: // is handled in executeMethod() and reported as a InvocationTargetException
629: // (not completely accurate, but we rather go with safety)
630: if (ret != null) {
631: switch (mi.getReturnType()) {
632: case Types.T_BOOLEAN:
633: ival = Types.booleanToInt(((Boolean) ret)
634: .booleanValue());
635: ti.push(ival, false);
636:
637: break;
638:
639: case Types.T_BYTE:
640: ti.push(((Byte) ret).byteValue(), false);
641:
642: break;
643:
644: case Types.T_CHAR:
645: ti.push(((Character) ret).charValue(), false);
646:
647: break;
648:
649: case Types.T_SHORT:
650: ti.push(((Short) ret).shortValue(), false);
651:
652: break;
653:
654: case Types.T_INT:
655: ti.push(((Integer) ret).intValue(), false);
656:
657: break;
658:
659: case Types.T_LONG:
660: ti.longPush(((Long) ret).longValue());
661:
662: break;
663:
664: case Types.T_FLOAT:
665: ival = Types.floatToInt(((Float) ret).floatValue());
666: ti.push(ival, false);
667:
668: break;
669:
670: case Types.T_DOUBLE:
671: lval = Types.doubleToLong(((Double) ret).doubleValue());
672: ti.longPush(lval);
673:
674: break;
675:
676: default:
677:
678: // everything else is supposed to be a reference
679: ti.push(((Integer) ret).intValue(), true);
680: }
681: }
682: }
683: }
|