// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information.
namespace WixToolset.Core.Link
{
using System;
using System.Collections.Generic;
using System.Linq;
using WixToolset.Data;
using WixToolset.Data.Symbols;
using WixToolset.Extensibility.Services;
///
/// Resolves all the simple references in a section.
///
internal class ResolveReferencesCommand
{
private readonly IntermediateSection entrySection;
private readonly IDictionary symbolsWithSections;
private HashSet referencedSymbols;
private HashSet resolvedSections;
public ResolveReferencesCommand(IMessaging messaging, IntermediateSection entrySection, IDictionary symbolsWithSections)
{
this.Messaging = messaging;
this.entrySection = entrySection;
this.symbolsWithSections = symbolsWithSections;
this.BuildingMergeModule = (SectionType.Module == entrySection.Type);
}
public IEnumerable ReferencedSymbolWithSections => this.referencedSymbols;
public IEnumerable ResolvedSections => this.resolvedSections;
private bool BuildingMergeModule { get; }
private IMessaging Messaging { get; }
///
/// Resolves all the simple references in a section.
///
public void Execute()
{
this.resolvedSections = new HashSet();
this.referencedSymbols = new HashSet();
this.RecursivelyResolveReferences(this.entrySection);
}
///
/// Recursive helper function to resolve all references of passed in section.
///
/// Section with references to resolve.
/// Note: recursive function.
private void RecursivelyResolveReferences(IntermediateSection section)
{
// If we already resolved this section, move on to the next.
if (!this.resolvedSections.Add(section))
{
return;
}
// Process all of the references contained in this section using the collection of
// symbols provided. Then recursively call this method to process the
// located symbol's section. All in all this is a very simple depth-first
// search of the references per-section.
foreach (var wixSimpleReferenceRow in section.Symbols.OfType())
{
// If we're building a Merge Module, ignore all references to the Media table
// because Merge Modules don't have Media tables.
if (this.BuildingMergeModule && wixSimpleReferenceRow.Table == "Media")
{
continue;
}
// See if the symbol (and any of its duplicates) are appropriately accessible.
if (this.symbolsWithSections.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbolWithSection))
{
var accessible = this.DetermineAccessibleSymbols(section, symbolWithSection);
if (accessible.Count == 1)
{
var accessibleSymbol = accessible[0];
if (this.referencedSymbols.Add(accessibleSymbol) && null != accessibleSymbol.Section)
{
this.RecursivelyResolveReferences(accessibleSymbol.Section);
}
}
else if (accessible.Count == 0)
{
this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbolWithSection.Access));
}
else // display errors for the duplicate symbols.
{
var accessibleSymbol = accessible[0];
var referencingSourceLineNumber = wixSimpleReferenceRow.SourceLineNumbers?.ToString();
if (String.IsNullOrEmpty(referencingSourceLineNumber))
{
this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name));
}
else
{
this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Symbol.SourceLineNumbers, accessibleSymbol.Name, referencingSourceLineNumber));
}
foreach (var accessibleDuplicate in accessible.Skip(1))
{
this.Messaging.Write(ErrorMessages.DuplicateSymbol2(accessibleDuplicate.Symbol.SourceLineNumbers));
}
}
}
else
{
this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName));
}
}
}
///
/// Determine if the symbol and any of its duplicates are accessbile by referencing section.
///
/// Section referencing the symbol.
/// Symbol being referenced.
/// List of symbols accessible by referencing section.
private List DetermineAccessibleSymbols(IntermediateSection referencingSection, SymbolWithSection symbolWithSection)
{
var accessibleSymbols = new List();
if (this.AccessibleSymbol(referencingSection, symbolWithSection))
{
accessibleSymbols.Add(symbolWithSection);
}
foreach (var dupe in symbolWithSection.PossiblyConflicts)
{
// don't count overridable WixActionSymbols
var symbolAction = symbolWithSection.Symbol as WixActionSymbol;
var dupeAction = dupe.Symbol as WixActionSymbol;
if (symbolAction?.Overridable != dupeAction?.Overridable)
{
continue;
}
if (this.AccessibleSymbol(referencingSection, dupe))
{
accessibleSymbols.Add(dupe);
}
}
foreach (var dupe in symbolWithSection.Redundants)
{
if (this.AccessibleSymbol(referencingSection, dupe))
{
accessibleSymbols.Add(dupe);
}
}
return accessibleSymbols;
}
///
/// Determine if a single symbol is accessible by the referencing section.
///
/// Section referencing the symbol.
/// Symbol being referenced.
/// True if symbol is accessible.
private bool AccessibleSymbol(IntermediateSection referencingSection, SymbolWithSection symbolWithSection)
{
switch (symbolWithSection.Access)
{
case AccessModifier.Global:
return true;
case AccessModifier.Library:
return symbolWithSection.Section.CompilationId == referencingSection.CompilationId || (null != symbolWithSection.Section.LibraryId && symbolWithSection.Section.LibraryId == referencingSection.LibraryId);
case AccessModifier.File:
return symbolWithSection.Section.CompilationId == referencingSection.CompilationId;
case AccessModifier.Section:
return referencingSection == symbolWithSection.Section;
default:
throw new ArgumentOutOfRangeException(nameof(symbolWithSection.Access));
}
}
}
}