Skip to content

Commit 1e07967

Browse files
committed
Fix profile union, type compat bugs, add type compat tests
1 parent 1177720 commit 1e07967

File tree

5 files changed

+182
-8
lines changed

5 files changed

+182
-8
lines changed

CrossCompatibility/CrossCompatibility/Utility/CompatibilityProfileLoader.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,14 @@ private async Task<CompatibilityProfileCacheEntry[]> GetProfilesFromPaths(IEnume
9595
// - which are trivially parallel
9696
// We want to control all concurrency from the caller,
9797
// but also want to parallelize the computations for maximum throughput.
98+
// In most scenarios, where the work has already been done, we want to avoid any parallel overhead we can.
9899
//
99100
// So we:
100101
// - Corrale all the load calls through a threadsafe cache of lazy calls (fan the load calls in from the number of calling threads)
101-
// - Transform the query into PLINQ
102-
// - Evaluate the lazy calls in parallel (fan the load calls out to the available global threadpool)
103-
// - Put them into an array for the caller
104-
return await Task.WhenAll(profilePaths.Select(path => GetProfileFromFilePath(path).Value));
102+
// - Transform the query into lazy tasks, lazy so that each task is only created and evaluated once, tasks so that they are handled by the threadpool
103+
// - Evaluate the lazy calls (fan the load calls out to the available global threadpool)
104+
// - Wait for the calls and marshall the results back into an array in the caller
105+
return await Task.WhenAll(profilePaths.Select(path => GetProfileFromPath(path).Value));
105106
}
106107

107108
/// <summary>
@@ -110,7 +111,7 @@ private async Task<CompatibilityProfileCacheEntry[]> GetProfilesFromPaths(IEnume
110111
/// </summary>
111112
/// <param name="path">The path to load a profile from.</param>
112113
/// <returns>A query object around the loaded profile.</returns>
113-
private Lazy<Task<CompatibilityProfileCacheEntry>> GetProfileFromFilePath(string path)
114+
private Lazy<Task<CompatibilityProfileCacheEntry>> GetProfileFromPath(string path)
114115
{
115116
if (path == null)
116117
{

CrossCompatibility/CrossCompatibility/Utility/ProfileCombination.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ private static object Union(AssemblyNameData thisAsmName, AssemblyNameData thatA
172172
private static object Union(TypeData thisType, TypeData thatType)
173173
{
174174
thisType.Instance = (MemberData)Union(thisType.Instance, thatType.Instance);
175-
thisType.Static = (MemberData)Union(thisType.Instance, thatType.Instance);
175+
thisType.Static = (MemberData)Union(thisType.Static, thatType.Static);
176176
return thisType;
177177
}
178178

Rules/CompatibilityRules/UseCompatibleTypes.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,12 @@ public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressio
179179
return AstVisitAction.Continue;
180180
}
181181

182+
// Leave the [type]::new() checking to another rule for now (TODO: could check constructor arity)
183+
if (methodNameAst.Value.Equals("new", StringComparison.OrdinalIgnoreCase))
184+
{
185+
return AstVisitAction.Continue;
186+
}
187+
182188
// We only need to get the full type name once -- from the union profile
183189
string typeName = TypeNaming.GetOuterMostTypeName(_anyProfile.Runtime.Types.TypeAcceleratorNames, typeExpr.TypeName);
184190
if (_typesToIgnore.Contains(typeName) || !_anyProfile.Runtime.Types.Types.TryGetValue(typeName, out TypeData unionType))
@@ -679,7 +685,7 @@ private TypeCompatibilityDiagnostic(
679685
{
680686
Type = incompatibleCommand;
681687
TargetPlatform = targetPlatform;
682-
MemberName = memberName;
688+
Member = memberName;
683689
Kind = kind;
684690
}
685691

@@ -701,7 +707,7 @@ private TypeCompatibilityDiagnostic(
701707
/// <summary>
702708
/// If this diagnostic is about a type member, the name of that member.
703709
/// </summary>
704-
public string MemberName { get; }
710+
public string Member { get; }
705711
}
706712

707713
/// <summary>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
class MyType
2+
{
3+
static [MyType] CreateFromString([string]$Str)
4+
{
5+
return [MyType]::new($Str)
6+
}
7+
8+
MyType([string]$Str)
9+
{
10+
$this.String = $Str
11+
}
12+
13+
[string]$String
14+
15+
[string]GetString()
16+
{
17+
return $this.String
18+
}
19+
}
20+
21+
$stack = [System.Collections.Generic.Stack[string]]::new()
22+
$add = 'Push'
23+
foreach ($s in 'a','b','c','d')
24+
{
25+
$stack.$add($s)
26+
}
27+
28+
$create = 'CreateFromString'
29+
30+
while ($stack.Count -gt 0)
31+
{
32+
$t = [MyType]::$create($stack.Pop())
33+
Write-Information $t.GetString()
34+
}
35+
36+
Get-EventLog -Name System | ogv
37+
38+
Invoke-WebRequest -Uri 'https://aka.ms/everyonebehappy/' -NoProxy -SkipHeaderValidation
39+
40+
$modSpec = $null
41+
if (-not [Microsoft.PowerShell.Commands.ModuleSpecification]::TryParse("@{ ModuleName = 'Microsoft.PowerShell.Utility'; ModuleVersion = '3.0' }", [ref]$modSpec))
42+
{
43+
throw "Module specification did not parse"
44+
}
45+
46+
$m = Import-Module -FullyQualifiedName $modSpec -PassThru
47+
48+
return $m

Tests/Rules/UseCompatibleTypes.Tests.ps1

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,16 @@ $script:TargetProfileConfigKey = 'TargetProfiles'
77

88
$script:Srv2012_3_profile = 'win-8_x64_6.2.9200.0_3.0_x64_4.0.30319.42000_framework'
99
$script:Srv2012r2_4_profile = 'win-8_x64_6.3.9600.0_4.0_x64_4.0.30319.42000_framework'
10+
$script:Srv2012r2_6_1_profile = 'win-8_x64_6.3.9600.0_6.1.2_x64_4.0.30319.42000_core'
11+
$script:Srv2016_5_profile = 'win-8_x64_10.0.14393.0_5.1.14393.2636_x64_4.0.30319.42000_framework'
12+
$script:Srv2016_6_1_profile = 'win-8_x64_10.0.14393.0_6.1.2_x64_4.0.30319.42000_core'
1013
$script:Srv2019_5_profile = 'win-8_x64_10.0.17763.0_5.1.17763.134_x64_4.0.30319.42000_framework'
1114
$script:Srv2019_6_1_profile = 'win-8_x64_10.0.17763.0_6.1.2_x64_4.0.30319.42000_core'
15+
$script:Win10_5_profile = 'win-48_x64_10.0.17763.0_5.1.17763.134_x64_4.0.30319.42000_framework'
16+
$script:Win10_6_1_profile = 'win-48_x64_10.0.17763.0_6.1.2_x64_4.0.30319.42000_core'
1217
$script:Ubuntu1804_6_1_profile = 'ubuntu_x64_18.04_6.1.2_x64_4.0.30319.42000_core'
18+
$script:Debian8_6_1_profile = 'debian_x64_8_6.1.2_x64_4.0.30319.42000_core'
19+
$script:Rhel76_6_1_profile = 'rhel_x64_7.6_6.1.2_x64_4.0.30319.42000_core'
1320

1421
$script:TypeCompatibilityTestCases = @(
1522
@{ Target = $script:Srv2012_3_profile; Script = '[System.Management.Automation.ModuleIntrinsics]::GetModulePath("here", "there", "everywhere")'; Types = @('System.Management.Automation.ModuleIntrinsics'); Version = "3.0"; OS = 'Windows'; ProblemCount = 1 }
@@ -18,14 +25,37 @@ $script:TypeCompatibilityTestCases = @(
1825
@{ Target = $script:Srv2012_3_profile; Script = '$kw = New-Object "System.Management.Automation.Language.DynamicKeyword"'; Types = @('System.Management.Automation.Language.DynamicKeyword'); Version = "3.0"; OS = 'Windows'; ProblemCount = 1 }
1926
@{ Target = $script:Srv2012_3_profile; Script = '& { param([Parameter(Position=0)][ArgumentCompleter({"Banana"})][string]$Hello) $Hello } "Banana"'; Types = @('ArgumentCompleter'); Version = "3.0"; OS = 'Windows'; ProblemCount = 1 }
2027

28+
@{ Target = $script:Srv2012r2_4_profile; Script = '[WildcardPattern]"bicycle*"'; Types = @('WildcardPattern'); Version = "4.0"; OS = 'Windows'; ProblemCount = 1 }
29+
@{ Target = $script:Srv2012r2_4_profile; Script = '$client = [System.Net.Http.HttpClient]::new()'; Types = @('System.Net.Http.HttpClient'); Version = "4.0"; OS = 'Windows'; ProblemCount = 1 }
30+
@{ Target = $script:Srv2012r2_4_profile; Script = '[Microsoft.PowerShell.EditMode]"Vi"'; Types = @('Microsoft.PowerShell.EditMode'); Version = "4.0"; OS = 'Windows'; ProblemCount = 1 }
31+
2132
@{ Target = $script:Srv2019_5_profile; Script = '[Microsoft.PowerShell.Commands.WebSslProtocol]::Default -eq "Tls12"'; Types = @('Microsoft.PowerShell.Commands.WebSslProtocol'); Version = "5.1"; OS = 'Windows'; ProblemCount = 1 }
33+
@{ Target = $script:Srv2019_5_profile; Script = '[System.Collections.Immutable.ImmutableList[string]]::Empty'; Types = @('System.Collections.Immutable.ImmutableList'); Version = "5.1"; OS = 'Windows'; ProblemCount = 1 }
34+
@{ Target = $script:Srv2019_5_profile; Script = '[System.Collections.Generic.TreeSet[string]]::new(@("duck", "goose", "banana"))'; Types = @('System.Collections.Generic.TreeSet'); Version = "5.1"; OS = 'Windows'; ProblemCount = 1 }
35+
36+
@{ Target = $script:Srv2019_6_1_profile; Script = 'function CertFunc { param([System.Net.ICertificatePolicy]$Policy) Do-Something $Policy }'; Types = @('System.Net.ICertificatePolicy'); Version = "6.1"; OS = 'Windows'; ProblemCount = 1 }
2237

2338
@{ Target = $script:Ubuntu1804_6_1_profile; Script = '[System.Management.Automation.Security.SystemPolicy]::GetSystemLockdownPolicy()'; Types = @('System.Management.Automation.Security.SystemPolicy'); Version = "6.1.2"; OS = 'Linux'; ProblemCount = 1 }
2439
@{ Target = $script:Ubuntu1804_6_1_profile; Script = '[System.Management.Automation.Security.SystemPolicy]::GetSystemLockdownPolicy()'; Types = @('System.Management.Automation.Security.SystemPolicy'); Version = "6.1.2"; OS = 'Linux'; ProblemCount = 1 }
2540
@{ Target = $script:Ubuntu1804_6_1_profile; Script = '[System.Management.Automation.Security.SystemEnforcementMode]$enforcementMode = "Audit"'; Types = @('System.Management.Automation.Security.SystemEnforcementMode'); Version = "6.1.2"; OS = 'Linux'; ProblemCount = 1 }
2641
@{ Target = $script:Ubuntu1804_6_1_profile; Script = '$ci = New-Object "Microsoft.PowerShell.Commands.ComputerInfo"'; Types = @('Microsoft.PowerShell.Commands.ComputerInfo'); Version = "6.1.2"; OS = 'Linux'; ProblemCount = 1 }
2742
)
2843

44+
$script:MemberCompatibilityTestCases = @(
45+
@{ Target = $script:Srv2012_3_profile; Script = '[System.Management.Automation.LanguagePrimitives]::ConvertTypeNameToPSTypeName("System.String")'; Types = @('System.Management.Automation.LanguagePrimitives'); Members = @('ConvertTypeNameToPSTypeName'); Version = "3.0"; OS = 'Windows'; ProblemCount = 1 }
46+
@{ Target = $script:Srv2012_3_profile; Script = '[System.Management.Automation.WildcardPattern]::Get("banana*", "None").IsMatch("bananaduck")'; Types = @('System.Management.Automation.WildcardPattern'); Members = @('Get'); Version = "3.0"; OS = 'Windows'; ProblemCount = 1 }
47+
48+
@{ Target = $script:Srv2012r2_4_profile; Script = 'if (-not [Microsoft.PowerShell.Commands.ModuleSpecification]::TryParse($msStr, [ref]$modSpec)){ throw "Bad!" }'; Types = @('Microsoft.PowerShell.Commands.ModuleSpecification'); Members = @('TryParse'); Version = "4.0"; OS = 'Windows'; ProblemCount = 1 }
49+
@{ Target = $script:Srv2012r2_4_profile; Script = '[System.Management.Automation.LanguagePrimitives]::IsObjectEnumerable($obj)'; Types = @('System.Management.Automation.LanguagePrimitives'); Members = @('IsObjectEnumerable'); Version = "4.0"; OS = 'Windows'; ProblemCount = 1 }
50+
51+
@{ Target = $script:Srv2019_5_profile; Script = '$socket = [System.Net.WebSockets.WebSocket]::CreateFromStream($stream, $true, "http", [timespan]::FromMinutes(10))'; Types = @('System.Net.WebSockets.WebSocket'); Members = @('CreateFromStream'); Version = "5.1"; OS = 'Windows'; ProblemCount = 1 }
52+
@{ Target = $script:Srv2019_5_profile; Script = '[System.Management.Automation.HostUtilities]::InvokeOnRunspace($command, $runspace)'; Types = @('System.Management.Automation.HostUtilities'); Members = @('InvokeOnRunspace'); Version = "5.1"; OS = 'Windows'; ProblemCount = 1 }
53+
54+
@{ Target = $script:Srv2019_6_1_profile; Script = '[Microsoft.PowerShell.ToStringCodeMethods]::PropertyValueCollection($obj)'; Types = @('Microsoft.PowerShell.ToStringCodeMethods'); Members = @('PropertyValueCollection'); Version = "6.1"; OS = 'Windows'; ProblemCount = 1 }
55+
56+
@{ Target = $script:Ubuntu1804_6_1_profile; Script = '[System.Management.Automation.Tracing.Tracer]::GetExceptionString($e)'; Types = @('System.Management.Automation.Tracing.Tracer'); Members = @('GetExceptionString'); Version = "6.1"; OS = 'Linux'; ProblemCount = 1 }
57+
)
58+
2959
Describe 'UseCompatibleTypes' {
3060
Context 'Targeting a single profile' {
3161
It "Reports <ProblemCount> problem(s) with <Script> on <OS> with PowerShell <Version> targeting <Target>" -TestCases $script:TypeCompatibilityTestCases {
@@ -52,5 +82,94 @@ Describe 'UseCompatibleTypes' {
5282
$diagnostics[$i].TargetPlatform.PowerShell.Version.Minor | Should -Be $Version.Minor
5383
}
5484
}
85+
86+
It "Reports <ProblemCount> problem(s) with <Script> on <OS> with PowerShell <Version> targeting <Target>" -TestCases $script:MemberCompatibilityTestCases {
87+
param($Script, [string]$Target, [string[]]$Types, [string[]]$Members, [version]$Version, [string]$OS, [int]$ProblemCount)
88+
89+
$settings = @{
90+
Rules = @{
91+
$script:RuleName = @{
92+
Enable = $true
93+
$script:TargetProfileConfigKey = @($Target)
94+
}
95+
}
96+
}
97+
98+
$diagnostics = Invoke-ScriptAnalyzer -IncludeRule $script:RuleName -ScriptDefinition $Script -Settings $settings
99+
100+
$diagnostics.Count | Should -Be $ProblemCount
101+
102+
for ($i = 0; $i -lt $diagnostics.Count; $i++)
103+
{
104+
$diagnostics[$i].Type | Should -BeExactly $Types[$i]
105+
$diagnostics[$i].Member | Should -BeExactly $Members[$i]
106+
$diagnostics[$i].TargetPlatform.OperatingSystem.Family | Should -Be $OS
107+
$diagnostics[$i].TargetPlatform.PowerShell.Version.Major | Should -Be $Version.Major
108+
$diagnostics[$i].TargetPlatform.PowerShell.Version.Minor | Should -Be $Version.Minor
109+
}
110+
}
111+
}
112+
113+
Context "Full file checking against all targets" {
114+
It "Finds all incompatibilities in the script" {
115+
$settings = @{
116+
Rules = @{
117+
$script:RuleName = @{
118+
Enable = $true
119+
$script:TargetProfileConfigKey = @(
120+
$script:Srv2012_3_profile
121+
$script:Srv2012r2_4_profile
122+
$script:Srv2012r2_6_1_profile
123+
$script:Srv2016_5_profile
124+
$script:Srv2016_6_1_profile
125+
$script:Srv2019_5_profile
126+
$script:Srv2019_6_1_profile
127+
$script:Win10_5_profile
128+
$script:Win10_6_1_profile
129+
$script:Ubuntu1804_6_1_profile
130+
$script:Debian8_6_1_profile
131+
$script:Rhel76_6_1_profile
132+
)
133+
}
134+
}
135+
}
136+
137+
$diagnostics = Invoke-ScriptAnalyzer -Path "$PSScriptRoot/CompatibilityRuleAssets/IncompatibleScript.ps1" -Settings $settings -IncludeRule PSUseCompatibleTypes
138+
$diagnostics.Count | Should -Be 2
139+
foreach ($diagnostic in $diagnostics)
140+
{
141+
$diagnostic.Member | Should -BeExactly 'TryParse'
142+
$diagnostic.Type | Should -BeExactly 'Microsoft.PowerShell.Commands.ModuleSpecification'
143+
}
144+
}
145+
}
146+
147+
Context "PSSA repository code checking" {
148+
It "Checks that there are no incompatibilities in PSSA build scripts" {
149+
$settings = @{
150+
Rules = @{
151+
$script:RuleName = @{
152+
Enable = $true
153+
$script:TargetProfileConfigKey = @(
154+
$script:Srv2012_3_profile
155+
$script:Srv2012r2_4_profile
156+
$script:Srv2012r2_6_1_profile
157+
$script:Srv2016_5_profile
158+
$script:Srv2016_6_1_profile
159+
$script:Srv2019_5_profile
160+
$script:Srv2019_6_1_profile
161+
$script:Win10_5_profile
162+
$script:Win10_6_1_profile
163+
$script:Ubuntu1804_6_1_profile
164+
$script:Debian8_6_1_profile
165+
$script:Rhel76_6_1_profile
166+
)
167+
}
168+
}
169+
}
170+
171+
$diagnostics = Invoke-ScriptAnalyzer -Path "$PSScriptRoot/../../" -Settings $settings -IncludeRule PSUseCompatibleTypes
172+
$diagnostics.Count | Should -Be 0
173+
}
55174
}
56175
}

0 commit comments

Comments
 (0)