1 /* 2 * Copyright (c) 2024, 2024, 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 java.io.PrintStream; 25 import java.util.ArrayList; 26 import java.util.Collections; 27 import java.util.HashMap; 28 import java.util.List; 29 30 import jdk.test.lib.Asserts; 31 import jdk.test.lib.process.OutputAnalyzer; 32 import jdk.test.lib.process.ProcessTools; 33 34 public class FieldLayoutAnalyzer { 35 36 // Mutable wrapper around log output to manage the cursor while parsing 37 static class LogOutput { 38 List<String> lines; 39 int cursor; 40 41 public LogOutput(List<String> lines) { 42 this.lines = lines; 43 cursor = 0; 44 } 45 46 String getCurrentLine() { return lines.get(cursor); } 47 String get(int idx) { return lines.get(idx); } 48 int size() { return lines.size(); } 49 void moveToNextLine() { cursor = cursor + 1; } 50 boolean hasMoreLines() { return cursor < lines.size(); } 51 } 52 53 static enum BlockType { 54 RESERVED, 55 INHERITED, 56 EMPTY, 57 REGULAR, 58 PADDING, 59 FLAT, 60 NULL_MARKER, 61 INHERITED_NULL_MARKER; 62 63 static BlockType parseType(String s) { 64 switch(s) { 65 case "RESERVED" : return RESERVED; 66 case "INHERITED" : return INHERITED; 67 case "EMPTY" : return EMPTY; 68 case "REGULAR" : return REGULAR; 69 case "PADDING" : return PADDING; 70 case "FLAT" : return FLAT; 71 case "NULL_MARKER" : return NULL_MARKER; 72 case "INHERITED_NULL_MARKER" : return INHERITED_NULL_MARKER; 73 default: 74 throw new RuntimeException("Unknown block type: " + s); 75 } 76 } 77 } 78 79 static public record FieldBlock (int offset, 80 BlockType type, 81 int size, 82 int alignment, 83 String name, 84 String signature, 85 String fieldClass, 86 int nullMarkerOffset, 87 boolean hasInternalNullMarker, 88 int referenceFieldOffset) { // for null marker blocks, gives the offset of the field they refer to 89 90 91 static FieldBlock createSpecialBlock(int offset, BlockType type, int size, int alignment, int referenceFieldOffset) { 92 return new FieldBlock(offset, type, size, alignment, null, null, null, -1, false, referenceFieldOffset); 93 } 94 95 static FieldBlock createJavaFieldBlock(int offset, BlockType type, int size, int alignment, String name, String signature, String fieldClass, int nullMarkerOffset, boolean hasInternalNullMarker) { 96 return new FieldBlock(offset, type, size, alignment, name, signature, fieldClass, nullMarkerOffset, hasInternalNullMarker, -1); 97 } 98 99 void print(PrintStream out) { 100 out.println("Offset=" + offset+ 101 " type=" + type + 102 " size=" + size + 103 " alignment=" + alignment + 104 " name=" + name + 105 " signature=" + signature + 106 " fieldClass=" + fieldClass); 107 } 108 109 boolean isFlat() { return type == BlockType.FLAT; } // Warning: always return false for inherited fields, even flat ones 110 boolean hasNullMarker() {return nullMarkerOffset != -1; } 111 112 static FieldBlock parseField(String line) { 113 String[] fieldLine = line.split("\\s+"); 114 // for(String s : fieldLine) { 115 // System.out.print("["+s+"]"); // debugging statement to be removed 116 // } 117 // System.out.println(); 118 int offset = Integer.parseInt(fieldLine[1].substring(1, fieldLine[1].length())); 119 BlockType type = BlockType.parseType(fieldLine[2]); 120 String[] size_align = fieldLine[3].split("/"); 121 int size = Integer.parseInt(size_align[0]); 122 int alignment = -1; 123 if (type != BlockType.RESERVED) { 124 alignment = Integer.parseInt(size_align[1]); 125 } else { 126 Asserts.assertTrue(size_align[1].equals("-")); 127 } 128 FieldBlock block = null; 129 switch(type) { 130 case BlockType.RESERVED: 131 case BlockType.EMPTY: 132 case BlockType.PADDING: 133 case BlockType.INHERITED_NULL_MARKER: { 134 block = FieldBlock.createSpecialBlock(offset, type, size, alignment, 0); 135 break; 136 } 137 case BlockType.REGULAR: 138 case BlockType.INHERITED: 139 case BlockType.FLAT: { 140 String name = fieldLine[4]; 141 String signature = fieldLine[5]; 142 String fieldClass = ""; 143 int nullMarkerOffset = -1; 144 boolean hasInternalNullMarker = false; 145 if (type == BlockType.FLAT) { 146 fieldClass = fieldLine[6]; 147 if (fieldLine.length >= 11) { 148 nullMarkerOffset = Integer.parseInt(fieldLine[10]); 149 } 150 if (fieldLine.length >= 12 ) { 151 Asserts.assertEquals(fieldLine[11], "(internal)"); 152 hasInternalNullMarker = true; 153 } 154 } 155 block = FieldBlock.createJavaFieldBlock(offset, type, size, alignment, name, signature, fieldClass, nullMarkerOffset, hasInternalNullMarker); 156 break; 157 } 158 case BlockType.NULL_MARKER: { 159 int referenceFieldOffset = Integer.parseInt(fieldLine[10]); 160 block = FieldBlock.createSpecialBlock(offset, type, size, alignment, referenceFieldOffset); 161 break; 162 } 163 } 164 Asserts.assertNotNull(block); 165 return block; 166 } 167 } 168 169 static class ClassLayout { 170 String name; 171 String superName; 172 boolean isValue; 173 int size; 174 int firstFieldOffset; 175 int alignment; 176 int exactSize; 177 String[] lines; 178 ArrayList<FieldBlock> staticFields; 179 ArrayList<FieldBlock> nonStaticFields; 180 int internalNullMarkerOffset; // -1 if no internal null marker 181 182 private ClassLayout() { 183 staticFields = new ArrayList<FieldBlock>(); 184 nonStaticFields = new ArrayList<FieldBlock>(); 185 } 186 187 void processField(String line, boolean isStatic) { 188 FieldBlock block = FieldBlock.parseField(line); 189 if (isStatic) { 190 Asserts.assertTrue(block.type != BlockType.INHERITED); // static fields cannotbe inherited 191 staticFields.add(block); 192 } else { 193 nonStaticFields.add(block); 194 } 195 } 196 197 static ClassLayout parseClassLayout(LogOutput lo) { 198 ClassLayout cl = new ClassLayout(); 199 // Parsing class name 200 Asserts.assertTrue(lo.getCurrentLine().startsWith("Layout of class"), lo.getCurrentLine()); 201 String[] first = lo.getCurrentLine().split("\\s+"); 202 cl.name = first[3]; 203 if (first.length == 6) { 204 Asserts.assertEquals(first[4], "extends"); 205 cl.superName = first[5]; 206 } else { 207 cl.superName = null; 208 } 209 // System.out.println("Class name: " + cl.name); 210 lo.moveToNextLine(); 211 Asserts.assertTrue(lo.getCurrentLine().startsWith("Instance fields:"), lo.getCurrentLine()); 212 lo.moveToNextLine(); 213 // Parsing instance fields 214 while (lo.getCurrentLine().startsWith(" @")) { 215 cl.processField(lo.getCurrentLine(), false); 216 lo.moveToNextLine(); 217 } 218 Asserts.assertTrue(lo.getCurrentLine().startsWith("Static fields:"), lo.getCurrentLine()); 219 lo.moveToNextLine(); 220 // Parsing static fields 221 while (lo.getCurrentLine().startsWith(" @")) { 222 cl.processField(lo.getCurrentLine(), true); 223 lo.moveToNextLine(); 224 } 225 Asserts.assertTrue(lo.getCurrentLine().startsWith("Instance size ="), lo.getCurrentLine()); 226 String[] sizeLine = lo.getCurrentLine().split("\\s+"); 227 cl.size = Integer.parseInt(sizeLine[3]); 228 lo.moveToNextLine(); 229 if (lo.getCurrentLine().startsWith("First field offset =")) { 230 // The class is a value class, more lines to parse 231 cl.isValue = true; 232 String[] firstOffsetLine = lo.getCurrentLine().split("\\s+"); 233 cl.firstFieldOffset = Integer.parseInt(firstOffsetLine[4]); 234 lo.moveToNextLine(); 235 String[] alignmentLine = lo.getCurrentLine().split("\\s+"); 236 cl.alignment = Integer.parseInt(alignmentLine[2]); 237 lo.moveToNextLine(); 238 String[] exactSizeLine = lo.getCurrentLine().split("\\s+"); 239 cl.exactSize = Integer.parseInt(exactSizeLine[3]); 240 lo.moveToNextLine(); 241 if (lo.getCurrentLine().startsWith("Null marker offset")) { 242 String[] nullMarkerLine = lo.getCurrentLine().split("\\s+"); 243 cl.internalNullMarkerOffset = Integer.parseInt(nullMarkerLine[4]); 244 lo.moveToNextLine(); 245 } else { 246 cl.internalNullMarkerOffset = -1; 247 } 248 } else { 249 cl.isValue = false; 250 } 251 252 Asserts.assertTrue(lo.getCurrentLine().startsWith("---"), lo.getCurrentLine()); 253 lo.moveToNextLine(); 254 return cl; 255 } 256 257 FieldBlock getFieldAtOffset(int offset, boolean isStatic) { 258 ArrayList<FieldBlock> fields = isStatic ? staticFields : nonStaticFields; 259 for (FieldBlock block : fields) { 260 if (block.offset == offset) return block; 261 } 262 throw new RuntimeException("No " + (isStatic ? "static" : "nonstatic") + " field found at offset "+ offset); 263 } 264 265 FieldBlock getFieldFromName(String fieldName, boolean isStatic) { 266 Asserts.assertTrue(fieldName != null); 267 ArrayList<FieldBlock> fields = isStatic ? staticFields : nonStaticFields; 268 for (FieldBlock block : fields) { 269 if (block.name() == null) continue; 270 String n = block.name().substring(1, block.name().length() - 1); // in the log, name is surrounded by double quotes 271 if (fieldName.equals(n)) return block; 272 } 273 throw new RuntimeException("No " + (isStatic ? "static" : "nonstatic") + " field found with name "+ fieldName); 274 } 275 276 } 277 278 ArrayList<ClassLayout> layouts; 279 int oopSize; 280 281 static String signatureToName(String sig) { 282 Asserts.assertTrue((sig.charAt(0) == 'L')); 283 Asserts.assertTrue((sig.charAt(sig.length() - 1) == ';')); 284 return sig.substring(1, sig.length() - 1); 285 } 286 287 private FieldLayoutAnalyzer() { 288 layouts = new ArrayList<ClassLayout>(); 289 } 290 291 public static FieldLayoutAnalyzer createFieldLayoutAnalyzer(LogOutput lo) { 292 FieldLayoutAnalyzer fla = new FieldLayoutAnalyzer(); 293 fla.generate(lo); 294 return fla; 295 } 296 297 ClassLayout getClassLayout(String name) { 298 for(ClassLayout layout : layouts) { 299 if (layout.name.equals(name)) return layout; 300 } 301 return null; 302 } 303 304 ClassLayout getClassLayoutFromName(String name) { 305 for(ClassLayout layout : layouts) { 306 String sub = layout.name.substring(0, layout.name.indexOf('@')); 307 if (name.equals(sub)) return layout; 308 } 309 return null; 310 } 311 312 void checkOffsetOnFields(ArrayList<FieldBlock> fields) { 313 HashMap<Integer, FieldBlock> map = new HashMap<Integer, FieldBlock>(); 314 for (FieldBlock fb : fields) { 315 Asserts.assertFalse(map.containsKey(fb.offset()), "Duplicate offset at " + fb.offset()); 316 map.put(fb.offset(), fb); 317 } 318 } 319 320 void checkOffsets() { 321 for (ClassLayout layout : layouts) { 322 try { 323 checkOffsetOnFields(layout.staticFields); 324 checkOffsetOnFields(layout.nonStaticFields); 325 } catch(Throwable t) { 326 System.out.println("Unexpection exception when checking offsets in class " + layout.name); 327 throw t; 328 } 329 } 330 } 331 332 void checkNoOverlapOnFields(ArrayList<FieldBlock> fields) { 333 for (int i = 0; i < fields.size() - 1; i++) { 334 FieldBlock f0 = fields.get(i); 335 FieldBlock f1 = fields.get(i + 1); 336 if (f0.offset + f0.size < f1.offset) { 337 throw new RuntimeException("Hole issue found at offset " + f1.offset); 338 } else if (f0.offset + f0.size > f1.offset) { 339 throw new RuntimeException("Overlap issue found at offset " + f1.offset); 340 } 341 } 342 } 343 344 void checkNoOverlap() { 345 for (ClassLayout layout : layouts) { 346 try { 347 checkNoOverlapOnFields(layout.staticFields); 348 checkNoOverlapOnFields(layout.nonStaticFields); 349 } catch(Throwable t) { 350 System.out.println("Unexpection exception when checking for overlaps/holes in class " + layout.name); 351 throw t; 352 } 353 } 354 } 355 356 void checkSizeAndAlignmentForField(FieldBlock block) { 357 Asserts.assertTrue(block.size() > 0); 358 if (block.type == BlockType.RESERVED) { 359 Asserts.assertTrue(block.alignment == -1); 360 return; 361 } 362 if (block.type == BlockType.EMPTY || block.type == BlockType.PADDING 363 || block.type == BlockType.NULL_MARKER || block.type == BlockType.INHERITED_NULL_MARKER) { 364 Asserts.assertTrue(block.alignment == 1, "alignment = " + block.alignment); 365 return; 366 } 367 368 switch(block.signature()) { 369 case "Z" : 370 case "B" : Asserts.assertTrue(block.size() == 1); 371 Asserts.assertTrue(block.alignment() == 1); 372 break; 373 case "S" : 374 case "C" : Asserts.assertTrue(block.size() == 2); 375 Asserts.assertTrue(block.alignment() == 2); 376 break; 377 case "F" : 378 case "I" : Asserts.assertTrue(block.size() == 4); 379 Asserts.assertTrue(block.alignment() == 4); 380 break; 381 case "J" : 382 case "D" : Asserts.assertTrue(block.size() == 8); 383 Asserts.assertTrue(block.alignment() == 8); 384 break; 385 default: { 386 if (block.signature().startsWith("[")) { 387 Asserts.assertEquals(oopSize, block.size()); 388 } else if (block.signature().startsWith("L")) { 389 if (block.type == BlockType.INHERITED || block.type == BlockType.INHERITED_NULL_MARKER) { 390 // Skip for now, will be verified when checking the class declaring the field 391 } else if (block.type == BlockType.REGULAR) { 392 Asserts.assertEquals(oopSize, block.size()); 393 } else { 394 Asserts.assertEquals(BlockType.FLAT, block.type); 395 ClassLayout fcl = getClassLayout(block.fieldClass); 396 Asserts.assertNotNull(fcl); 397 Asserts.assertEquals(block.size(), fcl.exactSize); 398 Asserts.assertEquals(block.alignment(), fcl.alignment); 399 } 400 } else { 401 throw new RuntimeException("Unknown signature type: " + block.signature); 402 } 403 } 404 Asserts.assertTrue(block.offset % block.alignment == 0); 405 } 406 } 407 408 void checkSizeAndAlignment() { 409 for (ClassLayout layout : layouts) { 410 try { 411 for (FieldBlock block : layout.staticFields) { 412 checkSizeAndAlignmentForField(block); 413 } 414 } catch(Throwable t) { 415 System.out.println("Unexpected exception when checking size and alignment in static fields of class " + layout.name); 416 throw t; 417 } 418 try { 419 for (FieldBlock block : layout.nonStaticFields) { 420 checkSizeAndAlignmentForField(block); 421 } 422 } catch(Throwable t) { 423 System.out.println("Unexpected exception when checking size and alignment in non-static fields of class " + layout.name); 424 throw t; 425 } 426 } 427 } 428 429 // Verify that fields marked as INHERITED are declared in a super class 430 void checkInheritedFields() { 431 for (ClassLayout layout : layouts) { 432 try { 433 // Preparing the list of ClassLayout of super classes 434 ArrayList<ClassLayout> supers = new ArrayList<ClassLayout>(); 435 String className = layout.superName; 436 while (className != null) { 437 ClassLayout cl = getClassLayout(className); 438 supers.add(cl); 439 className = cl.superName; 440 } 441 for (FieldBlock field : layout.nonStaticFields) { 442 if (field.type == BlockType.INHERITED) { 443 int i = 0; 444 boolean found = false; 445 FieldBlock b = null; 446 while(i < supers.size() && !found) { 447 b = supers.get(i).getFieldAtOffset(field.offset, false); 448 if (b.type != BlockType.INHERITED) found = true; 449 i++; 450 } 451 String location = new String(" at " + layout.name + " offset " + field.offset()); 452 Asserts.assertTrue(found, "No declaration found for an inherited field " + location); 453 Asserts.assertNotEquals(field.type, BlockType.EMPTY, location); 454 Asserts.assertEquals(field.size, b.size, location); 455 Asserts.assertEquals(field.alignment, b.alignment, location ); 456 Asserts.assertEquals(field.name(), b.name(), location); 457 Asserts.assertEquals(field.signature(), b.signature(), location); 458 } 459 } 460 } catch(Throwable t) { 461 System.out.println("Unexpexted exception when checking inherited fields in class " + layout.name); 462 } 463 } 464 } 465 466 static class Node { 467 ClassLayout classLayout; 468 Node superClass; 469 ArrayList<Node> subClasses = new ArrayList<Node>(); 470 } 471 472 // Verify that all fields declared in a class are present in all subclass 473 void checkSubClasses() { 474 // Generating the class inheritance graph 475 HashMap<String, Node> nodes = new HashMap<String, Node>(); 476 for (ClassLayout layout : layouts) { 477 try { 478 if (layout.name.contains("$$Lambda@0")) continue; // Skipping lambda classes 479 Node current = nodes.get(layout.name); 480 if (current == null) { 481 current = new Node(); 482 nodes.put(layout.name, current); 483 } 484 if (current.classLayout == null) { 485 current.classLayout = layout; 486 } else { 487 Asserts.assertEQ(current.classLayout, layout); 488 } 489 if (layout.superName != null) { 490 Node superNode = nodes.get(layout.superName); 491 if (superNode == null) { 492 superNode = new Node(); 493 superNode.subClasses.add(current); 494 nodes.put(layout.superName, superNode); 495 } 496 superNode.subClasses.add(current); 497 } 498 } catch(Throwable t) { 499 System.out.println("Unexpected exception when generating list of sub-classes of class " + layout.name); 500 throw t; 501 } 502 } 503 // Field verification 504 for (Node node : nodes.values()) { 505 ClassLayout layout = node.classLayout; 506 for (FieldBlock block : layout.nonStaticFields) { 507 if (block.offset() == 0) continue; // Skip object header 508 if (block.type() == BlockType.EMPTY) continue; // Empty spaces can be used by subclasses 509 if (block.type() == BlockType.PADDING) continue; // PADDING should have a finer inspection, preserved for @Contended and other imperative padding, and relaxed for abstract value conservative padding 510 // A special case for PADDING might be needed too => must NOT be used in subclasses 511 for (Node subnode : node.subClasses) { 512 try { 513 checkFieldInClass(block, subnode); 514 } catch(Throwable t) { 515 System.out.println("Unexpected exception when checking subclass " + subnode.classLayout.name + " of class " + layout.name); 516 throw t; 517 } 518 } 519 } 520 } 521 } 522 523 void checkFieldInClass(FieldBlock block, Node node) { 524 FieldBlock b = node.classLayout.getFieldAtOffset(block.offset, false); 525 Asserts.assertTrue((block.type != BlockType.NULL_MARKER && block.type != BlockType.INHERITED_NULL_MARKER && b.type == BlockType.INHERITED) 526 || ((block.type == BlockType.NULL_MARKER || block.type == BlockType.INHERITED_NULL_MARKER) && b.type == BlockType.INHERITED_NULL_MARKER)); 527 Asserts.assertEquals(b.signature(), block.signature()); 528 Asserts.assertEquals(b.name(), block.name()); 529 Asserts.assertEquals(b.size(), block.size()); 530 Asserts.assertTrue(b.alignment() == block.alignment()); 531 for (Node subnode : node.subClasses) { 532 checkFieldInClass(block, subnode); 533 } 534 } 535 536 void checkNullMarkers() { 537 for (ClassLayout layout : layouts) { 538 try { 539 BlockType last_type = BlockType.RESERVED; 540 boolean has_empty_slot = false; 541 for (FieldBlock block : layout.nonStaticFields) { 542 last_type = block.type; 543 if (block.type() == BlockType.FLAT && block.nullMarkerOffset() != -1) { 544 if (block.hasInternalNullMarker()) { 545 Asserts.assertTrue(block.nullMarkerOffset() > block.offset()); 546 Asserts.assertTrue(block.nullMarkerOffset() < block.offset() + block.size()); 547 } else { 548 FieldBlock marker = layout.getFieldAtOffset(block.nullMarkerOffset(), false); 549 Asserts.assertEquals(block.nullMarkerOffset(), marker.offset()); 550 } 551 } 552 if (block.type() == BlockType.NULL_MARKER) { 553 FieldBlock flatField = layout.getFieldAtOffset(block.referenceFieldOffset(), false); 554 Asserts.assertEquals(flatField.type(), BlockType.FLAT); 555 Asserts.assertEquals(flatField.nullMarkerOffset(), block.offset()); 556 } 557 if (block.type() == BlockType.EMPTY) has_empty_slot = true; 558 } 559 // null marker should not be added at the end of the layout if there's an empty slot 560 Asserts.assertTrue(last_type != BlockType.NULL_MARKER || has_empty_slot == false, 561 "Problem detected in layout of class " + layout.name); 562 // static layout => must not have NULL_MARKERS because static fields are never flat 563 for (FieldBlock block : layout.staticFields) { 564 Asserts.assertNotEquals(block.type(), BlockType.NULL_MARKER); 565 if (block.type() == BlockType.FLAT) { 566 Asserts.assertEquals(block.nullMarkerOffset(), -1); // -1 means no null marker 567 } 568 } 569 } catch(Throwable t) { 570 System.out.println("Unexpected exception while checking null markers in class " + layout.name); 571 throw t; 572 } 573 } 574 } 575 576 void check() { 577 checkOffsets(); 578 checkNoOverlap(); 579 checkSizeAndAlignment(); 580 checkInheritedFields(); 581 checkSubClasses(); 582 checkNullMarkers(); 583 } 584 585 private void generate(LogOutput lo) { 586 while (lo.hasMoreLines()) { 587 if (lo.getCurrentLine().startsWith("Heap oop size = ")) { 588 String[] oopSizeLine = lo.getCurrentLine().split("\\s+"); 589 oopSize = Integer.parseInt(oopSizeLine[4]); 590 Asserts.assertTrue(oopSize == 4 || oopSize == 8); 591 } 592 if (lo.getCurrentLine().startsWith("Layout of class")) { 593 ClassLayout cl = ClassLayout.parseClassLayout(lo); 594 layouts.add(cl); 595 } else { 596 lo.moveToNextLine(); // skipping line 597 } 598 } 599 Asserts.assertTrue(oopSize != 0); 600 } 601 }