Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set - #126691

Merged
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer
Apr 24, 2026
Merged

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set#126691
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer

Conversation

CopilotAI commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Description

LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer fail to report diagnostics (e.g. SYSLIB1051) for [LibraryImport] methods when StringMarshalling is set. This is a regression introduced in preview.3 when diagnostic reporting was moved from the generators to separate analyzer classes. Additionally, the generated inner [DllImport] stub omits CharSet = CharSet.Unicode when StringMarshalling.Utf16 is set, causing incorrect runtime marshalling of forwarded types (e.g. StringBuilder).

Root cause: The source generator marks its generated stub output with [GeneratedCode]. Roslyn's generated-code heuristics then also classify the user's partial method declaration as generated code. With ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None), both analyzers silently skipped all [LibraryImport] methods. Additionally, with Analyze | ReportDiagnostics enabled, RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances, which could produce duplicate diagnostics — one in user source and one in generated source. The generated implementation syntax (which has a body) also caused a spurious SYSLIB1050 (InvalidAttributedMethodSignature) because GetDiagnosticIfInvalidMethodForGeneration rejects methods with a body.

Reproduction:

// SYSLIB1051 should be reported but is NOT (with StringMarshalling.Utf16)[LibraryImport("kernel32.dll",StringMarshalling=StringMarshalling.Utf16)]internalstaticpartialintGetVolumeNameForVolumeMountPointW(stringvolumeMountPoint,[Out]StringBuildervolumeName,// No error — silent bad codegenintbufferLength);

Changes

  • LibraryImportDiagnosticsAnalyzer: Changed GeneratedCodeAnalysisFlags.NoneAnalyze | ReportDiagnostics so the analyzer runs on and reports diagnostics for methods whose partial declaration is classified as generated code. Added PartialDefinitionPart guard to skip the partial implementation part (avoiding duplicate diagnostics from RegisterSymbolAction firing for both parts). Added IsGeneratedByOurGenerator helper. When iterating DeclaringSyntaxReferences, the generated implementation syntax (body present + our [GeneratedCode]) is skipped to find the user's partial declaration. GetDiagnosticIfInvalidMethodForGeneration is skipped (via skipInvalidMethodCheck flag) when our generator has already produced an implementation — since the method must have been valid for the generator to run — but CalculateDiagnostics always runs to catch other issues. Diagnostics are filtered by SyntaxTree to only report those located in the user's (non-generated) source.
  • DownlevelLibraryImportDiagnosticsAnalyzer: Applied the same GeneratedCodeAnalysisFlags.Analyze | ReportDiagnostics fix, the same PartialDefinitionPart guard, the same selective SYSLIB1050 guard, and the same SyntaxTree diagnostic location filter.
  • LibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Now forwards CharSet = CharSet.Unicode to the inner [DllImport] when StringMarshalling.Utf16 is set, using the shared CreateEnumExpressionSyntax helper (extracted to class-level) to ensure consistency with the forwarder stub.
  • DownlevelLibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Applied the same CharSet = CharSet.Unicode forwarding fix using the shared CreateEnumExpressionSyntax helper (also extracted to class-level in the downlevel generator).
  • DownlevelLibraryImportGenerator.CreateForwarderDllImport: Updated to use the shared class-level CreateEnumExpressionSyntax helper.
  • Tests (Diagnostics.cs): Added StringBuilderNotSupported_ReportsDiagnostic and StringBuilderNotSupported_WithStringParam_ReportsDiagnostic regression tests covering StringBuilder with and without StringMarshalling variants.
  • Tests (Compiles.cs): Added ForwardedTypesWithStringMarshalling_InnerDllImportHasCharSet test verifying that the inner [DllImport] has CharSet.Unicode when StringMarshalling.Utf16 is set and no CharSet argument when other values are used. Fixed test assertion to use EndsWith to handle the global:: qualified name emitted by the generator. Fixed the DllImport attribute predicate to use EndsWith("DllImportAttribute") instead of Contains("DllImport") to avoid false matches on DefaultDllImportSearchPathsAttribute.

Testing

  • Both generator projects (LibraryImportGenerator, DownlevelLibraryImportGenerator) build successfully with zero errors and warnings.
  • All 709 unit tests pass (1 skipped, 0 failed), including previously-failing AddDisableRuntimeMarshallingAttributeFixerTests which validated the duplicate diagnostic fix.
  • Regression tests added for the diagnostic and code-generation fixes.
  • Code review and CodeQL scan passed with no issues.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

CopilotAI changed the title [WIP] Fix LibraryImportDiagnosticsAnalyzer to report SYSLIB1051 for StringBuilderFix LibraryImportDiagnosticsAnalyzer missing SYSLIB1051 for StringBuilder when StringMarshalling is setApr 9, 2026
CopilotAI requested a review from jkoritzinskyApril 9, 2026 07:03
@danmoseley

Copy link
Copy Markdown
Contributor

It seems Copilot could not make a fix before timing out.

Add tests verifying that SYSLIB1051 (ParameterTypeNotSupported) is
correctly reported for StringBuilder parameters when StringMarshalling
is set to Utf16 or Utf8, both as standalone parameters and alongside
string parameters with [Out] attribute.
Regression test for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 9, 2026 17:39
@jkoritzinsky

Copy link
Copy Markdown
Member

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets a regression in LibraryImportDiagnosticsAnalyzer where SYSLIB1051 is not reported for StringBuilder parameters when LibraryImportAttribute.StringMarshalling is specified, leading to silently incorrect forwarder stub generation.

Changes:

  • Added analyzer unit tests asserting SYSLIB1051 (ParameterTypeNotSupported) is reported for StringBuilder parameters with StringMarshalling set (Utf16/Utf8) and without it.
  • Added a reproduction-style test case for string + [Out] StringBuilder parameter combinations.

@github-actions

This comment has been minimized.

@danmoseley

Copy link
Copy Markdown
Contributor

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

@jkoritzinsky I updated the repro (top post #126687) to include a global.json -- can you still not repro with that?

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@jkoritzinsky also added the install command for preview 3. Using preview 2, it doesn't repro. I verified this by hand again, and it does repro for me with global.json pointing to 11.0.100-preview.3.26170.106

C:\temp\repro>type global.json | find /i "ver""version": "11.0.100-preview.3.26170.106",
C:\temp\repro>type program.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("If you see this, SYSLIB1051 was NOT reported.");
static partial class NativeMethods
{
[LibraryImport("kernel32.dll", StringMarshalling = StringMarshalling.Utf16)]
internal static partial int GetVolumeNameForVolumeMountPointW(
string volumeMountPoint,
[Out] StringBuilder volumeName,
int bufferLength);
}
C:\temp\repro>dotnet build
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (0.1s) → bin\Debug\net11.0\doit.dll
Build succeeded in 0.9s

and

C:\temp\repro>set emitcompilergeneratedfiles=true
C:\temp\repro>dotnet build --no-incremental
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (1.5s) → bin\Debug\net11.0\doit.dll
Build succeeded in 2.3s
C:\temp\repro>type obj\Debug\net11.0\generated\Microsoft.Interop.LibraryImportGenerator\Microsoft.Interop.LibraryImportGenerator\LibraryImports.g.cs
// <auto-generated/>
static unsafe partial class NativeMethods
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "11.0.14.17106")]
[global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
internal static partial int GetVolumeNameForVolumeMountPointW(string volumeMountPoint, global::System.Text.StringBuilder volumeName, int bufferLength)
{
int __retVal;
// Pin - Pin data in preparation for calling the P/Invoke.
fixed (void* __volumeMountPoint_native = &global::System.Runtime.InteropServices.Marshalling.Utf16StringMarshaller.GetPinnableReference(volumeMountPoint))
{
__retVal = __PInvoke((ushort*)__volumeMountPoint_native, volumeName, bufferLength);
}
return __retVal;
// Local P/Invoke
[global::System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", ExactSpelling = true)]
static extern unsafe int __PInvoke(ushort* __volumeMountPoint_native, [System.Runtime.InteropServices.OutAttribute] global::System.Text.StringBuilder volumeName, int __bufferLength_native);
}
}

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

The idea is that you get an IDE error, then you either fix the error or copy the generated code into your source and remove the LibraryImportAttribute. Then the error goes away as you aren't using the generator any more.

If I understand right then there are two bugs -- the error no longer firing, and the generated code not working (as I discovered in my original PR)

the issue with the generated code pasted above (I think) is that it doesn't have CharSet = CharSet.Unicode so I guess it defaulted to CharSet.None for volumeName which treats it as Ansi on Windows. (Aside, maybe generated code should always be explicit about CharSet anyway).

…16 is set
When LibraryImportGenerator creates a non-forwarder stub with an inner
local DllImport function, it did not forward StringMarshalling.Utf16 as
CharSet=Unicode. This caused any types forwarded to the runtime
marshaller (e.g. StringBuilder) to default to Ansi encoding, producing
incorrect results for Unicode APIs.
Add CharSet=CharSet.Unicode to the inner DllImport attribute when
StringMarshalling.Utf16 is specified on the LibraryImport, matching the
existing behavior in CreateForwarderDllImport.
Fix for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

…plementation from our generator
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/408e36a5-d92b-4944-8b37-13c9d31754fd
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 20, 2026 21:30
auto-merge was automatically disabled April 20, 2026 21:30

Head branch was pushed to by a user without write access

CopilotAI removed the request for review from CopilotApril 20, 2026 21:30

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

Done in commit b2bacee. Both LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer now have an IsGeneratedByOurGenerator helper that checks for [GeneratedCode] with a tool name starting with "Microsoft.Interop.LibraryImportGenerator". When iterating DeclaringSyntaxReferences, if the current syntax has a body AND the method has our generator's [GeneratedCode] attribute, we skip that syntax reference (the generated implementation) and continue to find the user's partial declaration to analyze.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

…enerated implementation; always run CalculateDiagnostics
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/301361db-1d3f-4105-871a-90438d6a01df
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 20, 2026 21:42

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

Done in commit d4687fc. Both analyzers now pass isGeneratedByOurGenerator as a skipInvalidMethodCheck parameter to AnalyzeMethodSyntax. When true, GetDiagnosticIfInvalidMethodForGeneration is skipped (since we know the method was valid — the generator already ran on it), but CalculateDiagnostics always runs regardless.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the location in the non-generated source.

…tial implementation part and filter by syntax tree
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/9fbadd4b-a632-4127-9b12-2bad0f80873f
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 21:12

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the loca...

Fixed in 351839b. The root cause was that with Analyze | ReportDiagnostics, Roslyn's RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances. Added a method.PartialDefinitionPart is not null guard at the top of AnalyzeMethod in both analyzers to skip the implementation part. Also added a SyntaxTree filter when reporting diagnostics to ensure only user-source locations are reported.

@jkoritzinsky
jkoritzinsky enabled auto-merge (squash) April 23, 2026 22:43
@jkoritzinsky
jkoritzinsky merged commit d754bd0 into mainApr 24, 2026
103 of 105 checks passed
@jkoritzinsky
jkoritzinsky deleted the copilot/fix-library-import-diagnostics-analyzer branch April 24, 2026 20:01
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

LibraryImportDiagnosticsAnalyzer fails to report SYSLIB1051 for StringBuilder when StringMarshalling is set, generating bad code

5 participants

@danmoseley@jkoritzinsky@jtschuster
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set - #126691

Merged
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer
Apr 24, 2026
Merged

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set#126691
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer

Conversation

CopilotAI commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Description

LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer fail to report diagnostics (e.g. SYSLIB1051) for [LibraryImport] methods when StringMarshalling is set. This is a regression introduced in preview.3 when diagnostic reporting was moved from the generators to separate analyzer classes. Additionally, the generated inner [DllImport] stub omits CharSet = CharSet.Unicode when StringMarshalling.Utf16 is set, causing incorrect runtime marshalling of forwarded types (e.g. StringBuilder).

Root cause: The source generator marks its generated stub output with [GeneratedCode]. Roslyn's generated-code heuristics then also classify the user's partial method declaration as generated code. With ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None), both analyzers silently skipped all [LibraryImport] methods. Additionally, with Analyze | ReportDiagnostics enabled, RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances, which could produce duplicate diagnostics — one in user source and one in generated source. The generated implementation syntax (which has a body) also caused a spurious SYSLIB1050 (InvalidAttributedMethodSignature) because GetDiagnosticIfInvalidMethodForGeneration rejects methods with a body.

Reproduction:

// SYSLIB1051 should be reported but is NOT (with StringMarshalling.Utf16)[LibraryImport("kernel32.dll",StringMarshalling=StringMarshalling.Utf16)]internalstaticpartialintGetVolumeNameForVolumeMountPointW(stringvolumeMountPoint,[Out]StringBuildervolumeName,// No error — silent bad codegenintbufferLength);

Changes

  • LibraryImportDiagnosticsAnalyzer: Changed GeneratedCodeAnalysisFlags.NoneAnalyze | ReportDiagnostics so the analyzer runs on and reports diagnostics for methods whose partial declaration is classified as generated code. Added PartialDefinitionPart guard to skip the partial implementation part (avoiding duplicate diagnostics from RegisterSymbolAction firing for both parts). Added IsGeneratedByOurGenerator helper. When iterating DeclaringSyntaxReferences, the generated implementation syntax (body present + our [GeneratedCode]) is skipped to find the user's partial declaration. GetDiagnosticIfInvalidMethodForGeneration is skipped (via skipInvalidMethodCheck flag) when our generator has already produced an implementation — since the method must have been valid for the generator to run — but CalculateDiagnostics always runs to catch other issues. Diagnostics are filtered by SyntaxTree to only report those located in the user's (non-generated) source.
  • DownlevelLibraryImportDiagnosticsAnalyzer: Applied the same GeneratedCodeAnalysisFlags.Analyze | ReportDiagnostics fix, the same PartialDefinitionPart guard, the same selective SYSLIB1050 guard, and the same SyntaxTree diagnostic location filter.
  • LibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Now forwards CharSet = CharSet.Unicode to the inner [DllImport] when StringMarshalling.Utf16 is set, using the shared CreateEnumExpressionSyntax helper (extracted to class-level) to ensure consistency with the forwarder stub.
  • DownlevelLibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Applied the same CharSet = CharSet.Unicode forwarding fix using the shared CreateEnumExpressionSyntax helper (also extracted to class-level in the downlevel generator).
  • DownlevelLibraryImportGenerator.CreateForwarderDllImport: Updated to use the shared class-level CreateEnumExpressionSyntax helper.
  • Tests (Diagnostics.cs): Added StringBuilderNotSupported_ReportsDiagnostic and StringBuilderNotSupported_WithStringParam_ReportsDiagnostic regression tests covering StringBuilder with and without StringMarshalling variants.
  • Tests (Compiles.cs): Added ForwardedTypesWithStringMarshalling_InnerDllImportHasCharSet test verifying that the inner [DllImport] has CharSet.Unicode when StringMarshalling.Utf16 is set and no CharSet argument when other values are used. Fixed test assertion to use EndsWith to handle the global:: qualified name emitted by the generator. Fixed the DllImport attribute predicate to use EndsWith("DllImportAttribute") instead of Contains("DllImport") to avoid false matches on DefaultDllImportSearchPathsAttribute.

Testing

  • Both generator projects (LibraryImportGenerator, DownlevelLibraryImportGenerator) build successfully with zero errors and warnings.
  • All 709 unit tests pass (1 skipped, 0 failed), including previously-failing AddDisableRuntimeMarshallingAttributeFixerTests which validated the duplicate diagnostic fix.
  • Regression tests added for the diagnostic and code-generation fixes.
  • Code review and CodeQL scan passed with no issues.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

CopilotAI changed the title [WIP] Fix LibraryImportDiagnosticsAnalyzer to report SYSLIB1051 for StringBuilderFix LibraryImportDiagnosticsAnalyzer missing SYSLIB1051 for StringBuilder when StringMarshalling is setApr 9, 2026
CopilotAI requested a review from jkoritzinskyApril 9, 2026 07:03
@danmoseley

Copy link
Copy Markdown
Contributor

It seems Copilot could not make a fix before timing out.

Add tests verifying that SYSLIB1051 (ParameterTypeNotSupported) is
correctly reported for StringBuilder parameters when StringMarshalling
is set to Utf16 or Utf8, both as standalone parameters and alongside
string parameters with [Out] attribute.
Regression test for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 9, 2026 17:39
@jkoritzinsky

Copy link
Copy Markdown
Member

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets a regression in LibraryImportDiagnosticsAnalyzer where SYSLIB1051 is not reported for StringBuilder parameters when LibraryImportAttribute.StringMarshalling is specified, leading to silently incorrect forwarder stub generation.

Changes:

  • Added analyzer unit tests asserting SYSLIB1051 (ParameterTypeNotSupported) is reported for StringBuilder parameters with StringMarshalling set (Utf16/Utf8) and without it.
  • Added a reproduction-style test case for string + [Out] StringBuilder parameter combinations.

@github-actions

This comment has been minimized.

@danmoseley

Copy link
Copy Markdown
Contributor

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

@jkoritzinsky I updated the repro (top post #126687) to include a global.json -- can you still not repro with that?

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@jkoritzinsky also added the install command for preview 3. Using preview 2, it doesn't repro. I verified this by hand again, and it does repro for me with global.json pointing to 11.0.100-preview.3.26170.106

C:\temp\repro>type global.json | find /i "ver""version": "11.0.100-preview.3.26170.106",
C:\temp\repro>type program.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("If you see this, SYSLIB1051 was NOT reported.");
static partial class NativeMethods
{
[LibraryImport("kernel32.dll", StringMarshalling = StringMarshalling.Utf16)]
internal static partial int GetVolumeNameForVolumeMountPointW(
string volumeMountPoint,
[Out] StringBuilder volumeName,
int bufferLength);
}
C:\temp\repro>dotnet build
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (0.1s) → bin\Debug\net11.0\doit.dll
Build succeeded in 0.9s

and

C:\temp\repro>set emitcompilergeneratedfiles=true
C:\temp\repro>dotnet build --no-incremental
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (1.5s) → bin\Debug\net11.0\doit.dll
Build succeeded in 2.3s
C:\temp\repro>type obj\Debug\net11.0\generated\Microsoft.Interop.LibraryImportGenerator\Microsoft.Interop.LibraryImportGenerator\LibraryImports.g.cs
// <auto-generated/>
static unsafe partial class NativeMethods
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "11.0.14.17106")]
[global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
internal static partial int GetVolumeNameForVolumeMountPointW(string volumeMountPoint, global::System.Text.StringBuilder volumeName, int bufferLength)
{
int __retVal;
// Pin - Pin data in preparation for calling the P/Invoke.
fixed (void* __volumeMountPoint_native = &global::System.Runtime.InteropServices.Marshalling.Utf16StringMarshaller.GetPinnableReference(volumeMountPoint))
{
__retVal = __PInvoke((ushort*)__volumeMountPoint_native, volumeName, bufferLength);
}
return __retVal;
// Local P/Invoke
[global::System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", ExactSpelling = true)]
static extern unsafe int __PInvoke(ushort* __volumeMountPoint_native, [System.Runtime.InteropServices.OutAttribute] global::System.Text.StringBuilder volumeName, int __bufferLength_native);
}
}

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

The idea is that you get an IDE error, then you either fix the error or copy the generated code into your source and remove the LibraryImportAttribute. Then the error goes away as you aren't using the generator any more.

If I understand right then there are two bugs -- the error no longer firing, and the generated code not working (as I discovered in my original PR)

the issue with the generated code pasted above (I think) is that it doesn't have CharSet = CharSet.Unicode so I guess it defaulted to CharSet.None for volumeName which treats it as Ansi on Windows. (Aside, maybe generated code should always be explicit about CharSet anyway).

…16 is set
When LibraryImportGenerator creates a non-forwarder stub with an inner
local DllImport function, it did not forward StringMarshalling.Utf16 as
CharSet=Unicode. This caused any types forwarded to the runtime
marshaller (e.g. StringBuilder) to default to Ansi encoding, producing
incorrect results for Unicode APIs.
Add CharSet=CharSet.Unicode to the inner DllImport attribute when
StringMarshalling.Utf16 is specified on the LibraryImport, matching the
existing behavior in CreateForwarderDllImport.
Fix for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

…plementation from our generator
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/408e36a5-d92b-4944-8b37-13c9d31754fd
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 20, 2026 21:30
auto-merge was automatically disabled April 20, 2026 21:30

Head branch was pushed to by a user without write access

CopilotAI removed the request for review from CopilotApril 20, 2026 21:30

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

Done in commit b2bacee. Both LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer now have an IsGeneratedByOurGenerator helper that checks for [GeneratedCode] with a tool name starting with "Microsoft.Interop.LibraryImportGenerator". When iterating DeclaringSyntaxReferences, if the current syntax has a body AND the method has our generator's [GeneratedCode] attribute, we skip that syntax reference (the generated implementation) and continue to find the user's partial declaration to analyze.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

…enerated implementation; always run CalculateDiagnostics
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/301361db-1d3f-4105-871a-90438d6a01df
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 20, 2026 21:42

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

Done in commit d4687fc. Both analyzers now pass isGeneratedByOurGenerator as a skipInvalidMethodCheck parameter to AnalyzeMethodSyntax. When true, GetDiagnosticIfInvalidMethodForGeneration is skipped (since we know the method was valid — the generator already ran on it), but CalculateDiagnostics always runs regardless.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the location in the non-generated source.

…tial implementation part and filter by syntax tree
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/9fbadd4b-a632-4127-9b12-2bad0f80873f
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 21:12

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the loca...

Fixed in 351839b. The root cause was that with Analyze | ReportDiagnostics, Roslyn's RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances. Added a method.PartialDefinitionPart is not null guard at the top of AnalyzeMethod in both analyzers to skip the implementation part. Also added a SyntaxTree filter when reporting diagnostics to ensure only user-source locations are reported.

@jkoritzinsky
jkoritzinsky enabled auto-merge (squash) April 23, 2026 22:43
@jkoritzinsky
jkoritzinsky merged commit d754bd0 into mainApr 24, 2026
103 of 105 checks passed
@jkoritzinsky
jkoritzinsky deleted the copilot/fix-library-import-diagnostics-analyzer branch April 24, 2026 20:01
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

LibraryImportDiagnosticsAnalyzer fails to report SYSLIB1051 for StringBuilder when StringMarshalling is set, generating bad code

5 participants

@danmoseley@jkoritzinsky@jtschuster
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set - #126691

Merged
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer
Apr 24, 2026
Merged

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set#126691
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer

Conversation

CopilotAI commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Description

LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer fail to report diagnostics (e.g. SYSLIB1051) for [LibraryImport] methods when StringMarshalling is set. This is a regression introduced in preview.3 when diagnostic reporting was moved from the generators to separate analyzer classes. Additionally, the generated inner [DllImport] stub omits CharSet = CharSet.Unicode when StringMarshalling.Utf16 is set, causing incorrect runtime marshalling of forwarded types (e.g. StringBuilder).

Root cause: The source generator marks its generated stub output with [GeneratedCode]. Roslyn's generated-code heuristics then also classify the user's partial method declaration as generated code. With ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None), both analyzers silently skipped all [LibraryImport] methods. Additionally, with Analyze | ReportDiagnostics enabled, RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances, which could produce duplicate diagnostics — one in user source and one in generated source. The generated implementation syntax (which has a body) also caused a spurious SYSLIB1050 (InvalidAttributedMethodSignature) because GetDiagnosticIfInvalidMethodForGeneration rejects methods with a body.

Reproduction:

// SYSLIB1051 should be reported but is NOT (with StringMarshalling.Utf16)[LibraryImport("kernel32.dll",StringMarshalling=StringMarshalling.Utf16)]internalstaticpartialintGetVolumeNameForVolumeMountPointW(stringvolumeMountPoint,[Out]StringBuildervolumeName,// No error — silent bad codegenintbufferLength);

Changes

  • LibraryImportDiagnosticsAnalyzer: Changed GeneratedCodeAnalysisFlags.NoneAnalyze | ReportDiagnostics so the analyzer runs on and reports diagnostics for methods whose partial declaration is classified as generated code. Added PartialDefinitionPart guard to skip the partial implementation part (avoiding duplicate diagnostics from RegisterSymbolAction firing for both parts). Added IsGeneratedByOurGenerator helper. When iterating DeclaringSyntaxReferences, the generated implementation syntax (body present + our [GeneratedCode]) is skipped to find the user's partial declaration. GetDiagnosticIfInvalidMethodForGeneration is skipped (via skipInvalidMethodCheck flag) when our generator has already produced an implementation — since the method must have been valid for the generator to run — but CalculateDiagnostics always runs to catch other issues. Diagnostics are filtered by SyntaxTree to only report those located in the user's (non-generated) source.
  • DownlevelLibraryImportDiagnosticsAnalyzer: Applied the same GeneratedCodeAnalysisFlags.Analyze | ReportDiagnostics fix, the same PartialDefinitionPart guard, the same selective SYSLIB1050 guard, and the same SyntaxTree diagnostic location filter.
  • LibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Now forwards CharSet = CharSet.Unicode to the inner [DllImport] when StringMarshalling.Utf16 is set, using the shared CreateEnumExpressionSyntax helper (extracted to class-level) to ensure consistency with the forwarder stub.
  • DownlevelLibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Applied the same CharSet = CharSet.Unicode forwarding fix using the shared CreateEnumExpressionSyntax helper (also extracted to class-level in the downlevel generator).
  • DownlevelLibraryImportGenerator.CreateForwarderDllImport: Updated to use the shared class-level CreateEnumExpressionSyntax helper.
  • Tests (Diagnostics.cs): Added StringBuilderNotSupported_ReportsDiagnostic and StringBuilderNotSupported_WithStringParam_ReportsDiagnostic regression tests covering StringBuilder with and without StringMarshalling variants.
  • Tests (Compiles.cs): Added ForwardedTypesWithStringMarshalling_InnerDllImportHasCharSet test verifying that the inner [DllImport] has CharSet.Unicode when StringMarshalling.Utf16 is set and no CharSet argument when other values are used. Fixed test assertion to use EndsWith to handle the global:: qualified name emitted by the generator. Fixed the DllImport attribute predicate to use EndsWith("DllImportAttribute") instead of Contains("DllImport") to avoid false matches on DefaultDllImportSearchPathsAttribute.

Testing

  • Both generator projects (LibraryImportGenerator, DownlevelLibraryImportGenerator) build successfully with zero errors and warnings.
  • All 709 unit tests pass (1 skipped, 0 failed), including previously-failing AddDisableRuntimeMarshallingAttributeFixerTests which validated the duplicate diagnostic fix.
  • Regression tests added for the diagnostic and code-generation fixes.
  • Code review and CodeQL scan passed with no issues.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

CopilotAI changed the title [WIP] Fix LibraryImportDiagnosticsAnalyzer to report SYSLIB1051 for StringBuilderFix LibraryImportDiagnosticsAnalyzer missing SYSLIB1051 for StringBuilder when StringMarshalling is setApr 9, 2026
CopilotAI requested a review from jkoritzinskyApril 9, 2026 07:03
@danmoseley

Copy link
Copy Markdown
Contributor

It seems Copilot could not make a fix before timing out.

Add tests verifying that SYSLIB1051 (ParameterTypeNotSupported) is
correctly reported for StringBuilder parameters when StringMarshalling
is set to Utf16 or Utf8, both as standalone parameters and alongside
string parameters with [Out] attribute.
Regression test for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 9, 2026 17:39
@jkoritzinsky

Copy link
Copy Markdown
Member

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets a regression in LibraryImportDiagnosticsAnalyzer where SYSLIB1051 is not reported for StringBuilder parameters when LibraryImportAttribute.StringMarshalling is specified, leading to silently incorrect forwarder stub generation.

Changes:

  • Added analyzer unit tests asserting SYSLIB1051 (ParameterTypeNotSupported) is reported for StringBuilder parameters with StringMarshalling set (Utf16/Utf8) and without it.
  • Added a reproduction-style test case for string + [Out] StringBuilder parameter combinations.

@github-actions

This comment has been minimized.

@danmoseley

Copy link
Copy Markdown
Contributor

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

@jkoritzinsky I updated the repro (top post #126687) to include a global.json -- can you still not repro with that?

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@jkoritzinsky also added the install command for preview 3. Using preview 2, it doesn't repro. I verified this by hand again, and it does repro for me with global.json pointing to 11.0.100-preview.3.26170.106

C:\temp\repro>type global.json | find /i "ver""version": "11.0.100-preview.3.26170.106",
C:\temp\repro>type program.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("If you see this, SYSLIB1051 was NOT reported.");
static partial class NativeMethods
{
[LibraryImport("kernel32.dll", StringMarshalling = StringMarshalling.Utf16)]
internal static partial int GetVolumeNameForVolumeMountPointW(
string volumeMountPoint,
[Out] StringBuilder volumeName,
int bufferLength);
}
C:\temp\repro>dotnet build
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (0.1s) → bin\Debug\net11.0\doit.dll
Build succeeded in 0.9s

and

C:\temp\repro>set emitcompilergeneratedfiles=true
C:\temp\repro>dotnet build --no-incremental
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (1.5s) → bin\Debug\net11.0\doit.dll
Build succeeded in 2.3s
C:\temp\repro>type obj\Debug\net11.0\generated\Microsoft.Interop.LibraryImportGenerator\Microsoft.Interop.LibraryImportGenerator\LibraryImports.g.cs
// <auto-generated/>
static unsafe partial class NativeMethods
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "11.0.14.17106")]
[global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
internal static partial int GetVolumeNameForVolumeMountPointW(string volumeMountPoint, global::System.Text.StringBuilder volumeName, int bufferLength)
{
int __retVal;
// Pin - Pin data in preparation for calling the P/Invoke.
fixed (void* __volumeMountPoint_native = &global::System.Runtime.InteropServices.Marshalling.Utf16StringMarshaller.GetPinnableReference(volumeMountPoint))
{
__retVal = __PInvoke((ushort*)__volumeMountPoint_native, volumeName, bufferLength);
}
return __retVal;
// Local P/Invoke
[global::System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", ExactSpelling = true)]
static extern unsafe int __PInvoke(ushort* __volumeMountPoint_native, [System.Runtime.InteropServices.OutAttribute] global::System.Text.StringBuilder volumeName, int __bufferLength_native);
}
}

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

The idea is that you get an IDE error, then you either fix the error or copy the generated code into your source and remove the LibraryImportAttribute. Then the error goes away as you aren't using the generator any more.

If I understand right then there are two bugs -- the error no longer firing, and the generated code not working (as I discovered in my original PR)

the issue with the generated code pasted above (I think) is that it doesn't have CharSet = CharSet.Unicode so I guess it defaulted to CharSet.None for volumeName which treats it as Ansi on Windows. (Aside, maybe generated code should always be explicit about CharSet anyway).

…16 is set
When LibraryImportGenerator creates a non-forwarder stub with an inner
local DllImport function, it did not forward StringMarshalling.Utf16 as
CharSet=Unicode. This caused any types forwarded to the runtime
marshaller (e.g. StringBuilder) to default to Ansi encoding, producing
incorrect results for Unicode APIs.
Add CharSet=CharSet.Unicode to the inner DllImport attribute when
StringMarshalling.Utf16 is specified on the LibraryImport, matching the
existing behavior in CreateForwarderDllImport.
Fix for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

…plementation from our generator
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/408e36a5-d92b-4944-8b37-13c9d31754fd
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 20, 2026 21:30
auto-merge was automatically disabled April 20, 2026 21:30

Head branch was pushed to by a user without write access

CopilotAI removed the request for review from CopilotApril 20, 2026 21:30

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

Done in commit b2bacee. Both LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer now have an IsGeneratedByOurGenerator helper that checks for [GeneratedCode] with a tool name starting with "Microsoft.Interop.LibraryImportGenerator". When iterating DeclaringSyntaxReferences, if the current syntax has a body AND the method has our generator's [GeneratedCode] attribute, we skip that syntax reference (the generated implementation) and continue to find the user's partial declaration to analyze.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

…enerated implementation; always run CalculateDiagnostics
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/301361db-1d3f-4105-871a-90438d6a01df
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 20, 2026 21:42

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

Done in commit d4687fc. Both analyzers now pass isGeneratedByOurGenerator as a skipInvalidMethodCheck parameter to AnalyzeMethodSyntax. When true, GetDiagnosticIfInvalidMethodForGeneration is skipped (since we know the method was valid — the generator already ran on it), but CalculateDiagnostics always runs regardless.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the location in the non-generated source.

…tial implementation part and filter by syntax tree
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/9fbadd4b-a632-4127-9b12-2bad0f80873f
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 21:12

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the loca...

Fixed in 351839b. The root cause was that with Analyze | ReportDiagnostics, Roslyn's RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances. Added a method.PartialDefinitionPart is not null guard at the top of AnalyzeMethod in both analyzers to skip the implementation part. Also added a SyntaxTree filter when reporting diagnostics to ensure only user-source locations are reported.

@jkoritzinsky
jkoritzinsky enabled auto-merge (squash) April 23, 2026 22:43
@jkoritzinsky
jkoritzinsky merged commit d754bd0 into mainApr 24, 2026
103 of 105 checks passed
@jkoritzinsky
jkoritzinsky deleted the copilot/fix-library-import-diagnostics-analyzer branch April 24, 2026 20:01
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

LibraryImportDiagnosticsAnalyzer fails to report SYSLIB1051 for StringBuilder when StringMarshalling is set, generating bad code

5 participants

@danmoseley@jkoritzinsky@jtschuster
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set - #126691

Merged
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer
Apr 24, 2026
Merged

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set#126691
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer

Conversation

CopilotAI commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Description

LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer fail to report diagnostics (e.g. SYSLIB1051) for [LibraryImport] methods when StringMarshalling is set. This is a regression introduced in preview.3 when diagnostic reporting was moved from the generators to separate analyzer classes. Additionally, the generated inner [DllImport] stub omits CharSet = CharSet.Unicode when StringMarshalling.Utf16 is set, causing incorrect runtime marshalling of forwarded types (e.g. StringBuilder).

Root cause: The source generator marks its generated stub output with [GeneratedCode]. Roslyn's generated-code heuristics then also classify the user's partial method declaration as generated code. With ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None), both analyzers silently skipped all [LibraryImport] methods. Additionally, with Analyze | ReportDiagnostics enabled, RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances, which could produce duplicate diagnostics — one in user source and one in generated source. The generated implementation syntax (which has a body) also caused a spurious SYSLIB1050 (InvalidAttributedMethodSignature) because GetDiagnosticIfInvalidMethodForGeneration rejects methods with a body.

Reproduction:

// SYSLIB1051 should be reported but is NOT (with StringMarshalling.Utf16)[LibraryImport("kernel32.dll",StringMarshalling=StringMarshalling.Utf16)]internalstaticpartialintGetVolumeNameForVolumeMountPointW(stringvolumeMountPoint,[Out]StringBuildervolumeName,// No error — silent bad codegenintbufferLength);

Changes

  • LibraryImportDiagnosticsAnalyzer: Changed GeneratedCodeAnalysisFlags.NoneAnalyze | ReportDiagnostics so the analyzer runs on and reports diagnostics for methods whose partial declaration is classified as generated code. Added PartialDefinitionPart guard to skip the partial implementation part (avoiding duplicate diagnostics from RegisterSymbolAction firing for both parts). Added IsGeneratedByOurGenerator helper. When iterating DeclaringSyntaxReferences, the generated implementation syntax (body present + our [GeneratedCode]) is skipped to find the user's partial declaration. GetDiagnosticIfInvalidMethodForGeneration is skipped (via skipInvalidMethodCheck flag) when our generator has already produced an implementation — since the method must have been valid for the generator to run — but CalculateDiagnostics always runs to catch other issues. Diagnostics are filtered by SyntaxTree to only report those located in the user's (non-generated) source.
  • DownlevelLibraryImportDiagnosticsAnalyzer: Applied the same GeneratedCodeAnalysisFlags.Analyze | ReportDiagnostics fix, the same PartialDefinitionPart guard, the same selective SYSLIB1050 guard, and the same SyntaxTree diagnostic location filter.
  • LibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Now forwards CharSet = CharSet.Unicode to the inner [DllImport] when StringMarshalling.Utf16 is set, using the shared CreateEnumExpressionSyntax helper (extracted to class-level) to ensure consistency with the forwarder stub.
  • DownlevelLibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Applied the same CharSet = CharSet.Unicode forwarding fix using the shared CreateEnumExpressionSyntax helper (also extracted to class-level in the downlevel generator).
  • DownlevelLibraryImportGenerator.CreateForwarderDllImport: Updated to use the shared class-level CreateEnumExpressionSyntax helper.
  • Tests (Diagnostics.cs): Added StringBuilderNotSupported_ReportsDiagnostic and StringBuilderNotSupported_WithStringParam_ReportsDiagnostic regression tests covering StringBuilder with and without StringMarshalling variants.
  • Tests (Compiles.cs): Added ForwardedTypesWithStringMarshalling_InnerDllImportHasCharSet test verifying that the inner [DllImport] has CharSet.Unicode when StringMarshalling.Utf16 is set and no CharSet argument when other values are used. Fixed test assertion to use EndsWith to handle the global:: qualified name emitted by the generator. Fixed the DllImport attribute predicate to use EndsWith("DllImportAttribute") instead of Contains("DllImport") to avoid false matches on DefaultDllImportSearchPathsAttribute.

Testing

  • Both generator projects (LibraryImportGenerator, DownlevelLibraryImportGenerator) build successfully with zero errors and warnings.
  • All 709 unit tests pass (1 skipped, 0 failed), including previously-failing AddDisableRuntimeMarshallingAttributeFixerTests which validated the duplicate diagnostic fix.
  • Regression tests added for the diagnostic and code-generation fixes.
  • Code review and CodeQL scan passed with no issues.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

CopilotAI changed the title [WIP] Fix LibraryImportDiagnosticsAnalyzer to report SYSLIB1051 for StringBuilderFix LibraryImportDiagnosticsAnalyzer missing SYSLIB1051 for StringBuilder when StringMarshalling is setApr 9, 2026
CopilotAI requested a review from jkoritzinskyApril 9, 2026 07:03
@danmoseley

Copy link
Copy Markdown
Contributor

It seems Copilot could not make a fix before timing out.

Add tests verifying that SYSLIB1051 (ParameterTypeNotSupported) is
correctly reported for StringBuilder parameters when StringMarshalling
is set to Utf16 or Utf8, both as standalone parameters and alongside
string parameters with [Out] attribute.
Regression test for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 9, 2026 17:39
@jkoritzinsky

Copy link
Copy Markdown
Member

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets a regression in LibraryImportDiagnosticsAnalyzer where SYSLIB1051 is not reported for StringBuilder parameters when LibraryImportAttribute.StringMarshalling is specified, leading to silently incorrect forwarder stub generation.

Changes:

  • Added analyzer unit tests asserting SYSLIB1051 (ParameterTypeNotSupported) is reported for StringBuilder parameters with StringMarshalling set (Utf16/Utf8) and without it.
  • Added a reproduction-style test case for string + [Out] StringBuilder parameter combinations.

@github-actions

This comment has been minimized.

@danmoseley

Copy link
Copy Markdown
Contributor

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

@jkoritzinsky I updated the repro (top post #126687) to include a global.json -- can you still not repro with that?

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@jkoritzinsky also added the install command for preview 3. Using preview 2, it doesn't repro. I verified this by hand again, and it does repro for me with global.json pointing to 11.0.100-preview.3.26170.106

C:\temp\repro>type global.json | find /i "ver""version": "11.0.100-preview.3.26170.106",
C:\temp\repro>type program.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("If you see this, SYSLIB1051 was NOT reported.");
static partial class NativeMethods
{
[LibraryImport("kernel32.dll", StringMarshalling = StringMarshalling.Utf16)]
internal static partial int GetVolumeNameForVolumeMountPointW(
string volumeMountPoint,
[Out] StringBuilder volumeName,
int bufferLength);
}
C:\temp\repro>dotnet build
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (0.1s) → bin\Debug\net11.0\doit.dll
Build succeeded in 0.9s

and

C:\temp\repro>set emitcompilergeneratedfiles=true
C:\temp\repro>dotnet build --no-incremental
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (1.5s) → bin\Debug\net11.0\doit.dll
Build succeeded in 2.3s
C:\temp\repro>type obj\Debug\net11.0\generated\Microsoft.Interop.LibraryImportGenerator\Microsoft.Interop.LibraryImportGenerator\LibraryImports.g.cs
// <auto-generated/>
static unsafe partial class NativeMethods
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "11.0.14.17106")]
[global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
internal static partial int GetVolumeNameForVolumeMountPointW(string volumeMountPoint, global::System.Text.StringBuilder volumeName, int bufferLength)
{
int __retVal;
// Pin - Pin data in preparation for calling the P/Invoke.
fixed (void* __volumeMountPoint_native = &global::System.Runtime.InteropServices.Marshalling.Utf16StringMarshaller.GetPinnableReference(volumeMountPoint))
{
__retVal = __PInvoke((ushort*)__volumeMountPoint_native, volumeName, bufferLength);
}
return __retVal;
// Local P/Invoke
[global::System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", ExactSpelling = true)]
static extern unsafe int __PInvoke(ushort* __volumeMountPoint_native, [System.Runtime.InteropServices.OutAttribute] global::System.Text.StringBuilder volumeName, int __bufferLength_native);
}
}

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

The idea is that you get an IDE error, then you either fix the error or copy the generated code into your source and remove the LibraryImportAttribute. Then the error goes away as you aren't using the generator any more.

If I understand right then there are two bugs -- the error no longer firing, and the generated code not working (as I discovered in my original PR)

the issue with the generated code pasted above (I think) is that it doesn't have CharSet = CharSet.Unicode so I guess it defaulted to CharSet.None for volumeName which treats it as Ansi on Windows. (Aside, maybe generated code should always be explicit about CharSet anyway).

…16 is set
When LibraryImportGenerator creates a non-forwarder stub with an inner
local DllImport function, it did not forward StringMarshalling.Utf16 as
CharSet=Unicode. This caused any types forwarded to the runtime
marshaller (e.g. StringBuilder) to default to Ansi encoding, producing
incorrect results for Unicode APIs.
Add CharSet=CharSet.Unicode to the inner DllImport attribute when
StringMarshalling.Utf16 is specified on the LibraryImport, matching the
existing behavior in CreateForwarderDllImport.
Fix for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

…plementation from our generator
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/408e36a5-d92b-4944-8b37-13c9d31754fd
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 20, 2026 21:30
auto-merge was automatically disabled April 20, 2026 21:30

Head branch was pushed to by a user without write access

CopilotAI removed the request for review from CopilotApril 20, 2026 21:30

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

Done in commit b2bacee. Both LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer now have an IsGeneratedByOurGenerator helper that checks for [GeneratedCode] with a tool name starting with "Microsoft.Interop.LibraryImportGenerator". When iterating DeclaringSyntaxReferences, if the current syntax has a body AND the method has our generator's [GeneratedCode] attribute, we skip that syntax reference (the generated implementation) and continue to find the user's partial declaration to analyze.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

…enerated implementation; always run CalculateDiagnostics
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/301361db-1d3f-4105-871a-90438d6a01df
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 20, 2026 21:42

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

Done in commit d4687fc. Both analyzers now pass isGeneratedByOurGenerator as a skipInvalidMethodCheck parameter to AnalyzeMethodSyntax. When true, GetDiagnosticIfInvalidMethodForGeneration is skipped (since we know the method was valid — the generator already ran on it), but CalculateDiagnostics always runs regardless.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the location in the non-generated source.

…tial implementation part and filter by syntax tree
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/9fbadd4b-a632-4127-9b12-2bad0f80873f
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 21:12

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the loca...

Fixed in 351839b. The root cause was that with Analyze | ReportDiagnostics, Roslyn's RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances. Added a method.PartialDefinitionPart is not null guard at the top of AnalyzeMethod in both analyzers to skip the implementation part. Also added a SyntaxTree filter when reporting diagnostics to ensure only user-source locations are reported.

@jkoritzinsky
jkoritzinsky enabled auto-merge (squash) April 23, 2026 22:43
@jkoritzinsky
jkoritzinsky merged commit d754bd0 into mainApr 24, 2026
103 of 105 checks passed
@jkoritzinsky
jkoritzinsky deleted the copilot/fix-library-import-diagnostics-analyzer branch April 24, 2026 20:01
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

LibraryImportDiagnosticsAnalyzer fails to report SYSLIB1051 for StringBuilder when StringMarshalling is set, generating bad code

5 participants

@danmoseley@jkoritzinsky@jtschuster
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set - #126691

Merged
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer
Apr 24, 2026
Merged

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set#126691
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer

Conversation

CopilotAI commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Description

LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer fail to report diagnostics (e.g. SYSLIB1051) for [LibraryImport] methods when StringMarshalling is set. This is a regression introduced in preview.3 when diagnostic reporting was moved from the generators to separate analyzer classes. Additionally, the generated inner [DllImport] stub omits CharSet = CharSet.Unicode when StringMarshalling.Utf16 is set, causing incorrect runtime marshalling of forwarded types (e.g. StringBuilder).

Root cause: The source generator marks its generated stub output with [GeneratedCode]. Roslyn's generated-code heuristics then also classify the user's partial method declaration as generated code. With ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None), both analyzers silently skipped all [LibraryImport] methods. Additionally, with Analyze | ReportDiagnostics enabled, RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances, which could produce duplicate diagnostics — one in user source and one in generated source. The generated implementation syntax (which has a body) also caused a spurious SYSLIB1050 (InvalidAttributedMethodSignature) because GetDiagnosticIfInvalidMethodForGeneration rejects methods with a body.

Reproduction:

// SYSLIB1051 should be reported but is NOT (with StringMarshalling.Utf16)[LibraryImport("kernel32.dll",StringMarshalling=StringMarshalling.Utf16)]internalstaticpartialintGetVolumeNameForVolumeMountPointW(stringvolumeMountPoint,[Out]StringBuildervolumeName,// No error — silent bad codegenintbufferLength);

Changes

  • LibraryImportDiagnosticsAnalyzer: Changed GeneratedCodeAnalysisFlags.NoneAnalyze | ReportDiagnostics so the analyzer runs on and reports diagnostics for methods whose partial declaration is classified as generated code. Added PartialDefinitionPart guard to skip the partial implementation part (avoiding duplicate diagnostics from RegisterSymbolAction firing for both parts). Added IsGeneratedByOurGenerator helper. When iterating DeclaringSyntaxReferences, the generated implementation syntax (body present + our [GeneratedCode]) is skipped to find the user's partial declaration. GetDiagnosticIfInvalidMethodForGeneration is skipped (via skipInvalidMethodCheck flag) when our generator has already produced an implementation — since the method must have been valid for the generator to run — but CalculateDiagnostics always runs to catch other issues. Diagnostics are filtered by SyntaxTree to only report those located in the user's (non-generated) source.
  • DownlevelLibraryImportDiagnosticsAnalyzer: Applied the same GeneratedCodeAnalysisFlags.Analyze | ReportDiagnostics fix, the same PartialDefinitionPart guard, the same selective SYSLIB1050 guard, and the same SyntaxTree diagnostic location filter.
  • LibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Now forwards CharSet = CharSet.Unicode to the inner [DllImport] when StringMarshalling.Utf16 is set, using the shared CreateEnumExpressionSyntax helper (extracted to class-level) to ensure consistency with the forwarder stub.
  • DownlevelLibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Applied the same CharSet = CharSet.Unicode forwarding fix using the shared CreateEnumExpressionSyntax helper (also extracted to class-level in the downlevel generator).
  • DownlevelLibraryImportGenerator.CreateForwarderDllImport: Updated to use the shared class-level CreateEnumExpressionSyntax helper.
  • Tests (Diagnostics.cs): Added StringBuilderNotSupported_ReportsDiagnostic and StringBuilderNotSupported_WithStringParam_ReportsDiagnostic regression tests covering StringBuilder with and without StringMarshalling variants.
  • Tests (Compiles.cs): Added ForwardedTypesWithStringMarshalling_InnerDllImportHasCharSet test verifying that the inner [DllImport] has CharSet.Unicode when StringMarshalling.Utf16 is set and no CharSet argument when other values are used. Fixed test assertion to use EndsWith to handle the global:: qualified name emitted by the generator. Fixed the DllImport attribute predicate to use EndsWith("DllImportAttribute") instead of Contains("DllImport") to avoid false matches on DefaultDllImportSearchPathsAttribute.

Testing

  • Both generator projects (LibraryImportGenerator, DownlevelLibraryImportGenerator) build successfully with zero errors and warnings.
  • All 709 unit tests pass (1 skipped, 0 failed), including previously-failing AddDisableRuntimeMarshallingAttributeFixerTests which validated the duplicate diagnostic fix.
  • Regression tests added for the diagnostic and code-generation fixes.
  • Code review and CodeQL scan passed with no issues.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

CopilotAI changed the title [WIP] Fix LibraryImportDiagnosticsAnalyzer to report SYSLIB1051 for StringBuilderFix LibraryImportDiagnosticsAnalyzer missing SYSLIB1051 for StringBuilder when StringMarshalling is setApr 9, 2026
CopilotAI requested a review from jkoritzinskyApril 9, 2026 07:03
@danmoseley

Copy link
Copy Markdown
Contributor

It seems Copilot could not make a fix before timing out.

Add tests verifying that SYSLIB1051 (ParameterTypeNotSupported) is
correctly reported for StringBuilder parameters when StringMarshalling
is set to Utf16 or Utf8, both as standalone parameters and alongside
string parameters with [Out] attribute.
Regression test for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 9, 2026 17:39
@jkoritzinsky

Copy link
Copy Markdown
Member

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets a regression in LibraryImportDiagnosticsAnalyzer where SYSLIB1051 is not reported for StringBuilder parameters when LibraryImportAttribute.StringMarshalling is specified, leading to silently incorrect forwarder stub generation.

Changes:

  • Added analyzer unit tests asserting SYSLIB1051 (ParameterTypeNotSupported) is reported for StringBuilder parameters with StringMarshalling set (Utf16/Utf8) and without it.
  • Added a reproduction-style test case for string + [Out] StringBuilder parameter combinations.

@github-actions

This comment has been minimized.

@danmoseley

Copy link
Copy Markdown
Contributor

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

@jkoritzinsky I updated the repro (top post #126687) to include a global.json -- can you still not repro with that?

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@jkoritzinsky also added the install command for preview 3. Using preview 2, it doesn't repro. I verified this by hand again, and it does repro for me with global.json pointing to 11.0.100-preview.3.26170.106

C:\temp\repro>type global.json | find /i "ver""version": "11.0.100-preview.3.26170.106",
C:\temp\repro>type program.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("If you see this, SYSLIB1051 was NOT reported.");
static partial class NativeMethods
{
[LibraryImport("kernel32.dll", StringMarshalling = StringMarshalling.Utf16)]
internal static partial int GetVolumeNameForVolumeMountPointW(
string volumeMountPoint,
[Out] StringBuilder volumeName,
int bufferLength);
}
C:\temp\repro>dotnet build
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (0.1s) → bin\Debug\net11.0\doit.dll
Build succeeded in 0.9s

and

C:\temp\repro>set emitcompilergeneratedfiles=true
C:\temp\repro>dotnet build --no-incremental
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (1.5s) → bin\Debug\net11.0\doit.dll
Build succeeded in 2.3s
C:\temp\repro>type obj\Debug\net11.0\generated\Microsoft.Interop.LibraryImportGenerator\Microsoft.Interop.LibraryImportGenerator\LibraryImports.g.cs
// <auto-generated/>
static unsafe partial class NativeMethods
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "11.0.14.17106")]
[global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
internal static partial int GetVolumeNameForVolumeMountPointW(string volumeMountPoint, global::System.Text.StringBuilder volumeName, int bufferLength)
{
int __retVal;
// Pin - Pin data in preparation for calling the P/Invoke.
fixed (void* __volumeMountPoint_native = &global::System.Runtime.InteropServices.Marshalling.Utf16StringMarshaller.GetPinnableReference(volumeMountPoint))
{
__retVal = __PInvoke((ushort*)__volumeMountPoint_native, volumeName, bufferLength);
}
return __retVal;
// Local P/Invoke
[global::System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", ExactSpelling = true)]
static extern unsafe int __PInvoke(ushort* __volumeMountPoint_native, [System.Runtime.InteropServices.OutAttribute] global::System.Text.StringBuilder volumeName, int __bufferLength_native);
}
}

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

The idea is that you get an IDE error, then you either fix the error or copy the generated code into your source and remove the LibraryImportAttribute. Then the error goes away as you aren't using the generator any more.

If I understand right then there are two bugs -- the error no longer firing, and the generated code not working (as I discovered in my original PR)

the issue with the generated code pasted above (I think) is that it doesn't have CharSet = CharSet.Unicode so I guess it defaulted to CharSet.None for volumeName which treats it as Ansi on Windows. (Aside, maybe generated code should always be explicit about CharSet anyway).

…16 is set
When LibraryImportGenerator creates a non-forwarder stub with an inner
local DllImport function, it did not forward StringMarshalling.Utf16 as
CharSet=Unicode. This caused any types forwarded to the runtime
marshaller (e.g. StringBuilder) to default to Ansi encoding, producing
incorrect results for Unicode APIs.
Add CharSet=CharSet.Unicode to the inner DllImport attribute when
StringMarshalling.Utf16 is specified on the LibraryImport, matching the
existing behavior in CreateForwarderDllImport.
Fix for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

…plementation from our generator
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/408e36a5-d92b-4944-8b37-13c9d31754fd
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 20, 2026 21:30
auto-merge was automatically disabled April 20, 2026 21:30

Head branch was pushed to by a user without write access

CopilotAI removed the request for review from CopilotApril 20, 2026 21:30

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

Done in commit b2bacee. Both LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer now have an IsGeneratedByOurGenerator helper that checks for [GeneratedCode] with a tool name starting with "Microsoft.Interop.LibraryImportGenerator". When iterating DeclaringSyntaxReferences, if the current syntax has a body AND the method has our generator's [GeneratedCode] attribute, we skip that syntax reference (the generated implementation) and continue to find the user's partial declaration to analyze.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

…enerated implementation; always run CalculateDiagnostics
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/301361db-1d3f-4105-871a-90438d6a01df
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 20, 2026 21:42

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

Done in commit d4687fc. Both analyzers now pass isGeneratedByOurGenerator as a skipInvalidMethodCheck parameter to AnalyzeMethodSyntax. When true, GetDiagnosticIfInvalidMethodForGeneration is skipped (since we know the method was valid — the generator already ran on it), but CalculateDiagnostics always runs regardless.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the location in the non-generated source.

…tial implementation part and filter by syntax tree
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/9fbadd4b-a632-4127-9b12-2bad0f80873f
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 21:12

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the loca...

Fixed in 351839b. The root cause was that with Analyze | ReportDiagnostics, Roslyn's RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances. Added a method.PartialDefinitionPart is not null guard at the top of AnalyzeMethod in both analyzers to skip the implementation part. Also added a SyntaxTree filter when reporting diagnostics to ensure only user-source locations are reported.

@jkoritzinsky
jkoritzinsky enabled auto-merge (squash) April 23, 2026 22:43
@jkoritzinsky
jkoritzinsky merged commit d754bd0 into mainApr 24, 2026
103 of 105 checks passed
@jkoritzinsky
jkoritzinsky deleted the copilot/fix-library-import-diagnostics-analyzer branch April 24, 2026 20:01
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

LibraryImportDiagnosticsAnalyzer fails to report SYSLIB1051 for StringBuilder when StringMarshalling is set, generating bad code

5 participants

@danmoseley@jkoritzinsky@jtschuster
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set - #126691

Merged
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer
Apr 24, 2026
Merged

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set#126691
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer

Conversation

CopilotAI commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Description

LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer fail to report diagnostics (e.g. SYSLIB1051) for [LibraryImport] methods when StringMarshalling is set. This is a regression introduced in preview.3 when diagnostic reporting was moved from the generators to separate analyzer classes. Additionally, the generated inner [DllImport] stub omits CharSet = CharSet.Unicode when StringMarshalling.Utf16 is set, causing incorrect runtime marshalling of forwarded types (e.g. StringBuilder).

Root cause: The source generator marks its generated stub output with [GeneratedCode]. Roslyn's generated-code heuristics then also classify the user's partial method declaration as generated code. With ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None), both analyzers silently skipped all [LibraryImport] methods. Additionally, with Analyze | ReportDiagnostics enabled, RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances, which could produce duplicate diagnostics — one in user source and one in generated source. The generated implementation syntax (which has a body) also caused a spurious SYSLIB1050 (InvalidAttributedMethodSignature) because GetDiagnosticIfInvalidMethodForGeneration rejects methods with a body.

Reproduction:

// SYSLIB1051 should be reported but is NOT (with StringMarshalling.Utf16)[LibraryImport("kernel32.dll",StringMarshalling=StringMarshalling.Utf16)]internalstaticpartialintGetVolumeNameForVolumeMountPointW(stringvolumeMountPoint,[Out]StringBuildervolumeName,// No error — silent bad codegenintbufferLength);

Changes

  • LibraryImportDiagnosticsAnalyzer: Changed GeneratedCodeAnalysisFlags.NoneAnalyze | ReportDiagnostics so the analyzer runs on and reports diagnostics for methods whose partial declaration is classified as generated code. Added PartialDefinitionPart guard to skip the partial implementation part (avoiding duplicate diagnostics from RegisterSymbolAction firing for both parts). Added IsGeneratedByOurGenerator helper. When iterating DeclaringSyntaxReferences, the generated implementation syntax (body present + our [GeneratedCode]) is skipped to find the user's partial declaration. GetDiagnosticIfInvalidMethodForGeneration is skipped (via skipInvalidMethodCheck flag) when our generator has already produced an implementation — since the method must have been valid for the generator to run — but CalculateDiagnostics always runs to catch other issues. Diagnostics are filtered by SyntaxTree to only report those located in the user's (non-generated) source.
  • DownlevelLibraryImportDiagnosticsAnalyzer: Applied the same GeneratedCodeAnalysisFlags.Analyze | ReportDiagnostics fix, the same PartialDefinitionPart guard, the same selective SYSLIB1050 guard, and the same SyntaxTree diagnostic location filter.
  • LibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Now forwards CharSet = CharSet.Unicode to the inner [DllImport] when StringMarshalling.Utf16 is set, using the shared CreateEnumExpressionSyntax helper (extracted to class-level) to ensure consistency with the forwarder stub.
  • DownlevelLibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Applied the same CharSet = CharSet.Unicode forwarding fix using the shared CreateEnumExpressionSyntax helper (also extracted to class-level in the downlevel generator).
  • DownlevelLibraryImportGenerator.CreateForwarderDllImport: Updated to use the shared class-level CreateEnumExpressionSyntax helper.
  • Tests (Diagnostics.cs): Added StringBuilderNotSupported_ReportsDiagnostic and StringBuilderNotSupported_WithStringParam_ReportsDiagnostic regression tests covering StringBuilder with and without StringMarshalling variants.
  • Tests (Compiles.cs): Added ForwardedTypesWithStringMarshalling_InnerDllImportHasCharSet test verifying that the inner [DllImport] has CharSet.Unicode when StringMarshalling.Utf16 is set and no CharSet argument when other values are used. Fixed test assertion to use EndsWith to handle the global:: qualified name emitted by the generator. Fixed the DllImport attribute predicate to use EndsWith("DllImportAttribute") instead of Contains("DllImport") to avoid false matches on DefaultDllImportSearchPathsAttribute.

Testing

  • Both generator projects (LibraryImportGenerator, DownlevelLibraryImportGenerator) build successfully with zero errors and warnings.
  • All 709 unit tests pass (1 skipped, 0 failed), including previously-failing AddDisableRuntimeMarshallingAttributeFixerTests which validated the duplicate diagnostic fix.
  • Regression tests added for the diagnostic and code-generation fixes.
  • Code review and CodeQL scan passed with no issues.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

CopilotAI changed the title [WIP] Fix LibraryImportDiagnosticsAnalyzer to report SYSLIB1051 for StringBuilderFix LibraryImportDiagnosticsAnalyzer missing SYSLIB1051 for StringBuilder when StringMarshalling is setApr 9, 2026
CopilotAI requested a review from jkoritzinskyApril 9, 2026 07:03
@danmoseley

Copy link
Copy Markdown
Contributor

It seems Copilot could not make a fix before timing out.

Add tests verifying that SYSLIB1051 (ParameterTypeNotSupported) is
correctly reported for StringBuilder parameters when StringMarshalling
is set to Utf16 or Utf8, both as standalone parameters and alongside
string parameters with [Out] attribute.
Regression test for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 9, 2026 17:39
@jkoritzinsky

Copy link
Copy Markdown
Member

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets a regression in LibraryImportDiagnosticsAnalyzer where SYSLIB1051 is not reported for StringBuilder parameters when LibraryImportAttribute.StringMarshalling is specified, leading to silently incorrect forwarder stub generation.

Changes:

  • Added analyzer unit tests asserting SYSLIB1051 (ParameterTypeNotSupported) is reported for StringBuilder parameters with StringMarshalling set (Utf16/Utf8) and without it.
  • Added a reproduction-style test case for string + [Out] StringBuilder parameter combinations.

@github-actions

This comment has been minimized.

@danmoseley

Copy link
Copy Markdown
Contributor

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

@jkoritzinsky I updated the repro (top post #126687) to include a global.json -- can you still not repro with that?

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@jkoritzinsky also added the install command for preview 3. Using preview 2, it doesn't repro. I verified this by hand again, and it does repro for me with global.json pointing to 11.0.100-preview.3.26170.106

C:\temp\repro>type global.json | find /i "ver""version": "11.0.100-preview.3.26170.106",
C:\temp\repro>type program.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("If you see this, SYSLIB1051 was NOT reported.");
static partial class NativeMethods
{
[LibraryImport("kernel32.dll", StringMarshalling = StringMarshalling.Utf16)]
internal static partial int GetVolumeNameForVolumeMountPointW(
string volumeMountPoint,
[Out] StringBuilder volumeName,
int bufferLength);
}
C:\temp\repro>dotnet build
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (0.1s) → bin\Debug\net11.0\doit.dll
Build succeeded in 0.9s

and

C:\temp\repro>set emitcompilergeneratedfiles=true
C:\temp\repro>dotnet build --no-incremental
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (1.5s) → bin\Debug\net11.0\doit.dll
Build succeeded in 2.3s
C:\temp\repro>type obj\Debug\net11.0\generated\Microsoft.Interop.LibraryImportGenerator\Microsoft.Interop.LibraryImportGenerator\LibraryImports.g.cs
// <auto-generated/>
static unsafe partial class NativeMethods
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "11.0.14.17106")]
[global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
internal static partial int GetVolumeNameForVolumeMountPointW(string volumeMountPoint, global::System.Text.StringBuilder volumeName, int bufferLength)
{
int __retVal;
// Pin - Pin data in preparation for calling the P/Invoke.
fixed (void* __volumeMountPoint_native = &global::System.Runtime.InteropServices.Marshalling.Utf16StringMarshaller.GetPinnableReference(volumeMountPoint))
{
__retVal = __PInvoke((ushort*)__volumeMountPoint_native, volumeName, bufferLength);
}
return __retVal;
// Local P/Invoke
[global::System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", ExactSpelling = true)]
static extern unsafe int __PInvoke(ushort* __volumeMountPoint_native, [System.Runtime.InteropServices.OutAttribute] global::System.Text.StringBuilder volumeName, int __bufferLength_native);
}
}

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

The idea is that you get an IDE error, then you either fix the error or copy the generated code into your source and remove the LibraryImportAttribute. Then the error goes away as you aren't using the generator any more.

If I understand right then there are two bugs -- the error no longer firing, and the generated code not working (as I discovered in my original PR)

the issue with the generated code pasted above (I think) is that it doesn't have CharSet = CharSet.Unicode so I guess it defaulted to CharSet.None for volumeName which treats it as Ansi on Windows. (Aside, maybe generated code should always be explicit about CharSet anyway).

…16 is set
When LibraryImportGenerator creates a non-forwarder stub with an inner
local DllImport function, it did not forward StringMarshalling.Utf16 as
CharSet=Unicode. This caused any types forwarded to the runtime
marshaller (e.g. StringBuilder) to default to Ansi encoding, producing
incorrect results for Unicode APIs.
Add CharSet=CharSet.Unicode to the inner DllImport attribute when
StringMarshalling.Utf16 is specified on the LibraryImport, matching the
existing behavior in CreateForwarderDllImport.
Fix for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

…plementation from our generator
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/408e36a5-d92b-4944-8b37-13c9d31754fd
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 20, 2026 21:30
auto-merge was automatically disabled April 20, 2026 21:30

Head branch was pushed to by a user without write access

CopilotAI removed the request for review from CopilotApril 20, 2026 21:30

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

Done in commit b2bacee. Both LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer now have an IsGeneratedByOurGenerator helper that checks for [GeneratedCode] with a tool name starting with "Microsoft.Interop.LibraryImportGenerator". When iterating DeclaringSyntaxReferences, if the current syntax has a body AND the method has our generator's [GeneratedCode] attribute, we skip that syntax reference (the generated implementation) and continue to find the user's partial declaration to analyze.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

…enerated implementation; always run CalculateDiagnostics
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/301361db-1d3f-4105-871a-90438d6a01df
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 20, 2026 21:42

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

Done in commit d4687fc. Both analyzers now pass isGeneratedByOurGenerator as a skipInvalidMethodCheck parameter to AnalyzeMethodSyntax. When true, GetDiagnosticIfInvalidMethodForGeneration is skipped (since we know the method was valid — the generator already ran on it), but CalculateDiagnostics always runs regardless.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the location in the non-generated source.

…tial implementation part and filter by syntax tree
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/9fbadd4b-a632-4127-9b12-2bad0f80873f
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 21:12

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the loca...

Fixed in 351839b. The root cause was that with Analyze | ReportDiagnostics, Roslyn's RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances. Added a method.PartialDefinitionPart is not null guard at the top of AnalyzeMethod in both analyzers to skip the implementation part. Also added a SyntaxTree filter when reporting diagnostics to ensure only user-source locations are reported.

@jkoritzinsky
jkoritzinsky enabled auto-merge (squash) April 23, 2026 22:43
@jkoritzinsky
jkoritzinsky merged commit d754bd0 into mainApr 24, 2026
103 of 105 checks passed
@jkoritzinsky
jkoritzinsky deleted the copilot/fix-library-import-diagnostics-analyzer branch April 24, 2026 20:01
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

LibraryImportDiagnosticsAnalyzer fails to report SYSLIB1051 for StringBuilder when StringMarshalling is set, generating bad code

5 participants

@danmoseley@jkoritzinsky@jtschuster
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set - #126691

Merged
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer
Apr 24, 2026
Merged

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set#126691
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer

Conversation

CopilotAI commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Description

LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer fail to report diagnostics (e.g. SYSLIB1051) for [LibraryImport] methods when StringMarshalling is set. This is a regression introduced in preview.3 when diagnostic reporting was moved from the generators to separate analyzer classes. Additionally, the generated inner [DllImport] stub omits CharSet = CharSet.Unicode when StringMarshalling.Utf16 is set, causing incorrect runtime marshalling of forwarded types (e.g. StringBuilder).

Root cause: The source generator marks its generated stub output with [GeneratedCode]. Roslyn's generated-code heuristics then also classify the user's partial method declaration as generated code. With ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None), both analyzers silently skipped all [LibraryImport] methods. Additionally, with Analyze | ReportDiagnostics enabled, RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances, which could produce duplicate diagnostics — one in user source and one in generated source. The generated implementation syntax (which has a body) also caused a spurious SYSLIB1050 (InvalidAttributedMethodSignature) because GetDiagnosticIfInvalidMethodForGeneration rejects methods with a body.

Reproduction:

// SYSLIB1051 should be reported but is NOT (with StringMarshalling.Utf16)[LibraryImport("kernel32.dll",StringMarshalling=StringMarshalling.Utf16)]internalstaticpartialintGetVolumeNameForVolumeMountPointW(stringvolumeMountPoint,[Out]StringBuildervolumeName,// No error — silent bad codegenintbufferLength);

Changes

  • LibraryImportDiagnosticsAnalyzer: Changed GeneratedCodeAnalysisFlags.NoneAnalyze | ReportDiagnostics so the analyzer runs on and reports diagnostics for methods whose partial declaration is classified as generated code. Added PartialDefinitionPart guard to skip the partial implementation part (avoiding duplicate diagnostics from RegisterSymbolAction firing for both parts). Added IsGeneratedByOurGenerator helper. When iterating DeclaringSyntaxReferences, the generated implementation syntax (body present + our [GeneratedCode]) is skipped to find the user's partial declaration. GetDiagnosticIfInvalidMethodForGeneration is skipped (via skipInvalidMethodCheck flag) when our generator has already produced an implementation — since the method must have been valid for the generator to run — but CalculateDiagnostics always runs to catch other issues. Diagnostics are filtered by SyntaxTree to only report those located in the user's (non-generated) source.
  • DownlevelLibraryImportDiagnosticsAnalyzer: Applied the same GeneratedCodeAnalysisFlags.Analyze | ReportDiagnostics fix, the same PartialDefinitionPart guard, the same selective SYSLIB1050 guard, and the same SyntaxTree diagnostic location filter.
  • LibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Now forwards CharSet = CharSet.Unicode to the inner [DllImport] when StringMarshalling.Utf16 is set, using the shared CreateEnumExpressionSyntax helper (extracted to class-level) to ensure consistency with the forwarder stub.
  • DownlevelLibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Applied the same CharSet = CharSet.Unicode forwarding fix using the shared CreateEnumExpressionSyntax helper (also extracted to class-level in the downlevel generator).
  • DownlevelLibraryImportGenerator.CreateForwarderDllImport: Updated to use the shared class-level CreateEnumExpressionSyntax helper.
  • Tests (Diagnostics.cs): Added StringBuilderNotSupported_ReportsDiagnostic and StringBuilderNotSupported_WithStringParam_ReportsDiagnostic regression tests covering StringBuilder with and without StringMarshalling variants.
  • Tests (Compiles.cs): Added ForwardedTypesWithStringMarshalling_InnerDllImportHasCharSet test verifying that the inner [DllImport] has CharSet.Unicode when StringMarshalling.Utf16 is set and no CharSet argument when other values are used. Fixed test assertion to use EndsWith to handle the global:: qualified name emitted by the generator. Fixed the DllImport attribute predicate to use EndsWith("DllImportAttribute") instead of Contains("DllImport") to avoid false matches on DefaultDllImportSearchPathsAttribute.

Testing

  • Both generator projects (LibraryImportGenerator, DownlevelLibraryImportGenerator) build successfully with zero errors and warnings.
  • All 709 unit tests pass (1 skipped, 0 failed), including previously-failing AddDisableRuntimeMarshallingAttributeFixerTests which validated the duplicate diagnostic fix.
  • Regression tests added for the diagnostic and code-generation fixes.
  • Code review and CodeQL scan passed with no issues.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

CopilotAI changed the title [WIP] Fix LibraryImportDiagnosticsAnalyzer to report SYSLIB1051 for StringBuilderFix LibraryImportDiagnosticsAnalyzer missing SYSLIB1051 for StringBuilder when StringMarshalling is setApr 9, 2026
CopilotAI requested a review from jkoritzinskyApril 9, 2026 07:03
@danmoseley

Copy link
Copy Markdown
Contributor

It seems Copilot could not make a fix before timing out.

Add tests verifying that SYSLIB1051 (ParameterTypeNotSupported) is
correctly reported for StringBuilder parameters when StringMarshalling
is set to Utf16 or Utf8, both as standalone parameters and alongside
string parameters with [Out] attribute.
Regression test for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 9, 2026 17:39
@jkoritzinsky

Copy link
Copy Markdown
Member

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets a regression in LibraryImportDiagnosticsAnalyzer where SYSLIB1051 is not reported for StringBuilder parameters when LibraryImportAttribute.StringMarshalling is specified, leading to silently incorrect forwarder stub generation.

Changes:

  • Added analyzer unit tests asserting SYSLIB1051 (ParameterTypeNotSupported) is reported for StringBuilder parameters with StringMarshalling set (Utf16/Utf8) and without it.
  • Added a reproduction-style test case for string + [Out] StringBuilder parameter combinations.

@github-actions

This comment has been minimized.

@danmoseley

Copy link
Copy Markdown
Contributor

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

@jkoritzinsky I updated the repro (top post #126687) to include a global.json -- can you still not repro with that?

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@jkoritzinsky also added the install command for preview 3. Using preview 2, it doesn't repro. I verified this by hand again, and it does repro for me with global.json pointing to 11.0.100-preview.3.26170.106

C:\temp\repro>type global.json | find /i "ver""version": "11.0.100-preview.3.26170.106",
C:\temp\repro>type program.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("If you see this, SYSLIB1051 was NOT reported.");
static partial class NativeMethods
{
[LibraryImport("kernel32.dll", StringMarshalling = StringMarshalling.Utf16)]
internal static partial int GetVolumeNameForVolumeMountPointW(
string volumeMountPoint,
[Out] StringBuilder volumeName,
int bufferLength);
}
C:\temp\repro>dotnet build
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (0.1s) → bin\Debug\net11.0\doit.dll
Build succeeded in 0.9s

and

C:\temp\repro>set emitcompilergeneratedfiles=true
C:\temp\repro>dotnet build --no-incremental
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (1.5s) → bin\Debug\net11.0\doit.dll
Build succeeded in 2.3s
C:\temp\repro>type obj\Debug\net11.0\generated\Microsoft.Interop.LibraryImportGenerator\Microsoft.Interop.LibraryImportGenerator\LibraryImports.g.cs
// <auto-generated/>
static unsafe partial class NativeMethods
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "11.0.14.17106")]
[global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
internal static partial int GetVolumeNameForVolumeMountPointW(string volumeMountPoint, global::System.Text.StringBuilder volumeName, int bufferLength)
{
int __retVal;
// Pin - Pin data in preparation for calling the P/Invoke.
fixed (void* __volumeMountPoint_native = &global::System.Runtime.InteropServices.Marshalling.Utf16StringMarshaller.GetPinnableReference(volumeMountPoint))
{
__retVal = __PInvoke((ushort*)__volumeMountPoint_native, volumeName, bufferLength);
}
return __retVal;
// Local P/Invoke
[global::System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", ExactSpelling = true)]
static extern unsafe int __PInvoke(ushort* __volumeMountPoint_native, [System.Runtime.InteropServices.OutAttribute] global::System.Text.StringBuilder volumeName, int __bufferLength_native);
}
}

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

The idea is that you get an IDE error, then you either fix the error or copy the generated code into your source and remove the LibraryImportAttribute. Then the error goes away as you aren't using the generator any more.

If I understand right then there are two bugs -- the error no longer firing, and the generated code not working (as I discovered in my original PR)

the issue with the generated code pasted above (I think) is that it doesn't have CharSet = CharSet.Unicode so I guess it defaulted to CharSet.None for volumeName which treats it as Ansi on Windows. (Aside, maybe generated code should always be explicit about CharSet anyway).

…16 is set
When LibraryImportGenerator creates a non-forwarder stub with an inner
local DllImport function, it did not forward StringMarshalling.Utf16 as
CharSet=Unicode. This caused any types forwarded to the runtime
marshaller (e.g. StringBuilder) to default to Ansi encoding, producing
incorrect results for Unicode APIs.
Add CharSet=CharSet.Unicode to the inner DllImport attribute when
StringMarshalling.Utf16 is specified on the LibraryImport, matching the
existing behavior in CreateForwarderDllImport.
Fix for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

…plementation from our generator
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/408e36a5-d92b-4944-8b37-13c9d31754fd
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 20, 2026 21:30
auto-merge was automatically disabled April 20, 2026 21:30

Head branch was pushed to by a user without write access

CopilotAI removed the request for review from CopilotApril 20, 2026 21:30

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

Done in commit b2bacee. Both LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer now have an IsGeneratedByOurGenerator helper that checks for [GeneratedCode] with a tool name starting with "Microsoft.Interop.LibraryImportGenerator". When iterating DeclaringSyntaxReferences, if the current syntax has a body AND the method has our generator's [GeneratedCode] attribute, we skip that syntax reference (the generated implementation) and continue to find the user's partial declaration to analyze.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

…enerated implementation; always run CalculateDiagnostics
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/301361db-1d3f-4105-871a-90438d6a01df
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 20, 2026 21:42

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

Done in commit d4687fc. Both analyzers now pass isGeneratedByOurGenerator as a skipInvalidMethodCheck parameter to AnalyzeMethodSyntax. When true, GetDiagnosticIfInvalidMethodForGeneration is skipped (since we know the method was valid — the generator already ran on it), but CalculateDiagnostics always runs regardless.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the location in the non-generated source.

…tial implementation part and filter by syntax tree
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/9fbadd4b-a632-4127-9b12-2bad0f80873f
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 21:12

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the loca...

Fixed in 351839b. The root cause was that with Analyze | ReportDiagnostics, Roslyn's RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances. Added a method.PartialDefinitionPart is not null guard at the top of AnalyzeMethod in both analyzers to skip the implementation part. Also added a SyntaxTree filter when reporting diagnostics to ensure only user-source locations are reported.

@jkoritzinsky
jkoritzinsky enabled auto-merge (squash) April 23, 2026 22:43
@jkoritzinsky
jkoritzinsky merged commit d754bd0 into mainApr 24, 2026
103 of 105 checks passed
@jkoritzinsky
jkoritzinsky deleted the copilot/fix-library-import-diagnostics-analyzer branch April 24, 2026 20:01
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

LibraryImportDiagnosticsAnalyzer fails to report SYSLIB1051 for StringBuilder when StringMarshalling is set, generating bad code

5 participants

@danmoseley@jkoritzinsky@jtschuster
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set - #126691

Merged
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer
Apr 24, 2026
Merged

Fix LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer missing diagnostics when StringMarshalling is set#126691
jkoritzinsky merged 12 commits into
mainfrom
copilot/fix-library-import-diagnostics-analyzer

Conversation

CopilotAI commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Description

LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer fail to report diagnostics (e.g. SYSLIB1051) for [LibraryImport] methods when StringMarshalling is set. This is a regression introduced in preview.3 when diagnostic reporting was moved from the generators to separate analyzer classes. Additionally, the generated inner [DllImport] stub omits CharSet = CharSet.Unicode when StringMarshalling.Utf16 is set, causing incorrect runtime marshalling of forwarded types (e.g. StringBuilder).

Root cause: The source generator marks its generated stub output with [GeneratedCode]. Roslyn's generated-code heuristics then also classify the user's partial method declaration as generated code. With ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None), both analyzers silently skipped all [LibraryImport] methods. Additionally, with Analyze | ReportDiagnostics enabled, RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances, which could produce duplicate diagnostics — one in user source and one in generated source. The generated implementation syntax (which has a body) also caused a spurious SYSLIB1050 (InvalidAttributedMethodSignature) because GetDiagnosticIfInvalidMethodForGeneration rejects methods with a body.

Reproduction:

// SYSLIB1051 should be reported but is NOT (with StringMarshalling.Utf16)[LibraryImport("kernel32.dll",StringMarshalling=StringMarshalling.Utf16)]internalstaticpartialintGetVolumeNameForVolumeMountPointW(stringvolumeMountPoint,[Out]StringBuildervolumeName,// No error — silent bad codegenintbufferLength);

Changes

  • LibraryImportDiagnosticsAnalyzer: Changed GeneratedCodeAnalysisFlags.NoneAnalyze | ReportDiagnostics so the analyzer runs on and reports diagnostics for methods whose partial declaration is classified as generated code. Added PartialDefinitionPart guard to skip the partial implementation part (avoiding duplicate diagnostics from RegisterSymbolAction firing for both parts). Added IsGeneratedByOurGenerator helper. When iterating DeclaringSyntaxReferences, the generated implementation syntax (body present + our [GeneratedCode]) is skipped to find the user's partial declaration. GetDiagnosticIfInvalidMethodForGeneration is skipped (via skipInvalidMethodCheck flag) when our generator has already produced an implementation — since the method must have been valid for the generator to run — but CalculateDiagnostics always runs to catch other issues. Diagnostics are filtered by SyntaxTree to only report those located in the user's (non-generated) source.
  • DownlevelLibraryImportDiagnosticsAnalyzer: Applied the same GeneratedCodeAnalysisFlags.Analyze | ReportDiagnostics fix, the same PartialDefinitionPart guard, the same selective SYSLIB1050 guard, and the same SyntaxTree diagnostic location filter.
  • LibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Now forwards CharSet = CharSet.Unicode to the inner [DllImport] when StringMarshalling.Utf16 is set, using the shared CreateEnumExpressionSyntax helper (extracted to class-level) to ensure consistency with the forwarder stub.
  • DownlevelLibraryImportGenerator.CreateTargetDllImportAsLocalStatement: Applied the same CharSet = CharSet.Unicode forwarding fix using the shared CreateEnumExpressionSyntax helper (also extracted to class-level in the downlevel generator).
  • DownlevelLibraryImportGenerator.CreateForwarderDllImport: Updated to use the shared class-level CreateEnumExpressionSyntax helper.
  • Tests (Diagnostics.cs): Added StringBuilderNotSupported_ReportsDiagnostic and StringBuilderNotSupported_WithStringParam_ReportsDiagnostic regression tests covering StringBuilder with and without StringMarshalling variants.
  • Tests (Compiles.cs): Added ForwardedTypesWithStringMarshalling_InnerDllImportHasCharSet test verifying that the inner [DllImport] has CharSet.Unicode when StringMarshalling.Utf16 is set and no CharSet argument when other values are used. Fixed test assertion to use EndsWith to handle the global:: qualified name emitted by the generator. Fixed the DllImport attribute predicate to use EndsWith("DllImportAttribute") instead of Contains("DllImport") to avoid false matches on DefaultDllImportSearchPathsAttribute.

Testing

  • Both generator projects (LibraryImportGenerator, DownlevelLibraryImportGenerator) build successfully with zero errors and warnings.
  • All 709 unit tests pass (1 skipped, 0 failed), including previously-failing AddDisableRuntimeMarshallingAttributeFixerTests which validated the duplicate diagnostic fix.
  • Regression tests added for the diagnostic and code-generation fixes.
  • Code review and CodeQL scan passed with no issues.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @steveisok, @tommcdon, @dotnet/dotnet-diag
See info in area-owners.md if you want to be subscribed.

CopilotAI changed the title [WIP] Fix LibraryImportDiagnosticsAnalyzer to report SYSLIB1051 for StringBuilderFix LibraryImportDiagnosticsAnalyzer missing SYSLIB1051 for StringBuilder when StringMarshalling is setApr 9, 2026
CopilotAI requested a review from jkoritzinskyApril 9, 2026 07:03
@danmoseley

Copy link
Copy Markdown
Contributor

It seems Copilot could not make a fix before timing out.

Add tests verifying that SYSLIB1051 (ParameterTypeNotSupported) is
correctly reported for StringBuilder parameters when StringMarshalling
is set to Utf16 or Utf8, both as standalone parameters and alongside
string parameters with [Out] attribute.
Regression test for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 9, 2026 17:39
@jkoritzinsky

Copy link
Copy Markdown
Member

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR targets a regression in LibraryImportDiagnosticsAnalyzer where SYSLIB1051 is not reported for StringBuilder parameters when LibraryImportAttribute.StringMarshalling is specified, leading to silently incorrect forwarder stub generation.

Changes:

  • Added analyzer unit tests asserting SYSLIB1051 (ParameterTypeNotSupported) is reported for StringBuilder parameters with StringMarshalling set (Utf16/Utf8) and without it.
  • Added a reproduction-style test case for string + [Out] StringBuilder parameter combinations.

@github-actions

This comment has been minimized.

@danmoseley

Copy link
Copy Markdown
Contributor

@danmoseley I had copilot crank locally for quite a while and it couldn't figure it out but it did add some regression tests that should cover your scenario and pass without any product changes. Can you get me a complog of your build that didn't report the diagnostic?

Is it possible that you have RunAnalyzers set to false or that VS set it to false for one run?

@jkoritzinsky I updated the repro (top post #126687) to include a global.json -- can you still not repro with that?

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@jkoritzinsky also added the install command for preview 3. Using preview 2, it doesn't repro. I verified this by hand again, and it does repro for me with global.json pointing to 11.0.100-preview.3.26170.106

C:\temp\repro>type global.json | find /i "ver""version": "11.0.100-preview.3.26170.106",
C:\temp\repro>type program.cs
using System;
using System.Runtime.InteropServices;
using System.Text;
Console.WriteLine("If you see this, SYSLIB1051 was NOT reported.");
static partial class NativeMethods
{
[LibraryImport("kernel32.dll", StringMarshalling = StringMarshalling.Utf16)]
internal static partial int GetVolumeNameForVolumeMountPointW(
string volumeMountPoint,
[Out] StringBuilder volumeName,
int bufferLength);
}
C:\temp\repro>dotnet build
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (0.1s) → bin\Debug\net11.0\doit.dll
Build succeeded in 0.9s

and

C:\temp\repro>set emitcompilergeneratedfiles=true
C:\temp\repro>dotnet build --no-incremental
Restore complete (0.3s)
info NETSDK1057: You are using a preview version of .NET. See: https://aka.ms/dotnet-support-policy
doit net11.0 succeeded (1.5s) → bin\Debug\net11.0\doit.dll
Build succeeded in 2.3s
C:\temp\repro>type obj\Debug\net11.0\generated\Microsoft.Interop.LibraryImportGenerator\Microsoft.Interop.LibraryImportGenerator\LibraryImports.g.cs
// <auto-generated/>
static unsafe partial class NativeMethods
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Interop.LibraryImportGenerator", "11.0.14.17106")]
[global::System.Runtime.CompilerServices.SkipLocalsInitAttribute]
internal static partial int GetVolumeNameForVolumeMountPointW(string volumeMountPoint, global::System.Text.StringBuilder volumeName, int bufferLength)
{
int __retVal;
// Pin - Pin data in preparation for calling the P/Invoke.
fixed (void* __volumeMountPoint_native = &global::System.Runtime.InteropServices.Marshalling.Utf16StringMarshaller.GetPinnableReference(volumeMountPoint))
{
__retVal = __PInvoke((ushort*)__volumeMountPoint_native, volumeName, bufferLength);
}
return __retVal;
// Local P/Invoke
[global::System.Runtime.InteropServices.DllImportAttribute("kernel32.dll", EntryPoint = "GetVolumeNameForVolumeMountPointW", ExactSpelling = true)]
static extern unsafe int __PInvoke(ushort* __volumeMountPoint_native, [System.Runtime.InteropServices.OutAttribute] global::System.Text.StringBuilder volumeName, int __bufferLength_native);
}
}

@danmoseley

danmoseley commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

The idea is that you get an IDE error, then you either fix the error or copy the generated code into your source and remove the LibraryImportAttribute. Then the error goes away as you aren't using the generator any more.

If I understand right then there are two bugs -- the error no longer firing, and the generated code not working (as I discovered in my original PR)

the issue with the generated code pasted above (I think) is that it doesn't have CharSet = CharSet.Unicode so I guess it defaulted to CharSet.None for volumeName which treats it as Ansi on Windows. (Aside, maybe generated code should always be explicit about CharSet anyway).

…16 is set
When LibraryImportGenerator creates a non-forwarder stub with an inner
local DllImport function, it did not forward StringMarshalling.Utf16 as
CharSet=Unicode. This caused any types forwarded to the runtime
marshaller (e.g. StringBuilder) to default to Ansi encoding, producing
incorrect results for Unicode APIs.
Add CharSet=CharSet.Unicode to the inner DllImport attribute when
StringMarshalling.Utf16 is specified on the LibraryImport, matching the
existing behavior in CreateForwarderDllImport.
Fix for #126687
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

…plementation from our generator
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/408e36a5-d92b-4944-8b37-13c9d31754fd
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI review requested due to automatic review settings April 20, 2026 21:30
auto-merge was automatically disabled April 20, 2026 21:30

Head branch was pushed to by a user without write access

CopilotAI removed the request for review from CopilotApril 20, 2026 21:30

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot please update the diagnostics analyzer to skip the SYSLIB1050 diagnostic when a LibraryImport method has an implementation marked with our GeneratedCodeAttribute pointing to our generator.

Done in commit b2bacee. Both LibraryImportDiagnosticsAnalyzer and DownlevelLibraryImportDiagnosticsAnalyzer now have an IsGeneratedByOurGenerator helper that checks for [GeneratedCode] with a tool name starting with "Microsoft.Interop.LibraryImportGenerator". When iterating DeclaringSyntaxReferences, if the current syntax has a body AND the method has our generator's [GeneratedCode] attribute, we skip that syntax reference (the generated implementation) and continue to find the user's partial declaration to analyze.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

…enerated implementation; always run CalculateDiagnostics
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/301361db-1d3f-4105-871a-90438d6a01df
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 20, 2026 21:42

CopilotAI commented Apr 20, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot I want you to only skip the GetDiagnosticIfInvalidMethodForGeneration call if there's generated source by our generators. The rest of the diagnostics analysis must run always.

Done in commit d4687fc. Both analyzers now pass isGeneratedByOurGenerator as a skipInvalidMethodCheck parameter to AnalyzeMethodSyntax. When true, GetDiagnosticIfInvalidMethodForGeneration is skipped (since we know the method was valid — the generator already ran on it), but CalculateDiagnostics always runs regardless.

@jkoritzinsky

Copy link
Copy Markdown
Member

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the location in the non-generated source.

…tial implementation part and filter by syntax tree
Agent-Logs-Url: https://github.com/dotnet/runtime/sessions/9fbadd4b-a632-4127-9b12-2bad0f80873f
Co-authored-by: jkoritzinsky <1571408+jkoritzinsky@users.noreply.github.com>
CopilotAI requested review from Copilot and removed request for CopilotApril 23, 2026 21:12

CopilotAI commented Apr 23, 2026

Copy link
Copy Markdown
ContributorAuthor

@copilot the analyzer is now returning two diagnostics, one in the generated code and one in the non-generated code. Please update the analyzer to only report diagnostics with the loca...

Fixed in 351839b. The root cause was that with Analyze | ReportDiagnostics, Roslyn's RegisterSymbolAction fires for both the partial definition and partial implementation as separate IMethodSymbol instances. Added a method.PartialDefinitionPart is not null guard at the top of AnalyzeMethod in both analyzers to skip the implementation part. Also added a SyntaxTree filter when reporting diagnostics to ensure only user-source locations are reported.

@jkoritzinsky
jkoritzinsky enabled auto-merge (squash) April 23, 2026 22:43
@jkoritzinsky
jkoritzinsky merged commit d754bd0 into mainApr 24, 2026
103 of 105 checks passed
@jkoritzinsky
jkoritzinsky deleted the copilot/fix-library-import-diagnostics-analyzer branch April 24, 2026 20:01
@github-actionsgithub-actionsBot locked and limited conversation to collaborators May 25, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

LibraryImportDiagnosticsAnalyzer fails to report SYSLIB1051 for StringBuilder when StringMarshalling is set, generating bad code

5 participants

@danmoseley@jkoritzinsky@jtschuster