private static void OnCompilationStart(CompilationStartAnalysisContext context) { var methodHelper = MethodHelper.Init(context.Compilation); if (methodHelper is null) { return; } context.RegisterOperationBlockStartAction(OnOperationBlockStart); return; void OnOperationBlockStart(OperationBlockStartAnalysisContext context) { // Patterns we are looking for: // Pattern #1 Pattern #2 // var obj = #HashCreation#; or #HashCreation#.CompuateHash(buffer); // ... // obj.CompuateHash(buffer); // The core search logic is split into 3 groups // 1. Scan all invocations: // a. if it is a Hash Create static method, store its local symbol + declaration operation and type symbols // b. if it is HashAlgorithm.ComputeHash / HashAlgorithm.TryComputeHash // 1. if its instance is a local reference, store its local reference + invocation // 2. if its instance is the creation of a hash instance, we found pattern #2. Report diagnostic. // c. if it is HashAlgorithm.Dispose, store the invocation operation // 2. Find all HashAlgorithm object creation (new) and store its local symbol + declaration operation and type symbols // 3. Find all HashAlgorithm local references and store them // At OperationBlockEnd: // 1. Compile a 'disposeMap' map of symbol -> dispose invocations (Dictionary<ILocalSymbol, ImmutableArray<IInvocationOperation>>) // 2. Compile a 'computeHashOnlyCountMap' map of symbol -> computehash invocation count (Dictionary<ILocalSymbol, int>) // a. if the count for its local references in the block does not match the count of computehash invocations + dispose invocations, it is excluded from this map // 3. Iterate the invocation, only report the invocation // a. hashAlgorithm type has a static HashData method // b. hashAlgorithm instance was created in the block // c. hashAlgorithm instance exist in the 'computeHashOnlyCountMap' map // Reporting of Diagnostic // The main span reported is at the ComputeHash method // // Properties: // if there is only 1 local reference of a symbol excluding dispose references, DeleteHashCreationPropertyKey is set // // Additional locations: // ComputeHash/ComputeHash(a,b,c)/TryComputeHash: // Pattern #1 Pattern #2 // 1. span where the hash instance was created // 2-N. dispose invocations var dataCollector = new DataCollector(); context.RegisterOperationAction(CaptureHashLocalReferenceOperation, OperationKind.LocalReference); context.RegisterOperationAction(CaptureCreateOrComputeHashInvocationOperation, OperationKind.Invocation); context.RegisterOperationAction(CaptureHashObjectCreationOperation, OperationKind.ObjectCreation); context.RegisterOperationBlockEndAction(OnOperationBlockEnd); return; void CaptureHashLocalReferenceOperation(OperationAnalysisContext context) { var localReferenceOperation = (ILocalReferenceOperation)context.Operation; if (methodHelper.IsLocalReferenceInheritingHashAlgorithm(localReferenceOperation)) { dataCollector.CollectLocalReferenceInheritingHashAlgorithm(localReferenceOperation); } } void CaptureCreateOrComputeHashInvocationOperation(OperationAnalysisContext context) { var invocationOperation = (IInvocationOperation)context.Operation; if (methodHelper.IsHashCreateMethod(invocationOperation)) { CaptureHashCreateInvocationOperation(dataCollector, invocationOperation); } else if (methodHelper.IsComputeHashMethod(invocationOperation, out ComputeType computeType)) { CaptureOrReportComputeHashInvocationOperation(context, methodHelper, dataCollector, invocationOperation, computeType); } else if (invocationOperation.Instance is ILocalReferenceOperation && methodHelper.IsDisposeMethod(invocationOperation)) { dataCollector.CollectDisposeInvocation(invocationOperation); } } void CaptureHashObjectCreationOperation(OperationAnalysisContext context) { var objectCreationOperation = (IObjectCreationOperation)context.Operation; if (!methodHelper.IsObjectCreationInheritingHashAlgorithm(objectCreationOperation)) { return; } if (TryGetVariableInitializerOperation(objectCreationOperation.Parent, out var variableInitializerOperation)) { CaptureVariableDeclaratorOperation(dataCollector, objectCreationOperation.Type, variableInitializerOperation); } } void OnOperationBlockEnd(OperationBlockAnalysisContext context) { var cancellationToken = context.CancellationToken; var dataResult = dataCollector.Compile(cancellationToken); if (dataResult is null) { return; } foreach (var(computeHash, type) in dataResult.ComputeHashMap) { ImmutableArray <Location> codefixerLocations; var localSymbol = ((ILocalReferenceOperation)computeHash.Instance).Local; var isToDeleteHashCreation = false; if (dataResult.TryGetDeclarationTuple(localSymbol, out var declarationTuple) && dataResult.TryGetSymbolComputeHashRefCountTuple(localSymbol, out var refCount) && methodHelper.TryGetHashDataMethod(declarationTuple.OriginalType, type, out var hashDataMethodSymbol)) { var disposeArray = dataResult.GetDisposeInvocationArray(localSymbol); isToDeleteHashCreation = refCount == 1; codefixerLocations = GetFixerLocations(declarationTuple.VariableIntitializerOperation, disposeArray, out var hashCreationLocationIndex); var diagnostics = CreateDiagnostics(computeHash, hashDataMethodSymbol.ContainingType, codefixerLocations, type, isToDeleteHashCreation, hashCreationLocationIndex); context.ReportDiagnostic(diagnostics); } } dataResult.Free(cancellationToken); } }