Sitelet https://web.archive.org/web/20260321093403/https://github.com/github/codeql/pull/6030
Skip to content

Add test-generator script + add generated models for Spring summary steps#6030

Merged
aschackmull merged 32 commits intogithub:mainfrom
smowton:smowton/admin/test-generator
Jul 14, 2021
Merged

Add test-generator script + add generated models for Spring summary steps#6030
aschackmull merged 32 commits intogithub:mainfrom
smowton:smowton/admin/test-generator

Conversation

@smowton
Copy link
Contributor

@smowton smowton commented Jun 7, 2021

This Python + QL script generates tests for flow summary specifications. It is invoked by providing a semicolon-separated value file giving the specifications to generate tests for + a Maven POM file that pulls sufficient dependencies to resolve the relevant methods, and emits a test CodeQL file, java file and (empty) expected file.

This PR also tests the generator by using it to add tests to @sauyon's draft PR, #5895

I imagine this will probably want to be split up. Subcomponents of the PR:

java/ql/src/semmle/code/java/dataflow/ExternalFlow.qll
java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImpl.qll
java/ql/src/semmle/code/java/dataflow/internal/FlowSummaryImplSpecific.qll

Exposing elements of the CSV parser so I can relate SSV rows to the functions modelled.

java/ql/src/utils/*

The test-generator script itself

java/ql/test/library-tests/frameworks/spring/*

The tests generated for #5895

java/ql/src/semmle/code/java/frameworks/spring/*

Sauyon's models, altered as proved necessary to generate tests for them

 java/ql/test/stubs/*

Stubs introduced to support the generated tests (these used java-autostub, not @joefarebrother's new stub generator)

}

private SummaryComponent interpretComponent(string c) {
SummaryComponent interpretComponent(string c) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the minor use of this predicate warrants making it public. The only added reference seems like something that could be written without it.


private class SummarizedCallableExternal extends SummarizedCallable {
SummarizedCallableExternal() { summaryElement(this, _, _, _) }
class SummarizedCallableExternal extends SummarizedCallable {
Copy link
Contributor

Choose a reason for hiding this comment

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

Making use of SummarizedCallableExternal is potentially problematic as that means we only generate tests for those rows that translate without problems. It there's a typo in a signature, or method name, or anything like that then we don't generate any test case, so we fail to catch the error.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think, for a csv row with a package name matching something we specify when running the generator, we'll want to be able to generate test cases for csv rows that are wrong in ways that cause the test case to not compile.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The invalid-row problem is addressed by the Python component, which introduces

query string getAFailedRow() {
  result = any(GenRow row | not exists(RowTestSnippet r | exists(r.getATestSnippetForRow(row))) | row)
}

This spots rows that didn't lead to any test generation, e.g. because a SummarizedCallableExternal wasn't matched, or a SummaryComponentStack parser didn't match.

Copy link
Contributor

Choose a reason for hiding this comment

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

Gotcha. This code snippet could move into the qll file.

@smowton
Copy link
Contributor Author

smowton commented Jun 11, 2021

To rephrase that a bit, I want to be able to:

  • Map a CSV row onto a Callable and two SummaryComponentStacks
  • Map a SummaryComponent back onto a fragment of CSV ("ArrayElement of", "Element of", "MapValue of" etc)

How would you be happy to expose those?

@aschackmull
Copy link
Contributor

Here's what I was thinking:

  • Identify a CSV row by its 9-tuple as given by summaryModel. If you need to wrap that as a single entity, use an IPA type and/or recalculate the string row by concatenating the columns.
  • For mapping a row to a callable, join with the already-exposed predicate interpretElement (the same join as in summaryElement in FlowSummaryImplSpecific.qll).
  • For mapping the input/output specs to SummaryComponentStacks, I'd introduce predicate interpretSpec(string spec, SummaryComponentStack stack) { interpretSpec(spec, 0, stack) } in FlowSummaryImpl.qll and expose that.

I think this keeps the API surface to a minimum and should cover:

Map a CSV row onto a Callable and two SummaryComponentStacks

For the input to the test generation, this can be any sort of filter on the existing rows (i.e. the 9-tuples). Using the complete row as a single string is fine and can be matched by string concatenation.

Map a SummaryComponent back onto a fragment of CSV ("ArrayElement of", "Element of", "MapValue of" etc)

Not sure what you need this for.

@smowton
Copy link
Contributor Author

smowton commented Jun 14, 2021

Map a SummaryComponent back onto a fragment of CSV ("ArrayElement of", "Element of", "MapValue of" etc)

Not sure what you need this for.

Creating the annotations that go in the test .ql file. For example, I know I want a function getMapValue that extracts a particular Content, and I'm currently using interpretComponent to map that back onto its string representation. For now I could get by with Content.toString() but that will surely fall over when we gain Field SummaryComponents.

@aschackmull
Copy link
Contributor

Creating the annotations that go in the test .ql file. For example, I know I want a function getMapValue that extracts a particular Content, and I'm currently using interpretComponent to map that back onto its string representation. For now I could get by with Content.toString() but that will surely fall over when we gain Field SummaryComponents.

Right, but using interpretComponent isn't going to cover all cases either (I'm thinking of lambdas), so new cases may need individual treatment anyway. (Field SummaryComponents technically already exists, but aren't accessible from csv).

@smowton smowton force-pushed the smowton/admin/test-generator branch from 72ba5b9 to 134f564 Compare June 22, 2021 08:16
@github-actions
Copy link
Contributor

⚠️ The head of this PR and the base branch were compared for differences in the framework coverage reports. The generated reports are available in the artifacts of this workflow run. The differences will be picked up by the nightly job after the PR gets merged. The following differences were found:

java

Generated file changes for java

  • Changes to framework-coverage-java.rst:
-    `Spring <https://spring.io/>`_,``org.springframework.*``,29,,41,,,,,14,,27
+    `Spring <https://spring.io/>`_,``org.springframework.*``,29,43,41,,,,,14,,27
-    Totals,,84,1622,181,13,6,6,,33,1,58
+    Totals,,84,1665,181,13,6,6,,33,1,58
  • Changes to framework-coverage-java.csv:
+ org.springframework.cache,,,12,,,,,,,,,,,,,,,12
+ org.springframework.data.repository,,,6,,,,,,,,,,,,,,,6
+ org.springframework.ui,,,25,,,,,,,,,,,,,,,25

@github-actions
Copy link
Contributor

⚠️ The head of this PR and the base branch were compared for differences in the framework coverage reports. The generated reports are available in the artifacts of this workflow run. The differences will be picked up by the nightly job after the PR gets merged. The following differences were found:

java

Generated file changes for java

  • Changes to framework-coverage-java.rst:
-    `Spring <https://spring.io/>`_,``org.springframework.*``,29,,41,,,,,14,,27
+    `Spring <https://spring.io/>`_,``org.springframework.*``,29,43,41,,,,,14,,27
-    Totals,,84,1622,181,13,6,6,,33,1,58
+    Totals,,84,1665,181,13,6,6,,33,1,58
  • Changes to framework-coverage-java.csv:
+ org.springframework.cache,,,12,,,,,,,,,,,,,,,12
+ org.springframework.data.repository,,,6,,,,,,,,,,,,,,,6
+ org.springframework.ui,,,25,,,,,,,,,,,,,,,25

@smowton
Copy link
Contributor Author

smowton commented Jun 22, 2021 •

@aschackmull I've reduced the churn in ExternalFlow.qll etc by implementing the mapping back from Content to an appropriate model string myself, and by exposing interpretSpec as suggested.

On exposing the row parser though, the alternative implementations seem worse:

  • Idea: specify what tests you want by giving a class spec (e.g. "test org.springframework.ui.*"). Problem is, now a mistaken CSV line org.spadework.ui looks like it's referring to some other package and is expected not to resolve to any Callable in this context. The mistake is likely to be silently ignored
  • Idea: specify what tests you want by a regex or wildcards that match against those strings s for which SummaryModelCsv.row(s) holds. This has a similar problem though: it's easy to typo in such a way that your row isn't selected.
  • Therefore I end up back at preferring to specify what tests I want to generate by specifying the exact SSV rows I want tests for. This way I can make a consistency check (does SummaryModelCsv.row(s) hold? Does interpretElement resolve anything? Do the two interpretSpec calls match?) and the chances of accidentally omitting tests are reduced. This does require that either I copy-paste the predicate summaryModel (with the obvious risk that we update the format and forget about the duplicate code that also needs updating), or I just expose and use it, which this version still does.

You mentioned above there was a problem relating to future use of an external predicate to supply source/sink/summary models, but surely even in that world we're likely to extract and parse rows from that external source, and so continue to have some appropriate isSummaryModelRow hook we can use?

@smowton smowton requested a review from a team as a code owner June 22, 2021 08:47
@github-actions github-actions bot added the C# label Jun 22, 2021
@github-actions
Copy link
Contributor

⚠️ The head of this PR and the base branch were compared for differences in the framework coverage reports. The generated reports are available in the artifacts of this workflow run. The differences will be picked up by the nightly job after the PR gets merged. The following differences were found:

java

Generated file changes for java

  • Changes to framework-coverage-java.rst:
-    `Spring <https://spring.io/>`_,``org.springframework.*``,29,,41,,,,,14,,27
+    `Spring <https://spring.io/>`_,``org.springframework.*``,29,43,41,,,,,14,,27
-    Totals,,84,1622,181,13,6,6,,33,1,58
+    Totals,,84,1665,181,13,6,6,,33,1,58
  • Changes to framework-coverage-java.csv:
+ org.springframework.cache,,,12,,,,,,,,,,,,,,,12
+ org.springframework.data.repository,,,6,,,,,,,,,,,,,,,6
+ org.springframework.ui,,,25,,,,,,,,,,,,,,,25

@smowton smowton force-pushed the smowton/admin/test-generator branch from 0b25f2c to 1c47076 Compare June 24, 2021 16:28
@github-actions
Copy link
Contributor

⚠️ The head of this PR and the base branch were compared for differences in the framework coverage reports. The generated reports are available in the artifacts of this workflow run. The differences will be picked up by the nightly job after the PR gets merged. The following differences were found:

java

Generated file changes for java

  • Changes to framework-coverage-java.rst:
-    `Spring <https://spring.io/>`_,``org.springframework.*``,29,,41,,,,,14,,27
+    `Spring <https://spring.io/>`_,``org.springframework.*``,29,43,41,,,,,14,,27
-    Totals,,84,1637,181,13,6,6,,33,1,58
+    Totals,,84,1680,181,13,6,6,,33,1,58
  • Changes to framework-coverage-java.csv:
+ org.springframework.cache,,,12,,,,,,,,,,,,,,,12
+ org.springframework.data.repository,,,6,,,,,,,,,,,,,,,6
+ org.springframework.ui,,,25,,,,,,,,,,,,,,,25

@github-actions
Copy link
Contributor

⚠️ The head of this PR and the base branch were compared for differences in the framework coverage reports. The generated reports are available in the artifacts of this workflow run. The differences will be picked up by the nightly job after the PR gets merged. The following differences were found:

java

Generated file changes for java

  • Changes to framework-coverage-java.rst:
-    `Spring <https://spring.io/>`_,``org.springframework.*``,29,,41,,,,,14,,27
+    `Spring <https://spring.io/>`_,``org.springframework.*``,29,43,41,,,,,14,,27
-    Totals,,84,1637,181,13,6,6,,33,1,58
+    Totals,,84,1680,181,13,6,6,,33,1,58
  • Changes to framework-coverage-java.csv:
+ org.springframework.cache,,,12,,,,,,,,,,,,,,,12
+ org.springframework.data.repository,,,6,,,,,,,,,,,,,,,6
+ org.springframework.ui,,,25,,,,,,,,,,,,,,,25

@github-actions
Copy link
Contributor

⚠️ The head of this PR and the base branch were compared for differences in the framework coverage reports. The generated reports are available in the artifacts of this workflow run. The differences will be picked up by the nightly job after the PR gets merged. The following differences were found:

java

Generated file changes for java

  • Changes to framework-coverage-java.rst:
-    `Spring <https://spring.io/>`_,``org.springframework.*``,29,,41,,,,,14,,27
+    `Spring <https://spring.io/>`_,``org.springframework.*``,29,43,41,,,,,14,,27
-    Totals,,84,1637,181,13,6,6,,33,1,58
+    Totals,,84,1680,181,13,6,6,,33,1,58
  • Changes to framework-coverage-java.csv:
+ org.springframework.cache,,,12,,,,,,,,,,,,,,,12
+ org.springframework.data.repository,,,6,,,,,,,,,,,,,,,6
+ org.springframework.ui,,,25,,,,,,,,,,,,,,,25

@github-actions
Copy link
Contributor

⚠️ The head of this PR and the base branch were compared for differences in the framework coverage reports. The generated reports are available in the artifacts of this workflow run. The differences will be picked up by the nightly job after the PR gets merged. The following differences were found:

java

Generated file changes for java

  • Changes to framework-coverage-java.rst:
-    `Spring <https://spring.io/>`_,``org.springframework.*``,29,,41,,,,,14,,27
+    `Spring <https://spring.io/>`_,``org.springframework.*``,29,43,41,,,,,14,,27
-    Totals,,84,1835,181,13,6,6,,33,1,58
+    Totals,,84,1878,181,13,6,6,,33,1,58
  • Changes to framework-coverage-java.csv:
+ org.springframework.cache,,,12,,,,,,,,,,,,,,,12
+ org.springframework.data.repository,,,6,,,,,,,,,,,,,,,6
+ org.springframework.ui,,,25,,,,,,,,,,,,,,,25

[
"org.springframework.data.repository;CrudRepository;true;findAll;;;MapValue of Argument[-1];Element of ReturnValue;value",
"org.springframework.data.repository;CrudRepository;true;findAllById;;;MapValue of Argument[-1];Element of ReturnValue;value",
// note for review; return value is an Option<T>; assuming Element is used.
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we have any models for that? Otherwise this will be a dead end.

@smowton smowton force-pushed the smowton/admin/test-generator branch from 550548e to bb5fefa Compare June 29, 2021 15:00
@smowton smowton added the no-change-note-required This PR does not need a change note label Jun 29, 2021
@smowton
Copy link
Contributor Author

smowton commented Jun 29, 2021

@aschackmull all comments applied, and demonstration tests dropped from this PR

(i = -1 or exists(callable.getParameter(i))) and
if baseInput = SummaryComponentStack::argument(i)
then result = "in"
else (
Copy link
Contributor

Choose a reason for hiding this comment

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

Superfluous parenthesis (parsing is unambiguous).

else (
if baseOutput = SummaryComponentStack::argument(i)
then result = "out"
else (
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

Comment on lines +298 to +300
kind = "value" and result = "// $hasValueFlow"
or
kind = "taint" and result = "// $hasTaintFlow"
Copy link
Contributor

Choose a reason for hiding this comment

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

Minor preference:

Suggested change
kind = "value" and result = "// $hasValueFlow"
or
kind = "taint" and result = "// $hasTaintFlow"
kind = "value" and result = "// $ hasValueFlow"
or
kind = "taint" and result = "// $ hasTaintFlow"

Comment on lines +353 to +354
not exists(SummaryComponentStack next | next.tail() = prev and next = input.drop(_)) and
result = baseInput
Copy link
Contributor

Choose a reason for hiding this comment

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

This disjunct does not bind prev.

Comment on lines +357 to +377
/**
* Returns a call to `source()` wrapped in `newWith` methods as needed according to `input`.
* For example, if the input specification is `ArrayElement of MapValue of Argument[0]`, this
* will return `newWithMapValue(newWithArrayElement(source()))`.
*
* This requires a slightly awkward walk, out from the element above the root (`Argument[0]` above)
* climbing up towards the outer `ArrayElement of...`. This is implemented by treating the stack as
* a circular list, walking it backwards and treating the root as a sentinel indicating we should
* emit `source()`.
*/
string getInput(SummaryComponentStack componentStack) {
componentStack = input.drop(_) and
(
if componentStack = baseInput
then result = "source()"
else
result =
"newWith" + contentToken(getContent(componentStack.head())) + "(" +
this.getInput(nextInput(componentStack)) + ")"
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

How about simplifying this as follows:

string getInput(SummaryComponentStack stack) {
  stack = input and result = "source()"
  or
  exists(SummaryComponentStack s |
    result = "newWith" + contentToken(getContent(s.head())) + "(" + this.getInput(s) + ")" and
    stack = s.tail()
  )
}

Then the desired result will be this.getInput(baseInput). Then you can also delete the confusing nextInput above.

@smowton
Copy link
Contributor Author

smowton commented Jul 13, 2021

@aschackmull all comments applied

* will return `newWithMapValue(newWithArrayElement(source()))`.
*/
string getInput(SummaryComponentStack stack) {
stack = input.drop(_) and
Copy link
Contributor

Choose a reason for hiding this comment

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

This conjunct is superfluous.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Dropped

@aschackmull aschackmull merged commit 11fc23b into github:main Jul 14, 2021
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.

3 participants