Skip to content

[treeplayer] Don't mistake formulas for script files in TTree::Draw - #22774

Open
guitargeek wants to merge 1 commit into
root-project:masterfrom
guitargeek:ROOT-8000
Open

[treeplayer] Don't mistake formulas for script files in TTree::Draw#22774
guitargeek wants to merge 1 commit into
root-project:masterfrom
guitargeek:ROOT-8000

Conversation

@guitargeek

@guitargeek guitargeek commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

TTree::Draw can take the name of a C++ script file instead of a TTreeFormula expression for both the variable expression and the selection. To decide between the two, TTreePlayer::DrawSelect checked whether a matching file exists via TSystem::IsFileInIncludePath(), which strips a trailing parenthesized part as ACLiC-style macro arguments. A formula like "abs(Bs_TRUEID)" was thus reduced to "abs", and if an unrelated file with that name happened to exist in the current directory, the Draw call failed:

root [0] tree->Draw("Bs_MCORR", "abs(Bs_TRUEID)")
Error in <TTreePlayer::DrawSelect>: Drawing using a C++ macro
currently requires that both the expression and the selection are
files: "Bs_MCORR" is not a file

Two inconsistencies made the trap wider: the selection branch did not require a dot in the candidate string at all, and the varexp branch checked for a dot before stripping the arguments, so a decimal constant inside the parentheses (e.g. "abs(x+0.5)") satisfied it.

Factor the decision into a single helper used for both arguments, which requires the candidate - after stripping the ACLiC mode and arguments - to have an extension. This is not a new restriction: TTreeProxyGenerator::WriteProxy() already refuses a script without an extension (the function name is derived from the basename minus the extension, for the cut script too), so such a file could never be processed anyway. It now evaluates as a formula instead of failing with a misleading error.

Add regression tests that exercise the previously failing Draw calls in the presence of a shadowing file named "abs", and that check a real "abs.C" macro is still recognized as a script because of its extension.

Fixes https://its.cern.ch/jira/browse/ROOT-8000

🤖 Done with the help of AI

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

Test Results

    23 files      23 suites   3d 17h 14m 59s ⏱️
 3 877 tests  3 876 ✅ 0 💤 1 ❌
78 960 runs  78 959 ✅ 0 💤 1 ❌

For more details on these failures, see this check.

Results for commit f7edbf1.

♻️ This comment has been updated with latest results.

Comment thread tree/treeplayer/src/TTreePlayer.cxx Outdated
Comment thread tree/treeplayer/src/TTreePlayer.cxx Outdated
/// in the TTree::Draw documentation. Determine whether 'expression' names
/// such a script file.
///
/// Besides checking that the file exists, require that it has an extension:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

who does require?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reworded it to "we require", with "we" being ROOT.

Comment thread tree/treeplayer/src/TTreePlayer.cxx Outdated
return 0;
}
Error("DrawSelect",
"Drawing using a C++ file currently requires that both the expression and the selection are "

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

c++ file --> c++ macro?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea! That fits much better

@dpiparo dpiparo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the opinion of @pcanal could be useful, however, for me the PR is good to go.

@jblomer jblomer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I think @pcanal should take a look, too.

Comment thread tree/treeplayer/src/TTreePlayer.cxx Outdated
return false;

const TString candidate = expression;
if (candidate.Index("Alt$") >= 0 || candidate.Index("Entries$") >= 0 || candidate.Index("LocalEntries$") >= 0 ||

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be nicer to read to put all the special functions in a vector and then to loop over the vector.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The updated PR is doing that

// the leading part of a formula (e.g. a file named "abs") must not make
// TTree::Draw interpret the formula as the name of a C++ script called with
// arguments: only files with an extension qualify as scripts.
TEST(TTreeDrawRegressions, ExpressionNotShadowedByFile)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps also test that the intended behavior still works, i.e. when you leave, say, abs.C as a macro and try to run it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a test for that, tracing the minimal required code path that is required to test with because full ACLiC would take about 10 seconds.

`TTree::Draw` can take the name of a C++ script file instead of a
TTreeFormula expression for both the variable expression and the
selection. To decide between the two, `TTreePlayer::DrawSelect` checked
whether a matching file exists via `TSystem::IsFileInIncludePath()`,
which strips a trailing parenthesized part as ACLiC-style macro
arguments. A formula like `"abs(Bs_TRUEID)"` was thus reduced to
`"abs"`, and if an unrelated file with that name happened to exist in
the current directory, the Draw call failed:

```
root [0] tree->Draw("Bs_MCORR", "abs(Bs_TRUEID)")
Error in <TTreePlayer::DrawSelect>: Drawing using a C++ macro
currently requires that both the expression and the selection are
files: "Bs_MCORR" is not a file
```

Two inconsistencies made the trap wider: the selection branch did not
require a dot in the candidate string at all, and the varexp branch
checked for a dot before stripping the arguments, so a decimal
constant inside the parentheses (e.g. `"abs(x+0.5)"`) satisfied it.

Factor the decision into a single helper used for both arguments, which
requires the candidate - after stripping the ACLiC mode and arguments -
to have an extension. This is not a new restriction:
`TTreeProxyGenerator::WriteProxy()` already refuses a script without an
extension (the function name is derived from the basename minus the
extension, for the cut script too), so such a file could never be
processed anyway. It now evaluates as a formula instead of failing with
a misleading error.

Add regression tests that exercise the previously failing Draw calls in
the presence of a shadowing file named "abs", and that check a real
"abs.C" macro is still recognized as a script because of its extension.

Fixes https://its.cern.ch/jira/browse/ROOT-8000

🤖 Done with the help of AI
@guitargeek

Copy link
Copy Markdown
Contributor Author

@dpiparo and @jblomer, thanks for the review comments! I have addressed them.

@pcanal, you have something to add to the review by Danilo and Jakob?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants