/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.jni;
import static org.jikesrvm.runtime.JavaSizeConstants.BITS_IN_BYTE;
import static org.jikesrvm.runtime.JavaSizeConstants.BYTES_IN_LONG;
import static org.jikesrvm.runtime.UnboxedSizeConstants.BYTES_IN_ADDRESS;
import java.lang.reflect.InvocationTargetException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.MemberReference;
import org.jikesrvm.classloader.MethodReference;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.classloader.UTF8Convert;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.runtime.Memory;
import org.jikesrvm.runtime.Reflection;
import org.jikesrvm.runtime.RuntimeEntrypoints;
import org.jikesrvm.scheduler.RVMThread;
import org.jikesrvm.util.StringUtilities;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;
import org.vmmagic.unboxed.Word;
/**
* Platform independent utility functions called from JNIFunctions
* (cannot be placed in JNIFunctions because methods
* there are specially compiled to be called from native).
*
* @see JNIFunctions
*/
public abstract class JNIGenericHelpers {
/**
* Whether to allow reads of undefined memory when computing strlen(..).
* This is enabled by default to improve performance. It will never cause
* undefined behaviour but it may turn up as a false positive when using
* tools such as Valgrind.
*/
private static final boolean ALLOW_READS_OF_UNDEFINED_MEMORY = true;
/**
* Computes the length of the given null-terminated string.
* <p>
* <strong>NOTE: This method may read undefined memory if {@link #ALLOW_READS_OF_UNDEFINED_MEMORY}
* is true. However, the behaviour of this method is always well-defined.</strong>
*
* @param ptr address of string in memory
* @return the length of the string in bytes
*/
public static int strlen(Address ptr) {
int length = 0;
// Read words at a time for better performance. This should be unproblematic unless
// you're using Valgrind or a similar tool to check for errors. Memory protection
// can't be a problem because the reads will be aligned and mprotect works at the
// page level (or even coarser).
if (ALLOW_READS_OF_UNDEFINED_MEMORY) {
// align address to size of machine
while (!ptr.toWord().and(Word.fromIntZeroExtend(BYTES_IN_ADDRESS - 1)).isZero()) {
byte bits = ptr.loadByte(Offset.fromIntZeroExtend(length));
if (bits == 0) {
return length;
}
length++;
}
// Ascii characters are normally in the range 1 to 128, if we subtract 1
// from each byte and look if the top bit of the byte is set then if it is
// the chances are the byte's value is 0. Loop over words doing this quick
// test and then do byte by byte tests when we think we have the 0
Word onesToSubtract;
Word maskToTestHighBits;
if (VM.BuildFor32Addr) {
onesToSubtract = Word.fromIntZeroExtend(0x01010101);
maskToTestHighBits = Word.fromIntZeroExtend(0x80808080);
} else {
onesToSubtract = Word.fromLong(0x0101010101010101L);
maskToTestHighBits = Word.fromLong(0x8080808080808080L);
}
while (true) {
Word bytes = ptr.loadWord(Offset.fromIntZeroExtend(length));
if (!bytes.minus(onesToSubtract).and(maskToTestHighBits).isZero()) {
if (VM.LittleEndian) {
for (int byteOff = 0; byteOff < BYTES_IN_ADDRESS; byteOff++) {
if (bytes.and(Word.fromIntZeroExtend(0xFF)).isZero()) {
return length + byteOff;
}
bytes = bytes.rshl(BITS_IN_BYTE);
}
} else {
for (int byteOff = BYTES_IN_ADDRESS - 1; byteOff >= 0; byteOff--) {
if (bytes.rshl(byteOff * 8).and(Word.fromIntZeroExtend(0xFF)).isZero()) {
return length + (BYTES_IN_ADDRESS - 1 - byteOff);
}
}
}
}
length += BYTES_IN_ADDRESS;
}
} else {
// Avoid reads of undefined memory by proceeding one byte at a time
Address currentAddress = ptr;
byte currentByte = currentAddress.loadByte(Offset.fromIntSignExtend(length));
while (currentByte != 0) {
length++;
currentByte = currentAddress.loadByte(Offset.fromIntSignExtend(length));
}
return length;
}
}
/**
* Given an address in C that points to a null-terminated string,
* create a new Java byte[] with a copy of the string.
*
* @param stringAddress an address in C space for a string
* @return a new Java byte[]
*/
public static byte[] createByteArrayFromC(Address stringAddress) {
int length = strlen(stringAddress);
byte[] contents = new byte[length];
Memory.memcopy(Magic.objectAsAddress(contents), stringAddress, length);
return contents;
}
private static String createString(CharsetDecoder csd, ByteBuffer bbuf) throws CharacterCodingException {
char[] v;
int o;
int c;
CharBuffer cbuf = csd.decode(bbuf);
if (cbuf.hasArray()) {
v = cbuf.array();
o = cbuf.position();
c = cbuf.remaining();
} else {
// Doubt this will happen. But just in case.
v = new char[cbuf.remaining()];
cbuf.get(v);
o = 0;
c = v.length;
}
return java.lang.JikesRVMSupport.newStringWithoutCopy(v, o, c);
}
/**
* Given an address in C that points to a null-terminated string,
* create a new Java String with a copy of the string.
*
* @param stringAddress an address in C space for a string
* @return a new Java String
*/
public static String createStringFromC(Address stringAddress) {
if (VM.fullyBooted) {
try {
String encoding = System.getProperty("file.encoding");
CharsetDecoder csd = Charset.forName(encoding).newDecoder();
csd.onMalformedInput(CodingErrorAction.REPLACE);
csd.onUnmappableCharacter(CodingErrorAction.REPLACE);
ByteBuffer bbuf =
java.nio.JikesRVMSupport.newDirectByteBuffer(stringAddress,
strlen(stringAddress));
return createString(csd, bbuf);
} catch (Exception ex) {
// Any problems fall through to default encoding
}
}
// Can't do real Char encoding until VM is fully booted.
// All Strings encountered during booting must be ascii
byte[] tmp = createByteArrayFromC(stringAddress);
return StringUtilities.asciiBytesToString(tmp);
}
/**
* Given an address in C that points to a null-terminated string,
* create a new UTF encoded Java String with a copy of the string.
*
* @param stringAddress an address in C space for a string
* @return a new Java String
*/
public static String createUTFStringFromC(Address stringAddress) {
final boolean USE_LIBRARY_CODEC = false;
byte[] tmp;
ByteBuffer bbuf;
if (VM.fullyBooted) {
try {
bbuf = java.nio.JikesRVMSupport.newDirectByteBuffer(stringAddress,
strlen(stringAddress));
if (USE_LIBRARY_CODEC) {
CharsetDecoder csd = Charset.forName("UTF8").newDecoder();
return createString(csd, bbuf);
} else {
return UTF8Convert.fromUTF8(bbuf);
}
} catch (Exception ex) {
// Any problems fall through to default encoding
}
}
// Can't do real Char encoding until VM is fully booted.
// All Strings encountered during booting must be ascii
tmp = createByteArrayFromC(stringAddress);
return StringUtilities.asciiBytesToString(tmp);
}
/**
* Converts a String into a a malloced regions.
*
* @param str the string to convert
* @param copyBuffer start address for a newly allocated buffer
* @param len length of the buffer
*/
public static void createUTFForCFromString(String str, Address copyBuffer, int len) {
ByteBuffer bbuf =
java.nio.JikesRVMSupport.newDirectByteBuffer(copyBuffer, len);
final boolean USE_LIBRARY_CODEC = false;
if (USE_LIBRARY_CODEC) {
char[] strChars = java.lang.JikesRVMSupport.getBackingCharArray(str);
int strOffset = java.lang.JikesRVMSupport.getStringOffset(str);
int strLen = java.lang.JikesRVMSupport.getStringLength(str);
CharBuffer cbuf = CharBuffer.wrap(strChars, strOffset, strLen);
CharsetEncoder cse = Charset.forName("UTF8").newEncoder();
cse.encode(cbuf, bbuf, true);
} else {
UTF8Convert.toUTF8(str, bbuf);
}
// store terminating zero
copyBuffer.store((byte)0, Offset.fromIntZeroExtend(len - 1));
}
/**
* A JNI helper function, to set the value pointed to by a C pointer
* of type (jboolean *).
* @param boolPtr Native pointer to a jboolean variable to be set. May be
* the NULL pointer, in which case we do nothing.
* @param val Value to set it to (usually TRUE)
*
*/
static void setBoolStar(Address boolPtr, boolean val) {
if (boolPtr.isZero()) {
return;
}
if (val) {
boolPtr.store((byte)1);
} else {
boolPtr.store((byte)0);
}
}
/**
* Dispatch method call
* @param obj this pointer for method to be invoked, or null if method is static
* @param mr reference to method to be invoked
* @param args argument array
* @param expectedReturnType a type reference for the expected return type
* @param nonVirtual should invocation be of the given method or should we use virtual dispatch on the object?
* @return return value of the method (boxed if primitive)
* @throws InvocationTargetException when the reflective method call fails
*/
protected static Object callMethod(Object obj, MethodReference mr, Object[] args, TypeReference expectedReturnType, boolean nonVirtual) throws InvocationTargetException {
RVMMethod targetMethod = mr.resolve();
TypeReference returnType = targetMethod.getReturnType();
if (JNIFunctions.traceJNI) {
VM.sysWriteln("JNI CallXXXMethod: " + mr);
}
if (expectedReturnType == null) { // for reference return type
if (!returnType.isReferenceType()) {
throw new IllegalArgumentException("Wrong return type for method (" + targetMethod + "): expected reference type instead of " + returnType);
}
} else { // for primitive return type
if (!returnType.definitelySame(expectedReturnType)) {
throw new IllegalArgumentException("Wrong return type for method (" + targetMethod + "): expected " + expectedReturnType + " instead of " + returnType);
}
}
// invoke the method
return Reflection.invoke(targetMethod, null, obj, args, nonVirtual);
}
/**
* Dispatch method call, arguments in jvalue*
* @param env the JNI environemnt for the thread
* @param objJREF a JREF index for the object
* @param methodID id of a MethodReference
* @param argAddress address of an array of jvalues (jvalue*)
* @param expectedReturnType a type reference for the expected return type
* @param nonVirtual should invocation be of the given method or should we use virtual dispatch on the object?
* @return return value of the method (boxed if primitive)
* @throws InvocationTargetException when reflective invocation fails
*/
protected static Object callMethodJValuePtr(JNIEnvironment env, int objJREF, int methodID, Address argAddress, TypeReference expectedReturnType, boolean nonVirtual) throws InvocationTargetException {
RuntimeEntrypoints.checkJNICountDownToGC();
try {
Object obj = env.getJNIRef(objJREF);
MethodReference mr = MemberReference.getMethodRef(methodID);
Object[] args = packageParametersFromJValuePtr(mr, argAddress);
return callMethod(obj, mr, args, expectedReturnType, nonVirtual);
} catch (Throwable unexpected) {
if (JNIFunctions.traceJNI) unexpected.printStackTrace(System.err);
env.recordException(unexpected);
return 0;
}
}
/**
* Repackage the arguments passed as an array of jvalue into an array of Object,
* used by the JNI functions CallStatic<type>MethodA
* @param targetMethod the target {@link MethodReference}
* @param argAddress an address into the C space for the array of jvalue unions
* @return an Object array holding the arguments wrapped at Objects
*/
protected static Object[] packageParametersFromJValuePtr(MethodReference targetMethod, Address argAddress) {
TypeReference[] argTypes = targetMethod.getParameterTypes();
int argCount = argTypes.length;
Object[] argObjectArray = new Object[argCount];
// get the JNIEnvironment for this thread in case we need to dereference any object arg
JNIEnvironment env = RVMThread.getCurrentThread().getJNIEnv();
Address addr = argAddress;
for (int i = 0; i < argCount; i++, addr = addr.plus(BYTES_IN_LONG)) {
// convert and wrap the argument according to the expected type
if (argTypes[i].isReferenceType()) {
// Avoid endianness issues by loading the whole slot
Word wholeSlot = addr.loadWord();
// for object, the arg is a JREF index, dereference to get the real object
int JREFindex = wholeSlot.toInt();
argObjectArray[i] = env.getJNIRef(JREFindex);
} else if (argTypes[i].isIntType()) {
argObjectArray[i] = addr.loadInt();
} else if (argTypes[i].isLongType()) {
argObjectArray[i] = addr.loadLong();
} else if (argTypes[i].isBooleanType()) {
// the 0/1 bit is stored in the high byte
argObjectArray[i] = addr.loadByte() != 0;
} else if (argTypes[i].isByteType()) {
// the target byte is stored in the high byte
argObjectArray[i] = addr.loadByte();
} else if (argTypes[i].isCharType()) {
// char is stored in the high 2 bytes
argObjectArray[i] = addr.loadChar();
} else if (argTypes[i].isShortType()) {
// short is stored in the high 2 bytes
argObjectArray[i] = addr.loadShort();
} else if (argTypes[i].isFloatType()) {
argObjectArray[i] = addr.loadFloat();
} else {
if (VM.VerifyAssertions) VM._assert(argTypes[i].isDoubleType());
argObjectArray[i] = addr.loadDouble();
}
}
return argObjectArray;
}
/**
* @param functionTableIndex slot in the JNI function table
* @return {@code true} if the function is implemented in Java (i.e.
* its code is in org.jikesrvm.jni.JNIFunctions) and {@code false}
* if the function is implemented in C (i.e. its code is in the
* bootloader)
*/
@Uninterruptible
public static boolean implementedInJava(int functionTableIndex) {
if (VM.BuildForPowerPC) {
return true;
}
// Indexes for the JNI functions are fixed according to the JNI
// specification and there is no need for links from functions
// to their indexes for anything else, so they're hardcoded here.
switch (functionTableIndex) {
case 28:
case 34: case 37: case 40: case 43:
case 46: case 49: case 52: case 55:
case 58: case 61: case 64: case 67:
case 70: case 73: case 76: case 79:
case 82: case 85: case 88: case 91:
case 114: case 117: case 120: case 123:
case 126: case 129: case 132: case 135:
case 138: case 141:
return false;
default:
return true;
}
}
}