/// <summary> /// Checks if expression is an explicit function parameter null-check and, if so, valuates it /// based on the assumption that parameter is non-null. /// </summary> /// <param name="valContext">Valuation context</param> /// <param name="expression">Expression to valuate</param> /// <param name="state">Valuation state</param> /// <returns>Expression valuation</returns> public bool?Valuate(NullCheckContext valContext, ExpressionSyntax expression, NullCheckState state) { if (state.filterParams != null && !state.filterParams.Any()) { return(null); } bool IsNullLiteral(ExpressionSyntax expr) => expr.IsKind(SyntaxKind.NullLiteralExpression); bool IsParameterRef(NullCheckContext lContext, ExpressionSyntax lExpression, NullCheckState lState) { var param = ParameterRefValidator.Validate(lContext, lExpression, lState); if (param != null) { lState.affectedParams.Add(param); return(true); } return(false); } if (!expression.IsKind(SyntaxKind.EqualsExpression) && !expression.IsKind(SyntaxKind.NotEqualsExpression)) { return(null); // not <smth> ==(!=) <smth> } // param != null === true bool valuation = expression.IsKind(SyntaxKind.NotEqualsExpression); var binaryExpr = (BinaryExpressionSyntax)expression; if (IsParameterRef(valContext, binaryExpr.Left, state) && IsNullLiteral(binaryExpr.Right) || IsParameterRef(valContext, binaryExpr.Right, state) && IsNullLiteral(binaryExpr.Left)) { // ok, param ==(!=) null or vice-versa return(valuation); } return(null); }
/// <summary> /// Checks if expression is a call to a method that has /// a null-check of reference-typed function parameters (contained in a filtering set, /// if it is provided) as its body and stores the parameters' symbols into state. /// Otherwise (or if method's body cannot be found) clears the state and returns <c>null</c>. /// </summary> /// <param name="valContext">Valuation context</param> /// <param name="expression">Expression to check</param> /// <param name="state">State to mutate</param> /// <returns>If the expression is a call to a method that has a null-check of /// reference-typed function parameters, the result is valuation of the method (<c>param != null</c>), /// and <c>null</c> otherwise or if method's body cannot be accessed.</returns> public bool?Valuate(NullCheckContext valContext, ExpressionSyntax expression, NullCheckState state) { if (!state.HasFilters) { state.Clear(); return(null); // everything is filtered out. no point in continuing. } if (!(expression is InvocationExpressionSyntax invocation)) { return(null); // expression is not an invocation } var definition = GetFunctionDefinition(valContext, invocation); if (definition == null || definition.SyntaxTree != valContext.context.SemanticModel.SyntaxTree) { // does not return bool or could not find body in this syntax tree return(null); } // create (callee argument, callee parameter) pairs var argZipParam = invocation.ArgumentList.Arguments.Zip( from param in definition.ParameterList.Parameters select valContext.context.SemanticModel.GetDeclaredSymbol(param), (arg, param) => new { arg, param } ); // select only callee arguments that are passed through from caller parameters var passthruArgsAndParams = from pair in argZipParam select new { arg = ParameterRefValidator.Validate(valContext, pair.arg.Expression, state), pair.param }; passthruArgsAndParams = from pair in passthruArgsAndParams where pair.arg != null select pair; // select the callee parameters for filtering var passthruParams = from pair in passthruArgsAndParams select pair.param; ExpressionSyntax bodyExpr; if (definition.Body != null) { // supports only body consisting of one return statement. bodyExpr = (definition.Body.Statements.FirstOrDefault() as ReturnStatementSyntax)?.Expression; if (bodyExpr == null) { return(null); } } else { bodyExpr = definition.ExpressionBody.Expression; } // find null check of passed-through caller parameters inside the callee var localState = new NullCheckState(new HashSet <IParameterSymbol>(passthruParams)); var valuation = valContext.valuationProvider.Valuate(valContext, bodyExpr, localState); // for each found checked callee parameter find corresponding caller parameter, // as correct filtering is already done // (this also clears the state if valuation failed) state.affectedParams = (from el in (from localSymbol in localState.affectedParams select passthruArgsAndParams.First( pair => SymbolEqualityComparer.Default.Equals(pair.param, localSymbol) ).arg) select el).ToList(); return(valuation); }