/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/FlowAnalysis/FlowAnalysisPass.cs
223 строки
12 KB
Cyrus Najmabadi
Improve diagnostic location for top-level-program with a reachable end
29 сен 2025, 16:53
29 сен 2025, 16:53
181beda
Код
Авторство
О чём код?
// 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. #nullable disable using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.CodeAnalysis.CSharp { internal class FlowAnalysisPass { /// <summary> /// The flow analysis pass. This pass reports required diagnostics for unreachable /// statements and uninitialized variables (through the call to FlowAnalysisWalker.Analyze), /// and inserts a final return statement if the end of a void-returning method is reachable. /// </summary> /// <param name="method">the method to be analyzed</param> /// <param name="block">the method's body</param> /// <param name="compilationState">The state of compilation of the enclosing type</param> /// <param name="diagnostics">the receiver of the reported diagnostics</param> /// <param name="hasTrailingExpression">indicates whether this Script had a trailing expression</param> /// <param name="originalBodyNested">the original method body is the last statement in the block</param> /// <returns>the rewritten block for the method (with a return statement possibly inserted)</returns> public static BoundBlock Rewrite( MethodSymbol method, BoundBlock block, TypeCompilationState compilationState, BindingDiagnosticBag diagnostics, bool hasTrailingExpression, bool originalBodyNested) { #if DEBUG // We should only see a trailingExpression if we're in a Script initializer. Debug.Assert(!hasTrailingExpression || method.IsScriptInitializer); var initialDiagnosticCount = diagnostics.ToReadOnly().Diagnostics.Length; #endif var compilation = method.DeclaringCompilation; if (method.ReturnsVoid || method.IsIterator || method.IsAsyncEffectivelyReturningTask(compilation)) { ImmutableArray<FieldSymbol> implicitlyInitializedFields = default; bool needsImplicitReturn = true; // we don't analyze synthesized void methods. if ((method.IsImplicitlyDeclared && !method.IsScriptInitializer) || Analyze(compilation, method, block, diagnostics.DiagnosticBag, out needsImplicitReturn, out implicitlyInitializedFields)) { if (!implicitlyInitializedFields.IsDefault) { Debug.Assert(!implicitlyInitializedFields.IsEmpty); // It's not expected to have implicitly initialized fields when a constructor initializer is present, except in error scenarios. Debug.Assert(method is not SourceMemberMethodSymbol { SyntaxNode: ConstructorDeclarationSyntax { Initializer: not null } } || block.HasErrors); block = PrependImplicitInitializations(block, method, implicitlyInitializedFields, compilationState, diagnostics); } if (needsImplicitReturn) { block = AppendImplicitReturn(block, method, originalBodyNested); } } } else if (Analyze(compilation, method, block, diagnostics.DiagnosticBag, out var needsImplicitReturn, out var unusedImplicitlyInitializedFields)) { Debug.Assert(unusedImplicitlyInitializedFields.IsDefault); Debug.Assert(needsImplicitReturn); // If the method is a lambda expression being converted to a non-void delegate type // and the end point is reachable then suppress the error here; a special error // will be reported by the lambda binder. Debug.Assert(method.MethodKind != MethodKind.AnonymousFunction); // Add implicit "return default(T)" if this is a submission that does not have a trailing expression. var submissionResultType = (method as SynthesizedInteractiveInitializerMethod)?.ResultType; if (!hasTrailingExpression && ((object)submissionResultType != null)) { Debug.Assert(!submissionResultType.IsVoidType()); var trailingExpression = new BoundDefaultExpression(method.GetNonNullSyntaxNode(), submissionResultType); var newStatements = block.Statements.Add(new BoundReturnStatement(trailingExpression.Syntax, RefKind.None, trailingExpression, @checked: false)); block = new BoundBlock(block.Syntax, ImmutableArray<LocalSymbol>.Empty, newStatements) { WasCompilerGenerated = true }; #if DEBUG // It should not be necessary to repeat analysis after adding this node, because adding a trailing // return in cases where one was missing should never produce different Diagnostics. IEnumerable<Diagnostic> getErrorsOnly(IEnumerable<Diagnostic> diags) => diags.Where(d => d.Severity == DiagnosticSeverity.Error); var flowAnalysisDiagnostics = DiagnosticBag.GetInstance(); Debug.Assert(!Analyze(compilation, method, block, flowAnalysisDiagnostics, needsImplicitReturn: out _, out unusedImplicitlyInitializedFields)); Debug.Assert(unusedImplicitlyInitializedFields.IsDefault); // Ignore warnings since flow analysis reports nullability mismatches. Debug.Assert(getErrorsOnly(flowAnalysisDiagnostics.ToReadOnly()).SequenceEqual(getErrorsOnly(diagnostics.ToReadOnly().Diagnostics.Skip(initialDiagnosticCount)))); flowAnalysisDiagnostics.Free(); #endif } // If there's more than one location, then the method is partial and we // have already reported a non-void partial method error. else if (method.Locations.Length == 1) { var location = method is SynthesizedSimpleProgramEntryPointSymbol entryPoint ? entryPoint.ReturnTypeSyntax.GetLocation() : method.GetFirstLocation(); diagnostics.Add(ErrorCode.ERR_ReturnExpected, location, method); } } return block; } private static BoundBlock PrependImplicitInitializations(BoundBlock body, MethodSymbol method, ImmutableArray<FieldSymbol> implicitlyInitializedFields, TypeCompilationState compilationState, BindingDiagnosticBag diagnostics) { Debug.Assert(!implicitlyInitializedFields.IsEmpty); Debug.Assert(method.MethodKind == MethodKind.Constructor); NamedTypeSymbol containingType = method.ContainingType; Debug.Assert(containingType.IsStructType()); var F = new SyntheticBoundNodeFactory(method, body.Syntax, compilationState, diagnostics); var builder = ArrayBuilder<BoundStatement>.GetInstance(implicitlyInitializedFields.Length); // Inline arrays of length > 1 must be initialized completely, simply initializing the element field is not sufficient. // For arrays of length == 1, initializing the element field is sufficient. if (containingType.HasInlineArrayAttribute(out int length) && length > 1 && containingType.TryGetPossiblyUnsupportedByLanguageInlineArrayElementField() is FieldSymbol elementField) { Debug.Assert(elementField == implicitlyInitializedFields.Single()); // this = default; builder.Add( F.ExpressionStatement( F.AssignmentExpression( F.This(), F.Default(containingType)))); } else { foreach (var field in implicitlyInitializedFields) { if (field.RefKind == RefKind.None) { // field = default(T); builder.Add( F.ExpressionStatement( F.AssignmentExpression( F.Field(F.This(), field), F.Default(field.Type)))); } else { // field = ref *default(T*); builder.Add( F.ExpressionStatement( F.AssignmentExpression( F.Field(F.This(), field), F.NullRef(field.TypeWithAnnotations), isRef: true))); } } } var initializations = F.HiddenSequencePoint(F.Block(builder.ToImmutableAndFree())); return body.Update(body.Locals, body.LocalFunctions, body.HasUnsafeModifier, body.Instrumentation, body.Statements.Insert(index: 0, initializations)); } private static BoundBlock AppendImplicitReturn(BoundBlock body, MethodSymbol method, bool originalBodyNested) { if (originalBodyNested) { var statements = body.Statements; int n = statements.Length; var builder = ArrayBuilder<BoundStatement>.GetInstance(n); builder.AddRange(statements, n - 1); builder.Add(AppendImplicitReturn((BoundBlock)statements[n - 1], method)); return body.Update(body.Locals, ImmutableArray<MethodSymbol>.Empty, body.HasUnsafeModifier, body.Instrumentation, builder.ToImmutableAndFree()); } else { return AppendImplicitReturn(body, method); } } // insert the implicit "return" statement at the end of the method body // Normally, we wouldn't bother attaching syntax trees to compiler-generated nodes, but these // ones are going to have sequence points. internal static BoundBlock AppendImplicitReturn(BoundBlock body, MethodSymbol method) { Debug.Assert(body != null); Debug.Assert(method != null); SyntaxNode syntax = body.Syntax; Debug.Assert(body.WasCompilerGenerated || syntax.IsKind(SyntaxKind.Block) || syntax.IsKind(SyntaxKind.ArrowExpressionClause) || syntax.IsKind(SyntaxKind.ConstructorDeclaration) || syntax.IsKind(SyntaxKind.CompilationUnit)); BoundStatement ret = (method.IsIterator && !method.IsAsync) ? (BoundStatement)BoundYieldBreakStatement.Synthesized(syntax) : BoundReturnStatement.Synthesized(syntax, RefKind.None, null); return body.Update(body.Locals, body.LocalFunctions, body.HasUnsafeModifier, body.Instrumentation, body.Statements.Add(ret)); } private static bool Analyze( CSharpCompilation compilation, MethodSymbol method, BoundBlock block, DiagnosticBag diagnostics, out bool needsImplicitReturn, out ImmutableArray<FieldSymbol> implicitlyInitializedFieldsOpt) { needsImplicitReturn = ControlFlowPass.Analyze(compilation, method, block, diagnostics); DefiniteAssignmentPass.Analyze(compilation, method, block, diagnostics, out implicitlyInitializedFieldsOpt, requireOutParamsAssigned: true); return needsImplicitReturn || !implicitlyInitializedFieldsOpt.IsDefault; } } }