Skip to content

Commit

Permalink
Merge pull request #1195 from rjmholt/case-sensitive-type-collection
Browse files Browse the repository at this point in the history
Prevent .NET members with names differing only by case from crashing the compatibility profiler
  • Loading branch information
JamesWTruher authored Apr 23, 2019
2 parents 3f69f93 + e818dfa commit df566b5
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class AssemblyData : ICloneable
/// and then type name.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public JsonCaseInsensitiveStringDictionary<JsonCaseInsensitiveStringDictionary<TypeData>> Types { get; set; }
public JsonDictionary<string, JsonDictionary<string, TypeData>> Types { get; set; }

/// <summary>
/// Create a deep clone of the assembly data object.
Expand All @@ -34,7 +34,7 @@ public object Clone()
return new AssemblyData()
{
AssemblyName = (AssemblyNameData)AssemblyName.Clone(),
Types = (JsonCaseInsensitiveStringDictionary<JsonCaseInsensitiveStringDictionary<TypeData>>)Types?.Clone()
Types = (JsonDictionary<string, JsonDictionary<string, TypeData>>)Types?.Clone()
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class AvailableTypeData : ICloneable
/// keyed by simple assembly name.
/// </summary>
[DataMember]
public JsonCaseInsensitiveStringDictionary<AssemblyData> Assemblies { get; set; }
public JsonDictionary<string, AssemblyData> Assemblies { get; set; }

/// <summary>
/// Create a deep clone of the available type data object.
Expand All @@ -36,7 +36,7 @@ public object Clone()
return new AvailableTypeData()
{
TypeAccelerators = (JsonCaseInsensitiveStringDictionary<TypeAcceleratorData>)TypeAccelerators.Clone(),
Assemblies = (JsonCaseInsensitiveStringDictionary<AssemblyData>)Assemblies.Clone()
Assemblies = (JsonDictionary<string, AssemblyData>)Assemblies.Clone()
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ public class MemberData : ICloneable
/// Fields on the type, keyed by field name.
/// </summary>
[DataMember(EmitDefaultValue = false)]
public JsonCaseInsensitiveStringDictionary<FieldData> Fields { get; set; }
public JsonDictionary<string, FieldData> Fields { get; set; }

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

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

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

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

/// <summary>
/// Create a deep clone of the member data object.
Expand All @@ -66,12 +66,12 @@ public object Clone()
return new MemberData()
{
Constructors = Constructors?.Select(c => (string[])c.Clone()).ToArray(),
Events = (JsonCaseInsensitiveStringDictionary<EventData>)Events?.Clone(),
Fields = (JsonCaseInsensitiveStringDictionary<FieldData>)Fields?.Clone(),
Events = (JsonDictionary<string, EventData>)Events?.Clone(),
Fields = (JsonDictionary<string, FieldData>)Fields?.Clone(),
Indexers = Indexers?.Select(i => (IndexerData)i.Clone()).ToArray(),
Methods = (JsonCaseInsensitiveStringDictionary<MethodData>)Methods?.Clone(),
NestedTypes = (JsonCaseInsensitiveStringDictionary<TypeData>)NestedTypes?.Clone(),
Properties = (JsonCaseInsensitiveStringDictionary<PropertyData>)Properties?.Clone(),
Methods = (JsonDictionary<string, MethodData>)Methods?.Clone(),
NestedTypes = (JsonDictionary<string, TypeData>)NestedTypes?.Clone(),
Properties = (JsonDictionary<string, PropertyData>)Properties?.Clone(),
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ public AssemblyData(Data.Types.AssemblyData assemblyData)
/// </summary>
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, TypeData>> Types => _types?.Value;

private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, TypeData>> CreateTypeDictionary(IReadOnlyDictionary<string, JsonCaseInsensitiveStringDictionary<Data.Types.TypeData>> typeData)
private static IReadOnlyDictionary<string, IReadOnlyDictionary<string, TypeData>> CreateTypeDictionary(IReadOnlyDictionary<string, JsonDictionary<string, Data.Types.TypeData>> typeData)
{
var namespaceDict = new Dictionary<string, IReadOnlyDictionary<string, TypeData>>(typeData.Count, StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, JsonCaseInsensitiveStringDictionary<Data.Types.TypeData>> nspace in typeData)
foreach (KeyValuePair<string, JsonDictionary<string, Data.Types.TypeData>> nspace in typeData)
{
var typeDict = new Dictionary<string, TypeData>(nspace.Value.Count, StringComparer.OrdinalIgnoreCase);
foreach (KeyValuePair<string, Data.Types.TypeData> type in nspace.Value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ private static object Union(ParameterSetData thisParameterSet, ParameterSetData

private static object Union(AvailableTypeData thisTypes, AvailableTypeData thatTypes)
{
thisTypes.Assemblies = StringDictionaryUnion(thisTypes.Assemblies, thatTypes.Assemblies, Union);
thisTypes.Assemblies = DictionaryUnion(thisTypes.Assemblies, thatTypes.Assemblies, Union);
thisTypes.TypeAccelerators = StringDictionaryUnion(thisTypes.TypeAccelerators, thatTypes.TypeAccelerators);

return thisTypes;
Expand All @@ -136,18 +136,18 @@ private static object Union(AssemblyData thisAssembly, AssemblyData thatAssembly
{
if (thisAssembly.Types == null)
{
thisAssembly.Types = new JsonCaseInsensitiveStringDictionary<JsonCaseInsensitiveStringDictionary<TypeData>>();
thisAssembly.Types = new JsonDictionary<string, JsonDictionary<string, TypeData>>();
}

foreach (KeyValuePair<string, JsonCaseInsensitiveStringDictionary<TypeData>> nspace in thatAssembly.Types)
foreach (KeyValuePair<string, JsonDictionary<string, TypeData>> nspace in thatAssembly.Types)
{
if (!thisAssembly.Types.ContainsKey(nspace.Key))
{
thisAssembly.Types.Add(nspace.Key, nspace.Value);
continue;
}

thisAssembly.Types[nspace.Key] = StringDictionaryUnion(thisAssembly.Types[nspace.Key], nspace.Value, Union);
thisAssembly.Types[nspace.Key] = DictionaryUnion(thisAssembly.Types[nspace.Key], nspace.Value, Union);
}
}

Expand Down Expand Up @@ -192,11 +192,11 @@ private static object Union(MemberData thisMembers, MemberData thatMembers)

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

thisMembers.Events = StringDictionaryUnion(thisMembers.Events, thatMembers.Events);
thisMembers.Fields = StringDictionaryUnion(thisMembers.Fields, thatMembers.Fields);
thisMembers.Methods = StringDictionaryUnion(thisMembers.Methods, thatMembers.Methods, Union);
thisMembers.NestedTypes = StringDictionaryUnion(thisMembers.NestedTypes, thatMembers.NestedTypes, Union);
thisMembers.Properties = StringDictionaryUnion(thisMembers.Properties, thatMembers.Properties, Union);
thisMembers.Events = DictionaryUnion(thisMembers.Events, thatMembers.Events);
thisMembers.Fields = DictionaryUnion(thisMembers.Fields, thatMembers.Fields);
thisMembers.Methods = DictionaryUnion(thisMembers.Methods, thatMembers.Methods, Union);
thisMembers.NestedTypes = DictionaryUnion(thisMembers.NestedTypes, thatMembers.NestedTypes, Union);
thisMembers.Properties = DictionaryUnion(thisMembers.Properties, thatMembers.Properties, Union);

return thisMembers;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public static AvailableTypeData AssembleAvailableTypes(
typeAcceleratorDict.Add(typeAccelerator.Key, ta);
}

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

try
{
// First check whether an assembly with this name already exists
// Only replace it if the current one is newer
AssemblyName asmName = asm.GetName();
if (asms.TryGetValue(asmName.Name, out AssemblyData currentAssemblyData)
&& asmName.Version < currentAssemblyData.AssemblyName.Version)
{
continue;
}

KeyValuePair<string, AssemblyData> asmData = AssembleAssembly(asm);
asms.Add(asmData.Key, asmData.Value);
asms[asmData.Key] = asmData.Value;
}
catch (ReflectionTypeLoadException e)
{
Expand Down Expand Up @@ -96,10 +105,10 @@ public static KeyValuePair<string, AssemblyData> AssembleAssembly(Assembly asm)
};

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

if (!namespacedTypes.ContainsKey(typeNamespace))
if (!namespacedTypes.TryGetValue(typeNamespace, out JsonDictionary<string, TypeData> typeDictionary))
{
namespacedTypes.Add(typeNamespace, new JsonCaseInsensitiveStringDictionary<TypeData>());
typeDictionary = new JsonDictionary<string, TypeData>();
}

TypeData typeData = AssembleType(type);

namespacedTypes[typeNamespace][type.Name] = typeData;
typeDictionary[type.Name] = typeData;
}
}

Expand Down Expand Up @@ -271,12 +280,12 @@ private static MemberData AssembleMembers(Type type, BindingFlags memberBinding)
return new MemberData()
{
Constructors = constructors.Any() ? constructors.Select(c => AssembleConstructor(c)).ToArray() : null,
Events = events.Any() ? new JsonCaseInsensitiveStringDictionary<EventData>(events.ToDictionary(e => e.Name, e => AssembleEvent(e), StringComparer.OrdinalIgnoreCase)) : null,
Fields = fields.Any() ? new JsonCaseInsensitiveStringDictionary<FieldData>(fields.ToDictionary(f => f.Name, f => AssembleField(f), StringComparer.OrdinalIgnoreCase)) : null,
Events = events.Any() ? new JsonDictionary<string, EventData>(events.ToDictionary(e => e.Name, e => AssembleEvent(e))) : null,
Fields = fields.Any() ? new JsonDictionary<string, FieldData>(fields.ToDictionary(f => f.Name, f => AssembleField(f))) : null,
Indexers = indexers.Any() ? indexers.Select(i => AssembleIndexer(i)).ToArray() : null,
Methods = methods.Any() ? new JsonCaseInsensitiveStringDictionary<MethodData>(methods.ToDictionary(m => m.Key, m => AssembleMethod(m.Value), StringComparer.OrdinalIgnoreCase)) : null,
NestedTypes = nestedTypes.Any() ? new JsonCaseInsensitiveStringDictionary<TypeData>(nestedTypes.ToDictionary(t => t.Name, t => AssembleType(t), StringComparer.OrdinalIgnoreCase)) : null,
Properties = properties.Any() ? new JsonCaseInsensitiveStringDictionary<PropertyData>(properties.ToDictionary(p => p.Name, p => AssembleProperty(p), StringComparer.OrdinalIgnoreCase)) : null
Methods = methods.Any() ? new JsonDictionary<string, MethodData>(methods.ToDictionary(m => m.Key, m => AssembleMethod(m.Value))) : null,
NestedTypes = nestedTypes.Any() ? new JsonDictionary<string, TypeData>(nestedTypes.ToDictionary(t => t.Name, t => AssembleType(t))) : null,
Properties = properties.Any() ? new JsonDictionary<string, PropertyData>(properties.ToDictionary(p => p.Name, p => AssembleProperty(p))) : null
};
}

Expand Down
36 changes: 36 additions & 0 deletions PSCompatibilityAnalyzer/Tests/QueryApi.Tests.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.

Describe ".NET type with members with names differing only by name" {
BeforeAll {
Add-Type -TypeDefinition @'
namespace PSScriptAnalyzerTests
{
public class QueryApiTestObject
{
public string JobId { get; set; }
public object JOBID { get; set; }
}
}
'@
}

It "Does not crash the query API" {
$typeData = [Microsoft.PowerShell.CrossCompatibility.Utility.TypeDataConversion]::AssembleType([PSScriptAnalyzerTests.QueryApiTestObject])

$typeQueryObject = New-Object 'Microsoft.PowerShell.CrossCompatibility.Query.TypeData' ('QueryApiTestObject', $typeData)

$typeData.Instance.Properties.Count | Should -Be 2

$typeData.Instance.Properties.ContainsKey('JobId') | Should -BeTrue
$typeData.Instance.Properties.ContainsKey('JOBID') | Should -BeTrue
$typeData.Instance.Properties.ContainsKey('jobid') | Should -Not -BeTrue

$typeQueryObject.Instance.Properties.Count | Should -Be 1

$typeQueryObject.Instance.Properties.ContainsKey('JobId') | Should -BeTrue
$typeQueryObject.Instance.Properties.ContainsKey('JobID') | Should -BeTrue
$typeQueryObject.Instance.Properties.ContainsKey('jobid') | Should -BeTrue
}
}

0 comments on commit df566b5

Please sign in to comment.