001/*
002 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003 *
004 *  This file is licensed to You under the Eclipse Public License (EPL);
005 *  You may not use this file except in compliance with the License. You
006 *  may obtain a copy of the License at
007 *
008 *      http://www.opensource.org/licenses/eclipse-1.0.php
009 *
010 *  See the COPYRIGHT.txt file distributed with this work for information
011 *  regarding copyright ownership.
012 */
013package org.jikesrvm.jni;
014
015import static org.jikesrvm.runtime.JavaSizeConstants.BITS_IN_BYTE;
016import static org.jikesrvm.runtime.JavaSizeConstants.BYTES_IN_LONG;
017import static org.jikesrvm.runtime.UnboxedSizeConstants.BYTES_IN_ADDRESS;
018
019import java.lang.reflect.InvocationTargetException;
020import java.nio.ByteBuffer;
021import java.nio.CharBuffer;
022import java.nio.charset.CharacterCodingException;
023import java.nio.charset.Charset;
024import java.nio.charset.CharsetDecoder;
025import java.nio.charset.CharsetEncoder;
026import java.nio.charset.CodingErrorAction;
027
028import org.jikesrvm.VM;
029import org.jikesrvm.classloader.MemberReference;
030import org.jikesrvm.classloader.MethodReference;
031import org.jikesrvm.classloader.RVMMethod;
032import org.jikesrvm.classloader.TypeReference;
033import org.jikesrvm.classloader.UTF8Convert;
034import org.jikesrvm.runtime.Magic;
035import org.jikesrvm.runtime.Memory;
036import org.jikesrvm.runtime.Reflection;
037import org.jikesrvm.runtime.RuntimeEntrypoints;
038import org.jikesrvm.scheduler.RVMThread;
039import org.jikesrvm.util.StringUtilities;
040import org.vmmagic.pragma.Uninterruptible;
041import org.vmmagic.unboxed.Address;
042import org.vmmagic.unboxed.Offset;
043import org.vmmagic.unboxed.Word;
044
045/**
046 * Platform independent utility functions called from JNIFunctions
047 * (cannot be placed in JNIFunctions because methods
048 * there are specially compiled to be called from native).
049 *
050 * @see JNIFunctions
051 */
052public abstract class JNIGenericHelpers {
053
054  /**
055   *  Whether to allow reads of undefined memory when computing strlen(..).
056   *  This is enabled by default to improve performance. It will never cause
057   *  undefined behaviour but it may turn up as a false positive when using
058   *  tools such as Valgrind.
059   */
060  private static final boolean ALLOW_READS_OF_UNDEFINED_MEMORY = true;
061
062  /**
063   * Computes the length of the given null-terminated string.
064   * <p>
065   * <strong>NOTE: This method may read undefined memory if {@link #ALLOW_READS_OF_UNDEFINED_MEMORY}
066   * is true. However, the behaviour of this method is always well-defined.</strong>
067   *
068   * @param ptr address of string in memory
069   * @return the length of the string in bytes
070   */
071  public static int strlen(Address ptr) {
072    int length = 0;
073    // Read words at a time for better performance. This should be unproblematic unless
074    // you're using Valgrind or a similar tool to check for errors. Memory protection
075    // can't be a problem because the reads will be aligned and mprotect works at the
076    // page level (or even coarser).
077    if (ALLOW_READS_OF_UNDEFINED_MEMORY) {
078      // align address to size of machine
079      while (!ptr.toWord().and(Word.fromIntZeroExtend(BYTES_IN_ADDRESS - 1)).isZero()) {
080        byte bits = ptr.loadByte(Offset.fromIntZeroExtend(length));
081        if (bits == 0) {
082          return length;
083        }
084        length++;
085      }
086      // Ascii characters are normally in the range 1 to 128, if we subtract 1
087      // from each byte and look if the top bit of the byte is set then if it is
088      // the chances are the byte's value is 0. Loop over words doing this quick
089      // test and then do byte by byte tests when we think we have the 0
090      Word onesToSubtract;
091      Word maskToTestHighBits;
092      if (VM.BuildFor32Addr) {
093        onesToSubtract     = Word.fromIntZeroExtend(0x01010101);
094        maskToTestHighBits = Word.fromIntZeroExtend(0x80808080);
095      } else {
096        onesToSubtract     = Word.fromLong(0x0101010101010101L);
097        maskToTestHighBits = Word.fromLong(0x8080808080808080L);
098      }
099      while (true) {
100        Word bytes = ptr.loadWord(Offset.fromIntZeroExtend(length));
101        if (!bytes.minus(onesToSubtract).and(maskToTestHighBits).isZero()) {
102          if (VM.LittleEndian) {
103            for (int byteOff = 0; byteOff < BYTES_IN_ADDRESS; byteOff++) {
104              if (bytes.and(Word.fromIntZeroExtend(0xFF)).isZero()) {
105                return length + byteOff;
106              }
107              bytes = bytes.rshl(BITS_IN_BYTE);
108            }
109          } else {
110            for (int byteOff = BYTES_IN_ADDRESS - 1; byteOff >= 0; byteOff--) {
111              if (bytes.rshl(byteOff * 8).and(Word.fromIntZeroExtend(0xFF)).isZero()) {
112                return length + (BYTES_IN_ADDRESS - 1 - byteOff);
113              }
114            }
115          }
116        }
117        length += BYTES_IN_ADDRESS;
118      }
119    } else {
120      // Avoid reads of undefined memory by proceeding one byte at a time
121      Address currentAddress = ptr;
122      byte currentByte = currentAddress.loadByte(Offset.fromIntSignExtend(length));
123      while (currentByte != 0) {
124        length++;
125        currentByte = currentAddress.loadByte(Offset.fromIntSignExtend(length));
126      }
127      return length;
128    }
129  }
130  /**
131   * Given an address in C that points to a null-terminated string,
132   * create a new Java byte[] with a copy of the string.
133   *
134   * @param stringAddress an address in C space for a string
135   * @return a new Java byte[]
136   */
137  public static byte[] createByteArrayFromC(Address stringAddress) {
138
139    int length = strlen(stringAddress);
140    byte[] contents = new byte[length];
141    Memory.memcopy(Magic.objectAsAddress(contents), stringAddress, length);
142
143    return contents;
144  }
145
146  private static String createString(CharsetDecoder csd, ByteBuffer bbuf) throws CharacterCodingException {
147    char[] v;
148    int o;
149    int c;
150    CharBuffer cbuf = csd.decode(bbuf);
151    if (cbuf.hasArray()) {
152      v = cbuf.array();
153      o = cbuf.position();
154      c = cbuf.remaining();
155    } else {
156      // Doubt this will happen. But just in case.
157      v = new char[cbuf.remaining()];
158      cbuf.get(v);
159      o = 0;
160      c = v.length;
161    }
162    return java.lang.JikesRVMSupport.newStringWithoutCopy(v, o, c);
163  }
164  /**
165   * Given an address in C that points to a null-terminated string,
166   * create a new Java String with a copy of the string.
167   *
168   * @param stringAddress an address in C space for a string
169   * @return a new Java String
170   */
171  public static String createStringFromC(Address stringAddress) {
172    if (VM.fullyBooted) {
173      try {
174        String encoding = System.getProperty("file.encoding");
175        CharsetDecoder csd = Charset.forName(encoding).newDecoder();
176        csd.onMalformedInput(CodingErrorAction.REPLACE);
177        csd.onUnmappableCharacter(CodingErrorAction.REPLACE);
178        ByteBuffer bbuf =
179          java.nio.JikesRVMSupport.newDirectByteBuffer(stringAddress,
180                                                       strlen(stringAddress));
181        return createString(csd, bbuf);
182      } catch (Exception ex) {
183        // Any problems fall through to default encoding
184      }
185    }
186    // Can't do real Char encoding until VM is fully booted.
187    // All Strings encountered during booting must be ascii
188    byte[] tmp = createByteArrayFromC(stringAddress);
189    return StringUtilities.asciiBytesToString(tmp);
190  }
191  /**
192   * Given an address in C that points to a null-terminated string,
193   * create a new UTF encoded Java String with a copy of the string.
194   *
195   * @param stringAddress an address in C space for a string
196   * @return a new Java String
197   */
198  public static String createUTFStringFromC(Address stringAddress) {
199    final boolean USE_LIBRARY_CODEC = false;
200    byte[] tmp;
201    ByteBuffer bbuf;
202    if (VM.fullyBooted) {
203      try {
204        bbuf = java.nio.JikesRVMSupport.newDirectByteBuffer(stringAddress,
205                                                            strlen(stringAddress));
206        if (USE_LIBRARY_CODEC) {
207          CharsetDecoder csd = Charset.forName("UTF8").newDecoder();
208          return createString(csd, bbuf);
209        } else {
210          return UTF8Convert.fromUTF8(bbuf);
211        }
212      } catch (Exception ex) {
213        // Any problems fall through to default encoding
214      }
215    }
216    // Can't do real Char encoding until VM is fully booted.
217    // All Strings encountered during booting must be ascii
218    tmp = createByteArrayFromC(stringAddress);
219    return StringUtilities.asciiBytesToString(tmp);
220  }
221
222
223  /**
224   * Converts a String into a a malloced regions.
225   *
226   * @param str the string to convert
227   * @param copyBuffer start address for a newly allocated buffer
228   * @param len length of the buffer
229   */
230  public static void createUTFForCFromString(String str, Address copyBuffer, int len) {
231    ByteBuffer bbuf =
232      java.nio.JikesRVMSupport.newDirectByteBuffer(copyBuffer, len);
233
234    final boolean USE_LIBRARY_CODEC = false;
235    if (USE_LIBRARY_CODEC) {
236      char[] strChars = java.lang.JikesRVMSupport.getBackingCharArray(str);
237      int strOffset = java.lang.JikesRVMSupport.getStringOffset(str);
238      int strLen = java.lang.JikesRVMSupport.getStringLength(str);
239      CharBuffer cbuf = CharBuffer.wrap(strChars, strOffset, strLen);
240      CharsetEncoder cse = Charset.forName("UTF8").newEncoder();
241      cse.encode(cbuf, bbuf, true);
242    } else {
243      UTF8Convert.toUTF8(str, bbuf);
244    }
245    // store terminating zero
246    copyBuffer.store((byte)0, Offset.fromIntZeroExtend(len - 1));
247  }
248
249  /**
250   * A JNI helper function, to set the value pointed to by a C pointer
251   * of type (jboolean *).
252   * @param boolPtr Native pointer to a jboolean variable to be set.   May be
253   *            the NULL pointer, in which case we do nothing.
254   * @param val Value to set it to (usually TRUE)
255   *
256   */
257  static void setBoolStar(Address boolPtr, boolean val) {
258    if (boolPtr.isZero()) {
259      return;
260    }
261    if (val) {
262      boolPtr.store((byte)1);
263    } else {
264      boolPtr.store((byte)0);
265    }
266  }
267
268  /**
269   * Dispatch method call
270   * @param obj this pointer for method to be invoked, or null if method is static
271   * @param mr reference to method to be invoked
272   * @param args argument array
273   * @param expectedReturnType a type reference for the expected return type
274   * @param nonVirtual should invocation be of the given method or should we use virtual dispatch on the object?
275   * @return return value of the method (boxed if primitive)
276   * @throws InvocationTargetException when the reflective method call fails
277   */
278  protected static Object callMethod(Object obj, MethodReference mr, Object[] args, TypeReference expectedReturnType, boolean nonVirtual) throws InvocationTargetException {
279    RVMMethod targetMethod = mr.resolve();
280    TypeReference returnType = targetMethod.getReturnType();
281
282    if (JNIFunctions.traceJNI) {
283      VM.sysWriteln("JNI CallXXXMethod: " + mr);
284    }
285
286    if (expectedReturnType == null) {   // for reference return type
287      if (!returnType.isReferenceType()) {
288        throw new IllegalArgumentException("Wrong return type for method (" + targetMethod + "): expected reference type instead of " + returnType);
289      }
290    } else { // for primitive return type
291      if (!returnType.definitelySame(expectedReturnType)) {
292        throw new IllegalArgumentException("Wrong return type for method (" + targetMethod + "): expected " + expectedReturnType + " instead of " + returnType);
293      }
294    }
295    // invoke the method
296    return Reflection.invoke(targetMethod, null, obj, args, nonVirtual);
297  }
298
299
300  /**
301   * Dispatch method call, arguments in jvalue*
302   * @param env the JNI environemnt for the thread
303   * @param objJREF a JREF index for the object
304   * @param methodID id of a MethodReference
305   * @param argAddress address of an array of jvalues (jvalue*)
306   * @param expectedReturnType a type reference for the expected return type
307   * @param nonVirtual should invocation be of the given method or should we use virtual dispatch on the object?
308   * @return return value of the method (boxed if primitive)
309   * @throws InvocationTargetException when reflective invocation fails
310   */
311  protected static Object callMethodJValuePtr(JNIEnvironment env, int objJREF, int methodID, Address argAddress, TypeReference expectedReturnType, boolean nonVirtual) throws InvocationTargetException {
312    RuntimeEntrypoints.checkJNICountDownToGC();
313    try {
314      Object obj = env.getJNIRef(objJREF);
315      MethodReference mr = MemberReference.getMethodRef(methodID);
316      Object[] args = packageParametersFromJValuePtr(mr, argAddress);
317      return callMethod(obj, mr, args, expectedReturnType, nonVirtual);
318    } catch (Throwable unexpected) {
319      if (JNIFunctions.traceJNI) unexpected.printStackTrace(System.err);
320      env.recordException(unexpected);
321      return 0;
322    }
323  }
324
325  /**
326   * Repackage the arguments passed as an array of jvalue into an array of Object,
327   * used by the JNI functions CallStatic&lt;type&gt;MethodA
328   * @param targetMethod the target {@link MethodReference}
329   * @param argAddress an address into the C space for the array of jvalue unions
330   * @return an Object array holding the arguments wrapped at Objects
331   */
332  protected static Object[] packageParametersFromJValuePtr(MethodReference targetMethod, Address argAddress) {
333    TypeReference[] argTypes = targetMethod.getParameterTypes();
334    int argCount = argTypes.length;
335    Object[] argObjectArray = new Object[argCount];
336
337    // get the JNIEnvironment for this thread in case we need to dereference any object arg
338    JNIEnvironment env = RVMThread.getCurrentThread().getJNIEnv();
339
340    Address addr = argAddress;
341    for (int i = 0; i < argCount; i++, addr = addr.plus(BYTES_IN_LONG)) {
342      // convert and wrap the argument according to the expected type
343      if (argTypes[i].isReferenceType()) {
344        // Avoid endianness issues by loading the whole slot
345        Word wholeSlot = addr.loadWord();
346        // for object, the arg is a JREF index, dereference to get the real object
347        int JREFindex = wholeSlot.toInt();
348        argObjectArray[i] = env.getJNIRef(JREFindex);
349      } else if (argTypes[i].isIntType()) {
350        argObjectArray[i] = addr.loadInt();
351      } else if (argTypes[i].isLongType()) {
352        argObjectArray[i] = addr.loadLong();
353      } else if (argTypes[i].isBooleanType()) {
354        // the 0/1 bit is stored in the high byte
355        argObjectArray[i] = addr.loadByte() != 0;
356      } else if (argTypes[i].isByteType()) {
357        // the target byte is stored in the high byte
358        argObjectArray[i] = addr.loadByte();
359      } else if (argTypes[i].isCharType()) {
360        // char is stored in the high 2 bytes
361        argObjectArray[i] = addr.loadChar();
362      } else if (argTypes[i].isShortType()) {
363        // short is stored in the high 2 bytes
364        argObjectArray[i] = addr.loadShort();
365      } else if (argTypes[i].isFloatType()) {
366        argObjectArray[i] = addr.loadFloat();
367      } else {
368        if (VM.VerifyAssertions) VM._assert(argTypes[i].isDoubleType());
369        argObjectArray[i] = addr.loadDouble();
370      }
371    }
372    return argObjectArray;
373  }
374
375  /**
376   * @param functionTableIndex slot in the JNI function table
377   * @return {@code true} if the function is implemented in Java (i.e.
378   *  its code is in org.jikesrvm.jni.JNIFunctions) and {@code false}
379   *  if the function is implemented in C (i.e. its code is in the
380   *  bootloader)
381   */
382  @Uninterruptible
383  public static boolean implementedInJava(int functionTableIndex) {
384    if (VM.BuildForPowerPC) {
385      return true;
386    }
387    // Indexes for the JNI functions are fixed according to the JNI
388    // specification and there is no need for links from functions
389    // to their indexes for anything else, so they're hardcoded here.
390    switch (functionTableIndex) {
391      case 28:
392      case 34: case 37: case 40: case 43:
393      case 46: case 49: case 52: case 55:
394      case 58: case 61: case 64: case 67:
395      case 70: case 73: case 76: case 79:
396      case 82: case 85: case 88: case 91:
397      case 114: case 117: case 120: case 123:
398      case 126: case 129: case 132: case 135:
399      case 138: case 141:
400        return false;
401      default:
402        return true;
403    }
404  }
405
406}