GP-155 - Help - fixed intermittent build failure due to different nodes

with shared 'text' attribute values
This commit is contained in:
dragonmacher 2020-11-02 19:25:10 -05:00
parent 798c3abf42
commit 68ef3a22c5
4 changed files with 252 additions and 145 deletions

View file

@ -68,8 +68,7 @@
text="Program Annotations Affecting the Decompiler"
target="help/topics/DecompilePlugin/DecompilerAnnotations.html">
<tocdef id="AnnoteFunctionBody" sortgroup="a" text="Machine Instructions" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteFunctionBody"/>
<!-- Space added to text="Comments" attribute below to distinguish it from the CommentsPlugin section of the same name -->
<tocdef id="AnnoteComments" sortgroup="b" text="Comments " target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteComments"/>
<tocdef id="AnnoteComments" sortgroup="b" text="Comments" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteComments"/>
<tocdef id="AnnoteVariables" sortgroup="c" text="Variable Annotations" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteVariables"/>
<tocdef id="AnnotePrototype" sortgroup="d" text="Function Prototypes" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnotePrototype"/>
<tocdef id="AnnoteMutability" sortgroup="e" text="Data Mutability" target="help/topics/DecompilePlugin/DecompilerAnnotations.html#AnnoteMutability"/>

View file

@ -1,6 +1,5 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,14 +15,14 @@
*/
package help;
import help.validator.LinkDatabase;
import help.validator.model.*;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import help.validator.LinkDatabase;
import help.validator.model.*;
/**
* A class that will take in a group of help directories and create a tree of
* help Table of Contents (TOC) items. Ideally, this tree can be used to create a single
@ -33,7 +32,6 @@ import java.util.*;
* We call this class an <b>overlay</b> tree to drive home the idea that each
* help directory's TOC data is put into the tree, with any duplicate paths overlayed
* on top of those from other help directories.
*
*/
public class OverlayHelpTree {
@ -44,10 +42,13 @@ public class OverlayHelpTree {
public OverlayHelpTree(TOCItemProvider tocItemProvider, LinkDatabase linkDatabase) {
this.linkDatabase = linkDatabase;
for (TOCItemExternal external : tocItemProvider.getTOCItemExternalsByDisplayMapping().values()) {
for (TOCItemExternal external : tocItemProvider.getTOCItemExternalsByDisplayMapping()
.values()) {
addExternalTOCItem(external);
}
for (TOCItemDefinition definition : tocItemProvider.getTOCItemDefinitionsByIDMapping().values()) {
for (TOCItemDefinition definition : tocItemProvider.getTOCItemDefinitionsByIDMapping()
.values()) {
addSourceTOCItem(definition);
}
}
@ -167,12 +168,6 @@ public class OverlayHelpTree {
return false;
}
// TODO delete this debug
// Set<Entry<String, Set<TOCItem>>> entrySet = parentToChildrenMap.entrySet();
// for (Entry<String, Set<TOCItem>> entry : entrySet) {
// System.out.println(entry.getKey() + " -> " + entry.getValue());
// }
OverlayNode newRootNode = new OverlayNode(null, rootItem);
buildChildren(newRootNode);
@ -255,6 +250,7 @@ public class OverlayHelpTree {
}
}
// TODO LOOKIE
private static final Comparator<OverlayNode> CHILD_SORT_COMPARATOR =
new Comparator<OverlayNode>() {
@Override
@ -283,7 +279,16 @@ public class OverlayHelpTree {
return -1;
}
return text1.compareTo(text2);
int result = text1.compareTo(text2);
if (result != 0) {
return result;
}
// At this point we have 2 nodes that have the same text attribute as children of
// a <TOCDEF> tag. This is OK, as we use text only for sorting, but not for the
// display text. Use the ID as a tie-breaker for sorting, which should provide
// sorting consistency.
return o1.getIDAttribute().compareTo(o2.getIDAttribute()); // ID should not be null
}
};
}

View file

@ -15,12 +15,12 @@
*/
package help.validator.model;
import help.validator.LinkDatabase;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.*;
import help.validator.LinkDatabase;
/**
* A Table of Contents entry, which is represented in the help output as an xml tag.
*/
@ -63,7 +63,8 @@ public abstract class TOCItem {
String sortPreference, int lineNumber) {
this.parentItem = parentItem;
this.sourceFile = sourceFile;
this.IDAttribute = ID;
this.IDAttribute = Objects.requireNonNull(ID,
"TOC Tag missing 'id' attribute: " + sourceFile + ":" + lineNumber);
this.textAttribute = text;
this.targetAttribute = target;
@ -157,82 +158,108 @@ public abstract class TOCItem {
@Override
public boolean equals(Object obj) {
if (this == obj)
if (this == obj) {
return true;
if (obj == null)
}
if (obj == null) {
return false;
if (getClass() != obj.getClass())
}
if (getClass() != obj.getClass()) {
return false;
}
TOCItem other = (TOCItem) obj;
if (IDAttribute == null) {
if (other.IDAttribute != null)
if (other.IDAttribute != null) {
return false;
}
else if (!IDAttribute.equals(other.IDAttribute))
}
else if (!IDAttribute.equals(other.IDAttribute)) {
return false;
}
if (sortPreference == null) {
if (other.sortPreference != null)
if (other.sortPreference != null) {
return false;
}
else if (!sortPreference.equals(other.sortPreference))
}
else if (!sortPreference.equals(other.sortPreference)) {
return false;
}
if (sourceFile == null) {
if (other.sourceFile != null)
if (other.sourceFile != null) {
return false;
}
else if (!sourceFile.equals(other.sourceFile))
}
else if (!sourceFile.equals(other.sourceFile)) {
return false;
}
if (targetAttribute == null) {
if (other.targetAttribute != null)
if (other.targetAttribute != null) {
return false;
}
else if (!targetAttribute.equals(other.targetAttribute))
}
else if (!targetAttribute.equals(other.targetAttribute)) {
return false;
}
if (textAttribute == null) {
if (other.textAttribute != null)
if (other.textAttribute != null) {
return false;
}
else if (!textAttribute.equals(other.textAttribute))
}
else if (!textAttribute.equals(other.textAttribute)) {
return false;
}
return true;
}
/**
* True if the two items are the same, except that they come from a different source file.
* @param other the other item
* @return true if equivalent
*/
public boolean isEquivalent(TOCItem other) {
if (this == other)
if (this == other) {
return true;
if (other == null)
}
if (other == null) {
return false;
if (getClass() != other.getClass())
}
if (getClass() != other.getClass()) {
return false;
}
if (IDAttribute == null) {
if (other.IDAttribute != null)
if (other.IDAttribute != null) {
return false;
}
else if (!IDAttribute.equals(other.IDAttribute))
}
else if (!IDAttribute.equals(other.IDAttribute)) {
return false;
}
if (sortPreference == null) {
if (other.sortPreference != null)
if (other.sortPreference != null) {
return false;
}
else if (!sortPreference.equals(other.sortPreference))
}
else if (!sortPreference.equals(other.sortPreference)) {
return false;
}
if (targetAttribute == null) {
if (other.targetAttribute != null)
if (other.targetAttribute != null) {
return false;
}
else if (!targetAttribute.equals(other.targetAttribute))
}
else if (!targetAttribute.equals(other.targetAttribute)) {
return false;
}
if (textAttribute == null) {
if (other.textAttribute != null)
if (other.textAttribute != null) {
return false;
}
else if (!textAttribute.equals(other.textAttribute))
}
else if (!textAttribute.equals(other.textAttribute)) {
return false;
}
return true;
}
@ -253,7 +280,8 @@ public abstract class TOCItem {
}
}
public String generateTOCItemTag(LinkDatabase linkDatabase, boolean isInlineTag, int indentLevel) {
public String generateTOCItemTag(LinkDatabase linkDatabase, boolean isInlineTag,
int indentLevel) {
StringBuilder buildy = new StringBuilder();
buildy.append(INDENTS[indentLevel]);
buildy.append('<').append(TOC_TAG_NAME).append(' ');

View file

@ -15,7 +15,7 @@
*/
package help;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.*;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -68,12 +68,12 @@ public class OverlayHelpTreeTest {
Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root.getIDAttribute();
TOCItemReference root_ref = referenceItem(root_ID, tocSourceFile);
TOCItemReference root_ref = tocref(root_ID, tocSourceFile);
String child_1_ID = child_1.getIDAttribute();
TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, tocSourceFile);
TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, tocSourceFile);
TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", tocSourceFile);
TOCItemDefinition child_2 = tocdef(child_1_ref, "child_2", tocSourceFile);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addExternal(root);
@ -88,6 +88,57 @@ public class OverlayHelpTreeTest {
assertOrder(spy, 3, child_2);
}
@Test
public void testSourceTOCFileThatDependsAnotherTOCSourceFile() {
/*
The first source file defines attributes that the second file references.
Example makeup we will create:
TOC_Source.xml
<tocdef id="root" target="fake">
<tocdef id="child_1" target="fake" />
</tocdef>
Another TOC_Source.xml
<tocref id="root">
<tocref="child_1">
<tocdef id="child_2" target="fake" />
</tocref>
</tocref>
*/
Path toc_1 = Paths.get("/fake/path_1/TOC_Source.xml");
TOCItemDefinition root = tocdef("root", toc_1);
TOCItemDefinition child_1 = tocdef(root, "child_1", toc_1);
Path toc_2 = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root.getIDAttribute();
String child_1_ID = child_1.getIDAttribute();
TOCItemReference root_ref = tocref(root_ID, toc_2);
TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, toc_2);
TOCItemDefinition child_2 = tocdef(child_1_ref, "child_2", toc_2);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addDefinition(root);
tocProvider.addDefinition(child_1);
tocProvider.addDefinition(child_2);// in the second TOC file
TOCSpyWriter spy = printOverlayTree(tocProvider, toc_2);
assertNodeCount(spy, 3);
assertOrder(spy, 1, root);
assertOrder(spy, 2, child_1);
assertOrder(spy, 3, child_2);
}
@Test
public void testSourceTOCFileThatDependsUponPreBuiltHelp_MultiplePreBuiltInputs() {
//
@ -131,18 +182,17 @@ public class OverlayHelpTreeTest {
TOCItemExternal prebuilt_a_child = externalItem(child_1_a, "prebuilt_a_child");
// note: same ID values, since they represent the same nodes, but from different TOC files
TOCItemExternal root_b = externalItemAlt(null, "root");
TOCItemExternal child_1_b = externalItemAlt(root_b, "child_1");
TOCItemExternal prebuilt_b_child = externalItemAlt(child_1_b, "prebuilt_b_child");
TOCItemExternal root_b = externalItem(null, "root");
TOCItemExternal child_1_b = externalItem(root_b, "child_1");
TOCItemExternal prebuilt_b_child = externalItem(child_1_b, "prebuilt_b_child");
Path tocSourceFile = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root_a.getIDAttribute();
TOCItemReference root_ref = referenceItem(root_ID, tocSourceFile);
TOCItemReference root_ref = tocref(root_ID, tocSourceFile);
String child_1_ID = child_1_a.getIDAttribute();
TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, tocSourceFile);
TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", tocSourceFile);
TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, tocSourceFile);
TOCItemDefinition child_2 = tocdef(child_1_ref, "child_2", tocSourceFile);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addExternal(root_a);
@ -166,54 +216,79 @@ public class OverlayHelpTreeTest {
}
@Test
public void testSourceTOCFileThatDependsAnotherTOCSourceFile() {
public void testSourceTOCFileThatHasNodeWithSameTextAttributeAsOneOfItsExternalModluleDependencies() {
/*
The first source file defines attributes that the second file references.
The first source file defines attributes that the second file references. Both files
will have multiple nodes that coincidentally share 'text' attribute values.
Note: the 'id' attributes have to be unique; the 'text' attributes do not have to be unique
Example makeup we will create:
TOC_Source.xml
PreBuild_TOC.xml
<tocdef id="root" target="fake">
<tocdef id="child_1" target="fake" />
</tocdef>
<tocitem id="root" target="fake">
<tocitem id="child_1_1" text="Child 1" target="fake" />
</tocitem>
Another PreBuild_TOC.xml
<tocitem id="root" target="fake">
<tocitem id="child_2_1" text=Child 1" target="fake" />
<tocitem id="child_2_2" text=Child 2" target="fake" />
</tocitem>
Another TOC_Source.xml
<tocref id="root">
<tocref="child_1">
<tocdef id="child_2" target="fake" />
<tocref="child_1_1">
<tocdef id="child_2_1a" text="Child 1a" target="fake" />
</tocref>
<tocdef id="child_3_2" text="Child 2" target="fake" />
</tocref>
*/
Path toc_1 = Paths.get("/fake/path_1/TOC_Source.xml");
TOCItemDefinition root = definitionItem("root", toc_1);
TOCItemDefinition child_1 = definitionItem(root, "child_1", toc_1);
TOCItemExternal root_a = externalItem("root");
TOCItemExternal child_1_1 = externalItem(root_a, "child_1_1", "Child 1");
Path toc_2 = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root.getIDAttribute();
String child_1_ID = child_1.getIDAttribute();
// note: same ID values, since they represent the same nodes, but from different TOC files
TOCItemExternal root_b = externalItem(null, "root");
TOCItemExternal child_2_1 = externalItem(root_b, "child_2_1", "Child 1");
TOCItemExternal child_2_2 = externalItem(root_b, "child_2_2", "Child 2");
TOCItemReference root_ref = referenceItem(root_ID, toc_2);
TOCItemReference child_1_ref = referenceItem(root_ref, child_1_ID, toc_2);
TOCItemDefinition child_2 = definitionItem(child_1_ref, "child_2", toc_2);
Path toc = Paths.get("/fake/path_2/TOC_Source.xml");
String root_ID = root_a.getIDAttribute();
String child_1_ID = child_1_1.getIDAttribute();
TOCItemReference root_ref = tocref(root_ID, toc);
TOCItemReference child_1_ref = tocref(root_ref, child_1_ID, toc);
TOCItemDefinition child_2_1a = tocdef(child_1_ref, "child_2_1a", "Child 1a", toc);
TOCItemDefinition child_3_2 = tocdef(root_ref, "child_3_2", "Child 2", toc);
TOCItemProviderTestStub tocProvider = new TOCItemProviderTestStub();
tocProvider.addDefinition(root);
tocProvider.addDefinition(child_1);
tocProvider.addDefinition(child_2);// in the second TOC file
tocProvider.addExternal(root_a);
tocProvider.addExternal(child_1_1);
tocProvider.addExternal(root_b);
tocProvider.addExternal(child_2_1);
tocProvider.addExternal(child_2_2);
tocProvider.addDefinition(child_2_1a); // in the first external TOC file
tocProvider.addDefinition(child_3_2);
TOCSpyWriter spy = printOverlayTree(tocProvider, toc_2);
TOCSpyWriter spy = printOverlayTree(tocProvider, toc);
assertNodeCount(spy, 3);
assertOrder(spy, 1, root);
assertOrder(spy, 2, child_1);
assertOrder(spy, 3, child_2);
assertNodeCount(spy, 4);
assertOrder(spy, 1, root_a);
assertOrder(spy, 2, child_1_1);
assertOrder(spy, 3, child_2_1a);
assertOrder(spy, 4, child_3_2);
// note: prebuilt_a_child and prebuilt_b_child don't get output, since they do not have
// the same TOC file ID as the help file being processed (in other words, they don't
// live in the TOC_Source.xml being processes, so they are not part of the output).
}
//==================================================================================================
@ -242,22 +317,26 @@ public class OverlayHelpTreeTest {
return spy;
}
private TOCItemDefinition definitionItem(String ID, Path tocSourceFile) {
return definitionItem(null, ID, tocSourceFile);
private TOCItemDefinition tocdef(String ID, Path tocSourceFile) {
return tocdef(null, ID, tocSourceFile);
}
private TOCItemDefinition definitionItem(TOCItem parent, String ID, Path tocSourceFile) {
private TOCItemDefinition tocdef(TOCItem parent, String ID, Path tocSourceFile) {
return tocdef(parent, ID, ID, tocSourceFile);
}
private TOCItemDefinition tocdef(TOCItem parent, String ID, String text, Path tocSourceFile) {
String target = "fake";
String sort = "";
int line = 1;
return new TOCItemDefinition(parent, tocSourceFile, ID, ID, target, sort, line);
return new TOCItemDefinition(parent, tocSourceFile, ID, text, target, sort, line);
}
private TOCItemReference referenceItem(String referenceID, Path tocSourceFile) {
return referenceItem(null, referenceID, tocSourceFile);
private TOCItemReference tocref(String referenceID, Path tocSourceFile) {
return tocref(null, referenceID, tocSourceFile);
}
private TOCItemReference referenceItem(TOCItem parent, String referenceID, Path tocSourceFile) {
private TOCItemReference tocref(TOCItem parent, String referenceID, Path tocSourceFile) {
return new TOCItemReference(parent, tocSourceFile, referenceID, 1);
}
@ -266,24 +345,20 @@ public class OverlayHelpTreeTest {
}
private TOCItemExternal externalItem(TOCItem parent, String ID) {
Path tocFile = Paths.get("/fake/path_1/PreBuild_TOC.xml");
String target = "fake";
String sort = "";
int line = 1;
return new TOCItemExternal(parent, tocFile, ID, ID, target, sort, line);
return externalItem(parent, ID, ID);
}
private TOCItemExternal externalItemAlt(TOCItem parent, String ID) {
private TOCItemExternal externalItem(TOCItem parent, String ID, String text) {
Path tocFile = Paths.get("/fake/path_1/PreBuild_TOC.xml");
String target = "fake";
String sort = "";
int line = 1;
return new TOCItemExternal(parent, tocFile, ID, ID, target, sort, line);
return new TOCItemExternal(parent, tocFile, ID, text, target, sort, line);
}
private void assertOrder(TOCSpyWriter spy, int ordinal, TOCItem item) {
String ID = spy.getItem(ordinal - 1 /* make an index */);
assertEquals("Did not find TOC item at expected index: " + ordinal, item.getIDAttribute(),
assertEquals("Did not find TOC item at expected ordinal: " + ordinal, item.getIDAttribute(),
ID);
}
@ -324,7 +399,7 @@ public class OverlayHelpTreeTest {
private void storeDisplayAttribute(String s) {
// create a pattern to pull out the display string
Pattern p = Pattern.compile(".*display=\"(.*)\" toc_id.*");
Pattern p = Pattern.compile(".*toc_id=\"(.*)\".*");
Matcher matcher = p.matcher(s.trim());
if (!matcher.matches()) {
@ -347,8 +422,8 @@ public class OverlayHelpTreeTest {
Map<String, TOCItemDefinition> definitions = new HashMap<>();
void addExternal(TOCItemExternal item) {
String displayText = item.getIDAttribute();
externals.put(displayText, item);
String ID = item.getIDAttribute();
externals.put(ID, item);
}
void addDefinition(TOCItemDefinition item) {