/Gendarme

Gendarme is a extensible rule-based tool to find problems in .NET applications and libraries. Gendarme inspects programs and libraries that contain code in ECMA CIL format (Mono and .NET) and looks for common problems with the code, problems that compilers do not typically check or have not historically checked.

Primary LanguageC#OtherNOASSERTION

altcode.gendarme

A Mono.Gendarme fork, built against a recent Mono.Cecil version, one that can load assemblies built with current compilers. Can be used with the Fake.build plugin.

Features

See the head of the release branch for the features in the latest actual release.

In this branch

  • Can load .net core assemblies
    • Will search the nuget cache for dependencies, though this can take some time as an alternative to using dotnet publish to get all the code you want to analyse in one place.
  • Will load debug information from embedded symbols or actual .pdb files if available even on non-Windows platforms.
    • The main impact is that the AvoidLongMethodsRule works by LoC and not IL against .net core code on all platforms.
  • Depending whether the Framework or dotnet tool version is used, the results may differ when faced with the same assembly, because of the different runtime being consulted
    • e.g. several types marked [Serializable] in the Framework are not so marked at dotnet, so serialization rules will give different answers
  • Because they use obsolescing functions not present in netstandard2.0 the following Gendarme.Rules.Security.Cas rules are only present in the Framework tool build, under the Obsolete.Rules.Security.Cas name:
    • AddMissingTypeInheritanceDemandRule
    • DoNotExposeMethodsProtectedByLinkDemandRule
    • DoNotReduceTypeSecurityOnMethodsRule
    • SecureGetObjectDataOverridesRule
  • The obsolete Gendarme.Rules.Portability.MonoCompatibilityReviewRule is not implemented in this fork.
  • DefineAZeroValueRule does not trigger for non-int32 enums that have a suitably typed zero value. This rule should not also be doing the job of EnumsShouldUseInt32Rule
  • Due to IL changes UseIsOperatorRule has been tuned to avoid false positives at the cost of missing some failure cases
  • Due to IL changes DoNotAssumeIntPtrSizeRule will give false negatives for simple (and often implicit) casts of IntPtr to other integer types.
  • New rule categories
    • AltCode.Rules.General for general purpose rules
      • JustifySuppressionRule to check the Justification property on SuppressMessage attribute
      • PreferStrongNamedAssembliesRule to replace deprecated/withdrawn FxCop rule Microsoft.Design#CA2210
      • AvoidAssemblySemanticVersionMismatchRule to insist that the API contract (major, minor, and optionally build if defined for the assembly) match, but the lesser facets, revision and possibly build are free.
    • AltCode.Rules.PowerShell for re-implementing the old Microsoft PowerShell FxCop rules
      • AltCode.Rules.PowerShell.UseOnlyStandardVerbsRule to replace "Microsoft.PowerShell#PS1001:UseOnlyStandardVerbs"
      • AltCode.Rules.PowerShell.DefineCmdletInTheCorrectNamespaceRule to replace "Microsoft.PowerShell#PS1011:DefineCmdletInTheCorrectNamespace"
  • In the text output, include a specimen global suppression attribute for each issue, for convenience when dealing with remaining intractable issues e.g. arising from code generation
    • While Scope is not heeded by the Gendarme process, it's there to placate other consumers (which will ignore the foreign rule); the comment indicates the corresponding object type within the Gendarme analysis in case they should ever be out of line.
    • The syntax and punctuation of the Target with regards to nested types and special names is as Gendarme expects, which differs somewhat from FxCop in annoying details
    • The emitted section looks like this:
Global Suppression Attribute:
[<assembly: SuppressMessage("Gendarme.Rules.Correctness",
                            "MethodCanBeMadeStaticRule",
                            Scope = "member", // MethodDefinition
                            Target = "ParameterNamesShouldMatch.Handler::ShowMessage(a,System.String)",
                            Justification = "")>]

Direction

After having achieved the first objective, of being able to analyze code from the new .net, the next goal of this fork has been to make the tool more F# aware, because that's where I personally use it the most. There are several places where F# code generation emits patterns that are detected by legacy Gendarme as erroneous, but which are not under sufficiently fine control by the developer or cannot be annotated to suppress a warning.

Known Issues

Unit test fixing

Having resolved many issues stemming from a Cecil change to what the name and namespace properties of a nested type returned, and differences in behaviour under .netstandard compared with the .net Framework, the remaining sources of test failure are compiler changes (from pre-Roslyn to now) . In particular, switch on integral values is often indistinguishable from an if or if/else if construct, so some tests have just been set to [Ignore]

The following rule suites currently have unit test failures

  • Framework -- 2 failures for Stack entry analysis for which there is no evidence of them ever having worked (even if the code under test is built with a 2008-vintage C# compiler the tests fail in the same way as at net6.0)
    • TestMultipleCatch()
    • TestTryCatchFinally() -- works with optimized build of C# 2008 (v2.0.50727\csc.exe /o)
  • Smells -- 2 failures and 1 ignored due to IL changes
    • false positive in SuccessOnNonDuplicatedCodeIntoForeachLoopTest (no evidence of ever passing : still fails when the code under test was compiled with a 2008 vintage compiler with or without optimization)
    • false positive in SuccessOnNonDuplicatedInSwitchLoadingByFieldsTest (Roslyn induced switch statement changes -- the test succeeds with the same code built with a 2008 vintage compiler)
    • AvoidSwitchStatementsRule test, FailOnMethodWithSwitchTesthas similarly been [Ignore]d because of the major IL changes involved (now decompiles to an if expression).

Changes made for F# support

For the moment this seems to suffice to tame unreasonable, or unfixable generated, issues --

  • Fix AvoidMultidimensionalIndexerRule for F# generated parameterless methods called get_Item
  • Fix comparison of nested type names in parameters against supplied types
  • Ignore [CompilerGenerated] methods for AvoidSwitchStatementsRule and CheckParametersNullityInVisibleMethodsRule
  • Ignore [CompilerGenerated] fields and methods for VariableNamesShouldNotMatchFieldNamesRule
  • Ignore <StartupCode$ names in UseCorrectCasingRule
  • Ignore generated types containg @ in their names for AvoidUnsealedUninheritedInternalTypesRule
  • Ignore the Tags generated type inside union types for AvoidVisibleConstantFieldRule
  • Explicitly exempt the get_ and set_ prefixes of getter and setter methods from the AvoidNonAlphanumericIdentifierRule since that was not already a thing.
  • Make AvoidUnneededUnboxingRule not applicable to [CompilerGenerated] functions (e.g. Union case CompareTo)
  • Exempt debugger-related generated types related to union types from AvoidUnsealedUninheritedInternalTypeRule and UseCorrectCasingRule
  • Exempt getter and setter methods from ConsiderConvertingMethodToPropertyRule (well, duh!)
  • Exempt types with only fully [CompilerGenerated] Equals and CompareTo methods from ImplementIComparableCorrectlyRule; also explicitly exempt record types
  • Even if a union type is [Obsolete] don't bother telling us its cases and case constructors depend on it.
  • In F# code (type with F# [CompilationMapping] attribute) then allow single lower-case letter generic types
  • Exempt methods of F# generated types with @ in the name from ParameterNamesShouldMatchOverriddenMethodRule
  • Exempt match on union types from AvoidSwitchStatementsRule
  • Exempt F# generated types with @ in the name from UseCorrectPrefixRule,VariableNamesShouldNotMatchFieldNamesRule and UseCorrectCasingRule
  • Exempt generated abstract closure types from AbstractTypesShouldNotHavePublicConstructorsRule
  • Exempt constructors of record types, or generated types with @ in the name, from AvoidLongParameterListsRule
  • Exempt generated types with @ in their names from AvoidUnnecessarySpecializationRule, AvoidSpeculativeGeneralityRule and MethodCanBeMadeStaticRule
  • Exempt F# placeholder arguments _ (compiled to _arg...) from UseCorrectCasingRule
  • Exempt module-bound functions from ConsiderConvertingMethodToPropertyRule
  • Exempt fields and constructors of records from RemoveDependenceOnObsoleteCodeRule; accessors will still be caught but can be [SuppressMessage]d as needed
  • Take account of F#'s habit of making a virtual call to the base type constructor in object types constructors.
  • Exempt F# code in modules, or where a match could equally be an if from AvoidSwitchStatementsRule, match being idiomatic and occasionally just happening to be on an explicit integral type
  • Exempt property backing fields for code like member val LocalSource = false with get, set from AvoidUnneededFieldInitializationRule
  • Exempt union cases (unsealed but not likely to be inherited) from AvoidUnsealedUninheritedInternalTypeRule
  • Exempt generated types with "@" in the name from AvoidMethodWithUnusedGenericTypeRule
  • Exempt the field accessors of F# types (usually bypassed by the compiler by code that goes direct to the backing field) from AvoidUncalledPrivateCodeRule
  • Allow for F# extension properties (TypeName.get_... and TypeName.set_...) and for generated parameter names of the form _arg... in AvoidNonAlphanumericIdentifierRule
  • Consider F# extension methods/properties to be object-bound rather than module bound for UseCorrectCasingRule
  • Add a heuristic to recognise F# compiler generated disposal after a use in EnsureLocalDisposalRule
  • Add a RelaxedAvoidCodeDuplicatedInSameClassRule, which looks for patterns aligning with visible sequence points, and excludes patterns containing throw new ArgumentNullException(...)
  • For AvoidLargeClassesRule, ignore FSharpFunc and FSharpTypeFunc valued fields in generated types with @ in their names; treat them as methods in the type instead.
  • For AvoidDeepNamespaceHierarchyRule, ignore F# generated namespaces of the form <StartupCode$a-b-c-d>.$.NETFramework,Version=...
  • For AvoidRepetitiveCastsRule, ignore F# is then as of anonymous temporaries (often happens in match expressions on sum types)
  • Adapt UseCorrectCasingRule to be compatible with FSharpLint for F# code (Non-class, non-public, functions should be camel-cased)
  • Add a RelaxedMarkAllNonSerializableFieldsRule which ignores F# types with @ in the name, keeping the full-strength version for cases where serializing a closure is intentional.
  • Skip types called <PrivateImplementationDetails>
  • Don't apply ParameterNamesShouldMatchOverridenMethodRule to cases where the base method has a null or empty parameter name (e.g. F# interfaces)
  • Don't apply DoNotDeclareVirtualMethodsInSealedTypeRule to F# closure types
  • Don't apply PreferStringComparisonOverrideRule to generated code
  • Don't apply Gendarme.Rules.Gendarme.UseCorrectSuffixRule to types in namespaces beginning "<StartupCode$"

Badges

Build GitHub CI Build history
Coverage Coveralls Coverage Status

Build process from trunk as per the CI YAML

Assumes net8.0/VS2022 build environment

  • dotnet tool restore
  • dotnet run --project ./Build/Setup.fsproj
  • dotnet run --project ./Build/Build.fsproj

The build stage can be done in Visual Studio with the Debug configuration to run the unit tests

Thanks to

  • Coveralls for allowing free services for Open Source projects