/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs
413 строк
19 KB
AlekseyTs
SourceMemberMethodSymbol.IsMetadataVirtual should force complete declaring type when queried by a different module (#84013)
05 июн 2026, 17:03
Не верифицирован
05 июн 2026, 17:03
04c548a
Код
Авторство
О чём код?
// 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.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Emit; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CSharp.Symbols { internal abstract partial class MethodToClassRewriter : BoundTreeToDifferentEnclosingContextRewriter { // For each captured variable, information about its replacement. May be populated lazily (that is, not all // upfront) by subclasses. Specifically, the async rewriter produces captured symbols for temps, including // ref locals, lazily. // The lambda rewriter also saves/restores the proxies across passes, since local function // reference rewriting is done in a separate pass but still requires the frame proxies // created in the first pass. protected Dictionary<Symbol, CapturedSymbolReplacement> proxies = new Dictionary<Symbol, CapturedSymbolReplacement>(); // Subclasses override this method to fetch a frame pointer. protected abstract BoundExpression FramePointer(SyntaxNode syntax, NamedTypeSymbol frameClass); // Containing type for any synthesized members. protected abstract NamedTypeSymbol ContainingType { get; } /// <summary> A not-null collection of synthesized methods generated for the current source type. </summary> protected readonly TypeCompilationState CompilationState; protected readonly BindingDiagnosticBag Diagnostics; protected readonly VariableSlotAllocator? slotAllocator; protected MethodToClassRewriter(VariableSlotAllocator? slotAllocator, TypeCompilationState compilationState, BindingDiagnosticBag diagnostics) { Debug.Assert(compilationState != null); Debug.Assert(diagnostics != null); Debug.Assert(diagnostics.DiagnosticBag != null); this.CompilationState = compilationState; this.Diagnostics = diagnostics; this.slotAllocator = slotAllocator; } /// <summary> /// Returns true if the specified local/parameter needs to be hoisted to a field. /// Variable may be hoisted even if it is not captured, to improve debugging experience. /// </summary> protected abstract bool NeedsProxy(Symbol localOrParameter); protected sealed override bool TryRewriteLocal(LocalSymbol local, [NotNullWhen(true)] out LocalSymbol? newLocal) { if (NeedsProxy(local)) { // no longer a local symbol newLocal = null; return false; } return base.TryRewriteLocal(local, out newLocal); } public abstract override BoundNode VisitScope(BoundScope node); public override BoundNode VisitForStatement(BoundForStatement node) { throw ExceptionUtilities.Unreachable(); } public override BoundNode VisitDoStatement(BoundDoStatement node) { throw ExceptionUtilities.Unreachable(); } public override BoundNode VisitWhileStatement(BoundWhileStatement node) { throw ExceptionUtilities.Unreachable(); } public override BoundNode VisitUsingStatement(BoundUsingStatement node) { throw ExceptionUtilities.Unreachable(); } public override BoundNode VisitPropertyAccess(BoundPropertyAccess node) { var rewrittenPropertySymbol = VisitPropertySymbol(node.PropertySymbol); var rewrittenReceiver = (BoundExpression?)Visit(node.ReceiverOpt); return node.Update(rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, rewrittenPropertySymbol, node.AutoPropertyAccessorKind, node.ResultKind, VisitType(node.Type)); } public override BoundNode VisitCall(BoundCall node) { var rewrittenMethodSymbol = VisitMethodSymbol(node.Method); var rewrittenReceiver = (BoundExpression?)this.Visit(node.ReceiverOpt); var rewrittenArguments = (ImmutableArray<BoundExpression>)this.VisitList(node.Arguments); var rewrittenType = this.VisitType(node.Type); // If the original receiver was a base access and it was rewritten, // change the method to point to the wrapper method if (BaseReferenceInReceiverWasRewritten(node.ReceiverOpt, rewrittenReceiver)) { if (node.Method.IsMetadataVirtual(CompilationState.Compilation.SourceModule)) { Debug.Assert(rewrittenMethodSymbol.IsMetadataVirtual(CompilationState.Compilation.SourceModule)); rewrittenMethodSymbol = GetMethodWrapperForBaseNonVirtualCall(rewrittenMethodSymbol, node.Syntax); } else { Debug.Assert(!rewrittenMethodSymbol.IsMetadataVirtual(CompilationState.Compilation.SourceModule)); } } return node.Update( rewrittenReceiver, initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown, rewrittenMethodSymbol, rewrittenArguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.IsDelegateCall, node.Expanded, node.InvokedAsExtensionMethod, node.ArgsToParamsOpt, node.DefaultArguments, node.ResultKind, rewrittenType); } private MethodSymbol GetMethodWrapperForBaseNonVirtualCall(MethodSymbol methodBeingCalled, SyntaxNode syntax) { var newMethod = GetOrCreateBaseFunctionWrapper(methodBeingCalled, syntax); if (!newMethod.IsGenericMethod) { return newMethod; } // for generic methods we need to construct the method to be actually called Debug.Assert(methodBeingCalled.IsGenericMethod); var typeArgs = methodBeingCalled.TypeArgumentsWithAnnotations; Debug.Assert(typeArgs.Length == newMethod.Arity); var visitedTypeArgs = ArrayBuilder<TypeWithAnnotations>.GetInstance(typeArgs.Length); foreach (var typeArg in typeArgs) { visitedTypeArgs.Add(typeArg.WithTypeAndModifiers(VisitType(typeArg.Type), typeArg.CustomModifiers)); } return newMethod.Construct(visitedTypeArgs.ToImmutableAndFree()); } private MethodSymbol GetOrCreateBaseFunctionWrapper(MethodSymbol methodBeingWrapped, SyntaxNode syntax) { methodBeingWrapped = methodBeingWrapped.ConstructedFrom; MethodSymbol? wrapper = this.CompilationState.GetMethodWrapper(methodBeingWrapped); if (wrapper is not null) { return wrapper; } var containingType = this.ContainingType; // create a method symbol string methodName = GeneratedNames.MakeBaseMethodWrapperName(this.CompilationState.NextWrapperMethodIndex); wrapper = new BaseMethodWrapperSymbol(containingType, methodBeingWrapped, syntax, methodName); // add the method to module if (this.CompilationState.Emitting) { this.CompilationState.ModuleBuilderOpt.AddSynthesizedDefinition(containingType, wrapper.GetCciAdapter()); } Debug.Assert(wrapper.SynthesizesLoweredBoundBody); wrapper.GenerateMethodBody(this.CompilationState, this.Diagnostics); return wrapper; } /// <remarks>Any new usage of this method will need a similar update in <see cref="RuntimeAsyncRewriter"/></remarks> private bool TryReplaceWithProxy(Symbol parameterOrLocal, SyntaxNode syntax, [NotNullWhen(true)] out BoundNode? replacement) { if (proxies.TryGetValue(parameterOrLocal, out CapturedSymbolReplacement? proxy)) { replacement = proxy.Replacement( syntax, static (frameType, arg) => arg.self.FramePointer(arg.syntax, frameType), (syntax, self: this)); return true; } replacement = null; return false; } public sealed override BoundNode VisitParameter(BoundParameter node) { if (TryReplaceWithProxy(node.ParameterSymbol, node.Syntax, out BoundNode? replacement)) { return replacement; } // Non-captured and expression tree lambda parameters don't have a proxy. return VisitUnhoistedParameter(node); } protected virtual BoundNode VisitUnhoistedParameter(BoundParameter node) { return base.VisitParameter(node)!; } public sealed override BoundNode VisitLocal(BoundLocal node) { if (TryReplaceWithProxy(node.LocalSymbol, node.Syntax, out BoundNode? replacement)) { return replacement; } // if a local needs a proxy it should have been allocated by its declaration node. Debug.Assert(!NeedsProxy(node.LocalSymbol)); return base.VisitLocal(node)!; } public override BoundNode? VisitLocalId(BoundLocalId node) => TryGetHoistedField(node.Local, out var fieldSymbol) ? node.Update(node.Local, fieldSymbol, node.Type) : base.VisitLocalId(node); public override BoundNode? VisitParameterId(BoundParameterId node) => TryGetHoistedField(node.Parameter, out var fieldSymbol) ? node.Update(node.Parameter, fieldSymbol, node.Type) : base.VisitParameterId(node); private bool TryGetHoistedField(Symbol variable, [NotNullWhen(true)] out FieldSymbol? field) { if (proxies.TryGetValue(variable, out CapturedSymbolReplacement? proxy)) { field = proxy switch { CapturedToStateMachineFieldReplacement stateMachineProxy => (FieldSymbol)stateMachineProxy.HoistedField, CapturedToFrameSymbolReplacement closureProxy => closureProxy.HoistedField, _ => throw ExceptionUtilities.UnexpectedValue(proxy) }; return true; } field = null; return false; } public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node) { BoundExpression originalLeft = node.Left; if (originalLeft.Kind != BoundKind.Local) { return base.VisitAssignmentOperator(node)!; } var leftLocal = (BoundLocal)originalLeft; BoundExpression originalRight = node.Right; if (leftLocal.LocalSymbol.RefKind != RefKind.None && node.IsRef && NeedsProxy(leftLocal.LocalSymbol)) { Debug.Assert(!proxies.ContainsKey(leftLocal.LocalSymbol)); Debug.Assert(originalRight.Kind != BoundKind.ConvertedStackAllocExpression); //spilling ref local variables throw ExceptionUtilities.Unreachable(); } if (NeedsProxy(leftLocal.LocalSymbol) && !proxies.ContainsKey(leftLocal.LocalSymbol)) { Debug.Assert(leftLocal.LocalSymbol.DeclarationKind == LocalDeclarationKind.None); // spilling temp variables throw ExceptionUtilities.Unreachable(); } BoundExpression rewrittenLeft = (BoundExpression)this.Visit(leftLocal); BoundExpression rewrittenRight = (BoundExpression)this.Visit(originalRight); TypeSymbol rewrittenType = VisitType(node.Type); // Check if we're assigning the result of stackalloc to a hoisted local. // If we are, we need to store the result in a temp local and then assign // the value of the local to the field corresponding to the hoisted local. // If the receiver of the field is on the stack when the stackalloc happens, // popping it will free the memory (?) or otherwise cause verification issues. // DevDiv Bugs 59454 if (rewrittenLeft.Kind != BoundKind.Local && originalRight.Kind == BoundKind.ConvertedStackAllocExpression) { // From ILGENREC::genAssign: // DevDiv Bugs 59454: Handle hoisted local initialized with a stackalloc // NOTE: Need to check for cast of stackalloc on RHS. // If LHS isLocal, then genAddr is a noop so regular case works fine. SyntheticBoundNodeFactory factory = new SyntheticBoundNodeFactory(this.CurrentMethod, rewrittenLeft.Syntax, this.CompilationState, this.Diagnostics); BoundAssignmentOperator tempAssignment; BoundLocal tempLocal = factory.StoreToTemp(rewrittenRight, out tempAssignment); Debug.Assert(!node.IsRef); BoundAssignmentOperator rewrittenAssignment = node.Update(rewrittenLeft, tempLocal, node.IsRef, rewrittenType); return new BoundSequence( node.Syntax, ImmutableArray.Create<LocalSymbol>(tempLocal.LocalSymbol), ImmutableArray.Create<BoundExpression>(tempAssignment), rewrittenAssignment, rewrittenType); } return node.Update(rewrittenLeft, rewrittenRight, node.IsRef, rewrittenType); } public override BoundNode VisitFieldInfo(BoundFieldInfo node) { var rewrittenField = ((FieldSymbol)node.Field.OriginalDefinition) .AsMember((NamedTypeSymbol)this.VisitType(node.Field.ContainingType)); return node.Update(rewrittenField, node.GetFieldFromHandle, node.Type); } public override BoundNode VisitFieldAccess(BoundFieldAccess node) { var receiverOpt = (BoundExpression?)this.Visit(node.ReceiverOpt); TypeSymbol type = this.VisitType(node.Type); var fieldSymbol = ((FieldSymbol)node.FieldSymbol.OriginalDefinition) .AsMember((NamedTypeSymbol)this.VisitType(node.FieldSymbol.ContainingType)); return node.Update(receiverOpt, fieldSymbol, node.ConstantValueOpt, node.ResultKind, type); } public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationExpression node) { BoundExpression originalArgument = node.Argument; BoundExpression rewrittenArgument = (BoundExpression)this.Visit(originalArgument); MethodSymbol? method = node.MethodOpt; // if the original receiver was BoundKind.BaseReference (i.e. from a method group) // and the receiver is overridden, change the method to point to a wrapper method if (BaseReferenceInReceiverWasRewritten(originalArgument, rewrittenArgument) && method!.IsMetadataVirtual(CompilationState.Compilation.SourceModule)) { method = GetMethodWrapperForBaseNonVirtualCall(method, originalArgument.Syntax); } method = VisitMethodSymbol(method); TypeSymbol type = this.VisitType(node.Type); return node.Update(rewrittenArgument, method, node.IsExtensionMethod, node.WasTargetTyped, type); } public override BoundNode VisitObjectInitializerMember(BoundObjectInitializerMember node) { ImmutableArray<BoundExpression> arguments = (ImmutableArray<BoundExpression>)this.VisitList(node.Arguments); TypeSymbol type = this.VisitType(node.Type); TypeSymbol receiverType = this.VisitType(node.ReceiverType); var member = node.MemberSymbol; Debug.Assert(member is not null); switch (member.Kind) { case SymbolKind.Field: member = VisitFieldSymbol((FieldSymbol)member); break; case SymbolKind.Property: member = VisitPropertySymbol((PropertySymbol)member); break; } return node.Update(member, arguments, node.ArgumentNamesOpt, node.ArgumentRefKindsOpt, node.Expanded, node.ArgsToParamsOpt, node.DefaultArguments, node.ResultKind, node.AccessorKind, receiverType, type); } private static bool BaseReferenceInReceiverWasRewritten([NotNullWhen(true)] BoundExpression? originalReceiver, [NotNullWhen(true)] BoundExpression? rewrittenReceiver) { return originalReceiver is { Kind: BoundKind.BaseReference } && rewrittenReceiver is { Kind: not BoundKind.BaseReference }; } /// <summary> /// A wrapper method that is created for non-virtually calling a base-class /// virtual method from other classes (like those created for lambdas...). /// </summary> internal sealed partial class BaseMethodWrapperSymbol : SynthesizedMethodBaseSymbol { internal BaseMethodWrapperSymbol(NamedTypeSymbol containingType, MethodSymbol methodBeingWrapped, SyntaxNode syntax, string name) : base(containingType, methodBeingWrapped, syntax.SyntaxTree.GetReference(syntax), syntax.GetLocation(), name, DeclarationModifiers.Private, isIterator: false) { Debug.Assert(containingType.ContainingModule is SourceModuleSymbol); Debug.Assert(ReferenceEquals(methodBeingWrapped, methodBeingWrapped.ConstructedFrom)); Debug.Assert(!methodBeingWrapped.IsStatic); TypeMap? typeMap = methodBeingWrapped.ContainingType is SubstitutedNamedTypeSymbol substitutedType ? substitutedType.TypeSubstitution : TypeMap.Empty; ImmutableArray<TypeParameterSymbol> typeParameters; if (!methodBeingWrapped.IsGenericMethod) { typeParameters = ImmutableArray<TypeParameterSymbol>.Empty; } else { typeMap = typeMap.WithAlphaRename(methodBeingWrapped, this, propagateAttributes: false, out typeParameters); } AssignTypeMapAndTypeParameters(typeMap, typeParameters); } } } }