/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Portable/CodeGen/CodeGenerator_HasHome.cs
264 строки
11 KB
AlekseyTs
Fix crash in code gen (#84681)
30 июл 2026, 15:07
Не верифицирован
30 июл 2026, 15:07
677eb5a
Код
Авторство
О чём код?
// 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.Diagnostics; using Microsoft.CodeAnalysis.CSharp.Symbols; namespace Microsoft.CodeAnalysis.CSharp.CodeGen; internal partial class CodeGenerator { internal enum AddressKind { // reference may be written to Writeable, // reference itself will not be written to, but may be used for call, callvirt. // for all purposes it is the same as Writeable, except when fetching an address of an array element // where it results in a ".readonly" prefix to deal with array covariance. Constrained, // reference itself will not be written to, nor it will be used to modify fields. ReadOnly, // same as ReadOnly, but we are not supposed to get a reference to a clone // regardless of compat settings. ReadOnlyStrict, } internal static bool IsAnyReadOnly(AddressKind addressKind) => addressKind >= AddressKind.ReadOnly; /// <summary> /// Checks if expression directly or indirectly represents a value with its own home. In /// such cases it is possible to get a reference without loading into a temporary. /// </summary> /// <param name="expression"> /// This should be a lowered node. This method does NOT expect nodes from initial binding. /// </param> internal static bool HasHome( BoundExpression expression, AddressKind addressKind, Symbol containingSymbol, bool peVerifyCompatEnabled, HashSet<LocalSymbol> stackLocalsOpt) { Debug.Assert(containingSymbol is object); switch (expression.Kind) { case BoundKind.ArrayAccess: if (addressKind == AddressKind.ReadOnly && !expression.Type.IsValueType && peVerifyCompatEnabled) { // due to array covariance getting a reference may throw ArrayTypeMismatch when element is not a struct, // passing "readonly." prefix would prevent that, but it is unverifiable, so will make a copy in compat case return false; } return true; case BoundKind.PointerIndirectionOperator: case BoundKind.RefValueOperator: return true; case BoundKind.ThisReference: var type = expression.Type; if (type.IsReferenceType) { Debug.Assert(IsAnyReadOnly(addressKind), "`this` is readonly in classes"); return true; } if (!IsAnyReadOnly(addressKind) && containingSymbol is MethodSymbol { ContainingSymbol: NamedTypeSymbol, IsEffectivelyReadOnly: true }) { return false; } return true; case BoundKind.ThrowExpression: // vacuously this is true, we can take address of throw without temps return true; case BoundKind.Parameter: return IsAnyReadOnly(addressKind) || ((BoundParameter)expression).ParameterSymbol.RefKind is not (RefKind.In or RefKind.RefReadOnlyParameter); case BoundKind.Local: // locals have home unless they are byval stack locals or ref-readonly // locals in a mutating call var local = ((BoundLocal)expression).LocalSymbol; return !((CodeGenerator.IsStackLocal(local, stackLocalsOpt) && local.RefKind == RefKind.None) || (!IsAnyReadOnly(addressKind) && local.RefKind == RefKind.RefReadOnly)); case BoundKind.Call: var methodRefKind = ((BoundCall)expression).Method.RefKind; return methodRefKind == RefKind.Ref || (IsAnyReadOnly(addressKind) && methodRefKind == RefKind.RefReadOnly); case BoundKind.Dup: //NB: Dup represents locals that do not need IL slot var dupRefKind = ((BoundDup)expression).RefKind; return dupRefKind == RefKind.Ref || (IsAnyReadOnly(addressKind) && dupRefKind == RefKind.RefReadOnly); case BoundKind.FieldAccess: return FieldAccessHasHome((BoundFieldAccess)expression, addressKind, containingSymbol, peVerifyCompatEnabled, stackLocalsOpt); case BoundKind.Sequence: return HasHome(((BoundSequence)expression).Value, addressKind, containingSymbol, peVerifyCompatEnabled, stackLocalsOpt); case BoundKind.AssignmentOperator: var assignment = (BoundAssignmentOperator)expression; if (!assignment.IsRef) { return false; } var lhsRefKind = assignment.Left.GetRefKind(); return lhsRefKind == RefKind.Ref || (IsAnyReadOnly(addressKind) && lhsRefKind is RefKind.RefReadOnly or RefKind.RefReadOnlyParameter); case BoundKind.RefArrayAccess: var right = (BoundRefArrayAccess)expression; return HasHome(right.ArrayAccess, AddressKind.Writeable, containingSymbol, peVerifyCompatEnabled, stackLocalsOpt); case BoundKind.ComplexConditionalReceiver: Debug.Assert(HasHome( ((BoundComplexConditionalReceiver)expression).ValueTypeReceiver, addressKind, containingSymbol, peVerifyCompatEnabled, stackLocalsOpt)); Debug.Assert(HasHome( ((BoundComplexConditionalReceiver)expression).ReferenceTypeReceiver, addressKind, containingSymbol, peVerifyCompatEnabled, stackLocalsOpt)); goto case BoundKind.ConditionalReceiver; case BoundKind.ConditionalReceiver: //ConditionalReceiver is a noop from Emit point of view. - it represents something that has already been pushed. //We should never need a temp for it. return true; case BoundKind.ConditionalOperator: var conditional = (BoundConditionalOperator)expression; // only ref conditional may be referenced as a variable if (!conditional.IsRef) { return false; } // branch that has no home will need a temporary // if both have no home, just say whole expression has no home // so we could just use one temp for the whole thing return HasHome(conditional.Consequence, addressKind, containingSymbol, peVerifyCompatEnabled, stackLocalsOpt) && HasHome(conditional.Alternative, addressKind, containingSymbol, peVerifyCompatEnabled, stackLocalsOpt); default: return false; } } /// <summary> /// Special HasHome for fields. /// A field has a readable home unless the field is a constant. /// A ref readonly field doesn't have a writable home. /// Other fields have a writable home unless the field is a readonly value /// and is used outside of a constructor or init method. /// </summary> private static bool FieldAccessHasHome( BoundFieldAccess fieldAccess, AddressKind addressKind, Symbol containingSymbol, bool peVerifyCompatEnabled, HashSet<LocalSymbol> stackLocalsOpt) { Debug.Assert(containingSymbol is object); FieldSymbol field = fieldAccess.FieldSymbol; // const fields are literal values with no homes. (ex: decimal.Zero) if (field.IsConst) { return false; } if (field.RefKind is RefKind.Ref) { return true; } // in readonly situations where ref to a copy is not allowed, consider fields as addressable if (addressKind == AddressKind.ReadOnlyStrict) { return true; } // ReadOnly references can always be taken unless we are in peverify compat mode if (addressKind == AddressKind.ReadOnly && !peVerifyCompatEnabled) { return true; } // Some field accesses must be values; values do not have homes. if (fieldAccess.IsByValue) { return false; } if (field.RefKind == RefKind.RefReadOnly) { return false; } Debug.Assert(field.RefKind == RefKind.None); if (!field.IsReadOnly) { // in a case if we have a writeable struct field with a receiver that only has a readable home we would need to pass it via a temp. // it would be advantageous to make a temp for the field, not for the outer struct, since the field is smaller and we can get to is by fetching references. // NOTE: this would not be profitable if we have to satisfy verifier, since for verifiability // we would not be able to dig for the inner field using references and the outer struct will have to be copied to a temp anyways. if (!peVerifyCompatEnabled) { Debug.Assert(!IsAnyReadOnly(addressKind)); var receiver = fieldAccess.ReceiverOpt; if (receiver?.Type.IsValueType == true) { // Check receiver: // has writeable home -> return true - the whole chain has writeable home (also a more common case) // has readable home -> return false - we need to copy the field // otherwise -> return true - the copy will be made at higher level so the leaf field can have writeable home return HasHome(receiver, addressKind, containingSymbol, peVerifyCompatEnabled, stackLocalsOpt) || !HasHome(receiver, AddressKind.ReadOnly, containingSymbol, peVerifyCompatEnabled, stackLocalsOpt); } } return true; } // while readonly fields have home it is not valid to refer to it when not constructing. if (!TypeSymbol.Equals(field.ContainingType, containingSymbol.ContainingSymbol as NamedTypeSymbol, TypeCompareKind.AllIgnoreOptions)) { return false; } if (field.IsStatic) { return containingSymbol is MethodSymbol { MethodKind: MethodKind.StaticConstructor } or FieldSymbol { IsStatic: true }; } else { return (containingSymbol is MethodSymbol { MethodKind: MethodKind.Constructor } or FieldSymbol { IsStatic: false } or MethodSymbol { IsInitOnly: true }) && fieldAccess.ReceiverOpt.Kind == BoundKind.ThisReference; } } }