-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Avoid race condition in map for TupleElementIndex #58500
Changes from all commits
bb4050c
44387c7
c63cb79
27d7a4b
e5cac13
f6dd0d8
30a00e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -4,11 +4,13 @@ | |||||||||||||||
|
||||||||||||||||
#nullable disable | ||||||||||||||||
|
||||||||||||||||
using System; | ||||||||||||||||
using System.Collections.Generic; | ||||||||||||||||
using System.Collections.Immutable; | ||||||||||||||||
using System.Diagnostics; | ||||||||||||||||
using System.Runtime.InteropServices; | ||||||||||||||||
using Microsoft.CodeAnalysis.CSharp.Emit; | ||||||||||||||||
using Microsoft.CodeAnalysis.RuntimeMembers; | ||||||||||||||||
using Microsoft.CodeAnalysis.Symbols; | ||||||||||||||||
using Roslyn.Utilities; | ||||||||||||||||
|
||||||||||||||||
|
@@ -464,15 +466,34 @@ public virtual int TupleElementIndex | |||||||||||||||
get | ||||||||||||||||
{ | ||||||||||||||||
// wrapped tuple fields already have this information and override this property | ||||||||||||||||
Debug.Assert(!(this is TupleElementFieldSymbol or TupleErrorFieldSymbol)); | ||||||||||||||||
Debug.Assert(!(this is TupleElementFieldSymbol or TupleErrorFieldSymbol or Retargeting.RetargetingFieldSymbol)); | ||||||||||||||||
if (!ContainingType.IsTupleType) | ||||||||||||||||
{ | ||||||||||||||||
return -1; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
var map = ContainingType.TupleFieldDefinitionsToIndexMap; | ||||||||||||||||
if (map is object && map.TryGetValue(this.OriginalDefinition, out int index)) | ||||||||||||||||
if (!ContainingType.IsDefinition) | ||||||||||||||||
{ | ||||||||||||||||
return index; | ||||||||||||||||
return this.OriginalDefinition.TupleElementIndex; | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
return -1; | ||||||||||||||||
var tupleElementPosition = NamedTypeSymbol.MatchesCanonicalTupleElementName(Name); | ||||||||||||||||
int arity = ContainingType.Arity; | ||||||||||||||||
if (tupleElementPosition <= 0 || tupleElementPosition > arity) | ||||||||||||||||
{ | ||||||||||||||||
// ex: no "Item2" in 'ValueTuple<T1>' | ||||||||||||||||
return -1; | ||||||||||||||||
} | ||||||||||||||||
Debug.Assert(tupleElementPosition < NamedTypeSymbol.ValueTupleRestPosition); | ||||||||||||||||
|
||||||||||||||||
WellKnownMember wellKnownMember = NamedTypeSymbol.GetTupleTypeMember(arity, tupleElementPosition); | ||||||||||||||||
MemberDescriptor descriptor = WellKnownMembers.GetDescriptor(wellKnownMember); | ||||||||||||||||
Symbol found = CSharpCompilation.GetRuntimeMember(ImmutableArray.Create<Symbol>(this), descriptor, CSharpCompilation.SpecialMembersSignatureComparer.Instance, | ||||||||||||||||
accessWithinOpt: null); // force lookup of public members only | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Comment on lines
+491
to
+492
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit:
Suggested change
|
||||||||||||||||
|
||||||||||||||||
return found is not null | ||||||||||||||||
? tupleElementPosition - 1 | ||||||||||||||||
: -1; | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is any of this expensive? would it be worth caching the reuslt of this? #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Considered this question and I lean towards not caching. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. cool, that makes sense to me :) |
||||||||||||||||
} | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -447,7 +447,7 @@ internal static int IsTupleElementNameReserved(string name) | |
return 0; | ||
} | ||
|
||
return matchesCanonicalElementName(name); | ||
return MatchesCanonicalTupleElementName(name); | ||
|
||
static bool isElementNameForbidden(string name) | ||
{ | ||
|
@@ -465,25 +465,25 @@ static bool isElementNameForbidden(string name) | |
return false; | ||
} | ||
} | ||
} | ||
|
||
// Returns 3 for "Item3". | ||
// Returns -1 otherwise. | ||
static int matchesCanonicalElementName(string name) | ||
// Returns 3 for "Item3". | ||
// Returns -1 otherwise. | ||
internal static int MatchesCanonicalTupleElementName(string name) | ||
{ | ||
if (name.StartsWith("Item", StringComparison.Ordinal)) | ||
{ | ||
if (name.StartsWith("Item", StringComparison.Ordinal)) | ||
string tail = name.Substring("Item".Length); | ||
if (int.TryParse(tail, out int number)) | ||
{ | ||
string tail = name.Substring(4); | ||
if (int.TryParse(tail, out int number)) | ||
if (number > 0 && string.Equals(name, TupleMemberName(number), StringComparison.Ordinal)) | ||
{ | ||
if (number > 0 && string.Equals(name, TupleMemberName(number), StringComparison.Ordinal)) | ||
{ | ||
return number; | ||
} | ||
return number; | ||
} | ||
} | ||
|
||
return -1; | ||
} | ||
|
||
return -1; | ||
} | ||
|
||
/// <summary> | ||
|
@@ -567,36 +567,6 @@ public sealed override ImmutableArray<TypeWithAnnotations> TupleElementTypesWith | |
public sealed override ImmutableArray<FieldSymbol> TupleElements | ||
=> IsTupleType ? TupleData!.TupleElements(this) : default; | ||
|
||
/// <summary> | ||
/// For tuple fields that aren't TupleElementFieldSymbol or TupleErrorFieldSymbol, we cache their tuple element index. | ||
/// This supports <see cref="FieldSymbol.TupleElementIndex"/>. | ||
/// For those fields, we map from their definition to an index. | ||
/// </summary> | ||
public SmallDictionary<FieldSymbol, int>? TupleFieldDefinitionsToIndexMap | ||
{ | ||
get | ||
{ | ||
if (!IsTupleType) | ||
{ | ||
return null; | ||
} | ||
|
||
if (!IsDefinition) | ||
{ | ||
return this.OriginalDefinition.TupleFieldDefinitionsToIndexMap; | ||
} | ||
|
||
return TupleData!.GetFieldDefinitionsToIndexMap(this); | ||
} | ||
} | ||
|
||
public virtual void InitializeTupleFieldDefinitionsToIndexMap() | ||
{ | ||
Debug.Assert(this.IsTupleType); | ||
Debug.Assert(this.IsDefinition); // we only store a map for definitions | ||
_ = this.GetMembers(); | ||
} | ||
|
||
public TMember? GetTupleMemberSymbolForUnderlyingMember<TMember>(TMember? underlyingMemberOpt) where TMember : Symbol | ||
{ | ||
return IsTupleType ? TupleData!.GetTupleMemberSymbolForUnderlyingMember(underlyingMemberOpt) : null; | ||
|
@@ -611,10 +581,6 @@ protected ArrayBuilder<Symbol> MakeSynthesizedTupleMembers(ImmutableArray<Symbol | |
var elementsMatchedByFields = ArrayBuilder<bool>.GetInstance(elementTypes.Length, fillWithValue: false); | ||
var members = ArrayBuilder<Symbol>.GetInstance(currentMembers.Length); | ||
|
||
// For tuple fields that aren't TupleElementFieldSymbol or TupleErrorFieldSymbol, we cache/map their tuple element index | ||
// corresponding to their definition. We only need to do that for the definition of ValueTuple types. | ||
var fieldDefinitionsToIndexMap = IsDefinition ? new SmallDictionary<FieldSymbol, int>(ReferenceEqualityComparer.Instance) : null; | ||
|
||
NamedTypeSymbol currentValueTuple = this; | ||
int currentNestingLevel = 0; | ||
|
||
|
@@ -692,7 +658,6 @@ protected ArrayBuilder<Symbol> MakeSynthesizedTupleMembers(ImmutableArray<Symbol | |
if (IsDefinition) | ||
{ | ||
defaultTupleField = field; | ||
fieldDefinitionsToIndexMap!.Add(field, tupleFieldIndex); | ||
} | ||
else | ||
{ | ||
|
@@ -819,10 +784,6 @@ protected ArrayBuilder<Symbol> MakeSynthesizedTupleMembers(ImmutableArray<Symbol | |
} | ||
|
||
elementsMatchedByFields.Free(); | ||
if (fieldDefinitionsToIndexMap is object) | ||
{ | ||
this.TupleData!.SetFieldDefinitionsToIndexMap(fieldDefinitionsToIndexMap); | ||
} | ||
return members; | ||
|
||
// Returns the nested type at a certain depth. | ||
|
@@ -847,11 +808,11 @@ static void collectTargetTupleFields(int arity, ImmutableArray<Symbol> members, | |
for (int i = 0; i < fieldsPerType; i++) | ||
{ | ||
WellKnownMember wellKnownTupleField = GetTupleTypeMember(arity, i + 1); | ||
fieldsForElements.Add((FieldSymbol?)GetWellKnownMemberInType(members, wellKnownTupleField)); | ||
fieldsForElements.Add((FieldSymbol?)getWellKnownMemberInType(members, wellKnownTupleField)); | ||
} | ||
} | ||
|
||
static Symbol? GetWellKnownMemberInType(ImmutableArray<Symbol> members, WellKnownMember relativeMember) | ||
static Symbol? getWellKnownMemberInType(ImmutableArray<Symbol> members, WellKnownMember relativeMember) | ||
{ | ||
Debug.Assert(relativeMember >= WellKnownMember.System_ValueTuple_T1__Item1 && relativeMember <= WellKnownMember.System_ValueTuple_TRest__ctor); | ||
|
||
|
@@ -954,13 +915,6 @@ internal sealed class TupleExtraData | |
|
||
private ImmutableArray<FieldSymbol> _lazyDefaultElementFields; | ||
|
||
/// <summary> | ||
/// For tuple fields that aren't TupleElementFieldSymbol or TupleErrorFieldSymbol, we cache their tuple element index. | ||
/// This supports <see cref="FieldSymbol.TupleElementIndex"/>. | ||
/// For those fields, we map from their definition to an index. | ||
/// </summary> | ||
private SmallDictionary<FieldSymbol, int>? _lazyFieldDefinitionsToIndexMap; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we certain that nothing else in this class is prone to a similar race? Would it be simpler and more robust to ensure that members and tuple data are in sync by using an alternative approach? For example, by ensuring that they are stored together at once. #Resolved There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've reviewed the other parts of For the suggestion to have |
||
|
||
private SmallDictionary<Symbol, Symbol>? _lazyUnderlyingDefinitionToMemberMap; | ||
|
||
/// <summary> | ||
|
@@ -1093,26 +1047,6 @@ ImmutableArray<FieldSymbol> collectTupleElementFields(NamedTypeSymbol tuple) | |
} | ||
} | ||
|
||
internal SmallDictionary<FieldSymbol, int> GetFieldDefinitionsToIndexMap(NamedTypeSymbol tuple) | ||
{ | ||
Debug.Assert(tuple.IsTupleType); | ||
Debug.Assert(tuple.IsDefinition); // we only store a map for definitions | ||
if (_lazyFieldDefinitionsToIndexMap is null) | ||
{ | ||
tuple.InitializeTupleFieldDefinitionsToIndexMap(); | ||
} | ||
|
||
Debug.Assert(_lazyFieldDefinitionsToIndexMap is object); | ||
return _lazyFieldDefinitionsToIndexMap; | ||
} | ||
|
||
internal void SetFieldDefinitionsToIndexMap(SmallDictionary<FieldSymbol, int> map) | ||
{ | ||
Debug.Assert(map.Keys.All(k => k.IsDefinition)); | ||
Debug.Assert(map.Values.All(v => v >= 0)); | ||
Interlocked.CompareExchange(ref _lazyFieldDefinitionsToIndexMap, map, null); | ||
} | ||
|
||
internal SmallDictionary<Symbol, Symbol> UnderlyingDefinitionToMemberMap | ||
{ | ||
get | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please clarify why we can assume this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this assert be moved to immediately following the
if (!ContainingType.IsDefinition) { ... }
block?