Skip to content

Commit 715a36a

Browse files
authored
[Microsoft.Android.Sdk.Analysis] Warn on missing activation ctors (#9447)
Fixes: #8410 Context: b3079db Context: https://learn.microsoft.com/dotnet/csharp/roslyn-sdk/tutorials/how-to-write-csharp-analyzer-code-fix Commit b3079db added a `Microsoft.Android.Sdk.Analysis.dll` assembly which contained a [`DiagnosticAnalyzer`][0] to suppress IDE0002 warnings. Extend that support to add a `CustomApplicationAnalyzer` which checks for the [`(IntPtr, JniHandleOwnership)`][1] constructor on [`Android.App.Application`][2] subclasses, as this constructor is currently required. If the constructor is missing: [Application] public class MyApp : Application { } then the app will crash during startup: System.NotSupportedException: Unable to activate instance of type MyApp from native handle 0x7ff1f3b468 (key_handle 0x466b26f). If we find an `Application` subclass that is missing the required constructor, we'll emit a DNAA0001 warning: warning DNAA0001: Application class 'MyApp' does not have an Activation Constructor. Additionally, provide a `CustomApplicationCodeFixProvider` which will inject the required constructor into the syntax tree. Finally, rename the previous `XAD0001` warning to `DNAS0001`. Our convention is as follows: * `DNA`: prefix for .NET for Android messages * `A` for *Analyzers*, `S` for *Suppressors* * 4 digit code. [0]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.diagnostics.diagnosticanalyzer?view=roslyn-dotnet-4.9.0 [1]: https://learn.microsoft.com/en-us/dotnet/api/android.app.application.-ctor?view=net-android-34.0#android-app-application-ctor(system-intptr-android-runtime-jnihandleownership) [2]: https://learn.microsoft.com/en-us/dotnet/api/android.app.application?view=net-android-34.0
1 parent 517bc21 commit 715a36a

10 files changed

+486
-88
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
; Shipped analyzer releases
2+
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
3+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
; Unshipped analyzer release
2+
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md
3+
4+
### New Rules
5+
6+
Rule ID | Category | Severity | Notes
7+
--------|----------|----------|-------
8+
DNAA0001 | Usage | Warning | CustomApplicationAnalyzer
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
using System.Collections.Immutable;
2+
using System.Linq;
3+
using Microsoft.Android.Sdk.Analysis;
4+
using Microsoft.CodeAnalysis;
5+
using Microsoft.CodeAnalysis.CSharp;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
using Microsoft.CodeAnalysis.Diagnostics;
8+
9+
[DiagnosticAnalyzer (LanguageNames.CSharp)]
10+
public class CustomApplicationAnalyzer : DiagnosticAnalyzer
11+
{
12+
private const string AndroidApplication = "Android.App.Application";
13+
public const string DiagnosticId = "DNAA0001";
14+
private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor (
15+
id: DiagnosticId,
16+
title: Resources.DNAA0001_Title,
17+
messageFormat: Resources.DNAA0001_MessageFormat,
18+
category: "Usage",
19+
defaultSeverity: DiagnosticSeverity.Warning,
20+
isEnabledByDefault: true
21+
);
22+
23+
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create (Rule);
24+
25+
public override void Initialize (AnalysisContext context)
26+
{
27+
context.ConfigureGeneratedCodeAnalysis (GeneratedCodeAnalysisFlags.None);
28+
context.EnableConcurrentExecution ();
29+
30+
// Register a syntax node action to analyze method declarations
31+
context.RegisterSyntaxNodeAction (AnalyzeClass, SyntaxKind.ClassDeclaration);
32+
}
33+
34+
private static void AnalyzeClass (SyntaxNodeAnalysisContext context)
35+
{
36+
var classDeclarationSyntax = context.Node as ClassDeclarationSyntax;
37+
if (classDeclarationSyntax == null)
38+
return;
39+
40+
var classSymbol = context.SemanticModel.GetDeclaredSymbol (classDeclarationSyntax) as INamedTypeSymbol;
41+
if (classSymbol == null)
42+
return;
43+
44+
if (!Utilities.IsDerivedFrom (classSymbol, AndroidApplication))
45+
return;
46+
47+
var constructors = classDeclarationSyntax.Members
48+
.OfType<ConstructorDeclarationSyntax> ();
49+
50+
bool foundActivationConstructor = false;
51+
foreach (var constructor in constructors) {
52+
var parameters = constructor.ParameterList.Parameters;
53+
if (parameters.Count != 2)
54+
continue;
55+
if (parameters [0].Type.ToString () != "IntPtr")
56+
continue;
57+
var ns = Utilities.GetNamespaceForParameterType (parameters [1], context.SemanticModel);
58+
var type = parameters [1].Type.ToString();
59+
var isJniHandle = (ns == "Android.Runtime") && (type == "JniHandleOwnership") || (type == "Android.Runtime.JniHandleOwnership");
60+
if (!isJniHandle)
61+
continue;
62+
foundActivationConstructor = true;
63+
}
64+
if (!foundActivationConstructor) {
65+
var diagnostic = Diagnostic.Create (Rule, classDeclarationSyntax.Identifier.GetLocation (), classDeclarationSyntax.Identifier.Text);
66+
context.ReportDiagnostic (diagnostic);
67+
}
68+
}
69+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System.Collections.Immutable;
2+
using System.Composition;
3+
using System.Linq;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Microsoft.CodeAnalysis;
7+
using Microsoft.CodeAnalysis.CodeActions;
8+
using Microsoft.CodeAnalysis.CodeFixes;
9+
using Microsoft.CodeAnalysis.CSharp;
10+
using Microsoft.CodeAnalysis.CSharp.Syntax;
11+
12+
[ExportCodeFixProvider (LanguageNames.CSharp, Name = nameof (CustomApplicationCodeFixProvider)), Shared]
13+
public class CustomApplicationCodeFixProvider : CodeFixProvider
14+
{
15+
private const string title = "Fix Activation Constructor";
16+
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create (CustomApplicationAnalyzer.DiagnosticId);
17+
18+
public sealed override FixAllProvider GetFixAllProvider ()
19+
{
20+
return WellKnownFixAllProviders.BatchFixer;
21+
}
22+
23+
public override async Task RegisterCodeFixesAsync (CodeFixContext context)
24+
{
25+
var root = await context.Document.GetSyntaxRootAsync (context.CancellationToken).ConfigureAwait (false);
26+
var diagnostic = context.Diagnostics.First ();
27+
var diagnosticSpan = diagnostic.Location.SourceSpan;
28+
var classDeclaration = root.FindToken (diagnosticSpan.Start).Parent.AncestorsAndSelf ()
29+
.OfType<ClassDeclarationSyntax> ().First ();
30+
context.RegisterCodeFix (CodeAction.Create (title, c =>
31+
InjectConstructorAsync (context.Document, classDeclaration, c), equivalenceKey: title), diagnostic);
32+
}
33+
34+
private async Task<Document> InjectConstructorAsync (Document document, ClassDeclarationSyntax classDeclaration, CancellationToken cancellationToken)
35+
{
36+
var constructor = CreateConstructorWithParameters (classDeclaration.Identifier);
37+
var newClassDeclaration = classDeclaration.AddMembers (constructor);
38+
var root = await document.GetSyntaxRootAsync (cancellationToken);
39+
var newRoot = root.ReplaceNode (classDeclaration, newClassDeclaration);
40+
return document.WithSyntaxRoot (newRoot);
41+
}
42+
43+
private ConstructorDeclarationSyntax CreateConstructorWithParameters (SyntaxToken identifier)
44+
{
45+
var parameters = SyntaxFactory.ParameterList (SyntaxFactory.SeparatedList (new [] {
46+
SyntaxFactory.Parameter (SyntaxFactory.Identifier ("javaReference"))
47+
.WithType (SyntaxFactory.ParseTypeName ("IntPtr")),
48+
SyntaxFactory.Parameter (SyntaxFactory.Identifier ("transfer"))
49+
.WithType (SyntaxFactory.ParseTypeName ("Android.Runtime.JniHandleOwnership"))
50+
}));
51+
var baseArguments = SyntaxFactory.ArgumentList (SyntaxFactory.SeparatedList (new [] {
52+
SyntaxFactory.Argument (SyntaxFactory.IdentifierName ("javaReference")),
53+
SyntaxFactory.Argument (SyntaxFactory.IdentifierName ("transfer"))
54+
}));
55+
var constructorInitializer = SyntaxFactory.ConstructorInitializer (SyntaxKind.BaseConstructorInitializer, baseArguments);
56+
var body = SyntaxFactory.Block ();
57+
var constructor = SyntaxFactory.ConstructorDeclaration (identifier)
58+
.WithModifiers (SyntaxFactory.TokenList (SyntaxFactory.Token (SyntaxKind.PublicKeyword)))
59+
.WithParameterList (parameters)
60+
.WithInitializer (constructorInitializer)
61+
.WithBody (body);
62+
return constructor;
63+
}
64+
}
Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
<Import Project="..\..\Configuration.props" />
3-
<PropertyGroup>
4-
<TargetFramework>netstandard2.0</TargetFramework>
5-
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
6-
<OutputPath>$(MicrosoftAndroidSdkAnalysisOutDir)</OutputPath>
7-
<IsRoslynComponent>true</IsRoslynComponent>
8-
<LangVersion>latest</LangVersion>
9-
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
10-
<AssemblyOriginatorKeyFile>..\..\product.snk</AssemblyOriginatorKeyFile>
11-
</PropertyGroup>
12-
<ItemGroup>
13-
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />
14-
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
15-
</ItemGroup>
16-
<ItemGroup>
17-
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
18-
</ItemGroup>
2+
<Import Project="..\..\Configuration.props" />
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
6+
<OutputPath>$(MicrosoftAndroidSdkAnalysisOutDir)</OutputPath>
7+
<IsRoslynComponent>true</IsRoslynComponent>
8+
<LangVersion>latest</LangVersion>
9+
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
10+
<AssemblyOriginatorKeyFile>..\..\product.snk</AssemblyOriginatorKeyFile>
11+
</PropertyGroup>
12+
<ItemGroup>
13+
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" PrivateAssets="all" />
14+
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" PrivateAssets="all" />
15+
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="4.11.0" PrivateAssets="all" />
16+
</ItemGroup>
17+
<ItemGroup>
18+
<Compile Update="Properties\Resources.Designer.cs">
19+
<DesignTime>True</DesignTime>
20+
<AutoGen>True</AutoGen>
21+
<DependentUpon>Resources.resx</DependentUpon>
22+
</Compile>
23+
</ItemGroup>
24+
<ItemGroup>
25+
<EmbeddedResource Update="Properties\Resources.resx">
26+
<Generator>PublicResXFileCodeGenerator</Generator>
27+
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
28+
</EmbeddedResource>
29+
</ItemGroup>
30+
<ItemGroup>
31+
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
32+
<AdditionalFiles Include="AnalyzerReleases.Unshipped.md" />
33+
</ItemGroup>
34+
<ItemGroup>
35+
<None Include="$(OutputPath)\$(AssemblyName).dll" Pack="true" PackagePath="analyzers/dotnet/cs" Visible="false" />
36+
</ItemGroup>
1937
</Project>

src/Microsoft.Android.Sdk.Analysis/Properties/Resources.Designer.cs

Lines changed: 85 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)