Skip to content

Fix context root preservation during nested evaluations#449

Merged
lukaszlenart merged 2 commits intomainfrom
fix/context-root-preservation-issue-390
Oct 25, 2025
Merged

Fix context root preservation during nested evaluations#449
lukaszlenart merged 2 commits intomainfrom
fix/context-root-preservation-issue-390

Conversation

@lukaszlenart
Copy link
Copy Markdown
Collaborator

Summary

Fixes #390 - Context root overwriting breaks expressions that reference #root during list processing operations.

Problem

PR #337 introduced a change to addDefaultContext() that unconditionally overwrites the context root with result.setRoot(root). This breaks functionality when:

  1. User creates an OgnlContext with a root object
  2. Expression performs list operations (selection, projection) with lambda expressions
  3. Lambda expression references #root.property
  4. During list iteration, getValue() is called with each list item as the root parameter
  5. addDefaultContext() overwrites the original context root with the list item
  6. #root now incorrectly points to the list item instead of the original context root

Example failure:

context = Ognl.createDefaultContext(rootObject);
// rootObject has property: contextProperty = "originalValue"
String expr = "testList.{? #root.contextProperty == 'originalValue'}";
Ognl.getValue(expr, context, rootObject);
// Fails: #root points to list item (String) instead of rootObject

Solution

Preserve the original context root during nested evaluations by detecting when:

  • initialContext exists with a non-null root
  • initialContext has user-defined variables (size() > 0)
  • A different root is being set (nested evaluation scenario)

When all conditions are met, preserve the original root. Otherwise, use default behavior.

Key insight: context.size() only counts user-defined variables, not reserved keys like #root, #this, _traceEvaluations. This allows us to distinguish between:

Changes

  1. ognl/src/main/java/ognl/Ognl.java: Updated addDefaultContext() with conditional logic to preserve context root during nested evaluations
  2. ognl/src/test/java/ognl/test/ContextRootPreservationTest.java: Added 8 comprehensive test cases covering:
    • List selection with #root references
    • List projection with #root references
    • Lambda expressions accessing #root
    • Context variable access during list processing
    • Verification that root is not overwritten by list items

Test Results

✅ All 607 tests pass (including 8 new tests)

[INFO] Tests run: 607, Failures: 0, Errors: 0, Skipped: 0
[INFO] BUILD SUCCESS

Backward Compatibility

The fix maintains full backward compatibility:

  • Existing tests that reuse contexts with different roots continue to work (when size() == 0)
  • New functionality correctly preserves root during nested evaluations (when size() > 0)
  • No changes to public API

Related Issues

Fixes issue where addDefaultContext() unconditionally overwrites the
context root, breaking expressions that reference #root during list
processing operations (selection, projection, etc.).

The fix preserves the original root when:
- Context exists with a non-null root
- Context has user-defined variables (size() > 0)
- A different root is being set (nested evaluation)

This allows #root to correctly reference the original context object
during list operations while still supporting legitimate context reuse
with different roots (when size() == 0).

Note: context.size() only counts user-defined variables, not built-in
reserved keys like #root, #this, and _traceEvaluations.

Fixes #390
Adds test cases covering various scenarios where context root should
be preserved during nested evaluations:

- List selection with #root references
- List projection with #root references
- Lambda expressions accessing #root
- Context variable access during list processing
- Verification that root is not overwritten by list items

These tests ensure issue #390 is properly resolved and prevent
regression in future changes.

Related to #390
@sonarqubecloud
Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
0.0% Coverage on New Code (required ≥ 80%)

See analysis details on SonarQube Cloud

@lukaszlenart lukaszlenart merged commit bd1e10d into main Oct 25, 2025
4 of 6 checks passed
@lukaszlenart lukaszlenart deleted the fix/context-root-preservation-issue-390 branch October 25, 2025 16:32
lukaszlenart added a commit that referenced this pull request Oct 25, 2025
…uilder

This commit resolves the conflict between the generic context feature and
PR #449 (fix/context-root-preservation-issue-390) by integrating the root
preservation logic into the OgnlContext.Builder provider.

The root preservation logic from PR #449 prevented context root overwriting
during nested evaluations (like list processing). When the generic context
branch replaced addDefaultContext() with a builder pattern, this logic was
lost during the rebase.

This fix adds the same conditional logic to the builder's default provider:
- Checks if initialContext is an OgnlContext instance
- Preserves original root when context has user variables (size() > 0)
- Preserves original root when new root differs from existing root
- Otherwise uses the new root (normal behavior)

All 607 tests pass, including the 8 context root preservation tests from
PR #449.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
lukaszlenart added a commit that referenced this pull request Oct 25, 2025
…uilder

This commit resolves the conflict between the generic context feature and
PR #449 (fix/context-root-preservation-issue-390) by integrating the root
preservation logic into the OgnlContext.Builder provider.

The root preservation logic from PR #449 prevented context root overwriting
during nested evaluations (like list processing). When the generic context
branch replaced addDefaultContext() with a builder pattern, this logic was
lost during the rebase.

This fix adds the same conditional logic to the builder's default provider:
- Checks if initialContext is an OgnlContext instance
- Preserves original root when context has user variables (size() > 0)
- Preserves original root when new root differs from existing root
- Otherwise uses the new root (normal behavior)

All 607 tests pass, including the 8 context root preservation tests from
PR #449.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@claude claude bot mentioned this pull request Oct 25, 2025
lukaszlenart added a commit that referenced this pull request Oct 25, 2025
…uilder

This commit resolves the conflict between the generic context feature and
PR #449 (fix/context-root-preservation-issue-390) by integrating the root
preservation logic into the OgnlContext.Builder provider.

The root preservation logic from PR #449 prevented context root overwriting
during nested evaluations (like list processing). When the generic context
branch replaced addDefaultContext() with a builder pattern, this logic was
lost during the rebase.

This fix adds the same conditional logic to the builder's default provider:
- Checks if initialContext is an OgnlContext instance
- Preserves original root when context has user variables (size() > 0)
- Preserves original root when new root differs from existing root
- Otherwise uses the new root (normal behavior)

All 607 tests pass, including the 8 context root preservation tests from
PR #449.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
lukaszlenart added a commit that referenced this pull request Oct 26, 2025
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.

ognl 3.4.5 - context root is overwritten on getValue()

1 participant