Add Analyzer rules to prohibit `this.GetType()`

seriously Meziantou is a godsend
This commit is contained in:
YoshiRulz 2022-07-19 03:45:38 +10:00
parent dcc8957be3
commit f3f90a4cd5
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
22 changed files with 131 additions and 53 deletions

View File

@ -16,6 +16,12 @@
<!-- Default branch of switch expression should throw InvalidOperationException/SwitchExpressionException or not throw -->
<Rule Id="BHI1005" Action="Error" />
<!-- Don't call this.GetType() in sealed type, use typeof operator -->
<Rule Id="BHI1100" Action="Error" />
<!-- Don't call this.GetType(), use typeof operator (or replace subtype check with better encapsulation) -->
<Rule Id="BHI1101" Action="Error" />
<!-- Throw NotImplementedException from methods/props marked [FeatureNotImplemented] -->
<Rule Id="BHI3300" Action="Error" />
</Rules>

View File

@ -1,10 +1,18 @@
namespace BizHawk.Analyzers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
internal static class RoslynUtils
{
public static SyntaxNode? EnclosingTypeDeclarationSyntax(this CSharpSyntaxNode node)
{
var parent = node.Parent;
while (parent is not (null or TypeDeclarationSyntax)) parent = parent.Parent;
return parent;
}
private static ITypeSymbol? GetThrownExceptionType(this SemanticModel model, ExpressionSyntax exprSyn)
=> exprSyn is ObjectCreationExpressionSyntax
? model.GetTypeInfo(exprSyn).Type
@ -16,6 +24,6 @@ internal static class RoslynUtils
public static ITypeSymbol? GetThrownExceptionType(this SemanticModel model, ThrowStatementSyntax tss)
=> model.GetThrownExceptionType(tss.Expression!);
public static bool Matches(this ITypeSymbol expected, ITypeSymbol? actual)
public static bool Matches(this ISymbol expected, ISymbol? actual)
=> SymbolEqualityComparer.Default.Equals(expected, actual);
}

View File

@ -0,0 +1,50 @@
namespace BizHawk.Analyzers;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UseTypeofOperatorAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor DiagNoGetTypeOnThis = new(
id: "BHI1101",
title: "Don't call this.GetType(), use typeof operator (or replace subtype check with better encapsulation)",
messageFormat: "Replace this.GetType() with typeof({0}) (or replace subtype check with better encapsulation)",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true);
private static readonly DiagnosticDescriptor DiagNoGetTypeOnThisSealed = new(
id: "BHI1100",
title: "Don't call this.GetType() in sealed type, use typeof operator",
messageFormat: "Replace this.GetType() with typeof({0})",
category: "Usage",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagNoGetTypeOnThisSealed, DiagNoGetTypeOnThis);
public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
ISymbol? objectDotGetTypeSym = null;
context.RegisterOperationAction(
oac =>
{
var operation = (IInvocationOperation) oac.Operation;
if (operation.IsImplicit || operation.Instance is null) return;
objectDotGetTypeSym ??= oac.Compilation.GetTypeByMetadataName("System.Object")!.GetMembers("GetType")[0];
if (!objectDotGetTypeSym.Matches(operation.TargetMethod)) return;
if (operation.Instance.Syntax is not ThisExpressionSyntax and not IdentifierNameSyntax { Identifier.Text: "GetType" }) return; // called on something that isn't `this`
var enclosingType = operation.SemanticModel.GetDeclaredSymbol(((CSharpSyntaxNode) operation.Syntax).EnclosingTypeDeclarationSyntax()!)!;
oac.ReportDiagnostic(Diagnostic.Create(enclosingType.IsSealed ? DiagNoGetTypeOnThisSealed : DiagNoGetTypeOnThis, operation.Syntax.GetLocation(), enclosingType.Name));
},
OperationKind.Invocation);
}
}

Binary file not shown.

View File

@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <summary>
/// Represents the tape device
/// </summary>
public class DatacorderDevice
public sealed class DatacorderDevice
{
private CPCBase _machine;
private Z80A _cpu => _machine.CPU;
@ -348,7 +348,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
{
// exception during operation
var e = ex;
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(DatacorderDevice).ToString() +
"\n\nTape image file has a valid CDT header, but threw an exception whilst data was being parsed.\n\n" + e.ToString());
}
}

View File

@ -170,7 +170,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <summary>
/// Holds specfic state information about a drive
/// </summary>
private class DriveState : IFDDHost
private sealed class DriveState : IFDDHost
{
/// <summary>
/// The drive ID from an FDC perspective
@ -775,7 +775,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
if (!found)
{
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(DriveState).ToString() +
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
}
}

View File

@ -214,7 +214,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
protected void LoadDiskMedia()
{
if (this.GetType() == typeof(CPC464))
if (this is CPC464)
{
CPC.CoreComm.ShowMessage("You are trying to load one of more disk images.\n\n Please select something other than CPC 464 emulation immediately and reboot the core");
return;

View File

@ -24,12 +24,14 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
public virtual bool IsWriter => false;
protected abstract Type SelfType { get; }
/// <summary>
/// Serialization method
/// </summary>
public virtual void Read(byte[] data)
{
throw new NotImplementedException(this.GetType() +
throw new NotImplementedException(SelfType +
"Read operation is not implemented for this converter");
}
@ -38,7 +40,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
public virtual void Write(byte[] data)
{
throw new NotImplementedException(this.GetType() +
throw new NotImplementedException(SelfType +
"Write operation is not implemented for this converter");
}
@ -47,7 +49,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
public virtual bool CheckType(byte[] data)
{
throw new NotImplementedException(this.GetType().ToString() +
throw new NotImplementedException(SelfType.ToString() +
"Check type operation is not implemented for this converter");
}

View File

@ -8,7 +8,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// <summary>
/// Responsible for TZX format serialization
/// </summary>
public class CdtConverter : MediaConverter
public sealed class CdtConverter : MediaConverter
{
/// <summary>
/// The type of serializer
@ -26,6 +26,9 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
/// </summary>
public override bool IsWriter => false;
protected override Type SelfType
=> typeof(CdtConverter);
/// <summary>
/// Working list of generated tape data blocks
/// </summary>
@ -164,7 +167,7 @@ namespace BizHawk.Emulation.Cores.Computers.AmstradCPC
if (ident != "ZXTape!" || eotm != 0x1A)
{
// this is not a valid TZX format file
throw new Exception(this.GetType() +
throw new Exception(typeof(CdtConverter) +
"This is not a valid TZX format file");
}

View File

@ -11,7 +11,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// Represents the tape device (or build-in datacorder as it was called +2 and above)
/// </summary>
public class DatacorderDevice : IPortIODevice
public sealed class DatacorderDevice : IPortIODevice
{
private SpectrumBase _machine { get; set; }
private Z80A _cpu { get; set; }
@ -312,7 +312,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
// exception during operation
var e = ex;
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(DatacorderDevice).ToString() +
"\n\nTape image file has a valid TZX header, but threw an exception whilst data was being parsed.\n\n" + e.ToString());
}
}
@ -332,7 +332,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
// exception during operation
var e = ex;
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(DatacorderDevice).ToString() +
"\n\nTape image file has a valid PZX header, but threw an exception whilst data was being parsed.\n\n" + e.ToString());
}
}
@ -352,7 +352,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
// exception during operation
var e = ex;
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(DatacorderDevice).ToString() +
"\n\nTape image file has a valid CSW header, but threw an exception whilst data was being parsed.\n\n" + e.ToString());
}
}
@ -372,7 +372,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
// exception during operation
var e = ex;
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(DatacorderDevice).ToString() +
"\n\nTape image file has a valid WAV header, but threw an exception whilst data was being parsed.\n\n" + e.ToString());
}
}
@ -391,7 +391,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
{
// exception during operation
var e = ex;
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(DatacorderDevice).ToString() +
"\n\nAn exception was thrown whilst data from this tape image was being parsed as TAP.\n\n" + e.ToString());
}
}

View File

@ -170,7 +170,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// Holds specfic state information about a drive
/// </summary>
private class DriveState : IFDDHost
private sealed class DriveState : IFDDHost
{
/// <summary>
/// The drive ID from an FDC perspective
@ -776,7 +776,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (!found)
{
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(DriveState).ToString() +
"\n\nDisk image file could not be parsed. Potentially an unknown format.");
}
}

View File

@ -227,7 +227,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
protected void LoadDiskMedia()
{
if (this.GetType() != typeof(ZX128Plus3))
if (this is not ZX128Plus3)
{
Spectrum.CoreComm.ShowMessage("You are trying to load one of more disk images.\n\n Please select ZX Spectrum +3 emulation immediately and reboot the core");
return;

View File

@ -157,16 +157,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// Detects whether this is a 48k machine (or a 128k in 48k mode)
/// </summary>
public virtual bool IsIn48kMode()
{
if (this.GetType() == typeof(ZX48) ||
this.GetType() == typeof(ZX16) ||
PagingDisabled)
{
return true;
}
else
return false;
}
=> PagingDisabled || this is ZX48 or ZX16;
/// <summary>
/// Monitors ROM access

View File

@ -26,12 +26,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public virtual bool IsWriter => false;
protected abstract Type SelfType { get; }
/// <summary>
/// Serialization method
/// </summary>
public virtual void Read(byte[] data)
{
throw new NotImplementedException(this.GetType().ToString() +
throw new NotImplementedException(SelfType.ToString() +
"Read operation is not implemented for this converter");
}
@ -40,7 +42,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public virtual void Write(byte[] data)
{
throw new NotImplementedException(this.GetType().ToString() +
throw new NotImplementedException(SelfType.ToString() +
"Write operation is not implemented for this converter");
}
@ -49,7 +51,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public virtual bool CheckType(byte[] data)
{
throw new NotImplementedException(this.GetType().ToString() +
throw new NotImplementedException(SelfType.ToString() +
"Check type operation is not implemented for this converter");
}

View File

@ -9,7 +9,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// Responsible for Compressed Square Wave conversion
/// https://web.archive.org/web/20171024182530/http://ramsoft.bbk.org.omegahg.com/csw.html
/// </summary>
public class CswConverter : MediaConverter
public sealed class CswConverter : MediaConverter
{
/// <summary>
/// The type of serializer
@ -32,6 +32,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public override bool IsWriter => false;
protected override Type SelfType
=> typeof(CswConverter);
private readonly DatacorderDevice _datacorder;
public CswConverter(DatacorderDevice _tapeDevice)
@ -80,14 +83,14 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (ident.ToUpper() != "COMPRESSED SQUARE WAVE")
{
// this is not a valid CSW format file
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(CswConverter).ToString() +
"This is not a valid CSW format file");
}
if (data[0x16] != 0x1a)
{
// invalid terminator code
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(CswConverter).ToString() +
"This image reports as a CSW but has an invalid terminator code");
}
@ -182,12 +185,12 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (compressionType == 1)
Array.Copy(data, _position, cswDataUncompressed, 0, cswDataUncompressed.Length);
else
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(CswConverter).ToString() +
"CSW Format unknown compression type");
}
else
{
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(CswConverter).ToString() +
"CSW Format Version " + majorVer + "." + minorVer + " is not currently supported");
}

View File

@ -9,7 +9,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// Reponsible for PZX format serializaton
/// Based on the information here: http://zxds.raxoft.cz/docs/pzx.txt
/// </summary>
public class PzxConverter : MediaConverter
public sealed class PzxConverter : MediaConverter
{
/// <summary>
/// The type of serializer
@ -27,6 +27,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public override bool IsWriter => false;
protected override Type SelfType
=> typeof(PzxConverter);
/// <summary>
/// Working list of generated tape data blocks
/// </summary>
@ -96,7 +99,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (ident.ToUpper() != "PZXT")
{
// this is not a valid TZX format file
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(PzxConverter).ToString() +
"This is not a valid PZX format file");
}

View File

@ -27,6 +27,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public override bool IsWriter => false;
protected override Type SelfType
=> typeof(TapConverter);
private readonly DatacorderDevice _datacorder;
public TapConverter(DatacorderDevice _tapeDevice)

View File

@ -8,7 +8,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// <summary>
/// Reponsible for TZX format serializaton
/// </summary>
public class TzxConverter : MediaConverter
public sealed class TzxConverter : MediaConverter
{
/// <summary>
/// The type of serializer
@ -26,6 +26,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public override bool IsWriter => false;
protected override Type SelfType
=> typeof(TzxConverter);
/// <summary>
/// Working list of generated tape data blocks
/// </summary>
@ -202,7 +205,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (ident != "ZXTape!" || eotm != 0x1A)
{
// this is not a valid TZX format file
throw new Exception(this.GetType() +
throw new Exception(typeof(TzxConverter) +
"This is not a valid TZX format file");
}

View File

@ -10,7 +10,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// Reponsible for WAV format conversion
/// Based heavily on code from zxmak2: https://archive.codeplex.com/?p=zxmak2
/// </summary>
public class WavConverter : MediaConverter
public sealed class WavConverter : MediaConverter
{
/// <summary>
/// The type of serializer
@ -28,6 +28,9 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
/// </summary>
public override bool IsWriter => false;
protected override Type SelfType
=> typeof(WavConverter);
/// <summary>
/// Position counter
/// </summary>
@ -74,7 +77,7 @@ namespace BizHawk.Emulation.Cores.Computers.SinclairSpectrum
if (ident.ToUpper() != "WAVE")
{
// this is not a valid TZX format file
throw new Exception(this.GetType().ToString() +
throw new Exception(typeof(WavConverter).ToString() +
"This is not a valid WAV format file");
}

View File

@ -212,7 +212,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
}
[StructLayout(LayoutKind.Sequential)]
public class SnesCallbacks
public sealed class SnesCallbacks
{
public snes_video_frame_t videoFrameCb;
public snes_audio_sample_t audioSampleCb;
@ -233,7 +233,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES
public IEnumerable<Delegate> AllDelegatesInMemoryOrder()
{
FieldsInOrder ??= GetType()
FieldsInOrder ??= typeof(SnesCallbacks)
.GetFields()
.OrderBy(BizInvokerUtilities.ComputeFieldOffset)
.ToList();

View File

@ -15,8 +15,9 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
{
[PortedCore(CoreNames.QuickNes, "", "0.7.0", "https://github.com/kode54/QuickNES")]
[ServiceNotApplicable(new[] { typeof(IDriveLight) })]
public partial class QuickNES : IEmulator, IVideoProvider, ISoundProvider, ISaveRam, IInputPollable, IBoardInfo, IVideoLogicalOffsets,
IStatable, IDebuggable, ISettable<QuickNES.QuickNESSettings, QuickNES.QuickNESSyncSettings>, INESPPUViewable
public sealed partial class QuickNES : IEmulator, IVideoProvider, ISoundProvider, ISaveRam, IInputPollable,
IBoardInfo, IVideoLogicalOffsets, IStatable, IDebuggable,
ISettable<QuickNES.QuickNESSettings, QuickNES.QuickNESSyncSettings>, INESPPUViewable
{
static QuickNES()
{
@ -292,7 +293,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
private void CheckDisposed()
{
if (Context == IntPtr.Zero)
throw new ObjectDisposedException(GetType().Name);
throw new ObjectDisposedException(typeof(QuickNES).Name);
}
// Fix some incorrect ines header entries that QuickNES uses to load games.

View File

@ -23,7 +23,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
* 3) support an ignore list of symbols
*/
public class GenDbgHlp : IDisposable
public sealed class GenDbgHlp : IDisposable
{
// config
private const string modulename = "libgenplusgx.dll";
@ -49,7 +49,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
public void SaveState(int statenum)
{
if (disposed) throw new ObjectDisposedException(this.GetType().ToString());
if (disposed) throw new ObjectDisposedException(typeof(GenDbgHlp).ToString());
data[statenum] ??= new byte[length];
@ -60,7 +60,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
public unsafe void Cmp(int statex, int statey)
{
if (disposed) throw new ObjectDisposedException(this.GetType().ToString());
if (disposed) throw new ObjectDisposedException(typeof(GenDbgHlp).ToString());
List<Tuple<int, int>> bads = new List<Tuple<int, int>>();
byte[] x = data[statex];
@ -134,7 +134,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Sega.gpgx
public List<Symbol> Find(IntPtr addr, int length)
{
if (disposed) throw new ObjectDisposedException(this.GetType().ToString());
if (disposed) throw new ObjectDisposedException(typeof(GenDbgHlp).ToString());
Symbol min = new Symbol { addr = addr };
Symbol max = new Symbol { addr = addr + length };