Skip to content

fix: enforce no-throw contract in Reflector.Try* APIs (PR #71 follow-ups)#76

Merged
IvanMurzak merged 1 commit into
mainfrom
fix/pr71-copilot-followups
Apr 29, 2026
Merged

fix: enforce no-throw contract in Reflector.Try* APIs (PR #71 follow-ups)#76
IvanMurzak merged 1 commit into
mainfrom
fix/pr71-copilot-followups

Conversation

@IvanMurzak
Copy link
Copy Markdown
Owner

Summary

Address 7 unresolved review threads from copilot-pull-request-reviewer on the merged PR #71. The Try* methods (TryPatch, TryModifyAt, TryReadAt, View) all document "errors accumulated in the optional Logs object; nothing is thrown", but several reflection call sites could let exceptions escape on write-only properties, throwing getters/setters, missing converters, or null patches applied to non-nullable value types.

This PR plugs all 7 leaks. No public API surface change; behaviour change is strictly: paths that previously threw now log and return false / null.

Changes per file

Reflector.Patch.cs

  • TryPatchInternal — guard the JSON-null patch path against non-nullable value-type targets (Nullable<T> still permits null). Previously the null would propagate to a SetValue that throws ArgumentException. (Thread #2930803248)
  • TryPatchInternal — wrap CreateInstance in try/catch. Reflector.DefaultValue documents that CreateInstance throws ArgumentException when no converter supports the type. (Thread #2930803081)
  • TryPatchMember — add CanRead check on properties; wrap field/property GetValue and SetValue in try/catch with logged failures. (Thread #2930803137)

Reflector.Navigate.cs

  • TryLookupMember — add CanRead check on properties; wrap field/property GetValue and SetValue in try/catch. (Thread #2930803150)
  • TryLookupMember — switch the "Available fields/properties" error list from raw GetFields/GetProperties to GetSerializableFields/Properties so the message matches what Serialize/TryModify actually operate on (matches the existing convention in TryNavigateOneSegment and BaseReflectionConverter.GetCachedSerializableMemberNames). Drops static on TryLookupMember to access the instance helpers — its only caller (TryModifyAtMember) is already an instance method, so the change is signature-compatible. (Thread #2930803173)

Reflector.ReadAt.cs

  • TryNavigateOneSegment — add CanRead check on properties; wrap field/property GetValue in try/catch with logged failures. (Thread #2930803191)

Reflector.View.cs

  • View — wrap the Serialize call in try/catch(ArgumentException). Serialize is documented to throw when both obj and fallbackObjType are null, or when no converter exists for the resolved type. Mirrors the existing TryCompilePattern try/catch convention in the same file. (Thread #2930803221)

Test plan

  • dotnet build --configuration Release — 0 warnings, 0 errors across netstandard2.1, net8.0, net9.0.
  • dotnet test --configuration Release — 1423/1423 pass on net8.0 and net9.0. No test changes — these fixes plug exception leaks that the existing suite did not exercise.
  • Optional follow-up: add targeted xUnit tests for each guarded path (write-only property, throwing getter/setter, null patch on int, missing converter on CreateInstance). Out of scope here; can land in a separate PR.

Notes

  • All Try* methods retain their bool / nullable return signature — no API surface change.
  • Each new catch logs to the supplied Logs? and to the ILogger? (when LogLevel.Error is enabled), matching the existing pattern in this file.
  • Closes the 7 unresolved threads on PR Add atomic modification API and JSON patch support with enhancements #71 (will reply on each thread with a link to this PR and resolve).

…ups)

Address 7 unresolved review threads from copilot-pull-request-reviewer
on PR #71. The Try* methods (TryPatch, TryModifyAt, TryReadAt, View) all
document "errors accumulated in the optional Logs object; nothing is
thrown", but several reflection call sites could let exceptions escape
on write-only properties, throwing getters/setters, missing converters,
or null patches against non-nullable value types.

Reflector.Patch.cs:
- TryPatchInternal: guard the JSON-null patch path against non-nullable
  value-type targets (Nullable<T> still permits null) — previously the
  null would propagate to a SetValue that throws ArgumentException.
- TryPatchInternal: wrap CreateInstance in try/catch — Reflector.DefaultValue
  documents that it throws ArgumentException when no converter supports the
  type.
- TryPatchMember: add CanRead check on properties; wrap field/property
  GetValue and SetValue in try/catch with logged failures.

Reflector.Navigate.cs:
- TryLookupMember: add CanRead check on properties; wrap field/property
  GetValue and SetValue in try/catch.
- TryLookupMember: switch the "Available fields/properties" error list
  from raw GetFields/GetProperties to GetSerializableFields/Properties so
  the message matches what Serialize/TryModify actually operate on
  (matches the existing convention in TryNavigateOneSegment and
  BaseReflectionConverter). Drop `static` on TryLookupMember to access
  the instance helpers — its only caller (TryModifyAtMember) is already
  an instance method.

Reflector.ReadAt.cs:
- TryNavigateOneSegment: add CanRead check on properties; wrap field/
  property GetValue in try/catch with logged failures.

Reflector.View.cs:
- View: wrap the Serialize call in try/catch(ArgumentException) — Serialize
  is documented to throw when both obj and fallbackObjType are null or no
  converter exists for the resolved type. Mirrors the existing
  TryCompilePattern try/catch convention in the same file.

Tests: 1423/1423 pass on net8.0 and net9.0 (no test changes — these fixes
plug exception leaks that the existing suite did not exercise).
@github-actions
Copy link
Copy Markdown
Contributor

Test Results

    2 files  ±0      2 suites  ±0   6m 35s ⏱️ -7s
1 423 tests ±0  1 423 ✅ ±0  0 💤 ±0  0 ❌ ±0 
2 846 runs  ±0  2 846 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 18080cd. ± Comparison against base commit 914c38f.

@IvanMurzak IvanMurzak self-assigned this Apr 29, 2026
@IvanMurzak IvanMurzak added the enhancement New feature or request label Apr 29, 2026
@IvanMurzak IvanMurzak merged commit 5a67f86 into main Apr 29, 2026
2 checks passed
@IvanMurzak IvanMurzak deleted the fix/pr71-copilot-followups branch April 29, 2026 18:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant