// 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.Link
{
using System;
using System.Collections.Generic;
using System.Linq;
using WixToolset.Data;
using WixToolset.Data.Tuples;
using WixToolset.Extensibility.Services;
///
/// Resolves all the simple references in a section.
///
internal class ResolveReferencesCommand
{
private IntermediateSection entrySection;
private IDictionary symbols;
private HashSet referencedSymbols;
private HashSet resolvedSections;
public ResolveReferencesCommand(IMessaging messaging, IntermediateSection entrySection, IDictionary symbols)
{
this.Messaging = messaging;
this.entrySection = entrySection;
this.symbols = symbols;
}
public bool BuildingMergeModule { private get; set; }
public IEnumerable ReferencedSymbols { get { return this.referencedSymbols; } }
public IEnumerable ResolvedSections { get { return this.resolvedSections; } }
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.Tuples.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;
}
if (!this.symbols.TryGetValue(wixSimpleReferenceRow.SymbolicName, out var symbol))
{
this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName));
}
else // see if the symbol (and any of its duplicates) are appropriately accessible.
{
IList accessible = DetermineAccessibleSymbols(section, symbol);
if (!accessible.Any())
{
this.Messaging.Write(ErrorMessages.UnresolvedReference(wixSimpleReferenceRow.SourceLineNumbers, wixSimpleReferenceRow.SymbolicName, symbol.Access));
}
else if (1 == accessible.Count)
{
var accessibleSymbol = accessible[0];
this.referencedSymbols.Add(accessibleSymbol);
if (null != accessibleSymbol.Section)
{
RecursivelyResolveReferences(accessibleSymbol.Section);
}
}
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.Row.SourceLineNumbers, accessibleSymbol.Name));
}
else
{
this.Messaging.Write(ErrorMessages.DuplicateSymbol(accessibleSymbol.Row.SourceLineNumbers, accessibleSymbol.Name, referencingSourceLineNumber));
}
foreach (Symbol accessibleDuplicate in accessible.Skip(1))
{
this.Messaging.Write(ErrorMessages.DuplicateSymbol2(accessibleDuplicate.Row.SourceLineNumbers));
}
}
}
}
}
///
/// 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 IList DetermineAccessibleSymbols(IntermediateSection referencingSection, Symbol symbol)
{
List symbols = new List();
if (AccessibleSymbol(referencingSection, symbol))
{
symbols.Add(symbol);
}
foreach (Symbol dupe in symbol.PossiblyConflictingSymbols)
{
if (AccessibleSymbol(referencingSection, dupe))
{
symbols.Add(dupe);
}
}
foreach (Symbol dupe in symbol.RedundantSymbols)
{
if (AccessibleSymbol(referencingSection, dupe))
{
symbols.Add(dupe);
}
}
return symbols;
}
///
/// 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, Symbol symbol)
{
switch (symbol.Access)
{
case AccessModifier.Public:
return true;
case AccessModifier.Internal:
return symbol.Section.CompilationId.Equals(referencingSection.CompilationId) || (null != symbol.Section.LibraryId && symbol.Section.LibraryId.Equals(referencingSection.LibraryId));
case AccessModifier.Protected:
return symbol.Section.CompilationId.Equals(referencingSection.CompilationId);
case AccessModifier.Private:
return referencingSection == symbol.Section;
default:
throw new ArgumentOutOfRangeException(nameof(symbol.Access));
}
}
}
}