Skip to content

Commit 8b8313c

Browse files
committed
Improve error reporting of duplicate symbols
Virtual symbols provide more interesting ways to have (and avoid) conflicts. Adding additional messages and cleaning up the existing messages should help users know what options they have to address conflicts. This also puts all the conflict resolution in ReportConflictingSymbolsCommand instead of spreading it across reference resolution as well.
1 parent 4960073 commit 8b8313c

12 files changed

+251
-78
lines changed

src/api/wix/WixToolset.Data/ErrorMessages.cs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -323,21 +323,6 @@ public static Message DuplicateSourcesForOutput(string sourceList, string output
323323
return Message(null, Ids.DuplicateSourcesForOutput, "Multiple source files ({0}) have resulted in the same output file '{1}'. This is likely because the source files only differ in extension or path. Rename the source files to avoid this problem.", sourceList, outputFile);
324324
}
325325

326-
public static Message DuplicateSymbol(SourceLineNumber sourceLineNumbers, string symbolName)
327-
{
328-
return Message(sourceLineNumbers, Ids.DuplicateSymbol, "Duplicate symbol '{0}' found. This typically means that an Id is duplicated. Access modifiers (global, library, file, section) cannot prevent these conflicts. Ensure all your identifiers of a given type (Directory, File, etc.) are unique.", symbolName);
329-
}
330-
331-
public static Message DuplicateSymbol(SourceLineNumber sourceLineNumbers, string symbolName, string referencingSourceLineNumber)
332-
{
333-
return Message(sourceLineNumbers, Ids.DuplicateSymbol, "Duplicate symbol '{0}' referenced by {1}. This typically means that an Id is duplicated. Ensure all your identifiers of a given type (Directory, File, etc.) are unique or use an access modifier to scope the identfier.", symbolName, referencingSourceLineNumber);
334-
}
335-
336-
public static Message DuplicateSymbol2(SourceLineNumber sourceLineNumbers)
337-
{
338-
return Message(sourceLineNumbers, Ids.DuplicateSymbol2, "Location of symbol related to previous error.");
339-
}
340-
341326
public static Message DuplicateTransform(string transform)
342327
{
343328
return Message(null, Ids.DuplicateTransform, "The transform {0} was included twice on the command line. Each transform can be applied to a patch only once.", transform);
@@ -2369,8 +2354,6 @@ public enum Ids
23692354
InvalidDateTimeFormat = 88,
23702355
MultipleEntrySections = 89,
23712356
MultipleEntrySections2 = 90,
2372-
DuplicateSymbol = 91,
2373-
DuplicateSymbol2 = 92,
23742357
MissingEntrySection = 93,
23752358
UnresolvedReference = 94,
23762359
MultiplePrimaryReferences = 95,

src/wix/WixToolset.Core/Link/FindEntrySectionAndLoadSymbolsCommand.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public FindEntrySectionAndLoadSymbolsCommand(IMessaging messaging, IEnumerable<I
3636
/// <summary>
3737
/// Gets the collection of possibly conflicting symbols.
3838
/// </summary>
39-
public IEnumerable<SymbolWithSection> PossibleConflicts { get; private set; }
39+
public IReadOnlyCollection<SymbolWithSection> PossibleConflicts { get; private set; }
4040

4141
/// <summary>
4242
/// Gets the collection of redundant symbols that should not be included
@@ -140,9 +140,7 @@ public void Execute()
140140
{
141141
if (symbolWithSection.Overrides is null)
142142
{
143-
var fullName = symbolWithSection.GetFullName();
144-
145-
this.Messaging.Write(LinkerErrors.VirtualSymbolNotFoundForOverride(symbolWithSection.Symbol.SourceLineNumbers, fullName));
143+
this.Messaging.Write(LinkerErrors.VirtualSymbolNotFoundForOverride(symbolWithSection.Symbol));
146144
}
147145
}
148146

src/wix/WixToolset.Core/Link/ReportConflictingSymbolsCommand.cs

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace WixToolset.Core.Link
99

1010
internal class ReportConflictingSymbolsCommand
1111
{
12-
public ReportConflictingSymbolsCommand(IMessaging messaging, IEnumerable<SymbolWithSection> possibleConflicts, IEnumerable<IntermediateSection> resolvedSections)
12+
public ReportConflictingSymbolsCommand(IMessaging messaging, IReadOnlyCollection<SymbolWithSection> possibleConflicts, ISet<IntermediateSection> resolvedSections)
1313
{
1414
this.Messaging = messaging;
1515
this.PossibleConflicts = possibleConflicts;
@@ -18,35 +18,62 @@ public ReportConflictingSymbolsCommand(IMessaging messaging, IEnumerable<SymbolW
1818

1919
private IMessaging Messaging { get; }
2020

21-
private IEnumerable<SymbolWithSection> PossibleConflicts { get; }
21+
private IReadOnlyCollection<SymbolWithSection> PossibleConflicts { get; }
2222

23-
private IEnumerable<IntermediateSection> ResolvedSections { get; }
23+
private ISet<IntermediateSection> ResolvedSections { get; }
2424

2525
public void Execute()
2626
{
27-
// Do a quick check if there are any possibly conflicting symbols that don't come from tables that allow
28-
// overriding. Hopefully the symbols with possible conflicts list is usually very short list (empty should
29-
// be the most common). If we find any matches, we'll do a more costly check to see if the possible conflicting
27+
// Do a quick check if there are any possibly conflicting symbols. Hopefully the symbols with possible conflicts
28+
// list is a very short list (empty should be the most common).
29+
//
30+
// If we have conflicts then we'll do a more costly check to see if the possible conflicting
3031
// symbols are in sections we actually referenced. From the resulting set, show an error for each duplicate
3132
// (aka: conflicting) symbol.
32-
var illegalDuplicates = this.PossibleConflicts.Where(s => s.Symbol.Definition.Type != SymbolDefinitionType.WixAction && s.Symbol.Definition.Type != SymbolDefinitionType.WixVariable).ToList();
33-
if (0 < illegalDuplicates.Count)
33+
if (0 < this.PossibleConflicts.Count)
3434
{
35-
var referencedSections = new HashSet<IntermediateSection>(this.ResolvedSections);
36-
37-
foreach (var referencedDuplicate in illegalDuplicates.Where(s => referencedSections.Contains(s.Section)))
35+
foreach (var referencedDuplicate in this.PossibleConflicts.Where(s => this.ResolvedSections.Contains(s.Section)))
3836
{
39-
var actuallyReferencedDuplicates = referencedDuplicate.PossiblyConflicts.Where(s => referencedSections.Contains(s.Section)).ToList();
37+
var actuallyReferencedDuplicates = referencedDuplicate.PossiblyConflicts.Where(s => this.ResolvedSections.Contains(s.Section)).ToList();
4038

41-
if (actuallyReferencedDuplicates.Any())
39+
if (actuallyReferencedDuplicates.Count > 0)
4240
{
43-
var fullName = referencedDuplicate.GetFullName();
41+
var conflicts = actuallyReferencedDuplicates.Append(referencedDuplicate).ToList();
42+
var virtualConflicts = conflicts.Where(s => s.Access == AccessModifier.Virtual).ToList();
43+
var overrideConflicts = conflicts.Where(s => s.Access == AccessModifier.Override).ToList();
44+
var otherConflicts = conflicts.Where(s => s.Access != AccessModifier.Virtual && s.Access != AccessModifier.Override).ToList();
45+
46+
IEnumerable<SymbolWithSection> reportDuplicates = actuallyReferencedDuplicates;
47+
48+
// If multiple symbols are virtual, use the duplicate virtual symbol message.
49+
if (virtualConflicts.Count > 1)
50+
{
51+
var first = virtualConflicts[0];
52+
var referencingSourceLineNumber = first.DirectReferences.FirstOrDefault()?.SourceLineNumbers;
4453

45-
this.Messaging.Write(ErrorMessages.DuplicateSymbol(referencedDuplicate.Symbol.SourceLineNumbers, fullName));
54+
reportDuplicates = virtualConflicts.Skip(1);
55+
56+
this.Messaging.Write(LinkerErrors.DuplicateVirtualSymbol(first.Symbol, referencingSourceLineNumber));
57+
}
58+
else if (virtualConflicts.Count == 1 && otherConflicts.Count > 0)
59+
{
60+
var first = otherConflicts[0];
61+
var referencingSourceLineNumber = first.DirectReferences.FirstOrDefault()?.SourceLineNumbers;
62+
63+
reportDuplicates = virtualConflicts;
64+
65+
this.Messaging.Write(LinkerErrors.VirtualSymbolMustBeOverridden(first.Symbol, referencingSourceLineNumber));
66+
}
67+
else
68+
{
69+
var referencingSourceLineNumber = referencedDuplicate.DirectReferences.FirstOrDefault()?.SourceLineNumbers;
70+
71+
this.Messaging.Write(LinkerErrors.DuplicateSymbol(referencedDuplicate.Symbol, referencingSourceLineNumber));
72+
}
4673

47-
foreach (var duplicate in actuallyReferencedDuplicates)
74+
foreach (var duplicate in reportDuplicates)
4875
{
49-
this.Messaging.Write(ErrorMessages.DuplicateSymbol2(duplicate.Symbol.SourceLineNumbers));
76+
this.Messaging.Write(LinkerErrors.DuplicateSymbol2(duplicate.Symbol));
5077
}
5178
}
5279
}

src/wix/WixToolset.Core/Link/ResolveReferencesCommand.cs

Lines changed: 27 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ public ResolveReferencesCommand(IMessaging messaging, IntermediateSection entryS
2727
this.BuildingMergeModule = (SectionType.Module == entrySection.Type);
2828
}
2929

30-
public IEnumerable<SymbolWithSection> ReferencedSymbolWithSections => this.referencedSymbols;
30+
public IReadOnlyCollection<SymbolWithSection> ReferencedSymbolWithSections => this.referencedSymbols;
3131

32-
public IEnumerable<IntermediateSection> ResolvedSections => this.resolvedSections;
32+
public ISet<IntermediateSection> ResolvedSections => this.resolvedSections;
3333

3434
private bool BuildingMergeModule { get; }
3535

@@ -63,55 +63,60 @@ private void RecursivelyResolveReferences(IntermediateSection section)
6363
// symbols provided. Then recursively call this method to process the
6464
// located symbol's section. All in all this is a very simple depth-first
6565
// search of the references per-section.
66-
foreach (var wixSimpleReferenceRow in section.Symbols.OfType<WixSimpleReferenceSymbol>())
66+
foreach (var reference in section.Symbols.OfType<WixSimpleReferenceSymbol>())
6767
{
6868
// If we're building a Merge Module, ignore all references to the Media table
6969
// because Merge Modules don't have Media tables.
70-
if (this.BuildingMergeModule && wixSimpleReferenceRow.Table == "Media")
70+
if (this.BuildingMergeModule && reference.Table == "Media")
7171
{
7272
continue;
7373
}
7474

7575
// See if the symbol (and any of its duplicates) are appropriately accessible.
76-
if (this.symbolsWithSections.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbolWithSection))
76+
if (this.symbolsWithSections.TryGetValue(reference.SymbolicName, out var symbolWithSection))
7777
{
7878
var accessible = this.DetermineAccessibleSymbols(section, symbolWithSection);
7979
if (accessible.Count == 1)
8080
{
8181
var accessibleSymbol = accessible[0];
82+
83+
accessibleSymbol.AddDirectReference(reference);
84+
8285
if (this.referencedSymbols.Add(accessibleSymbol) && null != accessibleSymbol.Section)
8386
{
8487
this.RecursivelyResolveReferences(accessibleSymbol.Section);
8588
}
8689
}
8790
else if (accessible.Count == 0)
8891
{
89-
this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbolWithSection.Access));
92+
this.Messaging.Write(ErrorMessages.UnresolvedReference(reference.SourceLineNumbers, reference.SymbolicName, symbolWithSection.Access));
9093
}
91-
else // display errors for the duplicate symbols.
94+
else // multiple symbols referenced creates conflicting symbols.
9295
{
93-
var accessibleSymbol = accessible[0];
94-
var accessibleFullName = accessibleSymbol.GetFullName();
95-
var referencingSourceLineNumber = wixSimpleReferenceRow.SourceLineNumbers?.ToString();
96-
97-
if (String.IsNullOrEmpty(referencingSourceLineNumber))
98-
{
99-
this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleFullName));
100-
}
101-
else
96+
// Remember the direct reference to the symbol for the error reporting later,
97+
// but do NOT continue resolving references found in these conflicting symbols.
98+
foreach (var conflict in accessible)
10299
{
103-
this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleFullName, referencingSourceLineNumber));
104-
}
100+
// This should NEVER happen.
101+
if (!conflict.PossiblyConflicts.Any())
102+
{
103+
throw new InvalidOperationException("If a reference can reference multiple symbols, those symbols MUST have already been recognized as possible conflicts.");
104+
}
105105

106-
foreach (var accessibleDuplicate in accessible.Skip(1))
107-
{
108-
this.Messaging.Write(ErrorMessages.DuplicateSymbol2(accessibleDuplicate.Symbol.SourceLineNumbers));
106+
conflict.AddDirectReference(reference);
107+
108+
this.referencedSymbols.Add(conflict);
109+
110+
if (conflict.Section != null)
111+
{
112+
this.resolvedSections.Add(conflict.Section);
113+
}
109114
}
110115
}
111116
}
112117
else
113118
{
114-
this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName));
119+
this.Messaging.Write(ErrorMessages.UnresolvedReference(reference.SourceLineNumbers, reference.SymbolicName));
115120
}
116121
}
117122
}
@@ -133,14 +138,6 @@ private List<SymbolWithSection> DetermineAccessibleSymbols(IntermediateSection r
133138

134139
foreach (var dupe in symbolWithSection.PossiblyConflicts)
135140
{
136-
//// don't count overridable WixActionSymbols
137-
//var symbolAction = symbolWithSection.Symbol as WixActionSymbol;
138-
//var dupeAction = dupe.Symbol as WixActionSymbol;
139-
//if (symbolAction?.Overridable != dupeAction?.Overridable)
140-
//{
141-
// continue;
142-
//}
143-
144141
if (this.AccessibleSymbol(referencingSection, dupe))
145142
{
146143
accessibleSymbols.Add(dupe);

src/wix/WixToolset.Core/Link/SymbolWithSection.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ namespace WixToolset.Core.Link
66
using System.Collections.Generic;
77
using System.Linq;
88
using WixToolset.Data;
9+
using WixToolset.Data.Symbols;
910

1011
/// <summary>
1112
/// Symbol with section representing a single unique symbol.
1213
/// </summary>
1314
internal class SymbolWithSection
1415
{
16+
private List<WixSimpleReferenceSymbol> directReferences;
1517
private HashSet<SymbolWithSection> possibleConflicts;
1618

1719
/// <summary>
@@ -43,6 +45,11 @@ public SymbolWithSection(IntermediateSection section, IntermediateSymbol symbol)
4345
/// <value>Section for the symbol.</value>
4446
public IntermediateSection Section { get; }
4547

48+
/// <summary>
49+
/// Gets any duplicates of this symbol with sections that are possible conflicts.
50+
/// </summary>
51+
public IEnumerable<WixSimpleReferenceSymbol> DirectReferences => this.directReferences ?? Enumerable.Empty<WixSimpleReferenceSymbol>();
52+
4653
/// <summary>
4754
/// Gets any duplicates of this symbol with sections that are possible conflicts.
4855
/// </summary>
@@ -67,6 +74,20 @@ public void AddPossibleConflict(SymbolWithSection symbolWithSection)
6774
this.possibleConflicts.Add(symbolWithSection);
6875
}
6976

77+
/// <summary>
78+
/// Adds a reference that directly points to this symbol.
79+
/// </summary>
80+
/// <param name="reference">The direct reference.</param>
81+
public void AddDirectReference(WixSimpleReferenceSymbol reference)
82+
{
83+
if (null == this.directReferences)
84+
{
85+
this.directReferences = new List<WixSimpleReferenceSymbol>();
86+
}
87+
88+
this.directReferences.Add(reference);
89+
}
90+
7091
/// <summary>
7192
/// Override a virtual symbol.
7293
/// </summary>

0 commit comments

Comments
 (0)