// TODO: Ideally, a load of static code analysis here, but just checking the first use would be a start. private void FirstUseOfUntrustedParameterShouldNotBeArgumentToTrustedParameter(SyntaxNodeAnalysisContext context) { var parameterSyntax = (ParameterSyntax)context.Node; var model = context.SemanticModel; var parameter = model.GetDeclaredSymbol(parameterSyntax); if (parameter.HasAttribute(TrustedAttributeName)) { return; } var container = parameter.ContainingSymbol; if ((container as IMethodSymbol)?.MethodKind == MethodKind.AnonymousFunction) { // Be lenient for anonymous functions. Not entirely sure whether this is a good idea, // admittedly. return; } var firstUse = container.DeclaringSyntaxReferences .SelectMany(syntaxRef => syntaxRef.GetSyntax().DescendantNodes()) .Select(node => ParameterUsage.ForNode(node, model, parameter)) .FirstOrDefault(usage => usage != null); if (firstUse?.CorrespondingParameter == null) { return; } if (firstUse.CorrespondingParameter.HasAttribute(TrustedAttributeName)) { context.ReportDiagnostic(UntrustedParameterIsTrusted, firstUse.UsageNode, parameter.Name, container.Name); } }
/// <summary> /// Single method to have a look at how a parameter is used within a method (often stopping /// at the first usage) and then checking whether that usage is appropriate for the /// attributes applied to the parameter. /// </summary> private void UberCheckParameter(SyntaxNodeAnalysisContext context) { var parameterSyntax = (ParameterSyntax)context.Node; var model = context.SemanticModel; var parameterSymbol = model.GetDeclaredSymbol(parameterSyntax); if (parameterSymbol.Type.IsValueType) { return; } bool notNull = HasNotNullAttribute(parameterSymbol); var container = parameterSymbol.ContainingSymbol; var methodSymbol = container as IMethodSymbol; if (methodSymbol?.MethodKind == MethodKind.AnonymousFunction) { // Be lenient for anonymous functions. Not entirely sure whether this is a good idea, // admittedly. Potentially check the attributes applied to the delegate type that the // anonymous function is being converted to. return; } if (container?.IsAbstract == true || methodSymbol?.MethodKind == MethodKind.DelegateInvoke) { // Abstract members (including interface members) and delegates can't check their parameters return; } var firstUse = container.DeclaringSyntaxReferences .SelectMany(syntaxRef => syntaxRef.GetSyntax().DescendantNodes()) .Select(node => ParameterUsage.ForNode(node, model, parameterSymbol)) .FirstOrDefault(usage => usage != null); if (firstUse == null) { // No "interesting" usage that could have been some other kind of check. (Could // be used in an assignment etc.) if (notNull) { context.ReportDiagnostic(NotNullParameterIsNotChecked, parameterSyntax.Identifier, parameterSymbol.Name); } return; } // First use is parameter.Foo or parameter.Foo(). This might always be invalid... see how much // noise it causes. Also consider extension methods, where it's valid to be null. if (firstUse.CorrespondingParameter == null) { if (container.DeclaredAccessibility == Accessibility.Public && container.ContainingType.DeclaredAccessibility == Accessibility.Public) { context.ReportDiagnostic(FirstNotNullParameterUseIsMemberAccess, firstUse.UsageNode); } return; } // Okay, it's an invocation (method, operator or whatever). It might be a CheckNotNull call, in which case we'll let the other // rule handle it. // TODO: Do we need to check that the right argument is being used here? if (IsCheckNotNull(firstUse.InvokedMember)) { return; } // If the first usage is an equality check, let's assume it's being handled correctly. Examples: // - Checking inequality against non-null values (e.g. in xxx) // - Checking equality against the null literal (e.g. in Period.Compare) if (firstUse.InvokedMember.Name == "op_Equality" || firstUse.InvokedMember.Name == "op_Inequality") { return; } if (HasNotNullAttribute(firstUse.CorrespondingParameter)) { // TODO: Consider the interplay of Trusted here. if (!notNull) { context.ReportDiagnostic(ParameterImplicitlyNotNullCheckedWithoutAttribute, firstUse.UsageNode, parameterSymbol.Name); } if (firstUse.CorrespondingParameter.Name != parameterSymbol.Name) { context.ReportDiagnostic(NotNullParameterCheckedWithWrongName, firstUse.UsageNode, parameterSymbol.Name, firstUse.CorrespondingParameter.Name); } return; } if (notNull) { // First usage doesn't check for nullity, despite this being a NotNull parameter. context.ReportDiagnostic(NotNullParameterIsNotChecked, parameterSyntax.Identifier, parameterSymbol.Name); } // TODO: Check later uses? }