Skip to content

WG012: flag MutableSideEffect with reference-equality value type - #72

Merged
vikas0686 merged 2 commits into
mainfrom
feature/wg012
Jul 11, 2026
Merged

vikas0686 merged 2 commits into
mainfrom
feature/wg012

Conversation

@vikas0686

@vikas0686 vikas0686 commented Jul 11, 2026 •

Copy link
Copy Markdown
Owner

Fix issue #3

New declarative rule type, mutable-side-effect-equality, alongside the existing forbidden-method: flags Workflow.mutableSideEffect(id, valueClass, updateFunction, func) calls whose value type relies on inherited, identity-based Object.equals(). Since func constructs a new instance every call, equals() never reports "unchanged", so every call appends a new history event regardless of whether the logical value actually changed.

RuleDefinition gains a valueTypeArgumentIndex field. A new ValueBasedEqualityArgumentTarget (wogu-temporal.callgraph, reusable) resolves the named argument's Class literal via the symbol solver and checks for value-based equals(): a Java record, a declared or inherited non-Object equals(), or an unresolvable type (outside WoGu's own classpath) are all treated as safe, matching the false-negative- preferring default every other rule uses for unresolvable code. MutableSideEffectEqualityRule builds the target from a RuleDefinition and reuses TemporalRuleSupport, the same shape ForbiddenMethodRule uses for its own type.

Also fixes SourceRootParser silently failing to parse record declarations and pattern-matching instanceof (both valid since Java 16, routine in code targeting this project's Java 17 baseline) because neither ParserConfiguration ever set a LanguageLevel. This was making every rule's analysis silently incomplete against such files, not just WG012's own tests.

Adds docs/rules/WG012.md, and a PaymentService.refreshCachedBalance() violation in sample-temporal-project demonstrating that mutableSideEffect()'s updateFunction is only as good as the value type's equals().

Summary by CodeRabbit

  • New Features

    • Added WG012 to flag determinism risks in mutableSideEffect() when the value type uses reference/identity-based equality.
    • Added rule guidance emphasizing value-based equals()/hashCode() (e.g., records) and that changing the comparison logic alone may not help.
    • Updated the sample workflow to demonstrate the new warning scenario.
  • Bug Fixes

    • Improved Java 17+ parsing accuracy (records and instanceof pattern matching).
  • Documentation

    • Added WG012 documentation and updated the changelog and sample project description.
  • Tests

    • Expanded coverage for WG012 and rule configuration parsing.

New declarative rule type, mutable-side-effect-equality, alongside the
existing forbidden-method: flags Workflow.mutableSideEffect(id,
valueClass, updateFunction, func) calls whose value type relies on
inherited, identity-based Object.equals(). Since func constructs a new
instance every call, equals() never reports "unchanged", so every call
appends a new history event regardless of whether the logical value
actually changed.

RuleDefinition gains a valueTypeArgumentIndex field. A new
ValueBasedEqualityArgumentTarget (wogu-temporal.callgraph, reusable)
resolves the named argument's Class<T> literal via the symbol solver
and checks for value-based equals(): a Java record, a declared or
inherited non-Object equals(), or an unresolvable type (outside WoGu's
own classpath) are all treated as safe, matching the false-negative-
preferring default every other rule uses for unresolvable code.
MutableSideEffectEqualityRule builds the target from a RuleDefinition
and reuses TemporalRuleSupport, the same shape ForbiddenMethodRule uses
for its own type.

Also fixes SourceRootParser silently failing to parse record
declarations and pattern-matching instanceof (both valid since Java
16, routine in code targeting this project's Java 17 baseline) because
neither ParserConfiguration ever set a LanguageLevel. This was making
every rule's analysis silently incomplete against such files, not just
WG012's own tests.

Adds docs/rules/WG012.md, and a PaymentService.refreshCachedBalance()
violation in sample-temporal-project demonstrating that
mutableSideEffect()'s updateFunction is only as good as the value
type's equals().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 38cab4ac-0b7b-49b9-b2b7-1cf69f3bfee2

📥 Commits

Reviewing files that changed from the base of the PR and between 00480f6 and 6ade27f.

📒 Files selected for processing (6)
  • docs/rules/WG012.md
  • wogu-temporal/src/main/java/dev/wogu/temporal/ForbiddenMethodRule.java
  • wogu-temporal/src/main/java/dev/wogu/temporal/MutableSideEffectEqualityRule.java
  • wogu-temporal/src/main/java/dev/wogu/temporal/callgraph/ValueBasedEqualityArgumentTarget.java
  • wogu-temporal/src/main/resources/rules/wg012.yaml
  • wogu-temporal/src/test/java/dev/wogu/temporal/MutableSideEffectEqualityRuleTest.java
✅ Files skipped from review due to trivial changes (1)
  • docs/rules/WG012.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • wogu-temporal/src/main/resources/rules/wg012.yaml
  • wogu-temporal/src/main/java/dev/wogu/temporal/callgraph/ValueBasedEqualityArgumentTarget.java

📝 Walkthrough

Walkthrough

Adds WG012 to detect mutableSideEffect value types without value-based equality, including rule loading, call analysis, documentation, sample usage, and tests. It also configures JavaParser and symbol resolution for Java 17 syntax.

Changes

WG012 mutable-side-effect equality

Layer / File(s) Summary
Rule contract and registry wiring
wogu-temporal/src/main/java/dev/wogu/temporal/RuleDefinition.java, RuleDefinitionLoader.java, RuleRegistry.java, wogu-temporal/src/main/resources/rules/wg012.yaml
Adds valueTypeArgumentIndex, YAML parsing, and registry support for the mutable-side-effect-equality rule type.
Equality target and rule evaluation
wogu-temporal/src/main/java/dev/wogu/temporal/callgraph/ValueBasedEqualityArgumentTarget.java, MutableSideEffectEqualityRule.java, ForbiddenMethodRule.java
Resolves the Class<T> argument and identifies types without value-based equality, then reports matching violations with metadata, fixes, and configured execution contexts.
Sample violation and rule documentation
sample-temporal-project/src/main/java/com/example/wogu/sample/services/*, sample-temporal-project/src/main/java/com/example/wogu/sample/PaymentWorkflowImpl.java, docs/rules/WG012.md, CHANGELOG.md, sample-temporal-project/pom.xml
Adds the CachedBalance example, invokes refreshCachedBalance(), and documents WG012 and the updated sample violation count.
WG012 integration tests
wogu-temporal/src/test/java/dev/wogu/temporal/*Test.java
Covers rule loading, registry creation, validator registration, reference equality violations, value-based equality cases, and unrelated sideEffect calls.

Java 17 parsing support

Layer / File(s) Summary
Java 17 parsing configuration
wogu-temporal/src/main/java/dev/wogu/temporal/SourceRootParser.java
Sets JAVA_17 for source parsing and type-solving configurations to support newer Java syntax.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant WorkflowSource
  participant EqualityTarget
  participant TypeResolver
  participant WG012Rule
  WorkflowSource->>EqualityTarget: inspect mutableSideEffect call
  EqualityTarget->>TypeResolver: resolve Class<T> argument
  TypeResolver-->>EqualityTarget: equality information
  EqualityTarget-->>WG012Rule: matching call target
  WG012Rule-->>WorkflowSource: emit violation and suggested fix
Loading

Possibly related issues

  • Issue #3 — Implements the proposed WG012 rule, including detection, documentation, YAML configuration, sample violation, and tests.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 6.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly names WG012 and accurately summarizes the main change: flagging mutableSideEffect calls with reference-equality value types.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/wg012

Comment @coderabbitai help to get the list of available commands.

@vikas0686 vikas0686 linked an issue Jul 11, 2026 that may be closed by this pull request

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
wogu-temporal/src/main/java/dev/wogu/temporal/RuleDefinitionLoader.java (1)

119-123: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Wrap parse failures with sourceName for consistency.

Unlike requiredString/stringList, optionalInt doesn't attribute failures to the offending rule file. A malformed valueTypeArgumentIndex (e.g. 1.5, true) throws a bare NumberFormatException with no indication of which YAML resource caused it.

🔧 Proposed fix
-  private static Integer optionalInt(Map<String, Object> data, String key) {
-    Object value = data.get(key);
-    return value == null ? null : Integer.valueOf(value.toString());
-  }
+  private static Integer optionalInt(Map<String, Object> data, String key, String sourceName) {
+    Object value = data.get(key);
+    if (value == null) {
+      return null;
+    }
+    try {
+      return Integer.valueOf(value.toString());
+    } catch (NumberFormatException e) {
+      throw new IllegalStateException(
+          "Rule definition '" + sourceName + "' has an invalid '" + key + "': " + value, e);
+    }
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wogu-temporal/src/main/java/dev/wogu/temporal/RuleDefinitionLoader.java`
around lines 119 - 123, Update optionalInt to accept the relevant sourceName and
wrap Integer parsing failures with that source context, matching the
error-handling behavior of requiredString and stringList. Update every
optionalInt call site to pass the rule file’s sourceName while preserving null
handling for absent values.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/rules/WG012.md`:
- Around line 81-85: Update the WG012 guidance and corresponding matcher
behavior so an updateFunction that explicitly compares relevant fields is
recognized as a safe remediation for mutableSideEffect values such as
CachedBalance.class. If the matcher cannot inspect or validate updateFunction
comparisons, revise the documentation to clearly identify explicit comparison as
a runtime workaround that does not suppress the diagnostic, rather than
presenting it as a rule-compliant fix.

In
`@wogu-temporal/src/main/java/dev/wogu/temporal/callgraph/ValueBasedEqualityArgumentTarget.java`:
- Around line 90-112: Update hasValueBasedEquality so its equals-method match
requires the sole parameter type to be java.lang.Object in addition to the
existing name, arity, and non-Object declaring-type checks; continue accepting
records and preserving the current fallback behavior.

In
`@wogu-temporal/src/main/java/dev/wogu/temporal/MutableSideEffectEqualityRule.java`:
- Around line 38-54: The MutableSideEffectEqualityRule constructor must retain
RuleDefinition’s suppressedContexts and requiredContexts when creating its
target or rule metadata. Pass both context sets through the relevant
ValueBasedEqualityArgumentTarget/TemporalRuleSupport path instead of using empty
sets, so findViolations honors declarative context constraints.

---

Nitpick comments:
In `@wogu-temporal/src/main/java/dev/wogu/temporal/RuleDefinitionLoader.java`:
- Around line 119-123: Update optionalInt to accept the relevant sourceName and
wrap Integer parsing failures with that source context, matching the
error-handling behavior of requiredString and stringList. Update every
optionalInt call site to pass the rule file’s sourceName while preserving null
handling for absent values.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f9594b07-b97d-45a5-ade3-4b387c5f4714

📥 Commits

Reviewing files that changed from the base of the PR and between 83c0c51 and 00480f6.

📒 Files selected for processing (19)
  • CHANGELOG.md
  • docs/rules/WG012.md
  • sample-temporal-project/pom.xml
  • sample-temporal-project/src/main/java/com/example/wogu/sample/PaymentWorkflowImpl.java
  • sample-temporal-project/src/main/java/com/example/wogu/sample/services/CachedBalance.java
  • sample-temporal-project/src/main/java/com/example/wogu/sample/services/PaymentService.java
  • wogu-temporal/src/main/java/dev/wogu/temporal/MutableSideEffectEqualityRule.java
  • wogu-temporal/src/main/java/dev/wogu/temporal/RuleDefinition.java
  • wogu-temporal/src/main/java/dev/wogu/temporal/RuleDefinitionLoader.java
  • wogu-temporal/src/main/java/dev/wogu/temporal/RuleRegistry.java
  • wogu-temporal/src/main/java/dev/wogu/temporal/SourceRootParser.java
  • wogu-temporal/src/main/java/dev/wogu/temporal/TemporalWorkflowValidator.java
  • wogu-temporal/src/main/java/dev/wogu/temporal/callgraph/ValueBasedEqualityArgumentTarget.java
  • wogu-temporal/src/main/resources/rules/wg012.yaml
  • wogu-temporal/src/test/java/dev/wogu/temporal/MutableSideEffectEqualityRuleTest.java
  • wogu-temporal/src/test/java/dev/wogu/temporal/RuleDefinitionLoaderTest.java
  • wogu-temporal/src/test/java/dev/wogu/temporal/RuleDefinitionTest.java
  • wogu-temporal/src/test/java/dev/wogu/temporal/RuleRegistryTest.java
  • wogu-temporal/src/test/java/dev/wogu/temporal/TemporalWorkflowValidatorTest.java

Comment thread docs/rules/WG012.md Outdated
@vikas0686 vikas0686 self-assigned this Jul 11, 2026
Three fixes from code review, each verified against current behavior
before changing anything:

- docs/rules/WG012.md and wg012.yaml's replacement text claimed an
  updateFunction that explicitly compares fields is an alternative fix.
  It isn't: ValueBasedEqualityArgumentTarget only ever inspects the
  valueClass argument, never updateFunction, so that advice would not
  have suppressed the diagnostic. Corrected both to say so plainly,
  and explained why the matcher doesn't attempt to validate a lambda's
  comparison logic (a trivial or inverted comparator would look the
  same syntactically).

- hasValueBasedEquality's equals-detection matched any 1-param method
  named "equals" not declared on Object, which incorrectly treated a
  same-named overload like equals(Price) as a real Object.equals(Object)
  override. Added a parameter-type check (must be java.lang.Object) so
  only a genuine override (or a record's synthesized one) counts.
  Verified by reverting the fix and confirming the new regression test
  fails without it.

- MutableSideEffectEqualityRule's constructor hardcoded Set.of() for
  both suppressedContexts and requiredContexts instead of reading them
  off the RuleDefinition, silently ignoring both fields (wg012.yaml
  itself doesn't use them yet, so no observable effect today, but any
  future mutable-side-effect-equality rule declaring either would have
  been broken). Reused ForbiddenMethodRule's toExecutionContext
  (widened to package-private) to parse and wire both through, mirroring
  ForbiddenMethodRule exactly. Verified the same way: reverted, watched
  the new regression test fail, restored.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@vikas0686
vikas0686 merged commit 661734b into main Jul 11, 2026
2 checks passed
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.

WG012 — MutableSideEffect with reference-equality value type

1 participant