Add Analyzer to enforce exception type for default switch branches

This commit is contained in:
YoshiRulz 2022-07-14 22:49:04 +10:00
parent 90fe31529e
commit 4f98733c29
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
16 changed files with 62 additions and 17 deletions

View File

@ -12,6 +12,9 @@
<!-- Verbatim interpolated strings should begin $@, not @$ --> <!-- Verbatim interpolated strings should begin $@, not @$ -->
<Rule Id="BHI1004" Action="Error" /> <Rule Id="BHI1004" Action="Error" />
<!-- Default branch of switch expression should throw InvalidOperationException/SwitchExpressionException or not throw -->
<Rule Id="BHI1005" Action="Error" />
</Rules> </Rules>
<Rules AnalyzerId="DocumentationAnalyzers" RuleNamespace="DocumentationAnalyzers.StyleRules"> <Rules AnalyzerId="DocumentationAnalyzers" RuleNamespace="DocumentationAnalyzers.StyleRules">
<!-- Place text in paragraphs --> <!-- Place text in paragraphs -->

View File

@ -1,5 +1,6 @@
namespace BizHawk.Analyzers; namespace BizHawk.Analyzers;
using System;
using System.Collections.Immutable; using System.Collections.Immutable;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
@ -10,6 +11,10 @@ using Microsoft.CodeAnalysis.Diagnostics;
[DiagnosticAnalyzer(LanguageNames.CSharp)] [DiagnosticAnalyzer(LanguageNames.CSharp)]
public class HawkSourceAnalyzer : DiagnosticAnalyzer public class HawkSourceAnalyzer : DiagnosticAnalyzer
{ {
private const string ERR_MSG_SWITCH_THROWS_UNKNOWN = "Indeterminable exception type in default switch branch, should be InvalidOperationException/SwitchExpressionException";
private const string ERR_MSG_SWITCH_THROWS_WRONG_TYPE = "Incorrect exception type in default switch branch, should be InvalidOperationException/SwitchExpressionException";
private static readonly DiagnosticDescriptor DiagInterpStringIsDollarAt = new( private static readonly DiagnosticDescriptor DiagInterpStringIsDollarAt = new(
id: "BHI1004", id: "BHI1004",
title: "Verbatim interpolated strings should begin $@, not @$", title: "Verbatim interpolated strings should begin $@, not @$",
@ -42,11 +47,20 @@ public class HawkSourceAnalyzer : DiagnosticAnalyzer
defaultSeverity: DiagnosticSeverity.Error, defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true); isEnabledByDefault: true);
private static readonly DiagnosticDescriptor DiagSwitchShouldThrowIOE = new(
id: "BHI1005",
title: "Default branch of switch expression should throw InvalidOperationException/SwitchExpressionException or not throw",
messageFormat: "{0}",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create( public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
DiagInterpStringIsDollarAt, DiagInterpStringIsDollarAt,
DiagNoAnonClasses, DiagNoAnonClasses,
DiagNoAnonDelegates, DiagNoAnonDelegates,
DiagNoQueryExpression); DiagNoQueryExpression,
DiagSwitchShouldThrowIOE);
public override void Initialize(AnalysisContext context) public override void Initialize(AnalysisContext context)
{ {
@ -69,11 +83,37 @@ public class HawkSourceAnalyzer : DiagnosticAnalyzer
case QueryExpressionSyntax: case QueryExpressionSyntax:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoQueryExpression, snac.Node.GetLocation())); snac.ReportDiagnostic(Diagnostic.Create(DiagNoQueryExpression, snac.Node.GetLocation()));
break; break;
case SwitchExpressionArmSyntax { WhenClause: null, Pattern: DiscardPatternSyntax, Expression: ThrowExpressionSyntax tes }:
if (tes.Expression is ObjectCreationExpressionSyntax oces)
{
// to resolve edge-cases involving aliases, you're supposed to use `snac.SemanticModel.GetTypeInfo(oces.Type).ConvertedType?.Name`, but I couldn't get it to work
if (((oces.Type as IdentifierNameSyntax)?.Identifier)?.ToString() is "SwitchExpressionException" or nameof(InvalidOperationException))
{
// correct usage, do not flag
}
else
{
snac.ReportDiagnostic(Diagnostic.Create(DiagSwitchShouldThrowIOE, tes.GetLocation(), ERR_MSG_SWITCH_THROWS_WRONG_TYPE));
}
}
else
{
// code reads `throw <something weird>`
snac.ReportDiagnostic(Diagnostic.Create(
DiagSwitchShouldThrowIOE,
tes.GetLocation(),
DiagnosticSeverity.Warning,
additionalLocations: null,
properties: null,
ERR_MSG_SWITCH_THROWS_UNKNOWN));
}
break;
} }
}, },
SyntaxKind.AnonymousObjectCreationExpression, SyntaxKind.AnonymousObjectCreationExpression,
SyntaxKind.AnonymousMethodExpression, SyntaxKind.AnonymousMethodExpression,
SyntaxKind.InterpolatedStringExpression, SyntaxKind.InterpolatedStringExpression,
SyntaxKind.QueryExpression); SyntaxKind.QueryExpression,
SyntaxKind.SwitchExpressionArm);
} }
} }

Binary file not shown.

View File

@ -257,7 +257,7 @@ namespace BizHawk.Bizware.DirectX
BlendEquationMode.Max => BlendOperation.Maximum, BlendEquationMode.Max => BlendOperation.Maximum,
BlendEquationMode.Min => BlendOperation.Minimum, BlendEquationMode.Min => BlendOperation.Minimum,
BlendEquationMode.FuncReverseSubtract => BlendOperation.ReverseSubtract, BlendEquationMode.FuncReverseSubtract => BlendOperation.ReverseSubtract,
_ => throw new ArgumentOutOfRangeException() _ => throw new InvalidOperationException()
}; };
} }
@ -284,7 +284,7 @@ namespace BizHawk.Bizware.DirectX
BlendingFactorSrc.Src1Color => throw new NotSupportedException(), BlendingFactorSrc.Src1Color => throw new NotSupportedException(),
BlendingFactorSrc.OneMinusSrc1Color => throw new NotSupportedException(), BlendingFactorSrc.OneMinusSrc1Color => throw new NotSupportedException(),
BlendingFactorSrc.OneMinusSrc1Alpha => throw new NotSupportedException(), BlendingFactorSrc.OneMinusSrc1Alpha => throw new NotSupportedException(),
_ => throw new ArgumentOutOfRangeException() _ => throw new InvalidOperationException()
}; };
public void SetBlendState(IBlendState rsBlend) public void SetBlendState(IBlendState rsBlend)

View File

@ -65,7 +65,7 @@ namespace BizHawk.Client.Common
VSystemID.Raw.SGB => CoreSystem.SuperGameBoy, VSystemID.Raw.SGB => CoreSystem.SuperGameBoy,
VSystemID.Raw.UZE => CoreSystem.UzeBox, VSystemID.Raw.UZE => CoreSystem.UzeBox,
VSystemID.Raw.PCFX => CoreSystem.PcFx, VSystemID.Raw.PCFX => CoreSystem.PcFx,
_ => throw new IndexOutOfRangeException($"{value} is missing in convert list") _ => throw new InvalidOperationException($"{value} is missing in convert list")
}; };
} }
@ -131,7 +131,7 @@ namespace BizHawk.Client.Common
CoreSystem.ZXSpectrum => VSystemID.Raw.ZXSpectrum, CoreSystem.ZXSpectrum => VSystemID.Raw.ZXSpectrum,
CoreSystem.AmstradCPC => VSystemID.Raw.AmstradCPC, CoreSystem.AmstradCPC => VSystemID.Raw.AmstradCPC,
CoreSystem.Odyssey2 => VSystemID.Raw.O2, CoreSystem.Odyssey2 => VSystemID.Raw.O2,
_ => throw new IndexOutOfRangeException($"{value} is missing in convert list") _ => throw new InvalidOperationException($"{value} is missing in convert list")
}; };
} }

View File

@ -14,6 +14,7 @@ namespace BizHawk.Client.Common
/// <remarks>should probably centralise these enum extensions and not-extensions somewhere... --yoshi</remarks> /// <remarks>should probably centralise these enum extensions and not-extensions somewhere... --yoshi</remarks>
public static class DisplaySurfaceIDParser public static class DisplaySurfaceIDParser
{ {
#pragma warning disable BHI1005 // switching on string, possibly from user input, ArgumentException is correct here
[return: NotNullIfNotNull("str")] [return: NotNullIfNotNull("str")]
public static DisplaySurfaceID? Parse(string? str) => str?.ToLowerInvariant() switch public static DisplaySurfaceID? Parse(string? str) => str?.ToLowerInvariant() switch
{ {
@ -24,12 +25,13 @@ namespace BizHawk.Client.Common
"native" => DisplaySurfaceID.Client, "native" => DisplaySurfaceID.Client,
_ => throw new ArgumentException(message: $"{str} is not the name of a display surface", paramName: nameof(str)) _ => throw new ArgumentException(message: $"{str} is not the name of a display surface", paramName: nameof(str))
}; };
#pragma warning restore BHI1005
public static string GetName(this DisplaySurfaceID surfaceID) => surfaceID switch public static string GetName(this DisplaySurfaceID surfaceID) => surfaceID switch
{ {
DisplaySurfaceID.EmuCore => "emucore", DisplaySurfaceID.EmuCore => "emucore",
DisplaySurfaceID.Client => "client", DisplaySurfaceID.Client => "client",
_ => throw new ArgumentException(message: "not a valid enum member", paramName: nameof(surfaceID)) _ => throw new InvalidOperationException()
}; };
} }
} }

View File

@ -1023,7 +1023,7 @@ namespace BizHawk.Client.Common
{ {
DisplaySurfaceID.EmuCore => (GameExtraPadding.Left + _currEmuWidth + GameExtraPadding.Right, GameExtraPadding.Top + _currEmuHeight + GameExtraPadding.Bottom), DisplaySurfaceID.EmuCore => (GameExtraPadding.Left + _currEmuWidth + GameExtraPadding.Right, GameExtraPadding.Top + _currEmuHeight + GameExtraPadding.Bottom),
DisplaySurfaceID.Client => (currNativeWidth, currNativeHeight), DisplaySurfaceID.Client => (currNativeWidth, currNativeHeight),
_ => throw new ArgumentException(message: "not a valid enum member", paramName: nameof(surfaceID)) _ => throw new InvalidOperationException()
}; };
IDisplaySurface ret = sdss.AllocateSurface(width, height, clear); IDisplaySurface ret = sdss.AllocateSurface(width, height, clear);

View File

@ -43,7 +43,7 @@ namespace BizHawk.Client.EmuHawk
EMsgBoxIcon.Question => MessageBoxIcon.Question, EMsgBoxIcon.Question => MessageBoxIcon.Question,
EMsgBoxIcon.Warning => MessageBoxIcon.Warning, EMsgBoxIcon.Warning => MessageBoxIcon.Warning,
EMsgBoxIcon.Info => MessageBoxIcon.Information, EMsgBoxIcon.Info => MessageBoxIcon.Information,
_ => throw new ArgumentException(message: "not a valid enum member", paramName: nameof(icon)) _ => throw new InvalidOperationException()
}); });
} }
} }

View File

@ -51,7 +51,7 @@ namespace BizHawk.Client.EmuHawk
EHostInputMethod.OpenTK => new OpenTKInputAdapter(), EHostInputMethod.OpenTK => new OpenTKInputAdapter(),
_ when OSTailoredCode.IsUnixHost => new OpenTKInputAdapter(), _ when OSTailoredCode.IsUnixHost => new OpenTKInputAdapter(),
EHostInputMethod.DirectInput => new DirectInputAdapter(), EHostInputMethod.DirectInput => new DirectInputAdapter(),
_ => throw new Exception() _ => throw new InvalidOperationException()
}; };
Console.WriteLine($"Using {Adapter.Desc} for host input (keyboard + gamepads)"); Console.WriteLine($"Using {Adapter.Desc} for host input (keyboard + gamepads)");
Adapter.UpdateConfig(_currentConfig); Adapter.UpdateConfig(_currentConfig);

View File

@ -774,7 +774,7 @@ namespace BizHawk.Client.EmuHawk
{ {
WatchSize.DWord => WatchSize.Word, WatchSize.DWord => WatchSize.Word,
WatchSize.Word => WatchSize.Byte, WatchSize.Word => WatchSize.Byte,
_ => throw new Exception() _ => throw new InvalidOperationException()
}; };
var a = Watch.GenerateWatch(ab.Domain, ab.Address, newSize, ab.Type, ab.BigEndian, ab.Notes); var a = Watch.GenerateWatch(ab.Domain, ab.Address, newSize, ab.Type, ab.BigEndian, ab.Notes);
var b = Watch.GenerateWatch(ab.Domain, ab.Address + (int) newSize, newSize, ab.Type, ab.BigEndian, ab.Notes); var b = Watch.GenerateWatch(ab.Domain, ab.Address + (int) newSize, newSize, ab.Type, ab.BigEndian, ab.Notes);

View File

@ -72,7 +72,7 @@ namespace BizHawk.Common
DistinctOS.Linux => new UnixMonoLLManager(), DistinctOS.Linux => new UnixMonoLLManager(),
DistinctOS.macOS => new UnixMonoLLManager(), DistinctOS.macOS => new UnixMonoLLManager(),
DistinctOS.Windows => new WindowsLLManager(), DistinctOS.Windows => new WindowsLLManager(),
_ => throw new ArgumentOutOfRangeException() _ => throw new InvalidOperationException()
}); });
public static ILinkedLibManager LinkedLibManager => _LinkedLibManager.Value; public static ILinkedLibManager LinkedLibManager => _LinkedLibManager.Value;

View File

@ -152,7 +152,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
{ {
0x20 or 0xfc => 3, 0x20 or 0xfc => 3,
0x22 => 4, 0x22 => 4,
_ => throw new ArgumentOutOfRangeException() _ => throw new InvalidOperationException()
}; };
} }
} }

View File

@ -214,7 +214,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy
ColorType.vbabgbold => OldVBAColor, ColorType.vbabgbold => OldVBAColor,
ColorType.gba => GBAColor, ColorType.gba => GBAColor,
ColorType.libretrogbc => LibretroGBCColor, ColorType.libretrogbc => LibretroGBCColor,
_ => throw new ArgumentOutOfRangeException(nameof(c)), _ => throw new InvalidOperationException()
}; };
int i = 0; int i = 0;

View File

@ -37,7 +37,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
"ARM v5 (Thumb)" => LibMelonDS.TraceMask.ARM9_THUMB, "ARM v5 (Thumb)" => LibMelonDS.TraceMask.ARM9_THUMB,
"ARM v4" => LibMelonDS.TraceMask.ARM7_ARM, "ARM v4" => LibMelonDS.TraceMask.ARM7_ARM,
"ARM v4 (Thumb)" => LibMelonDS.TraceMask.ARM7_THUMB, "ARM v4 (Thumb)" => LibMelonDS.TraceMask.ARM7_THUMB,
_ => throw new Exception("Invalid CPU mode?"), _ => throw new InvalidOperationException("Invalid CPU mode?")
}; };
if (Cpu.Length == 14) if (Cpu.Length == 14)

View File

@ -68,7 +68,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
ScreenSize.ABAB_64x32 => new Dimensions(2, 1), ScreenSize.ABAB_64x32 => new Dimensions(2, 1),
ScreenSize.AABB_32x64 => new Dimensions(1, 2), ScreenSize.AABB_32x64 => new Dimensions(1, 2),
ScreenSize.ABCD_64x64 => new Dimensions(2, 2), ScreenSize.ABCD_64x64 => new Dimensions(2, 2),
_ => throw new Exception() _ => throw new InvalidOperationException()
}; };
} }

View File

@ -63,7 +63,7 @@ namespace BizHawk.Emulation.Cores
"guncon" => NymaGunCon(num), "guncon" => NymaGunCon(num),
"justifier" => NymaKonamiJustifier(num), "justifier" => NymaKonamiJustifier(num),
"dancepad" => NymaDancePad(num), "dancepad" => NymaDancePad(num),
_ => throw new NotSupportedException($"device {device} is not supported"), _ => throw new InvalidOperationException($"device {device} is not supported")
}; };
} }