Skip to content

Commit 545efca

Browse files
authored
Run ILVerify (#17953)
1 parent faa3c47 commit 545efca

11 files changed

+753
-10
lines changed

.config/dotnet-tools.json

+24-10
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,62 @@
11
{
2+
"version": 1,
23
"isRoot": true,
34
"tools": {
45
"dotnet-counters": {
6+
"version": "8.0.547301",
57
"commands": [
68
"dotnet-counters"
79
],
8-
"version": "8.0.547301"
10+
"rollForward": true
911
},
1012
"dotnet-dump": {
13+
"version": "8.0.547301",
1114
"commands": [
1215
"dotnet-dump"
1316
],
14-
"version": "8.0.547301"
17+
"rollForward": true
1518
},
1619
"dotnet-gcdump": {
20+
"version": "8.0.547301",
1721
"commands": [
1822
"dotnet-gcdump"
1923
],
20-
"version": "8.0.547301"
24+
"rollForward": true
2125
},
2226
"dotnet-sos": {
27+
"version": "8.0.547301",
2328
"commands": [
2429
"dotnet-sos"
2530
],
26-
"version": "8.0.547301"
31+
"rollForward": true
2732
},
2833
"dotnet-symbol": {
34+
"version": "8.0.547301",
2935
"commands": [
3036
"dotnet-symbol"
3137
],
32-
"version": "8.0.547301"
38+
"rollForward": true
3339
},
3440
"dotnet-trace": {
41+
"version": "8.0.547301",
3542
"commands": [
3643
"dotnet-trace"
3744
],
38-
"version": "8.0.547301"
45+
"rollForward": true
46+
},
47+
"dotnet-ilverify": {
48+
"version": "9.0.0-rc.2.24473.5",
49+
"commands": [
50+
"ilverify"
51+
],
52+
"rollForward": true
3953
},
4054
"fantomas": {
55+
"version": "6.2.3",
4156
"commands": [
4257
"fantomas"
4358
],
44-
"version": "6.2.3"
59+
"rollForward": true
4560
}
46-
},
47-
"version": 1
48-
}
61+
}
62+
}

azure-pipelines-PR.yml

+20
Original file line numberDiff line numberDiff line change
@@ -808,3 +808,23 @@ stages:
808808
artifactName: 'Trim Test Logs Attempt $(System.JobAttempt) Logs $(_kind)'
809809
continueOnError: true
810810
condition: always()
811+
- job: ILVerify
812+
pool:
813+
name: $(DncEngPublicBuildPool)
814+
demands: ImageOverride -equals $(WindowsMachineQueueName)
815+
steps:
816+
- checkout: self
817+
clean: true
818+
- task: UseDotNet@2
819+
displayName: install SDK
820+
inputs:
821+
packageType: sdk
822+
useGlobalJson: true
823+
includePreviewVersions: true
824+
workingDirectory: $(Build.SourcesDirectory)
825+
installationPath: $(Agent.ToolsDirectory)/dotnet
826+
- script: dotnet tool restore
827+
displayName: Restore dotnet tools
828+
- pwsh: .\eng\ilverify.ps1
829+
displayName: Run ILVerify
830+
workingDirectory: $(Build.SourcesDirectory)

eng/ilverify.ps1

+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Cross-platform PowerShell script to verify the integrity of the produced dlls, using dotnet-ilverify.
2+
3+
# Set build script based on which OS we're running on - Windows (build.cmd), Linux or macOS (build.sh)
4+
5+
Write-Host "Checking whether running on Windows: $IsWindows"
6+
7+
[string] $repo_path = (Get-Item -Path $PSScriptRoot).Parent
8+
9+
Write-Host "Repository path: $repo_path"
10+
11+
[string] $script = if ($IsWindows) { Join-Path $repo_path "build.cmd" } else { Join-Path $repo_path "build.sh" }
12+
13+
# Set configurations to build
14+
[string[]] $configurations = @("Debug", "Release")
15+
16+
# The following are not passing ilverify checks, so we ignore them for now
17+
[string[]] $ignore_errors = @() # @("StackUnexpected", "UnmanagedPointer", "StackByRef", "ReturnPtrToStack", "ExpectedNumericType", "StackUnderflow")
18+
19+
[string] $default_tfm = "netstandard2.0"
20+
21+
[string] $artifacts_bin_path = Join-Path (Join-Path $repo_path "artifacts") "bin"
22+
23+
# List projects to verify, with TFMs
24+
$projects = @{
25+
"FSharp.Core" = @($default_tfm, "netstandard2.1")
26+
"FSharp.Compiler.Service" = @($default_tfm, "net9.0")
27+
}
28+
29+
# Run build script for each configuration (NOTE: We don't build Proto)
30+
foreach ($configuration in $configurations) {
31+
Write-Host "Building $configuration configuration..."
32+
& $script -c $configuration
33+
if ($LASTEXITCODE -ne 0 -And $LASTEXITCODE -ne '') {
34+
Write-Host "Build failed for $configuration configuration (last exit code: $LASTEXITCODE)."
35+
exit 1
36+
}
37+
}
38+
39+
# Check if ilverify is installed and available from the tool list (using `dotnet tool list -g `), and install it globally if not found.
40+
Write-Host "Checking if dotnet-ilverify is installed..."
41+
$dotnet_ilverify = dotnet tool list -g | Select-String -SimpleMatch -CaseSensitive "dotnet-ilverify"
42+
43+
if ([string]::IsNullOrWhiteSpace($dotnet_ilverify)) {
44+
Write-Host " dotnet-ilverify is not installed. Installing..."
45+
dotnet tool install dotnet-ilverify -g --prerelease
46+
} else {
47+
Write-Host " dotnet-ilverify is installed:`n $dotnet_ilverify"
48+
}
49+
50+
# Get the path to latest currently installed runtime
51+
[string[]] $runtimes = @(dotnet --list-runtimes | Select-String -SimpleMatch -CaseSensitive -List "Microsoft.NETCore.App")
52+
if ($runtimes -eq "") {
53+
Write-Host "No runtime found. Exiting..."
54+
exit 1
55+
} else {
56+
Write-Host "Found the following runtimes: "
57+
foreach ($runtime in $runtimes) {
58+
Write-Host " $runtime"
59+
}
60+
}
61+
62+
# Selecting the most recent runtime (e.g. last one in the list)
63+
[string] $runtime = $runtimes[-1]
64+
Write-Host " Selected runtime:`n $runtime"
65+
66+
# Parse path to runtime from something like "Microsoft.NETCore.App 5.0.0 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.0]" to "C:\Program Files\dotnet\shared\Microsoft.NETCore.App\5.0.0"
67+
[string] $runtime_path = $runtime -replace 'Microsoft.NETCore.App (?<RuntimeVersion>.+) \[(?<RuntimePath>.+)\]', '${RuntimePath}/${RuntimeVersion}'
68+
Write-Host " Using the following path to runtime:`n $runtime_path"
69+
70+
# Check whether path exists, if it doesn't something unexpected happens and needs investigation
71+
if (-not (Test-Path $runtime_path)) {
72+
Write-Host "Path to runtime not found. Exiting..."
73+
exit 1
74+
}
75+
76+
# For every artifact, every configuration and TFM, run a dotnet-ilverify with references from discovered runtime directory:
77+
foreach ($project in $projects.Keys) {
78+
foreach ($tfm in $projects[$project]) {
79+
foreach ($configuration in $configurations) {
80+
$dll_path = "$artifacts_bin_path/$project/$configuration/$tfm/$project.dll"
81+
if (-not (Test-Path $dll_path)) {
82+
Write-Host "DLL not found: $dll_path"
83+
exit 1
84+
}
85+
Write-Host "Verifying $dll_path..."
86+
# If there are any errors to ignore in the array, ignore them with `-g` flag
87+
$ignore_errors_string =
88+
if ($ignore_errors.Length -gt 0) {
89+
$ignore_errors | ForEach-Object {
90+
"-g $_"
91+
}
92+
} else { "" }
93+
$ilverify_cmd = "dotnet ilverify --statistics --sanity-checks --tokens $dll_path -r '$runtime_path/*.dll' -r '$artifacts_bin_path/$project/$configuration/$tfm/FSharp.Core.dll' $ignore_errors_string"
94+
Write-Host "Running ilverify command:`n $ilverify_cmd"
95+
96+
# Append output to output array
97+
$ilverify_output = @(Invoke-Expression "& $ilverify_cmd" -ErrorAction SilentlyContinue)
98+
99+
# Normalize output, get rid of paths in log like
100+
# [IL]: Error [StackUnexpected]: [/Users/u/code/fsharp3/artifacts/bin/FSharp.Compiler.Service/Release/net9.0/FSharp.Core.dll : Microsoft.FSharp.Collections.ArrayModule+Parallel::Choose([FSharp.Core]Microsoft.FSharp.Core.FSharpFunc`2<!!0,Microsoft.FSharp.Core.FSharpOption`1<!!1>>, !!0[])][offset 0x00000081][found Byte] Unexpected type on the stack.
101+
# This is a quick and dirty way to do it, but it works for now.
102+
$ilverify_output = $ilverify_output | ForEach-Object {
103+
if ($_ -match "\[IL\]: Error \[") {
104+
$parts = $_ -split " "
105+
"$($parts[0]) $($parts[1]) $($parts[2]) $($parts[4..$parts.Length])"
106+
} elseif ($_ -match "Error\(s\) Verifying") {
107+
# do nothing
108+
} else {
109+
$_
110+
}
111+
}
112+
113+
$baseline_file = Join-Path $repo_path "eng" "ilverify_${project}_${configuration}_${tfm}.bsl"
114+
115+
$baseline_actual_file = [System.IO.Path]::ChangeExtension($baseline_file, 'bsl.actual')
116+
117+
if (-not (Test-Path $baseline_file)) {
118+
Write-Host "Baseline file not found: $baseline_file"
119+
$ilverify_output | Set-Content $baseline_actual_file
120+
exit 1
121+
}
122+
123+
# Read baseline file into string array
124+
[string[]] $baseline = Get-Content $baseline_file
125+
126+
if ($baseline.Length -eq 0) {
127+
Write-Host "Baseline file is empty: $baseline_file"
128+
$ilverify_output | Set-Content $baseline_actual_file
129+
exit 1
130+
}
131+
132+
# Compare contents of both arrays, error if they're not equal
133+
134+
$cmp = Compare-Object $ilverify_output $baseline
135+
136+
if (-not $cmp) {
137+
Write-Host "ILverify output matches baseline."
138+
} else {
139+
Write-Host "ILverify output does not match baseline, differences:"
140+
141+
$cmp | Format-Table | Out-String | Write-Host
142+
$ilverify_output | Set-Content $baseline_actual_file
143+
144+
exit 1
145+
}
146+
}
147+
}
148+
}
149+
150+
exit 0

0 commit comments

Comments
 (0)