Skip to content

fix(controller): handle null values in renderWith() and date form helpers - #4633

Merged
bpamiri merged 3 commits into
wheels-dev:developfrom
michaeldiederich:fix/renderwith-null-pointer-exception
Oct 23, 2026
Merged

bpamiri merged 3 commits into
wheels-dev:developfrom
michaeldiederich:fix/renderwith-null-pointer-exception

Conversation

@michaeldiederich

@michaeldiederich michaeldiederich commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Description
When rendering JSON responses via renderWith() in vendor/wheels/controller/rendering.cfc, Wheels tries to convert oracle.sql.TIMESTAMP instances into CFML timestamps. If the data struct contains keys with null values (for example { "data": null, "error": null }), Lucee throws a NullPointerException, because its isInstanceOf(obj, className) implementation calls obj.getClass() on null.

A similar issue can occur in vendor/wheels/view/formsdate.cfc inside $dateOrTimeSelectNormalizeValue(), where isInstanceOf() is called on a value without prior null/object verification.

Steps to reproduce
Pass a struct with null values to renderWith():

response = {
    "success": true,
    "data": javaCast("null", ""),
    "error": javaCast("null", "")
};
renderWith(data=response);

Request the response format as json.

Actual behaviour
Lucee throws an unhandled Java exception: java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "obj" is null, from isInstanceOf in rendering.cfc.

Scope
The guard fixes JSON rendering and the date/time form-helper path. XML rendering of null struct values goes through the separate toXML path and is not covered here — that remains a follow-up.

Maintainer updates pushed to this branch

  • 8450de17 — extracted the LocalDateTime/Oracle TIMESTAMP unwrapping into $normalizeDateOrTimeObjectValue() so $dateOrTimeSelectNormalizeValue() passes the complexity gate (32 → 28); added the changelog fragment.
  • 5424ab6a — scoped the changelog fragment to JSON rendering.

Tests: core suite, H2, Adobe 2023, BoxLang, RustCFML and smoke legs are green on the current head.

@bpamiri bpamiri changed the title NullPointerException in renderWith() and formsdate when struct/value contains null fix(controller): handle null values in renderWith() and date form helpers Oct 23, 2026
@bpamiri

bpamiri commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the detailed report and the fix, Michael — the null-value repro and the isInstanceOf call sites are spot on, and we're taking this forward.

CI is green except the complexity gate: the added guard pushes $dateOrTimeSelectNormalizeValue() to 32 (the gate is 30). I'm pushing a small refactor to your branch that extracts the date/object normalization into a $-prefixed helper, so the main function drops back under the gate while behaviour stays the same. I'll also add a changelog fragment. No change needed from you — shout if you'd rather I carry it in a separate PR instead and leave your branch untouched.

…mplexity gate green

Moves the LocalDateTime/Oracle TIMESTAMP unwrapping out of $dateOrTimeSelectNormalizeValue into a $normalizeDateOrTimeObjectValue helper. Behaviour is unchanged: null and non-object values pass through untouched, the same two object types are converted, and the main helper drops from complexity 32 to under 30 so the code-quality gate passes. Adds the changelog fragment for the null-safety fix.

Signed-off-by: Peter Amiri <peter@alurium.com>
@github-actions github-actions Bot added the docs label Oct 23, 2026
@bpamiri

bpamiri commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator

Pushed a maintainer commit to your branch: 8450de170 refactor(view): extract date/time object normalization to keep the complexity gate green.

What it does:

  • Moves the LocalDateTime / Oracle TIMESTAMP unwrapping out of $dateOrTimeSelectNormalizeValue() into a new $normalizeDateOrTimeObjectValue() helper. Same conversions, same pass-through for null and non-object values — the only change is where the branches live.
  • cfml-complexity.py --gate 30 now reports PASS (the function was 32; back under the limit).
  • Adds changelog.d/renderwith-null-values.fixed.md for the fix.
  • PR title is now conventional (fix(controller): handle null values in renderWith() and date form helpers), which clears the commit-message check.

CI is re-running. No action needed from you — if you'd rather keep your branch exactly as you wrote it, say so and I'll move this into a maintainer-side PR instead.

XML rendering of null struct values still goes through the separate toXML path and is tracked separately; the fragment should not claim XML is fixed.

Signed-off-by: Peter Amiri <peter@alurium.com>
@bpamiri
bpamiri merged commit 121a1b7 into wheels-dev:develop Oct 23, 2026
23 checks passed
@michaeldiederich

Copy link
Copy Markdown
Contributor Author

Thank you very much!

bpamiri added a commit that referenced this pull request Oct 28, 2026
…alues (#4638) (#4650)

* fix(controller): render null XML values instead of throwing

Follow-up to #4633 (#4638). renderWith()'s JSON branch is null-safe, but the XML
branch still passed null struct/array/query/component values into toXML, whose
required data argument rejects null and whose branches read it directly. Guard
each recursion with isNull() and emit a type="null" element; a null top-level
value renders as empty output in $toXml(). The date-select helpers also coerce
a null bound value to "" before rendering, so no null reaches the select
renderers.

Regression specs cover renderWith() JSON and XML with null struct values (JSON
already passed on this head; XML failed before this change) and dateSelect()
with a null bound property. Add a changelog fragment.

Signed-off-by: Peter Amiri <peter@alurium.com>

* test(controller): assert the null XML element without formatting

BoxLang indents the generated XML differently, so match type="null" with a
format-agnostic FindNoCase instead of a contiguous element string.

Signed-off-by: Peter Amiri <peter@alurium.com>

* fix(controller): return a null XML document for a root-level null

$toXml() is declared xml, so returning "" for a null root value throws
"Cannot cast String [] to a value of type [xml]" on null-capable engines. Return
a well-formed null document instead (mirroring the nested toXML element) and
keep the changelog wording accurate.

Cover it with a root-null spec that skips where a null cannot be passed to a
required argument (Lucee null support off, RustCFML), plus a source guard that
always checks the declared document shape. Strengthen the formsdate spec so a
null bound property must render exactly like an empty value and produce the
full 12-option month select.

Signed-off-by: Peter Amiri <peter@alurium.com>

* test(global): probe null support instead of catching $toXml failures

The root-null spec wrapped $toXml in try/catch and skipped on any exception,
so a regression on a null-capable engine (the xml-cast error) would be skipped
rather than fail. Probe null-argument support with an unrelated helper, skip
only when the engine cannot pass null, then call $toXml outside any try/catch
and assert toBeXML() + the null element. Keep the static source guard as the
cross-engine pin.

Signed-off-by: Peter Amiri <peter@alurium.com>

---------

Signed-off-by: Peter Amiri <peter@alurium.com>
@bpamiri bpamiri mentioned this pull request Oct 30, 2026
6 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants