Skip to content

C++: Eliminate recursion from toString(). #3387

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
May 26, 2020
Merged

Conversation

geoffw0
Copy link
Contributor

@geoffw0 geoffw0 commented May 1, 2020

Removes recursion from toString(), resulting in moderately better performance when it's computed. The improvement comes from Element::ElementBase::toString_dispred#ff, which is still large and complex but is no longer recursive.

Some results:

Qt, SuspiciousAddWithSizeof.ql ~105s -> ~95s (90%)
wxWidgets, SuspiciousAddWithSizeof.ql 28s -> 24s (86%)
MongoDB, SuspiciousAddWithSizeof.ql ~29s -> 26s (90%)

As above but with #3352:

Qt, SuspiciousAddWithSizeof.ql ~86s -> ~74s (86%)

As a sequence (clearing cache only at the start):

mysql, NotInitialized.ql 232s -> 217s (94%)
mysql, AssignWhereCompareMeant.ql 17s -> 16s
mysql, SuspiciousAddWithSizeof.ql 3s -> 4s

And:

wireshark, select sum(Element e | | e.toString().length()) 129s -> 110s (85%)

Note that in the case of UsingDeclarationEntry I have traded detail in the result for better performance. There's a lot of scope for more changes like this if it's something we're comfortable doing.

@geoffw0 geoffw0 added the C++ label May 1, 2020
@jbj
Copy link
Contributor

jbj commented May 1, 2020

I'll just @-mention @github/codeql-c-analysis since the CODEOWNERS file was broken when this PR was opened.

@geoffw0
Copy link
Contributor Author

geoffw0 commented May 1, 2020

It looks like the check is failing because of the identical files check:

16:31:43  ERROR: ql/config/sync-files.py:0 -     ./cpp/ql/src/semmle/code/cpp/XML.qll
16:31:43  ERROR: ql/config/sync-files.py:0 -     ./csharp/ql/src/semmle/code/csharp/XML.qll
16:31:43  ERROR: ql/config/sync-files.py:0 -     ./java/ql/src/semmle/code/xml/XML.qll
16:31:43  ERROR: ql/config/sync-files.py:0 -     ./javascript/ql/src/semmle/javascript/XML.qll
16:31:43  ERROR: ql/config/sync-files.py:0 -     ./python/ql/src/semmle/python/xml/XML.qll

I'll fix this up, but it would be good to get feedback on those changes first.

@jbj
Copy link
Contributor

jbj commented May 2, 2020

Thanks for putting in the effort to eliminate this recursion. I had no idea how many changes it would require, but it looks like it's fairly minimal.

Will all the changes here preserve the exact same output we had before? It looks like UsingDeclarationEntry doesn't, but I think that's fixable.

@geoffw0 geoffw0 requested a review from a team as a code owner May 4, 2020 17:02
@geoffw0
Copy link
Contributor Author

geoffw0 commented May 4, 2020

I had no idea how many changes it would require, but it looks like it's fairly minimal.

Yes, though I should mention that I've only looked at what happens when you import cpp, if you include other library files (such as PrintIR.qll I think) it's possible there may once again be recursion. I'm not sure how such imports interact with cacheing either.

Will all the changes here preserve the exact same output we had before? It looks like UsingDeclarationEntry doesn't, but I think that's fixable.

With the exception of UsingDeclarationEntry that was the intent, and following the changes you suggested, all output should be the same now (it turned out several tests were failing due to UsingDeclarationEntry as well, all of which are now good - which is reassuring - naturally we have pretty good coverage of toString()).

@geoffw0 geoffw0 requested review from a team as code owners May 5, 2020 08:28
@geoffw0
Copy link
Contributor Author

geoffw0 commented May 5, 2020

Fixed the "sync identical files" issue. That means the part of this change in XML.qll is duplicated across all languages - though without any other changes I don't expect there to be much benefit.

Copy link
Contributor

@tausbn tausbn left a comment

Choose a reason for hiding this comment

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

I don't see any issue with the XML changes as far as Python is concerned.

Copy link
Contributor

@asgerf asgerf left a comment

Choose a reason for hiding this comment

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

It's causing some slightly concerning tuple count differences for JS (XSS query on juliusjs). I understand it's not exactly the fault of this PR, but rather a consequence of the unfortunate fact that XMLFile overrides File.toString.

It seems to me that XMLFile should just not be a subclass of File at all - logically it makes no sense that File.toString should have a special value for XML files, it's just an artifact of multiple inheritance. Is it feasible to remove the File base class instead?

With that said if it's important for C++ then LGTM. I'll deal with the fallout if it turns out to be an issue for JS.

@jbj
Copy link
Contributor

jbj commented May 12, 2020

I take back what I said in the meeting yesterday about this PR not having much benefit unless every last bit of recursion is eliminated.

I just looked at the evaluation logs for a large customer snapshot and found this in iteration 2:

[2020-05-12 12:38:57] (715s) Starting to evaluate predicate Element::ElementBase::toString_dispred#ff#cur_delta/2[2]@6df78e (iteration 2)
[2020-05-12 12:40:51] (829s) Tuple counts for Element::ElementBase::toString_dispred#ff#cur_delta:
                      0         ~0%      {2} r1 = JOIN Element::ElementBase::toString_dispred#ff#join_rhs AS L WITH Element::ElementBase::toString_dispred#ff#prev_delta AS R ON FIRST 1 OUTPUT L.<1>, R.<1>
                      0         ~0%      {2} r2 = JOIN namespace_decls_10#join_rhs AS L WITH Element::ElementBase::toString_dispred#ff#prev_delta AS R ON FIRST 1 OUTPUT L.<1>, R.<1>
                      0         ~0%      {2} r3 = r1 \/ r2
                      205059986 ~0%      {2} r4 = SCAN Element::ElementBase::toString_dispred#ff#prev_delta AS I OUTPUT I.<0>, ("using " ++ I.<1>)
                      0         ~0%      {2} r5 = JOIN r4 WITH Namespace::UsingDeclarationEntry::getDeclaration_dispred#ff_10#join_rhs AS R ON FIRST 1 OUTPUT R.<1>, r4.<1>
                      0         ~0%      {2} r6 = r3 \/ r5
                      0         ~0%      {2} r7 = JOIN Namespace::UsingDirectiveEntry::getNamespace_dispred#ff_10#join_rhs AS L WITH Element::ElementBase::toString_dispred#ff#prev_delta AS R ON FIRST 1 OUTPUT L.<1>, ("using namespace " ++ R.<1>)
                      0         ~0%      {2} r8 = r6 \/ r7
                      0         ~0%      {2} r9 = r8 AND NOT Element::ElementBase::toString_dispred#ff#prev AS R(r8.<0>, r8.<1>)
                                         return r9

Notice how it concatenates "using " with all results from the previous iteration before filtering those results to contain only UsingDeclarationEntry (0 rows). That means it constructs 205M strings that are immediately discarded.

The RA for iteration 1 seem fine in contrast, with the concatenation always happening before the filtering.

@geoffw0
Copy link
Contributor Author

geoffw0 commented May 12, 2020

It's causing some slightly concerning tuple count differences for JS (XSS query on juliusjs).

Thanks for spotting this. As far as I can tell from your report this isn't affecting results, just the number of rows in some intermediate computations - with potential performance impact. Do we know whether performance changes on this snapshot? Is the concern the extra DataFlow::DataFlow::Node::getAstNode_dispred#ff rows or does something else look fishy?

Unfortunately I'm not very well set up to test/debug this - I'm not set up for JS dev and haven't been able to find a single CPP snapshot that actually has XML elements in it.

@jbj
Copy link
Contributor

jbj commented May 20, 2020

The XML.qll problem may go away when #3520 (comment) happens.

@jbj
Copy link
Contributor

jbj commented May 25, 2020

@asgerf

It's causing some slightly concerning tuple count differences for JS (XSS query on juliusjs). I understand it's not exactly the fault of this PR, but rather a consequence of the unfortunate fact that XMLFile overrides File.toString.

There's clearly something I don't understand here. AFAICT the toString predicates on File and XMLParent both return the absolute path, so why does anything change with this PR? Would it work to implement XMLFile.toString as result = this.getAbsolutePath() instead? That would avoid going through the potentially ambiguous getName member.

It seems to me that XMLFile should just not be a subclass of File at all - logically it makes no sense that File.toString should have a special value for XML files, it's just an artifact of multiple inheritance. Is it feasible to remove the File base class instead?

There's no telling whether external users depend on this inheritance relationship, but I can easily imagine that they do. If we ensure that all ambiguous overrides on XMLFile are resolved in favour of File instead of XMLParent, does that solve the problem?

@asgerf
Copy link
Contributor

asgerf commented May 26, 2020

(@geoffw0 sorry I missed your comment from 14d ago.)

There's clearly something I don't understand here. AFAICT the toString predicates on File and XMLParent both return the absolute path, so why does anything change with this PR? [...]

I agree the results should be the same, it's just a question of how well the optimizer can handle the code.

In any case, I'm not able to reproduce the results anymore; I must have done something wrong in my initial test. Instead I get a large reduction in tuple counts now. Sorry about that. Please just move ahead with the PR.

Copy link
Contributor

@jbj jbj left a comment

Choose a reason for hiding this comment

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

I've re-triggered the tests and will merge if they pass.

@jbj jbj merged commit 5deeda0 into github:master May 26, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants