Skip to content

Commit 382df95

Browse files
dmealingclaude
andauthored
fix(render,verify): resolve nested @objectref by FQN across ports (ADR-0041) (#182)
* fix(render,verify): FQN-exact nested @objectref resolution across ports (ADR-0041) The verify --templates prompt-drift gate and render-helper payload field-tree derivation resolved a nested field.object @objectref by BARE short-name, so a fully-qualified ref (pkg::Name) bound the WRONG same-named object.value on a cross-package short-name collision — emptying the element subtree and raising a spurious ERR_VAR_NOT_ON_PAYLOAD on its inner {{fields}}. This ports the Java fix into the three remaining buggy ports and gates it cross-port: - Python: _resolve_nested_object_ref resolves FQN-exact when the ref contains "::". - C#: RenderHelperGenerator.ResolveNestedObjectRef is FQN-exact; the verify field-tree gets a dedicated ResolveObjectRef, kept SEPARATE from the bare FindObject used by record emission so record identifiers stay bare. - TS: the CLI verify (payload-field-tree) and docs-annotator (template-payload-tree) resolvers route through the shared refMatchesObject SSOT; the render-helper resolver was already FQN-safe. Shared gate: a new xpkg-collision/ sub-corpus under fixtures/template-output-render-conformance/ (two packages each declaring an object.value Note; a Digest payload referencing both by FQN @objectref) drives the render-helper conformance runners in TS/Python/C#/Kotlin and fails on revert. Java is already covered by TemplateVerifyTest's ADR-0041 collision case. Auto-derived has-accessor recognition in verify stays a per-port concern by design — no other port emits has<Field> accessors and no shared template uses them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013VXidfNN755CyTZTg1w8UV * no-mistakes(review): key render-helper cycle guard by FQN not bare name * no-mistakes(document): sync ADR-0041 consequences with non-JVM render/verify FQN fix --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 9b8955a commit 382df95

18 files changed

Lines changed: 474 additions & 45 deletions

File tree

fixtures/template-output-render-conformance/README.md

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ resolves to `templates/emails/welcome.subject.mustache`, and so on.
2626
- `nested/meta.json` — a **no-package** sub-corpus carrying the nested/array
2727
email case (`Order` over `Customer` + `Item[]`); see "Nested + array email"
2828
below. It shares the same `templates/` dir.
29+
- `xpkg-collision/` — a **multi-package** sub-corpus (`meta.alpha.json` +
30+
`meta.beta.json` + `meta.app.json`) gating **FQN-exact** nested `@objectRef`
31+
resolution across a cross-package short-name collision (ADR-0041); see
32+
"Cross-package short-name collision" below. It also shares `templates/`.
2933
- `templates/emails/order.subject.mustache` = `Order for {{customer.name}}`
3034
- `templates/emails/order.html.mustache` =
3135
`<h1>{{customer.name}}</h1><ul>{{#items}}<li>{{sku}} x{{qty}}</li>{{/items}}</ul>{{> shared/footer}}`
@@ -74,8 +78,10 @@ the XML entity set (`<`→`&lt;`, `>`→`&gt;`, `&`→`&amp;`, `"`→`&quot;`, `
7478
@objectRef, isArray) }`, plus an `email` template `OrderEmail`
7579
(`@payloadRef=Order`) over the `order.*` part-refs. It has **no package** on
7680
purpose: a bare `@objectRef` resolves identically across ports only when there is
77-
no package (TS resolves an objectRef by short name; the JVM expands a *packaged*
78-
ref to an FQN, so bare == FQN only at the root package). The shared
81+
no package: a bare `@objectRef` matches the value-object's short name in every
82+
port, so this sub-corpus isolates the nested/array/partial shape from any
83+
package-resolution concern. (The *packaged*/fully-qualified `@objectRef` case is
84+
gated separately by `xpkg-collision/` below, per ADR-0041.) The shared
7985
`templates/emails/order.*` + `templates/shared/footer` mustaches are reused.
8086

8187
Rendered with `{ customer: { name: "Ada" }, items: [ { sku: "A1", qty: 2 },
@@ -95,6 +101,38 @@ section-context drift (a `{{bogus}}` not on the `Item` element type the section
95101
pushes) FAILS codegen with `ERR_VAR_NOT_ON_PAYLOAD`, proving the gate walks the
96102
nested/section context, not just the root.
97103

104+
## Cross-package short-name collision — `xpkg-collision/`, `DigestDoc`
105+
106+
A **multi-package** sub-corpus (loaded as three sources — `meta.alpha.json` +
107+
`meta.beta.json` + `meta.app.json`, mirroring
108+
`fixtures/conformance/loader-same-name-distinct-packages`) that gates **FQN-exact**
109+
nested `@objectRef` resolution (ADR-0041):
110+
111+
- `acme::alpha` declares `object.value Note { alphaText: string }`.
112+
- `acme::beta` declares `object.value Note { betaText: string }` — a **colliding
113+
short name** in a different package.
114+
- `acme::app` declares payload `object.value Digest` with two `field.object`
115+
children referencing the two Notes by **fully-qualified** `@objectRef`
116+
(`acme::alpha::Note`, `acme::beta::Note`), and a `document` `template.output`
117+
`DigestDoc` (`@format=html`, `@textRef="xpkg/digest"`, `@payloadRef="Digest"`).
118+
- `templates/xpkg/digest.mustache` = `Alpha={{fromAlpha.alphaText}} Beta={{fromBeta.betaText}}`
119+
120+
Each port must resolve a fully-qualified `@objectRef` **exactly** on the
121+
package-qualified name — never a bare-tail fallback that binds whichever `Note`
122+
loads first. A bare-tail resolver collapses BOTH refs to one package's `Note`, so
123+
one of `{{fromAlpha.alphaText}}`/`{{fromBeta.betaText}}` lands on the wrong element
124+
type and the build-time drift gate throws `ERR_VAR_NOT_ON_PAYLOAD`. FQN-exact
125+
resolution binds each ref to its own package, so the clean template passes.
126+
127+
Rendered with `{ fromAlpha: { alphaText: "AA" }, fromBeta: { betaText: "BB" } }`:
128+
129+
- `renderDigestDoc(...)` = `"Alpha=AA Beta=BB"`
130+
131+
(The two colliding VOs share the BARE payload-record type name `Note`; each port's
132+
runner hand-authors — or otherwise reconciles — the payload record for that reason.
133+
The record-name collision is an orthogonal concern; this sub-corpus gates the
134+
`@objectRef` **resolver**, not payload-record naming.)
135+
98136
## Expected build-time drift FAILURE — `drift/`
99137

100138
`drift/meta.json` declares the same `Welcome` VO and a `document`
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Alpha={{fromAlpha.alphaText}} Beta={{fromBeta.betaText}}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"metadata.root": {
3+
"package": "acme::alpha",
4+
"children": [
5+
{
6+
"object.value": {
7+
"name": "Note",
8+
"children": [
9+
{ "field.string": { "name": "alphaText", "@required": true } }
10+
]
11+
}
12+
}
13+
]
14+
}
15+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"metadata.root": {
3+
"package": "acme::app",
4+
"children": [
5+
{
6+
"object.value": {
7+
"name": "Digest",
8+
"children": [
9+
{ "field.object": { "name": "fromAlpha", "@objectRef": "acme::alpha::Note" } },
10+
{ "field.object": { "name": "fromBeta", "@objectRef": "acme::beta::Note" } }
11+
]
12+
}
13+
},
14+
{
15+
"template.output": {
16+
"name": "DigestDoc",
17+
"@kind": "document",
18+
"@payloadRef": "Digest",
19+
"@textRef": "xpkg/digest",
20+
"@format": "html"
21+
}
22+
}
23+
]
24+
}
25+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"metadata.root": {
3+
"package": "acme::beta",
4+
"children": [
5+
{
6+
"object.value": {
7+
"name": "Note",
8+
"children": [
9+
{ "field.string": { "name": "betaText", "@required": true } }
10+
]
11+
}
12+
}
13+
]
14+
}
15+
}

server/csharp/MetaObjects.Codegen.Tests/PayloadCodegenTests.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,45 @@ public void Fully_qualified_objectRef_strips_to_bare_record_type_and_resolves_ne
8787
Assert.DoesNotContain("acme::ai::", src);
8888
}
8989

90+
// ADR-0041: cross-package short-name collision. Two object.value `Note`s
91+
// (acme::alpha with alphaText, acme::beta with betaText) and a payload `Digest`
92+
// referencing BOTH by FULLY-QUALIFIED @objectRef. The BuildPayloadFieldTree walk (the
93+
// `dotnet meta verify` field-tree) must bind each ref to its OWN package — not
94+
// bare-tail-collapse both to whichever Note loads first (pre-fix: both got alphaText).
95+
// This is the CLI-verify half of the fix; the render-helper half is gated by
96+
// RenderHelperConformanceTests.Document_DigestDoc_resolves_fqn_nested_objectRef_across_collision.
97+
[Fact]
98+
public void BuildPayloadFieldTree_resolves_fqn_nested_objectRef_across_package_collision()
99+
{
100+
const string alpha = """
101+
{ "metadata.root": { "package": "acme::alpha", "children": [
102+
{ "object.value": { "name": "Note", "children": [ { "field.string": { "name": "alphaText" } } ] } } ] } }
103+
""";
104+
const string beta = """
105+
{ "metadata.root": { "package": "acme::beta", "children": [
106+
{ "object.value": { "name": "Note", "children": [ { "field.string": { "name": "betaText" } } ] } } ] } }
107+
""";
108+
const string app = """
109+
{ "metadata.root": { "package": "acme::app", "children": [
110+
{ "object.value": { "name": "Digest", "children": [
111+
{ "field.object": { "name": "fromAlpha", "@objectRef": "acme::alpha::Note" } },
112+
{ "field.object": { "name": "fromBeta", "@objectRef": "acme::beta::Note" } } ] } } ] } }
113+
""";
114+
var root = new MetaDataLoader().Load([
115+
new InMemoryStringSource(alpha, id: "a.json"),
116+
new InMemoryStringSource(beta, id: "b.json"),
117+
new InMemoryStringSource(app, id: "c.json"),
118+
]).Root;
119+
120+
var tree = PayloadCodegen.BuildPayloadFieldTree(root, "Digest");
121+
122+
var fromAlpha = Assert.Single(tree, f => f.Name == "fromAlpha");
123+
var fromBeta = Assert.Single(tree, f => f.Name == "fromBeta");
124+
// FQN-exact: each ref binds to its OWN package's Note.
125+
Assert.Equal("alphaText", Assert.Single(fromAlpha.Fields!).Name);
126+
Assert.Equal("betaText", Assert.Single(fromBeta.Fields!).Name);
127+
}
128+
90129
[Fact]
91130
public void Emits_render_handle_binding_textRef_and_format()
92131
{

server/csharp/MetaObjects.Codegen.Tests/RenderHelperConformanceTests.cs

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ private static MetaRoot LoadFromFile(string metaJsonPath)
5050
return r.Root;
5151
}
5252

53+
// Multi-file (multi-package) load — one InMemoryStringSource per file, merged into a
54+
// single root (mirrors fixtures/conformance/loader-same-name-distinct-packages).
55+
private static MetaRoot LoadFromFiles(params string[] metaJsonPaths)
56+
{
57+
var sources = metaJsonPaths
58+
.Select((p, i) => (IMetaDataSource)new InMemoryStringSource(File.ReadAllText(p), id: $"corpus{i}.json"))
59+
.ToArray();
60+
var r = new MetaDataLoader().Load(sources);
61+
Assert.Empty(r.Errors);
62+
return r.Root;
63+
}
64+
5365
private static GenContext Ctx(MetaRoot root) => new()
5466
{
5567
Entities = root.Objects(),
@@ -192,6 +204,53 @@ public void Email_OrderEmail_renders_nested_array_loop_and_partial()
192204
Assert.Contains("<hr/>Sent by Acme", email.HtmlBody);
193205
}
194206

207+
// ---------------------------------------------------------------------
208+
// xpkg-collision/ — cross-package short-name collision (ADR-0041). Two packages
209+
// each declare an object.value `Note` (alpha: alphaText, beta: betaText); the
210+
// payload `Digest` references BOTH by FULLY-QUALIFIED @objectRef. A bare-tail
211+
// resolver binds both refs to whichever Note loads first → one of
212+
// {{fromAlpha.alphaText}}/{{fromBeta.betaText}} lands on the wrong element type and
213+
// the build-time drift gate throws ERR_VAR_NOT_ON_PAYLOAD. FQN-exact resolution binds
214+
// each ref to its own package. The two colliding VOs share the BARE record type name
215+
// `Note`, so (like the TS payloads.ts) the payload record is hand-authored with both
216+
// fields — the record-name collision is an orthogonal concern; this test gates the
217+
// render-helper's FQN-exact @objectRef resolver.
218+
// ---------------------------------------------------------------------
219+
220+
[Fact]
221+
public void Document_DigestDoc_resolves_fqn_nested_objectRef_across_collision()
222+
{
223+
var corpus = Corpus();
224+
var dir = Path.Combine(corpus, "xpkg-collision");
225+
var root = LoadFromFiles(
226+
Path.Combine(dir, "meta.alpha.json"),
227+
Path.Combine(dir, "meta.beta.json"),
228+
Path.Combine(dir, "meta.app.json"));
229+
var templates = Path.Combine(corpus, "templates");
230+
231+
// Must NOT throw: the FQN refs resolve to their own package's Note.
232+
var file = Assert.Single(new RenderHelperGenerator(templates).Generate(Ctx(root)), f => f.Path == "DigestDoc.render.cs");
233+
234+
var payloadSrc = "namespace Acme.Generated;\n"
235+
+ "public sealed record Note { public string? alphaText { get; init; } public string? betaText { get; init; } }\n"
236+
+ "public sealed record Digest { public Note fromAlpha { get; init; } public Note fromBeta { get; init; } }\n";
237+
var asm = CompileToAssembly(file.Content, payloadSrc);
238+
239+
var noteType = asm.GetType("Acme.Generated.Note")!;
240+
var digestType = asm.GetType("Acme.Generated.Digest")!;
241+
var fromAlpha = Activator.CreateInstance(noteType)!;
242+
noteType.GetProperty("alphaText")!.SetValue(fromAlpha, "AA");
243+
var fromBeta = Activator.CreateInstance(noteType)!;
244+
noteType.GetProperty("betaText")!.SetValue(fromBeta, "BB");
245+
var digest = Activator.CreateInstance(digestType)!;
246+
digestType.GetProperty("fromAlpha")!.SetValue(digest, fromAlpha);
247+
digestType.GetProperty("fromBeta")!.SetValue(digest, fromBeta);
248+
249+
var helper = asm.GetType("Acme.Generated.DigestDocRenderHelper")!;
250+
var outText = (string)helper.GetMethod("Render")!.Invoke(null, [digest, new FilesystemProvider(templates)])!;
251+
Assert.Equal("Alpha=AA Beta=BB", outText);
252+
}
253+
195254
// ---------------------------------------------------------------------
196255
// drift/ → the generator THROWS ERR_VAR_NOT_ON_PAYLOAD (fails codegen).
197256
// ---------------------------------------------------------------------

server/csharp/MetaObjects.Codegen/Generators/RenderHelperGenerator.cs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
// Reuse, not reimplementation: Renderer.Render (the emitted runtime call), Verify
2323
// (the build-time gate), FilesystemProvider (build-time ref resolution), and
2424
// EmailDocument (email return type). The payload field tree is walked from the VO
25-
// the same way the other generators walk it — but a nested field.object's
26-
// @objectRef is resolved by BARE short-name (cross-port render-helper consensus:
27-
// TS findObject / Java resolveNestedObjectRef), only recursing into object.value
28-
// (SUBTYPE_VALUE) targets, cycle-guarded.
25+
// the same way the other generators walk it — a nested field.object's @objectRef is
26+
// resolved FQN-exact when fully-qualified (ADR-0041) else by short name (cross-port
27+
// render-helper consensus: TS refMatchesObject / Java+Kotlin resolveNestedObjectRef),
28+
// only recursing into object.value (SUBTYPE_VALUE) targets, cycle-guarded.
2929
//
3030
// The emitted <fieldTree> literal is baked into the RenderRequest.Verify argument
3131
// so Renderer's runtime drift check matches the build-time gate that ran here.
@@ -231,16 +231,17 @@ private void GateRef(string templateName, string reference, IReadOnlyList<Payloa
231231
}
232232

233233
// -------------------------------------------------------------------------
234-
// Payload field-tree walk — nested @objectRef resolved by BARE short-name
235-
// (cross-port render-helper consensus: TS findObject / Java
236-
// resolveNestedObjectRef). Object-ref fields recurse into their target
237-
// object.value (SUBTYPE_VALUE only); a `seen` set guards reference cycles.
234+
// Payload field-tree walk — nested @objectRef resolved FQN-exact when
235+
// fully-qualified (ADR-0041) else by short name (cross-port render-helper
236+
// consensus: TS refMatchesObject / Java+Kotlin resolveNestedObjectRef). Object-ref
237+
// fields recurse into their target object.value (SUBTYPE_VALUE only); a `seen` set
238+
// guards reference cycles.
238239
// -------------------------------------------------------------------------
239240

240241
private static IReadOnlyList<PayloadField> DerivePayloadFieldTree(
241242
MetaRoot root, MetaData vo, HashSet<string> seen)
242243
{
243-
if (vo is null || !seen.Add(vo.Name)) return [];
244+
if (vo is null || !seen.Add(vo.ResolutionKey())) return [];
244245
var fields = new List<PayloadField>();
245246
foreach (var f in vo.Children().Where(c => c.Type == TYPE_FIELD))
246247
{
@@ -263,20 +264,24 @@ private static IReadOnlyList<PayloadField> DerivePayloadFieldTree(
263264

264265
/// <summary>
265266
/// Resolve a <c>field.object</c>'s <c>@objectRef</c> to its target
266-
/// <c>object.value</c> by BARE short-name — mirroring the TS render-helper's
267-
/// <c>findObject(root, ref)</c> (<c>c.type === OBJECT &amp;&amp; c.name === ref</c>)
268-
/// and the Java <c>resolveNestedObjectRef</c>. If the ref carries a package, only
269-
/// the segment after the last <c>::</c> is compared, against each
270-
/// <c>object.value</c>'s own short name. Returns null when no match is found.
267+
/// <c>object.value</c>. ADR-0041: a FULLY-QUALIFIED ref (contains <c>::</c>) resolves
268+
/// EXACTLY on the package-qualified name (<c>ResolutionKey()</c>/<c>Fqn()</c>) — never
269+
/// a bare-tail fallback that would bind a same-named <c>object.value</c> in the WRONG
270+
/// package on a cross-package short-name collision. A bare ref still matches by short
271+
/// name (first-wins). Mirrors the Java/Kotlin <c>resolveNestedObjectRef</c> + TS
272+
/// <c>refMatchesObject</c>. Returns null when no match is found.
271273
/// </summary>
272274
private static MetaData? ResolveNestedObjectRef(MetaRoot root, string reference)
273275
{
274276
if (string.IsNullOrEmpty(reference)) return null;
277+
bool fqn = reference.Contains("::");
275278
var refShort = CSharpNaming.StripPkg(reference);
276279
// ADR-0039: Children() — resolving root scan (behavior-identical; root has no super).
277280
return root.Children().FirstOrDefault(c =>
278281
c.Type == TYPE_OBJECT && c.SubType == OBJECT_SUBTYPE_VALUE &&
279-
CSharpNaming.StripPkg(c.Name) == refShort);
282+
(fqn
283+
? c.ResolutionKey() == reference || c.Fqn() == reference
284+
: CSharpNaming.StripPkg(c.Name) == refShort));
280285
}
281286

282287
/// <summary>

server/csharp/MetaObjects.Codegen/PayloadCodegen.cs

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,23 @@ public static class PayloadCodegen
4343

4444
private static MetaData? FindObject(MetaData root, string name) =>
4545
// ADR-0039: Children() — resolving root scan (behavior-identical; root has no super).
46+
// Record emission resolves the payload VO by BARE short name (a C# record identifier
47+
// is always bare, and EmitRecord derives the record name from this same voName).
4648
root.Children().FirstOrDefault(c => c.Type == TYPE_OBJECT && c.Name == name);
4749

50+
// ADR-0041: the verify field-tree resolver — a FULLY-QUALIFIED ref (contains ::) resolves
51+
// EXACTLY on the package-qualified name (ResolutionKey()/Fqn()), never a bare-tail fallback
52+
// that would bind a same-named object.value in the WRONG package on a cross-package
53+
// short-name collision. A bare ref matches by short name (first-wins). Kept SEPARATE from
54+
// FindObject so record emission (bare identifiers) is unaffected. Mirrors the render-helper
55+
// ResolveNestedObjectRef + Java/Kotlin + TS refMatchesObject.
56+
private static MetaData? ResolveObjectRef(MetaData root, string reference)
57+
{
58+
bool fqn = reference.Contains("::");
59+
return root.Children().FirstOrDefault(c => c.Type == TYPE_OBJECT &&
60+
(fqn ? c.ResolutionKey() == reference || c.Fqn() == reference : c.Name == reference));
61+
}
62+
4863
// ADR-0039: resolve array-ness through the super chain (isArray is a native
4964
// property, not an attr; the former OwnAttr("isArray") clause was dead code).
5065
private static bool IsArrayField(MetaData field) => field.ResolvedIsArray();
@@ -55,9 +70,10 @@ private static (string Type, string? RefVo) FieldType(MetaData owner, MetaData f
5570
{
5671
// ADR-0039: resolving — @objectRef may be inherited via extends.
5772
var refAttr = field.Attr(FIELD_ATTR_OBJECT_REF);
58-
// @objectRef may be authored fully-qualified (acme::sales::Brief) or bare;
59-
// the generated record type + the nested-record lookup key are the BARE
60-
// short name (FindObject matches bare names — every other caller strips too).
73+
// @objectRef may be authored fully-qualified (acme::sales::Brief) or bare; the
74+
// generated record TYPE name is the BARE short name (StripPkg). (The verify
75+
// field-tree path — BuildTree — resolves the FULL ref FQN-exact via ResolveObjectRef
76+
// per ADR-0041; record emission here stays bare: one C# type per short name.)
6177
string refName = refAttr is string s ? CSharpNaming.StripPkg(s) : "object";
6278
string? refVo = refAttr is string r ? CSharpNaming.StripPkg(r) : null;
6379
return (IsArrayField(field) ? $"IReadOnlyList<{refName}>" : refName, refVo);
@@ -155,14 +171,17 @@ public static IReadOnlyList<PayloadField> BuildPayloadFieldTree(MetaData root, s
155171

156172
private static IReadOnlyList<PayloadField> BuildTree(MetaData root, string voName, HashSet<string> visiting)
157173
{
158-
var vo = FindObject(root, voName);
174+
// ADR-0041: FQN-exact resolution for the verify field-tree (NOT the bare FindObject
175+
// used by record emission) so a fully-qualified nested @objectRef binds its own package.
176+
var vo = ResolveObjectRef(root, voName);
159177
if (vo is null || !visiting.Add(voName)) return [];
160178
var fields = new List<PayloadField>();
161179
foreach (var f in vo.Children().Where(c => c.Type == TYPE_FIELD))
162180
{
163181
// ADR-0039: resolving — @objectRef may be inherited via extends (TS reads f.attr).
182+
// ADR-0041: pass the FULL (possibly FQN) ref — ResolveObjectRef resolves it exactly.
164183
if (f.SubType == FIELD_SUBTYPE_OBJECT && f.Attr(FIELD_ATTR_OBJECT_REF) is string refName)
165-
fields.Add(new PayloadField(f.Name, BuildTree(root, CSharpNaming.StripPkg(refName), visiting)));
184+
fields.Add(new PayloadField(f.Name, BuildTree(root, refName, visiting)));
166185
else
167186
fields.Add(new PayloadField(f.Name));
168187
}

0 commit comments

Comments
 (0)