aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRonald Martin <cpuwzd@comcast.net>2021-02-12 22:37:15 -0500
committerRob Mensching <rob@firegiant.com>2021-02-19 14:57:41 -0800
commit9dd02e0c1bb6463de5d79f14ce06d078ef876a32 (patch)
tree96a3f60f31990d9c0a6ffb5c4d8d2430b277f695
parent0ce3a9b66da80c505fd8901df88d31b0ad43dac6 (diff)
downloadwix-9dd02e0c1bb6463de5d79f14ce06d078ef876a32.tar.gz
wix-9dd02e0c1bb6463de5d79f14ce06d078ef876a32.tar.bz2
wix-9dd02e0c1bb6463de5d79f14ce06d078ef876a32.zip
Checkks all custom actions for cycles. Version 3. Fixes wixtoolset/issues#6201
-rw-r--r--src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs78
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs37
-rw-r--r--src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs20
3 files changed, 114 insertions, 21 deletions
diff --git a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs
index d33836c3..056b129b 100644
--- a/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs
+++ b/src/WixToolset.Core.WindowsInstaller/Bind/SequenceActionsCommand.cs
@@ -67,7 +67,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
67 } 67 }
68 else // not a supported unscheduled action. 68 else // not a supported unscheduled action.
69 { 69 {
70 throw new InvalidOperationException("Found an action with no Sequence, Before, or After column set."); 70 throw new InvalidOperationException($"Found an action [{actionSymbol.Id.Id}] at [{actionSymbol.SourceLineNumbers}] with no Sequence, Before, or After column set.");
71 } 71 }
72 } 72 }
73 73
@@ -129,6 +129,9 @@ namespace WixToolset.Core.WindowsInstaller.Bind
129 } 129 }
130 } 130 }
131 131
132 // A dictionary used for detecting cyclic references among action symbols.
133 var firstReference = new Dictionary<WixActionSymbol, WixActionSymbol>();
134
132 // Build up dependency trees of the relatively scheduled actions. 135 // Build up dependency trees of the relatively scheduled actions.
133 // Use ToList() to create a copy of the required action symbols so that new symbols can 136 // Use ToList() to create a copy of the required action symbols so that new symbols can
134 // be added while enumerating. 137 // be added while enumerating.
@@ -142,7 +145,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
142 this.Messaging.Write(ErrorMessages.StandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action)); 145 this.Messaging.Write(ErrorMessages.StandardActionRelativelyScheduledInModule(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action));
143 } 146 }
144 147
145 this.SequenceActionSymbol(actionSymbol, requiredActionSymbols); 148 this.SequenceActionSymbol(actionSymbol, requiredActionSymbols, firstReference);
146 } 149 }
147 else if (SectionType.Module == this.Section.Type && 0 < actionSymbol.Sequence && !WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) // check for custom actions and dialogs that have a sequence number 150 else if (SectionType.Module == this.Section.Type && 0 < actionSymbol.Sequence && !WindowsInstallerStandard.IsStandardAction(actionSymbol.Action)) // check for custom actions and dialogs that have a sequence number
148 { 151 {
@@ -566,7 +569,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
566 /// </summary> 569 /// </summary>
567 /// <param name="actionSymbol">The action symbol to be sequenced.</param> 570 /// <param name="actionSymbol">The action symbol to be sequenced.</param>
568 /// <param name="requiredActionSymbols">Collection of actions which must be included.</param> 571 /// <param name="requiredActionSymbols">Collection of actions which must be included.</param>
569 private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary<string, WixActionSymbol> requiredActionSymbols) 572 private void SequenceActionSymbol(WixActionSymbol actionSymbol, Dictionary<string, WixActionSymbol> requiredActionSymbols, Dictionary<WixActionSymbol, WixActionSymbol> firstReference)
570 { 573 {
571 var after = false; 574 var after = false;
572 575
@@ -576,7 +579,7 @@ namespace WixToolset.Core.WindowsInstaller.Bind
576 } 579 }
577 else if (actionSymbol.Before == null) 580 else if (actionSymbol.Before == null)
578 { 581 {
579 throw new InvalidOperationException("Found an action with no Sequence, Before, or After column set."); 582 throw new InvalidOperationException($"Found an action [{actionSymbol.Id.Id}] at [{actionSymbol.SourceLineNumbers}] with no Sequence, Before, or After column set.");
580 } 583 }
581 584
582 var parentActionName = (after ? actionSymbol.After : actionSymbol.Before); 585 var parentActionName = (after ? actionSymbol.After : actionSymbol.Before);
@@ -597,10 +600,8 @@ namespace WixToolset.Core.WindowsInstaller.Bind
597 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Found an action with a non-existent {0} action: {1}.", (after ? "After" : "Before"), parentActionName)); 600 throw new InvalidOperationException(String.Format(CultureInfo.CurrentUICulture, "Found an action with a non-existent {0} action: {1}.", (after ? "After" : "Before"), parentActionName));
598 } 601 }
599 } 602 }
600 else if (actionSymbol == parentActionSymbol || this.ContainsChildActionSymbol(actionSymbol, parentActionSymbol)) // cycle detected 603
601 { 604 this.CheckForCircularActionReference(actionSymbol, requiredActionSymbols, firstReference);
602 throw new WixException(ErrorMessages.ActionCircularDependency(actionSymbol.SourceLineNumbers, actionSymbol.SequenceTable.ToString(), actionSymbol.Action, parentActionSymbol.Action));
603 }
604 605
605 // Add this action to the appropriate list of dependent action symbols. 606 // Add this action to the appropriate list of dependent action symbols.
606 var relativeActions = this.GetRelativeActions(parentActionSymbol); 607 var relativeActions = this.GetRelativeActions(parentActionSymbol);
@@ -608,19 +609,66 @@ namespace WixToolset.Core.WindowsInstaller.Bind
608 relatedSymbols.Add(actionSymbol); 609 relatedSymbols.Add(actionSymbol);
609 } 610 }
610 611
611 private bool ContainsChildActionSymbol(WixActionSymbol childSymbol, WixActionSymbol parentSymbol) 612 /// <summary>
613 /// Check the specified action symbol to see if it leads to a cycle.
614 /// </summary>
615 /// <para> Use the provided dictionary to note the initial action symbol that first led to each action
616 /// symbol. Any action symbol encountered that has already been encountered starting from a different
617 /// initial action symbol inherits the loop characteristics of that initial action symbol, and thus is
618 /// also not part of a cycle. However, any action symbol encountered that has already been encountered
619 /// starting from the same initial action symbol is an indication that the current action symbol is
620 /// part of a cycle.
621 /// </para>
622 /// <param name="actionSymbol">The action symbol to be checked.</param>
623 /// <param name="requiredActionSymbols">Collection of actions which must be included.</param>
624 /// <param name="firstReference">The first encountered action symbol that led to each action symbol.</param>
625 private void CheckForCircularActionReference(WixActionSymbol actionSymbol, Dictionary<string, WixActionSymbol> requiredActionSymbols, Dictionary<WixActionSymbol, WixActionSymbol> firstReference)
612 { 626 {
613 var result = false; 627 WixActionSymbol currentActionSymbol = null;
628 var parentActionSymbol = actionSymbol;
614 629
615 if (this.RelativeActionsForActions.TryGetValue(childSymbol.Id.Id, out var relativeActions)) 630 do
616 { 631 {
617 result = relativeActions.NextActions.Any(a => a.SequenceTable == parentSymbol.SequenceTable && a.Id.Id == parentSymbol.Id.Id) || 632 var previousActionSymbol = currentActionSymbol ?? parentActionSymbol;
618 relativeActions.PreviousActions.Any(a => a.SequenceTable == parentSymbol.SequenceTable && a.Id.Id == parentSymbol.Id.Id); 633 currentActionSymbol = parentActionSymbol;
619 }
620 634
621 return result; 635 if (!firstReference.TryGetValue(currentActionSymbol, out var existingInitialActionSymbol))
636 {
637 firstReference[currentActionSymbol] = actionSymbol;
638 }
639 else if (existingInitialActionSymbol == actionSymbol)
640 {
641 throw new WixException(ErrorMessages.ActionCircularDependency(currentActionSymbol.SourceLineNumbers, currentActionSymbol.SequenceTable.ToString(), currentActionSymbol.Action, previousActionSymbol.Action));
642 }
643
644 parentActionSymbol = this.GetParentActionSymbol(currentActionSymbol, requiredActionSymbols);
645 } while (null != parentActionSymbol);
622 } 646 }
623 647
648 /// <summary>
649 /// Get the action symbol that is the parent of the given action symbol.
650 /// </summary>
651 /// <param name="actionSymbol">The given action symbol.</param>
652 /// <param name="requiredActionSymbols">Collection of actions which must be included.</param>
653 /// <returns>Null if there is no parent. Used for loop termination.</returns>
654 private WixActionSymbol GetParentActionSymbol(WixActionSymbol actionSymbol, Dictionary<string, WixActionSymbol> requiredActionSymbols)
655 {
656 if (null == actionSymbol.Before && null == actionSymbol.After)
657 {
658 return null;
659 }
660
661 var parentActionKey = actionSymbol.SequenceTable.ToString() + "/" + (actionSymbol.After ?? actionSymbol.Before);
662
663 if (!requiredActionSymbols.TryGetValue(parentActionKey, out var parentActionSymbol))
664 {
665 WindowsInstallerStandard.TryGetStandardAction(parentActionKey, out parentActionSymbol);
666 }
667
668 return parentActionSymbol;
669 }
670
671
624 private RelativeActions GetRelativeActions(WixActionSymbol action) 672 private RelativeActions GetRelativeActions(WixActionSymbol action)
625 { 673 {
626 if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions)) 674 if (!this.RelativeActionsForActions.TryGetValue(action.Id.Id, out var relativeActions))
diff --git a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs
index 65f4be31..7980ea61 100644
--- a/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs
+++ b/src/test/WixToolsetTest.CoreIntegration/CustomActionFixture.cs
@@ -11,7 +11,7 @@ namespace WixToolsetTest.CoreIntegration
11 11
12 public class CustomActionFixture 12 public class CustomActionFixture
13 { 13 {
14 [Fact(Skip = "https://github.com/wixtoolset/issues/issues/6201")] 14 [Fact]
15 public void CanDetectCustomActionCycle() 15 public void CanDetectCustomActionCycle()
16 { 16 {
17 var folder = TestData.Get(@"TestData"); 17 var folder = TestData.Get(@"TestData");
@@ -22,8 +22,8 @@ namespace WixToolsetTest.CoreIntegration
22 var intermediateFolder = Path.Combine(baseFolder, "obj"); 22 var intermediateFolder = Path.Combine(baseFolder, "obj");
23 var msiPath = Path.Combine(baseFolder, @"bin\test.msi"); 23 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
24 24
25 var result = WixRunner.Execute(new[] 25 var exception = Assert.Throws<WixException>(() => WixRunner.Execute(new[]
26 { 26 {
27 "build", 27 "build",
28 Path.Combine(folder, "CustomAction", "CustomActionCycle.wxs"), 28 Path.Combine(folder, "CustomAction", "CustomActionCycle.wxs"),
29 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"), 29 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
@@ -31,10 +31,35 @@ namespace WixToolsetTest.CoreIntegration
31 "-bindpath", Path.Combine(folder, "SingleFile", "data"), 31 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
32 "-intermediateFolder", intermediateFolder, 32 "-intermediateFolder", intermediateFolder,
33 "-o", msiPath 33 "-o", msiPath
34 }); 34 }));
35
36 Assert.Equal("The InstallExecuteSequence table contains an action 'Action1' that is scheduled to come before or after action 'Action3', which is also scheduled to come before or after action 'Action1'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", exception.Message);
37 }
38 }
39
40 [Fact]
41 public void CanDetectCustomActionCycleWithTail()
42 {
43 var folder = TestData.Get(@"TestData");
44
45 using (var fs = new DisposableFileSystem())
46 {
47 var baseFolder = fs.GetFolder();
48 var intermediateFolder = Path.Combine(baseFolder, "obj");
49 var msiPath = Path.Combine(baseFolder, @"bin\test.msi");
50
51 var exception = Assert.Throws<WixException>(() => WixRunner.Execute(new[]
52 {
53 "build",
54 Path.Combine(folder, "CustomAction", "CustomActionCycleWithTail.wxs"),
55 Path.Combine(folder, "ProductWithComponentGroupRef", "MinimalComponentGroup.wxs"),
56 Path.Combine(folder, "ProductWithComponentGroupRef", "Product.wxs"),
57 "-bindpath", Path.Combine(folder, "SingleFile", "data"),
58 "-intermediateFolder", intermediateFolder,
59 "-o", msiPath
60 }));
35 61
36 var warnings = result.Messages.Where(m => m.Level == MessageLevel.Warning).ToArray(); 62 Assert.Equal("The InstallExecuteSequence table contains an action 'Action2' that is scheduled to come before or after action 'Action4', which is also scheduled to come before or after action 'Action2'. Please remove this circular dependency by changing the Before or After attribute for one of the actions.", exception.Message);
37 Assert.False(result.ExitCode == 0 && warnings.Length == 0);
38 } 63 }
39 } 64 }
40 65
diff --git a/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs
new file mode 100644
index 00000000..c64ef143
--- /dev/null
+++ b/src/test/WixToolsetTest.CoreIntegration/TestData/CustomAction/CustomActionCycleWithTail.wxs
@@ -0,0 +1,20 @@
1<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
2 <Fragment>
3 <ComponentGroup Id="ProductComponents">
4 <ComponentGroupRef Id="MinimalComponentGroup" />
5 </ComponentGroup>
6
7 <Binary Id="Binary1" SourceFile="test.txt" />
8 <CustomAction Id="Action1" DllEntry="EntryPoint1" BinaryRef="Binary1" />
9 <CustomAction Id="Action2" DllEntry="EntryPoint2" BinaryRef="Binary1" />
10 <CustomAction Id="Action3" DllEntry="EntryPoint3" BinaryRef="Binary1" />
11 <CustomAction Id="Action4" DllEntry="EntryPoint4" BinaryRef="Binary1" />
12
13 <InstallExecuteSequence>
14 <Custom Action="Action1" After="Action2" />
15 <Custom Action="Action2" After="Action3" />
16 <Custom Action="Action3" After="Action4" />
17 <Custom Action="Action4" After="Action2" />
18 </InstallExecuteSequence>
19 </Fragment>
20</Wix> \ No newline at end of file