1 /*
   2  * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import jdk.internal.misc.Unsafe;
  25 import java.lang.reflect.Field;
  26 
  27 /*
  28  * @test
  29  * @summary Test Unsafe.copyMemory
  30  * @modules java.base/jdk.internal.misc
  31  */
  32 public class CopyMemory {
  33     private static final boolean DEBUG = Boolean.getBoolean("CopyMemory.DEBUG");
  34 
  35     public static final long KB = 1024;
  36     public static final long MB = KB * 1024;
  37     public static final long GB = MB * 1024;
  38 
  39     private static final Unsafe UNSAFE;
  40     private static final int SMALL_COPY_SIZE = 32;
  41     private static final int BASE_ALIGNMENT = 16;
  42 
  43     static {
  44         try {
  45             Field f = jdk.internal.misc.Unsafe.class.getDeclaredField("theUnsafe");
  46             f.setAccessible(true);
  47             UNSAFE = (jdk.internal.misc.Unsafe) f.get(null);
  48         } catch (Exception e) {
  49             throw new RuntimeException("Unable to get Unsafe instance.", e);
  50         }
  51     }
  52 
  53     private static long alignDown(long value, long alignment) {
  54         return value & ~(alignment - 1);
  55     }
  56 
  57     private static long alignUp(long value, long alignment) {
  58         return (value + alignment - 1) & ~(alignment - 1);
  59     }
  60 
  61     private static boolean isAligned(long value, long alignment) {
  62         return value == alignDown(value, alignment);
  63     }
  64 
  65     private CopyMemory() {
  66     }
  67 
  68     /**
  69      * Generate verification data for a given offset
  70      *
  71      * The verification data is used to verify that the correct bytes
  72      * have indeed been copied and byte swapped.
  73      *
  74      * The data is generated based on the offset (in bytes) into the
  75      * source buffer. For a native buffer the offset is relative to
  76      * the base pointer. For a heap array it is relative to the
  77      * address of the first array element.
  78      *
  79      * This method will return the result of doing an elementSize byte
  80      * read starting at offset (in bytes).
  81      *
  82      * @param offset offset into buffer
  83      * @param elemSize size (in bytes) of the element
  84      *
  85      * @return the verification data, only the least significant
  86      * elemSize*8 bits are set, zero extended
  87      */
  88     private long getVerificationDataForOffset(long offset, long elemSize) {
  89         byte[] bytes = new byte[(int)elemSize];
  90 
  91         for (long i = 0; i < elemSize; i++) {
  92             bytes[(int)i] = (byte)(offset + i);
  93         }
  94 
  95         long o = UNSAFE.arrayBaseOffset(byte[].class);
  96 
  97         switch ((int)elemSize) {
  98         case 1: return Byte.toUnsignedLong(UNSAFE.getByte(bytes, o));
  99         case 2: return Short.toUnsignedLong(UNSAFE.getShortUnaligned(bytes, o));
 100         case 4: return Integer.toUnsignedLong(UNSAFE.getIntUnaligned(bytes, o));
 101         case 8: return UNSAFE.getLongUnaligned(bytes, o);
 102         default: throw new IllegalArgumentException("Invalid element size: " + elemSize);
 103         }
 104     }
 105 
 106     /**
 107      * Verify byte swapped data
 108      *
 109      * @param ptr the data to verify
 110      * @param srcOffset the srcOffset (in bytes) from which the copy started,
 111      *        used as key to regenerate the verification data
 112      * @param dstOffset the offset (in bytes) in the array at which to start
 113      *        the verification, relative to the first element in the array
 114      * @param size size (in bytes) of data to to verify
 115      * @param elemSize size (in bytes) of the individual array elements
 116      *
 117      * @throws RuntimeException if an error is found
 118      */
 119     private void verifySwappedData(GenericPointer ptr, long srcOffset, long dstOffset, long size, long elemSize) {
 120         for (long offset = 0; offset < size; offset += elemSize) {
 121             long expectedUnswapped = getVerificationDataForOffset(srcOffset + offset, elemSize);
 122             long expected = byteSwap(expectedUnswapped, elemSize);
 123 
 124             long actual = getArrayElem(ptr, dstOffset + offset, elemSize);
 125 
 126             if (expected != actual) {
 127                 throw new RuntimeException("srcOffset: 0x" + Long.toHexString(srcOffset) +
 128                                            " dstOffset: 0x" + Long.toHexString(dstOffset) +
 129                                            " size: 0x" + Long.toHexString(size) +
 130                                            " offset: 0x" + Long.toHexString(offset) +
 131                                            " expectedUnswapped: 0x" + Long.toHexString(expectedUnswapped) +
 132                                            " expected: 0x" + Long.toHexString(expected) +
 133                                            " != actual: 0x" + Long.toHexString(actual));
 134             }
 135         }
 136     }
 137 
 138     /**
 139      * Initialize an array with verification friendly data
 140      *
 141      * @param ptr pointer to the data to initialize
 142      * @param size size (in bytes) of the data
 143      * @param elemSize size (in bytes) of the individual elements
 144      */
 145     private void initVerificationData(GenericPointer ptr, long size, long elemSize) {
 146         for (long offset = 0; offset < size; offset++) {
 147             byte data = (byte)getVerificationDataForOffset(offset, 1);
 148 
 149             if (ptr.isOnHeap()) {
 150                 UNSAFE.putByte(ptr.getObject(), ptr.getOffset() + offset, data);
 151             } else {
 152                 UNSAFE.putByte(ptr.getOffset() + offset, data);
 153             }
 154         }
 155     }
 156 
 157     /**
 158      * Allocate a primitive array
 159      *
 160      * @param size size (in bytes) of all the array elements (elemSize * length)
 161      * @param elemSize the size of the array elements
 162      *
 163      * @return a newly allocated primitive array
 164      */
 165     Object allocArray(long size, long elemSize) {
 166         int length = (int)(size / elemSize);
 167 
 168         switch ((int)elemSize) {
 169         case 1: return new byte[length];
 170         case 2: return new short[length];
 171         case 4: return new int[length];
 172         case 8: return new long[length];
 173         default:
 174             throw new IllegalArgumentException("Invalid element size: " + elemSize);
 175         }
 176     }
 177 
 178     /**
 179      * Get the value of a primitive array entry
 180      *
 181      * @param ptr pointer to the data
 182      * @param offset offset (in bytes) of the array element, relative to the first element in the array
 183      *
 184      * @return the array element, as an unsigned long
 185      */
 186     private long getArrayElem(GenericPointer ptr, long offset, long elemSize) {
 187         if (ptr.isOnHeap()) {
 188             Object o = ptr.getObject();
 189             int index = (int)(offset / elemSize);
 190 
 191             if (o instanceof short[]) {
 192                 short[] arr = (short[])o;
 193                 return Short.toUnsignedLong(arr[index]);
 194             } else if (o instanceof int[]) {
 195                 int[] arr = (int[])o;
 196                 return Integer.toUnsignedLong(arr[index]);
 197             } else if (o instanceof long[]) {
 198                 long[] arr = (long[])o;
 199                 return arr[index];
 200             } else {
 201                 throw new IllegalArgumentException("Invalid object type: " + o.getClass().getName());
 202             }
 203         } else {
 204             long addr = ptr.getOffset() + offset;
 205 
 206             switch ((int)elemSize) {
 207             case 1: return Byte.toUnsignedLong(UNSAFE.getByte(addr));
 208             case 2: return Short.toUnsignedLong(UNSAFE.getShortUnaligned(null, addr));
 209             case 4: return Integer.toUnsignedLong(UNSAFE.getIntUnaligned(null, addr));
 210             case 8: return UNSAFE.getLongUnaligned(null, addr);
 211             default: throw new IllegalArgumentException("Invalid element size: " + elemSize);
 212             }
 213         }
 214     }
 215 
 216     private void putValue(long addr, long elemSize, long value) {
 217         switch ((int)elemSize) {
 218         case 1: UNSAFE.putByte(addr, (byte)value); break;
 219         case 2: UNSAFE.putShortUnaligned(null, addr, (short)value); break;
 220         case 4: UNSAFE.putIntUnaligned(null, addr, (int)value); break;
 221         case 8: UNSAFE.putLongUnaligned(null, addr, value); break;
 222         default: throw new IllegalArgumentException("Invalid element size: " + elemSize);
 223         }
 224     }
 225 
 226     /**
 227      * Get the size of the elements for an array
 228      *
 229      * @param o a primitive heap array
 230      *
 231      * @return the size (in bytes) of the individual array elements
 232      */
 233     private long getArrayElemSize(Object o) {
 234         if (o instanceof short[]) {
 235             return 2;
 236         } else if (o instanceof int[]) {
 237             return 4;
 238         } else if (o instanceof long[]) {
 239             return 8;
 240         } else {
 241             throw new IllegalArgumentException("Invalid object type: " + o.getClass().getName());
 242         }
 243     }
 244 
 245     /**
 246      * Byte swap a value
 247      *
 248      * @param value the value to swap, only the bytes*8 least significant bits are used
 249      * @param size size (in bytes) of the value
 250      *
 251      * @return the byte swapped value in the bytes*8 least significant bits
 252      */
 253     private long byteSwap(long value, long size) {
 254         switch ((int)size) {
 255         case 2: return Short.toUnsignedLong(Short.reverseBytes((short)value));
 256         case 4: return Integer.toUnsignedLong(Integer.reverseBytes((int)value));
 257         case 8: return Long.reverseBytes(value);
 258         default: throw new IllegalArgumentException("Invalid element size: " + size);
 259         }
 260     }
 261 
 262     /**
 263      * Verify data which has *not* been byte swapped
 264      *
 265      * @param ptr the data to verify
 266      * @param startOffset the offset (in bytes) at which to start the verification
 267      * @param size size (in bytes) of the data to verify
 268      *
 269      * @throws RuntimeException if an error is found
 270      */
 271     private void verifyUnswappedData(GenericPointer ptr, long startOffset, long srcOffset, long size) {
 272         for (long i = 0; i <  size; i++) {
 273             byte expected = (byte)getVerificationDataForOffset(srcOffset + i, 1);
 274 
 275             byte actual;
 276             if (ptr.isOnHeap()) {
 277                 actual = UNSAFE.getByte(ptr.getObject(), ptr.getOffset() + startOffset + i);
 278             } else {
 279                 actual = UNSAFE.getByte(ptr.getOffset() + startOffset + i);
 280             }
 281 
 282             if (expected != actual) {
 283                 throw new RuntimeException("startOffset: 0x" + Long.toHexString(startOffset) +
 284                                            " srcOffset: 0x" + Long.toHexString(srcOffset) +
 285                                            " size: 0x" + Long.toHexString(size) +
 286                                            " i: 0x" + Long.toHexString(i) +
 287                                            " expected: 0x" + Long.toHexString(expected) +
 288                                            " != actual: 0x" + Long.toHexString(actual));
 289             }
 290         }
 291     }
 292 
 293 
 294     /**
 295      * Copy and byte swap data from the source to the destination
 296      *
 297      * This method will pre-populate the whole source and destination
 298      * buffers with verification friendly data. It will then use
 299      * copypMemory to fill part of the destination buffer with
 300      * data from the source. Some space (padding) will be
 301      * left before and after the data in the destination buffer, which
 302      * should not be touched/overwritten by the copy call.
 303      *
 304      * Note: Both source and destination buffers will be overwritten!
 305      *
 306      * @param src source buffer to copy from
 307      * @param srcOffset the offset (in bytes) in the source buffer, relative to
 308      *        the first array element, at which to start reading data
 309      * @param dst destination buffer to copy to
 310      * @param dstOffset the offset (in bytes) in the destination
 311      *        buffer, relative to the first array element, at which to
 312      *        start writing data
 313      * @param bufSize the size (in bytes) of the src and dst arrays
 314      * @param copyBytes the size (in bytes) of the copy to perform,
 315      *        must be a multiple of elemSize
 316      *
 317      * @throws RuntimeException if an error is found
 318      */
 319     private void testCopy(GenericPointer src, long srcOffset,
 320                           GenericPointer dst, long dstOffset,
 321                           long bufSize, long copyBytes) {
 322         if (srcOffset + copyBytes > bufSize) {
 323             throw new IllegalArgumentException(
 324                 "srcOffset (" + srcOffset + ") + copyBytes (" + copyBytes + ") > bufSize (" + bufSize + ")");
 325         }
 326         if (dstOffset + copyBytes > bufSize) {
 327             throw new IllegalArgumentException(
 328                 "dstOffset (" + dstOffset + ") + copyBytes (" + copyBytes + ") > bufSize (" + bufSize + ")");
 329         }
 330 
 331         // Initialize the whole source buffer with a verification friendly pattern (no 0x00 bytes)
 332         initVerificationData(src, bufSize, 1);
 333         if (!src.equals(dst)) {
 334             initVerificationData(dst, bufSize, 1);
 335         }
 336 
 337         if (DEBUG) {
 338             System.out.println("===before===");
 339             for (int offset = 0; offset < bufSize; offset++) {
 340                 long srcValue = getArrayElem(src, offset, 1);
 341                 long dstValue = getArrayElem(dst, offset, 1);
 342 
 343                 System.out.println("offs=0x" + Long.toHexString(Integer.toUnsignedLong(offset)) +
 344                                  " src=0x" + Long.toHexString(srcValue) +
 345                                  " dst=0x" + Long.toHexString(dstValue));
 346             }
 347         }
 348 
 349         // Copy & swap data into the middle of the destination buffer
 350         UNSAFE.copyMemory(src.getObject(),
 351                           src.getOffset() + srcOffset,
 352                           dst.getObject(),
 353                           dst.getOffset() + dstOffset,
 354                           copyBytes);
 355 
 356         if (DEBUG) {
 357             System.out.println("===after===");
 358             for (int offset = 0; offset < bufSize; offset++) {
 359                 long srcValue = getArrayElem(src, offset, 1);
 360                 long dstValue = getArrayElem(dst, offset, 1);
 361 
 362                 System.out.println("offs=0x" + Long.toHexString(Integer.toUnsignedLong(offset)) +
 363                                  " src=0x" + Long.toHexString(srcValue) +
 364                                  " dst=0x" + Long.toHexString(dstValue));
 365             }
 366         }
 367 
 368         // Verify the the front padding is unchanged
 369         verifyUnswappedData(dst, 0, 0, dstOffset);
 370 
 371         // Verify copied data
 372         verifyUnswappedData(dst, dstOffset, srcOffset, copyBytes);
 373 
 374         // Verify that the back back padding is unchanged
 375         long frontAndDataBytes = dstOffset + copyBytes;
 376         long trailingBytes = bufSize - frontAndDataBytes;
 377         verifyUnswappedData(dst, frontAndDataBytes, frontAndDataBytes, trailingBytes);
 378     }
 379 
 380     /**
 381      * Test various configurations copying from one buffer to the other
 382      *
 383      * @param src the source buffer to copy from
 384      * @param dst the destination buffer to copy to
 385      * @param size size (in bytes) of the buffers
 386      * @param elemSize size (in bytes) of the individual elements
 387      *
 388      * @throws RuntimeException if an error is found
 389      */
 390     public void testBufferPair(GenericPointer src, GenericPointer dst, long size, long elemSize) {
 391         // offset in source from which to start reading data
 392         for (long srcOffset = 0; srcOffset < size; srcOffset += (src.isOnHeap() ? elemSize : 1)) {
 393 
 394             // offset in destination at which to start writing data
 395             for (int dstOffset = 0; dstOffset < size; dstOffset += (dst.isOnHeap() ? elemSize : 1)) {
 396 
 397                 // number of bytes to copy
 398                 long maxCopyBytes = Math.min(size - srcOffset, size - dstOffset);
 399                 for (long copyBytes = 0; copyBytes < maxCopyBytes; copyBytes += elemSize) {
 400                     try {
 401                         testCopy(src, srcOffset, dst, dstOffset, size, copyBytes);
 402                     } catch (RuntimeException e) {
 403                         // Wrap the exception in another exception to catch the relevant configuration data
 404                         throw new RuntimeException("testBufferPair: " +
 405                                                    "src=" + src +
 406                                                    " dst=" + dst +
 407                                                    " elemSize=0x" + Long.toHexString(elemSize) +
 408                                                    " copyBytes=0x" + Long.toHexString(copyBytes) +
 409                                                    " srcOffset=0x" + Long.toHexString(srcOffset) +
 410                                                    " dstOffset=0x" + Long.toHexString(dstOffset),
 411                                                    e);
 412                     }
 413                 }
 414             }
 415         }
 416     }
 417 
 418     /**
 419      * Test copying between various permutations of buffers
 420      *
 421      * @param buffers buffers to permute (src x dst)
 422      * @param size size (in bytes) of buffers
 423      * @param elemSize size (in bytes) of individual elements
 424      *
 425      * @throws RuntimeException if an error is found
 426      */
 427     public void testPermuteBuffers(GenericPointer[] buffers, long size, long elemSize) {
 428         for (int srcIndex = 0; srcIndex < buffers.length; srcIndex++) {
 429             for (int dstIndex = 0; dstIndex < buffers.length; dstIndex++) {
 430                 testBufferPair(buffers[srcIndex], buffers[dstIndex], size, elemSize);
 431             }
 432         }
 433     }
 434 
 435     /**
 436      * Test copying of a specific element size
 437      *
 438      * @param size size (in bytes) of buffers to allocate
 439      * @param elemSize size (in bytes) of individual elements
 440      *
 441      * @throws RuntimeException if an error is found
 442      */
 443     private void testElemSize(long size, long elemSize) {
 444         long buf1Raw = 0;
 445         long buf2Raw = 0;
 446 
 447         try {
 448             buf1Raw = UNSAFE.allocateMemory(size + BASE_ALIGNMENT);
 449             long buf1 = alignUp(buf1Raw, BASE_ALIGNMENT);
 450 
 451             buf2Raw = UNSAFE.allocateMemory(size + BASE_ALIGNMENT);
 452             long buf2 = alignUp(buf2Raw, BASE_ALIGNMENT);
 453 
 454             GenericPointer[] buffers = {
 455                 new GenericPointer(buf1),
 456                 new GenericPointer(buf2),
 457                 new GenericPointer(allocArray(size, elemSize)),
 458                 new GenericPointer(allocArray(size, elemSize))
 459             };
 460 
 461             testPermuteBuffers(buffers, size, elemSize);
 462         } finally {
 463             if (buf1Raw != 0) {
 464                 UNSAFE.freeMemory(buf1Raw);
 465             }
 466             if (buf2Raw != 0) {
 467                 UNSAFE.freeMemory(buf2Raw);
 468             }
 469         }
 470     }
 471 
 472     /**
 473      * Verify that small copies work
 474      */
 475     private void testSmallCopy() {
 476         int smallBufSize = SMALL_COPY_SIZE;
 477 
 478         testElemSize(smallBufSize, 1);
 479     }
 480 
 481 
 482     /**
 483      * Verify that large copies work
 484      */
 485     private void testLargeCopy() {
 486         long size = 2 * GB + 8;
 487         long bufRaw = 0;
 488 
 489         // Check that a large native copy succeeds
 490         try {
 491             try {
 492                 bufRaw = UNSAFE.allocateMemory(size + BASE_ALIGNMENT);
 493             } catch (OutOfMemoryError e) {
 494                 // Accept failure, skip test
 495                 return;
 496             }
 497 
 498             long buf = alignUp(bufRaw, BASE_ALIGNMENT);
 499 
 500             UNSAFE.copyMemory(null, buf, null, buf, size);
 501         } catch (Exception e) {
 502             throw new RuntimeException("copyMemory of large buffer failed");
 503         } finally {
 504             if (bufRaw != 0) {
 505                 UNSAFE.freeMemory(bufRaw);
 506             }
 507         }
 508     }
 509 
 510     /**
 511      * Run positive tests
 512      *
 513      * @throws RuntimeException if an error is found
 514      */
 515     private void testPositive() {
 516         testSmallCopy();
 517         testLargeCopy();
 518     }
 519 
 520     /**
 521      * Run negative tests, testing corner cases and the various exceptions
 522      *
 523      * @throws RuntimeException if an error is found
 524      */
 525     private void testNegative() {
 526         long bufRaw = 0;
 527 
 528         try {
 529             bufRaw = UNSAFE.allocateMemory(1024);
 530             long buf = alignUp(bufRaw, BASE_ALIGNMENT);
 531             short[] arr = new short[16];
 532 
 533             // Check illegal sizes
 534             System.out.println("Testing negative size");
 535             try {
 536                 UNSAFE.copyMemory(null, buf, null, buf, -1);
 537                 throw new RuntimeException("copyMemory failed to throw IAE for size=-1");
 538             } catch (IllegalArgumentException e) {
 539                 // good
 540             }
 541 
 542             System.out.println("Testing negative srcOffset");
 543             try {
 544                 // Check that negative srcOffset throws an IAE
 545                 UNSAFE.copyMemory(arr, -1, arr, UNSAFE.arrayBaseOffset(arr.getClass()), 16);
 546                 throw new RuntimeException("copyMemory failed to throw IAE for srcOffset=-1");
 547             } catch (IllegalArgumentException e) {
 548                 // good
 549             }
 550 
 551             System.out.println("Testing negative destOffset");
 552             try {
 553                 // Check that negative dstOffset throws an IAE
 554                 UNSAFE.copyMemory(arr, UNSAFE.arrayBaseOffset(arr.getClass()), arr, -1, 16);
 555                 throw new RuntimeException("copyMemory failed to throw IAE for destOffset=-1");
 556             } catch (IllegalArgumentException e) {
 557                 // good
 558             }
 559 
 560             System.out.println("Testing reference array");
 561             try {
 562                 // Check that a reference array destination throws IAE
 563                 UNSAFE.copyMemory(null, buf, new Object[16], UNSAFE.arrayBaseOffset(Object[].class), 16);
 564                 throw new RuntimeException("copyMemory failed to throw IAE");
 565             } catch (IllegalArgumentException e) {
 566                 // good
 567             }
 568 
 569             // Check that invalid source & dest pointers throw IAEs (only relevant on 32-bit platforms)
 570             if (UNSAFE.addressSize() == 4) {
 571                 long invalidPtr = (long)1 << 35; // Pick a random bit in upper 32 bits
 572 
 573                 try {
 574                     // Check that an invalid (not 32-bit clean) source pointer throws IAE
 575                     UNSAFE.copyMemory(null, invalidPtr, null, buf, 16);
 576                     throw new RuntimeException("copyMemory failed to throw IAE for srcOffset 0x" +
 577                                                Long.toHexString(invalidPtr));
 578                 } catch (IllegalArgumentException e) {
 579                     // good
 580                 }
 581 
 582                 try {
 583                     // Check that an invalid (not 32-bit clean) source pointer throws IAE
 584                     UNSAFE.copyMemory(null, buf, null, invalidPtr, 16);
 585                     throw new RuntimeException("copyMemory failed to throw IAE for destOffset 0x" +
 586                                                Long.toHexString(invalidPtr));
 587                 } catch (IllegalArgumentException e) {
 588                     // good
 589                 }
 590             }
 591         } finally {
 592             if (bufRaw != 0) {
 593                 UNSAFE.freeMemory(bufRaw);
 594             }
 595         }
 596     }
 597 
 598     /**
 599      * Run all tests
 600      *
 601      * @throws RuntimeException if an error is found
 602      */
 603     private void test() {
 604         testPositive();
 605         testNegative();
 606     }
 607 
 608     public static void main(String[] args) {
 609         CopyMemory cs = new CopyMemory();
 610         cs.test();
 611     }
 612 
 613     /**
 614      * Helper class to represent a "pointer" - either a heap array or
 615      * a pointer to a native buffer.
 616      *
 617      * In the case of a native pointer, the Object is null and the offset is
 618      * the absolute address of the native buffer.
 619      *
 620      * In the case of a heap object, the Object is a primitive array, and
 621      * the offset will be set to the base offset to the first element, meaning
 622      * the object and the offset together form a double-register pointer.
 623      */
 624     static class GenericPointer {
 625         private final Object o;
 626         private final long offset;
 627 
 628         private GenericPointer(Object o, long offset) {
 629             this.o = o;
 630             this.offset = offset;
 631         }
 632 
 633         public String toString() {
 634             return "GenericPointer(o={" + o + "}, offset=0x" + Long.toHexString(offset) + ")";
 635         }
 636 
 637         public boolean equals(Object other) {
 638             if (!(other instanceof GenericPointer)) {
 639                 return false;
 640             }
 641 
 642             GenericPointer otherp = (GenericPointer)other;
 643 
 644             return o == otherp.o && offset == otherp.offset;
 645         }
 646 
 647         GenericPointer(Object o) {
 648             this(o, UNSAFE.arrayBaseOffset(o.getClass()));
 649         }
 650 
 651         GenericPointer(long offset) {
 652             this(null, offset);
 653         }
 654 
 655         public boolean isOnHeap() {
 656             return o != null;
 657         }
 658 
 659         public Object getObject() {
 660             return o;
 661         }
 662 
 663         public long getOffset() {
 664             return offset;
 665         }
 666     }
 667 }