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 @$ -->
<Rule Id="BHI1004" Action="Error" />
<!-- Default branch of switch expression should throw InvalidOperationException/SwitchExpressionException or not throw -->
<Rule Id="BHI1005" Action="Error" />
</Rules>
<Rules AnalyzerId="DocumentationAnalyzers" RuleNamespace="DocumentationAnalyzers.StyleRules">
<!-- Place text in paragraphs -->

View File

@ -1,5 +1,6 @@
namespace BizHawk.Analyzers;
using System;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
@ -10,6 +11,10 @@ using Microsoft.CodeAnalysis.Diagnostics;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
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(
id: "BHI1004",
title: "Verbatim interpolated strings should begin $@, not @$",
@ -42,11 +47,20 @@ public class HawkSourceAnalyzer : DiagnosticAnalyzer
defaultSeverity: DiagnosticSeverity.Error,
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(
DiagInterpStringIsDollarAt,
DiagNoAnonClasses,
DiagNoAnonDelegates,
DiagNoQueryExpression);
DiagNoQueryExpression,
DiagSwitchShouldThrowIOE);
public override void Initialize(AnalysisContext context)
{
@ -69,11 +83,37 @@ public class HawkSourceAnalyzer : DiagnosticAnalyzer
case QueryExpressionSyntax:
snac.ReportDiagnostic(Diagnostic.Create(DiagNoQueryExpression, snac.Node.GetLocation()));
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.AnonymousMethodExpression,
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.Min => BlendOperation.Minimum,
BlendEquationMode.FuncReverseSubtract => BlendOperation.ReverseSubtract,
_ => throw new ArgumentOutOfRangeException()
_ => throw new InvalidOperationException()
};
}
@ -284,7 +284,7 @@ namespace BizHawk.Bizware.DirectX
BlendingFactorSrc.Src1Color => throw new NotSupportedException(),
BlendingFactorSrc.OneMinusSrc1Color => throw new NotSupportedException(),
BlendingFactorSrc.OneMinusSrc1Alpha => throw new NotSupportedException(),
_ => throw new ArgumentOutOfRangeException()
_ => throw new InvalidOperationException()
};
public void SetBlendState(IBlendState rsBlend)

View File

@ -65,7 +65,7 @@ namespace BizHawk.Client.Common
VSystemID.Raw.SGB => CoreSystem.SuperGameBoy,
VSystemID.Raw.UZE => CoreSystem.UzeBox,
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.AmstradCPC => VSystemID.Raw.AmstradCPC,
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>
public static class DisplaySurfaceIDParser
{
#pragma warning disable BHI1005 // switching on string, possibly from user input, ArgumentException is correct here
[return: NotNullIfNotNull("str")]
public static DisplaySurfaceID? Parse(string? str) => str?.ToLowerInvariant() switch
{
@ -24,12 +25,13 @@ namespace BizHawk.Client.Common
"native" => DisplaySurfaceID.Client,
_ => 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
{
DisplaySurfaceID.EmuCore => "emucore",
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.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);

View File

@ -43,7 +43,7 @@ namespace BizHawk.Client.EmuHawk
EMsgBoxIcon.Question => MessageBoxIcon.Question,
EMsgBoxIcon.Warning => MessageBoxIcon.Warning,
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(),
_ when OSTailoredCode.IsUnixHost => new OpenTKInputAdapter(),
EHostInputMethod.DirectInput => new DirectInputAdapter(),
_ => throw new Exception()
_ => throw new InvalidOperationException()
};
Console.WriteLine($"Using {Adapter.Desc} for host input (keyboard + gamepads)");
Adapter.UpdateConfig(_currentConfig);

View File

@ -774,7 +774,7 @@ namespace BizHawk.Client.EmuHawk
{
WatchSize.DWord => WatchSize.Word,
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 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.macOS => new UnixMonoLLManager(),
DistinctOS.Windows => new WindowsLLManager(),
_ => throw new ArgumentOutOfRangeException()
_ => throw new InvalidOperationException()
});
public static ILinkedLibManager LinkedLibManager => _LinkedLibManager.Value;

View File

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

View File

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

View File

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

View File

@ -68,7 +68,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES
ScreenSize.ABAB_64x32 => new Dimensions(2, 1),
ScreenSize.AABB_32x64 => new Dimensions(1, 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),
"justifier" => NymaKonamiJustifier(num),
"dancepad" => NymaDancePad(num),
_ => throw new NotSupportedException($"device {device} is not supported"),
_ => throw new InvalidOperationException($"device {device} is not supported")
};
}