-
Notifications
You must be signed in to change notification settings - Fork 463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement DisableRuntimeMarshalling analyzer and code fixer #5822
Implement DisableRuntimeMarshalling analyzer and code fixer #5822
Conversation
I meant to open this as a draft PR since it's not totally ready yet. My mistake. |
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshalling.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshalling.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshalling.cs
Outdated
Show resolved
Hide resolved
int sizePassedInType = {|CA1421:Marshal.SizeOf(type)|}; | ||
int sizePassedInGeneric = {|CA1421:Marshal.SizeOf<T>()|}; | ||
int sizePassedInGenericUnmanaged = {|CA1421:Marshal.SizeOf<U>()|}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This tests whether we'll crash on accessing the first argument or not.
"SizeOf" => invocation.TargetMethod.IsGenericMethod || invocation.Arguments[0].Value is ITypeOfOperation { TypeOperand.IsUnmanagedType: true },
} | |
int nonGenericNoParameter = Marshal.SizeOf(); | |
} |
Consider also adding the test for StructureToPtr
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
Outdated
Show resolved
Hide resolved
else if (operation.Arguments[0].Value is ITypeOfOperation { TypeOperand.IsUnmanagedType: true } typeOf) | ||
{ | ||
addUnsafeToEnclosingMethod = true; | ||
editor.ReplaceNode(syntax, SyntaxFactory.SizeOfExpression(GetTypeOfTypeSyntax((TypeOfExpressionSyntax)typeOf.Syntax))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider handling trivia.
.../CSharp/Microsoft.NetCore.Analyzers/InteropServices/CSharpDisableRuntimeMarshalling.Fixer.cs
Outdated
Show resolved
Hide resolved
.../CSharp/Microsoft.NetCore.Analyzers/InteropServices/CSharpDisableRuntimeMarshalling.Fixer.cs
Outdated
Show resolved
Hide resolved
…llable PtrToStructure cases.
…CoreAnalyzersResources.resx Co-authored-by: Youssef Victor <youssefvictor00@gmail.com>
…eruntimemarshalling-analyzer
…tzinsky/roslyn-analyzers into disableruntimemarshalling-analyzer
@jkoritzinsky Is the PR ready for review after your latest commits? If not, can you please convert it to a draft? Thanks. |
Codecov Report
@@ Coverage Diff @@
## main #5822 +/- ##
=========================================
Coverage 96.02% 96.03%
=========================================
Files 1310 1318 +8
Lines 302825 304802 +1977
Branches 9572 9657 +85
=========================================
+ Hits 290799 292718 +1919
- Misses 9740 9768 +28
- Partials 2286 2316 +30 |
@mavasani this is ready for review. |
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshalling.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshalling.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshalling.cs
Outdated
Show resolved
Hide resolved
@mavasani any more feedback on this PR? |
.../CSharp/Microsoft.NetCore.Analyzers/InteropServices/CSharpDisableRuntimeMarshalling.Fixer.cs
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx
Outdated
Show resolved
Hide resolved
...zers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshallingTests.cs
Outdated
Show resolved
Hide resolved
...zers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshallingTests.cs
Outdated
Show resolved
Hide resolved
// The first run fixes all the fixable diagnostics. | ||
// Since there are still some (unfixable) diagnostics, the test infrastructure decides to run the fix-all provider again. | ||
// The second run doesn't do anything, since the remaining diagnostics are unfixable. | ||
// Setting NumberOfFixAllIterations to -2 specifies that the fix-all provider can be run up to 2 times as part of a test run. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the comment!
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshalling.cs
Outdated
Show resolved
Hide resolved
@@ -1382,6 +1382,7 @@ private static async Task VerifyCSCodeFixAsync(string source, string codeFix, bo | |||
|
|||
// Verify that there are some instances of the diagnostic that we can't fix. | |||
test.FixedState.MarkupHandling = MarkupMode.Allow; | |||
test.MarkupOptions = MarkupOptions.UseFirstDescriptor; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is good to know!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM. Have added minor comments to address. Feel free to merge after addressing/responding to them. Thanks.
if (document.Project.CompilationOptions is CSharpCompilationOptions { AllowUnsafe: false }) | ||
{ | ||
// We can't code fix if unsafe code isn't allowed. | ||
return await document.GetSyntaxRootAsync(fixAllContext.CancellationToken); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this redundant? This was already checked in RegisterCodeFixesAsync
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried turning this into an assert and it tripped on a few tests, so I guess it's not redundant.
...zers/UnitTests/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshallingTests.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshalling.cs
Outdated
Show resolved
Hide resolved
src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/InteropServices/DisableRuntimeMarshalling.cs
Show resolved
Hide resolved
{ | ||
IMethodSymbol method = (IMethodSymbol)context.Symbol; | ||
|
||
DllImportData? dllImportData = method.GetDllImportData(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised this didn't cause a crash due to:
Also surprised on roslyn's side there is an exception thrown in this code path instead of simply returning null. cc @333fred
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't go down this path for function pointers. We go from AnalyzeFunctionPointerCall
to AnalyzeMethodSignature
directly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FunctionPointerMethodSymbol
is a method symbol. Shouldn't we get callbacks for it from context.RegisterSymbolAction(AnalyzeMethod, SymbolKind.Method);
? then we meet method.GetDllImportData()
unconditionally. I know this is likely not happening, otherwise a test would have crashed. But I don't yet see the reason
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we don't get callbacks for it for the same reason that we don't get callbacks for SymbolKind.Method
for local functions (which I just added code to handle in my most recent commit). It seems that symbols that are introduced due to an IOperation
are exposed through that operation in RegisterOperationAction
instead of through RegisterSymbolAction
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know! Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to report a diagnostic for properties?
e.g:
public static extern string P { [DllImport("abc")] get; }
I don't know if there will already be a diagnostic or not.
I didn't know that was even valid... Yes, we should add the diagnostic there too. |
@jkoritzinsky Consider also: using System;
using System.Runtime.InteropServices;
public class C
{
[DllImport("abc")]
public static extern int operator +(C a, C b);
[method: DllImport("abc")]
public extern static event Action G;
} Note: those edge cases are being stolen from Roslyn tests 😄 |
This is so much sadness right now 😢 The way the event test case compiles down also makes me sad, but I guess I should add tests to validate behavior. |
@mavasani I don't have permissions to merge, so can you hit the button for me? |
Fixes dotnet/runtime#63714