20
$\begingroup$

Bug introduced in 7.0.1 or earlier and fixed in 10.3.0


I believe I have come across a rather curious bug in TreeForm:

{"foo", "bar", "Plot"} // TreeForm

enter image description here

Note that the string "Plot" is formatted as True.

Unless I have accidentally changed something in my configuration this must be a bug.

I am using version 10.0.0 under Windows.


It turns out that the problem is far larger and more widespread than the initial manifestation indicated. Please see my self-answer for an analysis of this systemic problem.

$\endgroup$
  • 1
    $\begingroup$ This is the same result in V9.0.1 Windows 8.1 $\endgroup$ – RunnyKine Jul 24 '14 at 7:11
  • $\begingroup$ @RunnyKine Thanks for the report. I'm surprised this hasn't been noticed before. (Assuming this post is not a duplicate.) $\endgroup$ – Mr.Wizard Jul 24 '14 at 7:13
  • $\begingroup$ This almost feels like an easter egg. :) Because in my impression, the internal process of strings in TreeForm should be a one-liner. No room to make mistake. $\endgroup$ – xzhu Jul 24 '14 at 7:16
  • $\begingroup$ @trVoldemort Should be a one-liner? Get ready for a surprise. Try: TracePrint[ToBoxes @ TreeForm @ "Plot", TraceInternal -> True] (Be ready to kill the kernel.) $\endgroup$ – Mr.Wizard Jul 24 '14 at 7:23
  • $\begingroup$ Confirmed on Linux, for v9 and v10. $\endgroup$ – Yi Wang Jul 24 '14 at 18:07
12
$\begingroup$

Although this is hardly a debilitating bug I wondered what else might be affected so I decided to trace this further. I found that the bug affects TreeForm by way of TreePlot. Here is a reduced example of the call that originates in the exhibit above:

TreePlot[{1 -> 2, 1 -> 3, 1 -> 4}, Top, 1, 
 "VertexNames" -> {List, HoldForm["foo"], HoldForm["bar"], HoldForm["Plot"]}, 
 VertexLabeling -> All
]

enter image description here

This ends up calling Network`GraphPlotDump`Private`TreePlot2 which uses Network`GraphPlotDump`Private`GraphPlotInternalRuleList23D which ultimately becomes:

Network`GraphPlotDump`Private`GraphPlot23DInternalLabels[TreePlot, 
 SparseArray[Automatic, {4, 4}, 0, {1, {{0, 3, 3, 3, 3}, {{2}, {3}, {4}}}, {1, 1, 1}}],
 2, "Plot", "RuleList", {1, 2, 3, 4},
 Method -> {"LayeredDrawing", "Root" -> 1, "LayerSizeFunction" -> (1 &), "Rotation" -> 0},
 "VertexNames" -> {List, "foo", "bar", "Plot"}, VertexLabeling -> All,
 "EdgeLabels" -> None, "VertexTooltips" -> None, "EdgeTooltips" -> None]

And spelunking the trace of this command eventually leads us to:

{List, "foo", "bar", "Plot"} /. {"VertexTooltips" -> Automatic, 
  "EdgeTooltips" -> Automatic, "EdgeLabels" -> Automatic, "VertexNames" -> Automatic, 
  "VertexSizes" -> Automatic, "VertexColor" -> Automatic, "EdgeColor" -> Automatic, 
  "VertexFrameBackground" -> Automatic, "VertexFrameStyle" -> Automatic, 
  "VertexFrameMargins" -> Automatic, "VertexTextStyle" -> True, "Plot" -> True}

So it seems I have finally found the origin of the problem. Let's test it:

{"VertexTooltips", "EdgeTooltips", "EdgeLabels", "VertexNames", "VertexSizes", 
  "VertexColor", "EdgeColor", "VertexFrameBackground", "VertexFrameStyle", 
  "VertexFrameMargins", "VertexTextStyle", "Plot"} // TreeForm

enter image description here

Those ALL are incorrectly replaced.

Viewing the definition with:

Needs["GeneralUtilities`"]

PrintDefinitions @ Network`GraphPlotDump`Private`GraphPlot23DInternalLabels

we find a whole series of operations like:

vnames =             (* contexts omitted for clarity *)
 "VertexNames" /. 
   processOptionNames[Flatten[{opts}]] /. 
     processOptionNames[hiddenOptions[caller]]

(processOptionNames is Network`GraphPlotDump`Private`processOptionNames and it converts Option names into strings.)

Can you see the problem? The first ReplaceAll finds the value of "VertexNames" in explicit options, if it exists, but then the second ReplaceAll operates on that value rather than "VertexNames"! This is careless programming and it should never have lasted all this time in production code.

Since the problem is not limited to vertex names we can demonstrate other peculiar bugs such as this:

ticks = {{0.8, "Prelude"}, {0.7, "Epilog"},
         {0.5, "Axes"}, {0.4, "Ticks"},
         {0.2, "Foreground"}, {0.1, "Background"}};

TreePlot[{1 -> 2, 2 -> 3}, Frame -> True, FrameTicks -> {{True, ticks}, {False, False}}]

enter image description here

GraphPlot is also affected:

GraphPlot[{1 -> 2, 2 -> 1, 3 -> 1, 3 -> 2, 4 -> 1, 4 -> 2, 4 -> 4},
 Frame -> True, FrameTicks -> {{True, ticks}, {False, False}}]

enter image description here

A double application of ReplaceAll should never be used to extract option values. Yet disturbingly it seems that is has been used repeatedly.

Fortunately Graph appears unaffected, but one must wonder what other internal functions might also be affected?

| improve this answer | |
$\endgroup$
  • $\begingroup$ May also need examination: System`GeoPlotsDump`iGeoRegionValuePlot, Graphics`ArrayPlotDump`Private`MatrixPlotInternal $\endgroup$ – Mr.Wizard Jul 27 '15 at 17:30
  • $\begingroup$ Quick fix: treeForm[args__] := TreeForm @@ ({args} /. s_String :> StringJoin[FromCharacterCode[62304], s]); graphPlot[args__] := GraphPlot @@ ({args} /. s_String :> StringJoin[FromCharacterCode[62304], s]); $\endgroup$ – Rolf Mertig Jul 27 '15 at 21:57
  • 2
    $\begingroup$ Looks like this code was written before modern option processing functions like OptionValue were implemented (V6). We have fixed some of the most serious cases identified here. $\endgroup$ – Stefan R Jul 29 '15 at 19:34
  • 2
    $\begingroup$ @Stefan I'm sure that even before v6 other methods were standard. I found a number of cases that still do not use OptionValue but simply join the lists, i.e. "OptionName" /. Join[ Flatten[{opts}], hiddenOptions[caller] ]. I think so long as each element in Join is assured of being a List and not $Failed or whatever this should be robust, right? $\endgroup$ – Mr.Wizard Jul 29 '15 at 21:04
  • 4
    $\begingroup$ Sorry, my comment was vague. It is certainly possible to have robust option processing code without the V6+ features (e.g. I think your example should work fine). The buggy GraphPlot code was always bad. I just meant that it's now much much easier to do these things :-) $\endgroup$ – Stefan R Jul 29 '15 at 22:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.