Skip to content

Add PlaceholderList alert module#29

Draft
Copilot wants to merge 3 commits intomainfrom
copilot/add-placeholder-list-alert-module
Draft

Add PlaceholderList alert module#29
Copilot wants to merge 3 commits intomainfrom
copilot/add-placeholder-list-alert-module

Conversation

Copy link

Copilot AI commented Mar 19, 2026

Adds a generic PlaceholderList module for queries where a single alert element relates to a variable number of placeholder elements — collapsing N input rows per element into one alert row with a formatted list message.

New: src/qtil/list/PlaceholderList.qll

PlaceholderListSig<Element, Placeholder> — config signature:

  • predicate problems(Element e, string msg, Placeholder p, string pStr) — one row per placeholder
  • default int maxResults() — visible placeholder cap (default 5)
  • default string orderBy(Placeholder p) — sort key (default p.toString())

PlaceholderList<Element, Placeholder, Config> — produces a fixed-arity query predicate problems(...) with 5 placeholder pairs. Expands the single $@ in the input message:

Placeholders Message expansion
1 $@
2 $@ and $@
3 $@, $@, and $@
4 $@, $@, $@, and $@
5 $@, $@, $@, $@, and $@
>max $@, $@, $@, $@, and $@ and N more

Unused placeholder slots are padded with the first placeholder and "".

Example usage

module MyConfig implements PlaceholderListSig<Function, Variable> {
  predicate problems(Function f, string msg, Variable v, string vStr) {
    v = f.getAParameter() and
    msg = f.getName() + " has parameter $@." and
    vStr = v.getName()
  }
}

import PlaceholderList<Function, Variable, MyConfig>
// Produces: "foo has parameter $@, $@, $@, $@, and $@." with 5 variable links

Updated: src/qtil/Qtil.qll

Added import qtil.list.PlaceholderList to the main module.

Updated: README.md

Added a PlaceholderList description under the Lists section.


⌨️ Start Copilot coding agent tasks without leaving your editor — available in VS Code, Visual Studio, JetBrains IDEs and Eclipse.

Co-authored-by: MichaelRFairhurst <1627771+MichaelRFairhurst@users.noreply.github.com>
Agent-Logs-Url: https://github.com/advanced-security/codeql-qtil/sessions/2e31ff0c-05bb-412a-9027-d6546a1fff77
Copilot AI changed the title [WIP] Add placeholder list alert module Add PlaceholderList alert module Mar 19, 2026
Copilot AI requested a review from MichaelRFairhurst March 19, 2026 23:45
Copy link
Collaborator

@MichaelRFairhurst MichaelRFairhurst left a comment

Choose a reason for hiding this comment

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

Add a description as well to README.md, and delete .gitignore

* The maximum number of placeholders to show per alert. When there are more placeholders than
* this limit, the message will include "and N more" as plain text.
*
* The effective maximum is capped at 3, which is the number of placeholder pairs in the output
Copy link
Collaborator

Choose a reason for hiding this comment

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

Increase the maximum to 5

Copy link
Author

Choose a reason for hiding this comment

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

Done in cf18211 — default and cap are now 5; the output query predicate problems now has 5 placeholder pairs.

* Count the number of distinct placeholder values for a given `(element, message)` pair.
*/
private int countPlaceholders(Element e, string msg) {
Config::problems(e, msg, _, _) and
Copy link
Collaborator

Choose a reason for hiding this comment

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

this line is unnecessary

Copy link
Author

Choose a reason for hiding this comment

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

Removed from getNthPlaceholder (cf18211). Note: the equivalent line in countPlaceholders must stay — count aggregation doesn't provide binding for the outer string msg variable, unlike rank[n].

* ascending by `Config::orderBy`, with the placeholder's `toString()` as a secondary sort key.
*/
private Placeholder getNthPlaceholder(Element e, string msg, int n) {
Config::problems(e, msg, _, _) and
Copy link
Collaborator

Choose a reason for hiding this comment

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

this line is unnecessary

Copy link
Author

Choose a reason for hiding this comment

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

Removed in cf18211.

* number of additional placeholders not shown (0 or more).
*/
bindingset[showCount, moreCount]
private string expansion(int showCount, int moreCount) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

split this into placeholderExpansion (returning $@, $@ and $@, ...) and moreString (returning "" or " and n more".

expansion() can return placeholderExpansion(...) + moreString(...)

Copy link
Author

Choose a reason for hiding this comment

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

Done in cf18211 — split into placeholderExpansion(n), moreString(moreCount), and expansion(showCount, moreCount) = placeholderExpansion + moreString.

*/
private string expandedMsg(Element e, string origMsg) {
exists(int total, int maxR, int cappedMax, int sc, int mc |
total = countPlaceholders(e, origMsg) and
Copy link
Collaborator

Choose a reason for hiding this comment

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

reuse showCount here

Copy link
Author

Choose a reason for hiding this comment

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

Done in cf18211expandedMsg now calls showCount(e, origMsg) directly and uses it to compute mc.

@@ -0,0 +1,158 @@
import qtil.list.PlaceholderList
import qtil.testing.Qnit
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of a qnit test, have this be a basic query test.

You can have multiple test queries by defining different query predicates, e.g.

query predicate testMaxTwo = PlaceholderList<...>::problems/4;

And then the resulting set of query predicates will output the expected:

testMaxTwo
| my element has $@ | foo | "" | "" |
| my other element has $@ and $@ | foo | bar | "" |
| my last element has $@, $@, and 1 more | foo | bar | baz
testMaxThree
| my element has $@ | foo | "" | "" |
....

Copy link
Author

Choose a reason for hiding this comment

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

Done in cf18211 — replaced the Qnit-based test with two query predicate aliases (testDefault and testMaxTwo) and a matching .expected file showing the actual output rows.

…ests, README, remove .gitignore

Co-authored-by: MichaelRFairhurst <1627771+MichaelRFairhurst@users.noreply.github.com>
Agent-Logs-Url: https://github.com/advanced-security/codeql-qtil/sessions/a14c8c20-be9c-4b06-b4af-53039bacd071
Copy link
Author

Copilot AI commented Mar 20, 2026

Add a description as well to README.md, and delete .gitignore

Done in cf18211 — added a PlaceholderList section to the Lists part of README.md and deleted .gitignore.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add "placeholder list" alert module

2 participants