mirror of https://github.com/icsharpcode/ILSpy.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
754 lines
23 KiB
754 lines
23 KiB
// Copyright (c) 2026 Sebastien Lebreton |
|
// |
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this |
|
// software and associated documentation files (the "Software"), to deal in the Software |
|
// without restriction, including without limitation the rights to use, copy, modify, merge, |
|
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons |
|
// to whom the Software is furnished to do so, subject to the following conditions: |
|
// |
|
// The above copyright notice and this permission notice shall be included in all copies or |
|
// substantial portions of the Software. |
|
// |
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
|
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR |
|
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE |
|
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
|
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
|
// DEALINGS IN THE SOFTWARE. |
|
|
|
#nullable enable |
|
|
|
using System.Collections.Generic; |
|
using System.Diagnostics; |
|
using System.Linq; |
|
|
|
using ICSharpCode.Decompiler.IL.Transforms; |
|
using ICSharpCode.Decompiler.TypeSystem; |
|
|
|
namespace ICSharpCode.Decompiler.IL |
|
{ |
|
/// <summary> |
|
/// Infers the C# <c>scoped</c> modifier on locals whose initializer has a wider |
|
/// ref-safety context than a later assignment. |
|
/// </summary> |
|
/// <remarks> |
|
/// The modifier is erased from IL and PDBs, so recovery compares the ref-safe-context |
|
/// (for ref locals) or safe-context (for ref-struct locals) of the declaration initializer |
|
/// with each later store. The three contexts are ordered from narrowest to widest: |
|
/// function-member (current method), return-only, caller-context (calling method). |
|
/// A narrower later store proves that the original declaration required <c>scoped</c>. |
|
/// </remarks> |
|
class IntroduceScopedModifierOnLocals : IILTransform |
|
{ |
|
public void Run(ILFunction function, ILTransformContext context) |
|
{ |
|
if (!context.Settings.ScopedRef) |
|
return; |
|
|
|
foreach (var nestedFunction in function.Descendants.OfType<ILFunction>()) |
|
{ |
|
if (nestedFunction.Method is not { |
|
ParentModule: MetadataModule { UseUpdatedEscapeRules: true } |
|
} method) |
|
{ |
|
continue; |
|
} |
|
|
|
new Analyzer(nestedFunction, method, context).Run(); |
|
} |
|
} |
|
|
|
enum SafeContext : byte |
|
{ |
|
CurrentMethod, // function-member |
|
ReturnOnly, // return-only |
|
CallingMethod, // caller-context |
|
} |
|
|
|
readonly struct LocalScopes( |
|
SafeContext? refEscape, |
|
SafeContext? valEscape, |
|
bool isScoped, |
|
bool isScopedWithoutInitializer) |
|
{ |
|
public SafeContext? RefEscape { get; } = refEscape; |
|
public SafeContext? ValEscape { get; } = valEscape; |
|
public bool IsScoped { get; } = isScoped; |
|
public bool IsScopedWithoutInitializer { get; } = isScopedWithoutInitializer; |
|
} |
|
|
|
sealed class Analyzer(ILFunction function, IMethod method, ILTransformContext context) |
|
{ |
|
readonly Dictionary<ILVariable, StLoc[]> _stores = []; |
|
readonly Dictionary<ILVariable, List<StObj>> _fieldStores = []; |
|
readonly Dictionary<ILVariable, List<(CallInstruction Call, int ArgumentIndex)>> _callCaptures = []; |
|
Dictionary<ILVariable, LocalScopes> _scopes = []; |
|
|
|
public void Run() |
|
{ |
|
foreach (var variable in function.Variables) |
|
{ |
|
variable.IsScoped = false; |
|
variable.IsScopedWithoutInitializer = false; |
|
if (variable.Kind is not (VariableKind.Local or VariableKind.StackSlot) |
|
|| !variable.Type.IsRefLikeOrAllowsRefLikeType() |
|
|| variable.UsesInitialValue) |
|
{ |
|
continue; |
|
} |
|
|
|
var stores = variable.StoreInstructions.OfType<StLoc>().ToArray(); |
|
if (stores.Length == 0) |
|
continue; |
|
Debug.Assert(stores.All(store => store.Parent != null)); |
|
|
|
_stores.Add(variable, stores); |
|
_scopes.Add(variable, default); |
|
} |
|
|
|
foreach (var store in function.Descendants.OfType<StObj>()) |
|
{ |
|
foreach (var variable in GetStorageRoots(store.Target).Distinct()) |
|
{ |
|
if (!_stores.ContainsKey(variable)) |
|
continue; |
|
|
|
if (!_fieldStores.TryGetValue(variable, out var stores)) |
|
{ |
|
stores = []; |
|
_fieldStores.Add(variable, stores); |
|
} |
|
stores.Add(store); |
|
} |
|
} |
|
|
|
foreach (var call in function.Descendants.OfType<CallInstruction>()) |
|
{ |
|
for (int argumentIndex = 0; argumentIndex < call.Arguments.Count; argumentIndex++) |
|
{ |
|
if (!IsWritableRefStructArgument(call, argumentIndex)) |
|
continue; |
|
|
|
foreach (var variable in GetStorageRoots(call.Arguments[argumentIndex]).Distinct()) |
|
{ |
|
if (!_stores.ContainsKey(variable)) |
|
continue; |
|
|
|
if (!_callCaptures.TryGetValue(variable, out var captures)) |
|
{ |
|
captures = []; |
|
_callCaptures.Add(variable, captures); |
|
} |
|
captures.Add((call, argumentIndex)); |
|
} |
|
} |
|
} |
|
|
|
for (int iteration = 0; iteration <= _stores.Count * 3; iteration++) |
|
{ |
|
context.CancellationToken.ThrowIfCancellationRequested(); |
|
bool changed = false; |
|
var nextScopes = new Dictionary<ILVariable, LocalScopes>(_scopes); |
|
foreach (var pair in _stores) |
|
{ |
|
ILVariable variable = pair.Key; |
|
StLoc[] stores = pair.Value; |
|
var initializer = GetFirstStore(stores); |
|
LocalScopes scopes = CalculateLocalScopes(variable, initializer, stores); |
|
LocalScopes previous = _scopes[variable]; |
|
if (scopes.RefEscape != previous.RefEscape |
|
|| scopes.ValEscape != previous.ValEscape |
|
|| scopes.IsScoped != previous.IsScoped |
|
|| scopes.IsScopedWithoutInitializer != previous.IsScopedWithoutInitializer) |
|
{ |
|
nextScopes[variable] = scopes; |
|
changed = true; |
|
} |
|
} |
|
|
|
_scopes = nextScopes; |
|
if (!changed) |
|
break; |
|
} |
|
|
|
foreach (var pair in _scopes) |
|
{ |
|
pair.Key.IsScoped = pair.Value.IsScoped; |
|
pair.Key.IsScopedWithoutInitializer = pair.Value.IsScopedWithoutInitializer; |
|
} |
|
} |
|
|
|
LocalScopes CalculateLocalScopes(ILVariable variable, StLoc initializer, IReadOnlyList<StLoc> stores) |
|
{ |
|
bool isRefLocal = variable.Type.Kind == TypeKind.ByReference; |
|
SafeContext? initializerRefEscape = isRefLocal |
|
? GetRefEscape(initializer.Value) |
|
: SafeContext.CurrentMethod; |
|
SafeContext? initializerValEscape = GetVariableValueType(variable).IsRefLikeOrAllowsRefLikeType() |
|
? GetValEscape(initializer.Value) |
|
: SafeContext.CallingMethod; |
|
|
|
if (initializerRefEscape == null || initializerValEscape == null) |
|
return default; |
|
|
|
bool requiresScoped = false; |
|
SafeContext initializerEscape = isRefLocal |
|
? initializerRefEscape.Value |
|
: initializerValEscape.Value; |
|
bool requiresScopedWithoutInitializer = initializerEscape < SafeContext.CallingMethod; |
|
foreach (var store in stores) |
|
{ |
|
if (store == initializer) |
|
continue; |
|
|
|
SafeContext? storeEscape = isRefLocal |
|
? GetRefEscape(store.Value) |
|
: GetValEscape(store.Value); |
|
if (storeEscape is not { } scope) |
|
continue; |
|
|
|
if (scope < SafeContext.CallingMethod) |
|
requiresScopedWithoutInitializer = true; |
|
if (scope < initializerEscape) |
|
{ |
|
requiresScoped = true; |
|
} |
|
} |
|
|
|
if (!requiresScoped && _fieldStores.TryGetValue(variable, out var fieldStores)) |
|
{ |
|
foreach (var store in fieldStores) |
|
{ |
|
if (!initializer.IsBefore(store)) |
|
continue; |
|
|
|
SafeContext? storeEscape = store.Type.Kind == TypeKind.ByReference |
|
? GetRefEscape(store.Value) |
|
: GetValEscape(store.Value); |
|
if (storeEscape is not { } scope) |
|
continue; |
|
|
|
if (scope < SafeContext.CallingMethod) |
|
requiresScopedWithoutInitializer = true; |
|
if (scope < initializerValEscape.Value) |
|
{ |
|
requiresScoped = true; |
|
} |
|
} |
|
} |
|
|
|
if (!requiresScoped && _callCaptures.TryGetValue(variable, out var callCaptures)) |
|
{ |
|
foreach (var (call, argumentIndex) in callCaptures) |
|
{ |
|
if (!initializer.IsBefore(call)) |
|
continue; |
|
|
|
SafeContext? callEscape = GetInvocationEscapeToWritableArgument(call, argumentIndex); |
|
if (callEscape is not { } scope) |
|
continue; |
|
|
|
if (scope < SafeContext.CallingMethod) |
|
requiresScopedWithoutInitializer = true; |
|
if (scope < initializerValEscape.Value) |
|
{ |
|
requiresScoped = true; |
|
} |
|
} |
|
} |
|
|
|
if (!requiresScoped) |
|
{ |
|
return new LocalScopes( |
|
initializerRefEscape, |
|
initializerValEscape, |
|
isScoped: false, |
|
isScopedWithoutInitializer: requiresScopedWithoutInitializer); |
|
} |
|
|
|
return isRefLocal |
|
? new LocalScopes( |
|
SafeContext.CurrentMethod, |
|
SafeContext.CallingMethod, |
|
isScoped: true, |
|
isScopedWithoutInitializer: true) |
|
: new LocalScopes( |
|
SafeContext.CurrentMethod, |
|
SafeContext.CurrentMethod, |
|
isScoped: true, |
|
isScopedWithoutInitializer: true); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the declaration-initializer candidate: the first assignment in the final |
|
/// ILAst traversal order. |
|
/// </summary> |
|
static StLoc GetFirstStore(IReadOnlyList<StLoc> stores) |
|
{ |
|
StLoc first = stores[0]; |
|
for (int i = 1; i < stores.Count; i++) |
|
{ |
|
if (stores[i].IsBefore(first)) |
|
first = stores[i]; |
|
} |
|
|
|
return first; |
|
} |
|
|
|
static bool IsWritableRefStructArgument(CallInstruction call, int argumentIndex) |
|
{ |
|
if (argumentIndex == 0 && call.IsInstanceCall) |
|
{ |
|
return call.Method.DeclaringType.IsRefLikeOrAllowsRefLikeType() |
|
&& !call.Method.ThisIsRefReadOnly; |
|
} |
|
|
|
IParameter? parameter = call.GetParameter(argumentIndex); |
|
return parameter is { ReferenceKind: ReferenceKind.Ref or ReferenceKind.Out } |
|
&& GetValueType(parameter.Type).IsRefLikeOrAllowsRefLikeType(); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the locals whose storage may be reached through an address expression. |
|
/// A ref-return call may alias any eligible ref argument, so every such argument is followed. |
|
/// </summary> |
|
IEnumerable<ILVariable> GetStorageRoots(ILInstruction target) |
|
{ |
|
switch (target) |
|
{ |
|
case LdFlda fieldAddress: |
|
return GetStorageRoots(fieldAddress.Target); |
|
case LdElemaInlineArray inlineArray: |
|
return GetStorageRoots(inlineArray.Array); |
|
case LdLoca ldloca: |
|
return [ldloca.Variable]; |
|
case CallInstruction call: |
|
return GetRefReturnStorageArguments(call) |
|
.SelectMany(argumentIndex => GetStorageRoots(call.Arguments[argumentIndex])); |
|
default: |
|
return []; |
|
} |
|
} |
|
|
|
IEnumerable<int> GetRefReturnStorageArguments(CallInstruction call) |
|
{ |
|
if (call.Method.ReturnType is not ByReferenceType { ElementType: var elementType } |
|
|| !elementType.IsRefLikeOrAllowsRefLikeType()) |
|
{ |
|
yield break; |
|
} |
|
|
|
for (int argumentIndex = 0; argumentIndex < call.Arguments.Count; argumentIndex++) |
|
{ |
|
if (argumentIndex == 0 && call.IsInstanceCall) |
|
{ |
|
if (call.Method.DeclaringType.IsRefLikeOrAllowsRefLikeType() |
|
&& !call.Method.ThisIsRefReadOnly |
|
&& call.Method.GetThisParameterScope() != ScopedKind.ScopedRef) |
|
{ |
|
yield return argumentIndex; |
|
} |
|
continue; |
|
} |
|
|
|
IParameter? parameter = call.GetParameter(argumentIndex); |
|
if (parameter is { ReferenceKind: ReferenceKind.Ref or ReferenceKind.Out } |
|
&& GetValueType(parameter.Type).IsRefLikeOrAllowsRefLikeType() |
|
&& GetParameterRefEscape(parameter) != SafeContext.CurrentMethod) |
|
{ |
|
yield return argumentIndex; |
|
} |
|
} |
|
} |
|
|
|
SafeContext? GetRefEscape(ILInstruction value) |
|
{ |
|
switch (value) |
|
{ |
|
case LdLoc ldloc: |
|
return GetVariableScopes(ldloc.Variable).RefEscape; |
|
case LdLoca: |
|
case LocAlloc: |
|
case LocAllocSpan: |
|
return SafeContext.CurrentMethod; |
|
case LdsFlda: |
|
case LdElema: |
|
return SafeContext.CallingMethod; |
|
case LdElemaInlineArray inlineArray: |
|
return GetRefEscape(inlineArray.Array); |
|
case LdFlda fieldAddress: |
|
return GetFieldRefEscape(fieldAddress); |
|
case LdObjIfRef load: |
|
return GetRefEscape(load.Target); |
|
case CallInstruction call: |
|
return GetInvocationEscape(call, isRefEscape: true); |
|
case IfInstruction conditional: |
|
return Intersect(GetRefEscape(conditional.TrueInst), GetRefEscape(conditional.FalseInst)); |
|
case NullCoalescingInstruction coalescing: |
|
return Intersect(GetRefEscape(coalescing.ValueInst), GetRefEscape(coalescing.FallbackInst)); |
|
case Block block: |
|
return block.Kind == BlockKind.StackAllocInitializer |
|
? SafeContext.CurrentMethod |
|
: GetRefEscape(block.FinalInstruction); |
|
default: |
|
return null; |
|
} |
|
} |
|
|
|
SafeContext? GetValEscape(ILInstruction value) |
|
{ |
|
switch (value) |
|
{ |
|
case DefaultValue: |
|
case LdNull: |
|
case NewArr: |
|
return SafeContext.CallingMethod; |
|
case LocAlloc: |
|
case LocAllocSpan: |
|
return SafeContext.CurrentMethod; |
|
case LdLoc ldloc: |
|
return GetVariableScopes(ldloc.Variable).ValEscape; |
|
case LdLoca ldloca: |
|
return GetVariableScopes(ldloca.Variable).ValEscape; |
|
case LdsFlda: |
|
case LdElema: |
|
return SafeContext.CallingMethod; |
|
case LdElemaInlineArray inlineArray: |
|
return GetValEscape(inlineArray.Array); |
|
case LdFlda fieldAddress: |
|
return GetFieldValEscape(fieldAddress); |
|
case LdObj load: |
|
return GetValEscape(load.Target); |
|
case LdObjIfRef load: |
|
return GetValEscape(load.Target); |
|
case CallInstruction call: |
|
return GetInvocationEscape(call, isRefEscape: false); |
|
case IfInstruction conditional: |
|
return Intersect(GetValEscape(conditional.TrueInst), GetValEscape(conditional.FalseInst)); |
|
case NullCoalescingInstruction coalescing: |
|
return Intersect(GetValEscape(coalescing.ValueInst), GetValEscape(coalescing.FallbackInst)); |
|
case Block block: |
|
return block.Kind == BlockKind.StackAllocInitializer |
|
? SafeContext.CurrentMethod |
|
: GetValEscape(block.FinalInstruction); |
|
default: |
|
return null; |
|
} |
|
} |
|
|
|
SafeContext? GetFieldRefEscape(LdFlda fieldAddress) |
|
{ |
|
if (fieldAddress.Field.IsStatic || fieldAddress.Field.DeclaringType.IsReferenceType == true) |
|
return SafeContext.CallingMethod; |
|
|
|
if (fieldAddress.Field.ReturnType.Kind == TypeKind.ByReference) |
|
return GetValEscape(fieldAddress.Target); |
|
|
|
return GetRefEscape(fieldAddress.Target); |
|
} |
|
|
|
SafeContext? GetFieldValEscape(LdFlda fieldAddress) |
|
{ |
|
if (fieldAddress.Field.IsStatic |
|
|| !fieldAddress.Field.DeclaringType.IsRefLikeOrAllowsRefLikeType()) |
|
{ |
|
return SafeContext.CallingMethod; |
|
} |
|
|
|
return GetValEscape(fieldAddress.Target); |
|
} |
|
|
|
SafeContext? GetInvocationEscape(CallInstruction call, bool isRefEscape) |
|
{ |
|
IType returnType = call is NewObj ? call.Method.DeclaringType : call.Method.ReturnType; |
|
bool hasRefLikeReturn = GetValueType(returnType).IsRefLikeOrAllowsRefLikeType(); |
|
if (!isRefEscape && !hasRefLikeReturn) |
|
return SafeContext.CallingMethod; |
|
|
|
if (returnType.Kind == TypeKind.Unknown) |
|
return null; |
|
|
|
if (call.Method.ParentModule is not MetadataModule module) |
|
return null; |
|
|
|
return module.UseUpdatedEscapeRules |
|
? GetInvocationEscapeWithUpdatedRules(call, isRefEscape, returnType) |
|
: GetInvocationEscapeWithOldRules(call, isRefEscape); |
|
} |
|
|
|
SafeContext? GetInvocationEscapeWithUpdatedRules( |
|
CallInstruction call, bool isRefEscape, IType returnType) |
|
{ |
|
SafeContext? result = SafeContext.CallingMethod; |
|
bool returnsRefToRefStruct = returnType is ByReferenceType { ElementType: var elementType } |
|
&& elementType.IsRefLikeOrAllowsRefLikeType(); |
|
|
|
for (int i = 0; i < call.Arguments.Count; i++) |
|
{ |
|
if (i == 0 && call.IsInstanceCall) |
|
{ |
|
IType receiverType = call.Method.DeclaringType; |
|
if (receiverType.IsRefLikeOrAllowsRefLikeType()) |
|
{ |
|
result = AddInvocationEscape( |
|
result, GetValEscape(call.Arguments[i]), false, |
|
isRefEscape, returnsRefToRefStruct, isRefToRefStruct: true); |
|
} |
|
|
|
if (receiverType.IsReferenceType == false |
|
&& call.Method.GetThisParameterScope() != ScopedKind.ScopedRef) |
|
{ |
|
result = AddInvocationEscape( |
|
result, GetRefEscape(call.Arguments[i]), true, |
|
isRefEscape, returnsRefToRefStruct, |
|
isRefToRefStruct: receiverType.IsRefLikeOrAllowsRefLikeType()); |
|
} |
|
continue; |
|
} |
|
|
|
IParameter? parameter = call.GetParameter(i); |
|
if (parameter == null) |
|
return null; |
|
|
|
IType parameterType = GetValueType(parameter.Type); |
|
bool isRefToRefStruct = parameter.ReferenceKind != ReferenceKind.None |
|
&& parameterType.IsRefLikeOrAllowsRefLikeType(); |
|
if (parameterType.IsRefLikeOrAllowsRefLikeType() |
|
&& parameter.ReferenceKind != ReferenceKind.Out |
|
&& GetParameterValEscape(parameter) != SafeContext.CurrentMethod) |
|
{ |
|
result = AddInvocationEscape( |
|
result, GetValEscape(call.Arguments[i]), false, |
|
isRefEscape, returnsRefToRefStruct, isRefToRefStruct); |
|
} |
|
|
|
if (parameter.ReferenceKind != ReferenceKind.None |
|
&& GetParameterRefEscape(parameter) != SafeContext.CurrentMethod) |
|
{ |
|
result = AddInvocationEscape( |
|
result, GetRefEscape(call.Arguments[i]), true, |
|
isRefEscape, returnsRefToRefStruct, isRefToRefStruct); |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
SafeContext? GetInvocationEscapeWithOldRules(CallInstruction call, bool isRefEscape) |
|
{ |
|
SafeContext? result = SafeContext.CallingMethod; |
|
for (int i = 0; i < call.Arguments.Count; i++) |
|
{ |
|
if (i == 0 && call.IsInstanceCall) |
|
{ |
|
if (call.Method.DeclaringType.IsRefLikeOrAllowsRefLikeType()) |
|
result = Intersect(result, GetValEscape(call.Arguments[i])); |
|
continue; |
|
} |
|
|
|
IParameter? parameter = call.GetParameter(i); |
|
if (parameter == null) |
|
return null; |
|
|
|
if (!isRefEscape && GetValueType(parameter.Type).IsRefLikeOrAllowsRefLikeType()) |
|
result = Intersect(result, GetValEscape(call.Arguments[i])); |
|
|
|
if (isRefEscape && parameter.ReferenceKind != ReferenceKind.None) |
|
result = Intersect(result, GetRefEscape(call.Arguments[i])); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
SafeContext? GetInvocationEscapeToWritableArgument(CallInstruction call, int writableArgumentIndex) |
|
{ |
|
if (call.Method.ParentModule is not MetadataModule { UseUpdatedEscapeRules: true }) |
|
return null; |
|
|
|
// The destination context determines which arguments the callee can legally capture into it. |
|
SafeContext destinationValEscape; |
|
if (writableArgumentIndex == 0 && call.IsInstanceCall) |
|
{ |
|
destinationValEscape = call.Method.IsConstructor |
|
? SafeContext.ReturnOnly |
|
: SafeContext.CallingMethod; |
|
} |
|
else |
|
{ |
|
IParameter? writableParameter = call.GetParameter(writableArgumentIndex); |
|
if (writableParameter == null) |
|
return null; |
|
destinationValEscape = GetParameterValEscape(writableParameter); |
|
} |
|
|
|
SafeContext? result = SafeContext.CallingMethod; |
|
for (int i = 0; i < call.Arguments.Count; i++) |
|
{ |
|
if (i == writableArgumentIndex) |
|
continue; |
|
|
|
if (i == 0 && call.IsInstanceCall) |
|
{ |
|
IType receiverType = call.Method.DeclaringType; |
|
SafeContext receiverValEscape = call.Method.IsConstructor |
|
? SafeContext.ReturnOnly |
|
: SafeContext.CallingMethod; |
|
if (receiverType.IsRefLikeOrAllowsRefLikeType() |
|
&& receiverValEscape >= destinationValEscape) |
|
{ |
|
result = Intersect(result, GetValEscape(call.Arguments[i])); |
|
} |
|
|
|
SafeContext receiverRefEscape = call.Method.GetThisParameterScope() == ScopedKind.ScopedRef |
|
? SafeContext.CurrentMethod |
|
: SafeContext.ReturnOnly; |
|
if (receiverType.IsReferenceType == false |
|
&& receiverRefEscape >= destinationValEscape) |
|
{ |
|
result = Intersect(result, GetRefEscape(call.Arguments[i])); |
|
} |
|
continue; |
|
} |
|
|
|
IParameter? parameter = call.GetParameter(i); |
|
if (parameter == null) |
|
return null; |
|
|
|
IType parameterType = GetValueType(parameter.Type); |
|
if (parameterType.IsRefLikeOrAllowsRefLikeType() |
|
&& parameter.ReferenceKind != ReferenceKind.Out |
|
&& GetParameterValEscape(parameter) >= destinationValEscape) |
|
{ |
|
result = Intersect(result, GetValEscape(call.Arguments[i])); |
|
} |
|
|
|
if (parameter.ReferenceKind != ReferenceKind.None |
|
&& GetParameterRefEscape(parameter) >= destinationValEscape) |
|
{ |
|
result = Intersect(result, GetRefEscape(call.Arguments[i])); |
|
} |
|
} |
|
|
|
return result; |
|
} |
|
|
|
static SafeContext? AddInvocationEscape( |
|
SafeContext? current, |
|
SafeContext? argument, |
|
bool argumentIsRefEscape, |
|
bool invocationIsRefEscape, |
|
bool returnsRefToRefStruct, |
|
bool isRefToRefStruct) |
|
{ |
|
if (returnsRefToRefStruct |
|
&& (!isRefToRefStruct || argumentIsRefEscape != invocationIsRefEscape)) |
|
{ |
|
return current; |
|
} |
|
|
|
return Intersect(current, argument); |
|
} |
|
|
|
LocalScopes GetVariableScopes(ILVariable variable) |
|
{ |
|
if (_scopes.TryGetValue(variable, out var scopes)) |
|
return scopes; |
|
|
|
if (variable.Kind != VariableKind.Parameter) |
|
return default; |
|
|
|
if (variable.Index is not int index) |
|
return default; |
|
|
|
if (index < 0) |
|
return GetThisParameterScopes(); |
|
|
|
if (index >= method.Parameters.Count) |
|
return default; |
|
|
|
IParameter parameter = method.Parameters[index]; |
|
return new LocalScopes( |
|
GetParameterRefEscape(parameter), |
|
GetParameterValEscape(parameter), |
|
isScoped: false, |
|
isScopedWithoutInitializer: false); |
|
} |
|
|
|
LocalScopes GetThisParameterScopes() |
|
{ |
|
if (method.DeclaringTypeDefinition?.Kind != TypeKind.Struct) |
|
{ |
|
return new LocalScopes( |
|
SafeContext.CallingMethod, |
|
SafeContext.CallingMethod, |
|
isScoped: false, |
|
isScopedWithoutInitializer: false); |
|
} |
|
|
|
SafeContext refEscape = method.GetThisParameterScope() == ScopedKind.ScopedRef |
|
? SafeContext.CurrentMethod |
|
: SafeContext.ReturnOnly; |
|
SafeContext valEscape = method.IsConstructor |
|
? SafeContext.ReturnOnly |
|
: SafeContext.CallingMethod; |
|
return new LocalScopes( |
|
refEscape, |
|
valEscape, |
|
isScoped: false, |
|
isScopedWithoutInitializer: false); |
|
} |
|
|
|
static SafeContext GetParameterRefEscape(IParameter parameter) |
|
{ |
|
if (parameter.ReferenceKind == ReferenceKind.None) |
|
return SafeContext.CurrentMethod; |
|
if (parameter.GetEffectiveScope() == ScopedKind.ScopedRef) |
|
return SafeContext.CurrentMethod; |
|
|
|
bool useUpdatedEscapeRules = parameter.Owner?.ParentModule is MetadataModule { |
|
UseUpdatedEscapeRules: true |
|
}; |
|
if (parameter.Lifetime.HasUnscopedRefAttribute |
|
&& useUpdatedEscapeRules |
|
&& parameter.ReferenceKind == ReferenceKind.Out) |
|
{ |
|
return SafeContext.ReturnOnly; |
|
} |
|
if (parameter.Lifetime.HasUnscopedRefAttribute && useUpdatedEscapeRules) |
|
return SafeContext.CallingMethod; |
|
|
|
return SafeContext.ReturnOnly; |
|
} |
|
|
|
static SafeContext GetParameterValEscape(IParameter parameter) |
|
{ |
|
if (parameter.GetEffectiveScope() == ScopedKind.ScopedValue) |
|
return SafeContext.CurrentMethod; |
|
if (parameter.ReferenceKind == ReferenceKind.Out |
|
&& parameter.Owner?.ParentModule is MetadataModule { UseUpdatedEscapeRules: true }) |
|
{ |
|
return SafeContext.ReturnOnly; |
|
} |
|
|
|
return SafeContext.CallingMethod; |
|
} |
|
|
|
static IType GetVariableValueType(ILVariable variable) |
|
{ |
|
return GetValueType(variable.Type); |
|
} |
|
|
|
static IType GetValueType(IType type) |
|
{ |
|
return type is ByReferenceType byReference ? byReference.ElementType : type; |
|
} |
|
|
|
static SafeContext? Intersect(SafeContext? left, SafeContext? right) |
|
{ |
|
if (left == null || right == null) |
|
return null; |
|
return left < right ? left : right; |
|
} |
|
} |
|
} |
|
}
|
|
|