Skip to content

Conversation

@markro49
Copy link
Contributor

@markro49 markro49 commented Oct 3, 2025

No description provided.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
java/daikon/chicory/Instrument.java (1)

621-647: Document iterator contract next to iterator creation for future maintainers

The pairing between curMethodInfo.exit_location_is_included and curMethodInfo.exit_locations is sound, and the comment in generate_return_instrumentation (lines 742-745) now explains the “one boolean per return; line only when boolean is true” contract. However, the iterators are created and drained here, and the only enforcement is via the final assert !shouldIncludeIter.hasNext() / assert !exitLocationIter.hasNext().

Consider adding a brief comment where the iterators are initialized (lines 621-622) that:

  • States there is exactly one boolean per return instruction.
  • States an integer exists only when the corresponding boolean is true.
  • Mentions that generate_return_instrumentation consumes one boolean per return and conditionally one integer, and that the post-loop asserts ensure both iterators are exhausted.

That keeps the producer/consumer invariant discoverable where the iterators are managed, not just down in the helper.

Also applies to: 742-745

java/daikon/chicory/Instrument24.java (3)

114-116: Public debug_ppt_omit still creates tight coupling with DynComp

debug_ppt_omit remains public static so that daikon.dcomp.Instrument24 can toggle it. This shares mutable logging state across modules and couples the dcomp side tightly to Chicory’s internals.

If feasible, consider one of the previously suggested decoupling options:

  • Extract a shared “ppt filter”/logging utility that both Chicory and DynComp depend on, or
  • Keep the log field package-visible and expose a small accessor or configuration hook instead of a public mutable field.

960-992: Add post-loop sanity checks for exit-location iterators

addInstrumentationAtExits walks the instruction list and, for each ReturnInstruction, calls generate_return_instrumentation, which:

  • Consumes exactly one element from shouldIncludeIter per return, and
  • Consumes one element from exitLocationIter only when that boolean is true.

You already guard for too few entries via !hasNext() checks that throw a RuntimeException. However, if create_method_info_if_instrumented ever produces too many booleans or exit locations, the extra entries will silently remain unused after the loop.

To mirror the stronger contract enforced in the BCEL implementation, consider adding a short post-loop check, e.g.:

if (shouldIncludeIter.hasNext() || exitLocationIter.hasNext()) {
  throw new IllegalStateException(
      "Mismatched exit_location_* for " + mgen.getName()
          + " (extra entries after return scan)");
}

(or equivalent logging-based handling). This will catch producer/consumer mismatches early instead of letting stale iterator entries linger.

Also applies to: 1002-1051


1493-1500: Use !foundLine instead of foundLine == false

The condition:

if (line_number == prev_line_number && foundLine == false) {

could be simplified to:

if (line_number == prev_line_number && !foundLine) {

This is the idiomatic Java style and matches common usage elsewhere.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 96cc8eb and a5e8ce6.

📒 Files selected for processing (2)
  • java/daikon/chicory/Instrument.java (47 hunks)
  • java/daikon/chicory/Instrument24.java (57 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: codespecs.daikon (nontxt_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_bundled_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_latest_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (quick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (kvasir_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nonquick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (misc_ubuntu_jdk25)
  • GitHub Check: build
🔇 Additional comments (2)
java/daikon/chicory/Instrument24.java (2)

1354-1376: Unable to verify review comment — file not found in repository

The referenced file java/daikon/chicory/Instrument24.java and the methods convertDescriptorToFqBinaryName, classDescToClassGetName, and create_method_info_if_instrumented could not be located in the codebase. Verification of the concern regarding type variable and wildcard handling in generic signatures cannot proceed without access to the actual implementation.

To properly assess this review:

  • Confirm the file path and repository structure
  • Provide the implementation of convertDescriptorToFqBinaryName and related helpers
  • Clarify how type variables and wildcards in generic descriptors are currently handled

426-438: Align static constant-field collection and value representation with BCEL path

The JDK 24 path collects all fields with a ConstantValueAttribute (including non-primitives such as String) and converts each via formatConstantDesc, which calls item.resolveConstantDesc(MethodHandles.lookup()).toString(). In contrast, the BCEL-based implementation in Instrument.java only records final static fields of BasicType (primitives) and derives valueString via ConstantValue.toString().

This divergence means:

  • The set of keys in classInfo.staticMap can differ between implementations (e.g., static final String fields included here but not there)
  • The string representation of the same constant may differ (resolveConstantDesc(...).toString() vs. BCEL's ConstantValue.toString())

If downstream Chicory/DynComp logic expects identical staticMap contents regardless of which Instrument variant runs, align both implementations by either including non-primitives in both paths or excluding them from both, and centralize the constant-to-string conversion in a shared helper or documented format.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (7)
java/daikon/dcomp/StackMapUtils24.java (3)

51-52: Consider wiring debugInstrument to an existing DynComp debug flag.

The debugInstrument is initialized as disabled and only enabled inside addNewSpecialLocal via direct assignment to DCInstrument24.bcelDebug. This couples the utility class tightly to DCInstrument24. Consider initializing it from the debug flag directly or making the SimpleLog accept a supplier.


146-158: Consider abstracting offset calculation into a helper method.

The logic to compute newIndex and newOffset after skipping this and parameters could be extracted into a helper that returns both values (e.g., via a record). This would improve readability and testability.


175-177: Inconsistent format string style.

The format string mixes %s placeholders with string concatenation (+). Consider using one style consistently throughout.

     debugInstrument.log(
-        "Added a %s at slot %s.%n  name: %s type: %s size: %s%n",
-        isParam ? "parameter" : "local", varNew.slot(), varNew.name(), varNew.type(), argSize);
+        "Added a %s at slot %d.%n  name: %s type: %s size: %d%n",
+        isParam ? "parameter" : "local", varNew.slot(), varNew.name().stringValue(), varNew.type(), argSize);
java/daikon/dcomp/OperandStack24.java (1)

78-139: Fix equals/hashCode contract violation.

The equals method uses coarse equivalence (e.g., null matches any non-primitive, all non-primitives are treated as equivalent), but hashCode delegates to stack.hashCode() which uses exact ClassDesc values. This violates the Java contract that equal objects must have equal hash codes.

If OperandStack24 instances are used in hash-based collections (likely in CalcStack24), this will cause incorrect behavior.

Apply this diff to align hashCode with the coarse equality semantics:

   @Override
   public int hashCode(@GuardSatisfied OperandStack24 this) {
-    return stack.hashCode();
+    int result = 1;
+    for (ClassDesc item : stack) {
+      int elementHash;
+      if (item == null) {
+        elementHash = 0; // matches any non-primitive in equals
+      } else if (item.isPrimitive()) {
+        elementHash = 1; // all primitives are equivalent in equals
+      } else {
+        elementHash = 2; // all non-primitive, non-null items are equivalent in equals
+      }
+      result = 31 * result + elementHash;
+    }
+    return result;
   }
java/daikon/dcomp/CalcStack24.java (2)

142-941: The simulateInstruction method is comprehensive but very long.

At ~800 lines, this method is difficult to maintain and test. Previous reviews suggested decomposing it into helper methods (e.g., handleArithmeticOp, handleFieldAccess, handleInvoke). While the current implementation is functionally correct, consider this refactoring for improved maintainability.


992-1022: Consider using enhanced switch expression for conciseness.

The npaiToElementTypeDescriptor method can be simplified using Java's enhanced switch expression syntax.

   static String npaiToElementTypeDescriptor(NewPrimitiveArrayInstruction npai) {
-    switch (npai.typeKind()) {
-      case BOOLEAN -> {
-        return "Z";
-      }
-      case BYTE -> {
-        return "B";
-      }
-      case CHAR -> {
-        return "C";
-      }
-      case DOUBLE -> {
-        return "D";
-      }
-      case FLOAT -> {
-        return "F";
-      }
-      case INT -> {
-        return "I";
-      }
-      case LONG -> {
-        return "J";
-      }
-      case SHORT -> {
-        return "S";
-      }
-      default -> {
-        throw new DynCompError("unknown primitive type " + npai.typeKind() + " in: " + npai);
-      }
-    }
+    return switch (npai.typeKind()) {
+      case BOOLEAN -> "Z";
+      case BYTE -> "B";
+      case CHAR -> "C";
+      case DOUBLE -> "D";
+      case FLOAT -> "F";
+      case INT -> "I";
+      case LONG -> "J";
+      case SHORT -> "S";
+      default -> throw new DynCompError("unknown primitive type " + npai.typeKind() + " in: " + npai);
+    };
   }
java/daikon/dcomp/ClassGen24.java (1)

108-113: Fix Javadoc typo: “indicatated” → “indicated”.

The Javadoc for containsMethod still contains the typo previously reported:

-   * Determine if this class contains the indicatated method.
+   * Determine if this class contains the indicated method.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d374d77 and 244ba7e.

📒 Files selected for processing (4)
  • java/daikon/dcomp/CalcStack24.java (1 hunks)
  • java/daikon/dcomp/ClassGen24.java (1 hunks)
  • java/daikon/dcomp/OperandStack24.java (1 hunks)
  • java/daikon/dcomp/StackMapUtils24.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
java/daikon/dcomp/StackMapUtils24.java (2)
java/daikon/chicory/MethodGen24.java (1)
  • MethodGen24 (51-832)
java/daikon/dcomp/DCInstrument24.java (1)
  • DCInstrument24 (136-4952)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (7)
java/daikon/dcomp/StackMapUtils24.java (2)

63-104: LGTM! The instruction slot adjustment logic is correct.

The method properly handles all local-variable-referencing instructions (RetInstruction, IncrementInstruction, LoadInstruction, StoreInstruction) and correctly adjusts slots at or above the insertion point. The error handling wraps exceptions in DynCompError with context.


128-199: LGTM! The addNewSpecialLocal implementation is well-structured.

The method correctly:

  1. Handles both static and instance methods
  2. Computes the proper insertion point after parameters
  3. Updates locals table, parameter types/names when isParam is true
  4. Adjusts subsequent local slots and instruction references when code exists

The integration with MethodGen24.MInfo24 for scope labels and nextLocalIndex tracking is appropriate.

java/daikon/dcomp/OperandStack24.java (3)

45-48: LGTM! Constructor properly initializes maxStack.

The constructor correctly stores the maximum stack size bound.


59-69: LGTM! Clone implementation is correct.

The clone creates a new ArrayList to ensure copy-on-write semantics while sharing ClassDesc objects (which are immutable). Using DynCompError for the impossible CloneNotSupportedException is appropriate.


235-241: LGTM! slotSize correctly handles null and delegates to TypeKind.

Returning 1 for null is appropriate since null represents a reference type which always occupies 1 slot.

java/daikon/dcomp/CalcStack24.java (2)

50-56: LGTM! Class documentation properly notes side effects.

The Javadoc correctly indicates that methods may modify DCInstrument24.locals and DCInstrument24.stacks, addressing previous review feedback.


943-983: LGTM! lceToCD helper properly handles all LoadableConstantEntry types.

The method correctly maps:

  • ClassEntryCD_Class (not the described class - fixes previous issue)
  • ConstantDynamicEntry → its declared type
  • MethodHandleEntry / MethodTypeEntry → appropriate runtime types

This addresses the critical issues flagged in previous reviews.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (2)
java/daikon/dcomp/OperandStack24.java (2)

71-98: Align hashCode with the coarse equality defined in equals / compareOperandStackElements.

equals deliberately uses a very coarse notion of equality (e.g., null matches any non‑primitive; all non‑primitive arrays/classes are equivalent; all primitives must match exactly, otherwise a DynCompError is thrown), but hashCode currently delegates to stack.hashCode(), which depends on the exact ClassDesc values and on null vs non‑null. Two stacks that equals considers equal can therefore have different hash codes, which violates the equals/hashCode contract and can break behavior if OperandStack24 is ever used as a key in hash‑based collections.

I recommend making hashCode mirror the same coarse partitioning as compareOperandStackElements:

   /**
    * @return a hash code value for the object
    */
   @Override
   public int hashCode(@GuardSatisfied OperandStack24 this) {
-    return stack.hashCode();
+    int result = 1;
+    for (ClassDesc item : stack) {
+      final int elementHash;
+      if (item == null) {
+        // Matches any non-primitive in equals()
+        elementHash = 0;
+      } else if (item.isPrimitive()) {
+        // All primitives form a single equivalence class in equals()
+        elementHash = 1;
+      } else {
+        // All non-primitive, non-null types (arrays/classes) form a single class in equals()
+        elementHash = 2;
+      }
+      result = 31 * result + elementHash;
+    }
+    return result;
   }

If there’s a reason OperandStack24 is guaranteed never to be used in a hash‑based collection, it’d be good to document that explicitly; otherwise this change is important for correctness.

Also applies to: 100-153, 164-170


42-48: Enforce maxStack in terms of slots, not element count, to handle long/double correctly.

Javadoc and naming indicate maxStack is a bound on stack slots (matching the JVM), and slotsUsed() carefully accounts for wide values via slotSize(...). However, push currently checks stack.size() >= maxStack, which ignores wide types and can let the simulated stack exceed its slot capacity whenever long/double values are present.

For example, with maxStack == 2, pushing two long values would use 4 slots but still pass the stack.size() check. This defeats the purpose of maxStack as a validation guard and can mask bugs in the symbolic simulation that mishandle wide values.

Consider enforcing capacity using slots instead:

   /** Pushes a ClassDesc object onto the stack. */
   public void push(final ClassDesc type) {
-    if (stack.size() >= maxStack) {
-      throw new DynCompError("Operand stack size exceeded: " + stack);
-    }
-    stack.add(type);
+    // Enforce the JVM-style maxStack in terms of *slots*, not element count.
+    if (slotsUsed() + slotSize(type) > maxStack) {
+      throw new DynCompError("Operand stack size (in slots) exceeded: " + stack);
+    }
+    stack.add(type);
   }

If you prefer not to pay the slotsUsed() cost on every push, a cheaper alternative is to track a running slotsUsed counter updated in push/pop instead of recomputing each time.

Also applies to: 177-181, 215-221, 232-244

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6a099 and 6750d74.

📒 Files selected for processing (1)
  • java/daikon/dcomp/OperandStack24.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: codespecs.daikon (typecheck_bundled_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_latest_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (kvasir_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nonquick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (misc_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nontxt_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (quick_ubuntu_jdk25)
  • GitHub Check: build

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
java/daikon/dcomp/CalcStack24.java (3)

68-72: Clarify null sentinel ClassDesc naming and intent

nullCD is a sentinel value, but both the name and "fake.ClassDecs.for.null" are opaque. Consider renaming to something like NULL_SENTINEL_CD (or similar) and/or updating the Javadoc to explicitly state this is a sentinel, not a real class, to make its purpose clearer.


162-162: Fix typo in comment

The inline comment has a typo; please update it for clarity:

-    // caculate stack changes
+    // calculate stack changes

717-731: Resolve JSR/RET handling or explicitly scope to legacy classfiles

JSR/RET opcodes have been illegal in class file version 51.0+ (Java 7+), but this code still contains TODOs and a partial simulation using a fake return-address value. For a Java 24-focused tool, this is brittle.

Consider either:

  • Failing fast with an UnsupportedOperationException when encountering JSR/JSR_W/RET/RET_W, or
  • Clearly documenting that you intend to support legacy classfile versions (< 51.0) and explaining the limitations of the current approximation.

For a fail-fast approach, a minimal change would be:

-      // TODO: JSR and RET have been illegal since JDK 7 (class file version 51.0).
-      // Consider throwing an UnsupportedOperationException for these opcodes
-      // if we're only supporting modern class files (version 51.0+).
-      // Currently maintaining support for completeness.
-
-      // operand stack before: ...
-      // operand stack after:  ..., [return address]
       case Opcode.JSR:
       case Opcode.JSR_W:
-        // Perhaps we should add label of next instruction to work list but we have no idea
-        // what stack will be after return from the JSR.
-        stack.push(CD_Object); // there is no way to represent a return address, fake it
-        DiscontinuedInstruction.JsrInstruction ji = (DiscontinuedInstruction.JsrInstruction) inst;
-        addLabelsToWorklist(ji.target(), null, stack);
-        return false;
+        throw new UnsupportedOperationException(
+            "JSR/JSR_W opcodes are illegal in class files version 51.0+ and not supported: "
+                + inst);
-      // TODO: JSR and RET have been illegal since JDK 7 (class file version 51.0).
-      // Consider throwing an UnsupportedOperationException for these opcodes
-      // if we're only supporting modern class files (version 51.0+).
-      // Currently maintaining support for completeness.
-      // operand stack: no change
       case Opcode.RET:
       case Opcode.RET_W:
-        // the variable referenced must contain a return address
-        // that we would treat as target of jump; no way to do that
-        return false;
+        throw new UnsupportedOperationException(
+            "RET/RET_W opcodes are illegal in class files version 51.0+ and not supported: "
+                + inst);

Also applies to: 865-874

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5eff1fd and baebb2b.

📒 Files selected for processing (1)
  • java/daikon/dcomp/CalcStack24.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
java/daikon/dcomp/CalcStack24.java (1)
java/daikon/dcomp/DCInstrument24.java (1)
  • DCInstrument24 (136-4952)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: codespecs.daikon (kvasir_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_latest_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_bundled_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (quick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nontxt_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nonquick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (misc_ubuntu_jdk25)
  • GitHub Check: build

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
java/daikon/dcomp/OperandStack24.java (4)

71-98: Align hashCode with coarse equality semantics in equals.

equals treats all non‑primitive (including null) stack entries as equivalent and only distinguishes primitives, but hashCode delegates to stack.hashCode(), which depends on the exact ClassDesc (and on null vs non‑null). Two stacks that equals considers equal can currently have different hash codes, violating the equals/hashCode contract.

You should compute the hash code in terms of the same coarse partition used by equals (primitive vs non‑primitive/null), ignoring the specific reference types. For example:

   /**
    * @return a hash code value for the object
    */
   @Override
   public int hashCode(@GuardSatisfied OperandStack24 this) {
-    return stack.hashCode();
+    int result = 1;
+    for (ClassDesc item : stack) {
+      // In equals(), any non-primitive or null value is treated as equivalent,
+      // and primitives are treated as equivalent among themselves when they match.
+      int elementHash = (item == null || !item.isPrimitive()) ? 0 : 1;
+      result = 31 * result + elementHash;
+    }
+    return result;
   }

This preserves the intended approximate equality while restoring the required contract.

Also applies to: 165-171


204-214: Clarify pop(int count)’s Javadoc and BCEL-compatibility behavior.

The Javadoc still refers to “i elements” while the parameter is named count, and the method always returns null without explaining why. Tighten the docs to reduce confusion:

-  /**
-   * Pops i elements off the stack. Always returns {@code null}.
-   *
-   * @return {@code null}
-   */
+  /**
+   * Pops {@code count} elements off the stack. Always returns {@code null}.
+   *
+   * <p>The return value is retained for BCEL API compatibility and is always {@code null}.
+   *
+   * @return {@code null}
+   */

If you no longer need BCEL signature compatibility at all call sites, consider changing the return type to void in a follow‑up.


216-222: Enforce maxStack in terms of slots, not element count, in push.

maxStack is documented as “maximum number of stack slots,” but push enforces only stack.size() >= maxStack. A single long/double consumes 2 slots, so you can overflow maxStack in terms of slots while stack.size() is still below the bound (e.g., maxStack == 1, pushing a long).

Use slotsUsed() plus the incoming element’s slotSize to enforce the true slot capacity:

   /** Pushes a ClassDesc object onto the stack. */
   public void push(final ClassDesc type) {
-    if (stack.size() >= maxStack) {
+    if (slotsUsed() + slotSize(type) > maxStack) {
       throw new DynCompError("Operand stack size exceeded: " + stack);
     }
     stack.add(type);
   }

This aligns the runtime check with both the Javadoc and JVM stack semantics.


233-245: Iterate directly over stack in slotsUsed() instead of via peek(i).

slotsUsed() currently calls peek(i) inside the loop, which in turn calls size() and reverse‑indexes the list. Functionally it’s fine, but it adds indirection and unnecessary work.

A direct iteration over stack is simpler and avoids depending on peek’s indexing convention:

   public @NonNegative int slotsUsed(@GuardSatisfied OperandStack24 this) {
-    int slots = 0;
-    for (int i = 0; i < stack.size(); i++) {
-      slots += slotSize(peek(i));
-    }
-    return slots;
+    int slots = 0;
+    for (ClassDesc item : stack) {
+      slots += slotSize(item);
+    }
+    return slots;
   }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between baebb2b and 5503afe.

📒 Files selected for processing (1)
  • java/daikon/dcomp/OperandStack24.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: codespecs.daikon (kvasir_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (misc_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_bundled_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (quick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_latest_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nontxt_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nonquick_ubuntu_jdk25)
  • GitHub Check: build
🔇 Additional comments (1)
java/daikon/dcomp/OperandStack24.java (1)

262-275: Confirm slotSize’s range and reliance on TypeKind.from(ClassDesc).

Given the Javadoc (“2 for long/double, 1 for everything else”) and the use of TypeKind.from(item).slotSize(), this method should always return 1 or 2 (with null explicitly mapped to 1). That matches JVM slot semantics and answers the earlier question about its range.

Please double‑check against the Java 24 TypeKind/ClassDesc APIs to ensure no new kinds can appear here with a different slotSize.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (10)
java/daikon/dcomp/ClassGen24.java (3)

111-113: Typo: "indicatated" → "indicated"

This typo was flagged in a previous review but appears to still be present.

-   * Determine if this class contains the indicatated method.
+   * Determine if this class contains the indicated method.

202-209: Consider returning an unmodifiable view from getInterfaceList().

As noted in a previous review, callers (e.g., classBuilder.withInterfaces(classGen.getInterfaceList())) only read this list. Returning Collections.unmodifiableList(interfaceList) would prevent accidental modifications.

   public List<ClassEntry> getInterfaceList() {
-    return interfaceList;
+    return Collections.unmodifiableList(interfaceList);
   }

37-37: Consider making classModel final for consistency.

All other cached fields (accessFlags, interfaceList, superclassName, className, classBuilder, isInterface, isStatic) are declared final, but classModel is not. Since it's also assigned only in the constructor and never reassigned, making it final would be consistent with the other fields.

-  private ClassModel classModel;
+  private final ClassModel classModel;
java/daikon/chicory/MethodGen24.java (2)

614-622: Defensive validation still missing for setParameterNames.

As flagged in a previous review, this setter stores the caller's array directly without validating that paramNames.length == paramTypes.length. The Javadoc warns users, but a mismatch will cause ArrayIndexOutOfBoundsException in fixLocals, toString, and other methods that iterate both arrays in lockstep.

Consider adding defensive validation:

   public void setParameterNames(final @Identifier String[] paramNames) {
+    if (paramNames.length != this.paramTypes.length) {
+      throw new IllegalArgumentException(
+          "paramNames.length (" + paramNames.length + ") != paramTypes.length (" + this.paramTypes.length + ")");
+    }
     this.paramNames = paramNames;
   }

643-651: Defensive validation still missing for setParameterTypes.

Same issue as setParameterNames - the setter doesn't validate the length invariant.

   public void setParameterTypes(final ClassDesc[] paramTypes) {
+    if (paramTypes.length != this.paramNames.length) {
+      throw new IllegalArgumentException(
+          "paramTypes.length (" + paramTypes.length + ") != paramNames.length (" + this.paramNames.length + ")");
+    }
     this.paramTypes = paramTypes;
   }
java/daikon/chicory/Instrument24.java (1)

1254-1254: Use diamond operator for consistency.

This was flagged in a previous review. The explicit type argument should be removed to match the style used elsewhere in the file.

-    List<SwitchCase> newCaseList = new ArrayList<SwitchCase>();
+    List<SwitchCase> newCaseList = new ArrayList<>();
java/daikon/dcomp/BuildJDK24.java (4)

194-198: Filter only .class entries when writing jdk_classes.txt

The loop writes all entries from class_stream_map including non-.class resources. This should be restricted to only .class files to avoid polluting the class list.

       try (PrintWriter pw = new PrintWriter(jdk_classes_file, UTF_8.name())) {
         for (String classFileName : class_stream_map.keySet()) {
-          pw.println(classFileName.replace(".class", ""));
+          if (classFileName.endsWith(".class")) {
+            pw.println(classFileName.substring(0, classFileName.length() - ".class".length()));
+          }
         }
       }

364-390: Close InputStreams for skipped entries to prevent resource leaks

When skipping module-info.class, non-.class files, or Object.class, the associated InputStream from class_stream_map is never closed. For large JDK instrumentation runs, this can exhaust file descriptors.

         if (classFileName.equals("module-info.class")) {
           System.out.printf("Skipping file %s%n", classFileName);
+          try (InputStream ignored = class_stream_map.get(classFileName)) { /* close */ }
           continue;
         }
 
         // Handle non-.class files and Object.class.  In JDK 8, copy them unchanged.
         // For JDK 9+ we do not copy as these items will be loaded from the original module file.
         if (!classFileName.endsWith(".class") || classFileName.equals("java/lang/Object.class")) {
           if (BcelUtil.javaVersion > 8) {
             if (verbose) {
               System.out.printf("Skipping file %s%n", classFileName);
             }
+            try (InputStream ignored = class_stream_map.get(classFileName)) { /* close */ }
             continue;
           }

451-467: Ensure parent directories exist before writing interface classes

createDCompClass writes to destDir/java/lang/ but does not ensure the directory exists. While it may exist from prior instrumentation, this method could fail if called independently.

       classBytes =
           ClassFile.of()
               .build(
                   ClassDesc.of(Signatures.addPackage("java.lang", className)),
                   classBuilder -> finishCreateDCompClass(classBuilder, dcompInstrumented));
       // Write the byte array to a .class file.
       File outputFile = new File(new File(new File(destDir, "java"), "lang"), className + ".class");
+      File parent = outputFile.getParentFile();
+      if (parent != null) {
+        parent.mkdirs();
+      }
       Files.write(outputFile.toPath(), classBytes);

491-492: Consider making timeFormatter a constant

DateTimeFormatter is immutable and thread-safe. Declaring it as private static final with an uppercase name follows Java conventions.

-  private static final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss");
+  private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");

And update the usage at line 551:

-          LocalDateTime.now(ZoneId.systemDefault()).format(timeFormatter));
+          LocalDateTime.now(ZoneId.systemDefault()).format(TIME_FORMATTER));
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 09bd3e1 and d4ef177.

📒 Files selected for processing (9)
  • java/daikon/chicory/Instrument.java (47 hunks)
  • java/daikon/chicory/Instrument24.java (57 hunks)
  • java/daikon/chicory/MethodGen24.java (16 hunks)
  • java/daikon/chicory/MethodInfo.java (2 hunks)
  • java/daikon/dcomp/BuildJDK.java (6 hunks)
  • java/daikon/dcomp/BuildJDK24.java (1 hunks)
  • java/daikon/dcomp/ClassGen24.java (1 hunks)
  • java/daikon/dcomp/Instrument.java (10 hunks)
  • java/daikon/dcomp/Instrument24.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
java/daikon/dcomp/Instrument24.java (2)
java/daikon/chicory/ClassInfo.java (1)
  • ClassInfo (20-154)
java/daikon/dcomp/Premain.java (1)
  • Premain (32-417)
java/daikon/dcomp/BuildJDK24.java (3)
java/daikon/DynComp.java (1)
  • DynComp (25-541)
java/daikon/chicory/ClassInfo.java (1)
  • ClassInfo (20-154)
java/daikon/dcomp/DCInstrument24.java (1)
  • DCInstrument24 (136-4952)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: codespecs.daikon (typecheck_latest_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_bundled_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (kvasir_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nonquick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (misc_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nontxt_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (quick_ubuntu_jdk25)
  • GitHub Check: build
🔇 Additional comments (30)
java/daikon/chicory/MethodInfo.java (1)

104-104: LGTM: Consistent field renaming.

The constructor parameter and field assignment have been updated consistently with the field rename. The new name exit_location_is_included is more descriptive and improves code clarity.

Also applies to: 111-111

java/daikon/dcomp/ClassGen24.java (1)

94-109: LGTM: Cross-pool ClassEntry comparison fixed.

The addInterface method now correctly compares by internal name rather than relying on ClassEntry.equals(), which avoids the cross-constant-pool equality issue flagged in a previous review.

java/daikon/chicory/Instrument.java (4)

8-8: LGTM: ArraysPlume import added for cleaner parameter type mapping.

The new import enables the simplified getFullyQualifiedParameterTypes implementation using ArraysPlume.mapArray.


1142-1145: LGTM: Cleaner implementation using ArraysPlume.

Replacing the manual loop with ArraysPlume.mapArray is more concise and idiomatic.


621-624: LGTM: Iterator contract documented as requested.

The comment at lines 621-622 now documents the invariant that exit_location_is_included contains exactly one boolean per return instruction and exit_locations contains an integer only when that boolean is true. This addresses previous review feedback.


1265-1267: LGTM: Boolean check simplified.

The condition now uses the idiomatic !foundLine instead of foundLine == false.

java/daikon/chicory/MethodGen24.java (3)

315-348: LGTM: New constructor for DCInstrument24-created methods.

The constructor properly initializes all required fields and follows the same pattern as the main constructor. The empty localsTable will be populated when fixLocals is called.


447-525: LGTM: fixLocals implementation with clear postconditions.

The method handles the tricky cases well: missing locals for parameters, alignment gaps for category-2 parameters, and absent this for non-static native methods. The Javadoc clearly documents the postconditions. The re-sorting by slot after modifications is correct.


554-566: LGTM: isMain() implementation is correct.

The method correctly checks all criteria for a standard main method: static, void return type, name "main", single parameter of type String[].

java/daikon/chicory/Instrument24.java (4)

995-998: LGTM: Iterator exhaustion checks added.

The assertions at lines 996-997 verify that both shouldIncludeIter and exitLocationIter are fully consumed after instrumenting all returns, catching any mismatch between create_method_info_if_instrumented and the code scan.


1500-1502: LGTM: Boolean check uses idiomatic form.

The condition now uses !foundLine instead of foundLine == false.


838-856: LGTM: Clean decomposition of instrumentation pipeline.

The instrumentInstructionList method cleanly decomposes into addInstrumentationAtEntry and addInstrumentationAtExits, making the instrumentation flow easy to follow.


1665-1669: LGTM: Wildcard handling added for type arguments.

The convertTypeArgumentsToBinaryNames method now handles the unbounded wildcard * by returning ?, which is needed for signatures containing wildcards.

java/daikon/dcomp/BuildJDK.java (4)

249-267: LGTM: JarFile now uses try-with-resources.

The JarFile is now properly closed via try-with-resources, ensuring resources are released even if an exception occurs during processing.


464-464: LGTM: timeFormatter made static final.

DateTimeFormatter is thread-safe and immutable, so making this a static final field is appropriate and avoids recreating the formatter on each use.


55-55: LGTM: Class made final.

Making BuildJDK final prevents unintended subclassing, which is appropriate for a utility class with a private constructor.


485-485: LGTM: Method renamed to instrument_jdk_class().

The rename aligns with the broader refactoring in DCInstrument to clarify that this instruments a single JDK class.

java/daikon/dcomp/Instrument.java (5)

27-30: LGTM: Section markers improve code organization.

The "Start/End of diagnostics" comments help readers navigate the file structure.


81-96: LGTM: Renamed to writeDebugClassFiles with improved error handling.

The method is renamed consistently with Instrument24.java, and the error handling now conditionally prints stack traces only when debug is enabled.


273-292: LGTM: is_dcomp now uses @InternalForm annotation.

The parameter type annotation correctly reflects that the method receives class names in internal form (with / separators) directly from the transform method. The string comparisons are updated to use / instead of ..


302-319: LGTM: is_transformer updated consistently.

The @InternalForm annotation and /-based comparisons are consistent with the is_dcomp changes.


175-178: LGTM: Added check for java/lang/instrument/ classes.

This new guard prevents instrumenting Java's own instrumentation classes, which could cause circular dependencies or failures.

java/daikon/dcomp/BuildJDK24.java (3)

113-131: LGTM!

The main method structure is well-organized with clear branching for the two usage modes (JDK instrumentation vs. specific class files). The progress reporting and error handling are appropriate.


207-241: LGTM!

The check_java_home method properly validates the Java environment with clear error messages and appropriate exit conditions.


506-553: LGTM!

The instrumentClassFile method is well-structured with proper directory creation, error handling, and progress reporting. The console check before printing progress is a nice touch.

java/daikon/dcomp/Instrument24.java (5)

156-161: LGTM!

The null check for className is now correctly placed before the call to Signatures.internalFormToBinaryName(), preventing the NPE that was flagged in the previous review.


94-126: LGTM!

The writeDebugClassFiles method has robust error handling that prevents instrumentation failures due to debug output issues. The design to catch and log errors without rethrowing is appropriate for diagnostic code.


142-301: LGTM!

The transform method is well-structured with:

  • Proper null handling for className and loader
  • Appropriate checks for shutdown, pre-instrumented classes, JDK classes, and known transformers
  • Graceful error handling that returns null (no transformation) rather than crashing
  • Optional debug output when DynComp.dump is enabled

The one-time warning for detected transformers (lines 229-236) is a good UX pattern.


310-330: LGTM!

The is_dcomp helper correctly identifies internal DynComp classes while allowing test classes (DcompTest, ChicoryTest) to be instrumented. The explicit exclusions for plumelib and specific Daikon classes are appropriate.


339-358: LGTM!

The is_transformer heuristic covers common bytecode transformation libraries (Groovy, Mockito, Objenesis). The list can be extended if additional conflicts are discovered.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
java/daikon/dcomp/Instrument.java (1)

298-320: Add @InternalForm annotation to is_transformer parameter.

The is_transformer method checks slash-separated class name patterns (lines 304-318), which is InternalForm notation, and is called with an @InternalForm String at line 197. However, its signature (line 302) lacks the @InternalForm annotation, unlike the consistent is_dcomp method at line 273. This type annotation inconsistency should be corrected.

Apply this diff:

-  protected static boolean is_transformer(String className) {
+  protected static boolean is_transformer(@InternalForm String className) {
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4ef177 and a0ea76c.

📒 Files selected for processing (1)
  • java/daikon/dcomp/Instrument.java (10 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: codespecs.daikon (typecheck_latest_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (quick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_bundled_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nontxt_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (kvasir_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (misc_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nonquick_ubuntu_jdk25)
  • GitHub Check: build
🔇 Additional comments (9)
java/daikon/dcomp/Instrument.java (9)

20-24: LGTM: Improved documentation clarity.

The javadoc updates better explain the class's role as a ClassFileTransformer and clarify the transform method's decision-making behavior.


27-30: LGTM: Clear section boundaries.

The diagnostic block markers improve code organization and readability.


81-96: LGTM: Method rename and improved error handling.

The rename to writeDebugClassFiles is more descriptive, and the conditional stack trace printing (lines 91-93) reduces noise when debug mode is disabled while preserving diagnostic information when needed.


121-132: LGTM: Early binaryClassName introduction improves consistency.

Computing binaryClassName early (line 131) and using it uniformly throughout the method improves logging consistency and readability.


139-178: LGTM: Improved pre-check guards and comments.

The consolidated problem class check (lines 165-168) and the explicit skip for java/lang/instrument/ classes (lines 175-178) improve code clarity and robustness. The comment updates enhance readability.


214-219: LGTM: Accurate loader terminology.

Using "loaders" (plural) correctly reflects that multiple loader instances are being logged.


227-251: LGTM: Consistent and improved error handling.

The standardized error handling for both parsing (lines 227-233) and transformation (lines 239-251) errors with conditional stack traces reduces noise while preserving diagnostic information when needed. Returning null correctly signals no bytecode changes.


253-262: LGTM: Clear return flow.

The refactored return logic is clear and consistent with the use of binaryClassName throughout.


269-292: LGTM: Proper type annotations for is_dcomp.

The signature change to private static boolean is_dcomp(@InternalForm String className) correctly documents that the method expects slash-separated class names, matching its implementation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
java/daikon/dcomp/BuildJDK24.java (4)

194-198: Filter only .class entries when writing to jdk_classes.txt.

The loop writes all keys from class_stream_map including potential non-.class resources. This should filter for .class entries only.

       try (PrintWriter pw = new PrintWriter(jdk_classes_file, UTF_8.name())) {
         for (String classFileName : class_stream_map.keySet()) {
-          pw.println(classFileName.replace(".class", ""));
+          if (classFileName.endsWith(".class")) {
+            pw.println(classFileName.substring(0, classFileName.length() - ".class".length()));
+          }
         }
       }

343-344: Typo: "indentified" → "identified".

-   * Instrument each of the classes indentified by the class_stream_map argument.
+   * Instrument each of the classes identified by the class_stream_map argument.

367-394: Close InputStreams for skipped entries to avoid resource leaks.

For entries skipped (e.g., module-info.class, non-.class files, or Object.class on JDK 9+), the corresponding InputStream from class_stream_map is never closed, leading to resource exhaustion when processing many files.

         if (classFileName.equals("module-info.class")) {
           System.out.printf("Skipping file %s%n", classFileName);
+          try (InputStream ignored = class_stream_map.get(classFileName)) { /* close */ }
           continue;
         }

         // Handle non-.class files and Object.class.  In JDK 8, copy them unchanged.
         // For JDK 9+ we do not copy as these items will be loaded from the original module file.
         if (!classFileName.endsWith(".class") || classFileName.equals("java/lang/Object.class")) {
           if (BcelUtil.javaVersion > 8) {
             if (verbose) {
               System.out.printf("Skipping file %s%n", classFileName);
             }
+            try (InputStream ignored = class_stream_map.get(classFileName)) { /* close */ }
             continue;
           }

464-466: Ensure parent directories exist before writing.

Files.write() will throw NoSuchFileException if the parent directory doesn't exist. While java/lang is likely created during instrument_classes, it's safer to explicitly ensure it exists.

       File outputFile = new File(new File(new File(destDir, "java"), "lang"), className + ".class");
+      File parent = outputFile.getParentFile();
+      if (parent != null) {
+        parent.mkdirs();
+      }
       Files.write(outputFile.toPath(), classBytes);
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a0ea76c and 9a51a83.

📒 Files selected for processing (2)
  • java/daikon/dcomp/BuildJDK.java (6 hunks)
  • java/daikon/dcomp/BuildJDK24.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
java/daikon/dcomp/BuildJDK24.java (3)
java/daikon/DynComp.java (1)
  • DynComp (25-541)
java/daikon/chicory/ClassInfo.java (1)
  • ClassInfo (20-154)
java/daikon/dcomp/DCInstrument24.java (1)
  • DCInstrument24 (136-4952)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: codespecs.daikon (typecheck_latest_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (misc_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nonquick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (quick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (kvasir_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_bundled_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nontxt_ubuntu_jdk25)
  • GitHub Check: build
🔇 Additional comments (7)
java/daikon/dcomp/BuildJDK.java (3)

55-58: LGTM! Class made final with appropriate constructor.

Making BuildJDK a final class with a private constructor is appropriate since this is a utility class that should not be subclassed and is only instantiated within main.


467-467: LGTM! DateTimeFormatter is now static final.

DateTimeFormatter is immutable and thread-safe, so making timeFormatter a private static final field is the correct approach.


486-489: LGTM! API call updated to align with refactored instrumentation.

The change from instrument_jdk() to instrument_jdk_class() properly aligns with the DCInstrument API refactoring introduced in this PR.

java/daikon/dcomp/BuildJDK24.java (4)

59-62: LGTM! Proper utility class structure.

The class is correctly declared as public final with a private constructor, consistent with BuildJDK.java.


253-280: LGTM! JarFile handling is consistent with BuildJDK.java.

The intentional non-closure of the JarFile is documented and necessary to keep InputStream objects valid until they are consumed by instrument_classes. For a batch tool that exits after completion, this is acceptable.


478-492: LGTM! Interface class generation using ClassFile API.

The implementation correctly uses the Java ClassFile API to build interface classes with appropriate flags, version, and optional method declaration.


510-556: LGTM! Instrumentation flow is well-structured.

The method properly creates ClassInfo, delegates to DCInstrument24, handles instrumentation failure, writes debug files when enabled, and provides progress feedback.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (9)
java/daikon/dcomp/OperandStack24.java (3)

75-102: Fix equals/hashCode contract violation.

The equals method uses coarse equality (e.g., null matches any non-primitive; all non-primitive arrays/classes are treated as equivalent), but hashCode delegates to stack.hashCode(), which depends on exact ClassDesc values. This violates the equals/hashCode contract: two stacks that equals deems equal can have different hash codes.

Apply this diff to fix the contract:

   @Override
   public int hashCode(@GuardSatisfied OperandStack24 this) {
-    return stack.hashCode();
+    int result = 1;
+    for (ClassDesc item : stack) {
+      int elementHash;
+      if (item == null) {
+        elementHash = 0; // matches any non-primitive in equals
+      } else if (item.isPrimitive()) {
+        elementHash = item.hashCode(); // distinguish different primitive types
+      } else {
+        elementHash = 2; // all non-primitive, non-null items are equivalent in equals
+      }
+      result = 31 * result + elementHash;
+    }
+    return result;
   }

Also applies to: 171-177


119-122: Annotate parameters as @Nullable for accuracy.

The method explicitly handles thisItem == null and otherItem == null, but the parameters are declared without nullness annotations. Marking them @Nullable documents the intended contract and aligns with the implementation.

   protected boolean compareOperandStackElements(
       @GuardSatisfied OperandStack24 this,
-      @Nullable ClassDesc thisItem,
-      @Nullable ClassDesc otherItem) {
+      @Nullable ClassDesc thisItem,
+      @Nullable ClassDesc otherItem) {

Note: The code already has @Nullable annotations (lines 121-122), so this appears to be addressed.


210-220: Clarify pop(int count) contract.

The method always returns null regardless of what was popped. If this is for BCEL API compatibility, the current implementation is fine. Consider changing the return type to void if all call sites ignore the return value.

java/daikon/dcomp/BuildJDK24.java (5)

253-280: Critical: InputStreams become invalid after JarFile is closed.

The comment at lines 258-259 claims the JarFile is intentionally not closed, but the code structure still risks issues. More importantly, for JDK 8 paths, if the JarFile is closed (or the code is refactored to use try-with-resources), the stored InputStreams become invalid.

Consider reading entry contents into memory:

+import java.io.ByteArrayInputStream;
...
-      // Get the InputStream for this file
-      InputStream is = jfile.getInputStream(entry);
-      class_stream_map.put(entryName, is);
+      // Read the bytes into memory so streams remain valid
+      try (InputStream is = jfile.getInputStream(entry)) {
+        byte[] bytes = is.readAllBytes();
+        class_stream_map.put(entryName, new ByteArrayInputStream(bytes));
+      }

194-198: Filter only .class entries when writing jdk_classes.txt.

The loop writes all keys from class_stream_map, including non-.class resources. This could cause issues when the list is consumed later.

       try (PrintWriter pw = new PrintWriter(jdk_classes_file, UTF_8.name())) {
         for (String classFileName : class_stream_map.keySet()) {
-          pw.println(classFileName.replace(".class", ""));
+          if (classFileName.endsWith(".class")) {
+            pw.println(classFileName.replace(".class", ""));
+          }
         }
       }

343-344: Typo: "indentified" → "identified".

-   * Instrument each of the classes indentified by the class_stream_map argument.
+   * Instrument each of the classes identified by the class_stream_map argument.

367-370: Close InputStreams for skipped entries to avoid resource leaks.

When entries are skipped (module-info.class, non-.class files, Object.class), the InputStreams from class_stream_map are never closed, potentially exhausting file descriptors when processing modules.

         if (classFileName.equals("module-info.class")) {
           System.out.printf("Skipping file %s%n", classFileName);
+          try (InputStream ignored = class_stream_map.get(classFileName)) { /* close */ }
           continue;
         }
...
         if (!classFileName.endsWith(".class") || classFileName.equals("java/lang/Object.class")) {
           if (BcelUtil.javaVersion > 8) {
             if (verbose) {
               System.out.printf("Skipping file %s%n", classFileName);
             }
+            try (InputStream ignored = class_stream_map.get(classFileName)) { /* close */ }
             continue;
           }

Also applies to: 374-380


464-466: Ensure parent directories exist before writing interface classes.

The code creates outputFile but doesn't ensure the parent directory (java/lang) exists under destDir.

       File outputFile = new File(new File(new File(destDir, "java"), "lang"), className + ".class");
+      File parent = outputFile.getParentFile();
+      if (parent != null && !parent.exists()) {
+        parent.mkdirs();
+      }
       Files.write(outputFile.toPath(), classBytes);
java/daikon/dcomp/CalcStack24.java (1)

962-992: Consider using enhanced switch expression for conciseness.

The switch statement is verbose. Java's enhanced switch expression would be more concise:

   static String npaiToElementTypeDescriptor(NewPrimitiveArrayInstruction npai) {
-    switch (npai.typeKind()) {
-      case BOOLEAN -> {
-        return "Z";
-      }
-      case BYTE -> {
-        return "B";
-      }
-      // ... more cases
-    }
+    return switch (npai.typeKind()) {
+      case BOOLEAN -> "Z";
+      case BYTE -> "B";
+      case CHAR -> "C";
+      case DOUBLE -> "D";
+      case FLOAT -> "F";
+      case INT -> "I";
+      case LONG -> "J";
+      case SHORT -> "S";
+      default -> throw new DynCompError(
+          "unknown primitive type " + npai.typeKind() + " in: " + npai);
+    };
   }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a51a83 and 0fbcd4c.

📒 Files selected for processing (6)
  • java/daikon/chicory/MethodInfo.java (2 hunks)
  • java/daikon/dcomp/BuildJDK24.java (1 hunks)
  • java/daikon/dcomp/CalcStack24.java (1 hunks)
  • java/daikon/dcomp/Instrument.java (10 hunks)
  • java/daikon/dcomp/Instrument24.java (1 hunks)
  • java/daikon/dcomp/OperandStack24.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
java/daikon/dcomp/CalcStack24.java (2)
java/daikon/chicory/MethodGen24.java (1)
  • MethodGen24 (51-840)
java/daikon/dcomp/DCInstrument24.java (1)
  • DCInstrument24 (136-4956)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: codespecs.daikon (typecheck_latest_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_bundled_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (quick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (misc_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nonquick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nontxt_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (kvasir_ubuntu_jdk25)
  • GitHub Check: build
🔇 Additional comments (23)
java/daikon/chicory/MethodInfo.java (3)

52-64: LGTM! Excellent documentation.

The expanded Javadoc clearly explains the different treatment of exit locations by Chicory vs. DynComp, and properly documents the relationship with exit_location_is_included. The typo fix ("Bassed" → "Based") and the structured formatting enhance readability.


67-72: LGTM! Field rename and documentation improve clarity.

The rename from is_included to exit_location_is_included makes the purpose immediately clear. The expanded Javadoc effectively addresses past review feedback by explicitly stating that the two lists are not index-aligned and that exit_locations is a filtered subset. This prevents potential misuse.


108-108: LGTM! Constructor parameter rename is consistent.

The constructor parameter rename matches the field rename, maintaining consistency throughout the codebase. The assignment correctly uses the new field name.

Also applies to: 115-115

java/daikon/dcomp/Instrument.java (8)

27-42: Good improvement: diagnostic boundaries now correctly encompass all diagnostic fields.

The transformer_seen field is now properly placed before the "End of diagnostics" marker, addressing the previous review concern about its confusing placement.


84-99: LGTM: Improved error handling with conditional stack traces.

The method rename to writeDebugClassFiles is more descriptive, and the conditional stack trace printing (lines 94-96) appropriately limits diagnostic output to debug mode.


121-144: LGTM: Clear early returns and improved logging.

The introduction of binaryClassName (line 131) improves logging consistency throughout the method, and the clarifying comments enhance code readability.


175-178: LGTM: Appropriate skip for Java instrumentation API classes.

The new check correctly skips classes in java/lang/instrument/ package, preventing instrumentation of Java's own instrumentation infrastructure which could cause conflicts.


214-219: LGTM: More accurate logging terminology.

The use of "loaders" (plural) correctly reflects that two loader references are being logged in each message.


227-233: LGTM: Consistent error handling pattern with graceful degradation.

The consistent conditional stack trace printing and explicit comments about returning null (lines 232, 250, 260) improve both debugging capability and code clarity. The graceful degradation approach (returning null to leave bytecode unchanged) is appropriate for a class transformer.

Also applies to: 245-251


273-292: LGTM: Improved consistency and type safety.

The parameter rename to className with @InternalForm annotation improves type safety, and the consistent early return pattern enhances readability.


302-320: LGTM: Visibility change supports extensibility for Java 24.

The change from private to protected (line 302) enables subclass access, likely needed for Instrument24 mentioned in the PR objectives. The parameter rename and early return refactoring maintain consistency with is_dcomp.

java/daikon/dcomp/OperandStack24.java (1)

275-281: LGTM! slotSize correctly handles null and uses TypeKind.

The implementation properly handles null (returns 1) and delegates to TypeKind.from(item).slotSize() for non-null items, correctly returning 2 for long/double and 1 for everything else.

java/daikon/dcomp/BuildJDK24.java (2)

494-495: LGTM! timeFormatter is already properly declared.

The timeFormatter is declared as private static final, which is correct for an immutable, thread-safe DateTimeFormatter.


558-561: Javadoc correctly references ClassFile API's verifier.

Previous feedback about incorrect BCEL reference has been addressed. The comment now correctly references "the ClassFile API's verifier".

java/daikon/dcomp/Instrument24.java (4)

154-161: LGTM! Null check correctly placed before internalFormToBinaryName.

The previous review flagged a potential NPE where Signatures.internalFormToBinaryName(className) was called before the null check. This has been correctly fixed - the null check at line 156 now occurs before the conversion at line 161.


97-129: LGTM! Debug file writing with robust error handling.

The writeDebugClassFiles method properly handles errors during BCEL parsing and file writing without affecting the main instrumentation flow. Errors are logged but don't propagate, which is appropriate for debug output.


176-216: LGTM! JDK class filtering is comprehensive.

The transform method properly filters:

  • Pre-instrumented classes (line 171)
  • JDK classes when not using instrumented JDK (line 181)
  • Problem packages (line 189)
  • Problem classes for Java 9+ (line 195)
  • Proxy classes (line 200)
  • Instrumentation classes (line 205)
  • DCRuntime (line 210)

339-358: LGTM! Transformer detection heuristic is appropriate.

The is_transformer method correctly identifies known bytecode transformation tools (Groovy, Mockito, Objenesis) that may conflict with DynComp. The one-time warning (via transformer_seen flag) is good UX.

java/daikon/dcomp/CalcStack24.java (5)

53-68: LGTM! Class documentation and NULL_CD sentinel are well-defined.

The class Javadoc now correctly documents that methods may modify DCInstrument24.locals and DCInstrument24.stacks. The NULL_CD sentinel is appropriately named and documented.


714-716: LGTM! JSR/RET opcodes correctly fall through to error.

The comment explains that JSR/RET have been illegal since JDK 7, and the opcodes correctly fall through to the default case which throws DynCompError("Unexpected instruction opcode"). This is the appropriate handling for modern class files.


920-953: LGTM! lceToCD correctly handles all LoadableConstantEntry types.

The method properly handles:

  • ClassEntryCD_Class (line 923) - correctly pushes java.lang.Class, not the described class
  • ConstantDynamicEntrycde.typeSymbol() (line 926)
  • MethodHandleEntryjava.lang.invoke.MethodHandle (line 941)
  • MethodTypeEntryjava.lang.invoke.MethodType (line 944)

Previous critical issues about ldc class literals have been resolved.


994-1001: LGTM! Javadoc @param description is now correct.

The @param result description correctly states "the result type descriptor to push (may be void or a primitive/reference type)" which matches the ClassDesc parameter type.


165-181: LGTM! AALOAD handling correctly addresses null arrayref case.

The code properly handles the case where the arrayref is null or NULL_CD, pushing NULL_CD to maintain stack consistency. This is important for symbolic simulation where the actual value isn't known.

Comment on lines 148 to 160
@SuppressWarnings("fallthrough")
static boolean simulateInstruction(Instruction inst, int inst_index, OperandStack24 stack) {
if (DCInstrument24.stacks[inst_index] != null) {
throw new DynCompError("instruction revisited: " + inst);
} else {
DCInstrument24.stacks[inst_index] = stack.getClone();
}
if (DCInstrument24.debugOperandStack) {
System.out.println(
"save stack state at: " + inst_index + ", " + DCInstrument24.stacks[inst_index]);
System.out.println("opcode: " + inst.opcode());
}
// calculate stack changes
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider adding method name to error message for easier debugging.

When an instruction is revisited, the error message only shows the instruction. Adding method context would help debugging.

     if (DCInstrument24.stacks[inst_index] != null) {
-      throw new DynCompError("instruction revisited: " + inst);
+      throw new DynCompError("instruction revisited at index " + inst_index + ": " + inst);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@SuppressWarnings("fallthrough")
static boolean simulateInstruction(Instruction inst, int inst_index, OperandStack24 stack) {
if (DCInstrument24.stacks[inst_index] != null) {
throw new DynCompError("instruction revisited: " + inst);
} else {
DCInstrument24.stacks[inst_index] = stack.getClone();
}
if (DCInstrument24.debugOperandStack) {
System.out.println(
"save stack state at: " + inst_index + ", " + DCInstrument24.stacks[inst_index]);
System.out.println("opcode: " + inst.opcode());
}
// calculate stack changes
@SuppressWarnings("fallthrough")
static boolean simulateInstruction(Instruction inst, int inst_index, OperandStack24 stack) {
if (DCInstrument24.stacks[inst_index] != null) {
throw new DynCompError("instruction revisited at index " + inst_index + ": " + inst);
} else {
DCInstrument24.stacks[inst_index] = stack.getClone();
}
if (DCInstrument24.debugOperandStack) {
System.out.println(
"save stack state at: " + inst_index + ", " + DCInstrument24.stacks[inst_index]);
System.out.println("opcode: " + inst.opcode());
}
// calculate stack changes
🤖 Prompt for AI Agents
In java/daikon/dcomp/CalcStack24.java around lines 148-160, the thrown
DynCompError lacks the method context; update the throw to include the method
name (e.g. throw new DynCompError("instruction revisited: " + inst + " in
method: " + methodName)). If a method name is accessible from the Instruction
(e.g. inst.method() or inst.getOwner()/inst.getName()), use that; otherwise add
a methodName parameter to simulateInstruction and propagate it from callers so
you can append " in method: " + methodName to the error message.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
java/daikon/chicory/Instrument24.java (2)

743-836: Entry/exit instrumentation split improves clarity; iterator checks guard consistency

  • instrumentCode now delegates to instrumentInstructionList, which in turn separates entry instrumentation (addInstrumentationAtEntry) from exit handling (addInstrumentationAtExits), making the control flow easier to follow.
  • Entry instrumentation:
    • Allocates a this_invocation_nonce via generateIncrementNonce and createLocalWithMethodScope.
    • Inserts Runtime.enter before the first LineNumber/Instruction while preserving leading LocalVariable and optional initial Label.
    • Tracks the original first‑instruction label via minfo.entryLabel and labelMap.
  • Exit instrumentation:
    • Uses exit_location_is_included and exit_locations iterators, with asserts ensuring both are fully consumed after scanning all CodeElements.
    • For included returns, duplicates the result into return__$trace2_val with correct handling for category‑2 types (DUP2) and then appends a call to Runtime.exit with the correct source line.

This decomposition is a solid structural improvement, and the iterator assertions should help catch mismatches between metadata and actual return instructions.

Also applies to: 838-999


1361-1383: Descriptor and type‑name helpers are reasonable; note limitations on generics

  • classDescToClassGetName correctly maps primitives and reference types to a Class.getName()‑like form ([Ljava.lang.String;‑style for arrays, java.lang.Foo for classes).
  • getFqBinaryNameParameterTypes uses ArraysPlume.mapArray to derive fully qualified binary names from parameter ClassDescs, which simplifies string handling in create_method_info_if_instrumented.
  • convertDescriptorToFqBinaryName, convertClassTypeDescriptorToFqBinaryName, and convertTypeArgumentsToBinaryNames provide extended support for decoding descriptors and simple signature forms (including trivial "*" wildcards).

These helpers match the documented intent (“some support for signatures” and no wildcard/type‑variable handling); if you later start feeding full generic signatures (with +, -, or T...;) through them, consider adding explicit handling or a guarded fallback to avoid IllegalArgumentExceptions on such descriptors.

Also applies to: 1451-1477, 1558-1698

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0fbcd4c and 657b783.

📒 Files selected for processing (8)
  • java/daikon/chicory/Instrument24.java (56 hunks)
  • java/daikon/dcomp/BuildJDK.java (7 hunks)
  • java/daikon/dcomp/BuildJDK24.java (1 hunks)
  • java/daikon/dcomp/CalcStack24.java (1 hunks)
  • java/daikon/dcomp/ClassGen24.java (1 hunks)
  • java/daikon/dcomp/Instrument24.java (1 hunks)
  • java/daikon/dcomp/OperandStack24.java (1 hunks)
  • java/daikon/dcomp/StackMapUtils24.java (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
java/daikon/dcomp/StackMapUtils24.java (2)
java/daikon/chicory/MethodGen24.java (1)
  • MethodGen24 (51-840)
java/daikon/dcomp/DCInstrument24.java (1)
  • DCInstrument24 (136-4946)
java/daikon/dcomp/BuildJDK.java (1)
java/daikon/dcomp/BuildJDK24.java (1)
  • SuppressWarnings (55-594)
java/daikon/dcomp/Instrument24.java (3)
java/daikon/DynComp.java (1)
  • DynComp (25-541)
java/daikon/chicory/ClassInfo.java (1)
  • ClassInfo (20-154)
java/daikon/dcomp/Premain.java (1)
  • Premain (32-417)
java/daikon/dcomp/CalcStack24.java (1)
java/daikon/dcomp/DCInstrument24.java (1)
  • DCInstrument24 (136-4946)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: codespecs.daikon (typecheck_bundled_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (typecheck_latest_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nontxt_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (misc_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (nonquick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (quick_ubuntu_jdk25)
  • GitHub Check: codespecs.daikon (kvasir_ubuntu_jdk25)
  • GitHub Check: build
🔇 Additional comments (6)
java/daikon/dcomp/BuildJDK.java (1)

466-511: Static timeFormatter + doc tweaks look correct

Making timeFormatter private static final and documenting classFileName as "internal form" aligns this implementation with BuildJDK24 and avoids unnecessary per-instance formatter allocation. No functional issues spotted in the updated instrumentation method.

java/daikon/dcomp/CalcStack24.java (1)

66-71: Symbolic stack simulation and helpers look consistent with JVM semantics

  • NULL_CD is used consistently as the null sentinel, and array / class / null merging logic appears coherent.
  • LDC handling via lceToCD now covers all modern constant kinds (class, dynamic, handle, method type, primitives, strings) with appropriate ClassDescs.
  • npaiToElementTypeDescriptor and pushResultClassDesc correctly model NEWARRAY element descriptors and integral‑widening of stack values (e.g., byte/short/char/boolean → int).
  • simulateCodeElement / simulateInstruction and addLabelsToWorklist interact cleanly with DCInstrument24’s stacks/worklist representation.

No functional problems spotted in these refactored regions.

Also applies to: 85-135, 171-201, 751-759, 913-975, 977-995, 997-1013

java/daikon/dcomp/StackMapUtils24.java (1)

50-101: Local-slot shifting logic for inserted parameters/locals looks sound

  • addNewSpecialLocal computes the insertion slot after this and existing parameters using lastArg.slot() + slotSize(lastArg), then adjusts later locals and instruction operands via adjust_code_for_locals_change.
  • adjust_code_for_locals_change correctly updates RetInstruction, IncrementInstruction, LoadInstruction, and StoreInstruction whose slot is ≥ the insertion offset.
  • minfo.nextLocalIndex and maxLocals are kept in sync with the new variable’s slot size.

This should safely handle both 1‑slot and 2‑slot locals for the two DynComp special variables.

Also applies to: 125-194

java/daikon/dcomp/Instrument24.java (1)

154-178: Transform flow and null‑safety for className look solid

  • The className == null guard now precedes internalFormToBinaryName, avoiding the NPE risk the JVM’s transform contract allows.
  • JDK vs non‑JDK routing (problem packages/classes, proxies, instrumentation classes, DCRuntime, dcomp/chicory/plumelib, other transformers) is consistent with the existing DynComp heuristics.
  • Class parsing and instrumentation both fail soft: parse or DCInstrument24.instrument errors log a message and return null, leaving the bytecodes unchanged.
  • Debug dumping of uninstrumented and instrumented bytes is correctly wrapped in try/catch so failures there do not break instrumentation.

No additional issues spotted in this transformer.

Also applies to: 241-305

java/daikon/chicory/Instrument24.java (2)

111-190: Shared ppt‑filtering (shouldIgnore) wiring across Chicory/DynComp is coherent

  • debug_ppt_omit is explicitly enabled from both Chicory and DynComp sides, and shouldIgnore consults Runtime.ppt_omit_pattern and ppt_select_pattern in a single place.
  • Exclusion precedes inclusion, and the “include only” semantics when ppt_select_pattern is non‑empty are clear and documented in the final if (!Runtime.ppt_select_pattern.isEmpty()) block.

This centralizes the ppt‑filter policy cleanly for both instruments.

Also applies to: 192-229


230-270: Refactored transform path and debug dumping behave correctly

  • The new writeDebugClassFiles helper is robust: it tolerates BCEL parse failures and IO errors, logging them only when debug is enabled and otherwise not interfering with instrumentation.
  • transform now logs entry with the Chicory prefix, checks className == null early, filters out boot classes, Chicory’s own classes (isChicoryClass), and proxies, then parses and instruments via ClassFile API in a try/catch that returns null on failure.
  • Instrumented vs non‑instrumented cases are handled cleanly: classInfo.shouldInclude gates the final return and optional debug class dumps.

Overall, the transform flow is clearer and more failure‑tolerant than the prior version.

Also applies to: 288-377

Comment on lines +190 to +199
// Write out the list of all classes in the jar file
File jdk_classes_file = new File(dest_dir, "java/lang/jdk_classes.txt");
System.out.printf("Writing a list of class names to %s%n", jdk_classes_file);
// Class names are written in internal form.
try (PrintWriter pw = new PrintWriter(jdk_classes_file, UTF_8.name())) {
for (String classFileName : class_stream_map.keySet()) {
pw.println(classFileName.replace(".class", ""));
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Restrict jdk_classes.txt to .class entries only

The current loop writes every key from class_stream_map into jdk_classes.txt:

for (String classFileName : class_stream_map.keySet()) {
  pw.println(classFileName.replace(".class", ""));
}

This means non‑class resources (and module-info.class, etc.) are recorded as if they were class names, and replace(".class", "") will also silently truncate any resource name that merely contains that substring.

For downstream consumers, it’s more accurate to record only actual class entries and to remove the suffix precisely:

-        for (String classFileName : class_stream_map.keySet()) {
-          pw.println(classFileName.replace(".class", ""));
-        }
+        for (String classFileName : class_stream_map.keySet()) {
+          if (classFileName.endsWith(".class")) {
+            pw.println(
+                classFileName.substring(0, classFileName.length() - ".class".length()));
+          }
+        }

This keeps jdk_classes.txt aligned with the set of class files actually being instrumented.

🤖 Prompt for AI Agents
In java/daikon/dcomp/BuildJDK24.java around lines 190 to 199, the loop writes
every key from class_stream_map and uses replace(".class", "") which can include
non-class resources and truncate names containing ".class"; change the loop to
only handle real class files by checking that classFileName.endsWith(".class")
and skipping entries like "module-info.class", and then remove the suffix
precisely (e.g., take substring(0, classFileName.length() - 6)) before writing
to jdk_classes.txt so only actual class names are recorded.

Comment on lines +290 to +341
Map<String, InputStream> gather_runtime_from_modules() {

Map<String, InputStream> class_stream_map = new HashMap<>();
FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
Path modules = fs.getPath("/modules");
// The path java_home+/lib/modules is the file in the host file system that
// corresponds to the modules file in the jrt: file system.
System.out.printf("using modules directory %s/lib/modules%n", java_home);
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(modules, "java.base*")) {
for (Path moduleDir : directoryStream) {
gather_runtime_from_modules_directory(
moduleDir, moduleDir.toString().length(), class_stream_map);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return class_stream_map;
}

/**
* This is a helper method for {@link #gather_runtime_from_modules}. It recurses down the module
* directory tree, selects the classes we want to instrument, creates an InputStream for each of
* these classes, and adds this information to the {@code class_stream_map} argument.
*
* @param path module file, which might be subdirectory
* @param modulePrefixLength length of "/module/..." path prefix before start of actual member
* path
* @param class_stream_map a map from class file name to InputStream that collects the results
*/
@SuppressWarnings("builder:required.method.not.called") // assignment into collection of @Owning
void gather_runtime_from_modules_directory(
Path path, int modulePrefixLength, Map<String, InputStream> class_stream_map) {

if (Files.isDirectory(path)) {
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path)) {
for (Path subpath : directoryStream) {
gather_runtime_from_modules_directory(subpath, modulePrefixLength, class_stream_map);
}
} catch (IOException e) {
throw new UncheckedIOException(e);
}
} else {
String entryName = path.toString().substring(modulePrefixLength + 1);
try {
// Get the InputStream for this file
InputStream is = Files.newInputStream(path);
class_stream_map.put(entryName, is);
} catch (Exception e) {
throw new Error(e);
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Close module InputStreams for skipped entries (resource leak on JDK 9+ path)

For Java 9+ you populate class_stream_map in gather_runtime_from_modules_directory with Files.newInputStream(path) for every module file:

InputStream is = Files.newInputStream(path);
class_stream_map.put(entryName, is);

In instrument_classes:

  • You never close the streams for:
    • module-info.class (explicit continue;).
    • Non‑.class entries when BcelUtil.javaVersion > 8 (you log and continue; without touching the stream).

Only .class files that reach the parsing block are wrapped in try (InputStream is = class_stream_map.get(classFileName)) { ... }, so their streams are closed.

On a full java.base run this leaves a number of InputStreams open until process exit, which is avoidable and was flagged in an earlier review.

One minimal fix is to close streams when you skip:

      if (classFileName.equals("module-info.class")) {
        System.out.printf("Skipping file %s%n", classFileName);
+       try (InputStream in = class_stream_map.get(classFileName)) {
+         // close skipped stream
+       } catch (IOException e) {
+         throw new UncheckedIOException(e);
+       }
        continue;
      }

@@
      if (!classFileName.endsWith(".class") || classFileName.equals("java/lang/Object.class")) {
        if (BcelUtil.javaVersion > 8) {
          if (verbose) {
            System.out.printf("Skipping file %s%n", classFileName);
          }
+         try (InputStream in = class_stream_map.get(classFileName)) {
+           // close skipped stream
+         } catch (IOException e) {
+           throw new UncheckedIOException(e);
+         }
          continue;
        }

Alternatively, you could avoid putting non‑.class entries into class_stream_map in the first place for the modules path.

Also applies to: 349-380, 396-425

🤖 Prompt for AI Agents
java/daikon/dcomp/BuildJDK24.java lines 290-341 (also apply similar fixes to
349-380 and 396-425): the code opens InputStreams for every module file and
stores them in class_stream_map, but later skips many entries (module-info.class
and non-*.class on JDK>8) without closing their streams, causing resource leaks;
fix by either (A) changing gather_runtime_from_modules_directory to only put
.class entries into class_stream_map (avoid opening streams for non-class
files), or (B) if retaining the current map, ensure any time you skip an entry
in instrument_classes you first close the associated InputStream (retrieve it
from class_stream_map and call close(), handling IOExceptions) and remove it
from the map before continue; apply the same pattern to the other referenced
ranges.

Comment on lines +94 to +109
/**
* Add an interface to this class.
*
* @param name the interface name, in binary format
*/
public void addInterface(@BinaryName String name) {
String internalName = name.replace('.', '/');
for (ClassEntry existing : interfaceList) {
if (existing.asInternalName().equals(internalName)) {
return;
}
}
ClassDesc ue = ClassDesc.of(name);
ClassEntry ce = classBuilder.constantPool().classEntry(ue);
interfaceList.add(ce);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider returning an unmodifiable interfaceList view

addInterface correctly avoids duplicates by comparing internal names, and interfaceList is now instance‑scoped. However, getInterfaceList() returns the mutable internal list directly:

public List<ClassEntry> getInterfaceList() {
  return interfaceList;
}

Current callers (e.g., passing into ClassBuilder.withInterfaces) appear to only read, but exposing the raw list makes it easy for future code to mutate interfaceList accidentally and diverge from the underlying ClassModel.

If external mutation isn’t required, prefer:

@@
-  public List<ClassEntry> getInterfaceList() {
-    return interfaceList;
-  }
+  public List<ClassEntry> getInterfaceList() {
+    return java.util.Collections.unmodifiableList(interfaceList);
+  }

This keeps the internal state under ClassGen24’s control while preserving existing read‑only usage.

Also applies to: 202-209

🤖 Prompt for AI Agents
In java/daikon/dcomp/ClassGen24.java around lines 94-109 (and similarly at lines
202-209), getInterfaceList currently returns the mutable internal interfaceList
exposing internal state; change the getter to return an unmodifiable view (e.g.,
wrap with Collections.unmodifiableList or use List.copyOf) so callers can read
but not mutate the internal list, and keep internal methods (like addInterface)
operating on the modifiable field.

Comment on lines +75 to +160
/**
* Returns true if and only if this OperandStack equals another, meaning equal lengths and equal
* objects on the stacks. This method is used to verify that the operand stacks match when two
* execution paths meet. A special case is {@code null} on an operand stack, which matches
* anything but a primitive. As we are assuming that the class file is valid, then it is okay for
* an object to exist on one execution path and be null on the other.
*/
@Override
public boolean equals(@GuardSatisfied OperandStack24 this, @GuardSatisfied @Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof OperandStack24)) {
return false;
}
final OperandStack24 other = (OperandStack24) o;
if (this.stack.size() != other.stack.size()) {
return false;
}
for (int i = 0; i < this.stack.size(); i++) {
ClassDesc thisItem = this.stack.get(i);
ClassDesc otherItem = other.stack.get(i);
if (!compareOperandStackElements(thisItem, otherItem)) {
return false;
}
}
return true;
}

/**
* Returns true if and only if the two OperandStack elements are equal. An element may be one of
* four possible items:
*
* <ul>
* <li>{@code null} a special case as it matches anything but a primitive
* <li>a primitive (such as {@code int}, {@code long}, etc.)
* <li>an array reference
* <li>an object reference
* </ul>
*
* @param thisItem one OperandStack element
* @param otherItem another OperandStack element
* @return true if and only if the items match
*/
protected boolean compareOperandStackElements(
@GuardSatisfied OperandStack24 this,
@Nullable ClassDesc thisItem,
@Nullable ClassDesc otherItem) {
if (thisItem == null) {
if (otherItem != null && otherItem.isPrimitive()) {
return false;
}
// We assume null matches any array or any class.
return true;
} else if (otherItem == null) {
// we know thisItem != null
if (thisItem.isPrimitive()) {
return false;
}
// We assume null matches any array or any class.
return true;
} else if (thisItem.isArray()) {
if (otherItem.isPrimitive()) {
return false;
}
// We assume an array matches any array or any class as they all have Object as a superclass
// at some point.
return true;
} else if (thisItem.isClassOrInterface()) {
if (otherItem.isPrimitive()) {
return false;
}
// We assume a class matches any array or any class as they all have Object as a superclass
// at some point.
return true;
} else if (thisItem.isPrimitive() && !otherItem.isPrimitive()) {
return false;
}
// Both operands are primitives - they better match.
if (thisItem.equals(otherItem)) {
return true;
} else {
throw new DynCompError(
"Operand stack primitives don't match: " + thisItem + ", " + otherItem);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix equals/hashCode contract violation for OperandStack24

equals deliberately treats many different values as equivalent:

  • null matches any non‑primitive (array or class).
  • Any array matches any non‑primitive.
  • Any class matches any non‑primitive.
  • Primitives must match exactly or a DynCompError is thrown.

However, hashCode distinguishes null from non‑primitive entries:

  • nullelementHash = 0
  • any non‑primitive, non‑null → elementHash = 2

So stacks like [null] and [someNonPrimitive] compare equal but get different hash codes, breaking the equals/hashCode contract. This is dangerous if OperandStack24 is ever used as a key in hash‑based collections (which is likely in the symbolic execution / worklist logic).

You need to ensure all values that are equal under compareOperandStackElements produce the same per‑element hash bucket. One straightforward fix is:

  • Treat all non‑primitive (including null, arrays, classes) as a single bucket.
  • Distinguish primitives by their ClassDesc.hashCode() as you do now.

For example:

   @Override
   public int hashCode(@GuardSatisfied OperandStack24 this) {
     int result = 1;
     for (ClassDesc item : stack) {
-      int elementHash;
-      if (item == null) {
-        elementHash = 0; // matches any non-primitive in equals
-      } else if (item.isPrimitive()) {
-        elementHash = item.hashCode(); // distinguish different primitive types
-      } else {
-        elementHash = 2; // all non-primitive, non-null items are equivalent in equals
-      }
+      int elementHash;
+      if (item != null && item.isPrimitive()) {
+        // Primitives must match exactly in equals, so use their own hash code.
+        elementHash = item.hashCode();
+      } else {
+        // `null`, arrays, and classes are all treated as equivalent in equals.
+        elementHash = 2;
+      }
       result = 31 * result + elementHash;
     }
     return result;
   }

This preserves the coarse equivalence from equals while restoring the required equals/hashCode contract.

Also applies to: 171-189

🤖 Prompt for AI Agents
In java/daikon/dcomp/OperandStack24 around lines 75-160 (and also apply same fix
for the other hashCode region at 171-189), the equals implementation treats
null, any array, and any class as equivalent but the current hashCode
distinguishes null vs non-null non-primitives; update the per-element hash
computation so all non-primitive entries (null, arrays, classes) map to the same
bucket (e.g., a constant value like 0 or 2) while primitives use their
ClassDesc.hashCode() as before; modify the loop that builds the overall hash to
compute elementHash = (item == null || !item.isPrimitive()) ?
NON_PRIMITIVE_BUCKET : item.hashCode(), combine these into the final result, and
ensure both hashCode locations use the same logic so equals/hashCode contract
holds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants