Skip to content

Commit df566b5

Browse files
authored
Merge pull request #1195 from rjmholt/case-sensitive-type-collection
Prevent .NET members with names differing only by case from crashing the compatibility profiler
2 parents 3f69f93 + e818dfa commit df566b5

File tree

7 files changed

+82
-37
lines changed

7 files changed

+82
-37
lines changed

PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/AssemblyData.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class AssemblyData : ICloneable
2424
/// and then type name.
2525
/// </summary>
2626
[DataMember(EmitDefaultValue = false)]
27-
public JsonCaseInsensitiveStringDictionary<JsonCaseInsensitiveStringDictionary<TypeData>> Types { get; set; }
27+
public JsonDictionary<string, JsonDictionary<string, TypeData>> Types { get; set; }
2828

2929
/// <summary>
3030
/// Create a deep clone of the assembly data object.
@@ -34,7 +34,7 @@ public object Clone()
3434
return new AssemblyData()
3535
{
3636
AssemblyName = (AssemblyNameData)AssemblyName.Clone(),
37-
Types = (JsonCaseInsensitiveStringDictionary<JsonCaseInsensitiveStringDictionary<TypeData>>)Types?.Clone()
37+
Types = (JsonDictionary<string, JsonDictionary<string, TypeData>>)Types?.Clone()
3838
};
3939
}
4040
}

PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/AvailableTypeData.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public class AvailableTypeData : ICloneable
2626
/// keyed by simple assembly name.
2727
/// </summary>
2828
[DataMember]
29-
public JsonCaseInsensitiveStringDictionary<AssemblyData> Assemblies { get; set; }
29+
public JsonDictionary<string, AssemblyData> Assemblies { get; set; }
3030

3131
/// <summary>
3232
/// Create a deep clone of the available type data object.
@@ -36,7 +36,7 @@ public object Clone()
3636
return new AvailableTypeData()
3737
{
3838
TypeAccelerators = (JsonCaseInsensitiveStringDictionary<TypeAcceleratorData>)TypeAccelerators.Clone(),
39-
Assemblies = (JsonCaseInsensitiveStringDictionary<AssemblyData>)Assemblies.Clone()
39+
Assemblies = (JsonDictionary<string, AssemblyData>)Assemblies.Clone()
4040
};
4141
}
4242
}

PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Data/Types/MemberData.cs

+10-10
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ public class MemberData : ICloneable
2626
/// Fields on the type, keyed by field name.
2727
/// </summary>
2828
[DataMember(EmitDefaultValue = false)]
29-
public JsonCaseInsensitiveStringDictionary<FieldData> Fields { get; set; }
29+
public JsonDictionary<string, FieldData> Fields { get; set; }
3030

3131
/// <summary>
3232
/// Properties on this type, keyed by property name.
3333
/// </summary>
3434
[DataMember(EmitDefaultValue = false)]
35-
public JsonCaseInsensitiveStringDictionary<PropertyData> Properties { get; set; }
35+
public JsonDictionary<string, PropertyData> Properties { get; set; }
3636

3737
/// <summary>
3838
/// Indexers on the type.
@@ -44,19 +44,19 @@ public class MemberData : ICloneable
4444
/// Methods on the type, keyed by method name.
4545
/// </summary>
4646
[DataMember(EmitDefaultValue = false)]
47-
public JsonCaseInsensitiveStringDictionary<MethodData> Methods { get; set; }
47+
public JsonDictionary<string, MethodData> Methods { get; set; }
4848

4949
/// <summary>
5050
/// Events on the type, keyed by event name.
5151
/// </summary>
5252
[DataMember(EmitDefaultValue = false)]
53-
public JsonCaseInsensitiveStringDictionary<EventData> Events { get; set; }
53+
public JsonDictionary<string, EventData> Events { get; set; }
5454

5555
/// <summary>
5656
/// Types nested within the type, keyed by type name.
5757
/// </summary>
5858
[DataMember(EmitDefaultValue = false)]
59-
public JsonCaseInsensitiveStringDictionary<TypeData> NestedTypes { get; set; }
59+
public JsonDictionary<string, TypeData> NestedTypes { get; set; }
6060

6161
/// <summary>
6262
/// Create a deep clone of the member data object.
@@ -66,12 +66,12 @@ public object Clone()
6666
return new MemberData()
6767
{
6868
Constructors = Constructors?.Select(c => (string[])c.Clone()).ToArray(),
69-
Events = (JsonCaseInsensitiveStringDictionary<EventData>)Events?.Clone(),
70-
Fields = (JsonCaseInsensitiveStringDictionary<FieldData>)Fields?.Clone(),
69+
Events = (JsonDictionary<string, EventData>)Events?.Clone(),
70+
Fields = (JsonDictionary<string, FieldData>)Fields?.Clone(),
7171
Indexers = Indexers?.Select(i => (IndexerData)i.Clone()).ToArray(),
72-
Methods = (JsonCaseInsensitiveStringDictionary<MethodData>)Methods?.Clone(),
73-
NestedTypes = (JsonCaseInsensitiveStringDictionary<TypeData>)NestedTypes?.Clone(),
74-
Properties = (JsonCaseInsensitiveStringDictionary<PropertyData>)Properties?.Clone(),
72+
Methods = (JsonDictionary<string, MethodData>)Methods?.Clone(),
73+
NestedTypes = (JsonDictionary<string, TypeData>)NestedTypes?.Clone(),
74+
Properties = (JsonDictionary<string, PropertyData>)Properties?.Clone(),
7575
};
7676
}
7777
}

PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Query/Types/AssemblyData.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ public AssemblyData(Data.Types.AssemblyData assemblyData)
3838
/// </summary>
3939
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, TypeData>> Types => _types?.Value;
4040

41-
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, TypeData>> CreateTypeDictionary(IReadOnlyDictionary<string, JsonCaseInsensitiveStringDictionary<Data.Types.TypeData>> typeData)
41+
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, TypeData>> CreateTypeDictionary(IReadOnlyDictionary<string, JsonDictionary<string, Data.Types.TypeData>> typeData)
4242
{
4343
var namespaceDict = new Dictionary<string, IReadOnlyDictionary<string, TypeData>>(typeData.Count, StringComparer.OrdinalIgnoreCase);
44-
foreach (KeyValuePair<string, JsonCaseInsensitiveStringDictionary<Data.Types.TypeData>> nspace in typeData)
44+
foreach (KeyValuePair<string, JsonDictionary<string, Data.Types.TypeData>> nspace in typeData)
4545
{
4646
var typeDict = new Dictionary<string, TypeData>(nspace.Value.Count, StringComparer.OrdinalIgnoreCase);
4747
foreach (KeyValuePair<string, Data.Types.TypeData> type in nspace.Value)

PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Utility/ProfileCombination.cs

+9-9
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ private static object Union(ParameterSetData thisParameterSet, ParameterSetData
122122

123123
private static object Union(AvailableTypeData thisTypes, AvailableTypeData thatTypes)
124124
{
125-
thisTypes.Assemblies = StringDictionaryUnion(thisTypes.Assemblies, thatTypes.Assemblies, Union);
125+
thisTypes.Assemblies = DictionaryUnion(thisTypes.Assemblies, thatTypes.Assemblies, Union);
126126
thisTypes.TypeAccelerators = StringDictionaryUnion(thisTypes.TypeAccelerators, thatTypes.TypeAccelerators);
127127

128128
return thisTypes;
@@ -136,18 +136,18 @@ private static object Union(AssemblyData thisAssembly, AssemblyData thatAssembly
136136
{
137137
if (thisAssembly.Types == null)
138138
{
139-
thisAssembly.Types = new JsonCaseInsensitiveStringDictionary<JsonCaseInsensitiveStringDictionary<TypeData>>();
139+
thisAssembly.Types = new JsonDictionary<string, JsonDictionary<string, TypeData>>();
140140
}
141141

142-
foreach (KeyValuePair<string, JsonCaseInsensitiveStringDictionary<TypeData>> nspace in thatAssembly.Types)
142+
foreach (KeyValuePair<string, JsonDictionary<string, TypeData>> nspace in thatAssembly.Types)
143143
{
144144
if (!thisAssembly.Types.ContainsKey(nspace.Key))
145145
{
146146
thisAssembly.Types.Add(nspace.Key, nspace.Value);
147147
continue;
148148
}
149149

150-
thisAssembly.Types[nspace.Key] = StringDictionaryUnion(thisAssembly.Types[nspace.Key], nspace.Value, Union);
150+
thisAssembly.Types[nspace.Key] = DictionaryUnion(thisAssembly.Types[nspace.Key], nspace.Value, Union);
151151
}
152152
}
153153

@@ -192,11 +192,11 @@ private static object Union(MemberData thisMembers, MemberData thatMembers)
192192

193193
thisMembers.Constructors = ParameterUnion(thisMembers.Constructors, thatMembers.Constructors);
194194

195-
thisMembers.Events = StringDictionaryUnion(thisMembers.Events, thatMembers.Events);
196-
thisMembers.Fields = StringDictionaryUnion(thisMembers.Fields, thatMembers.Fields);
197-
thisMembers.Methods = StringDictionaryUnion(thisMembers.Methods, thatMembers.Methods, Union);
198-
thisMembers.NestedTypes = StringDictionaryUnion(thisMembers.NestedTypes, thatMembers.NestedTypes, Union);
199-
thisMembers.Properties = StringDictionaryUnion(thisMembers.Properties, thatMembers.Properties, Union);
195+
thisMembers.Events = DictionaryUnion(thisMembers.Events, thatMembers.Events);
196+
thisMembers.Fields = DictionaryUnion(thisMembers.Fields, thatMembers.Fields);
197+
thisMembers.Methods = DictionaryUnion(thisMembers.Methods, thatMembers.Methods, Union);
198+
thisMembers.NestedTypes = DictionaryUnion(thisMembers.NestedTypes, thatMembers.NestedTypes, Union);
199+
thisMembers.Properties = DictionaryUnion(thisMembers.Properties, thatMembers.Properties, Union);
200200

201201
return thisMembers;
202202
}

PSCompatibilityAnalyzer/Microsoft.PowerShell.CrossCompatibility/Utility/TypeDataConversion.cs

+21-12
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public static AvailableTypeData AssembleAvailableTypes(
5050
typeAcceleratorDict.Add(typeAccelerator.Key, ta);
5151
}
5252

53-
var asms = new JsonCaseInsensitiveStringDictionary<AssemblyData>();
53+
var asms = new JsonDictionary<string, AssemblyData>();
5454
foreach (Assembly asm in assemblies)
5555
{
5656
// Don't want to include this module or assembly in the output
@@ -61,8 +61,17 @@ public static AvailableTypeData AssembleAvailableTypes(
6161

6262
try
6363
{
64+
// First check whether an assembly with this name already exists
65+
// Only replace it if the current one is newer
66+
AssemblyName asmName = asm.GetName();
67+
if (asms.TryGetValue(asmName.Name, out AssemblyData currentAssemblyData)
68+
&& asmName.Version < currentAssemblyData.AssemblyName.Version)
69+
{
70+
continue;
71+
}
72+
6473
KeyValuePair<string, AssemblyData> asmData = AssembleAssembly(asm);
65-
asms.Add(asmData.Key, asmData.Value);
74+
asms[asmData.Key] = asmData.Value;
6675
}
6776
catch (ReflectionTypeLoadException e)
6877
{
@@ -96,10 +105,10 @@ public static KeyValuePair<string, AssemblyData> AssembleAssembly(Assembly asm)
96105
};
97106

98107
Type[] types = asm.GetTypes();
99-
JsonCaseInsensitiveStringDictionary<JsonCaseInsensitiveStringDictionary<TypeData>> namespacedTypes = null;
108+
JsonDictionary<string, JsonDictionary<string, TypeData>> namespacedTypes = null;
100109
if (types.Any())
101110
{
102-
namespacedTypes = new JsonCaseInsensitiveStringDictionary<JsonCaseInsensitiveStringDictionary<TypeData>>();
111+
namespacedTypes = new JsonDictionary<string, JsonDictionary<string, TypeData>>();
103112
foreach (Type type in asm.GetTypes())
104113
{
105114
if (!type.IsPublic)
@@ -110,14 +119,14 @@ public static KeyValuePair<string, AssemblyData> AssembleAssembly(Assembly asm)
110119
// Some types don't have a namespace, but we still want to file them
111120
string typeNamespace = type.Namespace ?? "";
112121

113-
if (!namespacedTypes.ContainsKey(typeNamespace))
122+
if (!namespacedTypes.TryGetValue(typeNamespace, out JsonDictionary<string, TypeData> typeDictionary))
114123
{
115-
namespacedTypes.Add(typeNamespace, new JsonCaseInsensitiveStringDictionary<TypeData>());
124+
typeDictionary = new JsonDictionary<string, TypeData>();
116125
}
117126

118127
TypeData typeData = AssembleType(type);
119128

120-
namespacedTypes[typeNamespace][type.Name] = typeData;
129+
typeDictionary[type.Name] = typeData;
121130
}
122131
}
123132

@@ -271,12 +280,12 @@ private static MemberData AssembleMembers(Type type, BindingFlags memberBinding)
271280
return new MemberData()
272281
{
273282
Constructors = constructors.Any() ? constructors.Select(c => AssembleConstructor(c)).ToArray() : null,
274-
Events = events.Any() ? new JsonCaseInsensitiveStringDictionary<EventData>(events.ToDictionary(e => e.Name, e => AssembleEvent(e), StringComparer.OrdinalIgnoreCase)) : null,
275-
Fields = fields.Any() ? new JsonCaseInsensitiveStringDictionary<FieldData>(fields.ToDictionary(f => f.Name, f => AssembleField(f), StringComparer.OrdinalIgnoreCase)) : null,
283+
Events = events.Any() ? new JsonDictionary<string, EventData>(events.ToDictionary(e => e.Name, e => AssembleEvent(e))) : null,
284+
Fields = fields.Any() ? new JsonDictionary<string, FieldData>(fields.ToDictionary(f => f.Name, f => AssembleField(f))) : null,
276285
Indexers = indexers.Any() ? indexers.Select(i => AssembleIndexer(i)).ToArray() : null,
277-
Methods = methods.Any() ? new JsonCaseInsensitiveStringDictionary<MethodData>(methods.ToDictionary(m => m.Key, m => AssembleMethod(m.Value), StringComparer.OrdinalIgnoreCase)) : null,
278-
NestedTypes = nestedTypes.Any() ? new JsonCaseInsensitiveStringDictionary<TypeData>(nestedTypes.ToDictionary(t => t.Name, t => AssembleType(t), StringComparer.OrdinalIgnoreCase)) : null,
279-
Properties = properties.Any() ? new JsonCaseInsensitiveStringDictionary<PropertyData>(properties.ToDictionary(p => p.Name, p => AssembleProperty(p), StringComparer.OrdinalIgnoreCase)) : null
286+
Methods = methods.Any() ? new JsonDictionary<string, MethodData>(methods.ToDictionary(m => m.Key, m => AssembleMethod(m.Value))) : null,
287+
NestedTypes = nestedTypes.Any() ? new JsonDictionary<string, TypeData>(nestedTypes.ToDictionary(t => t.Name, t => AssembleType(t))) : null,
288+
Properties = properties.Any() ? new JsonDictionary<string, PropertyData>(properties.ToDictionary(p => p.Name, p => AssembleProperty(p))) : null
280289
};
281290
}
282291

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
Describe ".NET type with members with names differing only by name" {
5+
BeforeAll {
6+
Add-Type -TypeDefinition @'
7+
namespace PSScriptAnalyzerTests
8+
{
9+
public class QueryApiTestObject
10+
{
11+
public string JobId { get; set; }
12+
13+
public object JOBID { get; set; }
14+
}
15+
}
16+
'@
17+
}
18+
19+
It "Does not crash the query API" {
20+
$typeData = [Microsoft.PowerShell.CrossCompatibility.Utility.TypeDataConversion]::AssembleType([PSScriptAnalyzerTests.QueryApiTestObject])
21+
22+
$typeQueryObject = New-Object 'Microsoft.PowerShell.CrossCompatibility.Query.TypeData' ('QueryApiTestObject', $typeData)
23+
24+
$typeData.Instance.Properties.Count | Should -Be 2
25+
26+
$typeData.Instance.Properties.ContainsKey('JobId') | Should -BeTrue
27+
$typeData.Instance.Properties.ContainsKey('JOBID') | Should -BeTrue
28+
$typeData.Instance.Properties.ContainsKey('jobid') | Should -Not -BeTrue
29+
30+
$typeQueryObject.Instance.Properties.Count | Should -Be 1
31+
32+
$typeQueryObject.Instance.Properties.ContainsKey('JobId') | Should -BeTrue
33+
$typeQueryObject.Instance.Properties.ContainsKey('JobID') | Should -BeTrue
34+
$typeQueryObject.Instance.Properties.ContainsKey('jobid') | Should -BeTrue
35+
}
36+
}

0 commit comments

Comments
 (0)