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<type>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}