Skip to content

Commit 99b296b

Browse files
dsymeDon Syme
and
Don Syme
authored
Fix 7456, 3704, 12019 (debug scopes, self arg, incorrect information display on shadowing) (#12018)
* fix Fix 7456, 3704, 12019 (debug scopes, self arg, incorrect information display on shadowing) #12018 * update baselines * fix regression in debug codegen * update test * update baselines * fix debug scopes for module initialization code * fix cases of nested shadowing * fix shadowing for some cases * add missing file * fix build * update baselines Co-authored-by: Don Syme <donsyme@fastmail.com>
1 parent 185ddc8 commit 99b296b

22 files changed

+686
-249
lines changed

src/fsharp/IlxGen.fs

+219-138
Large diffs are not rendered by default.

src/fsharp/absil/ilwrite.fs

+4-2
Original file line numberDiff line numberDiff line change
@@ -2146,7 +2146,6 @@ module Codebuf =
21462146
mkScopeNode cenv importScope localSigs (s1, e1, cl.DebugMappings, children))
21472147
trees
21482148

2149-
21502149
// Emit the SEH tree
21512150
let rec emitExceptionHandlerTree (codebuf: CodeBuffer) (Node (x, childSEH)) =
21522151
List.iter (emitExceptionHandlerTree codebuf) childSEH // internal first
@@ -2183,7 +2182,10 @@ module Codebuf =
21832182

21842183
// Build the locals information, ready to emit
21852184
let localsTree = makeLocalsTree cenv importScope localSigs pc2pos code.Labels code.Locals
2186-
localsTree
2185+
2186+
// Adjust the scopes for shadowing
2187+
let unshadowed = List.collect (unshadowScopes >> Array.toList) localsTree
2188+
unshadowed
21872189

21882190
let EmitMethodCode cenv importScope localSigs env nm code =
21892191
use codebuf = CodeBuffer.Create nm

src/fsharp/absil/ilwritepdb.fs

+92-26
Original file line numberDiff line numberDiff line change
@@ -293,20 +293,6 @@ let scopeSorter (scope1: PdbMethodScope) (scope2: PdbMethodScope) =
293293
elif (scope1.EndOffset - scope1.StartOffset) < (scope2.EndOffset - scope2.StartOffset) then 1
294294
else 0
295295

296-
let collectScopes scope =
297-
let list = List<PdbMethodScope>()
298-
let rec toList scope parent =
299-
let nested =
300-
match parent with
301-
| Some p -> scope.StartOffset <> p.StartOffset || scope.EndOffset <> p.EndOffset
302-
| None -> true
303-
304-
if nested then list.Add scope
305-
scope.Children |> Seq.iter(fun s -> toList s (if nested then Some scope else parent))
306-
307-
toList scope None
308-
list.ToArray() |> Array.sortWith<PdbMethodScope> scopeSorter
309-
310296
type PortablePdbGenerator (embedAllSource: bool, embedSourceList: string list, sourceLink: string, checksumAlgorithm, showTimes, info: PdbData, pathMap: PathMap) =
311297

312298
let docs =
@@ -527,30 +513,50 @@ type PortablePdbGenerator (embedAllSource: bool, embedSourceList: string list, s
527513
importScopesTable.Add(imports, result)
528514
result
529515

530-
let writeMethodScopes methToken scope =
531-
for s in collectScopes scope do
516+
let flattenScopes rootScope =
517+
let list = List<PdbMethodScope>()
518+
let rec flattenScopes scope parent =
519+
520+
list.Add scope
521+
for nestedScope in scope.Children do
522+
let isNested =
523+
match parent with
524+
| Some p -> nestedScope.StartOffset >= p.StartOffset && nestedScope.EndOffset <= p.EndOffset
525+
| None -> true
526+
527+
flattenScopes nestedScope (if isNested then Some scope else parent)
528+
529+
flattenScopes rootScope None
530+
531+
list.ToArray()
532+
|> Array.sortWith<PdbMethodScope> scopeSorter
533+
534+
let writeMethodScopes methToken rootScope =
535+
536+
let flattenedScopes = flattenScopes rootScope
532537

533-
// Get or create the import scope for this method
534-
let importScopeHandle =
538+
// Get or create the import scope for this method
539+
let importScopeHandle =
535540
#if EMIT_IMPORT_SCOPES
536-
match s.Imports with
537-
| None -> Unchecked.defaultof<_>
538-
| Some imports -> getImportScopeIndex imports
541+
match s.Imports with
542+
| None -> Unchecked.defaultof<_>
543+
| Some imports -> getImportScopeIndex imports
539544
#else
540-
getImportScopeIndex |> ignore // make sure this code counts as used
541-
Unchecked.defaultof<_>
545+
getImportScopeIndex |> ignore // make sure this code counts as used
546+
Unchecked.defaultof<_>
542547
#endif
543548

549+
for scope in flattenedScopes do
544550
let lastRowNumber = MetadataTokens.GetRowNumber(LocalVariableHandle.op_Implicit lastLocalVariableHandle)
545551
let nextHandle = MetadataTokens.LocalVariableHandle(lastRowNumber + 1)
546552

547553
metadata.AddLocalScope(MetadataTokens.MethodDefinitionHandle(methToken),
548554
importScopeHandle,
549555
nextHandle,
550556
Unchecked.defaultof<LocalConstantHandle>,
551-
s.StartOffset, s.EndOffset - s.StartOffset ) |>ignore
557+
scope.StartOffset, scope.EndOffset - scope.StartOffset ) |>ignore
552558

553-
for localVariable in s.Locals do
559+
for localVariable in scope.Locals do
554560
lastLocalVariableHandle <- metadata.AddLocalVariable(LocalVariableAttributes.None, localVariable.Index, metadata.GetOrAddString(localVariable.Name))
555561

556562
let emitMethod minfo =
@@ -957,7 +963,8 @@ let logDebugInfo (outfile: string) (info: PdbData) =
957963
fprintfn sw "ENTRYPOINT\r\n %b\r\n" info.EntryPoint.IsSome
958964
fprintfn sw "DOCUMENTS"
959965
for i, doc in Seq.zip [0 .. info.Documents.Length-1] info.Documents do
960-
fprintfn sw " [%d] %s" i doc.File
966+
// File names elided because they are ephemeral during testing
967+
fprintfn sw " [%d] <elided-for-testing>" i // doc.File
961968
fprintfn sw " Type: %A" doc.DocumentType
962969
fprintfn sw " Language: %A" doc.Language
963970
fprintfn sw " Vendor: %A" doc.Vendor
@@ -987,3 +994,62 @@ let logDebugInfo (outfile: string) (info: PdbData) =
987994
| None -> ()
988995
| Some rootscope -> writeScope "" rootscope
989996
fprintfn sw ""
997+
998+
let rec allNamesOfScope acc (scope: PdbMethodScope) =
999+
let acc = (acc, scope.Locals) ||> Array.fold (fun z l -> Set.add l.Name z)
1000+
let acc = (acc, scope.Children) ||> allNamesOfScopes
1001+
acc
1002+
and allNamesOfScopes acc (scopes: PdbMethodScope[]) =
1003+
(acc, scopes) ||> Array.fold allNamesOfScope
1004+
1005+
let rec pushShadowedLocals (localsToPush: PdbLocalVar[]) (scope: PdbMethodScope) =
1006+
// Check if child scopes are properly nested
1007+
if scope.Children |> Array.forall (fun child ->
1008+
child.StartOffset >= scope.StartOffset && child.EndOffset <= scope.EndOffset) then
1009+
1010+
let children = scope.Children |> Array.sortWith scopeSorter
1011+
1012+
// Find all the names defined in this scope
1013+
let scopeNames = set [| for n in scope.Locals -> n.Name |]
1014+
1015+
// Rename if necessary as we push
1016+
let rename, unprocessed = localsToPush |> Array.partition (fun l -> scopeNames.Contains l.Name)
1017+
let renamed = [| for l in rename -> { l with Name = l.Name + " (shadowed)" } |]
1018+
1019+
let localsToPush2 = [| yield! renamed; yield! unprocessed; yield! scope.Locals |]
1020+
let newChildren, splits = children |> Array.map (pushShadowedLocals localsToPush2) |> Array.unzip
1021+
1022+
// Check if a rename in any of the children forces a split
1023+
if splits |> Array.exists id then
1024+
let results =
1025+
[|
1026+
// First fill in the gaps between the children with an adjusted version of this scope.
1027+
let gaps =
1028+
[| yield (scope.StartOffset, scope.StartOffset)
1029+
for newChild in children do
1030+
yield (newChild.StartOffset, newChild.EndOffset)
1031+
yield (scope.EndOffset, scope.EndOffset) |]
1032+
1033+
for ((_,a),(b,_)) in Array.pairwise gaps do
1034+
if a < b then
1035+
yield { scope with Locals=localsToPush2; Children = [| |]; StartOffset = a; EndOffset = b}
1036+
1037+
yield! Array.concat newChildren
1038+
|]
1039+
let results2 = results |> Array.sortWith scopeSorter
1040+
results2, true
1041+
else
1042+
let splitsParent = renamed.Length > 0
1043+
[| { scope with Locals=localsToPush2 } |], splitsParent
1044+
else
1045+
[| scope |], false
1046+
1047+
// Check to see if a scope has a local with the same name as any of its children
1048+
//
1049+
// If so, do not emit 'scope' itself. Instead,
1050+
// 1. Emit a copy of 'scope' in each true gap, with all locals
1051+
// 2. Adjust each child scope to also contain the locals from 'scope',
1052+
// adding the text " (shadowed)" to the names of those with name conflicts.
1053+
let unshadowScopes rootScope =
1054+
let result, _ = pushShadowedLocals [| |] rootScope
1055+
result

src/fsharp/absil/ilwritepdb.fsi

+11
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,21 @@ type HashAlgorithm =
119119
| Sha256
120120

121121
val generatePortablePdb : embedAllSource: bool -> embedSourceList: string list -> sourceLink: string -> checksumAlgorithm: HashAlgorithm -> showTimes: bool -> info: PdbData -> pathMap:PathMap -> int64 * BlobContentId * MemoryStream * string * byte[]
122+
122123
val compressPortablePdbStream : uncompressedLength:int64 -> contentId:BlobContentId -> stream:MemoryStream -> int64 * BlobContentId * MemoryStream
124+
123125
val embedPortablePdbInfo: uncompressedLength: int64 -> contentId: BlobContentId -> stream: MemoryStream -> showTimes: bool -> fpdb: string -> cvChunk: BinaryChunk -> pdbChunk: BinaryChunk -> deterministicPdbChunk: BinaryChunk -> checksumPdbChunk: BinaryChunk -> algorithmName: string -> checksum: byte[] -> embeddedPdb: bool -> deterministic: bool -> idd[]
126+
124127
val writePortablePdbInfo: contentId: BlobContentId -> stream: MemoryStream -> showTimes: bool -> fpdb: string -> pathMap: PathMap -> cvChunk: BinaryChunk -> deterministicPdbChunk: BinaryChunk -> checksumPdbChunk: BinaryChunk -> algorithmName: string -> checksum: byte[] -> embeddedPdb: bool -> deterministic: bool -> idd[]
125128

126129
#if !FX_NO_PDB_WRITER
127130
val writePdbInfo : showTimes:bool -> f:string -> fpdb:string -> info:PdbData -> cvChunk:BinaryChunk -> idd[]
128131
#endif
132+
133+
/// Check to see if a scope has a local with the same name as any of its children
134+
///
135+
/// If so, do not emit 'scope' itself. Instead,
136+
/// 1. Emit a copy of 'scope' in each true gap, with all locals
137+
/// 2. Adjust each child scope to also contain the locals from 'scope',
138+
/// adding the text " (shadowed)" to the names of those with name conflicts.
139+
val unshadowScopes: PdbMethodScope -> PdbMethodScope[]

tests/FSharp.Test.Utilities/CompilerAssert.fs

+27-3
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ open TestFramework
2121
[<Sealed>]
2222
type ILVerifier (dllFilePath: string) =
2323

24-
member this.VerifyIL (expectedIL: string list) =
24+
member _.VerifyIL (expectedIL: string list) =
2525
ILChecker.checkIL dllFilePath expectedIL
2626

27-
//member this.VerifyILWithDebugPoints (expectedIL: string list) =
28-
// ILChecker.checkILWithDebugPoints dllFilePath expectedIL
27+
[<Sealed>]
28+
type PdbDebugInfo(debugInfo: string) =
29+
30+
member _.InfoText = debugInfo
2931

3032
type Worker () =
3133
inherit MarshalByRefObject()
@@ -622,6 +624,28 @@ type CompilerAssert private () =
622624
f (ILVerifier outputFilePath)
623625
)
624626

627+
static member CompileLibraryAndVerifyDebugInfoWithOptions options (expectedFile: string) (source: string) =
628+
let options = [| yield! options; yield"--test:DumpDebugInfo" |]
629+
compile false options source (fun (errors, outputFilePath) ->
630+
let errors =
631+
errors |> Array.filter (fun x -> x.Severity = FSharpDiagnosticSeverity.Error)
632+
if errors.Length > 0 then
633+
Assert.Fail (sprintf "Compile had errors: %A" errors)
634+
let debugInfoFile = outputFilePath + ".debuginfo"
635+
if not (File.Exists expectedFile) then
636+
File.Copy(debugInfoFile, expectedFile)
637+
failwith $"debug info expected file {expectedFile} didn't exist, now copied over"
638+
let debugInfo = File.ReadAllLines(debugInfoFile)
639+
let expected = File.ReadAllLines(expectedFile)
640+
if debugInfo <> expected then
641+
File.Copy(debugInfoFile, expectedFile, overwrite=true)
642+
failwith $"""debug info mismatch
643+
Expected is in {expectedFile}
644+
Actual is in {debugInfoFile}
645+
Updated automatically, please check diffs in your pull request, changes must be scrutinized
646+
"""
647+
)
648+
625649
static member CompileLibraryAndVerifyIL (source: string) (f: ILVerifier -> unit) =
626650
CompilerAssert.CompileLibraryAndVerifyILWithOptions [||] source f
627651

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
ENTRYPOINT
2+
false
3+
4+
DOCUMENTS
5+
[0] <elided-for-testing>
6+
Type: None
7+
Language: None
8+
Vendor: None
9+
10+
METHODS
11+
f2
12+
Params: []
13+
Range: Some "[0,5:9] - [0,5:11]"
14+
Points:
15+
- Doc: 0 Offset:0 [5:5]-[5-15]
16+
- Doc: 0 Offset:2 [6:5]-[6-14]
17+
- Doc: 0 Offset:3 [16707566:0]-[16707566-0]
18+
- Doc: 0 Offset:6 [7:9]-[7-21]
19+
- Doc: 0 Offset:16 [8:9]-[8-18]
20+
- Doc: 0 Offset:17 [16707566:0]-[16707566-0]
21+
- Doc: 0 Offset:20 [9:12]-[9-24]
22+
- Doc: 0 Offset:26 [10:12]-[10-22]
23+
- Doc: 0 Offset:28 [11:12]-[11-14]
24+
- Doc: 0 Offset:30 [13:12]-[13-24]
25+
- Doc: 0 Offset:37 [14:12]-[14-22]
26+
- Doc: 0 Offset:40 [15:12]-[15-14]
27+
- Doc: 0 Offset:43 [17:9]-[17-21]
28+
- Doc: 0 Offset:54 [18:9]-[18-18]
29+
- Doc: 0 Offset:55 [16707566:0]-[16707566-0]
30+
- Doc: 0 Offset:58 [19:12]-[19-24]
31+
- Doc: 0 Offset:65 [20:12]-[20-22]
32+
- Doc: 0 Offset:68 [21:12]-[21-14]
33+
- Doc: 0 Offset:71 [23:12]-[23-24]
34+
- Doc: 0 Offset:78 [24:12]-[24-22]
35+
- Doc: 0 Offset:81 [25:12]-[25-14]
36+
Scopes:
37+
- [0-84]
38+
- [1-15]
39+
Locals: ["0: v1"]
40+
- [15-25]
41+
Locals: ["0: v1"; "1: v2"]
42+
- [25-27]
43+
Locals: ["0: v1 (shadowed)"; "1: v2"; "2: v1"]
44+
- [27-30]
45+
Locals: ["1: v2 (shadowed)"; "0: v1 (shadowed)"; "2: v1"; "3: v2"]
46+
- [30-35]
47+
Locals: ["0: v1"; "1: v2"]
48+
- [35-38]
49+
Locals: ["0: v1 (shadowed)"; "1: v2"; "4: v1"]
50+
- [38-43]
51+
Locals: ["1: v2 (shadowed)"; "0: v1 (shadowed)"; "4: v1"; "5: v2"]
52+
- [43-52]
53+
Locals: ["0: v1"]
54+
- [52-63]
55+
Locals: ["0: v1"; "6: v2"]
56+
- [63-66]
57+
Locals: ["0: v1 (shadowed)"; "6: v2"; "7: v1"]
58+
- [66-71]
59+
Locals: ["6: v2 (shadowed)"; "0: v1 (shadowed)"; "7: v1"; "8: v2"]
60+
- [71-76]
61+
Locals: ["0: v1"; "6: v2"]
62+
- [76-79]
63+
Locals: ["0: v1 (shadowed)"; "6: v2"; "9: v1"]
64+
- [79-84]
65+
Locals: ["6: v2 (shadowed)"; "0: v1 (shadowed)"; "9: v1"; "10: v2"]
66+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information.
2+
3+
namespace FSharp.Compiler.UnitTests.CodeGen.EmittedIL
4+
5+
open FSharp.Test
6+
open NUnit.Framework
7+
8+
[<TestFixture>]
9+
module DebugScopes =
10+
11+
[<Test>]
12+
let SimpleFunction() =
13+
CompilerAssert.CompileLibraryAndVerifyDebugInfoWithOptions
14+
[|"--debug:portable"; "--optimize-"; "--optimize-"|]
15+
(__SOURCE_DIRECTORY__ + "/SimpleFunction.debuginfo.expected")
16+
"""
17+
module Test
18+
let f x =
19+
let y = 1
20+
2
21+
"""
22+
23+
[<Test>]
24+
let SimpleShadowingFunction() =
25+
CompilerAssert.CompileLibraryAndVerifyDebugInfoWithOptions
26+
[|"--debug:portable"; "--optimize-"; "--optimize-"|]
27+
(__SOURCE_DIRECTORY__ + "/SimpleShadowingFunction.debuginfo.expected")
28+
"""
29+
module Test
30+
let f x =
31+
let y = 1
32+
let y = y+1
33+
let y = y+1
34+
2
35+
"""
36+
37+
[<Test>]
38+
let ComplexShadowingFunction() =
39+
CompilerAssert.CompileLibraryAndVerifyDebugInfoWithOptions
40+
[|"--debug:portable"; "--optimize-"; "--optimize-"|]
41+
(__SOURCE_DIRECTORY__ + "/ComplexShadowingFunction.debuginfo.expected")
42+
"""
43+
module Test
44+
45+
let f2 (a, b) =
46+
let v1 = 1
47+
if a then
48+
let v2 = 1.4
49+
if b then
50+
let v1 = "3"
51+
let v2 = 5
52+
v1
53+
else
54+
let v1 = "3"
55+
let v2 = 5
56+
v1
57+
else
58+
let v2 = 1.4
59+
if b then
60+
let v1 = "3"
61+
let v2 = 5
62+
v1
63+
else
64+
let v1 = "3"
65+
let v2 = 5
66+
v1
67+
68+
69+
70+
"""
71+

0 commit comments

Comments
 (0)