// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.PointsToAnalysis; using Microsoft.CodeAnalysis.Operations; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.FlowAnalysis.DataFlow.ParameterValidationAnalysis { using ParameterValidationAnalysisData = DictionaryAnalysisData<AbstractLocation, ParameterValidationAbstractValue>; internal partial class ParameterValidationAnalysis : ForwardDataFlowAnalysis<ParameterValidationAnalysisData, ParameterValidationAnalysisContext, ParameterValidationAnalysisResult, ParameterValidationBlockAnalysisResult, ParameterValidationAbstractValue> { /// <summary> /// Operation visitor to flow the location validation values across a given statement in a basic block. /// </summary> private sealed class ParameterValidationDataFlowOperationVisitor : AbstractLocationDataFlowOperationVisitor<ParameterValidationAnalysisData, ParameterValidationAnalysisContext, ParameterValidationAnalysisResult, ParameterValidationAbstractValue> { private readonly ImmutableDictionary<IParameterSymbol, SyntaxNode>.Builder? _hazardousParameterUsageBuilder; private readonly INamedTypeSymbol? _notNullAttributeType; public ParameterValidationDataFlowOperationVisitor(ParameterValidationAnalysisContext analysisContext) : base(analysisContext) { Debug.Assert(analysisContext.OwningSymbol.Kind == SymbolKind.Method); Debug.Assert(analysisContext.PointsToAnalysisResult != null); if (analysisContext.TrackHazardousParameterUsages) { _hazardousParameterUsageBuilder = ImmutableDictionary.CreateBuilder<IParameterSymbol, SyntaxNode>(); } _notNullAttributeType = analysisContext.WellKnownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemDiagnosticsCodeAnalysisNotNullAttribute); } public ImmutableDictionary<IParameterSymbol, SyntaxNode> HazardousParameterUsages { get { RoslynDebug.Assert(_hazardousParameterUsageBuilder != null); return _hazardousParameterUsageBuilder.ToImmutable(); } } protected override ParameterValidationAbstractValue GetAbstractDefaultValue(ITypeSymbol? type) => ValueDomain.Bottom; protected override bool HasAnyAbstractValue(ParameterValidationAnalysisData data) => data.Count > 0; protected override ParameterValidationAbstractValue GetAbstractValue(AbstractLocation location) => CurrentAnalysisData.TryGetValue(location, out var value) ? value : ValueDomain.Bottom; protected override void ResetCurrentAnalysisData() => ResetAnalysisData(CurrentAnalysisData); private bool IsTrackedLocation(AbstractLocation location) { return CurrentAnalysisData.ContainsKey(location) || location.Symbol is IParameterSymbol parameter && parameter.Type.IsReferenceType && Equals(parameter.ContainingSymbol, GetBottomOfStackOwningSymbol()); ISymbol GetBottomOfStackOwningSymbol() { if (DataFlowAnalysisContext.InterproceduralAnalysisData == null) { return OwningSymbol; } return DataFlowAnalysisContext.InterproceduralAnalysisData.MethodsBeingAnalyzed .Single(m => m.InterproceduralAnalysisData == null) .OwningSymbol; } } private bool IsNotOrMaybeValidatedLocation(AbstractLocation location) => CurrentAnalysisData.TryGetValue(location, out var value) && (value == ParameterValidationAbstractValue.NotValidated || value == ParameterValidationAbstractValue.MayBeValidated); protected override void StopTrackingAbstractValue(AbstractLocation location) => CurrentAnalysisData.Remove(location); protected override void SetAbstractValue(AbstractLocation location, ParameterValidationAbstractValue value) { if (IsTrackedLocation(location)) { CurrentAnalysisData[location] = value; } } protected override void SetAbstractValueForAssignment(IOperation target, IOperation? assignedValueOperation, ParameterValidationAbstractValue assignedValue, bool mayBeAssignment = false) { // If we are assigning to parameter, mark it as validated on this path. if (target is IParameterReferenceOperation) { MarkValidatedLocations(target); } } protected override void SetAbstractValueForTupleElementAssignment(AnalysisEntity tupleElementEntity, IOperation assignedValueOperation, ParameterValidationAbstractValue assignedValue) { // We are only tracking default parameter locations. } protected override void SetAbstractValueForArrayElementInitializer(IArrayCreationOperation arrayCreation, ImmutableArray<AbstractIndex> indices, ITypeSymbol elementType, IOperation initializer, ParameterValidationAbstractValue value) { // We are only tracking default parameter locations. } protected override void SetValueForParameterPointsToLocationOnEntry(IParameterSymbol parameter, PointsToAbstractValue pointsToAbstractValue) { if (pointsToAbstractValue.Kind == PointsToAbstractValueKind.KnownLocations) { var value = HasAnyNullValidationAttribute(parameter) ? ParameterValidationAbstractValue.Validated : ParameterValidationAbstractValue.NotValidated; SetAbstractValue(pointsToAbstractValue.Locations, value); } } private bool HasAnyNullValidationAttribute(IParameterSymbol? parameter) => parameter != null && parameter.GetAttributes().Any(attr => attr.AttributeClass != null && (attr.AttributeClass.Name.Equals("ValidatedNotNullAttribute", StringComparison.OrdinalIgnoreCase) || attr.AttributeClass.Equals(_notNullAttributeType))); protected override void EscapeValueForParameterPointsToLocationOnExit(IParameterSymbol parameter, AnalysisEntity analysisEntity, ImmutableHashSet<AbstractLocation> escapedLocations) { // Mark parameters as validated if they are non-null at all non-exception return paths and null at one of the unhandled throw operations. var notValidatedLocations = escapedLocations.Where(IsNotOrMaybeValidatedLocation); if (notValidatedLocations.Any()) { if (TryGetNullAbstractValueAtCurrentBlockEntry(analysisEntity, out NullAbstractValue nullAbstractValue) && nullAbstractValue == NullAbstractValue.NotNull && TryGetMergedNullAbstractValueAtUnhandledThrowOperationsInGraph(analysisEntity, out NullAbstractValue mergedValueAtUnhandledThrowOperations) && mergedValueAtUnhandledThrowOperations != NullAbstractValue.NotNull) { SetAbstractValue(notValidatedLocations, ParameterValidationAbstractValue.Validated); } } } private void MarkValidatedLocations(IOperation operation) => SetAbstractValue(GetNotValidatedLocations(operation), ParameterValidationAbstractValue.Validated); private IEnumerable<AbstractLocation> GetNotValidatedLocations(IOperation operation) { var pointsToLocation = GetPointsToAbstractValue(operation); return pointsToLocation.Locations.Where(IsNotOrMaybeValidatedLocation); } private static bool IsHazardousIfNull(IOperation operation) { if (operation.Kind == OperationKind.ConditionalAccessInstance) { return false; } return operation.Parent switch { IMemberReferenceOperation memberReference => memberReference.Instance == operation, IArrayElementReferenceOperation arrayElementReference => arrayElementReference.ArrayReference == operation, IInvocationOperation invocation => invocation.Instance == operation, _ => false, }; } private void HandlePotentiallyHazardousOperation(IOperation operation, IEnumerable<AbstractLocation> nonValidatedLocations) { Debug.Assert(_hazardousParameterUsageBuilder != null); if (GetNullAbstractValue(operation) == NullAbstractValue.NotNull) { // We are sure the value is non-null, so cannot be hazardous. return; } HandleHazardousOperation(operation.Syntax, nonValidatedLocations); } private void HandleHazardousOperation(SyntaxNode syntaxNode, IEnumerable<AbstractLocation> nonValidatedLocations) { RoslynDebug.Assert(_hazardousParameterUsageBuilder != null); foreach (var location in nonValidatedLocations) { Debug.Assert(IsNotOrMaybeValidatedLocation(location)); var parameter = (IParameterSymbol)location.Symbol!; if (!_hazardousParameterUsageBuilder.TryGetValue(parameter, out var currentSyntaxNode) || syntaxNode.SpanStart < currentSyntaxNode.SpanStart) { _hazardousParameterUsageBuilder[parameter] = syntaxNode; } } } protected override ParameterValidationAnalysisData MergeAnalysisData(ParameterValidationAnalysisData value1, ParameterValidationAnalysisData value2) => ParameterValidationAnalysisDomainInstance.Merge(value1, value2); protected override void UpdateValuesForAnalysisData(ParameterValidationAnalysisData targetAnalysisData) => UpdateValuesForAnalysisData(targetAnalysisData, CurrentAnalysisData); protected override ParameterValidationAnalysisData GetClonedAnalysisData(ParameterValidationAnalysisData analysisData) => GetClonedAnalysisDataHelper(analysisData); public override ParameterValidationAnalysisData GetEmptyAnalysisData() => GetEmptyAnalysisDataHelper(); protected override ParameterValidationAnalysisData GetExitBlockOutputData(ParameterValidationAnalysisResult analysisResult) => GetClonedAnalysisDataHelper(analysisResult.ExitBlockOutput.Data); protected override void ApplyMissingCurrentAnalysisDataForUnhandledExceptionData(ParameterValidationAnalysisData dataAtException, ThrownExceptionInfo throwBranchWithExceptionType) => ApplyMissingCurrentAnalysisDataForUnhandledExceptionData(dataAtException, CurrentAnalysisData); protected override bool Equals(ParameterValidationAnalysisData value1, ParameterValidationAnalysisData value2) => EqualsHelper(value1, value2); #region Visit overrides public override ParameterValidationAbstractValue Visit(IOperation? operation, object? argument) { var value = base.Visit(operation, argument); if (operation != null) { if (_hazardousParameterUsageBuilder != null && IsHazardousIfNull(operation)) { var notValidatedLocations = GetNotValidatedLocations(operation); HandlePotentiallyHazardousOperation(operation, notValidatedLocations); } } return value; } public override ParameterValidationAbstractValue VisitReDimClause(IReDimClauseOperation operation, object? argument) { var value = base.VisitReDimClause(operation, argument); MarkValidatedLocations(operation.Operand); return value; } public override ParameterValidationAbstractValue VisitObjectCreation(IObjectCreationOperation operation, object? argument) { var value = base.VisitObjectCreation(operation, argument); ProcessRegularInvocationOrCreation(operation.Constructor, operation.Arguments, operation); return value; } public override ParameterValidationAbstractValue VisitInvocation_NonLambdaOrDelegateOrLocalFunction( IMethodSymbol method, IOperation? visitedInstance, ImmutableArray<IArgumentOperation> visitedArguments, bool invokedAsDelegate, IOperation originalOperation, ParameterValidationAbstractValue defaultValue) { var value = base.VisitInvocation_NonLambdaOrDelegateOrLocalFunction(method, visitedInstance, visitedArguments, invokedAsDelegate, originalOperation, defaultValue); ProcessRegularInvocationOrCreation(method, visitedArguments, originalOperation); return value; } public override ParameterValidationAbstractValue VisitInvocation_Lambda( IFlowAnonymousFunctionOperation lambda, ImmutableArray<IArgumentOperation> visitedArguments, IOperation originalOperation, ParameterValidationAbstractValue defaultValue) { var value = base.VisitInvocation_Lambda(lambda, visitedArguments, originalOperation, defaultValue); ProcessLambdaOrLocalFunctionInvocation(lambda.Symbol, originalOperation); return value; } public override ParameterValidationAbstractValue VisitInvocation_LocalFunction( IMethodSymbol localFunction, ImmutableArray<IArgumentOperation> visitedArguments, IOperation originalOperation, ParameterValidationAbstractValue defaultValue) { var value = base.VisitInvocation_LocalFunction(localFunction, visitedArguments, originalOperation, defaultValue); ProcessLambdaOrLocalFunctionInvocation(localFunction, originalOperation); return value; } private void ProcessRegularInvocationOrCreation(IMethodSymbol? targetMethod, ImmutableArray<IArgumentOperation> arguments, IOperation operation) { if (targetMethod == null) return; Debug.Assert(!targetMethod.IsLambdaOrLocalFunctionOrDelegate()); if (targetMethod.IsArgumentNullCheckMethod()) { if (arguments.Length == 1) { // "static bool SomeType.IsNullXXX(obj)" check. MarkValidatedLocations(arguments[0]); } } else if (!targetMethod.Parameters.IsEmpty && !arguments.IsEmpty && ExceptionNamedType != null && targetMethod.ContainingType.DerivesFrom(ExceptionNamedType)) { // FxCop compat: special cases handled by FxCop. // 1. First argument of type System.Runtime.Serialization.SerializationInfo to System.Exception.GetObjectData or its override is validated. // 2. First argument of type System.Runtime.Serialization.SerializationInfo to constructor of System.Exception or its subtype is validated. if (targetMethod.IsGetObjectData(SerializationInfoNamedType, StreamingContextNamedType) || targetMethod.IsSerializationConstructor(SerializationInfoNamedType, StreamingContextNamedType)) { MarkValidatedLocations(arguments[0]); } } else if (_hazardousParameterUsageBuilder != null && !targetMethod.IsExternallyVisible() && TryGetInterproceduralAnalysisResult(operation, out var invokedMethodAnalysisResult)) { // Check if this private/internal method that has hazardous usages of non-validated argument. Debug.Assert(!targetMethod.IsVirtual && !targetMethod.IsOverride); var hazardousParameterUsagesInInvokedMethod = invokedMethodAnalysisResult.HazardousParameterUsages; if (!hazardousParameterUsagesInInvokedMethod.IsEmpty) { foreach (var argument in arguments) { var notValidatedLocations = GetNotValidatedLocations(argument); foreach (var location in notValidatedLocations) { var parameter = (IParameterSymbol)location.Symbol!; if (hazardousParameterUsagesInInvokedMethod.ContainsKey(parameter)) { HandlePotentiallyHazardousOperation(argument, notValidatedLocations); break; } } } } } // Mark arguments passed to parameters of null check validation methods as validated. // Also mark arguments passed to parameters with ValidatedNotNullAttribute as validated. var isNullCheckValidationMethod = DataFlowAnalysisContext.IsNullCheckValidationMethod(targetMethod.OriginalDefinition); foreach (var argument in arguments) { var notValidatedLocations = GetNotValidatedLocations(argument); if (notValidatedLocations.Any()) { if (isNullCheckValidationMethod || HasAnyNullValidationAttribute(argument.Parameter)) { MarkValidatedLocations(argument); } } } } private void ProcessLambdaOrLocalFunctionInvocation(IMethodSymbol targetMethod, IOperation invocation) { Debug.Assert(targetMethod.MethodKind is MethodKind.LambdaMethod or MethodKind.LocalFunction); // Lambda and local function invocations can access captured variables. if (_hazardousParameterUsageBuilder != null && TryGetInterproceduralAnalysisResult(invocation, out var invokedMethodAnalysisResult)) { var notValidatedLocations = CurrentAnalysisData.Keys.Where(IsNotOrMaybeValidatedLocation); if (notValidatedLocations.Any()) { var hazardousParameterUsagesInInvokedMethod = invokedMethodAnalysisResult.HazardousParameterUsages; foreach (var kvp in hazardousParameterUsagesInInvokedMethod) { var parameter = kvp.Key; var syntaxNode = kvp.Value; if (!_hazardousParameterUsageBuilder.ContainsKey(parameter)) { HandleHazardousOperation(syntaxNode, notValidatedLocations.Where(l => Equals(l.Symbol, parameter))); } } } } } public override ParameterValidationAbstractValue VisitBinaryOperatorCore(IBinaryOperation operation, object? argument) { var value = base.VisitBinaryOperatorCore(operation, argument); // Mark a location as validated on paths where we know it is non-null. // if (x != null) // { // // Validated on this path // } // if (x != null) // { // // This code path // } var isNullNotEqualsOnWhenTrue = FlowBranchConditionKind == ControlFlowConditionKind.WhenTrue && (operation.OperatorKind == BinaryOperatorKind.NotEquals || operation.OperatorKind == BinaryOperatorKind.ObjectValueNotEquals); // if (x == null) { ... } // else // { // // This code path // } var isNullEqualsOnWhenFalse = FlowBranchConditionKind == ControlFlowConditionKind.WhenFalse && (operation.OperatorKind == BinaryOperatorKind.Equals || operation.OperatorKind == BinaryOperatorKind.ObjectValueEquals); if (isNullNotEqualsOnWhenTrue || isNullEqualsOnWhenFalse) { if (GetNullAbstractValue(operation.RightOperand) == NullAbstractValue.Null) { // if (x != null) MarkValidatedLocations(operation.LeftOperand); } else if (GetNullAbstractValue(operation.LeftOperand) == NullAbstractValue.Null) { // if (null != x) MarkValidatedLocations(operation.RightOperand); } } return value; } public override ParameterValidationAbstractValue VisitIsNull(IIsNullOperation operation, object? argument) { var value = base.VisitIsNull(operation, argument); // Mark a location as validated on paths where user has performed an IsNull check. // See comments in VisitBinaryOperatorCore override above for further details. MarkValidatedLocations(operation.Operand); return value; } public override ParameterValidationAbstractValue VisitIsPattern(IIsPatternOperation operation, object? argument) { var value = base.VisitIsPattern(operation, argument); // Mark a location as validated on false path where user has performed an IsPattern check with null on true path. // See comments in VisitBinaryOperatorCore override above for further details. if (FlowBranchConditionKind == ControlFlowConditionKind.WhenFalse && GetNullAbstractValue(operation.Pattern) == NullAbstractValue.Null) { MarkValidatedLocations(operation.Value); } // Mark a location as validated on true path where user has performed an IsPattern check with not null on true path. if (FlowBranchConditionKind == ControlFlowConditionKind.WhenTrue && GetNullAbstractValue(operation.Pattern) == NullAbstractValue.NotNull) { MarkValidatedLocations(operation.Value); } return value; } public override ParameterValidationAbstractValue VisitIsType(IIsTypeOperation operation, object? argument) { var value = base.VisitIsType(operation, argument); // Mark a location as validated on paths where user has performed an IsType check, for example 'x is object'. // See comments in VisitBinaryOperatorCore override above for further details. if (FlowBranchConditionKind == ControlFlowConditionKind.WhenTrue) { MarkValidatedLocations(operation.ValueOperand); } return value; } #endregion } } } |