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  }