From 732e2c9f39228ffffb56a04883dc7efaf6fee5c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 08:09:17 +0000 Subject: [PATCH 01/60] Bump nlohmann_json from 3.11.3 to 3.12.0.bcr.1 Bumps [nlohmann_json](https://github.com/nlohmann/json) from 3.11.3 to 3.12.0.bcr.1. - [Release notes](https://github.com/nlohmann/json/releases) - [Changelog](https://github.com/nlohmann/json/blob/develop/ChangeLog.md) - [Commits](https://github.com/nlohmann/json/commits) --- updated-dependencies: - dependency-name: nlohmann_json dependency-version: 3.12.0.bcr.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- MODULE.bazel | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MODULE.bazel b/MODULE.bazel index 16b4a4691f8a..6d93889d4a88 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -24,7 +24,7 @@ bazel_dep(name = "rules_python", version = "1.9.0") bazel_dep(name = "rules_shell", version = "0.7.1") bazel_dep(name = "bazel_skylib", version = "1.9.0") bazel_dep(name = "abseil-cpp", version = "20260107.1", repo_name = "absl") -bazel_dep(name = "nlohmann_json", version = "3.11.3", repo_name = "json") +bazel_dep(name = "nlohmann_json", version = "3.12.0.bcr.1", repo_name = "json") bazel_dep(name = "fmt", version = "12.1.0-codeql.1") bazel_dep(name = "rules_kotlin", version = "2.2.2-codeql.1") bazel_dep(name = "gazelle", version = "0.50.0") From 5c2e5bfe8eee8a8e573b5fef1d8d6cbf46c9c4d9 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sun, 5 Jul 2026 17:20:52 +0100 Subject: [PATCH 02/60] Convert qlref tests to inline expectations --- .../CWE-094-dataURL/CodeInjection.qlref | 3 +- .../Security/CWE-094-dataURL/test.js | 12 +++--- .../EnvValueAndKeyInjection.qlref | 3 +- .../CWE-099/EnvValueAndKeyInjection/test.js | 6 +-- .../EnvValueInjection/EnvValueInjection.qlref | 3 +- .../CWE-099/EnvValueInjection/test.js | 8 ++-- .../CWE-347/localsource/JsonWebToken.js | 10 ++--- ...odeJwtWithoutVerificationLocalSource.qlref | 3 +- .../Security/CWE-347/localsource/jose.js | 4 +- .../Security/CWE-347/localsource/jwtDecode.js | 4 +- .../Security/CWE-347/localsource/jwtSimple.js | 4 +- .../CWE-347/remotesource/JsonWebToken.js | 10 ++--- .../decodeJwtWithoutVerification.qlref | 3 +- .../Security/CWE-347/remotesource/jose.js | 4 +- .../CWE-347/remotesource/jwtDecode.js | 4 +- .../CWE-347/remotesource/jwtSimple.js | 4 +- .../experimental/Security/CWE-918/SSRF.qlref | 3 +- .../SsrfIpv6TransitionIncompleteGuard.qlref | 3 +- .../bad-private-ip-pkg.js | 2 +- .../bad-rfc1918-regex.js | 2 +- .../Security/CWE-918/check-domain.js | 10 ++--- .../Security/CWE-918/check-middleware.js | 2 +- .../Security/CWE-918/check-path.js | 10 ++--- .../Security/CWE-918/check-regex.js | 12 +++--- .../Security/CWE-918/check-validator.js | 14 +++---- .../MultipleArgumentsToSetConstructor.qlref | 3 +- .../MultipleArgumentsToSetConstructorBad.js | 2 +- .../MultipleArgumentsToSetConstructor/tst.js | 2 +- .../UnpromotedRouteHandlerCandidate.qlref | 3 +- .../UnpromotedRouteSetupCandidate.qlref | 3 +- .../frameworks/HTTP-heuristics/src/hapi.js | 2 +- .../frameworks/HTTP-heuristics/src/nodejs.js | 6 +-- .../HTTP-heuristics/src/route-objects.js | 14 +++---- .../frameworks/HTTP-heuristics/src/tst.js | 42 +++++++++---------- .../frameworks/Templating/CodeInjection.qlref | 3 +- .../frameworks/Templating/app.js | 26 ++++++------ .../Templating/views/angularjs_include.ejs | 4 +- .../Templating/views/angularjs_sinks.ejs | 4 +- .../frameworks/Templating/views/ejs_sinks.ejs | 6 +-- .../frameworks/Templating/views/hbs_sinks.hbs | 6 +-- .../frameworks/Templating/views/njk_sinks.njk | 10 ++--- 41 files changed, 145 insertions(+), 134 deletions(-) diff --git a/javascript/ql/test/experimental/Security/CWE-094-dataURL/CodeInjection.qlref b/javascript/ql/test/experimental/Security/CWE-094-dataURL/CodeInjection.qlref index 3caf7ab7b43b..9ed18359d203 100644 --- a/javascript/ql/test/experimental/Security/CWE-094-dataURL/CodeInjection.qlref +++ b/javascript/ql/test/experimental/Security/CWE-094-dataURL/CodeInjection.qlref @@ -1 +1,2 @@ -experimental/Security/CWE-094-dataURL/CodeInjection.ql \ No newline at end of file +query: experimental/Security/CWE-094-dataURL/CodeInjection.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/experimental/Security/CWE-094-dataURL/test.js b/javascript/ql/test/experimental/Security/CWE-094-dataURL/test.js index a5a2e76fa3c8..b8599b5687cc 100644 --- a/javascript/ql/test/experimental/Security/CWE-094-dataURL/test.js +++ b/javascript/ql/test/experimental/Security/CWE-094-dataURL/test.js @@ -2,21 +2,21 @@ const { Worker } = require('node:worker_threads'); var app = require('express')(); app.post('/path', async function (req, res) { - const payload = req.query.queryParameter // like: payload = 'data:text/javascript,console.log("hello!");//' + const payload = req.query.queryParameter // $ Source // like: payload = 'data:text/javascript,console.log("hello!");//' let payloadURL = new URL(payload + sth) // NOT OK - new Worker(payloadURL); + new Worker(payloadURL); // $ Alert payloadURL = new URL(payload + sth) // NOT OK - new Worker(payloadURL); + new Worker(payloadURL); // $ Alert payloadURL = new URL(sth + payload) // OK new Worker(payloadURL); }); app.post('/path2', async function (req, res) { - const payload = req.query.queryParameter // like: payload = 'data:text/javascript,console.log("hello!");//' - await import(payload) // NOT OK - await import(payload + sth) // NOT OK + const payload = req.query.queryParameter // $ Source // like: payload = 'data:text/javascript,console.log("hello!");//' + await import(payload) // $ Alert // NOT OK + await import(payload + sth) // $ Alert // NOT OK await import(sth + payload) // OK }); diff --git a/javascript/ql/test/experimental/Security/CWE-099/EnvValueAndKeyInjection/EnvValueAndKeyInjection.qlref b/javascript/ql/test/experimental/Security/CWE-099/EnvValueAndKeyInjection/EnvValueAndKeyInjection.qlref index fde9a286e5a8..dbd1332e35ae 100644 --- a/javascript/ql/test/experimental/Security/CWE-099/EnvValueAndKeyInjection/EnvValueAndKeyInjection.qlref +++ b/javascript/ql/test/experimental/Security/CWE-099/EnvValueAndKeyInjection/EnvValueAndKeyInjection.qlref @@ -1 +1,2 @@ -experimental/Security/CWE-099/EnvValueAndKeyInjection.ql \ No newline at end of file +query: experimental/Security/CWE-099/EnvValueAndKeyInjection.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/experimental/Security/CWE-099/EnvValueAndKeyInjection/test.js b/javascript/ql/test/experimental/Security/CWE-099/EnvValueAndKeyInjection/test.js index a12377c9cec9..87c151853e79 100644 --- a/javascript/ql/test/experimental/Security/CWE-099/EnvValueAndKeyInjection/test.js +++ b/javascript/ql/test/experimental/Security/CWE-099/EnvValueAndKeyInjection/test.js @@ -2,9 +2,9 @@ const http = require('node:http'); http.createServer((req, res) => { - const { EnvValue, EnvKey } = req.body; - process.env[EnvKey] = EnvValue; // NOT OK - process.env[EnvKey] = EnvValue; // NOT OK + const { EnvValue, EnvKey } = req.body; // $ Source + process.env[EnvKey] = EnvValue; // $ Alert // NOT OK + process.env[EnvKey] = EnvValue; // $ Alert // NOT OK res.end('env has been injected!'); }); diff --git a/javascript/ql/test/experimental/Security/CWE-099/EnvValueInjection/EnvValueInjection.qlref b/javascript/ql/test/experimental/Security/CWE-099/EnvValueInjection/EnvValueInjection.qlref index e03328beda4f..9fc1b79b8107 100644 --- a/javascript/ql/test/experimental/Security/CWE-099/EnvValueInjection/EnvValueInjection.qlref +++ b/javascript/ql/test/experimental/Security/CWE-099/EnvValueInjection/EnvValueInjection.qlref @@ -1 +1,2 @@ -experimental/Security/CWE-099/EnvValueInjection.ql \ No newline at end of file +query: experimental/Security/CWE-099/EnvValueInjection.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/experimental/Security/CWE-099/EnvValueInjection/test.js b/javascript/ql/test/experimental/Security/CWE-099/EnvValueInjection/test.js index cb28f01b88b0..e95ef9cad772 100644 --- a/javascript/ql/test/experimental/Security/CWE-099/EnvValueInjection/test.js +++ b/javascript/ql/test/experimental/Security/CWE-099/EnvValueInjection/test.js @@ -1,10 +1,10 @@ const http = require('node:http'); http.createServer((req, res) => { - const { EnvValue } = req.body; - process.env["A_Critical_Env"] = EnvValue; // NOT OK - process.env[AKey] = EnvValue; // NOT OK - process.env.AKey = EnvValue; // NOT OK + const { EnvValue } = req.body; // $ Source + process.env["A_Critical_Env"] = EnvValue; // $ Alert // NOT OK + process.env[AKey] = EnvValue; // $ Alert // NOT OK + process.env.AKey = EnvValue; // $ Alert // NOT OK res.end('env has been injected!'); }); diff --git a/javascript/ql/test/experimental/Security/CWE-347/localsource/JsonWebToken.js b/javascript/ql/test/experimental/Security/CWE-347/localsource/JsonWebToken.js index 022b0bda11f4..21c5b00e4feb 100644 --- a/javascript/ql/test/experimental/Security/CWE-347/localsource/JsonWebToken.js +++ b/javascript/ql/test/experimental/Security/CWE-347/localsource/JsonWebToken.js @@ -10,18 +10,18 @@ function aJWT() { } (function () { - const UserToken = aJwt() + const UserToken = aJwt() // $ Alert // BAD: no signature verification - jwtJsonwebtoken.decode(UserToken) // NOT OK + jwtJsonwebtoken.decode(UserToken) // $ Sink // NOT OK })(); (function () { - const UserToken = aJwt() + const UserToken = aJwt() // $ Alert // BAD: no signature verification - jwtJsonwebtoken.decode(UserToken) // NOT OK - jwtJsonwebtoken.verify(UserToken, getSecret(), { algorithms: ["HS256", "none"] }) // NOT OK + jwtJsonwebtoken.decode(UserToken) // $ Sink // NOT OK + jwtJsonwebtoken.verify(UserToken, getSecret(), { algorithms: ["HS256", "none"] }) // $ Sink // NOT OK })(); (function () { diff --git a/javascript/ql/test/experimental/Security/CWE-347/localsource/decodeJwtWithoutVerificationLocalSource.qlref b/javascript/ql/test/experimental/Security/CWE-347/localsource/decodeJwtWithoutVerificationLocalSource.qlref index ee8effa049c5..36743e927596 100644 --- a/javascript/ql/test/experimental/Security/CWE-347/localsource/decodeJwtWithoutVerificationLocalSource.qlref +++ b/javascript/ql/test/experimental/Security/CWE-347/localsource/decodeJwtWithoutVerificationLocalSource.qlref @@ -1 +1,2 @@ -experimental/Security/CWE-347/decodeJwtWithoutVerificationLocalSource.ql \ No newline at end of file +query: experimental/Security/CWE-347/decodeJwtWithoutVerificationLocalSource.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/experimental/Security/CWE-347/localsource/jose.js b/javascript/ql/test/experimental/Security/CWE-347/localsource/jose.js index 625618e194dc..c1e8597147d1 100644 --- a/javascript/ql/test/experimental/Security/CWE-347/localsource/jose.js +++ b/javascript/ql/test/experimental/Security/CWE-347/localsource/jose.js @@ -9,10 +9,10 @@ function aJWT() { } (function () { - const UserToken = aJwt() + const UserToken = aJwt() // $ Alert // no signature verification - jose.decodeJwt(UserToken) // NOT OK + jose.decodeJwt(UserToken) // $ Sink // NOT OK })(); (async function () { diff --git a/javascript/ql/test/experimental/Security/CWE-347/localsource/jwtDecode.js b/javascript/ql/test/experimental/Security/CWE-347/localsource/jwtDecode.js index f3d4a40314c7..21eb58cb40d5 100644 --- a/javascript/ql/test/experimental/Security/CWE-347/localsource/jwtDecode.js +++ b/javascript/ql/test/experimental/Security/CWE-347/localsource/jwtDecode.js @@ -10,9 +10,9 @@ function aJWT() { } (function () { - const UserToken = aJwt() + const UserToken = aJwt() // $ Alert // jwt-decode // no signature verification - jwt_decode(UserToken) // NOT OK + jwt_decode(UserToken) // $ Sink // NOT OK })(); \ No newline at end of file diff --git a/javascript/ql/test/experimental/Security/CWE-347/localsource/jwtSimple.js b/javascript/ql/test/experimental/Security/CWE-347/localsource/jwtSimple.js index 73b79d86d758..320d382c76c6 100644 --- a/javascript/ql/test/experimental/Security/CWE-347/localsource/jwtSimple.js +++ b/javascript/ql/test/experimental/Security/CWE-347/localsource/jwtSimple.js @@ -10,10 +10,10 @@ function aJWT() { } (function () { - const UserToken = aJwt() + const UserToken = aJwt() // $ Alert // BAD: no signature verification - jwt_simple.decode(UserToken, getSecret(), true); // NOT OK + jwt_simple.decode(UserToken, getSecret(), true); // $ Sink // NOT OK })(); (function () { diff --git a/javascript/ql/test/experimental/Security/CWE-347/remotesource/JsonWebToken.js b/javascript/ql/test/experimental/Security/CWE-347/remotesource/JsonWebToken.js index 0e39e95b632d..d1199a226cf1 100644 --- a/javascript/ql/test/experimental/Security/CWE-347/remotesource/JsonWebToken.js +++ b/javascript/ql/test/experimental/Security/CWE-347/remotesource/JsonWebToken.js @@ -7,18 +7,18 @@ function getSecret() { return "A Safe generated random key" } app.get('/jwtJsonwebtoken1', (req, res) => { - const UserToken = req.headers.authorization; + const UserToken = req.headers.authorization; // $ Alert // BAD: no signature verification - jwtJsonwebtoken.decode(UserToken) // NOT OK + jwtJsonwebtoken.decode(UserToken) // $ Sink // NOT OK }) app.get('/jwtJsonwebtoken2', (req, res) => { - const UserToken = req.headers.authorization; + const UserToken = req.headers.authorization; // $ Alert // BAD: no signature verification - jwtJsonwebtoken.decode(UserToken) // NOT OK - jwtJsonwebtoken.verify(UserToken, getSecret(), { algorithms: ["HS256", "none"] }) // NOT OK + jwtJsonwebtoken.decode(UserToken) // $ Sink // NOT OK + jwtJsonwebtoken.verify(UserToken, getSecret(), { algorithms: ["HS256", "none"] }) // $ Sink // NOT OK }) app.get('/jwtJsonwebtoken3', (req, res) => { diff --git a/javascript/ql/test/experimental/Security/CWE-347/remotesource/decodeJwtWithoutVerification.qlref b/javascript/ql/test/experimental/Security/CWE-347/remotesource/decodeJwtWithoutVerification.qlref index 9e7ea468ee79..d37a36dbcca3 100644 --- a/javascript/ql/test/experimental/Security/CWE-347/remotesource/decodeJwtWithoutVerification.qlref +++ b/javascript/ql/test/experimental/Security/CWE-347/remotesource/decodeJwtWithoutVerification.qlref @@ -1 +1,2 @@ -experimental/Security/CWE-347/decodeJwtWithoutVerification.ql \ No newline at end of file +query: experimental/Security/CWE-347/decodeJwtWithoutVerification.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/experimental/Security/CWE-347/remotesource/jose.js b/javascript/ql/test/experimental/Security/CWE-347/remotesource/jose.js index 30c57650e336..28b7ca95f7e9 100644 --- a/javascript/ql/test/experimental/Security/CWE-347/remotesource/jose.js +++ b/javascript/ql/test/experimental/Security/CWE-347/remotesource/jose.js @@ -8,9 +8,9 @@ function getSecret() { } app.get('/jose1', (req, res) => { - const UserToken = req.headers.authorization; + const UserToken = req.headers.authorization; // $ Alert // no signature verification - jose.decodeJwt(UserToken) // NOT OK + jose.decodeJwt(UserToken) // $ Sink // NOT OK }) diff --git a/javascript/ql/test/experimental/Security/CWE-347/remotesource/jwtDecode.js b/javascript/ql/test/experimental/Security/CWE-347/remotesource/jwtDecode.js index 76a26e0df366..6bdc92ba6260 100644 --- a/javascript/ql/test/experimental/Security/CWE-347/remotesource/jwtDecode.js +++ b/javascript/ql/test/experimental/Security/CWE-347/remotesource/jwtDecode.js @@ -8,11 +8,11 @@ function getSecret() { } app.get('/jwtDecode', (req, res) => { - const UserToken = req.headers.authorization; + const UserToken = req.headers.authorization; // $ Alert // jwt-decode // no signature verification - jwt_decode(UserToken) // NOT OK + jwt_decode(UserToken) // $ Sink // NOT OK }) app.listen(port, () => { diff --git a/javascript/ql/test/experimental/Security/CWE-347/remotesource/jwtSimple.js b/javascript/ql/test/experimental/Security/CWE-347/remotesource/jwtSimple.js index 4803309e3e55..4652ffcc3c68 100644 --- a/javascript/ql/test/experimental/Security/CWE-347/remotesource/jwtSimple.js +++ b/javascript/ql/test/experimental/Security/CWE-347/remotesource/jwtSimple.js @@ -7,10 +7,10 @@ function getSecret() { return "A Safe generated random key" } app.get('/jwtSimple1', (req, res) => { - const UserToken = req.headers.authorization; + const UserToken = req.headers.authorization; // $ Alert // no signature verification - jwt_simple.decode(UserToken, getSecret(), true); // NOT OK + jwt_simple.decode(UserToken, getSecret(), true); // $ Sink // NOT OK }) app.get('/jwtSimple2', (req, res) => { diff --git a/javascript/ql/test/experimental/Security/CWE-918/SSRF.qlref b/javascript/ql/test/experimental/Security/CWE-918/SSRF.qlref index 05a9c8145e60..7819b4827f28 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/SSRF.qlref +++ b/javascript/ql/test/experimental/Security/CWE-918/SSRF.qlref @@ -1 +1,2 @@ -./experimental/Security/CWE-918/SSRF.ql \ No newline at end of file +query: ./experimental/Security/CWE-918/SSRF.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/SsrfIpv6TransitionIncompleteGuard.qlref b/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/SsrfIpv6TransitionIncompleteGuard.qlref index 50159ab72fe1..c26b3d73324a 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/SsrfIpv6TransitionIncompleteGuard.qlref +++ b/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/SsrfIpv6TransitionIncompleteGuard.qlref @@ -1 +1,2 @@ -experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard.ql \ No newline at end of file +query: experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/bad-private-ip-pkg.js b/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/bad-private-ip-pkg.js index 972d7aad9b73..53e3d3660c23 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/bad-private-ip-pkg.js +++ b/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/bad-private-ip-pkg.js @@ -8,6 +8,6 @@ async function validateUrlHost(host) { // NOT OK throw new Error('blocked private host'); } return fetch('http://' + host + '/'); -} +} // $ Alert[javascript/ssrf-ipv6-transition-incomplete-guard] module.exports = { validateUrlHost }; diff --git a/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/bad-rfc1918-regex.js b/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/bad-rfc1918-regex.js index be70a4a5e5dc..4c067666242b 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/bad-rfc1918-regex.js +++ b/javascript/ql/test/experimental/Security/CWE-918/SsrfIpv6TransitionIncompleteGuard/bad-rfc1918-regex.js @@ -13,6 +13,6 @@ function checkTargetHost(host) { // NOT OK throw new Error('blocked internal host'); } return http.get('http://' + host + '/'); -} +} // $ Alert[javascript/ssrf-ipv6-transition-incomplete-guard] module.exports = { checkTargetHost }; diff --git a/javascript/ql/test/experimental/Security/CWE-918/check-domain.js b/javascript/ql/test/experimental/Security/CWE-918/check-domain.js index 0821140ab5fe..e46f4b0bb9e6 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/check-domain.js +++ b/javascript/ql/test/experimental/Security/CWE-918/check-domain.js @@ -13,8 +13,8 @@ const app = express(); app.get('/check-with-axios', req => { // without validation - const url = req.query.url; - axios.get(url); //SSRF + const url = req.query.url; // $ Source + axios.get(url); // $ Alert // SSRF // validating domain only const decodedURI = decodeURIComponent(req.query.url); @@ -22,8 +22,8 @@ app.get('/check-with-axios', req => { const { hostname } = url.parse(decodedURI); - if (isValidDomain(hostname, validDomains)) { - axios.get(req.query.url); //SSRF + if (isValidDomain(hostname, VALID_DOMAINS)) { + axios.get(req.query.url); // $ Alert // SSRF } }); @@ -31,4 +31,4 @@ const isValidDomain = (hostname, validDomains) => ( validDomains.some(domain => ( hostname === domain || hostname.endsWith(`.${domain}`)) ) -); \ No newline at end of file +); diff --git a/javascript/ql/test/experimental/Security/CWE-918/check-middleware.js b/javascript/ql/test/experimental/Security/CWE-918/check-middleware.js index 2a1e6d541665..f79250ae5f70 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/check-middleware.js +++ b/javascript/ql/test/experimental/Security/CWE-918/check-middleware.js @@ -6,7 +6,7 @@ const express = require('express'); const app = express(); app.get('/check-with-axios', validationMiddleware, req => { - axios.get("test.com/" + req.query.tainted); // OK is sanitized by the middleware - False Positive + axios.get("test.com/" + req.query.tainted); // $ Alert // OK is sanitized by the middleware - False Positive }); diff --git a/javascript/ql/test/experimental/Security/CWE-918/check-path.js b/javascript/ql/test/experimental/Security/CWE-918/check-path.js index b26e4924460f..25427ec7f8e9 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/check-path.js +++ b/javascript/ql/test/experimental/Security/CWE-918/check-path.js @@ -16,11 +16,11 @@ app.get('/check-with-axios', req => { const hardcoded = 'hardcodeado'; axios.get('test.com/' + hardcoded); // OK - axios.get('test.com/' + req.query.tainted); // SSRF + axios.get('test.com/' + req.query.tainted); // $ Alert // SSRF axios.get('test.com/' + Number(req.query.tainted)); // OK axios.get('test.com/' + req.user.id); // OK axios.get('test.com/' + encodeURIComponent(req.query.tainted)); // OK - axios.get(`/addresses/${req.query.tainted}`); // SSRF + axios.get(`/addresses/${req.query.tainted}`); // $ Alert // SSRF axios.get(`/addresses/${encodeURIComponent(req.query.tainted)}`); // OK if (Number.isInteger(req.query.tainted)) { @@ -30,11 +30,11 @@ app.get('/check-with-axios', req => { if (isValidInput(req.query.tainted)){ axios.get('test.com/' + req.query.tainted); // OK } else { - axios.get('test.com/' + req.query.tainted); // SSRF + axios.get('test.com/' + req.query.tainted); // $ Alert // SSRF } if (doesntCheckAnything(req.query.tainted)) { - axios.get('test.com/' + req.query.tainted); // SSRF + axios.get('test.com/' + req.query.tainted); // $ Alert // SSRF } if (isValidPath(req.query.tainted, VALID_PATHS)) { @@ -42,7 +42,7 @@ app.get('/check-with-axios', req => { } let baseURL = require('config').base - axios.get(`${baseURL}${req.query.tainted}`); // SSRF + axios.get(`${baseURL}${req.query.tainted}`); // $ Alert // SSRF if(!isValidInput(req.query.tainted)) { return; diff --git a/javascript/ql/test/experimental/Security/CWE-918/check-regex.js b/javascript/ql/test/experimental/Security/CWE-918/check-regex.js index 238aa9068432..9d09d1832ef2 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/check-regex.js +++ b/javascript/ql/test/experimental/Security/CWE-918/check-regex.js @@ -13,7 +13,7 @@ app.get('/check-with-axios', req => { axios.get("test.com/" + req.query.tainted); // OK } if (req.query.tainted.match(/^.*$/)) { // anything - axios.get("test.com/" + req.query.tainted); // SSRF - False Negative + axios.get("test.com/" + req.query.tainted); // $ Alert // SSRF - False Negative } const baseURL = "test.com/" @@ -21,24 +21,24 @@ app.get('/check-with-axios', req => { axios.get(baseURL + req.params.tainted); // OK } if (!isValidPath(req.params.tainted) ) { - axios.get(baseURL + req.params.tainted); // SSRF + axios.get(baseURL + req.params.tainted); // $ Alert // SSRF } else { axios.get(baseURL + req.params.tainted); // OK } // Blacklists are not safe if (!req.query.tainted.match(/^[/\.%]+$/)) { - axios.get("test.com/" + req.query.tainted); // SSRF + axios.get("test.com/" + req.query.tainted); // $ Alert // SSRF } if (!isInBlacklist(req.params.tainted) ) { - axios.get(baseURL + req.params.tainted); // SSRF + axios.get(baseURL + req.params.tainted); // $ Alert // SSRF } if (!isValidPath(req.params.tainted)) { return; } - axios.get("test.com/" + req.query.tainted); // OK - False Positive + axios.get("test.com/" + req.query.tainted); // $ Alert // OK - False Positive if (req.query.tainted.matchAll(/^[0-9a-z]+$/g)) { // letters and numbers axios.get("test.com/" + req.query.tainted); // OK @@ -58,7 +58,7 @@ app.get('/check-with-axios', req => { axios.get(baseURL + req.params.tainted); // OK } if (!isValidPathMatchAll(req.params.tainted) ) { - axios.get(baseURL + req.params.tainted); // NOT OK - SSRF + axios.get(baseURL + req.params.tainted); // $ Alert // NOT OK - SSRF } else { axios.get(baseURL + req.params.tainted); // OK } diff --git a/javascript/ql/test/experimental/Security/CWE-918/check-validator.js b/javascript/ql/test/experimental/Security/CWE-918/check-validator.js index dfe3314b07b3..631f032d9564 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/check-validator.js +++ b/javascript/ql/test/experimental/Security/CWE-918/check-validator.js @@ -12,7 +12,7 @@ app.get("/check-with-axios", req => { axios.get("test.com/" + req.query.tainted); // OK } if (isAlphanumeric(req.query.tainted)) { - axios.get("test.com/" + req.query.tainted); // SSRF + axios.get("test.com/" + req.query.tainted); // $ Alert // SSRF } if (validAlphanumeric(req.query.tainted)) { axios.get("test.com/" + req.query.tainted); // OK @@ -24,7 +24,7 @@ app.get("/check-with-axios", req => { axios.get("test.com/" + req.query.tainted); // OK } if (wrongValidation(req.query.tainted)) { - axios.get("test.com/" + req.query.tainted); // SSRF + axios.get("test.com/" + req.query.tainted); // $ Alert // SSRF } // numbers @@ -47,25 +47,25 @@ app.get("/check-with-axios", req => { axios.get("test.com/" + req.query.tainted); // OK } if (validHexa(req.query.tainted)) { - axios.get("test.com/" + req.query.tainted); // OK. False Positive + axios.get("test.com/" + req.query.tainted); // $ Alert // OK. False Positive } // with simple assignation - const numberURL = req.query.tainted; + const numberURL = req.query.tainted; // $ Source if (validNumber(numberURL)) { axios.get("test.com/" + numberURL); // OK } if (validNumber(numberURL)) { - axios.get("test.com/" + req.query.tainted); // OK. False Positive + axios.get("test.com/" + req.query.tainted); // $ Alert // OK. False Positive } if (validNumber(req.query.tainted)) { - axios.get("test.com/" + numberURL); // OK. False Positive + axios.get("test.com/" + numberURL); // $ Alert // OK. False Positive } if (validHexadecimal(req.query.tainted) || validHexaColor(req.query.tainted) || validDecimal(req.query.tainted) || validFloat(req.query.tainted) || validInt(req.query.tainted) || validNumber(req.query.tainted) || validOctal(req.query.tainted)) { - axios.get("test.com/" + req.query.tainted); // OK. False Positive + axios.get("test.com/" + req.query.tainted); // $ Alert // OK. False Positive } }); diff --git a/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/MultipleArgumentsToSetConstructor.qlref b/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/MultipleArgumentsToSetConstructor.qlref index 3cba54a3a0c5..51cb2f3db8ba 100644 --- a/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/MultipleArgumentsToSetConstructor.qlref +++ b/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/MultipleArgumentsToSetConstructor.qlref @@ -1 +1,2 @@ -experimental/StandardLibrary/MultipleArgumentsToSetConstructor.ql +query: experimental/StandardLibrary/MultipleArgumentsToSetConstructor.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/MultipleArgumentsToSetConstructorBad.js b/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/MultipleArgumentsToSetConstructorBad.js index 4bce4b54c1b4..ea51918032a2 100644 --- a/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/MultipleArgumentsToSetConstructorBad.js +++ b/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/MultipleArgumentsToSetConstructorBad.js @@ -1,4 +1,4 @@ -const vowels = new Set('a', 'e', 'i', 'o', 'u'); +const vowels = new Set('a', 'e', 'i', 'o', 'u'); // $ Alert function isVowel(char) { return vowels.has(char.toLowerCase()); diff --git a/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/tst.js b/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/tst.js index 7f43ae5f9664..1f12916af5b3 100644 --- a/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/tst.js +++ b/javascript/ql/test/experimental/StandardLibrary/MultipleArgumentsToSetConstructor/tst.js @@ -1,6 +1,6 @@ let xs = [1, 2, 3]; let ys = [4, 5, 6]; -new Set(...xs, ...ys); // NOT OK +new Set(...xs, ...ys); // $ Alert // NOT OK new Set([...xs, ...ys]); // OK new Set(xs); // OK new Set(); // OK \ No newline at end of file diff --git a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/UnpromotedRouteHandlerCandidate.qlref b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/UnpromotedRouteHandlerCandidate.qlref index 51fb87eed725..c383d032bc09 100644 --- a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/UnpromotedRouteHandlerCandidate.qlref +++ b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/UnpromotedRouteHandlerCandidate.qlref @@ -1 +1,2 @@ -meta/analysis-quality/UnpromotedRouteHandlerCandidate.ql \ No newline at end of file +query: meta/analysis-quality/UnpromotedRouteHandlerCandidate.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/UnpromotedRouteSetupCandidate.qlref b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/UnpromotedRouteSetupCandidate.qlref index 5ce57dc19ba9..2ef54efcfd0a 100644 --- a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/UnpromotedRouteSetupCandidate.qlref +++ b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/UnpromotedRouteSetupCandidate.qlref @@ -1 +1,2 @@ -meta/analysis-quality/UnpromotedRouteSetupCandidate.ql \ No newline at end of file +query: meta/analysis-quality/UnpromotedRouteSetupCandidate.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/hapi.js b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/hapi.js index 581e2401e05e..9ea46e7ce68f 100644 --- a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/hapi.js +++ b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/hapi.js @@ -1 +1 @@ -function handler(request, h){} +function handler(request, h){} // $ Alert[js/unpromoted-route-handler-candidate] diff --git a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/nodejs.js b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/nodejs.js index 57bcde69d531..315c6dd1379b 100644 --- a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/nodejs.js +++ b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/nodejs.js @@ -2,14 +2,14 @@ var http = require('http'); http.createServer(function(req, res){}); -unknown.createServer(function(req, res){}); +unknown.createServer(function(req, res){}); // $ Alert[js/unpromoted-route-setup-candidate] var createServer = http.createServer; createServer(function(req, res){}); http.createServer().on("request", function(req, res){}); -unknown.on("request", function(req, res){}); -unknown.once("request", function(req, res){}); +unknown.on("request", function(req, res){}); // $ Alert[js/unpromoted-route-setup-candidate] +unknown.once("request", function(req, res){}); // $ Alert[js/unpromoted-route-setup-candidate] function getHandler(){ return function(req, res){}; diff --git a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/route-objects.js b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/route-objects.js index 64dbe4555600..2abb91d0f11b 100644 --- a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/route-objects.js +++ b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/route-objects.js @@ -4,10 +4,10 @@ var app = express(); var route1 = { method: 'post', url: '/foo', - middleWares: [function(req, res){}], + middleWares: [function(req, res){}], // $ Alert[js/unpromoted-route-handler-candidate] handler(req, res) { - } + } // $ Alert[js/unpromoted-route-handler-candidate] }; app[route1.method](route1.url, route1.middleWares, route1.handler); @@ -19,14 +19,14 @@ var routes = [ url: '/foo', handler(req, res) { - } + } // $ Alert[js/unpromoted-route-handler-candidate] }, { method: 'post', url: '/foo', handler(req, res) { - } + } // $ Alert[js/unpromoted-route-handler-candidate] } ]; routes.forEach((route) => { @@ -39,7 +39,7 @@ var route2 = { url: '/foo', handler(req, res) { - } + } // $ Alert[js/unpromoted-route-handler-candidate] }; app[route2.method.toLowerCase()](route2.url, route2.handler); @@ -49,13 +49,13 @@ var route3 = { url: '/foo', handler(req, res) { - } + } // $ Alert[js/unpromoted-route-handler-candidate] }; function wrap(f){ return function(req, res){ f(req); - } + } // $ Alert[js/unpromoted-route-handler-candidate] } app[route3.method](route3.url, wrap(route3.handler)); confuse(wrap); // confuse the type inference diff --git a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/tst.js b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/tst.js index e2d6cfd1ebf0..871ddf9603b7 100644 --- a/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/tst.js +++ b/javascript/ql/test/library-tests/frameworks/HTTP-heuristics/src/tst.js @@ -3,9 +3,9 @@ var app = express(); app.get('/some/path', function(req, res) {}) -someOtherApp.get('/some/path', function(req, res) {}) +someOtherApp.get('/some/path', function(req, res) {}) // $ Alert[js/unpromoted-route-setup-candidate] -someOtherApp.get('/some/path', function(request, response) {}) +someOtherApp.get('/some/path', function(request, response) {}) // $ Alert[js/unpromoted-route-setup-candidate] someOtherApp.get('/some/path', function(r) { r.acceptsCharsets() @@ -27,23 +27,23 @@ someOtherApp.get('/some/path', function(r, s, n) { n('route') }) -someOtherApp.delete('/some/path', function(req, res) {}) +someOtherApp.delete('/some/path', function(req, res) {}) // $ Alert[js/unpromoted-route-setup-candidate] someOtherApp.get('/some/path', function(req, res) {}, - function(req, res) {}) + function(req, res) {}) // $ Alert[js/unpromoted-route-setup-candidate] someOtherApp.get('/some/path', [ function(req, res) {}, function(req, res) {} -]) +]) // $ Alert[js/unpromoted-route-setup-candidate] someOtherApp.get('/some/path', function() {}, - function(req, res) {}) + function(req, res) {}) // $ Alert[js/unpromoted-route-setup-candidate] -function f(req, res) {} +function f(req, res) {} // $ Alert[js/unpromoted-route-handler-candidate] function f(ctx, next) { ctx.acceptsCharsets() @@ -51,25 +51,25 @@ function f(ctx, next) { function f(req, res) { req() -} +} // $ Alert[js/unpromoted-route-handler-candidate] function called(req,res) { -} +} // $ Alert[js/unpromoted-route-handler-candidate] called() function f(req,res) { return; -} +} // $ Alert[js/unpromoted-route-handler-candidate] function f(req,res) { return x; -} +} // $ Alert[js/unpromoted-route-handler-candidate] function adHocTestsFor_HeuristicRouteHandler() { function rh_dead(req, res) { - } + } // $ Alert[js/unpromoted-route-handler-candidate] function rh_flowToSetup(req, res) { @@ -84,7 +84,7 @@ function adHocTestsFor_HeuristicRouteHandler() { function rh_flowToHeuristicSetup(req, res) { } - unknownApp.get('/some/path', rh_flowToHeuristicSetup) + unknownApp.get('/some/path', rh_flowToHeuristicSetup) // $ Alert[js/unpromoted-route-setup-candidate] } function adHocTestsFor_HeuristicRouteSetups() { @@ -93,22 +93,22 @@ function adHocTestsFor_HeuristicRouteSetups() { } app.get('/some/path', rh); - unknownApp.get('/some/path', rh); + unknownApp.get('/some/path', rh); // $ Alert[js/unpromoted-route-setup-candidate] - unknownApp.get('/some/path', [rh]); + unknownApp.get('/some/path', [rh]); // $ Alert[js/unpromoted-route-setup-candidate] unknownApp.get('/some/path', unknown); unknownApp.get('/some/path', [unknown]); - unknownApp.get('/some/path', unknown, rh); + unknownApp.get('/some/path', unknown, rh); // $ Alert[js/unpromoted-route-setup-candidate] } function adHocTestsFor_HeuristicRouteHandler_withTracking() { function get_rh_dead() { return function rh_dead(req, res) { - } + } // $ Alert[js/unpromoted-route-handler-candidate] } var rh_dead = get_rh_dead(); @@ -134,7 +134,7 @@ function adHocTestsFor_HeuristicRouteHandler_withTracking() { } } var rh_flowToHeuristicSetup = get_rh_flowToHeuristicSetup(); - unknownApp.get('/some/path', rh_flowToHeuristicSetup) + unknownApp.get('/some/path', rh_flowToHeuristicSetup) // $ Alert[js/unpromoted-route-setup-candidate] } function adHocTestsFor_HeuristicRouteSetups_withTracking() { @@ -146,13 +146,13 @@ function adHocTestsFor_HeuristicRouteSetups_withTracking() { var rh = get_rh(); app.get('/some/path', rh); - unknownApp.get('/some/path', rh); + unknownApp.get('/some/path', rh); // $ Alert[js/unpromoted-route-setup-candidate] - unknownApp.get('/some/path', [rh]); + unknownApp.get('/some/path', [rh]); // $ Alert[js/unpromoted-route-setup-candidate] unknownApp.get('/some/path', unknown); unknownApp.get('/some/path', [unknown]); - unknownApp.get('/some/path', unknown, rh); + unknownApp.get('/some/path', unknown, rh); // $ Alert[js/unpromoted-route-setup-candidate] } diff --git a/javascript/ql/test/library-tests/frameworks/Templating/CodeInjection.qlref b/javascript/ql/test/library-tests/frameworks/Templating/CodeInjection.qlref index fe9adbf3b64d..bfeec8aec393 100644 --- a/javascript/ql/test/library-tests/frameworks/Templating/CodeInjection.qlref +++ b/javascript/ql/test/library-tests/frameworks/Templating/CodeInjection.qlref @@ -1 +1,2 @@ -Security/CWE-094/CodeInjection.ql +query: Security/CWE-094/CodeInjection.ql +postprocess: utils/test/InlineExpectationsTestQuery.ql diff --git a/javascript/ql/test/library-tests/frameworks/Templating/app.js b/javascript/ql/test/library-tests/frameworks/Templating/app.js index 8666d79b644f..2822d40be5c1 100644 --- a/javascript/ql/test/library-tests/frameworks/Templating/app.js +++ b/javascript/ql/test/library-tests/frameworks/Templating/app.js @@ -12,11 +12,11 @@ app.get('/ejs', (req, res) => { }, dataInStringLiteral: req.query.dataInStringLiteral, dataInStringLiteralRaw: req.query.dataInStringLiteralRaw, - dataInGeneratedCode: req.query.dataInGeneratedCode, + dataInGeneratedCode: req.query.dataInGeneratedCode, // $ Source dataInGeneratedCodeRaw: req.query.dataInGeneratedCodeRaw, - backslashSink1: req.query.backslashSink1, + backslashSink1: req.query.backslashSink1, // $ Source backslashSink2: req.query.backslashSink2, - dataInEventHandlerString: req.query.dataInEventHandlerString, + dataInEventHandlerString: req.query.dataInEventHandlerString, // $ Source dataInEventHandlerStringRaw: req.query.dataInEventHandlerStringRaw, }); }); @@ -31,11 +31,11 @@ app.get('/hbs', (req, res) => { }, dataInStringLiteral: req.query.dataInStringLiteral, dataInStringLiteralRaw: req.query.dataInStringLiteralRaw, - dataInGeneratedCode: req.query.dataInGeneratedCode, + dataInGeneratedCode: req.query.dataInGeneratedCode, // $ Source dataInGeneratedCodeRaw: req.query.dataInGeneratedCodeRaw, - backslashSink1: req.query.backslashSink1, + backslashSink1: req.query.backslashSink1, // $ Source backslashSink2: req.query.backslashSink2, - dataInEventHandlerString: req.query.dataInEventHandlerString, + dataInEventHandlerString: req.query.dataInEventHandlerString, // $ Source dataInEventHandlerStringRaw: req.query.dataInEventHandlerStringRaw, }); }); @@ -50,20 +50,20 @@ app.get('/njk', (req, res) => { }, dataInStringLiteral: req.query.dataInStringLiteral, dataInStringLiteralRaw: req.query.dataInStringLiteralRaw, - dataInGeneratedCode: req.query.dataInGeneratedCode, - dataInGeneratedCodeRaw: req.query.dataInGeneratedCodeRaw, + dataInGeneratedCode: req.query.dataInGeneratedCode, // $ Source + dataInGeneratedCodeRaw: req.query.dataInGeneratedCodeRaw, // $ Source dataInGeneratedCodeJsonRaw: req.query.dataInGeneratedCodeJsonRaw, - backslashSink1: req.query.backslashSink1, + backslashSink1: req.query.backslashSink1, // $ Source backslashSink2: req.query.backslashSink2, - dataInEventHandlerString: req.query.dataInEventHandlerString, - dataInEventHandlerStringRaw: req.query.dataInEventHandlerStringRaw, + dataInEventHandlerString: req.query.dataInEventHandlerString, // $ Source + dataInEventHandlerStringRaw: req.query.dataInEventHandlerStringRaw, // $ Source }); }); app.get('/angularjs', (req, res) => { res.render('angularjs_sinks', { - escapedHtml: req.query.escapedHtml, - rawHtml: req.query.rawHtml, + escapedHtml: req.query.escapedHtml, // $ Source + rawHtml: req.query.rawHtml, // $ Source }); }); diff --git a/javascript/ql/test/library-tests/frameworks/Templating/views/angularjs_include.ejs b/javascript/ql/test/library-tests/frameworks/Templating/views/angularjs_include.ejs index 2d02e173275f..e9a6f436a8fe 100644 --- a/javascript/ql/test/library-tests/frameworks/Templating/views/angularjs_include.ejs +++ b/javascript/ql/test/library-tests/frameworks/Templating/views/angularjs_include.ejs @@ -1,5 +1,5 @@
- <%= escapedHtml %> - <%- rawHtml %> + <%= escapedHtml %> + <%- rawHtml %>
diff --git a/javascript/ql/test/library-tests/frameworks/Templating/views/angularjs_sinks.ejs b/javascript/ql/test/library-tests/frameworks/Templating/views/angularjs_sinks.ejs index 47105bb360ec..5dc0c0763cf3 100644 --- a/javascript/ql/test/library-tests/frameworks/Templating/views/angularjs_sinks.ejs +++ b/javascript/ql/test/library-tests/frameworks/Templating/views/angularjs_sinks.ejs @@ -1,7 +1,7 @@ - <%= escapedHtml %> - <%- rawHtml %> + <%= escapedHtml %> + <%- rawHtml %> <% include angularjs_include %> diff --git a/javascript/ql/test/library-tests/frameworks/Templating/views/ejs_sinks.ejs b/javascript/ql/test/library-tests/frameworks/Templating/views/ejs_sinks.ejs index 42dfc124a709..b25de673433b 100644 --- a/javascript/ql/test/library-tests/frameworks/Templating/views/ejs_sinks.ejs +++ b/javascript/ql/test/library-tests/frameworks/Templating/views/ejs_sinks.ejs @@ -10,15 +10,15 @@ var dataInStringLiteral = "<%= dataInStringLiteral %>"; var dataInStringLiteralRaw = "<%- dataInStringLiteralRaw %>"; - var dataInGeneratedCode = <%= dataInGeneratedCode %>; + var dataInGeneratedCode = <%= dataInGeneratedCode %>; // $ Alert var dataInGeneratedCodeRaw = <%- dataInGeneratedCodeRaw %>; - init("<%= backslashSink1 %>", "<%= backslashSink2 %>"); + init("<%= backslashSink1 %>", "<%= backslashSink2 %>"); // $ Alert var mustache = "{{ rawHtml }}"; - + <%- include('ejs_include1', { foo: rawHtml }) _%> diff --git a/javascript/ql/test/library-tests/frameworks/Templating/views/hbs_sinks.hbs b/javascript/ql/test/library-tests/frameworks/Templating/views/hbs_sinks.hbs index 198af1cd8d41..a723f95e7745 100644 --- a/javascript/ql/test/library-tests/frameworks/Templating/views/hbs_sinks.hbs +++ b/javascript/ql/test/library-tests/frameworks/Templating/views/hbs_sinks.hbs @@ -22,15 +22,15 @@ var dataInStringLiteral = "{{ dataInStringLiteral }}"; var dataInStringLiteralRaw = "{{{ dataInStringLiteralRaw }}}"; - var dataInGeneratedCode = {{ dataInGeneratedCode }}; + var dataInGeneratedCode = {{ dataInGeneratedCode }}; // $ Alert var dataInGeneratedCodeRaw = {{{ dataInGeneratedCodeRaw }}}; - init("{{ backslashSink1 }}", "{{ backslashSink2 }}"); + init("{{ backslashSink1 }}", "{{ backslashSink2 }}"); // $ Alert var ejs = "<%= rawHtml %>"; - + diff --git a/javascript/ql/test/library-tests/frameworks/Templating/views/njk_sinks.njk b/javascript/ql/test/library-tests/frameworks/Templating/views/njk_sinks.njk index fcfba32a26c8..dc34a5c0bbb1 100644 --- a/javascript/ql/test/library-tests/frameworks/Templating/views/njk_sinks.njk +++ b/javascript/ql/test/library-tests/frameworks/Templating/views/njk_sinks.njk @@ -10,16 +10,16 @@ var dataInStringLiteral = "{{ dataInStringLiteral }}"; var dataInStringLiteralRaw = "{{ dataInStringLiteralRaw | safe }}"; - var dataInGeneratedCode = {{ dataInGeneratedCode }}; - var dataInGeneratedCodeRaw = {{ dataInGeneratedCodeRaw | safe }}; + var dataInGeneratedCode = {{ dataInGeneratedCode }}; // $ Alert + var dataInGeneratedCodeRaw = {{ dataInGeneratedCodeRaw | safe }}; // $ Alert var dataInGeneratedCodeJsonRaw = {{ dataInGeneratedCodeJsonRaw | json | safe }}; - init("{{ backslashSink1 }}", "{{ backslashSink2 }}"); + init("{{ backslashSink1 }}", "{{ backslashSink2 }}"); // $ Alert var ejs = "<%= rawHtml %>"; - - + + From de7723f214b3d63060add7a59f390d3633ac903b Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sun, 5 Jul 2026 17:07:49 +0100 Subject: [PATCH 03/60] Add SPURIOUS and MISSING tags --- .../Security/CWE-918/check-middleware.js | 2 +- .../Security/CWE-918/check-regex.js | 20 +++++++++---------- .../Security/CWE-918/check-validator.js | 16 +++++++-------- .../missing-explicit-injection.js | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/javascript/ql/test/experimental/Security/CWE-918/check-middleware.js b/javascript/ql/test/experimental/Security/CWE-918/check-middleware.js index f79250ae5f70..5895501e7bfe 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/check-middleware.js +++ b/javascript/ql/test/experimental/Security/CWE-918/check-middleware.js @@ -6,7 +6,7 @@ const express = require('express'); const app = express(); app.get('/check-with-axios', validationMiddleware, req => { - axios.get("test.com/" + req.query.tainted); // $ Alert // OK is sanitized by the middleware - False Positive + axios.get("test.com/" + req.query.tainted); // $ SPURIOUS: Alert // OK is sanitized by the middleware - False Positive }); diff --git a/javascript/ql/test/experimental/Security/CWE-918/check-regex.js b/javascript/ql/test/experimental/Security/CWE-918/check-regex.js index 9d09d1832ef2..710a811857fd 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/check-regex.js +++ b/javascript/ql/test/experimental/Security/CWE-918/check-regex.js @@ -13,14 +13,14 @@ app.get('/check-with-axios', req => { axios.get("test.com/" + req.query.tainted); // OK } if (req.query.tainted.match(/^.*$/)) { // anything - axios.get("test.com/" + req.query.tainted); // $ Alert // SSRF - False Negative + axios.get("test.com/" + req.query.tainted); // $ Alert // SSRF } const baseURL = "test.com/" - if (isValidPath(req.params.tainted) ) { + if (isValidPath(req.params.tainted)) { axios.get(baseURL + req.params.tainted); // OK } - if (!isValidPath(req.params.tainted) ) { + if (!isValidPath(req.params.tainted)) { axios.get(baseURL + req.params.tainted); // $ Alert // SSRF } else { axios.get(baseURL + req.params.tainted); // OK @@ -30,7 +30,7 @@ app.get('/check-with-axios', req => { if (!req.query.tainted.match(/^[/\.%]+$/)) { axios.get("test.com/" + req.query.tainted); // $ Alert // SSRF } - if (!isInBlacklist(req.params.tainted) ) { + if (!isInBlacklist(req.params.tainted)) { axios.get(baseURL + req.params.tainted); // $ Alert // SSRF } @@ -38,7 +38,7 @@ app.get('/check-with-axios', req => { return; } - axios.get("test.com/" + req.query.tainted); // $ Alert // OK - False Positive + axios.get("test.com/" + req.query.tainted); // $ SPURIOUS: Alert // OK - False Positive if (req.query.tainted.matchAll(/^[0-9a-z]+$/g)) { // letters and numbers axios.get("test.com/" + req.query.tainted); // OK @@ -48,20 +48,20 @@ app.get('/check-with-axios', req => { } }); -const isValidPath = path => path.match(/^[0-9a-z]+$/); +const isValidPath = path => path.match(/^[0-9a-z]+$/); -const isInBlackList = path => path.match(/^[/\.%]+$/); +const isInBlackList = path => path.match(/^[/\.%]+$/); app.get('/check-with-axios', req => { const baseURL = "test.com/" - if (isValidPathMatchAll(req.params.tainted) ) { + if (isValidPathMatchAll(req.params.tainted)) { axios.get(baseURL + req.params.tainted); // OK } - if (!isValidPathMatchAll(req.params.tainted) ) { + if (!isValidPathMatchAll(req.params.tainted)) { axios.get(baseURL + req.params.tainted); // $ Alert // NOT OK - SSRF } else { axios.get(baseURL + req.params.tainted); // OK } }); -const isValidPathMatchAll = path => path.matchAll(/^[0-9a-z]+$/g); +const isValidPathMatchAll = path => path.matchAll(/^[0-9a-z]+$/g); diff --git a/javascript/ql/test/experimental/Security/CWE-918/check-validator.js b/javascript/ql/test/experimental/Security/CWE-918/check-validator.js index 631f032d9564..37d9895b24e2 100644 --- a/javascript/ql/test/experimental/Security/CWE-918/check-validator.js +++ b/javascript/ql/test/experimental/Security/CWE-918/check-validator.js @@ -47,7 +47,7 @@ app.get("/check-with-axios", req => { axios.get("test.com/" + req.query.tainted); // OK } if (validHexa(req.query.tainted)) { - axios.get("test.com/" + req.query.tainted); // $ Alert // OK. False Positive + axios.get("test.com/" + req.query.tainted); // $ SPURIOUS: Alert // OK. False Positive } // with simple assignation @@ -56,16 +56,16 @@ app.get("/check-with-axios", req => { axios.get("test.com/" + numberURL); // OK } if (validNumber(numberURL)) { - axios.get("test.com/" + req.query.tainted); // $ Alert // OK. False Positive + axios.get("test.com/" + req.query.tainted); // $ SPURIOUS: Alert // OK. False Positive } if (validNumber(req.query.tainted)) { - axios.get("test.com/" + numberURL); // $ Alert // OK. False Positive + axios.get("test.com/" + numberURL); // $ SPURIOUS: Alert // OK. False Positive } - if (validHexadecimal(req.query.tainted) || validHexaColor(req.query.tainted) || - validDecimal(req.query.tainted) || validFloat(req.query.tainted) || validInt(req.query.tainted) || - validNumber(req.query.tainted) || validOctal(req.query.tainted)) { - axios.get("test.com/" + req.query.tainted); // $ Alert // OK. False Positive + if (validHexadecimal(req.query.tainted) || validHexaColor(req.query.tainted) || + validDecimal(req.query.tainted) || validFloat(req.query.tainted) || validInt(req.query.tainted) || + validNumber(req.query.tainted) || validOctal(req.query.tainted)) { + axios.get("test.com/" + req.query.tainted); // $ SPURIOUS: Alert // OK. False Positive } }); @@ -93,6 +93,6 @@ const validHexaColor = url => validator.isHexColor(url); const validUUID = url => validator.isUUID(url); // unsafe validators -const wrongValidation = url => validator.isByteLength(url, {min:4,max:8}); +const wrongValidation = url => validator.isByteLength(url, { min: 4, max: 8 }); const isAlphanumeric = url => true; diff --git a/javascript/ql/test/query-tests/AngularJS/MissingExplicitInjection/missing-explicit-injection.js b/javascript/ql/test/query-tests/AngularJS/MissingExplicitInjection/missing-explicit-injection.js index 629b62d5b08e..85186dfe8dca 100644 --- a/javascript/ql/test/query-tests/AngularJS/MissingExplicitInjection/missing-explicit-injection.js +++ b/javascript/ql/test/query-tests/AngularJS/MissingExplicitInjection/missing-explicit-injection.js @@ -21,7 +21,7 @@ controller: notInjected7 }; - function injected8(name){} // OK - false negative: we do not track through properties + function injected8(name){} // $ MISSING: Alert // false negative: we do not track through properties var obj8 = { controller: injected8 }; From 662c7b08e8abafda2d3b4d2f75a407ebb481701b Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Thu, 2 Jul 2026 14:31:30 +0200 Subject: [PATCH 04/60] Swift: Turn of caching and integrated driver in autobuild * Caching cases compiler invocations to be omitted. You can try this for yourself by turning on caching while building the project from the `xcode-hello` integration test, then cleaning of the build artifacts and building the project again while caching is enabled. This yields a database that is practically empty. * The integrated driver causes compiler invocations to depend on modules shipped with Xcode. Those are unfortunately incompatible with our extractor. --- swift/swift-autobuilder/BuildRunner.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/swift/swift-autobuilder/BuildRunner.cpp b/swift/swift-autobuilder/BuildRunner.cpp index 46108e8259da..2e0045d3c2b6 100644 --- a/swift/swift-autobuilder/BuildRunner.cpp +++ b/swift/swift-autobuilder/BuildRunner.cpp @@ -79,6 +79,9 @@ bool buildXcodeTarget(const XcodeTarget& target, bool dryRun) { argv.push_back(target.name); argv.push_back("CODE_SIGNING_REQUIRED=NO"); argv.push_back("CODE_SIGNING_ALLOWED=NO"); + argv.push_back("COMPILATION_CACHE_ENABLE_CACHING=NO"); + argv.push_back("SWIFT_ENABLE_COMPILE_CACHE=NO"); + argv.push_back("SWIFT_USE_INTEGRATED_DRIVER=NO"); return run_build_command(argv, dryRun); } From 8ecdb2cde0c6dd563edf12adf563aa8935bb2ea0 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Thu, 2 Jul 2026 14:36:34 +0200 Subject: [PATCH 05/60] Swift: update integration tests to not use the integrated driver --- swift/ql/integration-tests/osx/hello-ios/test.py | 3 ++- swift/ql/integration-tests/osx/hello-xcode/test.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/swift/ql/integration-tests/osx/hello-ios/test.py b/swift/ql/integration-tests/osx/hello-ios/test.py index b5871f323ccf..ecb4a940d8bd 100644 --- a/swift/ql/integration-tests/osx/hello-ios/test.py +++ b/swift/ql/integration-tests/osx/hello-ios/test.py @@ -7,5 +7,6 @@ def test(codeql, swift, xcode_16): codeql.database.create( command="xcodebuild build " - "CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO", + "CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO " + "SWIFT_USE_INTEGRATED_DRIVER=NO", ) diff --git a/swift/ql/integration-tests/osx/hello-xcode/test.py b/swift/ql/integration-tests/osx/hello-xcode/test.py index 35b0d6dae58e..cf6a17f39084 100644 --- a/swift/ql/integration-tests/osx/hello-xcode/test.py +++ b/swift/ql/integration-tests/osx/hello-xcode/test.py @@ -9,5 +9,6 @@ def test(codeql, swift, xcode_all): command="xcodebuild build " "-project codeql-swift-autobuild-test.xcodeproj " "-target codeql-swift-autobuild-test " - "CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO", + "CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO " + "SWIFT_USE_INTEGRATED_DRIVER=NO", ) From b4db71a38b9938cb9c5828bc096ff6aa48340143 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Thu, 2 Jul 2026 14:46:21 +0200 Subject: [PATCH 06/60] Swift: Update autobuild tests --- .../integration-tests/autobuilder/failure/diagnostics.expected | 2 +- .../swift-autobuilder/tests/hello-autobuilder/commands.expected | 2 +- .../tests/hello-targets-with-tests-suffix/commands.expected | 2 +- swift/swift-autobuilder/tests/hello-tests/commands.expected | 2 +- swift/swift-autobuilder/tests/hello-workspace/commands.expected | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/swift/ql/integration-tests/autobuilder/failure/diagnostics.expected b/swift/ql/integration-tests/autobuilder/failure/diagnostics.expected index 2f43e334bc09..248495e3bd2c 100644 --- a/swift/ql/integration-tests/autobuilder/failure/diagnostics.expected +++ b/swift/ql/integration-tests/autobuilder/failure/diagnostics.expected @@ -1,5 +1,5 @@ { - "markdownMessage": "`autobuild` failed to run the build command:\n\n```\n/usr/bin/xcodebuild build -project /hello-failure.xcodeproj -target hello-failure CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO\n```\n\nSet up a [manual build command][1] or [check the logs of the autobuild step][2].\n\n[1]: https://docs.github.com/en/enterprise-server/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages#adding-build-steps-for-a-compiled-language\n[2]: https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/using-workflow-run-logs", + "markdownMessage": "`autobuild` failed to run the build command:\n\n```\n/usr/bin/xcodebuild build -project /hello-failure.xcodeproj -target hello-failure CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO COMPILATION_CACHE_ENABLE_CACHING=NO SWIFT_ENABLE_COMPILE_CACHE=NO SWIFT_USE_INTEGRATED_DRIVER=NO\n```\n\nSet up a [manual build command][1] or [check the logs of the autobuild step][2].\n\n[1]: https://docs.github.com/en/enterprise-server/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages#adding-build-steps-for-a-compiled-language\n[2]: https://docs.github.com/en/actions/monitoring-and-troubleshooting-workflows/using-workflow-run-logs", "severity": "error", "source": { "extractorName": "swift", diff --git a/swift/swift-autobuilder/tests/hello-autobuilder/commands.expected b/swift/swift-autobuilder/tests/hello-autobuilder/commands.expected index 9d2be19b9c45..dd086268ddbb 100644 --- a/swift/swift-autobuilder/tests/hello-autobuilder/commands.expected +++ b/swift/swift-autobuilder/tests/hello-autobuilder/commands.expected @@ -1 +1 @@ -/usr/bin/xcodebuild build -project ./hello-autobuilder.xcodeproj -target hello-autobuilder CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO +/usr/bin/xcodebuild build -project ./hello-autobuilder.xcodeproj -target hello-autobuilder CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO COMPILATION_CACHE_ENABLE_CACHING=NO SWIFT_ENABLE_COMPILE_CACHE=NO SWIFT_USE_INTEGRATED_DRIVER=NO diff --git a/swift/swift-autobuilder/tests/hello-targets-with-tests-suffix/commands.expected b/swift/swift-autobuilder/tests/hello-targets-with-tests-suffix/commands.expected index 4506ff8aa56e..3d31114ca325 100644 --- a/swift/swift-autobuilder/tests/hello-targets-with-tests-suffix/commands.expected +++ b/swift/swift-autobuilder/tests/hello-targets-with-tests-suffix/commands.expected @@ -1 +1 @@ -/usr/bin/xcodebuild build -project ./Foo.xcodeproj -target FooDemo CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO +/usr/bin/xcodebuild build -project ./Foo.xcodeproj -target FooDemo CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO COMPILATION_CACHE_ENABLE_CACHING=NO SWIFT_ENABLE_COMPILE_CACHE=NO SWIFT_USE_INTEGRATED_DRIVER=NO diff --git a/swift/swift-autobuilder/tests/hello-tests/commands.expected b/swift/swift-autobuilder/tests/hello-tests/commands.expected index a34306fe74c2..46e061896a85 100644 --- a/swift/swift-autobuilder/tests/hello-tests/commands.expected +++ b/swift/swift-autobuilder/tests/hello-tests/commands.expected @@ -1 +1 @@ -/usr/bin/xcodebuild build -project ./hello-tests.xcodeproj -target hello-tests CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO +/usr/bin/xcodebuild build -project ./hello-tests.xcodeproj -target hello-tests CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO COMPILATION_CACHE_ENABLE_CACHING=NO SWIFT_ENABLE_COMPILE_CACHE=NO SWIFT_USE_INTEGRATED_DRIVER=NO diff --git a/swift/swift-autobuilder/tests/hello-workspace/commands.expected b/swift/swift-autobuilder/tests/hello-workspace/commands.expected index ad85eb8c24bb..d9d20e69d087 100644 --- a/swift/swift-autobuilder/tests/hello-workspace/commands.expected +++ b/swift/swift-autobuilder/tests/hello-workspace/commands.expected @@ -1 +1 @@ -/usr/bin/xcodebuild build -workspace ./Hello.xcworkspace -scheme hello-workspace CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO +/usr/bin/xcodebuild build -workspace ./Hello.xcworkspace -scheme hello-workspace CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO COMPILATION_CACHE_ENABLE_CACHING=NO SWIFT_ENABLE_COMPILE_CACHE=NO SWIFT_USE_INTEGRATED_DRIVER=NO From 36af59adbf9771e7911c8dc5310c8fde4f95f217 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Thu, 2 Jul 2026 15:34:22 +0200 Subject: [PATCH 07/60] Swift: Update integration test --- .../autobuilder/xcode-fails-spm-works/Files.macos_26.expected | 3 --- .../autobuilder/xcode-fails-spm-works/test.py | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 swift/ql/integration-tests/autobuilder/xcode-fails-spm-works/Files.macos_26.expected diff --git a/swift/ql/integration-tests/autobuilder/xcode-fails-spm-works/Files.macos_26.expected b/swift/ql/integration-tests/autobuilder/xcode-fails-spm-works/Files.macos_26.expected deleted file mode 100644 index 8a3be429106c..000000000000 --- a/swift/ql/integration-tests/autobuilder/xcode-fails-spm-works/Files.macos_26.expected +++ /dev/null @@ -1,3 +0,0 @@ -| Package.swift:0:0:0:0 | Package.swift | -| Sources/hello-world/hello_world.swift:0:0:0:0 | Sources/hello-world/hello_world.swift | -| file://:0:0:0:0 | | diff --git a/swift/ql/integration-tests/autobuilder/xcode-fails-spm-works/test.py b/swift/ql/integration-tests/autobuilder/xcode-fails-spm-works/test.py index 4beed91f233b..298fd2726d0b 100644 --- a/swift/ql/integration-tests/autobuilder/xcode-fails-spm-works/test.py +++ b/swift/ql/integration-tests/autobuilder/xcode-fails-spm-works/test.py @@ -3,7 +3,6 @@ @runs_on.macos -@pytest.mark.ql_test("DB-CHECK", xfail=not runs_on.macos_26) -@pytest.mark.ql_test("*", expected=f"{'.macos_26' if runs_on.macos_26 else ''}.expected") +@pytest.mark.ql_test("DB-CHECK", xfail=True) def test(codeql, swift): codeql.database.create() From 1fb4f9d20850339ddf071df498f3442e9c0c4414 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 19 Jun 2026 14:53:35 +0000 Subject: [PATCH 08/60] yeast: Move schema and YAML loader into yeast-schema crate For type checking rules, we need to be able to load schemas (so we know what to check against). However, since we can't have yeast-macros depending on yeast (where the schema-handling code currently lives) as this would introduce a circular dependency, we instead split the schema-related code into its own yeast-schema crate. --- Cargo.lock | 10 + Cargo.toml | 1 + .../tree_sitter_extractors_deps/defs.bzl | 31 + shared/yeast-schema/BUILD.bazel | 12 + shared/yeast-schema/Cargo.toml | 9 + shared/yeast-schema/src/lib.rs | 33 + shared/yeast-schema/src/node_types_yaml.rs | 762 +++++++++++++++++ shared/yeast-schema/src/schema.rs | 340 ++++++++ shared/yeast/BUILD.bazel | 4 +- shared/yeast/Cargo.toml | 1 + shared/yeast/src/lib.rs | 17 +- shared/yeast/src/node_types_yaml.rs | 777 +----------------- shared/yeast/src/schema.rs | 325 ++------ 13 files changed, 1275 insertions(+), 1047 deletions(-) create mode 100644 shared/yeast-schema/BUILD.bazel create mode 100644 shared/yeast-schema/Cargo.toml create mode 100644 shared/yeast-schema/src/lib.rs create mode 100644 shared/yeast-schema/src/node_types_yaml.rs create mode 100644 shared/yeast-schema/src/schema.rs diff --git a/Cargo.lock b/Cargo.lock index 4fab55a6444f..76043ec0a439 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3724,6 +3724,7 @@ dependencies = [ "tree-sitter-python", "tree-sitter-ruby", "yeast-macros", + "yeast-schema", ] [[package]] @@ -3735,6 +3736,15 @@ dependencies = [ "syn", ] +[[package]] +name = "yeast-schema" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "serde_yaml", +] + [[package]] name = "yoke" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 62eb2e7e920c..9c15b486062b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "shared/tree-sitter-extractor", "shared/yeast", "shared/yeast-macros", + "shared/yeast-schema", "ruby/extractor", "unified/extractor", "unified/extractor/tree-sitter-swift", diff --git a/misc/bazel/3rdparty/tree_sitter_extractors_deps/defs.bzl b/misc/bazel/3rdparty/tree_sitter_extractors_deps/defs.bzl index 11842460638f..7fbdfc4bbd4b 100644 --- a/misc/bazel/3rdparty/tree_sitter_extractors_deps/defs.bzl +++ b/misc/bazel/3rdparty/tree_sitter_extractors_deps/defs.bzl @@ -403,6 +403,13 @@ _NORMAL_DEPENDENCIES = { "syn": Label("@vendor_ts__syn-2.0.106//:syn"), }, }, + "shared/yeast-schema": { + _COMMON_CONDITION: { + "serde": Label("@vendor_ts__serde-1.0.228//:serde"), + "serde_json": Label("@vendor_ts__serde_json-1.0.145//:serde_json"), + "serde_yaml": Label("@vendor_ts__serde_yaml-0.9.34-deprecated//:serde_yaml"), + }, + }, "unified/extractor": { _COMMON_CONDITION: { "clap": Label("@vendor_ts__clap-4.5.48//:clap"), @@ -456,6 +463,10 @@ _NORMAL_ALIASES = { _COMMON_CONDITION: { }, }, + "shared/yeast-schema": { + _COMMON_CONDITION: { + }, + }, "unified/extractor": { _COMMON_CONDITION: { }, @@ -488,6 +499,8 @@ _NORMAL_DEV_DEPENDENCIES = { }, "shared/yeast-macros": { }, + "shared/yeast-schema": { + }, "unified/extractor": { }, "unified/extractor/tree-sitter-swift": { @@ -513,6 +526,8 @@ _NORMAL_DEV_ALIASES = { }, "shared/yeast-macros": { }, + "shared/yeast-schema": { + }, "unified/extractor": { }, "unified/extractor/tree-sitter-swift": { @@ -536,6 +551,8 @@ _PROC_MACRO_DEPENDENCIES = { }, "shared/yeast-macros": { }, + "shared/yeast-schema": { + }, "unified/extractor": { }, "unified/extractor/tree-sitter-swift": { @@ -559,6 +576,8 @@ _PROC_MACRO_ALIASES = { }, "shared/yeast-macros": { }, + "shared/yeast-schema": { + }, "unified/extractor": { }, "unified/extractor/tree-sitter-swift": { @@ -582,6 +601,8 @@ _PROC_MACRO_DEV_DEPENDENCIES = { }, "shared/yeast-macros": { }, + "shared/yeast-schema": { + }, "unified/extractor": { }, "unified/extractor/tree-sitter-swift": { @@ -607,6 +628,8 @@ _PROC_MACRO_DEV_ALIASES = { }, "shared/yeast-macros": { }, + "shared/yeast-schema": { + }, "unified/extractor": { }, "unified/extractor/tree-sitter-swift": { @@ -630,6 +653,8 @@ _BUILD_DEPENDENCIES = { }, "shared/yeast-macros": { }, + "shared/yeast-schema": { + }, "unified/extractor": { }, "unified/extractor/tree-sitter-swift": { @@ -657,6 +682,8 @@ _BUILD_ALIASES = { }, "shared/yeast-macros": { }, + "shared/yeast-schema": { + }, "unified/extractor": { }, "unified/extractor/tree-sitter-swift": { @@ -682,6 +709,8 @@ _BUILD_PROC_MACRO_DEPENDENCIES = { }, "shared/yeast-macros": { }, + "shared/yeast-schema": { + }, "unified/extractor": { }, "unified/extractor/tree-sitter-swift": { @@ -705,6 +734,8 @@ _BUILD_PROC_MACRO_ALIASES = { }, "shared/yeast-macros": { }, + "shared/yeast-schema": { + }, "unified/extractor": { }, "unified/extractor/tree-sitter-swift": { diff --git a/shared/yeast-schema/BUILD.bazel b/shared/yeast-schema/BUILD.bazel new file mode 100644 index 000000000000..85f008a1aa67 --- /dev/null +++ b/shared/yeast-schema/BUILD.bazel @@ -0,0 +1,12 @@ +load("@rules_rust//rust:defs.bzl", "rust_library") +load("//misc/bazel/3rdparty/tree_sitter_extractors_deps:defs.bzl", "aliases", "all_crate_deps") + +exports_files(["Cargo.toml"]) + +rust_library( + name = "yeast-schema", + srcs = glob(["src/**/*.rs"]), + aliases = aliases(), + visibility = ["//visibility:public"], + deps = all_crate_deps(), +) diff --git a/shared/yeast-schema/Cargo.toml b/shared/yeast-schema/Cargo.toml new file mode 100644 index 000000000000..4cf534d4f0ce --- /dev/null +++ b/shared/yeast-schema/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "yeast-schema" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_yaml = "0.9" diff --git a/shared/yeast-schema/src/lib.rs b/shared/yeast-schema/src/lib.rs new file mode 100644 index 000000000000..8e15571c3558 --- /dev/null +++ b/shared/yeast-schema/src/lib.rs @@ -0,0 +1,33 @@ +//! Schema definitions and YAML/JSON node-types loaders for YEAST. +//! +//! This crate carries the parts of the YEAST framework that don't need +//! `tree-sitter`: the [`schema::Schema`] type and its associated +//! [`schema::NodeType`] / [`schema::FieldCardinality`] helpers, plus the +//! YAML and JSON conversion helpers in [`node_types_yaml`]. +//! +//! It exists so that both the runtime crate (`yeast`) and the +//! compile-time `rules!` proc macro (`yeast-macros`) can build against a +//! single source of truth without dragging tree-sitter (a heavy C-backed +//! dep) into the proc-macro toolchain. +//! +//! Tree-sitter-aware adapters — building a `Schema` from a +//! `tree_sitter::Language`, or loading a YAML schema on top of one — +//! live in `yeast::schema` and `yeast::node_types_yaml` respectively. + +pub mod node_types_yaml; +pub mod schema; + +/// Field IDs are stable `u16`s, matching tree-sitter's representation so a +/// schema built from a tree-sitter language can preserve the language's +/// existing IDs. +pub type FieldId = u16; + +/// Kind IDs are stable `u16`s. Like `FieldId`, this matches tree-sitter's +/// representation. +pub type KindId = u16; + +/// Sentinel field id used to mean "the implicit unfielded slot" (what the +/// tree-sitter docs call `children` and what YEAST surfaces in queries as +/// the bare `child:` field). Reserved to avoid clashing with real field +/// IDs allocated by `Schema::register_field`. +pub const CHILD_FIELD: u16 = u16::MAX; diff --git a/shared/yeast-schema/src/node_types_yaml.rs b/shared/yeast-schema/src/node_types_yaml.rs new file mode 100644 index 000000000000..5f6a3906f7cb --- /dev/null +++ b/shared/yeast-schema/src/node_types_yaml.rs @@ -0,0 +1,762 @@ +/// Converts a YAML node-types file to the tree-sitter `node-types.json` format. +/// +/// # YAML format +/// +/// ```yaml +/// supertypes: +/// _expression: +/// - assignment +/// - binary +/// +/// named: +/// assignment: +/// left: _lhs +/// right: _expression +/// identifier: +/// +/// unnamed: +/// - "+" +/// - "end" +/// ``` +/// +/// See the crate-level docs for the full format specification. +use std::collections::{BTreeMap, BTreeSet}; +use std::fmt::Write; + +use crate::CHILD_FIELD; +use serde::Deserialize; +use serde_json::json; + +/// Top-level YAML structure. +#[derive(Deserialize, Default)] +struct YamlNodeTypes { + #[serde(default)] + supertypes: BTreeMap>, + #[serde(default)] + named: BTreeMap>>, + #[serde(default)] + unnamed: Vec, +} + +/// A reference to a node type. Can be: +/// - a plain string (resolved by looking up named vs unnamed) +/// - a map `{unnamed: "name"}` to force unnamed interpretation +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged)] +enum TypeRef { + Name(String), + Explicit { unnamed: String }, +} + +/// A field value: either a single type ref or a list of them. +#[derive(Deserialize, Debug, Clone)] +#[serde(untagged)] +enum TypeRefOrList { + Single(TypeRef), + List(Vec), +} + +impl TypeRefOrList { + fn into_vec(self) -> Vec { + match self { + TypeRefOrList::Single(t) => vec![t], + TypeRefOrList::List(v) => v, + } + } +} + +/// Parsed field name: base name + multiplicity markers. +struct FieldSpec { + name: Option, // None for $children + multiple: bool, + required: bool, +} + +fn parse_field_name(raw: &str) -> FieldSpec { + let is_children = + raw == "$children" || raw == "$children?" || raw == "$children*" || raw == "$children+"; + + let suffix = raw.chars().last().filter(|c| matches!(c, '?' | '*' | '+')); + + let (multiple, required) = match suffix { + Some('?') => (false, false), + Some('*') => (true, false), + Some('+') => (true, true), + _ => (false, true), // bare field name = required, single + }; + + let name = if is_children { + None + } else { + let base = raw.trim_end_matches(['?', '*', '+']); + Some(base.to_string()) + }; + + FieldSpec { + name, + multiple, + required, + } +} + +/// Resolve a TypeRef to a (type, named) pair, given the sets of known named +/// and unnamed types. +fn resolve_type_ref_pair( + type_ref: &TypeRef, + named_types: &BTreeSet, + unnamed_types: &BTreeSet, +) -> (String, bool) { + match type_ref { + TypeRef::Explicit { unnamed } => (unnamed.clone(), false), + TypeRef::Name(name) => { + let is_named = named_types.contains(name); + let is_unnamed = unnamed_types.contains(name); + if is_named && is_unnamed { + (name.clone(), true) + } else if is_unnamed { + (name.clone(), false) + } else { + (name.clone(), true) + } + } + } +} + +/// Resolve a TypeRef to a {type, named} JSON record, given the sets of known named +/// and unnamed types. +fn resolve_type_ref( + type_ref: &TypeRef, + named_types: &BTreeSet, + unnamed_types: &BTreeSet, +) -> serde_json::Value { + let (kind, named) = resolve_type_ref_pair(type_ref, named_types, unnamed_types); + json!({"type": kind, "named": named}) +} + +/// Convert YAML string to node-types JSON string. +pub fn convert(yaml_input: &str) -> Result { + let yaml: YamlNodeTypes = + serde_yaml::from_str(yaml_input).map_err(|e| format!("Failed to parse YAML: {e}"))?; + + // Build the sets of known named and unnamed types for resolution. + let mut named_types = BTreeSet::new(); + for name in yaml.supertypes.keys() { + named_types.insert(name.clone()); + } + for name in yaml.named.keys() { + named_types.insert(name.clone()); + } + let unnamed_types: BTreeSet = yaml.unnamed.iter().cloned().collect(); + + let mut output = Vec::new(); + + // 1. Supertypes + for (name, members) in &yaml.supertypes { + let subtypes: Vec<_> = members + .iter() + .map(|m| resolve_type_ref(m, &named_types, &unnamed_types)) + .collect(); + output.push(json!({ + "type": name, + "named": true, + "subtypes": subtypes, + })); + } + + // 2. Named nodes + for (name, fields_opt) in &yaml.named { + let fields_map = match fields_opt { + None => { + // Leaf token: no fields, no children, no subtypes + output.push(json!({ + "type": name, + "named": true, + "fields": {}, + })); + continue; + } + Some(m) if m.is_empty() => { + output.push(json!({ + "type": name, + "named": true, + "fields": {}, + })); + continue; + } + Some(m) => m, + }; + + let mut json_fields = serde_json::Map::new(); + let mut json_children: Option = None; + + for (raw_field_name, type_refs) in fields_map { + let spec = parse_field_name(raw_field_name); + let types: Vec<_> = type_refs + .clone() + .into_vec() + .iter() + .map(|t| resolve_type_ref(t, &named_types, &unnamed_types)) + .collect(); + + // Cloning to make the borrow checker happy + let field_info = json!({ + "multiple": spec.multiple, + "required": spec.required, + "types": types, + }); + + if spec.name.is_none() { + // $children + json_children = Some(field_info); + } else { + json_fields.insert(spec.name.unwrap(), field_info); + } + } + + let mut entry = json!({ + "type": name, + "named": true, + "fields": json_fields, + }); + + if let Some(children) = json_children { + entry + .as_object_mut() + .unwrap() + .insert("children".to_string(), children); + } + + output.push(entry); + } + + // 3. Unnamed tokens + for name in &yaml.unnamed { + output.push(json!({ + "type": name, + "named": false, + })); + } + + serde_json::to_string_pretty(&output).map_err(|e| format!("Failed to serialize JSON: {e}")) +} + +/// Apply YAML node-type definitions to a mutable Schema. +/// Registers all types, fields, and allowed types from the YAML into the +/// schema. Public so callers can layer YAML node-types onto a Schema that +/// already has fields/kinds preregistered from another source (e.g. a +/// tree-sitter language). +pub fn extend_schema_from_yaml( + schema: &mut crate::schema::Schema, + yaml_input: &str, +) -> Result<(), String> { + let yaml: YamlNodeTypes = + serde_yaml::from_str(yaml_input).map_err(|e| format!("Failed to parse YAML: {e}"))?; + apply_yaml_to_schema(&yaml, schema); + Ok(()) +} + +fn apply_yaml_to_schema( + yaml: &YamlNodeTypes, + schema: &mut crate::schema::Schema, +) { + // Register all supertypes as node kinds + for name in yaml.supertypes.keys() { + schema.register_kind(name); + } + + // Register named node kinds and their fields + for (name, fields_opt) in &yaml.named { + schema.register_kind(name); + if let Some(fields) = fields_opt { + for raw_field_name in fields.keys() { + let spec = parse_field_name(raw_field_name); + if let Some(field_name) = &spec.name { + schema.register_field(field_name); + } + } + } + } + + // Register unnamed tokens as node kinds + for name in &yaml.unnamed { + schema.register_unnamed_kind(name); + } + + let mut named_types = BTreeSet::new(); + for name in yaml.supertypes.keys() { + named_types.insert(name.clone()); + } + for name in yaml.named.keys() { + named_types.insert(name.clone()); + } + let unnamed_types: BTreeSet = yaml.unnamed.iter().cloned().collect(); + + for (supertype, members) in &yaml.supertypes { + let node_types = members + .iter() + .map(|m| { + let (kind, named) = resolve_type_ref_pair(m, &named_types, &unnamed_types); + crate::schema::NodeType { kind, named } + }) + .collect(); + schema.set_supertype_members(supertype, node_types); + } + + // Register allowed field child types for type checking. + for (parent_kind, fields_opt) in &yaml.named { + let Some(fields) = fields_opt else { + continue; + }; + + for (raw_field_name, type_refs) in fields { + let spec = parse_field_name(raw_field_name); + let field_id = match &spec.name { + Some(name) => schema.register_field(name), + None => CHILD_FIELD, + }; + + let mut node_types = type_refs + .clone() + .into_vec() + .into_iter() + .map(|type_ref| { + let (kind, named) = resolve_type_ref_pair(&type_ref, &named_types, &unnamed_types); + crate::schema::NodeType { kind, named } + }) + .collect::>(); + node_types.sort_by(|a, b| a.kind.cmp(&b.kind).then(a.named.cmp(&b.named))); + node_types.dedup_by(|a, b| a.kind == b.kind && a.named == b.named); + schema.set_field_types(parent_kind, field_id, node_types); + schema.set_field_cardinality( + parent_kind, + field_id, + crate::schema::FieldCardinality { + multiple: spec.multiple, + required: spec.required, + }, + ); + } + } +} + +pub fn schema_from_yaml(yaml_input: &str) -> Result { + let mut schema = crate::schema::Schema::new(); + extend_schema_from_yaml(&mut schema, yaml_input)?; + Ok(schema) +} + +// --------------------------------------------------------------------------- +// JSON → YAML conversion +// --------------------------------------------------------------------------- + +/// JSON node-types structures (mirrors tree-sitter's format). +#[derive(Deserialize)] +struct JsonNodeInfo { + #[serde(rename = "type")] + kind: String, + named: bool, + #[serde(default)] + fields: BTreeMap, + children: Option, + #[serde(default)] + subtypes: Vec, +} + +#[derive(Deserialize)] +struct JsonNodeType { + #[serde(rename = "type")] + kind: String, + named: bool, +} + +#[derive(Deserialize)] +struct JsonFieldInfo { + multiple: bool, + required: bool, + types: Vec, +} + +/// Convert a tree-sitter node-types.json string to the YAML format. +pub fn convert_from_json(json_input: &str) -> Result { + let nodes: Vec = + serde_json::from_str(json_input).map_err(|e| format!("Failed to parse JSON: {e}"))?; + + // Collect all named and unnamed types for disambiguation decisions. + let mut all_named: BTreeSet = BTreeSet::new(); + let mut all_unnamed: BTreeSet = BTreeSet::new(); + for node in &nodes { + if node.named { + all_named.insert(node.kind.clone()); + } else { + all_unnamed.insert(node.kind.clone()); + } + } + + let mut supertypes: BTreeMap> = BTreeMap::new(); + let mut named: BTreeMap>> = BTreeMap::new(); + let mut unnamed: Vec = Vec::new(); + + for node in nodes { + if !node.named { + unnamed.push(node.kind); + continue; + } + + if !node.subtypes.is_empty() { + supertypes.insert(node.kind, node.subtypes); + continue; + } + + if node.fields.is_empty() && node.children.is_none() { + // Leaf token + named.insert(node.kind, None); + } else { + let mut fields = BTreeMap::new(); + for (name, info) in node.fields { + fields.insert(name, info); + } + if let Some(children) = node.children { + fields.insert("$children".to_string(), children); + } + named.insert(node.kind, Some(fields)); + } + } + + // Now emit YAML + let mut out = String::new(); + + // Supertypes + if !supertypes.is_empty() { + writeln!(out, "supertypes:").unwrap(); + for (name, members) in &supertypes { + writeln!(out, " {name}:").unwrap(); + for member in members { + let ref_str = format_type_ref(&member.kind, member.named, &all_named, &all_unnamed); + writeln!(out, " - {ref_str}").unwrap(); + } + } + writeln!(out).unwrap(); + } + + // Named + if !named.is_empty() { + writeln!(out, "named:").unwrap(); + for (name, fields_opt) in &named { + match fields_opt { + None => { + writeln!(out, " {name}:").unwrap(); + } + Some(fields) => { + writeln!(out, " {name}:").unwrap(); + for (field_name, info) in fields { + let suffix = field_suffix(info.multiple, info.required); + let yaml_name = if field_name == "$children" { + format!("$children{suffix}") + } else { + format!("{field_name}{suffix}") + }; + + let type_refs: Vec = info + .types + .iter() + .map(|t| format_type_ref(&t.kind, t.named, &all_named, &all_unnamed)) + .collect(); + + if type_refs.len() == 1 { + writeln!(out, " {yaml_name}: {}", type_refs[0]).unwrap(); + } else { + let list = type_refs + .iter() + .map(|s| s.as_str()) + .collect::>() + .join(", "); + writeln!(out, " {yaml_name}: [{list}]").unwrap(); + } + } + } + } + } + writeln!(out).unwrap(); + } + + // Unnamed + if !unnamed.is_empty() { + writeln!(out, "unnamed:").unwrap(); + for name in &unnamed { + writeln!(out, " - {}", force_quote(name)).unwrap(); + } + } + + Ok(out) +} + +fn field_suffix(multiple: bool, required: bool) -> &'static str { + match (multiple, required) { + (false, true) => "", + (false, false) => "?", + (true, true) => "+", + (true, false) => "*", + } +} + +/// Format a type reference for YAML output. Uses the disambiguation rule: +/// plain string if unambiguous, `{unnamed: name}` if the name exists as both +/// named and unnamed and we need the unnamed interpretation. +fn format_type_ref( + kind: &str, + named: bool, + all_named: &BTreeSet, + _all_unnamed: &BTreeSet, +) -> String { + if named { + quote_yaml(kind) + } else { + let is_also_named = all_named.contains(kind); + if is_also_named { + format!("{{unnamed: {}}}", force_quote(kind)) + } else { + force_quote(kind) + } + } +} + +/// Always wrap in double quotes. Used for unnamed node references so they're +/// visually distinct from named ones — YAML treats both forms as equivalent strings. +fn force_quote(s: &str) -> String { + format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")) +} + +/// Quote a YAML string value if it contains special characters or could be +/// misinterpreted. +fn quote_yaml(s: &str) -> String { + let needs_quoting = s.is_empty() + || s.contains(|c: char| { + matches!( + c, + ':' | '{' + | '}' + | '[' + | ']' + | ',' + | '&' + | '*' + | '#' + | '?' + | '|' + | '-' + | '<' + | '>' + | '=' + | '!' + | '%' + | '@' + | '`' + | '"' + | '\'' + ) + }) + || s.starts_with(' ') + || s.ends_with(' ') + || s == "true" + || s == "false" + || s == "null" + || s == "yes" + || s == "no" + || s.parse::().is_ok(); + + if needs_quoting { + format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")) + } else { + s.to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_basic_conversion() { + let yaml = r#" +supertypes: + _expression: + - assignment + - binary + +named: + assignment: + left: _lhs + right: _expression + binary: + left: [_expression, _simple_numeric] + operator: ["!=", "+"] + right: _expression + argument_list: + $children*: [_expression, block_argument] + identifier: + +unnamed: + - "!=" + - "+" + - "end" +"#; + + let json_str = convert(yaml).unwrap(); + let result: Vec = serde_json::from_str(&json_str).unwrap(); + + // Check supertype + let expr = &result[0]; + assert_eq!(expr["type"], "_expression"); + assert_eq!(expr["named"], true); + assert_eq!(expr["subtypes"].as_array().unwrap().len(), 2); + + // Check assignment + let assign = result.iter().find(|n| n["type"] == "assignment").unwrap(); + assert_eq!(assign["fields"]["left"]["required"], true); + assert_eq!(assign["fields"]["left"]["multiple"], false); + assert_eq!(assign["fields"]["left"]["types"][0]["type"], "_lhs"); + assert_eq!(assign["fields"]["left"]["types"][0]["named"], true); + + // Check binary.operator — "!=" and "+" should resolve to unnamed + let binary = result.iter().find(|n| n["type"] == "binary").unwrap(); + let op_types = binary["fields"]["operator"]["types"].as_array().unwrap(); + assert_eq!(op_types[0]["type"], "!="); + assert_eq!(op_types[0]["named"], false); + assert_eq!(op_types[1]["type"], "+"); + assert_eq!(op_types[1]["named"], false); + + // Check argument_list has children, not a field + let arg_list = result + .iter() + .find(|n| n["type"] == "argument_list") + .unwrap(); + assert!(arg_list.get("children").is_some()); + assert_eq!(arg_list["children"]["multiple"], true); + assert_eq!(arg_list["children"]["required"], false); + + // Check identifier is a leaf + let ident = result.iter().find(|n| n["type"] == "identifier").unwrap(); + assert_eq!(ident["fields"].as_object().unwrap().len(), 0); + + // Check unnamed tokens + let end = result.iter().find(|n| n["type"] == "end").unwrap(); + assert_eq!(end["named"], false); + } + + #[test] + fn test_explicit_unnamed_disambiguation() { + let yaml = r#" +named: + foo: + field: [{unnamed: bar}] + +unnamed: + - bar +"#; + + let json_str = convert(yaml).unwrap(); + let result: Vec = serde_json::from_str(&json_str).unwrap(); + let foo = result.iter().find(|n| n["type"] == "foo").unwrap(); + assert_eq!(foo["fields"]["field"]["types"][0]["named"], false); + } + + #[test] + fn test_field_suffixes() { + let yaml = r#" +named: + test_node: + required_single: foo + optional_single?: foo + required_multiple+: foo + optional_multiple*: foo +"#; + + let json_str = convert(yaml).unwrap(); + let result: Vec = serde_json::from_str(&json_str).unwrap(); + let node = result.iter().find(|n| n["type"] == "test_node").unwrap(); + let fields = node["fields"].as_object().unwrap(); + + assert_eq!(fields["required_single"]["required"], true); + assert_eq!(fields["required_single"]["multiple"], false); + + assert_eq!(fields["optional_single"]["required"], false); + assert_eq!(fields["optional_single"]["multiple"], false); + + assert_eq!(fields["required_multiple"]["required"], true); + assert_eq!(fields["required_multiple"]["multiple"], true); + + assert_eq!(fields["optional_multiple"]["required"], false); + assert_eq!(fields["optional_multiple"]["multiple"], true); + } + + #[test] + fn test_json_to_yaml() { + let json = r#"[ + {"type": "_expression", "named": true, "subtypes": [ + {"type": "assignment", "named": true}, + {"type": "identifier", "named": true} + ]}, + {"type": "assignment", "named": true, "fields": { + "left": {"multiple": false, "required": true, "types": [ + {"type": "_expression", "named": true} + ]}, + "right": {"multiple": false, "required": false, "types": [ + {"type": "_expression", "named": true} + ]} + }, "children": { + "multiple": true, "required": false, "types": [ + {"type": "identifier", "named": true} + ] + }}, + {"type": "identifier", "named": true, "fields": {}}, + {"type": "=", "named": false}, + {"type": "end", "named": false} + ]"#; + + let yaml = convert_from_json(json).unwrap(); + + // Verify key structures are present + assert!(yaml.contains("supertypes:")); + assert!(yaml.contains("_expression:")); + assert!(yaml.contains("named:")); + assert!(yaml.contains("assignment:")); + assert!(yaml.contains("left:")); + assert!(yaml.contains("right?:")); + assert!(yaml.contains("$children*:")); + assert!(yaml.contains("identifier:")); + assert!(yaml.contains("unnamed:")); + assert!(yaml.contains("\"=\"")); + assert!(yaml.contains("end")); + } + + #[test] + fn test_round_trip() { + let yaml_input = r#" +supertypes: + _expression: + - assignment + - identifier + +named: + assignment: + left: _expression + right?: _expression + $children*: identifier + identifier: + +unnamed: + - "=" + - end +"#; + + // YAML → JSON → YAML + let json = convert(yaml_input).unwrap(); + let yaml_output = convert_from_json(&json).unwrap(); + // YAML → JSON again (should be identical) + let json2 = convert(&yaml_output).unwrap(); + + let v1: serde_json::Value = serde_json::from_str(&json).unwrap(); + let v2: serde_json::Value = serde_json::from_str(&json2).unwrap(); + assert_eq!(v1, v2); + } +} diff --git a/shared/yeast-schema/src/schema.rs b/shared/yeast-schema/src/schema.rs new file mode 100644 index 000000000000..4acd14377a4d --- /dev/null +++ b/shared/yeast-schema/src/schema.rs @@ -0,0 +1,340 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use crate::{FieldId, KindId, CHILD_FIELD}; + +#[derive(Clone, Debug)] +pub struct NodeType { + pub kind: String, + pub named: bool, +} + +/// Multiplicity/optionality of a field declaration. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct FieldCardinality { + /// Whether the field may hold more than one child. + pub multiple: bool, + /// Whether at least one child must be present. + pub required: bool, +} + +/// A schema defining node kinds and field names for the output AST. +/// Built from a node-types.yml file, independent of any tree-sitter grammar. +/// +/// # Memory management +/// +/// `register_field`/`register_kind`/`register_unnamed_kind` (and their +/// `_with_id` siblings) use `Box::leak` to obtain `&'static str` names. This +/// is intentional: the `&'static str` names appear pervasively in `Node`, +/// `AstCursor`, query patterns, and the extractor's TRAP output, where +/// adding a lifetime would propagate widely. +/// +/// The leak is bounded by the number of distinct kind/field names registered. +/// Schemas are expected to be constructed once per process (e.g. at extractor +/// startup) and reused. Repeated construction in long-running processes will +/// leak memory unboundedly and should be avoided. +#[derive(Clone)] +pub struct Schema { + field_ids: BTreeMap, + field_names: BTreeMap, + next_field_id: FieldId, + kind_ids: BTreeMap, + unnamed_kind_ids: BTreeMap, + kind_names: BTreeMap, + next_kind_id: KindId, + field_types: BTreeMap<(String, FieldId), Vec>, + field_cardinalities: BTreeMap<(String, FieldId), FieldCardinality>, + supertypes: BTreeMap>, +} + +impl Default for Schema { + fn default() -> Self { + Self::new() + } +} + +impl Schema { + pub fn new() -> Self { + Self { + field_ids: BTreeMap::new(), + field_names: BTreeMap::new(), + next_field_id: 1, // 0 is reserved + kind_ids: BTreeMap::new(), + unnamed_kind_ids: BTreeMap::new(), + kind_names: BTreeMap::new(), + next_kind_id: 1, // 0 is reserved + field_types: BTreeMap::new(), + field_cardinalities: BTreeMap::new(), + supertypes: BTreeMap::new(), + } + } + + /// Register a field name, returning its ID. + /// If already registered, returns the existing ID. + pub fn register_field(&mut self, name: &str) -> FieldId { + if name == "child" { + return CHILD_FIELD; + } + if let Some(&id) = self.field_ids.get(name) { + return id; + } + let id = self.next_field_id; + assert!(id < CHILD_FIELD, "too many fields"); + self.next_field_id += 1; + let leaked: &'static str = Box::leak(name.to_string().into_boxed_str()); + self.field_ids.insert(name.to_string(), id); + self.field_names.insert(id, leaked); + id + } + + /// Register a field name with a specific ID, e.g. when importing IDs + /// from an external source like a tree-sitter language. If the name is + /// already registered (with any ID), nothing is changed and the + /// existing ID is returned. + pub fn register_field_with_id(&mut self, name: &str, id: FieldId) -> FieldId { + if name == "child" { + return CHILD_FIELD; + } + if let Some(&existing) = self.field_ids.get(name) { + return existing; + } + assert!(id < CHILD_FIELD, "too many fields"); + let leaked: &'static str = Box::leak(name.to_string().into_boxed_str()); + self.field_ids.insert(name.to_string(), id); + self.field_names.insert(id, leaked); + if id >= self.next_field_id { + self.next_field_id = id + 1; + } + id + } + + /// Register a named node kind name, returning its ID. + /// If already registered, returns the existing ID. + pub fn register_kind(&mut self, name: &str) -> KindId { + if let Some(&id) = self.kind_ids.get(name) { + return id; + } + let id = self.next_kind_id; + self.next_kind_id += 1; + let leaked: &'static str = Box::leak(name.to_string().into_boxed_str()); + self.kind_ids.insert(name.to_string(), id); + self.kind_names.insert(id, leaked); + id + } + + /// Register a named node kind with a specific ID, e.g. when importing + /// IDs from a tree-sitter language. If the name is already registered, + /// nothing is changed and the existing ID is returned. + pub fn register_kind_with_id(&mut self, name: &str, id: KindId) -> KindId { + if let Some(&existing) = self.kind_ids.get(name) { + return existing; + } + let leaked: &'static str = Box::leak(name.to_string().into_boxed_str()); + self.kind_ids.insert(name.to_string(), id); + self.kind_names.insert(id, leaked); + if id >= self.next_kind_id { + self.next_kind_id = id + 1; + } + id + } + + /// Register an unnamed token kind (e.g. `"="`, `"end"`), returning its ID. + /// If already registered, returns the existing ID. + pub fn register_unnamed_kind(&mut self, name: &str) -> KindId { + if let Some(&id) = self.unnamed_kind_ids.get(name) { + return id; + } + let id = self.next_kind_id; + self.next_kind_id += 1; + let leaked: &'static str = Box::leak(name.to_string().into_boxed_str()); + self.unnamed_kind_ids.insert(name.to_string(), id); + self.kind_names.insert(id, leaked); + id + } + + /// Register an unnamed token kind with a specific ID. If the name is + /// already registered as unnamed, nothing is changed and the existing + /// ID is returned. + pub fn register_unnamed_kind_with_id(&mut self, name: &str, id: KindId) -> KindId { + if let Some(&existing) = self.unnamed_kind_ids.get(name) { + return existing; + } + let leaked: &'static str = Box::leak(name.to_string().into_boxed_str()); + self.unnamed_kind_ids.insert(name.to_string(), id); + self.kind_names.insert(id, leaked); + if id >= self.next_kind_id { + self.next_kind_id = id + 1; + } + id + } + + /// Track a name for a kind ID without registering it as named or + /// unnamed. Useful when importing tree-sitter ID tables that may + /// contain duplicate IDs across the named/unnamed split. + pub fn record_kind_name(&mut self, id: KindId, name: &'static str) { + self.kind_names.entry(id).or_insert(name); + if id >= self.next_kind_id { + self.next_kind_id = id + 1; + } + } + + pub fn field_id_for_name(&self, name: &str) -> Option { + if name == "child" { + return Some(CHILD_FIELD); + } + self.field_ids.get(name).copied() + } + + pub fn field_name_for_id(&self, id: FieldId) -> Option<&'static str> { + if id == CHILD_FIELD { + return Some("child"); + } + self.field_names.get(&id).copied() + } + + pub fn id_for_node_kind(&self, kind: &str) -> Option { + self.kind_ids.get(kind).copied() + } + + pub fn id_for_unnamed_node_kind(&self, kind: &str) -> Option { + self.unnamed_kind_ids.get(kind).copied() + } + + /// Has `kind` been registered as a named kind (concrete node or + /// supertype)? + pub fn has_named_kind(&self, kind: &str) -> bool { + self.id_for_node_kind(kind).is_some() + } + + /// Has `kind` been registered as an unnamed token kind? + pub fn has_unnamed_kind(&self, kind: &str) -> bool { + self.id_for_unnamed_node_kind(kind).is_some() + } + + /// Is `field_name` declared as a field on `parent_kind`? + /// `field_name == None` checks for the implicit unfielded slot + /// (`$children`/`CHILD_FIELD`). + pub fn has_field(&self, parent_kind: &str, field_name: Option<&str>) -> bool { + let field_id = match field_name { + Some(name) => match self.field_id_for_name(name) { + Some(id) => id, + None => return false, + }, + None => CHILD_FIELD, + }; + self.field_types(parent_kind, field_id).is_some() + } + + pub fn node_kind_for_id(&self, id: KindId) -> Option<&'static str> { + self.kind_names.get(&id).copied() + } + + pub fn set_field_types( + &mut self, + parent_kind: &str, + field_id: FieldId, + node_types: Vec, + ) { + self.field_types + .insert((parent_kind.to_string(), field_id), node_types); + } + + pub fn field_types( + &self, + parent_kind: &str, + field_id: FieldId, + ) -> Option<&Vec> { + self.field_types + .get(&(parent_kind.to_string(), field_id)) + } + + pub fn set_field_cardinality( + &mut self, + parent_kind: &str, + field_id: FieldId, + cardinality: FieldCardinality, + ) { + self.field_cardinalities + .insert((parent_kind.to_string(), field_id), cardinality); + } + + /// Returns the declared cardinality for a field, if known. + pub fn field_cardinality( + &self, + parent_kind: &str, + field_id: FieldId, + ) -> Option { + self.field_cardinalities + .get(&(parent_kind.to_string(), field_id)) + .copied() + } + + /// Returns an iterator over all `(field_id, field_name)` pairs that are + /// declared as required (`required: true`) for the given `parent_kind`. + pub fn required_fields_for_kind<'a>( + &'a self, + parent_kind: &'a str, + ) -> impl Iterator)> + 'a { + self.field_cardinalities + .iter() + .filter(move |((kind, _), card)| kind == parent_kind && card.required) + .map(move |((_, field_id), _)| { + let name = self.field_name_for_id(*field_id); + (*field_id, name) + }) + } + + pub fn set_supertype_members(&mut self, supertype: &str, node_types: Vec) { + self.supertypes.insert(supertype.to_string(), node_types); + } + + /// Returns the declared members of a supertype, if known. + pub fn supertype_members(&self, supertype: &str) -> Option<&Vec> { + self.supertypes.get(supertype) + } + + /// Is `kind` a known supertype (an abstract grouping)? + pub fn is_supertype(&self, kind: &str) -> bool { + self.supertypes.contains_key(kind) + } + + fn allows_node( + &self, + node_type: &NodeType, + node_kind: &str, + node_named: bool, + active: &mut BTreeSet, + ) -> bool { + if node_type.kind == node_kind && node_type.named == node_named { + return true; + } + + if !node_type.named { + return false; + } + + let Some(members) = self.supertypes.get(&node_type.kind) else { + return false; + }; + + if !active.insert(node_type.kind.clone()) { + return false; + } + + let matched = members + .iter() + .any(|member| self.allows_node(member, node_kind, node_named, active)); + active.remove(&node_type.kind); + matched + } + + pub fn node_matches_types( + &self, + node_kind: &str, + node_named: bool, + node_types: &[NodeType], + ) -> bool { + node_types.iter().any(|node_type| { + self.allows_node(node_type, node_kind, node_named, &mut BTreeSet::new()) + }) + } +} diff --git a/shared/yeast/BUILD.bazel b/shared/yeast/BUILD.bazel index fe0b01bb87bd..5217f20ec67d 100644 --- a/shared/yeast/BUILD.bazel +++ b/shared/yeast/BUILD.bazel @@ -14,5 +14,7 @@ rust_library( "//shared/yeast-macros", ], visibility = ["//visibility:public"], - deps = all_crate_deps(), + deps = all_crate_deps() + [ + "//shared/yeast-schema", + ], ) diff --git a/shared/yeast/Cargo.toml b/shared/yeast/Cargo.toml index 166887c324cf..518a0d1cefc2 100644 --- a/shared/yeast/Cargo.toml +++ b/shared/yeast/Cargo.toml @@ -10,6 +10,7 @@ serde_json = "1.0.108" serde_yaml = "0.9" tree-sitter = ">= 0.23.0" yeast-macros = { path = "../yeast-macros" } +yeast-schema = { path = "../yeast-schema" } tree-sitter-ruby = "0.23" tree-sitter-python = "0.23" diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index 2f6da2039d12..c4b448077267 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -57,8 +57,13 @@ impl IntoIterator for Id { } /// Field and Kind ids are provided by tree-sitter -type FieldId = u16; -type KindId = u16; +type FieldId = yeast_schema::FieldId; +type KindId = yeast_schema::KindId; + +/// Sentinel field id used to mean "the implicit unfielded slot". +/// Re-exported from `yeast-schema` so the runtime and the schema share a +/// single value. +pub use yeast_schema::CHILD_FIELD; /// Trait for values that can be appended to a field's id list inside a /// `tree!`/`trees!`/`rule!` template (in `{expr}` placeholders). @@ -157,8 +162,6 @@ impl YeastSourceRange for &T { } } -pub const CHILD_FIELD: u16 = u16::MAX; - #[derive(Debug)] pub struct AstCursor<'a> { ast: &'a Ast, @@ -304,7 +307,7 @@ impl std::fmt::Debug for Ast { impl Ast { /// Construct an AST from a TS tree pub fn from_tree(language: tree_sitter::Language, tree: &tree_sitter::Tree) -> Self { - let schema = schema::Schema::from_language(&language); + let schema = schema::from_language(&language); Self::from_tree_with_schema(schema, tree, &language) } @@ -1251,7 +1254,7 @@ impl DesugaringConfig { pub fn build_schema(&self, language: &tree_sitter::Language) -> Result { match self.output_node_types_yaml { Some(yaml) => node_types_yaml::schema_from_yaml_with_language(yaml, language), - None => Ok(schema::Schema::from_language(language)), + None => Ok(schema::from_language(language)), } } } @@ -1265,7 +1268,7 @@ pub struct Runner<'a, C = ()> { impl<'a, C> Runner<'a, C> { /// Create a runner using the input grammar's schema for output. pub fn new(language: tree_sitter::Language, phases: &'a [Phase]) -> Self { - let schema = schema::Schema::from_language(&language); + let schema = schema::from_language(&language); Self { language, schema, diff --git a/shared/yeast/src/node_types_yaml.rs b/shared/yeast/src/node_types_yaml.rs index f4d9f2a1c427..7beb4bb25bed 100644 --- a/shared/yeast/src/node_types_yaml.rs +++ b/shared/yeast/src/node_types_yaml.rs @@ -1,767 +1,22 @@ -/// Converts a YAML node-types file to the tree-sitter `node-types.json` format. -/// -/// # YAML format -/// -/// ```yaml -/// supertypes: -/// _expression: -/// - assignment -/// - binary -/// -/// named: -/// assignment: -/// left: _lhs -/// right: _expression -/// identifier: -/// -/// unnamed: -/// - "+" -/// - "end" -/// ``` -/// -/// See the crate-level docs for the full format specification. -use std::collections::{BTreeMap, BTreeSet}; -use std::fmt::Write; - -use crate::CHILD_FIELD; -use serde::Deserialize; -use serde_json::json; - -/// Top-level YAML structure. -#[derive(Deserialize, Default)] -struct YamlNodeTypes { - #[serde(default)] - supertypes: BTreeMap>, - #[serde(default)] - named: BTreeMap>>, - #[serde(default)] - unnamed: Vec, -} - -/// A reference to a node type. Can be: -/// - a plain string (resolved by looking up named vs unnamed) -/// - a map `{unnamed: "name"}` to force unnamed interpretation -#[derive(Deserialize, Debug, Clone)] -#[serde(untagged)] -enum TypeRef { - Name(String), - Explicit { unnamed: String }, -} - -/// A field value: either a single type ref or a list of them. -#[derive(Deserialize, Debug, Clone)] -#[serde(untagged)] -enum TypeRefOrList { - Single(TypeRef), - List(Vec), -} - -impl TypeRefOrList { - fn into_vec(self) -> Vec { - match self { - TypeRefOrList::Single(t) => vec![t], - TypeRefOrList::List(v) => v, - } - } -} - -/// Parsed field name: base name + multiplicity markers. -struct FieldSpec { - name: Option, // None for $children - multiple: bool, - required: bool, -} - -fn parse_field_name(raw: &str) -> FieldSpec { - let is_children = - raw == "$children" || raw == "$children?" || raw == "$children*" || raw == "$children+"; - - let suffix = raw.chars().last().filter(|c| matches!(c, '?' | '*' | '+')); - - let (multiple, required) = match suffix { - Some('?') => (false, false), - Some('*') => (true, false), - Some('+') => (true, true), - _ => (false, true), // bare field name = required, single - }; - - let name = if is_children { - None - } else { - let base = raw.trim_end_matches(['?', '*', '+']); - Some(base.to_string()) - }; - - FieldSpec { - name, - multiple, - required, - } -} - -/// Resolve a TypeRef to a (type, named) pair, given the sets of known named -/// and unnamed types. -fn resolve_type_ref_pair( - type_ref: &TypeRef, - named_types: &BTreeSet, - unnamed_types: &BTreeSet, -) -> (String, bool) { - match type_ref { - TypeRef::Explicit { unnamed } => (unnamed.clone(), false), - TypeRef::Name(name) => { - let is_named = named_types.contains(name); - let is_unnamed = unnamed_types.contains(name); - if is_named && is_unnamed { - (name.clone(), true) - } else if is_unnamed { - (name.clone(), false) - } else { - (name.clone(), true) - } - } - } -} - -/// Resolve a TypeRef to a {type, named} JSON record, given the sets of known named -/// and unnamed types. -fn resolve_type_ref( - type_ref: &TypeRef, - named_types: &BTreeSet, - unnamed_types: &BTreeSet, -) -> serde_json::Value { - let (kind, named) = resolve_type_ref_pair(type_ref, named_types, unnamed_types); - json!({"type": kind, "named": named}) -} - -/// Convert YAML string to node-types JSON string. -pub fn convert(yaml_input: &str) -> Result { - let yaml: YamlNodeTypes = - serde_yaml::from_str(yaml_input).map_err(|e| format!("Failed to parse YAML: {e}"))?; - - // Build the sets of known named and unnamed types for resolution. - let mut named_types = BTreeSet::new(); - for name in yaml.supertypes.keys() { - named_types.insert(name.clone()); - } - for name in yaml.named.keys() { - named_types.insert(name.clone()); - } - let unnamed_types: BTreeSet = yaml.unnamed.iter().cloned().collect(); - - let mut output = Vec::new(); - - // 1. Supertypes - for (name, members) in &yaml.supertypes { - let subtypes: Vec<_> = members - .iter() - .map(|m| resolve_type_ref(m, &named_types, &unnamed_types)) - .collect(); - output.push(json!({ - "type": name, - "named": true, - "subtypes": subtypes, - })); - } - - // 2. Named nodes - for (name, fields_opt) in &yaml.named { - let fields_map = match fields_opt { - None => { - // Leaf token: no fields, no children, no subtypes - output.push(json!({ - "type": name, - "named": true, - "fields": {}, - })); - continue; - } - Some(m) if m.is_empty() => { - output.push(json!({ - "type": name, - "named": true, - "fields": {}, - })); - continue; - } - Some(m) => m, - }; - - let mut json_fields = serde_json::Map::new(); - let mut json_children: Option = None; - - for (raw_field_name, type_refs) in fields_map { - let spec = parse_field_name(raw_field_name); - let types: Vec<_> = type_refs - .clone() - .into_vec() - .iter() - .map(|t| resolve_type_ref(t, &named_types, &unnamed_types)) - .collect(); - - // Cloning to make the borrow checker happy - let field_info = json!({ - "multiple": spec.multiple, - "required": spec.required, - "types": types, - }); - - if spec.name.is_none() { - // $children - json_children = Some(field_info); - } else { - json_fields.insert(spec.name.unwrap(), field_info); - } - } - - let mut entry = json!({ - "type": name, - "named": true, - "fields": json_fields, - }); - - if let Some(children) = json_children { - entry - .as_object_mut() - .unwrap() - .insert("children".to_string(), children); - } - - output.push(entry); - } - - // 3. Unnamed tokens - for name in &yaml.unnamed { - output.push(json!({ - "type": name, - "named": false, - })); - } - - serde_json::to_string_pretty(&output).map_err(|e| format!("Failed to serialize JSON: {e}")) -} - -/// Apply YAML node-type definitions to a mutable Schema. -/// Registers all types, fields, and allowed types from the YAML into the schema. -fn apply_yaml_to_schema(yaml: &YamlNodeTypes, schema: &mut crate::schema::Schema) { - // Register all supertypes as node kinds - for name in yaml.supertypes.keys() { - schema.register_kind(name); - } - - // Register named node kinds and their fields - for (name, fields_opt) in &yaml.named { - schema.register_kind(name); - if let Some(fields) = fields_opt { - for raw_field_name in fields.keys() { - let spec = parse_field_name(raw_field_name); - if let Some(field_name) = &spec.name { - schema.register_field(field_name); - } - } - } - } - - // Register unnamed tokens as node kinds - for name in &yaml.unnamed { - schema.register_unnamed_kind(name); - } - - let mut named_types = BTreeSet::new(); - for name in yaml.supertypes.keys() { - named_types.insert(name.clone()); - } - for name in yaml.named.keys() { - named_types.insert(name.clone()); - } - let unnamed_types: BTreeSet = yaml.unnamed.iter().cloned().collect(); - - for (supertype, members) in &yaml.supertypes { - let node_types = members - .iter() - .map(|m| { - let (kind, named) = resolve_type_ref_pair(m, &named_types, &unnamed_types); - crate::schema::NodeType { kind, named } - }) - .collect(); - schema.set_supertype_members(supertype, node_types); - } - - // Register allowed field child types for type checking. - for (parent_kind, fields_opt) in &yaml.named { - let Some(fields) = fields_opt else { - continue; - }; - - for (raw_field_name, type_refs) in fields { - let spec = parse_field_name(raw_field_name); - let field_id = match &spec.name { - Some(name) => schema.register_field(name), - None => CHILD_FIELD, - }; - - let mut node_types = type_refs - .clone() - .into_vec() - .into_iter() - .map(|type_ref| { - let (kind, named) = - resolve_type_ref_pair(&type_ref, &named_types, &unnamed_types); - crate::schema::NodeType { kind, named } - }) - .collect::>(); - node_types.sort_by(|a, b| a.kind.cmp(&b.kind).then(a.named.cmp(&b.named))); - node_types.dedup_by(|a, b| a.kind == b.kind && a.named == b.named); - schema.set_field_types(parent_kind, field_id, node_types); - schema.set_field_cardinality( - parent_kind, - field_id, - crate::schema::FieldCardinality { - multiple: spec.multiple, - required: spec.required, - }, - ); - } - } -} - -pub fn schema_from_yaml(yaml_input: &str) -> Result { - let yaml: YamlNodeTypes = - serde_yaml::from_str(yaml_input).map_err(|e| format!("Failed to parse YAML: {e}"))?; - - let mut schema = crate::schema::Schema::new(); - apply_yaml_to_schema(&yaml, &mut schema); - - Ok(schema) -} - -/// Build a Schema from a YAML string, extending a tree-sitter Language. -/// The Schema inherits all field/kind names from the Language, plus any -/// additional ones defined in the YAML. +//! YAML/JSON node-types loaders for YEAST. +//! +//! The pure YAML/JSON conversion routines live in [`yeast_schema::node_types_yaml`]. +//! This module re-exports them and adds the tree-sitter-aware adapter +//! [`schema_from_yaml_with_language`]. + +pub use yeast_schema::node_types_yaml::{ + convert, convert_from_json, extend_schema_from_yaml, schema_from_yaml, +}; + +/// Build a Schema from a YAML string, layered on top of a tree-sitter +/// `Language`. The Schema inherits all field/kind names from the language +/// (preserving the language's IDs), plus any additional ones defined in +/// the YAML. pub fn schema_from_yaml_with_language( yaml_input: &str, language: &tree_sitter::Language, ) -> Result { - let yaml: YamlNodeTypes = - serde_yaml::from_str(yaml_input).map_err(|e| format!("Failed to parse YAML: {e}"))?; - - let mut schema = crate::schema::Schema::from_language(language); - apply_yaml_to_schema(&yaml, &mut schema); - + let mut schema = crate::schema::from_language(language); + extend_schema_from_yaml(&mut schema, yaml_input)?; Ok(schema) } - -// --------------------------------------------------------------------------- -// JSON → YAML conversion -// --------------------------------------------------------------------------- - -/// JSON node-types structures (mirrors tree-sitter's format). -#[derive(Deserialize)] -struct JsonNodeInfo { - #[serde(rename = "type")] - kind: String, - named: bool, - #[serde(default)] - fields: BTreeMap, - children: Option, - #[serde(default)] - subtypes: Vec, -} - -#[derive(Deserialize)] -struct JsonNodeType { - #[serde(rename = "type")] - kind: String, - named: bool, -} - -#[derive(Deserialize)] -struct JsonFieldInfo { - multiple: bool, - required: bool, - types: Vec, -} - -/// Convert a tree-sitter node-types.json string to the YAML format. -pub fn convert_from_json(json_input: &str) -> Result { - let nodes: Vec = - serde_json::from_str(json_input).map_err(|e| format!("Failed to parse JSON: {e}"))?; - - // Collect all named and unnamed types for disambiguation decisions. - let mut all_named: BTreeSet = BTreeSet::new(); - let mut all_unnamed: BTreeSet = BTreeSet::new(); - for node in &nodes { - if node.named { - all_named.insert(node.kind.clone()); - } else { - all_unnamed.insert(node.kind.clone()); - } - } - - let mut supertypes: BTreeMap> = BTreeMap::new(); - let mut named: BTreeMap>> = BTreeMap::new(); - let mut unnamed: Vec = Vec::new(); - - for node in nodes { - if !node.named { - unnamed.push(node.kind); - continue; - } - - if !node.subtypes.is_empty() { - supertypes.insert(node.kind, node.subtypes); - continue; - } - - if node.fields.is_empty() && node.children.is_none() { - // Leaf token - named.insert(node.kind, None); - } else { - let mut fields = BTreeMap::new(); - for (name, info) in node.fields { - fields.insert(name, info); - } - if let Some(children) = node.children { - fields.insert("$children".to_string(), children); - } - named.insert(node.kind, Some(fields)); - } - } - - // Now emit YAML - let mut out = String::new(); - - // Supertypes - if !supertypes.is_empty() { - writeln!(out, "supertypes:").unwrap(); - for (name, members) in &supertypes { - writeln!(out, " {name}:").unwrap(); - for member in members { - let ref_str = format_type_ref(&member.kind, member.named, &all_named, &all_unnamed); - writeln!(out, " - {ref_str}").unwrap(); - } - } - writeln!(out).unwrap(); - } - - // Named - if !named.is_empty() { - writeln!(out, "named:").unwrap(); - for (name, fields_opt) in &named { - match fields_opt { - None => { - writeln!(out, " {name}:").unwrap(); - } - Some(fields) => { - writeln!(out, " {name}:").unwrap(); - for (field_name, info) in fields { - let suffix = field_suffix(info.multiple, info.required); - let yaml_name = if field_name == "$children" { - format!("$children{suffix}") - } else { - format!("{field_name}{suffix}") - }; - - let type_refs: Vec = info - .types - .iter() - .map(|t| format_type_ref(&t.kind, t.named, &all_named, &all_unnamed)) - .collect(); - - if type_refs.len() == 1 { - writeln!(out, " {yaml_name}: {}", type_refs[0]).unwrap(); - } else { - let list = type_refs - .iter() - .map(|s| s.as_str()) - .collect::>() - .join(", "); - writeln!(out, " {yaml_name}: [{list}]").unwrap(); - } - } - } - } - } - writeln!(out).unwrap(); - } - - // Unnamed - if !unnamed.is_empty() { - writeln!(out, "unnamed:").unwrap(); - for name in &unnamed { - writeln!(out, " - {}", force_quote(name)).unwrap(); - } - } - - Ok(out) -} - -fn field_suffix(multiple: bool, required: bool) -> &'static str { - match (multiple, required) { - (false, true) => "", - (false, false) => "?", - (true, true) => "+", - (true, false) => "*", - } -} - -/// Format a type reference for YAML output. Uses the disambiguation rule: -/// plain string if unambiguous, `{unnamed: name}` if the name exists as both -/// named and unnamed and we need the unnamed interpretation. -fn format_type_ref( - kind: &str, - named: bool, - all_named: &BTreeSet, - _all_unnamed: &BTreeSet, -) -> String { - if named { - quote_yaml(kind) - } else { - let is_also_named = all_named.contains(kind); - if is_also_named { - format!("{{unnamed: {}}}", force_quote(kind)) - } else { - force_quote(kind) - } - } -} - -/// Always wrap in double quotes. Used for unnamed node references so they're -/// visually distinct from named ones — YAML treats both forms as equivalent strings. -fn force_quote(s: &str) -> String { - format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")) -} - -/// Quote a YAML string value if it contains special characters or could be -/// misinterpreted. -fn quote_yaml(s: &str) -> String { - let needs_quoting = s.is_empty() - || s.contains(|c: char| { - matches!( - c, - ':' | '{' - | '}' - | '[' - | ']' - | ',' - | '&' - | '*' - | '#' - | '?' - | '|' - | '-' - | '<' - | '>' - | '=' - | '!' - | '%' - | '@' - | '`' - | '"' - | '\'' - ) - }) - || s.starts_with(' ') - || s.ends_with(' ') - || s == "true" - || s == "false" - || s == "null" - || s == "yes" - || s == "no" - || s.parse::().is_ok(); - - if needs_quoting { - format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")) - } else { - s.to_string() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_basic_conversion() { - let yaml = r#" -supertypes: - _expression: - - assignment - - binary - -named: - assignment: - left: _lhs - right: _expression - binary: - left: [_expression, _simple_numeric] - operator: ["!=", "+"] - right: _expression - argument_list: - $children*: [_expression, block_argument] - identifier: - -unnamed: - - "!=" - - "+" - - "end" -"#; - - let json_str = convert(yaml).unwrap(); - let result: Vec = serde_json::from_str(&json_str).unwrap(); - - // Check supertype - let expr = &result[0]; - assert_eq!(expr["type"], "_expression"); - assert_eq!(expr["named"], true); - assert_eq!(expr["subtypes"].as_array().unwrap().len(), 2); - - // Check assignment - let assign = result.iter().find(|n| n["type"] == "assignment").unwrap(); - assert_eq!(assign["fields"]["left"]["required"], true); - assert_eq!(assign["fields"]["left"]["multiple"], false); - assert_eq!(assign["fields"]["left"]["types"][0]["type"], "_lhs"); - assert_eq!(assign["fields"]["left"]["types"][0]["named"], true); - - // Check binary.operator — "!=" and "+" should resolve to unnamed - let binary = result.iter().find(|n| n["type"] == "binary").unwrap(); - let op_types = binary["fields"]["operator"]["types"].as_array().unwrap(); - assert_eq!(op_types[0]["type"], "!="); - assert_eq!(op_types[0]["named"], false); - assert_eq!(op_types[1]["type"], "+"); - assert_eq!(op_types[1]["named"], false); - - // Check argument_list has children, not a field - let arg_list = result - .iter() - .find(|n| n["type"] == "argument_list") - .unwrap(); - assert!(arg_list.get("children").is_some()); - assert_eq!(arg_list["children"]["multiple"], true); - assert_eq!(arg_list["children"]["required"], false); - - // Check identifier is a leaf - let ident = result.iter().find(|n| n["type"] == "identifier").unwrap(); - assert_eq!(ident["fields"].as_object().unwrap().len(), 0); - - // Check unnamed tokens - let end = result.iter().find(|n| n["type"] == "end").unwrap(); - assert_eq!(end["named"], false); - } - - #[test] - fn test_explicit_unnamed_disambiguation() { - let yaml = r#" -named: - foo: - field: [{unnamed: bar}] - -unnamed: - - bar -"#; - - let json_str = convert(yaml).unwrap(); - let result: Vec = serde_json::from_str(&json_str).unwrap(); - let foo = result.iter().find(|n| n["type"] == "foo").unwrap(); - assert_eq!(foo["fields"]["field"]["types"][0]["named"], false); - } - - #[test] - fn test_field_suffixes() { - let yaml = r#" -named: - test_node: - required_single: foo - optional_single?: foo - required_multiple+: foo - optional_multiple*: foo -"#; - - let json_str = convert(yaml).unwrap(); - let result: Vec = serde_json::from_str(&json_str).unwrap(); - let node = result.iter().find(|n| n["type"] == "test_node").unwrap(); - let fields = node["fields"].as_object().unwrap(); - - assert_eq!(fields["required_single"]["required"], true); - assert_eq!(fields["required_single"]["multiple"], false); - - assert_eq!(fields["optional_single"]["required"], false); - assert_eq!(fields["optional_single"]["multiple"], false); - - assert_eq!(fields["required_multiple"]["required"], true); - assert_eq!(fields["required_multiple"]["multiple"], true); - - assert_eq!(fields["optional_multiple"]["required"], false); - assert_eq!(fields["optional_multiple"]["multiple"], true); - } - - #[test] - fn test_json_to_yaml() { - let json = r#"[ - {"type": "_expression", "named": true, "subtypes": [ - {"type": "assignment", "named": true}, - {"type": "identifier", "named": true} - ]}, - {"type": "assignment", "named": true, "fields": { - "left": {"multiple": false, "required": true, "types": [ - {"type": "_expression", "named": true} - ]}, - "right": {"multiple": false, "required": false, "types": [ - {"type": "_expression", "named": true} - ]} - }, "children": { - "multiple": true, "required": false, "types": [ - {"type": "identifier", "named": true} - ] - }}, - {"type": "identifier", "named": true, "fields": {}}, - {"type": "=", "named": false}, - {"type": "end", "named": false} - ]"#; - - let yaml = convert_from_json(json).unwrap(); - - // Verify key structures are present - assert!(yaml.contains("supertypes:")); - assert!(yaml.contains("_expression:")); - assert!(yaml.contains("named:")); - assert!(yaml.contains("assignment:")); - assert!(yaml.contains("left:")); - assert!(yaml.contains("right?:")); - assert!(yaml.contains("$children*:")); - assert!(yaml.contains("identifier:")); - assert!(yaml.contains("unnamed:")); - assert!(yaml.contains("\"=\"")); - assert!(yaml.contains("end")); - } - - #[test] - fn test_round_trip() { - let yaml_input = r#" -supertypes: - _expression: - - assignment - - identifier - -named: - assignment: - left: _expression - right?: _expression - $children*: identifier - identifier: - -unnamed: - - "=" - - end -"#; - - // YAML → JSON → YAML - let json = convert(yaml_input).unwrap(); - let yaml_output = convert_from_json(&json).unwrap(); - // YAML → JSON again (should be identical) - let json2 = convert(&yaml_output).unwrap(); - - let v1: serde_json::Value = serde_json::from_str(&json).unwrap(); - let v2: serde_json::Value = serde_json::from_str(&json2).unwrap(); - assert_eq!(v1, v2); - } -} diff --git a/shared/yeast/src/schema.rs b/shared/yeast/src/schema.rs index da13bb8b6b70..daa8ad98eb5b 100644 --- a/shared/yeast/src/schema.rs +++ b/shared/yeast/src/schema.rs @@ -1,285 +1,54 @@ -use std::collections::{BTreeMap, BTreeSet}; - -use crate::{FieldId, KindId, CHILD_FIELD}; - -#[derive(Clone, Debug)] -pub struct NodeType { - pub kind: String, - pub named: bool, -} - -/// Multiplicity/optionality of a field declaration. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -pub struct FieldCardinality { - /// Whether the field may hold more than one child. - pub multiple: bool, - /// Whether at least one child must be present. - pub required: bool, -} - -/// A schema defining node kinds and field names for the output AST. -/// Built from a node-types.yml file, independent of any tree-sitter grammar. -/// -/// # Memory management -/// -/// `register_field`/`register_kind`/`register_unnamed_kind` use `Box::leak` -/// to obtain `&'static str` names. This is intentional: the `&'static str` -/// names appear pervasively in `Node`, `AstCursor`, query patterns, and the -/// extractor's TRAP output, where adding a lifetime would propagate widely. -/// -/// The leak is bounded by the number of distinct kind/field names registered. -/// Schemas are expected to be constructed once per process (e.g. at extractor -/// startup) and reused. Repeated construction in long-running processes will -/// leak memory unboundedly and should be avoided. -#[derive(Clone)] -pub struct Schema { - field_ids: BTreeMap, - field_names: BTreeMap, - next_field_id: FieldId, - kind_ids: BTreeMap, - unnamed_kind_ids: BTreeMap, - kind_names: BTreeMap, - next_kind_id: KindId, - field_types: BTreeMap<(String, FieldId), Vec>, - field_cardinalities: BTreeMap<(String, FieldId), FieldCardinality>, - supertypes: BTreeMap>, -} - -impl Default for Schema { - fn default() -> Self { - Self::new() - } -} - -impl Schema { - pub fn new() -> Self { - Self { - field_ids: BTreeMap::new(), - field_names: BTreeMap::new(), - next_field_id: 1, // 0 is reserved - kind_ids: BTreeMap::new(), - unnamed_kind_ids: BTreeMap::new(), - kind_names: BTreeMap::new(), - next_kind_id: 1, // 0 is reserved - field_types: BTreeMap::new(), - field_cardinalities: BTreeMap::new(), - supertypes: BTreeMap::new(), - } - } - - /// Create a schema from a tree-sitter language, importing all its - /// known field and kind names. - pub fn from_language(language: &tree_sitter::Language) -> Self { - let mut schema = Self::new(); - // Import all field names, preserving tree-sitter's IDs - for id in 1..=language.field_count() as u16 { - if let Some(name) = language.field_name_for_id(id) { - schema.field_ids.insert(name.to_string(), id); - schema.field_names.insert(id, name); - if id >= schema.next_field_id { - schema.next_field_id = id + 1; - } +//! YEAST schema types. +//! +//! The schema struct itself lives in the [`yeast_schema`] crate (so it can +//! be shared with the `yeast-macros` proc-macro crate without dragging +//! tree-sitter into proc-macro compiles). This module re-exports its +//! public API and supplies the one tree-sitter-aware adapter the runtime +//! needs: [`from_language`]. + +pub use yeast_schema::schema::{FieldCardinality, NodeType, Schema}; + +/// Build a [`Schema`] from a tree-sitter language, importing all its +/// known field and kind names so the resulting schema's IDs line up with +/// the language's own IDs (i.e. `field_name_for_id` agrees). +pub fn from_language(language: &tree_sitter::Language) -> Schema { + let mut schema = Schema::new(); + + // Import all field names, preserving tree-sitter's IDs. + for id in 1..=language.field_count() as u16 { + if let Some(name) = language.field_name_for_id(id) { + schema.register_field_with_id(name, id); + } + } + + // Import all node kind names, preserving tree-sitter's IDs. + // Track named and unnamed variants separately. For both, prefer the + // canonical ID returned by `id_for_node_kind`, since some languages + // have multiple IDs for the same name (e.g. the reserved error token + // at ID 0 may share a name with a real token). + for id in 0..language.node_kind_count() as u16 { + if let Some(name) = language.node_kind_for_id(id) { + if name.is_empty() { + continue; } - } - // Import all node kind names, preserving tree-sitter's IDs. - // Track named and unnamed variants separately. For both named and - // unnamed kinds, use the canonical ID from id_for_node_kind, since - // some languages have multiple IDs for the same name (e.g., the - // reserved error token at ID 0 may share a name with a real token). - for id in 0..language.node_kind_count() as u16 { - if let Some(name) = language.node_kind_for_id(id) { - if !name.is_empty() { - let is_named = language.node_kind_is_named(id); - if is_named { - let canonical_id = language.id_for_node_kind(name, true); - if canonical_id != 0 && !schema.kind_ids.contains_key(name) { - schema.kind_ids.insert(name.to_string(), canonical_id); - schema.kind_names.insert(canonical_id, name); - } - } else { - let canonical_id = language.id_for_node_kind(name, false); - if canonical_id != 0 && !schema.unnamed_kind_ids.contains_key(name) { - schema - .unnamed_kind_ids - .insert(name.to_string(), canonical_id); - schema.kind_names.insert(canonical_id, name); - } - } - // Always track the name for any ID we encounter - schema.kind_names.entry(id).or_insert(name); - if id >= schema.next_kind_id { - schema.next_kind_id = id + 1; - } + let is_named = language.node_kind_is_named(id); + if is_named { + let canonical_id = language.id_for_node_kind(name, true); + if canonical_id != 0 && schema.id_for_node_kind(name).is_none() { + schema.register_kind_with_id(name, canonical_id); + } + } else { + let canonical_id = language.id_for_node_kind(name, false); + if canonical_id != 0 && schema.id_for_unnamed_node_kind(name).is_none() { + schema.register_unnamed_kind_with_id(name, canonical_id); } } + // Always track the name for any ID we encounter (so + // `node_kind_for_id` works for the literal `id` we saw, even + // when it isn't the canonical one). + schema.record_kind_name(id, name); } - schema - } - - /// Register a field name, returning its ID. - /// If already registered, returns the existing ID. - pub fn register_field(&mut self, name: &str) -> FieldId { - if name == "child" { - return CHILD_FIELD; - } - if let Some(&id) = self.field_ids.get(name) { - return id; - } - let id = self.next_field_id; - assert!(id < CHILD_FIELD, "too many fields"); - self.next_field_id += 1; - let leaked: &'static str = Box::leak(name.to_string().into_boxed_str()); - self.field_ids.insert(name.to_string(), id); - self.field_names.insert(id, leaked); - id - } - - /// Register a named node kind name, returning its ID. - /// If already registered, returns the existing ID. - pub fn register_kind(&mut self, name: &str) -> KindId { - if let Some(&id) = self.kind_ids.get(name) { - return id; - } - let id = self.next_kind_id; - self.next_kind_id += 1; - let leaked: &'static str = Box::leak(name.to_string().into_boxed_str()); - self.kind_ids.insert(name.to_string(), id); - self.kind_names.insert(id, leaked); - id } - /// Register an unnamed token kind (e.g. `"="`, `"end"`), returning its ID. - /// If already registered, returns the existing ID. - pub fn register_unnamed_kind(&mut self, name: &str) -> KindId { - if let Some(&id) = self.unnamed_kind_ids.get(name) { - return id; - } - let id = self.next_kind_id; - self.next_kind_id += 1; - let leaked: &'static str = Box::leak(name.to_string().into_boxed_str()); - self.unnamed_kind_ids.insert(name.to_string(), id); - self.kind_names.insert(id, leaked); - id - } - - pub fn field_id_for_name(&self, name: &str) -> Option { - if name == "child" { - return Some(CHILD_FIELD); - } - self.field_ids.get(name).copied() - } - - pub fn field_name_for_id(&self, id: FieldId) -> Option<&'static str> { - if id == CHILD_FIELD { - return Some("child"); - } - self.field_names.get(&id).copied() - } - - pub fn id_for_node_kind(&self, kind: &str) -> Option { - self.kind_ids.get(kind).copied() - } - - pub fn id_for_unnamed_node_kind(&self, kind: &str) -> Option { - self.unnamed_kind_ids.get(kind).copied() - } - - pub fn node_kind_for_id(&self, id: KindId) -> Option<&'static str> { - self.kind_names.get(&id).copied() - } - - pub fn set_field_types( - &mut self, - parent_kind: &str, - field_id: FieldId, - node_types: Vec, - ) { - self.field_types - .insert((parent_kind.to_string(), field_id), node_types); - } - - pub fn field_types(&self, parent_kind: &str, field_id: FieldId) -> Option<&Vec> { - self.field_types.get(&(parent_kind.to_string(), field_id)) - } - - pub fn set_field_cardinality( - &mut self, - parent_kind: &str, - field_id: FieldId, - cardinality: FieldCardinality, - ) { - self.field_cardinalities - .insert((parent_kind.to_string(), field_id), cardinality); - } - - /// Returns the declared cardinality for a field, if known. - pub fn field_cardinality( - &self, - parent_kind: &str, - field_id: FieldId, - ) -> Option { - self.field_cardinalities - .get(&(parent_kind.to_string(), field_id)) - .copied() - } - - /// Returns an iterator over all `(field_id, field_name)` pairs that are - /// declared as required (`required: true`) for the given `parent_kind`. - pub fn required_fields_for_kind<'a>( - &'a self, - parent_kind: &'a str, - ) -> impl Iterator)> + 'a { - self.field_cardinalities - .iter() - .filter(move |((kind, _), card)| kind == parent_kind && card.required) - .map(move |((_, field_id), _)| { - let name = self.field_name_for_id(*field_id); - (*field_id, name) - }) - } - - pub fn set_supertype_members(&mut self, supertype: &str, node_types: Vec) { - self.supertypes.insert(supertype.to_string(), node_types); - } - - fn allows_node( - &self, - node_type: &NodeType, - node_kind: &str, - node_named: bool, - active: &mut BTreeSet, - ) -> bool { - if node_type.kind == node_kind && node_type.named == node_named { - return true; - } - - if !node_type.named { - return false; - } - - let Some(members) = self.supertypes.get(&node_type.kind) else { - return false; - }; - - if !active.insert(node_type.kind.clone()) { - return false; - } - - let matched = members - .iter() - .any(|member| self.allows_node(member, node_kind, node_named, active)); - active.remove(&node_type.kind); - matched - } - - pub fn node_matches_types( - &self, - node_kind: &str, - node_named: bool, - node_types: &[NodeType], - ) -> bool { - node_types.iter().any(|node_type| { - self.allows_node(node_type, node_kind, node_named, &mut BTreeSet::new()) - }) - } + schema } From ea36f2d7f8fbfccae9d05fa5addd4a1ab4d97d06 Mon Sep 17 00:00:00 2001 From: Taus Date: Fri, 19 Jun 2026 15:31:02 +0000 Subject: [PATCH 09/60] yeast: add `rules!` macro This macro allows the easy addition of multiple rules at the same time. In addition, it also accepts an input and output schema, which eventually will be used to check the validity of the rewrite rules. --- shared/yeast-macros/src/lib.rs | 40 +++ shared/yeast-macros/src/parse.rs | 243 +++++++++++++++++++ shared/yeast/doc/yeast.md | 41 ++++ shared/yeast/src/lib.rs | 2 +- shared/yeast/tests/input-types.yml | 40 +++ shared/yeast/tests/test.rs | 120 +++++++++ unified/extractor/tests/rules_macro_smoke.rs | 25 ++ 7 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 shared/yeast/tests/input-types.yml create mode 100644 unified/extractor/tests/rules_macro_smoke.rs diff --git a/shared/yeast-macros/src/lib.rs b/shared/yeast-macros/src/lib.rs index 7db97f9fb709..0df96c91d26b 100644 --- a/shared/yeast-macros/src/lib.rs +++ b/shared/yeast-macros/src/lib.rs @@ -113,3 +113,43 @@ pub fn rule(input: TokenStream) -> TokenStream { Err(err) => err.to_compile_error().into(), } } + +/// Bundle a list of YEAST rewrite rules with input/output node-types +/// schema paths. Returns a `Vec`; substitutable for +/// `vec![rule!(...), ...]`. +/// +/// Each comma-separated item in the bracketed list may be: +/// +/// 1. A **bare rule body** `(query) => (template)` — the `rule!(...)` +/// wrapper is implicit. +/// 2. An explicit `rule!(...)` invocation, possibly chained as +/// `rule!(...).repeated()` or path-prefixed as `yeast::rule!(...)`. +/// 3. Any other expression returning a `Rule` (helper-function calls, +/// conditionals). +/// +/// ```ignore +/// let translation_rules: Vec = yeast::rules! { +/// input: "tree-sitter-swift/node-types.yml", +/// output: "ast_types.yml", +/// [ +/// (source_file (_)* @cs) => (top_level body: {..cs}), +/// (simple_identifier) @id => (name_expr identifier: (identifier #{id})), +/// rule!((integer_literal) @lit => (int_literal #{lit})).repeated(), +/// helper_fn(), +/// ] +/// }; +/// ``` +/// +/// Paths are resolved relative to the consuming crate's `CARGO_MANIFEST_DIR` +/// (the same convention `include_str!` uses for relative paths). The +/// resolved paths are also emitted as `include_str!` references so the +/// consuming crate gets invalidated when a schema YAML changes, prepping +/// the ground for compile-time type-checking against those schemas. +#[proc_macro] +pub fn rules(input: TokenStream) -> TokenStream { + let input2: TokenStream2 = input.into(); + match parse::parse_rules_top(input2) { + Ok(output) => output.into(), + Err(err) => err.to_compile_error().into(), + } +} diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index 2ab6236fdac9..01070c74bbce 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -897,6 +897,219 @@ fn expect_repetition(tokens: &mut Tokens) -> Result { } } +// --------------------------------------------------------------------------- +// rules! parsing — bundle a list of rules with input/output schema paths. +// +// The macro accepts both bare rule bodies (`(query) => (template)`) and +// explicit `rule!(...)` invocations. The schema paths are recorded but +// not yet consumed; a later change layers compile-time type-checking on +// top, using these paths to load the input/output schemas. +// --------------------------------------------------------------------------- + +/// Parse `rules! { input: "path", output: "path", [ items, ... ] }`. +/// +/// Each item in the bracketed list can be: +/// * a **bare rule body** `(query) => (template)` — wrapped implicitly +/// in `yeast::rule! { ... }` for codegen; +/// * an explicit `rule!(...)` (or `rule!(...).repeated()`, +/// `yeast::rule!(...)`, etc.) — passed through verbatim; +/// * any other expression returning a `Rule` (helper-function calls, +/// conditionals) — passed through verbatim. +/// +/// Returns a `Vec` containing the items in order. The expansion +/// also emits `include_str!` references to the resolved schema paths so +/// Cargo treats them as inputs to the consuming crate; this validates +/// path existence at compile time and prepares the ground for later +/// schema-aware checks. +pub fn parse_rules_top(input: TokenStream) -> Result { + let mut tokens = input.into_iter().peekable(); + + let input_path = parse_named_string_arg(&mut tokens, "input")?; + expect_punct(&mut tokens, ',', "expected `,` after input path")?; + let output_path = parse_named_string_arg(&mut tokens, "output")?; + expect_punct(&mut tokens, ',', "expected `,` after output path")?; + + // Resolve paths relative to the consuming crate's CARGO_MANIFEST_DIR + // so callers can write paths like "tree-sitter-swift/node-types.yml" + // alongside their other workspace-relative includes (e.g. include_str!). + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").map_err(|_| { + syn::Error::new( + Span::call_site(), + "rules!: CARGO_MANIFEST_DIR is not set; cannot resolve schema paths", + ) + })?; + let resolve_path = |raw: &str| -> std::path::PathBuf { + let p = std::path::PathBuf::from(raw); + if p.is_absolute() { + p + } else { + std::path::PathBuf::from(&manifest_dir).join(p) + } + }; + let input_abs = resolve_path(&input_path.value); + let output_abs = resolve_path(&output_path.value); + + let list = expect_group(&mut tokens, Delimiter::Bracket)?; + if let Some(tok) = tokens.next() { + return Err(syn::Error::new_spanned( + tok, + "unexpected token after `rules!` list", + )); + } + + let items = split_top_level_commas(list.stream()); + let emitted_items: Vec = items + .into_iter() + .map(|item| { + // Bare rule body — wrap in `yeast::rule! { ... }` so the + // existing rule-construction macro handles codegen. Other + // items pass through unchanged. + if has_top_level_arrow(&item) { + quote! { yeast::rule! { #item } } + } else { + item + } + }) + .collect(); + + // Emit `include_str!` references to both schema files so Cargo + // treats them as inputs to the consuming crate's compilation. The + // `const _` bindings are unused; rustc/LLVM drop them after the + // file-input dependency edge is recorded. Absolute paths are used + // because `include_str!` resolves relative paths against the source + // file, while `rules!`'s own paths are relative to + // `CARGO_MANIFEST_DIR`. + let input_abs_str = input_abs.to_string_lossy().into_owned(); + let output_abs_str = output_abs.to_string_lossy().into_owned(); + let input_lit = proc_macro2::Literal::string(&input_abs_str); + let output_lit = proc_macro2::Literal::string(&output_abs_str); + + Ok(quote! { + { + const _: &::core::primitive::str = ::core::include_str!(#input_lit); + const _: &::core::primitive::str = ::core::include_str!(#output_lit); + vec![ #(#emitted_items),* ] + } + }) +} + +/// True iff `item` contains a `=>` operator at the top level (not nested +/// inside any group). Used to detect bare rule bodies inside `rules!`. +fn has_top_level_arrow(item: &TokenStream) -> bool { + let toks: Vec = item.clone().into_iter().collect(); + find_top_level_arrow(&toks).is_some() +} + +/// Find the index of the first token of a top-level `=>` operator (the +/// `=`), ignoring `=>` inside any group. Returns `None` if not present. +fn find_top_level_arrow(toks: &[TokenTree]) -> Option { + let mut i = 0; + while i + 1 < toks.len() { + if let (TokenTree::Punct(p1), TokenTree::Punct(p2)) = (&toks[i], &toks[i + 1]) { + if p1.as_char() == '=' + && p1.spacing() == proc_macro2::Spacing::Joint + && p2.as_char() == '>' + { + return Some(i); + } + } + i += 1; + } + None +} + +/// A string literal argument named `expected_name` parsed from `name: "value"`. +struct NamedString { + value: String, + #[allow(dead_code)] + span: Span, +} + +fn parse_named_string_arg(tokens: &mut Tokens, expected_name: &str) -> Result { + let name = expect_ident( + tokens, + &format!("expected `{expected_name}:` argument"), + )?; + if name != expected_name { + return Err(syn::Error::new_spanned( + name, + format!("expected `{expected_name}:` argument"), + )); + } + expect_punct( + tokens, + ':', + &format!("expected `:` after `{expected_name}`"), + )?; + let lit = expect_literal(tokens)?; + let span = lit.span(); + let value = string_literal_value(&lit).ok_or_else(|| { + syn::Error::new( + span, + format!("`{expected_name}` must be a string literal path"), + ) + })?; + Ok(NamedString { value, span }) +} + +/// Read a literal as a plain Rust string, stripping the surrounding quotes +/// and unescaping. Falls back to `None` if the literal isn't a string. +fn string_literal_value(lit: &Literal) -> Option { + let raw = lit.to_string(); + let bytes = raw.as_bytes(); + // Match plain `"..."` literals; reject byte strings, raw strings (for + // simplicity), char literals, numbers, etc. + if bytes.first() != Some(&b'"') || bytes.last() != Some(&b'"') { + return None; + } + let mut out = String::with_capacity(raw.len()); + let mut chars = raw[1..raw.len() - 1].chars(); + while let Some(c) = chars.next() { + if c != '\\' { + out.push(c); + continue; + } + match chars.next()? { + 'n' => out.push('\n'), + 't' => out.push('\t'), + 'r' => out.push('\r'), + '\\' => out.push('\\'), + '\'' => out.push('\''), + '"' => out.push('"'), + '0' => out.push('\0'), + other => { + // Unknown escape — give up rather than silently mis-parse. + out.push('\\'); + out.push(other); + } + } + } + Some(out) +} + +/// Split a token stream into top-level comma-separated items. Commas inside +/// any group token (parens, brackets, braces) are ignored so that things +/// like `rule!(a, b)` aren't accidentally split. +fn split_top_level_commas(stream: TokenStream) -> Vec { + let mut items = Vec::new(); + let mut current: Vec = Vec::new(); + for tt in stream { + if let TokenTree::Punct(p) = &tt { + if p.as_char() == ',' && p.spacing() == proc_macro2::Spacing::Alone { + if !current.is_empty() { + items.push(current.drain(..).collect()); + } + continue; + } + } + current.push(tt); + } + if !current.is_empty() { + items.push(current.into_iter().collect()); + } + items +} + fn maybe_wrap_capture(tokens: &mut Tokens, base: TokenStream) -> Result { if peek_is_at(tokens) { let name = consume_capture_marker(tokens)?; @@ -970,3 +1183,33 @@ fn maybe_wrap_list_capture(tokens: &mut Tokens, elem: TokenStream) -> Result` is present. + let toks = quote! { (a) => (b) }; + assert!(has_top_level_arrow(&toks)); + // `rule!((a) => (b))`: the `=>` is INSIDE the macro group, so + // it's not at top level. Must NOT be detected as a bare body. + let toks = quote! { rule!((a) => (b)) }; + assert!(!has_top_level_arrow(&toks)); + // Helper call: no `=>` anywhere. + let toks = quote! { make_rule() }; + assert!(!has_top_level_arrow(&toks)); + // Match expressions inside a block: `=>` is inside braces. + let toks = quote! { { match x { 1 => 2, _ => 3 } } }; + assert!(!has_top_level_arrow(&toks)); + // Bare shorthand form: top-level `=>` followed by a bare ident. + let toks = quote! { (a) => kind }; + assert!(has_top_level_arrow(&toks)); + } +} diff --git a/shared/yeast/doc/yeast.md b/shared/yeast/doc/yeast.md index 8aa050592f6b..3427597be2e6 100644 --- a/shared/yeast/doc/yeast.md +++ b/shared/yeast/doc/yeast.md @@ -437,3 +437,44 @@ For the dbscheme/QL code generator, set `Language::desugar` to a `DesugaringConfig` carrying the same YAML; the generator converts it to JSON for downstream code generation. The `phases` field of the config is unused at code-generation time. + +## The `rules!` macro + +The [`rules!`] macro bundles a list of rewrite rules with the input and +output node-types schema paths. It's a drop-in replacement for the +hand-written `vec![rule!(...), rule!(...), ...]` form and accepts a +slightly looser syntax: bare rule bodies don't need an explicit +`rule!(...)` wrapper. + +```rust +let translation_rules: Vec = yeast::rules! { + input: "tree-sitter-swift/node-types.yml", + output: "ast_types.yml", + [ + (simple_identifier) @name + => + (name_expr identifier: (identifier #{name})), + + (integer_literal) @lit + => + (int_literal #{lit}), + ] +}; +``` + +Each comma-separated item in the bracketed list may be: + +- A **bare rule body** `(query) => (template)` — no `rule!(...)` wrapper. +- An explicit `rule!(...)` invocation, with optional postfix calls such + as `rule!(...).repeated()`. +- Any other expression returning a `Rule` (helper functions, etc.). + +Schema paths are resolved relative to the consuming crate's +`CARGO_MANIFEST_DIR` (the same convention `include_str!` uses for +relative paths). The resolved paths are emitted as `include_str!` +references in the expansion so the consuming crate's incremental cache +invalidates when a schema YAML changes — laying the groundwork for +schema-aware compile-time checks on the rule bodies. + +The `Vec` produced by `rules!` flows into `add_phase` exactly as +before. \ No newline at end of file diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index c4b448077267..e6759f1ddb92 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -15,7 +15,7 @@ pub mod schema; pub mod tree_builder; mod visitor; -pub use yeast_macros::{query, rule, tree, trees}; +pub use yeast_macros::{query, rule, rules, tree, trees}; use captures::Captures; use query::QueryNode; diff --git a/shared/yeast/tests/input-types.yml b/shared/yeast/tests/input-types.yml new file mode 100644 index 000000000000..6bc184ec6470 --- /dev/null +++ b/shared/yeast/tests/input-types.yml @@ -0,0 +1,40 @@ +# Test input schema for yeast rules! macro tests. Covers a small subset of +# tree-sitter-ruby kinds used by the test rules. Kept deliberately small so +# the macro's compile-time loader can be exercised over a known surface. + +named: + program: + $children*: [assignment, call, identifier, for] + + assignment: + left: [identifier, left_assignment_list] + right: [identifier, integer, call] + + left_assignment_list: + $children*: identifier + + for: + pattern: [identifier, left_assignment_list] + value: in + body: do + + in: + $children: [identifier, call] + + do: + $children*: [identifier, assignment, call] + + call: + receiver: [identifier, call] + method: identifier + + identifier: + integer: + +unnamed: + - "=" + - "," + - "for" + - "in" + - "do" + - "end" diff --git a/shared/yeast/tests/test.rs b/shared/yeast/tests/test.rs index 57a9e17dbd4c..4a2fb26f4fe3 100644 --- a/shared/yeast/tests/test.rs +++ b/shared/yeast/tests/test.rs @@ -1322,3 +1322,123 @@ fn test_hash_brace_uses_capture_location_for_leaf() { assert_eq!(bar.start_byte(), 4); assert_eq!(bar.end_byte(), 7); } + +// ---- `rules!` macro tests (compile-time type-checking) ---- + +/// `rules!` should accept well-typed rules using the bare-rule-body +/// syntax (no inner `rule!` invocations) and produce a `Vec` that +/// behaves identically to a plain `vec![rule!(...)]` list. +#[test] +fn test_rules_macro_accepts_bare_rule_body() { + let rules: Vec = yeast::rules! { + input: "tests/input-types.yml", + output: "tests/node-types.yml", + [ + (assignment + left: (_) @left + right: (_) @right + ) + => + (assignment + left: {right} + right: {left} + ), + ] + }; + + let dump = run_and_dump("x = 1", rules); + assert_dump_eq( + &dump, + r#" + program + assignment + left: integer "1" + right: identifier "x" + "#, + ); +} + +/// The bare-rule-body shorthand `=> output_kind` should also be accepted. +#[test] +fn test_rules_macro_accepts_bare_shorthand_form() { + let rules: Vec = yeast::rules! { + input: "tests/input-types.yml", + output: "tests/node-types.yml", + [ + (assignment + left: (_) @method + right: (_) @receiver + ) + => call, + ] + }; + + let dump = run_and_dump("x = 1", rules); + assert_dump_eq( + &dump, + r#" + program + call + method: identifier "x" + receiver: integer "1" + "#, + ); +} + +/// Backwards-compat: explicit `rule!(...)` invocations inside `rules!` +/// should still type-check and behave the same as the bare form. +#[test] +fn test_rules_macro_accepts_explicit_rule_macro() { + let rules: Vec = yeast::rules! { + input: "tests/input-types.yml", + output: "tests/node-types.yml", + [ + rule!( + (assignment + left: (_) @left + right: (_) @right + ) + => + (assignment + left: {right} + right: {left} + ) + ), + ] + }; + assert_eq!(rules.len(), 1); +} + +/// `rules!` should pass through items that aren't bare rule bodies or +/// `rule!(...)` calls (e.g. helper-function calls returning a `Rule`), +/// without type-checking them. Bare and explicit rules in the same list +/// still get checked. +#[test] +fn test_rules_macro_allows_non_rule_items() { + fn extra() -> yeast::Rule { + rule!((identifier) => (identifier "extra")) + } + let rules: Vec = yeast::rules! { + input: "tests/input-types.yml", + output: "tests/node-types.yml", + [ + (integer) => (integer "checked"), + extra(), + ] + }; + assert_eq!(rules.len(), 2); +} + +/// `rules!` should accept lists that mix bare-rule and explicit-rule items. +#[test] +fn test_rules_macro_mixes_bare_and_explicit_forms() { + let rules: Vec = yeast::rules! { + input: "tests/input-types.yml", + output: "tests/node-types.yml", + [ + (integer) => (integer "I"), + rule!((identifier) => (identifier "S")), + ] + }; + assert_eq!(rules.len(), 2); +} diff --git a/unified/extractor/tests/rules_macro_smoke.rs b/unified/extractor/tests/rules_macro_smoke.rs new file mode 100644 index 000000000000..cde8ae3ca4ab --- /dev/null +++ b/unified/extractor/tests/rules_macro_smoke.rs @@ -0,0 +1,25 @@ +/// Smoke test: load a few real Swift translation rules through the new +/// `yeast::rules!` macro using the bare-rule-body syntax, and confirm the +/// input + output schemas accept them. Compiles only — any type-checking +/// error surfaces as a compile-time error. +#[test] +fn rules_macro_compiles_against_real_swift_schemas() { + let _rules: Vec = yeast::rules! { + input: "tree-sitter-swift/node-types.yml", + output: "ast_types.yml", + [ + (simple_identifier) @name + => + (name_expr + identifier: (identifier #{name})), + + (integer_literal) @lit + => + (int_literal #{lit}), + + (line_string_literal) @lit + => + (string_literal #{lit}), + ] + }; +} From ee04938dedea3e1a83b4dd38ecc68c90b48abe10 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 2 Jul 2026 12:05:23 +0000 Subject: [PATCH 10/60] yeast: Require type annotations on root-level Rust interpolations In order to facilitate static type checking of rules (and to make it easier for human readers as well), rust blocks at the root level (i.e. rules of the form `... => { ... }`) must now have a type annotation in front. All other forms are unaffected: if the right hand side of a rule is a tree, we can read the type of the root node directly. For interpolations that happen inside of such a tree, we can recover the type by looking at what field we're interpolating into, and consulting the output schema. All existing uses have been updated to have the appropriate type annotations, though these are of course not checked yet (and so could be wrong). Finally, this commit also removes the final catch-all rule `_ @node => {node}`. Because of the preceding rule that matches `(_) @node`, this rule would only ever match unnamed nodes, and I think in practice it did not match at all (at least not in our current set of tests). To give it a proper type we would have to add some notion of an "any" type, which I would like to avoid. If it _does_ turn out to be needed, we can easily add it back (ideally with a test-case that shows why it's still needed). --- shared/yeast-macros/src/parse.rs | 143 +++++++++++++++++- shared/yeast/doc/yeast.md | 65 +++++++- shared/yeast/tests/test.rs | 116 +++++++++++++- .../extractor/src/languages/swift/swift.rs | 59 ++++---- 4 files changed, 337 insertions(+), 46 deletions(-) diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index 01070c74bbce..986a9bac641a 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -617,6 +617,76 @@ fn extract_captures_inner( } } +/// A rule's return-type annotation, when the body is a Rust block. Written +/// between `=>` and the block body using the schema's own vocabulary: +/// +/// ```text +/// => kind { … } // single node of that kind +/// => kind? { … } // Option (0 or 1) +/// => kind* { … } // Vec (0+) +/// ``` +/// +/// Template bodies (`=> (kind …)`) never carry an annotation — the +/// output kind is the template root. The shorthand `=> kind` (no +/// body) also carries no annotation. See `parse_rule_top` for dispatch. +#[derive(Clone, Debug)] +struct ReturnAnnotation { + kind: Ident, + multiplicity: AnnotationMultiplicity, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +enum AnnotationMultiplicity { + Single, + Optional, + Repeated, +} + +/// Peek at the token stream to decide whether the transform following +/// `=>` is a **new** annotation form (`kind [? | *] { … }`). If so, +/// consume the annotation and return it, leaving the `{ … }` body in +/// the stream for the caller to parse. Otherwise leave the stream +/// untouched and return `None`. +/// +/// The lookahead distinguishes: +/// `kind {` → annotation (single) +/// `kind? {` → annotation (optional) +/// `kind* {` → annotation (repeated) +/// `kind` → shorthand form (no `{` follows) — NOT an annotation +/// anything else → template or bare block — NOT an annotation +fn try_consume_return_annotation(tokens: &mut Tokens) -> Result> { + // Must start with an identifier (the kind name). + let mut lookahead = tokens.clone(); + let Some(TokenTree::Ident(_)) = lookahead.next() else { + return Ok(None); + }; + // Then optionally `?` or `*`, then a `{` group. + let after_suffix = match lookahead.peek() { + Some(TokenTree::Punct(p)) if p.as_char() == '?' || p.as_char() == '*' => { + lookahead.next(); + lookahead.peek() + } + other => other, + }; + if !matches!(after_suffix, Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace) { + return Ok(None); + } + // Commit: consume the ident + suffix from the real stream. + let kind = expect_ident(tokens, "expected output-kind name in annotation")?; + let multiplicity = match tokens.peek() { + Some(TokenTree::Punct(p)) if p.as_char() == '?' => { + tokens.next(); + AnnotationMultiplicity::Optional + } + Some(TokenTree::Punct(p)) if p.as_char() == '*' => { + tokens.next(); + AnnotationMultiplicity::Repeated + } + _ => AnnotationMultiplicity::Single, + }; + Ok(Some(ReturnAnnotation { kind, multiplicity })) +} + /// Parse `rule!( query => transform )`. pub fn parse_rule_top(input: TokenStream) -> Result { let mut tokens = input.into_iter().peekable(); @@ -688,8 +758,52 @@ pub fn parse_rule_top(input: TokenStream) -> Result { }) .collect(); - // Parse transform: either shorthand `=> kind_name` or full `=> (template ...)` - let transform_body = if peek_is_field(&mut tokens) && { + // Parse transform: the token(s) after `=>` fall into one of three + // shapes, dispatched in order: + // + // 1. `kind [? | *] { rust_body }` — annotated Rust body (NEW). + // Static-analysis-ready: the annotation declares the output + // kind and multiplicity in the schema's own vocabulary. + // 2. `kind` alone — shorthand: emit `(kind field: {@cap})…` from + // the query's captures. + // 3. anything else — full template form (`(kind …)` or bare + // `{ … }` splice via `parse_direct_list`). + let annotation = try_consume_return_annotation(&mut tokens)?; + + let transform_body = if let Some(annotation) = annotation { + // Annotation form: `=> kind [? | *] { rust_body }`. + let body_group = expect_group(&mut tokens, Delimiter::Brace)?; + if let Some(tok) = tokens.next() { + return Err(syn::Error::new_spanned( + tok, + "unexpected token after annotated rule body", + )); + } + let body = body_group.stream(); + // The annotation is not yet consumed by codegen — it will drive + // typed handles once the schema-driven codegen lands. For now, + // emit a self-documenting reference to the annotated kind and + // preserve today's `Vec` closure return so behavior + // is unchanged. + let kind_str = annotation.kind.to_string(); + let mult_str = match annotation.multiplicity { + AnnotationMultiplicity::Single => "single", + AnnotationMultiplicity::Optional => "optional", + AnnotationMultiplicity::Repeated => "repeated", + }; + let _ = (kind_str, mult_str); // silence unused warnings until wired + + // For now, adapt the user's typed return value to the framework's + // `Vec` closure result. This uses `IntoFieldIds`, which + // already accepts a bare `Id`, an iterable of ids, or `Option` + // — matching the three annotation multiplicities. + quote! { + let __value = { #body }; + let mut __ids: Vec = Vec::new(); + yeast::IntoFieldIds::extend_into(__value, &mut __ids); + __ids + } + } else if peek_is_field(&mut tokens) && { // Shorthand form: bare identifier = output node kind. // Auto-generate template from captures. let mut lookahead = tokens.clone(); @@ -749,6 +863,26 @@ pub fn parse_rule_top(input: TokenStream) -> Result { vec![__id] } } else { + // Reject bare `{ ... }` transforms — they used to be accepted + // as either a Rust body producing a `Vec` or a template + // consisting of a single `{cap}` splice. Both patterns lost + // static-analysis information (no visible output kind), so we + // now require rules with block bodies to use the annotation + // form `=> kind [? | *] { ... }`. Templates must start with a + // parenthesized node (e.g. `(if_expr ...)`). + if let Some(TokenTree::Group(g)) = tokens.peek() { + if g.delimiter() == Delimiter::Brace { + let span = g.span(); + return Err(syn::Error::new( + span, + "bare `{...}` rule bodies are no longer accepted; \ + use the annotation form `=> kind [? | *] { ... }` \ + (where the kind names the output node's schema kind, \ + optionally suffixed with `?` or `*` for multiplicity)", + )); + } + } + // Full template form let transform_items = parse_direct_list(&mut tokens, &ctx_ident)?; @@ -1026,10 +1160,7 @@ struct NamedString { } fn parse_named_string_arg(tokens: &mut Tokens, expected_name: &str) -> Result { - let name = expect_ident( - tokens, - &format!("expected `{expected_name}:` argument"), - )?; + let name = expect_ident(tokens, &format!("expected `{expected_name}:` argument"))?; if name != expected_name { return Err(syn::Error::new_spanned( name, diff --git a/shared/yeast/doc/yeast.md b/shared/yeast/doc/yeast.md index 3427597be2e6..90edb510c1a1 100644 --- a/shared/yeast/doc/yeast.md +++ b/shared/yeast/doc/yeast.md @@ -312,13 +312,15 @@ already conforms to the output schema. For rules that need the raw (input-schema) capture — typically to read its source text or to translate it explicitly with mutable context state between calls — use `@@name` instead. The body sees the original -input-schema `Id`: +input-schema `Id`. Because these rules always have a Rust block body, +they use the annotation form (see [the `rule!` macro +section](#the-rule-macro) for the full grammar): ```rust yeast::rule!( (assignment left: (_) @@raw_lhs right: (_) @rhs) => - { + call { // raw_lhs is untranslated: read its original source text. let text = ctx.ast.source_text(raw_lhs); // rhs is already translated by the auto-translate prefix. @@ -372,26 +374,79 @@ automatically: single captures bind as `Id`, repeated captures (after ## The `rule!` macro -`rule!` combines a query and a transform into a single declaration: +`rule!` combines a query and a transform into a single declaration. +There are three transform forms, each suited to a different level of +rule complexity: ```rust -// Full template form +// 1. Template form — a tree literal describing the output. yeast::rule!( (query_pattern field: (_) @capture) => (output_template field: {capture}) ) -// Shorthand form — captures become fields on the output node +// 2. Shorthand form — captures become fields on a bare output kind. yeast::rule!( (query_pattern field: (_) @capture) => output_kind ) + +// 3. Annotation form — a Rust block body preceded by the output kind. +yeast::rule!( + (query_pattern child: (_)+ @@children) + => + output_kind* { + // arbitrary Rust; must evaluate to a value compatible with the + // declared multiplicity (see below). + let mut result = Vec::new(); + for child in children { + result.extend(ctx.translate(child)?); + } + result + } +) ``` The shorthand `=> kind` form auto-generates the template, mapping each capture name to a field of the same name on the output node. +### Annotation form + +Rules that need imperative logic — mutating [`BuildCtx`] state per +iteration, computing intermediate values, or looping over captures — +use the annotation form. It has three shapes distinguished by a suffix +on the output-kind identifier: + +| Syntax | Body must evaluate to | Meaning | +|---------------------|-------------------------------------|--------------------------------| +| `=> kind { ... }` | a single node id of `kind` | Emit exactly one node. | +| `=> kind? { ... }` | an `Option` of a node id of `kind` | Emit 0 or 1 nodes (`None`/`Some`). | +| `=> kind* { ... }` | an iterable of node ids of `kind` | Emit 0+ nodes; flattens into the enclosing splice slot. | + +The suffix mirrors the `?` / `*` markers used elsewhere in the schema +DSL (see [`ast_types.yml`](../../../unified/extractor/ast_types.yml)): +bare identifier = required single, `?` = optional single, `*` = +repeated. + +The annotation names the schema kind of the output, giving the macro +enough information for future static analysis (e.g. computing the +static output type of translated captures at their consumer sites). + +**Bare `=> { ... }` block bodies are rejected** — every Rust-block body +must carry an annotation, so the output kind is always visible without +having to inspect the block's expression. + +### Choosing between the forms + +Prefer the simplest form that fits: + +- If the whole transform is a tree literal, use the **template form**. +- If the transform is a template whose root matches a query capture + 1:1, use the **shorthand form**. +- If the transform needs Rust logic (loops, `let` bindings, calls to + `ctx.translate`, etc.), use the **annotation form**. + ## Integration with the extractor A YEAST desugaring pass is configured with a [`DesugaringConfig`], which diff --git a/shared/yeast/tests/test.rs b/shared/yeast/tests/test.rs index 4a2fb26f4fe3..3a24709dd9ff 100644 --- a/shared/yeast/tests/test.rs +++ b/shared/yeast/tests/test.rs @@ -989,7 +989,7 @@ fn test_one_shot_recurses_into_returned_capture() { yeast::rule!( (assignment left: (_) @left right: (_) @right) => - {left} + identifier { left } ), yeast::rule!((identifier) => (identifier "ID")), yeast::rule!((integer) => (integer "INT")), @@ -1084,7 +1084,7 @@ fn test_raw_capture_marker() { yeast::rule!( (assignment left: (_) @@raw_lhs right: (_) @rhs) => - { + call { let text = ctx.ast.source_text(raw_lhs); tree!((call method: (identifier #{text.as_str()}) @@ -1139,7 +1139,7 @@ fn test_raw_capture_marker_explicit_translate() { yeast::rule!( (assignment left: (_) @@raw_lhs right: (_) @rhs) => - { + call { let translated_lhs = ctx.translate(raw_lhs)?; tree!((call method: {translated_lhs} @@ -1442,3 +1442,113 @@ fn test_rules_macro_mixes_bare_and_explicit_forms() { }; assert_eq!(rules.len(), 2); } + +// ---- Rule-body return-type annotation tests ---- +// +// The annotation form `=> kind [? | *] { rust_body }` is the future +// interface for Rust-bodied rules: the schema-vocabulary annotation +// declares the rule's output kind for static analysis. Today's codegen +// does NOT yet consume the annotation (it just adapts the returned +// value to `Vec` via `IntoFieldIds`); these tests only exercise +// the parser + the runtime-equivalence property. + +/// Annotation form with `*` (repeated): the rule body returns a +/// `Vec` and the annotation says the outputs are `assignment`s. +#[test] +fn test_rule_annotation_repeated() { + // Behaviourally equivalent to a two-node splice template. + let r: Rule = rule!( + (assignment left: (_) @l right: (_) @r) + => + assignment* { + let a1 = tree!((assignment left: {l} right: {r})); + let a2 = tree!((assignment left: {r} right: {l})); + vec![a1, a2] + } + ); + let ast = run_and_ast("x = 1", vec![r]); + // Just verify the run completes without a schema error; two + // top-level `assignment` nodes should appear as siblings. + let mut count = 0usize; + for id in ast.reachable_node_ids() { + if let Some(n) = ast.get_node(id) { + if n.kind_name() == "assignment" { + count += 1; + } + } + } + assert!( + count >= 2, + "expected at least two assignment nodes, got {count}" + ); +} + +/// Annotation form with `?` (optional): the rule body returns +/// `Option`. This uses `None` so the rule effectively deletes the +/// node. +#[test] +fn test_rule_annotation_optional_none() { + // Delete every `integer` (returning None yields no output nodes). + let r: Rule = rule!( + (integer) @lit + => + integer? { + let _ = lit; + None:: + } + ); + let ast = run_and_ast("42", vec![r]); + // No integer node should survive. + for id in ast.reachable_node_ids() { + if let Some(n) = ast.get_node(id) { + assert_ne!(n.kind_name(), "integer", "integer should have been deleted"); + } + } +} + +/// Annotation form (single): the rule body returns a bare `Id`. +#[test] +fn test_rule_annotation_single() { + // Identity on assignment nodes, expressed with the annotation form. + let r: Rule = rule!( + (assignment left: (_) @l right: (_) @r) + => + assignment { + tree!((assignment left: {l} right: {r})) + } + ); + let ast = run_and_ast("x = 1", vec![r]); + let mut has_assignment = false; + for id in ast.reachable_node_ids() { + if let Some(n) = ast.get_node(id) { + if n.kind_name() == "assignment" { + has_assignment = true; + } + } + } + assert!(has_assignment, "expected an assignment node"); +} + +/// The shorthand `=> kind` form (no body, no annotation) must still be +/// distinguished from the annotation form and continue to work. +#[test] +fn test_shorthand_still_works_alongside_annotation_syntax() { + let r: Rule = rule!( + (assignment left: (_) @method right: (_) @receiver) + => + call + ); + let ast = run_and_ast("x = 1", vec![r]); + let mut has_call = false; + for id in ast.reachable_node_ids() { + if let Some(n) = ast.get_node(id) { + if n.kind_name() == "call" { + has_call = true; + } + } + } + assert!( + has_call, + "shorthand form should still produce a `call` node" + ); +} diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 54cce1a7fc4c..cb3fdff88753 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -133,8 +133,8 @@ fn translation_rules() -> Vec> { ) ), // Declarations may be wrapped in local/global wrapper nodes. - rule!((global_declaration _ @inner) => {inner}), - rule!((local_declaration _ @inner) => {inner}), + rule!((global_declaration _ @inner) => stmt { inner }), + rule!((local_declaration _ @inner) => stmt { inner }), // ---- Literals ---- rule!((integer_literal) => (int_literal)), rule!((hex_literal) => (int_literal)), @@ -227,7 +227,7 @@ fn translation_rules() -> Vec> { type: _? @ty computed_value: (computed_property accessor: _+ @@accessors)) => - {{ + accessor_declaration* { ctx.property_name = Some(tree!((identifier #{pattern}))); ctx.property_type = ty; @@ -239,7 +239,7 @@ fn translation_rules() -> Vec> { result.extend(ctx.translate(acc)?); } result - }} + } ), // Computed property: shorthand getter (no explicit get/set, just // statements) → a single accessor_declaration with kind "get". @@ -277,7 +277,7 @@ fn translation_rules() -> Vec> { value: _? @@val observers: (willset_didset_block willset: _? @@ws didset: _? @@ds)) => - {{ + member* { // The initializer value must not inherit the binding // context (it may contain patterns, e.g. a switch // expression), so translate it inside a `ctx.scoped` @@ -313,7 +313,7 @@ fn translation_rules() -> Vec> { result.extend(ctx.translate(obs)?); } result - }} + } ), // property_binding with any pattern name (identifier or // destructuring). Reads outer modifiers / chained tag from `ctx`. @@ -351,7 +351,7 @@ fn translation_rules() -> Vec> { declarator: _* @@decls (modifiers)* @mods) => - {{ + member* { let binding_text = ctx.ast.source_text(binding_kind); let binding = ctx.literal("modifier", &binding_text); // The `let`/`var` binding modifier leads the declaration's @@ -365,7 +365,7 @@ fn translation_rules() -> Vec> { result.extend(ctx.translate(decl)?); } result - }} + } ), // ---- Enums ---- // enum_type_parameter → parameter (with optional name as pattern). @@ -425,7 +425,7 @@ fn translation_rules() -> Vec> { rule!( (enum_entry case: _+ @@cases (modifiers)* @mods) => - {{ + member* { ctx.outer_modifiers = mods; let mut result = Vec::new(); @@ -434,7 +434,7 @@ fn translation_rules() -> Vec> { result.extend(ctx.translate(case)?); } result - }} + } ), // Plain assignment: `x = expr` rule!( @@ -449,9 +449,9 @@ fn translation_rules() -> Vec> { (compound_assign_expr target: {target} operator: (infix_operator #{op}) value: {value}) ), // Unwrap `type` wrapper node - rule!((type name: @inner) => {inner}), + rule!((type name: @inner) => type_expr { inner }), // `directly_assignable_expression` is just a wrapper; unwrap it - rule!((directly_assignable_expression expr: @inner) => {inner}), + rule!((directly_assignable_expression expr: @inner) => expr { inner }), // Pattern with bound_identifier → name_pattern. rule!( (pattern bound_identifier: @name) @@ -463,12 +463,12 @@ fn translation_rules() -> Vec> { rule!( (pattern kind: (binding_pattern binding: (value_binding_pattern mutability: @@binding_kind) pattern: @@pattern)) => - {{ + pattern* { let binding_text = ctx.ast.source_text(binding_kind); let binding = ctx.literal("modifier", &binding_text); ctx.outer_modifiers = vec![binding]; ctx.translate(pattern)? - }} + } ), // case T.foo(x,y) pattern rule!( @@ -501,7 +501,7 @@ fn translation_rules() -> Vec> { rule!( (pattern kind: (simple_identifier) @name) => - { + pattern { if ctx.in_binding_pattern() { tree!((name_pattern identifier: (identifier #{name}))) } else { @@ -537,10 +537,10 @@ fn translation_rules() -> Vec> { rule!( (function_parameter parameter: @@p default_value: _? @def) => - {{ + parameter* { ctx.default_value = def; ctx.translate(p)? - }} + } ), // Parameter with external name and type rule!( @@ -763,7 +763,7 @@ fn translation_rules() -> Vec> { element: (pattern_element pattern: (name_pattern identifier: (identifier #{name}))))) ), // If-condition — unwrap (pass through the inner expression/pattern) - rule!((if_condition kind: @inner) => {inner}), + rule!((if_condition kind: @inner) => expr_or_pattern { inner }), // ---- Loops ---- // For-in loop with optional where-clause guard. rule!( @@ -796,7 +796,7 @@ fn translation_rules() -> Vec> { body: (block stmt: {body})) ), // Labeled statement (e.g. `outer: for ...`). Strip the trailing ':' from the label token. - rule!((labeled_statement label: (statement_label) @lbl statement: @stmt) => { + rule!((labeled_statement label: (statement_label) @lbl statement: @stmt) => labeled_stmt { let text = ctx.ast.source_text(lbl); let name = &text[..text.len() - 1]; tree!((labeled_stmt label: (identifier #{name}) stmt: {stmt})) @@ -818,7 +818,7 @@ fn translation_rules() -> Vec> { rule!((dictionary_literal_item key: @k value: @v) => (key_value_pair key: {k} value: {v})), // ---- Optionals and errors ---- // Optional chaining — unwrap the marker - rule!((optional_chain_marker expr: @inner) => {inner}), + rule!((optional_chain_marker expr: @inner) => expr { inner }), // try/try?/try! expr → unary_expr with operator "try", "try?" or "try!" rule!((try_expression (try_operator) @op expr: @inner) => (unary_expr operator: (prefix_operator #{op}) operand: {inner})), rule!((try_expression operator: (try_operator) @op expr: @inner) => (unary_expr operator: (prefix_operator #{op}) operand: {inner})), @@ -874,7 +874,7 @@ fn translation_rules() -> Vec> { rule!( (identifier part: _+ @parts) => - {member_chain(&mut ctx, parts)} + expr { member_chain(&mut ctx, parts) } ), // Scoped import declaration (for example `import struct Foo.Bar`): // flatten the identifier parts into a member_access_expr and bind the @@ -905,7 +905,7 @@ fn translation_rules() -> Vec> { // Super expression → super_expr rule!((super_expression) => (super_expr)), // Modifiers — unwrap to individual modifier children - rule!((modifiers _* @mods) => {mods}), + rule!((modifiers _* @mods) => modifier* { mods }), rule!((attribute) @m => (modifier #{m})), rule!((visibility_modifier) @m => (modifier #{m})), rule!((function_modifier) @m => (modifier #{m})), @@ -917,7 +917,7 @@ fn translation_rules() -> Vec> { rule!((inheritance_modifier) @m => (modifier #{m})), rule!((property_behavior_modifier) @m => (modifier #{m})), // Type annotations — unwrap - rule!((type_annotation type: @inner) => {inner}), + rule!((type_annotation type: @inner) => type_expr { inner }), // user_type is split into simple_user_type parts. // Keep a conservative textual fallback to avoid dropping type information. rule!((user_type) @ty => (named_type_expr name: (identifier #{ty}))), @@ -1092,7 +1092,7 @@ fn translation_rules() -> Vec> { type: _? @ty (modifiers)* @mods) => - {{ + accessor_declaration* { ctx.property_name = Some(tree!((identifier #{name}))); ctx.property_type = ty; ctx.outer_modifiers = mods; @@ -1103,7 +1103,7 @@ fn translation_rules() -> Vec> { result.extend(ctx.translate(acc)?); } result - }} + } ), // getter_specifier / setter_specifier → bodyless accessor_declaration // getter_specifier / setter_specifier → bodyless @@ -1130,7 +1130,7 @@ fn translation_rules() -> Vec> { modifier: {chained_modifier(&mut ctx)}) ), // protocol_property_requirements wrapper — should be consumed by above; fallback - rule!((protocol_property_requirements accessor: _* @accs) => {accs}), + rule!((protocol_property_requirements accessor: _* @accs) => accessor_declaration* { accs }), // Computed getter → accessor_declaration (body optional). // Reads property name/type from the outer property_binding rule // and binding/outer modifiers + chained tag from the outer @@ -1186,7 +1186,7 @@ fn translation_rules() -> Vec> { // willset/didset block — spread to children (only reachable as a // fallback; the outer property_binding manual rule normally // captures the willset/didset clauses directly). - rule!((willset_didset_block _* @clauses) => {clauses}), + rule!((willset_didset_block _* @clauses) => accessor_declaration* { clauses }), // willset clause → accessor_declaration (body optional). Reads // `ctx.property_name` set by the outer property_binding rule and // binding/outer modifiers + chained tag from the outer @@ -1220,11 +1220,6 @@ fn translation_rules() -> Vec> { => (unsupported_node) ), - rule!( - _ @node - => - {node} - ), ] } From 11afcce8b32be1ef6624e61665bc60955f8bc329 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 2 Jul 2026 12:57:43 +0000 Subject: [PATCH 11/60] yeast: Fix bug in matching `(_)` Turns out, `(_)` would match both named and unnamed nodes, as we never checked the value of the `match_unnamed` field. This is the real reason why the final catch-all rule we removed in the last commit was superfluous -- unnamed nodes were being caught by the penultimate rule instead (and mapped to `unsupported_node`). Having fixed the bug, we now (correctly) get errors due to unmatched unnamed nodes in the input. To fix this, we change the catch-all rule to match unnamed nodes as well. This restores the previous behaviour exactly. At some point, we should find a better way to handle unnamed nodes, as it seems wasteful to map these to `unsupported_node` (since we in practice only use them for their string content). Perhaps we should not attempt to translate unnamed nodes at all? --- shared/yeast/src/query.rs | 12 +++++++++++- unified/extractor/src/languages/swift/swift.rs | 11 +++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/shared/yeast/src/query.rs b/shared/yeast/src/query.rs index bcf0f7facab1..3e61ac60b2be 100644 --- a/shared/yeast/src/query.rs +++ b/shared/yeast/src/query.rs @@ -66,7 +66,17 @@ impl QueryNode { pub fn do_match(&self, ast: &Ast, node: Id, matches: &mut Captures) -> Result { match self { - QueryNode::Any { .. } => Ok(true), + QueryNode::Any { match_unnamed } => { + if *match_unnamed { + Ok(true) + } else { + // `(_)` only matches named nodes, matching tree-sitter + // semantics. Bare `_` (with `match_unnamed = true`) + // matches any node. + let n = ast.get_node(node).unwrap(); + Ok(n.is_named()) + } + } QueryNode::Node { kind, children } => { let node = ast.get_node(node).unwrap(); let target_kind = ast diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index cb3fdff88753..74809ea18e41 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -1215,8 +1215,15 @@ fn translation_rules() -> Vec> { // Preprocessor conditionals — unsupported rule!((diagnostic) => (unsupported_node)), // ---- Fallbacks ---- - rule!( - (_) + // Bare `_` (rather than `(_)`) so this matches both named nodes + // and unnamed tokens. Any unnamed token that escapes the + // input-schema-specific rules (e.g. captured operators in + // `additive_expression op: @op`) has its auto-translated value + // replaced with an `unsupported_node` whose source range is + // inherited from the original token, so `#{op}` still reads the + // original text. + rule!( + _ => (unsupported_node) ), From d609680083f078bdfec7f1acbed55ed9c79c27d2 Mon Sep 17 00:00:00 2001 From: Taus Date: Thu, 2 Jul 2026 21:03:59 +0000 Subject: [PATCH 12/60] yeast: Fix escaping bug in yeast-macros Happily, it turned out that there was already a library function for handling this case. --- shared/yeast-macros/src/parse.rs | 37 +++++--------------------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index 986a9bac641a..01c0b574b1cf 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -1183,39 +1183,12 @@ fn parse_named_string_arg(tokens: &mut Tokens, expected_name: &str) -> Result Option { - let raw = lit.to_string(); - let bytes = raw.as_bytes(); - // Match plain `"..."` literals; reject byte strings, raw strings (for - // simplicity), char literals, numbers, etc. - if bytes.first() != Some(&b'"') || bytes.last() != Some(&b'"') { - return None; - } - let mut out = String::with_capacity(raw.len()); - let mut chars = raw[1..raw.len() - 1].chars(); - while let Some(c) = chars.next() { - if c != '\\' { - out.push(c); - continue; - } - match chars.next()? { - 'n' => out.push('\n'), - 't' => out.push('\t'), - 'r' => out.push('\r'), - '\\' => out.push('\\'), - '\'' => out.push('\''), - '"' => out.push('"'), - '0' => out.push('\0'), - other => { - // Unknown escape — give up rather than silently mis-parse. - out.push('\\'); - out.push(other); - } - } - } - Some(out) + let tokens = TokenStream::from(TokenTree::Literal(lit.clone())); + syn::parse2::(tokens).ok().map(|s| s.value()) } /// Split a token stream into top-level comma-separated items. Commas inside From 233c3ce30d70aba4d450ee280adc95b8cfe6f64d Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Thu, 9 Jul 2026 16:47:49 +0100 Subject: [PATCH 13/60] Fix incorrect sanitizers --- ...6-07-09-remove-incorrect-path-sanitizer.md | 4 ++ go/ql/lib/ext/path.filepath.model.yml | 5 --- .../go/security/TaintedPathCustomizations.qll | 37 ------------------- .../Security/CWE-022/TaintedPath.expected | 15 +++++++- .../Security/CWE-022/TaintedPath.go | 8 ++-- .../Security/CWE-022/ZipSlip.expected | 17 ++++++++- .../test/query-tests/Security/CWE-022/tst.go | 8 ++-- 7 files changed, 40 insertions(+), 54 deletions(-) create mode 100644 go/ql/lib/change-notes/2026-07-09-remove-incorrect-path-sanitizer.md diff --git a/go/ql/lib/change-notes/2026-07-09-remove-incorrect-path-sanitizer.md b/go/ql/lib/change-notes/2026-07-09-remove-incorrect-path-sanitizer.md new file mode 100644 index 000000000000..4e7a4be2cafe --- /dev/null +++ b/go/ql/lib/change-notes/2026-07-09-remove-incorrect-path-sanitizer.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* The function `Rel` in `path/filepath` was incorrectly considered a sanitizer for `go/path-injection` and `go/zipslip`. This has now been fixed, which may lead to more results for those queries. diff --git a/go/ql/lib/ext/path.filepath.model.yml b/go/ql/lib/ext/path.filepath.model.yml index d450e2bbc568..15bcb7d386d8 100644 --- a/go/ql/lib/ext/path.filepath.model.yml +++ b/go/ql/lib/ext/path.filepath.model.yml @@ -1,9 +1,4 @@ extensions: - - addsTo: - pack: codeql/go-all - extensible: barrierModel - data: - - ["path/filepath", "", False, "Rel", "", "", "ReturnValue", "path-injection", "manual"] - addsTo: pack: codeql/go-all extensible: summaryModel diff --git a/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll b/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll index 20341159c64c..d49365537373 100644 --- a/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll +++ b/go/ql/lib/semmle/go/security/TaintedPathCustomizations.qll @@ -129,43 +129,6 @@ module TaintedPath { DotDotReplaceAll() { this.getReplacedString() = ["..", "."] } } - /** - * A node `nd` guarded by a check that ensures it is contained within some root folder, - * considered as a sanitizer for path traversal. - * - * We currently recognize checks of the following form: - * - * ``` - * ..., err := filepath.Rel(base, path) - * if err == nil { - * // path is known to be contained in base - * } - * ``` - */ - class PathContainmentCheck extends SanitizerGuard, DataFlow::EqualityTestNode { - DataFlow::Node path; - boolean outcome; - - PathContainmentCheck() { - exists(Function f, FunctionInput inp, FunctionOutput outp, DataFlow::Property p | - f.hasQualifiedName("path/filepath", "Rel") and - inp.isParameter(1) and - outp.isResult(1) and - p.isNil() - | - exists(DataFlow::Node call, DataFlow::Node res | - call = f.getACall() and - DataFlow::localFlow(outp.getNode(call), res) - | - p.checkOn(this, outcome, res) and - path = inp.getNode(call) - ) - ) - } - - override predicate checks(Expr e, boolean branch) { e = path.asExpr() and branch = outcome } - } - /** * A call of the form `strings.HasPrefix(path, ...)` considered as a sanitizer guard * for `path`. diff --git a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected index c95fa5e7af62..851ed3533ae9 100644 --- a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected +++ b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.expected @@ -1,25 +1,36 @@ #select | TaintedPath.go:18:29:18:40 | tainted_path | TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:18:29:18:40 | tainted_path | This path depends on a $@. | TaintedPath.go:15:18:15:22 | selection of URL | user-provided value | | TaintedPath.go:22:28:22:69 | call to Join | TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:22:28:22:69 | call to Join | This path depends on a $@. | TaintedPath.go:15:18:15:22 | selection of URL | user-provided value | +| TaintedPath.go:27:28:27:45 | sanitized_filepath | TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:27:28:27:45 | sanitized_filepath | This path depends on a $@. | TaintedPath.go:15:18:15:22 | selection of URL | user-provided value | +| TaintedPath.go:43:29:43:40 | tainted_path | TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:43:29:43:40 | tainted_path | This path depends on a $@. | TaintedPath.go:15:18:15:22 | selection of URL | user-provided value | | TaintedPath.go:74:28:74:57 | call to Clean | TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:74:28:74:57 | call to Clean | This path depends on a $@. | TaintedPath.go:15:18:15:22 | selection of URL | user-provided value | edges | TaintedPath.go:15:18:15:22 | selection of URL | TaintedPath.go:15:18:15:30 | call to Query | provenance | Src:MaD:2 MaD:3 | | TaintedPath.go:15:18:15:30 | call to Query | TaintedPath.go:18:29:18:40 | tainted_path | provenance | Sink:MaD:1 | | TaintedPath.go:15:18:15:30 | call to Query | TaintedPath.go:22:57:22:68 | tainted_path | provenance | | | TaintedPath.go:22:57:22:68 | tainted_path | TaintedPath.go:22:28:22:69 | call to Join | provenance | FunctionModel Sink:MaD:1 | +| TaintedPath.go:22:57:22:68 | tainted_path | TaintedPath.go:26:63:26:74 | tainted_path | provenance | | +| TaintedPath.go:22:57:22:68 | tainted_path | TaintedPath.go:43:29:43:40 | tainted_path | provenance | Sink:MaD:1 | | TaintedPath.go:22:57:22:68 | tainted_path | TaintedPath.go:74:39:74:56 | ...+... | provenance | | -| TaintedPath.go:74:39:74:56 | ...+... | TaintedPath.go:74:28:74:57 | call to Clean | provenance | MaD:4 Sink:MaD:1 | +| TaintedPath.go:26:2:26:75 | ... := ...[0] | TaintedPath.go:27:28:27:45 | sanitized_filepath | provenance | Sink:MaD:1 | +| TaintedPath.go:26:63:26:74 | tainted_path | TaintedPath.go:26:2:26:75 | ... := ...[0] | provenance | MaD:4 | +| TaintedPath.go:74:39:74:56 | ...+... | TaintedPath.go:74:28:74:57 | call to Clean | provenance | MaD:5 Sink:MaD:1 | models | 1 | Sink: io/ioutil; ; false; ReadFile; ; ; Argument[0]; path-injection; manual | | 2 | Source: net/http; Request; true; URL; ; ; ; remote; manual | | 3 | Summary: net/url; URL; true; Query; ; ; Argument[receiver]; ReturnValue; taint; manual | -| 4 | Summary: path; ; false; Clean; ; ; Argument[0]; ReturnValue; taint; manual | +| 4 | Summary: path/filepath; ; false; Rel; ; ; Argument[0..1]; ReturnValue[0]; taint; manual | +| 5 | Summary: path; ; false; Clean; ; ; Argument[0]; ReturnValue; taint; manual | nodes | TaintedPath.go:15:18:15:22 | selection of URL | semmle.label | selection of URL | | TaintedPath.go:15:18:15:30 | call to Query | semmle.label | call to Query | | TaintedPath.go:18:29:18:40 | tainted_path | semmle.label | tainted_path | | TaintedPath.go:22:28:22:69 | call to Join | semmle.label | call to Join | | TaintedPath.go:22:57:22:68 | tainted_path | semmle.label | tainted_path | +| TaintedPath.go:26:2:26:75 | ... := ...[0] | semmle.label | ... := ...[0] | +| TaintedPath.go:26:63:26:74 | tainted_path | semmle.label | tainted_path | +| TaintedPath.go:27:28:27:45 | sanitized_filepath | semmle.label | sanitized_filepath | +| TaintedPath.go:43:29:43:40 | tainted_path | semmle.label | tainted_path | | TaintedPath.go:74:28:74:57 | call to Clean | semmle.label | call to Clean | | TaintedPath.go:74:39:74:56 | ...+... | semmle.label | ...+... | subpaths diff --git a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go index 812b56f7c94b..680c108d0562 100644 --- a/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go +++ b/go/ql/test/query-tests/Security/CWE-022/TaintedPath.go @@ -22,9 +22,9 @@ func handler(w http.ResponseWriter, r *http.Request) { data, _ = ioutil.ReadFile(filepath.Join("/home/user/", tainted_path)) // $ Alert[go/path-injection] w.Write(data) - // GOOD: This can only read inside the provided safe path + // BAD: This could still read any file on the file system sanitized_filepath, _ := filepath.Rel("/home/user/safepath", tainted_path) - data, _ = ioutil.ReadFile(sanitized_filepath) + data, _ = ioutil.ReadFile(sanitized_filepath) // $ Alert[go/path-injection] w.Write(data) // GOOD: This can only read inside the provided safe path @@ -37,10 +37,10 @@ func handler(w http.ResponseWriter, r *http.Request) { data, _ = ioutil.ReadFile(strings.ReplaceAll(tainted_path, "..", "")) w.Write(data) - // GOOD: This can only read inside the provided safe path + // BAD: This could still read any file on the file system _, err := filepath.Rel("/home/user/safepath", tainted_path) if err == nil { - data, _ = ioutil.ReadFile(tainted_path) + data, _ = ioutil.ReadFile(tainted_path) // $ Alert[go/path-injection] w.Write(data) } diff --git a/go/ql/test/query-tests/Security/CWE-022/ZipSlip.expected b/go/ql/test/query-tests/Security/CWE-022/ZipSlip.expected index 3bfd80a120ca..88f1666af3b9 100644 --- a/go/ql/test/query-tests/Security/CWE-022/ZipSlip.expected +++ b/go/ql/test/query-tests/Security/CWE-022/ZipSlip.expected @@ -2,7 +2,9 @@ | UnsafeUnzipSymlinkGood.go:72:3:72:25 | ... := ...[0] | UnsafeUnzipSymlinkGood.go:72:3:72:25 | ... := ...[0] | UnsafeUnzipSymlinkGood.go:61:31:61:62 | call to Join | Unsanitized archive entry, which may contain '..', is used in a $@. | UnsafeUnzipSymlinkGood.go:61:31:61:62 | call to Join | file system operation | | ZipSlip.go:11:2:15:2 | range statement[1] | ZipSlip.go:11:2:15:2 | range statement[1] | ZipSlip.go:14:20:14:20 | p | Unsanitized archive entry, which may contain '..', is used in a $@. | ZipSlip.go:14:20:14:20 | p | file system operation | | tarslip.go:15:2:15:30 | ... := ...[0] | tarslip.go:15:2:15:30 | ... := ...[0] | tarslip.go:16:14:16:34 | call to Dir | Unsanitized archive entry, which may contain '..', is used in a $@. | tarslip.go:16:14:16:34 | call to Dir | file system operation | +| tst.go:23:2:43:2 | range statement[1] | tst.go:23:2:43:2 | range statement[1] | tst.go:27:21:27:48 | call to Join | Unsanitized archive entry, which may contain '..', is used in a $@. | tst.go:27:21:27:48 | call to Join | file system operation | | tst.go:23:2:43:2 | range statement[1] | tst.go:23:2:43:2 | range statement[1] | tst.go:29:20:29:23 | path | Unsanitized archive entry, which may contain '..', is used in a $@. | tst.go:29:20:29:23 | path | file system operation | +| tst.go:23:2:43:2 | range statement[1] | tst.go:23:2:43:2 | range statement[1] | tst.go:31:21:31:24 | path | Unsanitized archive entry, which may contain '..', is used in a $@. | tst.go:31:21:31:24 | path | file system operation | edges | UnsafeUnzipSymlinkGood.go:52:24:52:32 | SSA def(candidate) | UnsafeUnzipSymlinkGood.go:61:53:61:61 | candidate | provenance | | | UnsafeUnzipSymlinkGood.go:61:53:61:61 | candidate | UnsafeUnzipSymlinkGood.go:61:31:61:62 | call to Join | provenance | FunctionModel Sink:MaD:3 | @@ -14,14 +16,20 @@ edges | ZipSlip.go:12:3:12:30 | ... := ...[0] | ZipSlip.go:14:20:14:20 | p | provenance | Sink:MaD:1 | | ZipSlip.go:12:24:12:29 | selection of Name | ZipSlip.go:12:3:12:30 | ... := ...[0] | provenance | MaD:4 | | tarslip.go:15:2:15:30 | ... := ...[0] | tarslip.go:16:23:16:33 | selection of Name | provenance | | -| tarslip.go:16:23:16:33 | selection of Name | tarslip.go:16:14:16:34 | call to Dir | provenance | MaD:5 Sink:MaD:2 | +| tarslip.go:16:23:16:33 | selection of Name | tarslip.go:16:14:16:34 | call to Dir | provenance | MaD:6 Sink:MaD:2 | +| tst.go:23:2:43:2 | range statement[1] | tst.go:25:38:25:41 | path | provenance | | | tst.go:23:2:43:2 | range statement[1] | tst.go:29:20:29:23 | path | provenance | Sink:MaD:1 | +| tst.go:23:2:43:2 | range statement[1] | tst.go:31:21:31:24 | path | provenance | Sink:MaD:1 | +| tst.go:25:3:25:42 | ... := ...[0] | tst.go:27:41:27:47 | relpath | provenance | | +| tst.go:25:38:25:41 | path | tst.go:25:3:25:42 | ... := ...[0] | provenance | MaD:5 | +| tst.go:27:41:27:47 | relpath | tst.go:27:21:27:48 | call to Join | provenance | FunctionModel Sink:MaD:1 | models | 1 | Sink: io/ioutil; ; false; WriteFile; ; ; Argument[0]; path-injection; manual | | 2 | Sink: os; ; false; MkdirAll; ; ; Argument[0]; path-injection; manual | | 3 | Sink: os; ; false; Readlink; ; ; Argument[0]; path-injection; manual | | 4 | Summary: path/filepath; ; false; Abs; ; ; Argument[0]; ReturnValue[0]; taint; manual | -| 5 | Summary: path; ; false; Dir; ; ; Argument[0]; ReturnValue; taint; manual | +| 5 | Summary: path/filepath; ; false; Rel; ; ; Argument[0..1]; ReturnValue[0]; taint; manual | +| 6 | Summary: path; ; false; Dir; ; ; Argument[0]; ReturnValue; taint; manual | nodes | UnsafeUnzipSymlinkGood.go:52:24:52:32 | SSA def(candidate) | semmle.label | SSA def(candidate) | | UnsafeUnzipSymlinkGood.go:61:31:61:62 | call to Join | semmle.label | call to Join | @@ -37,5 +45,10 @@ nodes | tarslip.go:16:14:16:34 | call to Dir | semmle.label | call to Dir | | tarslip.go:16:23:16:33 | selection of Name | semmle.label | selection of Name | | tst.go:23:2:43:2 | range statement[1] | semmle.label | range statement[1] | +| tst.go:25:3:25:42 | ... := ...[0] | semmle.label | ... := ...[0] | +| tst.go:25:38:25:41 | path | semmle.label | path | +| tst.go:27:21:27:48 | call to Join | semmle.label | call to Join | +| tst.go:27:41:27:47 | relpath | semmle.label | relpath | | tst.go:29:20:29:23 | path | semmle.label | path | +| tst.go:31:21:31:24 | path | semmle.label | path | subpaths diff --git a/go/ql/test/query-tests/Security/CWE-022/tst.go b/go/ql/test/query-tests/Security/CWE-022/tst.go index 4cf3a77c4c8d..33b2aa072c0d 100644 --- a/go/ql/test/query-tests/Security/CWE-022/tst.go +++ b/go/ql/test/query-tests/Security/CWE-022/tst.go @@ -14,7 +14,7 @@ func uploadFile(w http.ResponseWriter, r *http.Request) { file, handler, _ := r.FormFile("file") // err handling defer file.Close() - tempFile, _ := ioutil.TempFile("/tmp", handler.Filename) // NOT OK + tempFile, _ := ioutil.TempFile("/tmp", handler.Filename) // OK use(tempFile) } @@ -24,11 +24,11 @@ func unzip2(f string, root string) { path := f.Name relpath, err := filepath.Rel(root, path) if err == nil { - ioutil.WriteFile(filepath.Join(root, relpath), []byte("present"), 0666) // OK + ioutil.WriteFile(filepath.Join(root, relpath), []byte("present"), 0666) // $ Sink[go/zipslip] } - ioutil.WriteFile(path, []byte("present"), 0666) // $ Sink[go/zipslip] // NOT OK + ioutil.WriteFile(path, []byte("present"), 0666) // $ Sink[go/zipslip] if containedIn(path, root) { - ioutil.WriteFile(path, []byte("present"), 0666) // OK + ioutil.WriteFile(path, []byte("present"), 0666) // $ Sink[go/zipslip] } if ok, _ := regexp.MatchString("^[a-z]*$", path); ok { ioutil.WriteFile(path, []byte("present"), 0666) // OK From ec83894d48760db35e3f6cc47c239880fcaedc5d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 9 Jul 2026 17:25:35 +0000 Subject: [PATCH 14/60] Post-release preparation for codeql-cli-2.26.1 --- actions/ql/lib/qlpack.yml | 2 +- actions/ql/src/qlpack.yml | 2 +- cpp/ql/lib/qlpack.yml | 2 +- cpp/ql/src/qlpack.yml | 2 +- csharp/ql/campaigns/Solorigate/lib/qlpack.yml | 2 +- csharp/ql/campaigns/Solorigate/src/qlpack.yml | 2 +- csharp/ql/lib/qlpack.yml | 2 +- csharp/ql/src/qlpack.yml | 2 +- go/ql/consistency-queries/qlpack.yml | 2 +- go/ql/lib/qlpack.yml | 2 +- go/ql/src/qlpack.yml | 2 +- java/ql/lib/qlpack.yml | 2 +- java/ql/src/qlpack.yml | 2 +- javascript/ql/lib/qlpack.yml | 2 +- javascript/ql/src/qlpack.yml | 2 +- misc/suite-helpers/qlpack.yml | 2 +- python/ql/lib/qlpack.yml | 2 +- python/ql/src/qlpack.yml | 2 +- ruby/ql/lib/qlpack.yml | 2 +- ruby/ql/src/qlpack.yml | 2 +- rust/ql/lib/qlpack.yml | 2 +- rust/ql/src/qlpack.yml | 2 +- shared/concepts/qlpack.yml | 2 +- shared/controlflow/qlpack.yml | 2 +- shared/dataflow/qlpack.yml | 2 +- shared/mad/qlpack.yml | 2 +- shared/namebinding/qlpack.yml | 2 +- shared/quantum/qlpack.yml | 2 +- shared/rangeanalysis/qlpack.yml | 2 +- shared/regex/qlpack.yml | 2 +- shared/ssa/qlpack.yml | 2 +- shared/threat-models/qlpack.yml | 2 +- shared/tutorial/qlpack.yml | 2 +- shared/typeflow/qlpack.yml | 2 +- shared/typeinference/qlpack.yml | 2 +- shared/typetracking/qlpack.yml | 2 +- shared/typos/qlpack.yml | 2 +- shared/util/qlpack.yml | 2 +- shared/xml/qlpack.yml | 2 +- shared/yaml/qlpack.yml | 2 +- swift/ql/lib/qlpack.yml | 2 +- swift/ql/src/qlpack.yml | 2 +- 42 files changed, 42 insertions(+), 42 deletions(-) diff --git a/actions/ql/lib/qlpack.yml b/actions/ql/lib/qlpack.yml index 77ecbfaa8f17..fec6a9446d0d 100644 --- a/actions/ql/lib/qlpack.yml +++ b/actions/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/actions-all -version: 0.4.39 +version: 0.4.40-dev library: true warnOnImplicitThis: true dependencies: diff --git a/actions/ql/src/qlpack.yml b/actions/ql/src/qlpack.yml index b623ff9b903b..2d53972a2eaa 100644 --- a/actions/ql/src/qlpack.yml +++ b/actions/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/actions-queries -version: 0.6.31 +version: 0.6.32-dev library: false warnOnImplicitThis: true groups: [actions, queries] diff --git a/cpp/ql/lib/qlpack.yml b/cpp/ql/lib/qlpack.yml index 5f4b92a191fc..73226a267c9e 100644 --- a/cpp/ql/lib/qlpack.yml +++ b/cpp/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/cpp-all -version: 12.0.0 +version: 12.0.1-dev groups: cpp dbscheme: semmlecode.cpp.dbscheme extractor: cpp diff --git a/cpp/ql/src/qlpack.yml b/cpp/ql/src/qlpack.yml index 1d1a9206a28e..b193e3ea3603 100644 --- a/cpp/ql/src/qlpack.yml +++ b/cpp/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/cpp-queries -version: 1.7.0 +version: 1.7.1-dev groups: - cpp - queries diff --git a/csharp/ql/campaigns/Solorigate/lib/qlpack.yml b/csharp/ql/campaigns/Solorigate/lib/qlpack.yml index 9fb3427e685c..e33ebfaa5cc6 100644 --- a/csharp/ql/campaigns/Solorigate/lib/qlpack.yml +++ b/csharp/ql/campaigns/Solorigate/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-solorigate-all -version: 1.7.70 +version: 1.7.71-dev groups: - csharp - solorigate diff --git a/csharp/ql/campaigns/Solorigate/src/qlpack.yml b/csharp/ql/campaigns/Solorigate/src/qlpack.yml index 4b55c9412078..b25077c16670 100644 --- a/csharp/ql/campaigns/Solorigate/src/qlpack.yml +++ b/csharp/ql/campaigns/Solorigate/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-solorigate-queries -version: 1.7.70 +version: 1.7.71-dev groups: - csharp - solorigate diff --git a/csharp/ql/lib/qlpack.yml b/csharp/ql/lib/qlpack.yml index 336a7c6be731..0a034f90073c 100644 --- a/csharp/ql/lib/qlpack.yml +++ b/csharp/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-all -version: 7.1.0 +version: 7.1.1-dev groups: csharp dbscheme: semmlecode.csharp.dbscheme extractor: csharp diff --git a/csharp/ql/src/qlpack.yml b/csharp/ql/src/qlpack.yml index b243e8fb928d..ee88fc20f703 100644 --- a/csharp/ql/src/qlpack.yml +++ b/csharp/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/csharp-queries -version: 1.8.0 +version: 1.8.1-dev groups: - csharp - queries diff --git a/go/ql/consistency-queries/qlpack.yml b/go/ql/consistency-queries/qlpack.yml index d7752bb1bf64..d5e7e5af81e5 100644 --- a/go/ql/consistency-queries/qlpack.yml +++ b/go/ql/consistency-queries/qlpack.yml @@ -1,5 +1,5 @@ name: codeql-go-consistency-queries -version: 1.0.53 +version: 1.0.54-dev groups: - go - queries diff --git a/go/ql/lib/qlpack.yml b/go/ql/lib/qlpack.yml index 1573f5b2ad8f..dd21ea23eec0 100644 --- a/go/ql/lib/qlpack.yml +++ b/go/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/go-all -version: 7.2.1 +version: 7.2.2-dev groups: go dbscheme: go.dbscheme extractor: go diff --git a/go/ql/src/qlpack.yml b/go/ql/src/qlpack.yml index 98b0c2cf12c0..68359b55c923 100644 --- a/go/ql/src/qlpack.yml +++ b/go/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/go-queries -version: 1.6.6 +version: 1.6.7-dev groups: - go - queries diff --git a/java/ql/lib/qlpack.yml b/java/ql/lib/qlpack.yml index a3664696b9da..09b4eb797c77 100644 --- a/java/ql/lib/qlpack.yml +++ b/java/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/java-all -version: 9.2.1 +version: 9.2.2-dev groups: java dbscheme: config/semmlecode.dbscheme extractor: java diff --git a/java/ql/src/qlpack.yml b/java/ql/src/qlpack.yml index 2c49d49c6172..4dc88f1fa1c8 100644 --- a/java/ql/src/qlpack.yml +++ b/java/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/java-queries -version: 1.11.6 +version: 1.11.7-dev groups: - java - queries diff --git a/javascript/ql/lib/qlpack.yml b/javascript/ql/lib/qlpack.yml index 3ffde4f1d31a..9ffe3dc1a055 100644 --- a/javascript/ql/lib/qlpack.yml +++ b/javascript/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/javascript-all -version: 2.8.1 +version: 2.8.2-dev groups: javascript dbscheme: semmlecode.javascript.dbscheme extractor: javascript diff --git a/javascript/ql/src/qlpack.yml b/javascript/ql/src/qlpack.yml index 3a88e0c6b139..56c7b6675e1b 100644 --- a/javascript/ql/src/qlpack.yml +++ b/javascript/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/javascript-queries -version: 2.4.1 +version: 2.4.2-dev groups: - javascript - queries diff --git a/misc/suite-helpers/qlpack.yml b/misc/suite-helpers/qlpack.yml index 248b9c7207cf..38df6eb9512a 100644 --- a/misc/suite-helpers/qlpack.yml +++ b/misc/suite-helpers/qlpack.yml @@ -1,4 +1,4 @@ name: codeql/suite-helpers -version: 1.0.53 +version: 1.0.54-dev groups: shared warnOnImplicitThis: true diff --git a/python/ql/lib/qlpack.yml b/python/ql/lib/qlpack.yml index 837f6c437dd5..415f397df570 100644 --- a/python/ql/lib/qlpack.yml +++ b/python/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/python-all -version: 7.2.1 +version: 7.2.2-dev groups: python dbscheme: semmlecode.python.dbscheme extractor: python diff --git a/python/ql/src/qlpack.yml b/python/ql/src/qlpack.yml index 5e0040f834fb..703a6ecb6f51 100644 --- a/python/ql/src/qlpack.yml +++ b/python/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/python-queries -version: 1.8.6 +version: 1.8.7-dev groups: - python - queries diff --git a/ruby/ql/lib/qlpack.yml b/ruby/ql/lib/qlpack.yml index ed80e6ead32c..4ec86b3f63b6 100644 --- a/ruby/ql/lib/qlpack.yml +++ b/ruby/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/ruby-all -version: 6.0.1 +version: 6.0.2-dev groups: ruby extractor: ruby dbscheme: ruby.dbscheme diff --git a/ruby/ql/src/qlpack.yml b/ruby/ql/src/qlpack.yml index 1016e0294afc..2b01f60bc656 100644 --- a/ruby/ql/src/qlpack.yml +++ b/ruby/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/ruby-queries -version: 1.6.6 +version: 1.6.7-dev groups: - ruby - queries diff --git a/rust/ql/lib/qlpack.yml b/rust/ql/lib/qlpack.yml index 74edd9e3549d..94135af275f7 100644 --- a/rust/ql/lib/qlpack.yml +++ b/rust/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/rust-all -version: 0.2.17 +version: 0.2.18-dev groups: rust extractor: rust dbscheme: rust.dbscheme diff --git a/rust/ql/src/qlpack.yml b/rust/ql/src/qlpack.yml index ecc76c64f3bb..7fdf25e71220 100644 --- a/rust/ql/src/qlpack.yml +++ b/rust/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/rust-queries -version: 0.1.38 +version: 0.1.39-dev groups: - rust - queries diff --git a/shared/concepts/qlpack.yml b/shared/concepts/qlpack.yml index b7f9407b10b0..fae2ee681789 100644 --- a/shared/concepts/qlpack.yml +++ b/shared/concepts/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/concepts -version: 0.0.27 +version: 0.0.28-dev groups: shared library: true dependencies: diff --git a/shared/controlflow/qlpack.yml b/shared/controlflow/qlpack.yml index 4fdac9e19299..f18fc3f099b3 100644 --- a/shared/controlflow/qlpack.yml +++ b/shared/controlflow/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/controlflow -version: 2.0.37 +version: 2.0.38-dev groups: shared library: true dependencies: diff --git a/shared/dataflow/qlpack.yml b/shared/dataflow/qlpack.yml index 24de3405f909..4c5e24dafcb7 100644 --- a/shared/dataflow/qlpack.yml +++ b/shared/dataflow/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/dataflow -version: 2.1.9 +version: 2.1.10-dev groups: shared library: true dependencies: diff --git a/shared/mad/qlpack.yml b/shared/mad/qlpack.yml index 44bf6c5c6a6d..79f075ca1f0c 100644 --- a/shared/mad/qlpack.yml +++ b/shared/mad/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/mad -version: 1.0.53 +version: 1.0.54-dev groups: shared library: true dependencies: diff --git a/shared/namebinding/qlpack.yml b/shared/namebinding/qlpack.yml index 9bd12561910a..2e7acee2ef87 100644 --- a/shared/namebinding/qlpack.yml +++ b/shared/namebinding/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/namebinding -version: 0.0.2 +version: 0.0.3-dev groups: shared library: true dependencies: diff --git a/shared/quantum/qlpack.yml b/shared/quantum/qlpack.yml index 9f8995a2ca64..2b3c84caceea 100644 --- a/shared/quantum/qlpack.yml +++ b/shared/quantum/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/quantum -version: 0.0.31 +version: 0.0.32-dev groups: shared library: true dependencies: diff --git a/shared/rangeanalysis/qlpack.yml b/shared/rangeanalysis/qlpack.yml index b934c84900c1..e1368aa2345e 100644 --- a/shared/rangeanalysis/qlpack.yml +++ b/shared/rangeanalysis/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/rangeanalysis -version: 1.0.53 +version: 1.0.54-dev groups: shared library: true dependencies: diff --git a/shared/regex/qlpack.yml b/shared/regex/qlpack.yml index c64f4833d95f..3de28f64f281 100644 --- a/shared/regex/qlpack.yml +++ b/shared/regex/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/regex -version: 1.0.53 +version: 1.0.54-dev groups: shared library: true dependencies: diff --git a/shared/ssa/qlpack.yml b/shared/ssa/qlpack.yml index d92b095c581f..be9a88a001af 100644 --- a/shared/ssa/qlpack.yml +++ b/shared/ssa/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/ssa -version: 2.0.29 +version: 2.0.30-dev groups: shared library: true dependencies: diff --git a/shared/threat-models/qlpack.yml b/shared/threat-models/qlpack.yml index 782bde4ee5cc..b63415957428 100644 --- a/shared/threat-models/qlpack.yml +++ b/shared/threat-models/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/threat-models -version: 1.0.53 +version: 1.0.54-dev library: true groups: shared dataExtensions: diff --git a/shared/tutorial/qlpack.yml b/shared/tutorial/qlpack.yml index 44f487b4b396..f1cc9f375cf1 100644 --- a/shared/tutorial/qlpack.yml +++ b/shared/tutorial/qlpack.yml @@ -1,7 +1,7 @@ name: codeql/tutorial description: Library for the CodeQL detective tutorials, helping new users learn to write CodeQL queries. -version: 1.0.53 +version: 1.0.54-dev groups: shared library: true warnOnImplicitThis: true diff --git a/shared/typeflow/qlpack.yml b/shared/typeflow/qlpack.yml index 5545bfb44220..c69f8203a95a 100644 --- a/shared/typeflow/qlpack.yml +++ b/shared/typeflow/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/typeflow -version: 1.0.53 +version: 1.0.54-dev groups: shared library: true dependencies: diff --git a/shared/typeinference/qlpack.yml b/shared/typeinference/qlpack.yml index b9625bdbc6da..0d181eacd022 100644 --- a/shared/typeinference/qlpack.yml +++ b/shared/typeinference/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/typeinference -version: 0.0.34 +version: 0.0.35-dev groups: shared library: true dependencies: diff --git a/shared/typetracking/qlpack.yml b/shared/typetracking/qlpack.yml index 2f007b6795d2..fe58d4175564 100644 --- a/shared/typetracking/qlpack.yml +++ b/shared/typetracking/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/typetracking -version: 2.0.37 +version: 2.0.38-dev groups: shared library: true dependencies: diff --git a/shared/typos/qlpack.yml b/shared/typos/qlpack.yml index 3b6940512a64..e6465e717341 100644 --- a/shared/typos/qlpack.yml +++ b/shared/typos/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/typos -version: 1.0.53 +version: 1.0.54-dev groups: shared library: true warnOnImplicitThis: true diff --git a/shared/util/qlpack.yml b/shared/util/qlpack.yml index 44ea768668f8..7c1589ce7c60 100644 --- a/shared/util/qlpack.yml +++ b/shared/util/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/util -version: 2.0.40 +version: 2.0.41-dev groups: shared library: true dependencies: null diff --git a/shared/xml/qlpack.yml b/shared/xml/qlpack.yml index 6c506e429871..bb3f29d17187 100644 --- a/shared/xml/qlpack.yml +++ b/shared/xml/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/xml -version: 1.0.53 +version: 1.0.54-dev groups: shared library: true dependencies: diff --git a/shared/yaml/qlpack.yml b/shared/yaml/qlpack.yml index a9b004ad8ff0..e169087c6244 100644 --- a/shared/yaml/qlpack.yml +++ b/shared/yaml/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/yaml -version: 1.0.53 +version: 1.0.54-dev groups: shared library: true warnOnImplicitThis: true diff --git a/swift/ql/lib/qlpack.yml b/swift/ql/lib/qlpack.yml index f4160734f511..807adcb196fc 100644 --- a/swift/ql/lib/qlpack.yml +++ b/swift/ql/lib/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/swift-all -version: 6.7.2 +version: 6.7.3-dev groups: swift extractor: swift dbscheme: swift.dbscheme diff --git a/swift/ql/src/qlpack.yml b/swift/ql/src/qlpack.yml index b1fd127caaa4..050ce125a8e8 100644 --- a/swift/ql/src/qlpack.yml +++ b/swift/ql/src/qlpack.yml @@ -1,5 +1,5 @@ name: codeql/swift-queries -version: 1.3.6 +version: 1.3.7-dev groups: - swift - queries From 643080208b38f793b75d3d390280f26b57b2f8f3 Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Thu, 9 Jul 2026 17:01:29 -0400 Subject: [PATCH 15/60] Add UntrustedCheckoutTOCTOUX ControlCheck model fix --- actions/ql/lib/codeql/actions/security/ControlChecks.qll | 4 +--- .../src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.ql | 3 ++- .../Security/CWE-367/UntrustedCheckoutTOCTOUCritical.expected | 2 ++ 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/actions/ql/lib/codeql/actions/security/ControlChecks.qll b/actions/ql/lib/codeql/actions/security/ControlChecks.qll index 41f512abbc34..900f62556b05 100644 --- a/actions/ql/lib/codeql/actions/security/ControlChecks.qll +++ b/actions/ql/lib/codeql/actions/security/ControlChecks.qll @@ -144,9 +144,7 @@ class EnvironmentCheck extends ControlCheck instanceof Environment { // Environment checks are not effective against any mutable attacks // they do actually protect against untrusted code execution (sha) override predicate protectsCategoryAndEvent(string category, string event) { - event = actor_is_attacker_event() and category = any_category() - or - event = actor_not_attacker_event() and category = non_toctou_category() + event = [actor_is_attacker_event(), actor_not_attacker_event()] and category = non_toctou_category() } } diff --git a/actions/ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.ql b/actions/ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.ql index 2aacf20b35fc..14a6c9a4c569 100644 --- a/actions/ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.ql +++ b/actions/ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.ql @@ -23,7 +23,8 @@ where // the checked-out code may lead to arbitrary code execution checkout.getAFollowingStep() = step and // the checkout occurs in a privileged context - inPrivilegedContext(checkout, event) and + inPrivilegedContext(checkout, event) + and // the mutable checkout step is protected by an Insufficient access check exists(ControlCheck check1 | check1.protects(checkout, event, "untrusted-checkout")) and not exists(ControlCheck check2 | check2.protects(checkout, event, "untrusted-checkout-toctou")) diff --git a/actions/ql/test/query-tests/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.expected b/actions/ql/test/query-tests/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.expected index da66ff822a39..4f0bd967c2b0 100644 --- a/actions/ql/test/query-tests/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.expected +++ b/actions/ql/test/query-tests/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.expected @@ -99,6 +99,8 @@ edges #select | .github/workflows/comment.yml:58:9:60:2 | Run Step | .github/workflows/comment.yml:54:9:58:6 | Uses Step | .github/workflows/comment.yml:58:9:60:2 | Run Step | Insufficient protection against execution of untrusted code on a privileged workflow ($@). | .github/workflows/comment.yml:4:3:4:15 | issue_comment | issue_comment | | .github/workflows/comment.yml:68:9:68:43 | Run Step | .github/workflows/comment.yml:64:9:68:6 | Uses Step | .github/workflows/comment.yml:68:9:68:43 | Run Step | Insufficient protection against execution of untrusted code on a privileged workflow ($@). | .github/workflows/comment.yml:4:3:4:15 | issue_comment | issue_comment | +| .github/workflows/deployment1.yml:27:10:30:7 | Run Step | .github/workflows/deployment1.yml:16:10:22:7 | Uses Step | .github/workflows/deployment1.yml:27:10:30:7 | Run Step | Insufficient protection against execution of untrusted code on a privileged workflow ($@). | .github/workflows/deployment1.yml:5:3:5:21 | pull_request_target | pull_request_target | +| .github/workflows/deployment1.yml:30:10:31:53 | Run Step | .github/workflows/deployment1.yml:16:10:22:7 | Uses Step | .github/workflows/deployment1.yml:30:10:31:53 | Run Step | Insufficient protection against execution of untrusted code on a privileged workflow ($@). | .github/workflows/deployment1.yml:5:3:5:21 | pull_request_target | pull_request_target | | .github/workflows/test0.yml:58:9:60:2 | Run Step | .github/workflows/test0.yml:54:9:58:6 | Uses Step | .github/workflows/test0.yml:58:9:60:2 | Run Step | Insufficient protection against execution of untrusted code on a privileged workflow ($@). | .github/workflows/test0.yml:4:3:4:15 | issue_comment | issue_comment | | .github/workflows/test0.yml:68:9:68:43 | Run Step | .github/workflows/test0.yml:64:9:68:6 | Uses Step | .github/workflows/test0.yml:68:9:68:43 | Run Step | Insufficient protection against execution of untrusted code on a privileged workflow ($@). | .github/workflows/test0.yml:4:3:4:15 | issue_comment | issue_comment | | .github/workflows/test4.yml:85:7:88:54 | Uses Step | .github/workflows/test4.yml:79:7:85:4 | Uses Step | .github/workflows/test4.yml:85:7:88:54 | Uses Step | Insufficient protection against execution of untrusted code on a privileged workflow ($@). | .github/workflows/test4.yml:5:3:5:15 | issue_comment | issue_comment | From 8ff671f99ddbc6bb6d0ad27c998b75cf80468035 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 00:44:25 +0000 Subject: [PATCH 16/60] Add changed framework coverage reports --- .../library-coverage/coverage.csv | 599 +++++++++--------- .../library-coverage/coverage.rst | 4 +- 2 files changed, 302 insertions(+), 301 deletions(-) diff --git a/java/documentation/library-coverage/coverage.csv b/java/documentation/library-coverage/coverage.csv index 75c6ca1da780..39bb4b37e593 100644 --- a/java/documentation/library-coverage/coverage.csv +++ b/java/documentation/library-coverage/coverage.csv @@ -1,299 +1,300 @@ -package,sink,source,summary,sink:bean-validation,sink:command-injection,sink:credentials-key,sink:credentials-password,sink:credentials-username,sink:encryption-iv,sink:encryption-salt,sink:environment-injection,sink:file-content-store,sink:fragment-injection,sink:groovy-injection,sink:hostname-verification,sink:html-injection,sink:information-leak,sink:intent-redirection,sink:jexl-injection,sink:jndi-injection,sink:js-injection,sink:ldap-injection,sink:log-injection,sink:mvel-injection,sink:notification,sink:ognl-injection,sink:path-injection,sink:path-injection[read],sink:pending-intents,sink:regex-use,sink:regex-use[-1],sink:regex-use[0],sink:regex-use[],sink:regex-use[f-1],sink:regex-use[f1],sink:regex-use[f],sink:request-forgery,sink:response-splitting,sink:sql-injection,sink:template-injection,sink:trust-boundary-violation,sink:unsafe-deserialization,sink:url-forward,sink:url-redirection,sink:xpath-injection,sink:xslt-injection,source:android-external-storage-dir,source:commandargs,source:contentprovider,source:database,source:environment,source:file,source:remote,summary:taint,summary:value -actions.osgi,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6, -android.app,77,,103,,,,,,,,,,11,,,,,7,,,,,,,42,,,,17,,,,,,,,,,,,,,,,,,,,,,,,,18,85 -android.content,24,31,154,,,,,,,,,,,,,,,16,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,4,,27,,,,,63,91 -android.database,59,,41,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,59,,,,,,,,,,,,,,,41, -android.net,,,60,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,45,15 -android.os,1,2,122,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,2,,,,,,,41,81 -android.support.v4.app,11,,,,,,,,,,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -android.util,6,16,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,16,, -android.webkit,3,2,,,,,,,,,,,,,,2,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,, -android.widget,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1, -androidx.core.app,47,,95,,,,,,,,,,,,,,,,,,,,,,41,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,12,83 -androidx.fragment.app,11,,,,,,,,,,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -androidx.slice,2,5,88,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,5,,,,,27,61 -antlr,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -ch.ethz.ssh2,2,,,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -cn.hutool.core.codec,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -com.alibaba.com.caucho.hessian.io,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,, -com.alibaba.druid.sql,1,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,1, -com.alibaba.fastjson2,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -com.amazonaws.auth,2,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.auth0.jwt.algorithms,6,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.azure.identity,3,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.caucho.burlap.io,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, -com.caucho.hessian.io,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,, -com.cedarsoftware.util.io,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,, -com.couchbase.client.core.env,15,,1,,,,9,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -com.couchbase.client.java,10,,,,,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,, -com.esotericsoftware.kryo.io,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -com.esotericsoftware.kryo5.io,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -com.esotericsoftware.yamlbeans,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, -com.fasterxml.jackson.core,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -com.fasterxml.jackson.databind,2,,8,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,8, -com.google.common.base,4,,87,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,1,,,,,,,,,,,,,,,,,,,,,63,24 -com.google.common.cache,,,17,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,17 -com.google.common.collect,,,553,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,551 -com.google.common.flogger,29,,,,,,,,,,,,,,,,,,,,,,29,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.google.common.io,10,,73,,,,,,,,,1,,,,,,,,,,,,,,,4,5,,,,,,,,,,,,,,,,,,,,,,,,,,72,1 -com.google.gson,,,52,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,38,14 -com.hubspot.jinjava,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,, -com.jcraft.jsch,5,,1,,,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,1, -com.microsoft.sqlserver.jdbc,4,,,,,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.mitchellbosecke.pebble,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,, -com.mongodb,10,,,,,,4,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.opensymphony.xwork2,56,,961,,,,,,,,,,,,,,,,,,,,,,,56,,,,,,,,,,,,,,,,,,,,,,,,,,,,867,94 -com.rabbitmq.client,,21,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,21,7, -com.sshtools.j2ssh.authentication,3,,,,,,1,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.sun.crypto.provider,19,,,,,17,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.sun.jndi.ldap,4,,,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.sun.net.httpserver,3,,,,,,1,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.sun.net.ssl,3,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.sun.rowset,3,,,,,,2,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.sun.security.auth.module,2,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.sun.security.ntlm,5,,,,,,3,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.sun.security.sasl.digest,3,,,,,,2,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.thoughtworks.xstream,1,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.trilead.ssh2,13,,,,,2,4,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.unboundid.ldap.sdk,17,,,,,,,,,,,,,,,,,,,,,17,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -com.zaxxer.hikari,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,, -flexjson,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 -freemarker.cache,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,, -freemarker.template,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,7,,,,,,,,,,,,,,, -groovy.lang,26,,,,,,,,,,,,,26,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -groovy.text,1,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -groovy.util,5,,,,,,,,,,,,,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -hudson,75,9,2648,,4,,,,,,3,2,,,,4,,,,,,,,,,,39,17,,,,,,,,,6,,,,,,,,,,,,,,,5,4,2572,76 -io.jsonwebtoken,,2,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,4, -io.netty.bootstrap,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,,,,,,,,,, -io.netty.buffer,,,207,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,130,77 -io.netty.channel,9,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,,,,,,,,,,,,,,,,2,, -io.netty.handler.codec,4,13,259,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,3,,,,,,,,,,,,,,,,13,143,116 -io.netty.handler.ssl,4,,,,,,,,,,,,,,,,,,,,,,,,,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,, -io.netty.handler.stream,1,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,, -io.netty.resolver,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -io.netty.util,2,,23,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,1,,,,,,,,,,,,,,,,,21,2 -io.undertow.server.handlers.resource,1,,3,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,3, -jakarta.activation,2,,2,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,1,,,,,,,,,,,,,,,,,2, -jakarta.faces.context,4,7,,,,,,,,,,,,,,2,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,7,, -jakarta.json,,,123,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,100,23 -jakarta.persistence,2,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,1, -jakarta.servlet,2,26,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,26,, -jakarta.ws.rs.client,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,, -jakarta.ws.rs.container,,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,, -jakarta.ws.rs.core,2,,149,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,94,55 -jakarta.xml.bind.attachment,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,, -java.applet,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,11, -java.awt,1,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,3 -java.beans,1,,177,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,82,95 -java.io,66,1,225,,,,,,,,,22,,,,,,,,,,,,,,,29,15,,,,,,,,,,,,,,,,,,,,,,,,1,,202,23 -java.lang,38,3,790,,13,,,,,,1,,,,,,,,,,,,8,,,,2,9,,,4,,,1,,,,,,,,,,,,,,,,,3,,,510,280 -java.math,,,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9 -java.net,23,3,347,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,21,,,,,,,,,,,,,,,,3,248,99 -java.nio,47,,499,,,,,,,,,5,,,,,,,,,,,,,,,25,16,,,,,,,,,1,,,,,,,,,,,,,,,,,302,197 -java.rmi,,,68,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,45,23 -java.security,21,,583,,,11,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,285,298 -java.sql,15,1,292,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,9,,,,,,,,,,,1,,,,274,18 -java.text,,,154,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,72,82 -java.time,,,131,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,27,104 -java.util,48,2,1340,,,,,,,,,1,,,,,,,,,,,34,,,,3,,,,,5,2,,1,2,,,,,,,,,,,,,,,2,,,558,782 -javafx.scene.web,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,, -javax.accessibility,,,63,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,28,35 -javax.activation,2,,7,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,1,,,,,,,,,,,,,,,,,7, -javax.annotation.processing,,,28,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,25,3 -javax.crypto,19,,140,,,12,3,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,76,64 -javax.faces.context,4,7,,,,,,,,,,,,,,2,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,7,, -javax.imageio,1,,304,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,138,166 -javax.jms,,9,57,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,57, -javax.json,,,123,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,100,23 -javax.lang.model,,,277,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,217,60 -javax.management,2,,766,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,363,403 -javax.naming,7,,341,,,,,,,,,,,,,,,,,6,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,191,150 -javax.net,4,,136,,,,2,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,87,49 -javax.portlet,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,, -javax.print,2,,133,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,102,31 -javax.rmi.ssl,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6 -javax.script,1,,50,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,14,36 -javax.security.auth,7,,147,,,4,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,50,97 -javax.security.cert,,,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5, -javax.security.sasl,,,49,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,42,7 -javax.servlet,10,29,3,,,,,,,,,,,,,,1,,,,,,,,,,,2,,,,,,,,,,3,,,2,,2,,,,,,,,,,29,3, -javax.smartcardio,,,34,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,24,10 -javax.sound.midi,,,60,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,51,9 -javax.sound.sampled,,,90,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,53,37 -javax.sql,7,,126,,,,4,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,68,58 -javax.tools,,,66,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,62,4 -javax.transaction.xa,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -javax.validation,1,1,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, -javax.ws.rs.client,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,, -javax.ws.rs.container,,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,, -javax.ws.rs.core,3,,149,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,2,,,,,,,,,,94,55 -javax.xml.bind.attachment,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,, -javax.xml.catalog,,,12,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,11,1 -javax.xml.crypto,,,269,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,172,97 -javax.xml.datatype,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,1 -javax.xml.namespace,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,10 -javax.xml.parsers,,,37,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35,2 -javax.xml.stream,,,221,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,201,20 -javax.xml.transform,2,,134,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,72,62 -javax.xml.validation,,,29,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,29, -javax.xml.xpath,3,,26,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,26, -jenkins,,,523,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,500,23 -jodd.json,,,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10 -kotlin,16,,1849,,,,,,,,,,,,,,,,,,,,,,,,11,3,,,,,,,,,2,,,,,,,,,,,,,,,,,1836,13 -liquibase.database.jvm,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,, -liquibase.statement.core,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,, -net.lingala.zip4j,2,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,, -net.schmizz.sshj,4,,,,,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -net.sf.json,2,,338,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,321,17 -net.sf.saxon.s9api,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,,,,,,,,, -ognl,6,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -okhttp3,4,,50,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,,,,,,,,23,27 -org.acegisecurity,,,49,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,49, -org.antlr.runtime,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.avro,18,19,,,,,,,,,,,,,,,,,,,,,,,,,17,,,,,,,,,,1,,,,,,,,,,,1,,,,17,1,, -org.apache.commons.codec,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6, -org.apache.commons.collections,,,800,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,17,783 -org.apache.commons.collections4,,,806,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,17,789 -org.apache.commons.compress.archivers.tar,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4, -org.apache.commons.exec,10,,,,6,,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.commons.fileupload,,11,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,11,4, -org.apache.commons.httpclient.util,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -org.apache.commons.io,124,,570,,,,,,,,,4,,,,,,,,,,,,,,,102,3,,,,,,,,,15,,,,,,,,,,,,,,,,,556,14 -org.apache.commons.jelly,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,, -org.apache.commons.jexl2,15,,,,,,,,,,,,,,,,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.commons.jexl3,15,,,,,,,,,,,,,,,,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.commons.lang,1,,767,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,596,171 -org.apache.commons.lang3,7,,425,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,1,,,,,,,,,,,,294,131 -org.apache.commons.logging,6,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.commons.net,13,12,,,,,2,2,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,6,,,,,,,,,,,,,,,,12,, -org.apache.commons.ognl,6,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.commons.text,,,272,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,220,52 -org.apache.cxf.catalog,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,, -org.apache.cxf.common.classloader,3,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,2,,,,,,,,,,,,,,,,,, -org.apache.cxf.common.jaxb,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.cxf.common.logging,6,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.cxf.configuration.jsse,2,,,,,,,,,,,,,,1,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.cxf.helpers,10,,,,,,,,,,,,,,,,,,,,,,,,,,5,,,,,,,,,,,,,,,,,,5,,,,,,,,,, -org.apache.cxf.resource,9,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,5,,,,,,,,,,,,,,,,,, -org.apache.cxf.staxutils,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.cxf.tools.corba.utils,4,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.cxf.tools.util,10,,,,,,,,,,,,,,,,,,,,,,,,,,10,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.cxf.transform,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,, -org.apache.directory.ldap.client.api,1,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.hadoop.fs,3,,11,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,11, -org.apache.hadoop.hive.metastore,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,,,,,,,, -org.apache.hadoop.hive.ql.exec,1,,1,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -org.apache.hadoop.hive.ql.metadata,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.hc.client5.http.async.methods,84,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,84,,,,,,,,,,,,,,,,,, -org.apache.hc.client5.http.classic.methods,37,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,37,,,,,,,,,,,,,,,,,, -org.apache.hc.client5.http.fluent,19,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,19,,,,,,,,,,,,,,,,,, -org.apache.hc.client5.http.protocol,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 -org.apache.hc.client5.http.utils,,,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,7, -org.apache.hc.core5.benchmark,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,, -org.apache.hc.core5.function,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -org.apache.hc.core5.http,73,2,45,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,72,,,,,,,,,,,,,,,,2,45, -org.apache.hc.core5.net,,,18,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,18, -org.apache.hc.core5.util,,,24,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,18,6 -org.apache.hive.hcatalog.templeton,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,, -org.apache.http,53,3,117,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,51,,,,,,,,,,,,,,,,3,108,9 -org.apache.ibatis.jdbc,6,,57,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,57, -org.apache.ibatis.mapping,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -org.apache.log4j,11,,,,,,,,,,,,,,,,,,,,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.logging.log4j,359,,8,,,,,,,,,,,,,,,,,,,,359,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,4 -org.apache.shiro.authc,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, -org.apache.shiro.codec,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -org.apache.shiro.jndi,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.shiro.mgt,1,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.sshd.client.session,3,,,,,,1,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.struts.beanvalidation.validation.interceptor,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4, -org.apache.struts2,14,,3873,,,,,,,,,,,,,,,,,,,,,,,11,,,,,,,,,,,,,,,3,,,,,,,,,,,,,3839,34 -org.apache.tools.ant,14,,,,1,,,,,,,,,,,,,,,,,,,,,,5,8,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.apache.tools.zip,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -org.apache.velocity.app,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,,,,,, -org.apache.velocity.runtime,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,,,,,, -org.codehaus.cargo.container.installer,3,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,1,,,,,,,,,,,,,,,,,, -org.codehaus.groovy.control,1,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.dom4j,20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,20,,,,,,,,,, -org.eclipse.jetty.client,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,, -org.exolab.castor.xml,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, -org.fusesource.leveldbjni,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.geogebra.web.full.main,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,, -org.gradle.api.file,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3, -org.hibernate,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10,,,,,,,,,,,,,,,, -org.ho.yaml,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,, -org.influxdb,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,, -org.jabsorb,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, -org.jboss.logging,324,,,,,,,,,,,,,,,,,,,,,,324,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.jboss.vfs,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.jdbi.v3.core,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,, -org.jenkins.ui.icon,,,49,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,48,1 -org.jenkins.ui.symbol,,,33,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,25,8 -org.jooq,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,, -org.json,,,236,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,198,38 -org.keycloak.models.map.storage,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,, -org.kohsuke.stapler,20,24,363,,,,,,,,,,,,,2,,,,,,,,,,,8,1,,,,,,,,,3,,,,,,1,5,,,,,,,,,24,352,11 -org.lastaflute.web,,1,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,4, -org.mvel2,16,,,,,,,,,,,,,,,,,,,,,,,16,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.openjdk.jmh.runner.options,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.owasp.esapi,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -org.pac4j.jwt.config.encryption,4,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.pac4j.jwt.config.signature,4,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.scijava.log,13,,,,,,,,,,,,,,,,,,,,,,13,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.slf4j,55,,6,,,,,,,,,,,,,,,,,,,,55,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,4 -org.springframework.beans,,,30,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,30 -org.springframework.boot.jdbc,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,, -org.springframework.cache,,,13,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,13 -org.springframework.context,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3, -org.springframework.core.io,17,,6,,,,,,,,,,,,,,,,,,,,,,,,16,,,,,,,,,,1,,,,,,,,,,,,,,,,,6, -org.springframework.data.repository,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 -org.springframework.http,14,,77,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,14,,,,,,,,,,,,,,,,,67,10 -org.springframework.jdbc.core,19,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,19,,,,,,,,,,,,,,,, -org.springframework.jdbc.datasource,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,,,,,,,,, -org.springframework.jdbc.object,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,,,,,,,,,,,,,,,, -org.springframework.jndi,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.springframework.ldap,47,,,,,,,,,,,,,,,,,,,33,,14,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.springframework.security.core.userdetails,2,,,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -org.springframework.security.web.savedrequest,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,, -org.springframework.ui,,,32,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,32 -org.springframework.util,10,,142,,,,,,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,,,,,,,,,,,,90,52 -org.springframework.validation,,,13,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,13, -org.springframework.web.client,13,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,13,,,,,,,,,,,,,,,,3,, -org.springframework.web.context.request,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,, -org.springframework.web.multipart,,12,12,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,12,12, -org.springframework.web.portlet,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,, -org.springframework.web.reactive.function.client,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,,,,,,,,,, -org.springframework.web.servlet,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,, -org.springframework.web.socket,,8,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,6, -org.springframework.web.util,,9,159,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,134,25 -org.thymeleaf,2,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,2, -org.xml.sax,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -org.xmlpull.v1,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,, -org.yaml.snakeyaml,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, -play.libs.ws,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,, -play.mvc,1,13,24,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,13,24, -ratpack.core.form,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3, -ratpack.core.handling,,6,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4, -ratpack.core.http,,10,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10,10, -ratpack.exec,,,48,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,48 -ratpack.form,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3, -ratpack.func,,,35,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35 -ratpack.handling,,6,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4, -ratpack.http,,10,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10,10, -ratpack.util,,,35,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35 -retrofit2,1,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,1, -software.amazon.awssdk.transfer.s3.model,8,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.jvmstat.perfdata.monitor.protocol.local,3,,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.jvmstat.perfdata.monitor.protocol.rmi,1,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.misc,3,,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.net.ftp,5,,,,,,2,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.net.www.protocol.http,3,,,,,,2,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.security.acl,1,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.security.jgss.krb5,2,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.security.krb5,9,,,,,3,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.security.pkcs,4,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.security.pkcs11,3,,,,,1,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.security.provider,2,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.security.ssl,3,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.security.x509,1,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, -sun.tools.jconsole,28,,,,,,13,15,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +package,sink,source,summary,sink:bean-validation,sink:command-injection,sink:credentials-key,sink:credentials-password,sink:credentials-username,sink:encryption-iv,sink:encryption-salt,sink:environment-injection,sink:file-content-store,sink:fragment-injection,sink:groovy-injection,sink:hostname-verification,sink:html-injection,sink:information-leak,sink:intent-redirection,sink:jexl-injection,sink:jndi-injection,sink:js-injection,sink:ldap-injection,sink:log-injection,sink:mvel-injection,sink:notification,sink:ognl-injection,sink:path-injection,sink:path-injection[read],sink:pending-intents,sink:regex-use,sink:regex-use[-1],sink:regex-use[0],sink:regex-use[],sink:regex-use[f-1],sink:regex-use[f1],sink:regex-use[f],sink:request-forgery,sink:response-splitting,sink:sql-injection,sink:template-injection,sink:trust-boundary-violation,sink:unsafe-deserialization,sink:url-forward,sink:url-redirection,sink:xpath-injection,sink:xslt-injection,sink:xxe,source:android-external-storage-dir,source:commandargs,source:contentprovider,source:database,source:environment,source:file,source:remote,summary:taint,summary:value +actions.osgi,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6, +android.app,77,,103,,,,,,,,,,11,,,,,7,,,,,,,42,,,,17,,,,,,,,,,,,,,,,,,,,,,,,,,18,85 +android.content,24,31,154,,,,,,,,,,,,,,,16,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,4,,27,,,,,63,91 +android.database,59,,41,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,59,,,,,,,,,,,,,,,,41, +android.net,,,60,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,45,15 +android.os,1,2,122,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,2,,,,,,,41,81 +android.support.v4.app,11,,,,,,,,,,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +android.util,6,16,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,16,, +android.webkit,3,2,,,,,,,,,,,,,,2,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,, +android.widget,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,1, +androidx.core.app,47,,95,,,,,,,,,,,,,,,,,,,,,,41,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,12,83 +androidx.fragment.app,11,,,,,,,,,,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +androidx.slice,2,5,88,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,5,,,,,27,61 +antlr,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +ch.ethz.ssh2,2,,,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +cn.hutool.core.codec,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +com.alibaba.com.caucho.hessian.io,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,, +com.alibaba.druid.sql,1,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,1, +com.alibaba.fastjson2,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +com.amazonaws.auth,2,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.auth0.jwt.algorithms,6,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.azure.identity,3,,,,,1,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.caucho.burlap.io,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,, +com.caucho.hessian.io,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,, +com.cedarsoftware.util.io,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,, +com.couchbase.client.core.env,15,,1,,,,9,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +com.couchbase.client.java,10,,,,,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,, +com.esotericsoftware.kryo.io,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +com.esotericsoftware.kryo5.io,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +com.esotericsoftware.yamlbeans,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,, +com.fasterxml.jackson.core,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +com.fasterxml.jackson.databind,2,,8,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,8, +com.google.common.base,4,,87,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,1,,,,,,,,,,,,,,,,,,,,,,63,24 +com.google.common.cache,,,17,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,17 +com.google.common.collect,,,553,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,551 +com.google.common.flogger,29,,,,,,,,,,,,,,,,,,,,,,29,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.google.common.io,10,,73,,,,,,,,,1,,,,,,,,,,,,,,,4,5,,,,,,,,,,,,,,,,,,,,,,,,,,,72,1 +com.google.gson,,,52,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,38,14 +com.hubspot.jinjava,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,, +com.jcraft.jsch,5,,1,,,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,1, +com.microsoft.sqlserver.jdbc,4,,,,,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.mitchellbosecke.pebble,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,, +com.mongodb,10,,,,,,4,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.opensymphony.xwork2,56,,961,,,,,,,,,,,,,,,,,,,,,,,56,,,,,,,,,,,,,,,,,,,,,,,,,,,,,867,94 +com.rabbitmq.client,,21,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,21,7, +com.sshtools.j2ssh.authentication,3,,,,,,1,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.sun.crypto.provider,19,,,,,17,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.sun.jndi.ldap,4,,,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.sun.net.httpserver,3,,,,,,1,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.sun.net.ssl,3,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.sun.rowset,3,,,,,,2,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.sun.security.auth.module,2,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.sun.security.ntlm,5,,,,,,3,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.sun.security.sasl.digest,3,,,,,,2,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.thoughtworks.xstream,1,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.trilead.ssh2,13,,,,,2,4,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.unboundid.ldap.sdk,17,,,,,,,,,,,,,,,,,,,,,17,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +com.zaxxer.hikari,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,, +flexjson,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 +freemarker.cache,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,, +freemarker.template,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,7,,,,,,,,,,,,,,,, +groovy.lang,26,,,,,,,,,,,,,26,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +groovy.text,1,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +groovy.util,5,,,,,,,,,,,,,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +hudson,75,9,2648,,4,,,,,,3,2,,,,4,,,,,,,,,,,39,17,,,,,,,,,6,,,,,,,,,,,,,,,,5,4,2572,76 +io.jsonwebtoken,,2,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,4, +io.netty.bootstrap,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,,,,,,,,,,, +io.netty.buffer,,,207,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,130,77 +io.netty.channel,9,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,,,,,,,,,,,,,,,,,2,, +io.netty.handler.codec,4,13,259,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,3,,,,,,,,,,,,,,,,,13,143,116 +io.netty.handler.ssl,4,,,,,,,,,,,,,,,,,,,,,,,,,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,, +io.netty.handler.stream,1,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,, +io.netty.resolver,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +io.netty.util,2,,23,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,1,,,,,,,,,,,,,,,,,,21,2 +io.undertow.server.handlers.resource,1,,3,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,3, +jakarta.activation,2,,2,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,1,,,,,,,,,,,,,,,,,,2, +jakarta.faces.context,4,7,,,,,,,,,,,,,,2,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,7,, +jakarta.json,,,123,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,100,23 +jakarta.persistence,2,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,1, +jakarta.servlet,2,26,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,26,, +jakarta.ws.rs.client,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,, +jakarta.ws.rs.container,,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,, +jakarta.ws.rs.core,2,,149,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,94,55 +jakarta.xml.bind.attachment,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,, +java.applet,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,11, +java.awt,1,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,3 +java.beans,1,,177,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,82,95 +java.io,66,1,225,,,,,,,,,22,,,,,,,,,,,,,,,29,15,,,,,,,,,,,,,,,,,,,,,,,,,1,,202,23 +java.lang,38,3,790,,13,,,,,,1,,,,,,,,,,,,8,,,,2,9,,,4,,,1,,,,,,,,,,,,,,,,,,3,,,510,280 +java.math,,,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9 +java.net,23,3,347,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,21,,,,,,,,,,,,,,,,,3,248,99 +java.nio,47,,499,,,,,,,,,5,,,,,,,,,,,,,,,25,16,,,,,,,,,1,,,,,,,,,,,,,,,,,,302,197 +java.rmi,,,68,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,45,23 +java.security,21,,583,,,11,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,285,298 +java.sql,15,1,292,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,9,,,,,,,,,,,,1,,,,274,18 +java.text,,,154,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,72,82 +java.time,,,131,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,27,104 +java.util,48,2,1340,,,,,,,,,1,,,,,,,,,,,34,,,,3,,,,,5,2,,1,2,,,,,,,,,,,,,,,,2,,,558,782 +javafx.scene.web,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,, +javax.accessibility,,,63,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,28,35 +javax.activation,2,,7,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,1,,,,,,,,,,,,,,,,,,7, +javax.annotation.processing,,,28,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,25,3 +javax.crypto,19,,140,,,12,3,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,76,64 +javax.faces.context,4,7,,,,,,,,,,,,,,2,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,7,, +javax.imageio,1,,304,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,138,166 +javax.jms,,9,57,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,57, +javax.json,,,123,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,100,23 +javax.lang.model,,,277,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,217,60 +javax.management,2,,766,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,363,403 +javax.naming,7,,341,,,,,,,,,,,,,,,,,6,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,191,150 +javax.net,4,,136,,,,2,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,87,49 +javax.portlet,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,, +javax.print,2,,133,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,102,31 +javax.rmi.ssl,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6 +javax.script,1,,50,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,14,36 +javax.security.auth,7,,147,,,4,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,50,97 +javax.security.cert,,,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5, +javax.security.sasl,,,49,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,42,7 +javax.servlet,10,29,3,,,,,,,,,,,,,,1,,,,,,,,,,,2,,,,,,,,,,3,,,2,,2,,,,,,,,,,,29,3, +javax.smartcardio,,,34,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,24,10 +javax.sound.midi,,,60,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,51,9 +javax.sound.sampled,,,90,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,53,37 +javax.sql,7,,126,,,,4,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,68,58 +javax.tools,,,66,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,62,4 +javax.transaction.xa,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +javax.validation,1,1,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, +javax.ws.rs.client,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,, +javax.ws.rs.container,,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,, +javax.ws.rs.core,3,,149,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,2,,,,,,,,,,,94,55 +javax.xml.bind.attachment,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,, +javax.xml.catalog,,,12,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,11,1 +javax.xml.crypto,,,269,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,172,97 +javax.xml.datatype,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,1 +javax.xml.namespace,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,10 +javax.xml.parsers,,,37,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35,2 +javax.xml.stream,,,221,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,201,20 +javax.xml.transform,2,,134,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,72,62 +javax.xml.validation,,,29,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,29, +javax.xml.xpath,3,,26,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,,26, +jenkins,,,523,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,500,23 +jodd.json,,,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10 +kotlin,16,,1849,,,,,,,,,,,,,,,,,,,,,,,,11,3,,,,,,,,,2,,,,,,,,,,,,,,,,,,1836,13 +liquibase.database.jvm,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,, +liquibase.statement.core,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,, +net.lingala.zip4j,2,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +net.schmizz.sshj,4,,,,,,2,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +net.sf.json,2,,338,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,321,17 +net.sf.saxon.s9api,5,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5,,,,,,,,,, +ognl,6,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +okhttp3,4,,50,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,,,,,,,,,23,27 +org.acegisecurity,,,49,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,49, +org.antlr.runtime,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.avro,18,19,,,,,,,,,,,,,,,,,,,,,,,,,17,,,,,,,,,,1,,,,,,,,,,,,1,,,,17,1,, +org.apache.commons.codec,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6, +org.apache.commons.collections,,,800,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,17,783 +org.apache.commons.collections4,,,806,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,17,789 +org.apache.commons.compress.archivers.tar,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4, +org.apache.commons.exec,10,,,,6,,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.commons.fileupload,,11,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,11,4, +org.apache.commons.httpclient.util,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +org.apache.commons.io,124,,570,,,,,,,,,4,,,,,,,,,,,,,,,102,3,,,,,,,,,15,,,,,,,,,,,,,,,,,,556,14 +org.apache.commons.jelly,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,, +org.apache.commons.jexl2,15,,,,,,,,,,,,,,,,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.commons.jexl3,15,,,,,,,,,,,,,,,,,,15,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.commons.lang,1,,767,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,596,171 +org.apache.commons.lang3,7,,425,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,1,,,,,,,,,,,,,294,131 +org.apache.commons.logging,6,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.commons.net,13,12,,,,,2,2,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,6,,,,,,,,,,,,,,,,,12,, +org.apache.commons.ognl,6,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.commons.text,,,272,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,220,52 +org.apache.cxf.catalog,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,, +org.apache.cxf.common.classloader,3,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,2,,,,,,,,,,,,,,,,,,, +org.apache.cxf.common.jaxb,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.cxf.common.logging,6,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.cxf.configuration.jsse,2,,,,,,,,,,,,,,1,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.cxf.helpers,10,,,,,,,,,,,,,,,,,,,,,,,,,,5,,,,,,,,,,,,,,,,,,5,,,,,,,,,,, +org.apache.cxf.resource,9,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,5,,,,,,,,,,,,,,,,,,, +org.apache.cxf.staxutils,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.cxf.tools.corba.utils,4,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.cxf.tools.util,10,,,,,,,,,,,,,,,,,,,,,,,,,,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.cxf.transform,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,, +org.apache.directory.ldap.client.api,1,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.hadoop.fs,3,,11,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,11, +org.apache.hadoop.hive.metastore,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,,,,,,,,, +org.apache.hadoop.hive.ql.exec,1,,1,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +org.apache.hadoop.hive.ql.metadata,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.hc.client5.http.async.methods,84,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,84,,,,,,,,,,,,,,,,,,, +org.apache.hc.client5.http.classic.methods,37,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,37,,,,,,,,,,,,,,,,,,, +org.apache.hc.client5.http.fluent,19,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,19,,,,,,,,,,,,,,,,,,, +org.apache.hc.client5.http.protocol,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 +org.apache.hc.client5.http.utils,,,7,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,7, +org.apache.hc.core5.benchmark,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,, +org.apache.hc.core5.function,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +org.apache.hc.core5.http,73,2,45,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,72,,,,,,,,,,,,,,,,,2,45, +org.apache.hc.core5.net,,,18,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,18, +org.apache.hc.core5.util,,,24,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,18,6 +org.apache.hive.hcatalog.templeton,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,, +org.apache.http,53,3,117,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,,,,51,,,,,,,,,,,,,,,,,3,108,9 +org.apache.ibatis.jdbc,6,,57,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,57, +org.apache.ibatis.mapping,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +org.apache.log4j,11,,,,,,,,,,,,,,,,,,,,,,11,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.logging.log4j,359,,8,,,,,,,,,,,,,,,,,,,,359,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,4 +org.apache.poi,43,24,2216,,,,,,,,,,,,,,,,,,,,,,,,40,,,,,,,,,,,,,,,,,,,,3,,,,,1,23,,1160,1056 +org.apache.shiro.authc,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,, +org.apache.shiro.codec,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +org.apache.shiro.jndi,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.shiro.mgt,1,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.sshd.client.session,3,,,,,,1,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.struts.beanvalidation.validation.interceptor,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4, +org.apache.struts2,14,,3873,,,,,,,,,,,,,,,,,,,,,,,11,,,,,,,,,,,,,,,3,,,,,,,,,,,,,,3839,34 +org.apache.tools.ant,14,,,,1,,,,,,,,,,,,,,,,,,,,,,5,8,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.apache.tools.zip,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +org.apache.velocity.app,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,,,,,,, +org.apache.velocity.runtime,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,,,,,,, +org.codehaus.cargo.container.installer,3,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,1,,,,,,,,,,,,,,,,,,, +org.codehaus.groovy.control,1,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.dom4j,20,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,20,,,,,,,,,,, +org.eclipse.jetty.client,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,, +org.exolab.castor.xml,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,, +org.fusesource.leveldbjni,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.geogebra.web.full.main,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,, +org.gradle.api.file,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3, +org.hibernate,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10,,,,,,,,,,,,,,,,, +org.ho.yaml,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,, +org.influxdb,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,, +org.jabsorb,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,, +org.jboss.logging,324,,,,,,,,,,,,,,,,,,,,,,324,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.jboss.vfs,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.jdbi.v3.core,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,,,,,,,,,,,,,,,,,,, +org.jenkins.ui.icon,,,49,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,48,1 +org.jenkins.ui.symbol,,,33,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,25,8 +org.jooq,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,, +org.json,,,236,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,198,38 +org.keycloak.models.map.storage,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,, +org.kohsuke.stapler,20,24,363,,,,,,,,,,,,,2,,,,,,,,,,,8,1,,,,,,,,,3,,,,,,1,5,,,,,,,,,,24,352,11 +org.lastaflute.web,,1,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,4, +org.mvel2,16,,,,,,,,,,,,,,,,,,,,,,,16,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.openjdk.jmh.runner.options,1,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.owasp.esapi,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +org.pac4j.jwt.config.encryption,4,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.pac4j.jwt.config.signature,4,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.scijava.log,13,,,,,,,,,,,,,,,,,,,,,,13,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.slf4j,55,,6,,,,,,,,,,,,,,,,,,,,55,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,4 +org.springframework.beans,,,30,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,30 +org.springframework.boot.jdbc,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,, +org.springframework.cache,,,13,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,13 +org.springframework.context,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3, +org.springframework.core.io,17,,6,,,,,,,,,,,,,,,,,,,,,,,,16,,,,,,,,,,1,,,,,,,,,,,,,,,,,,6, +org.springframework.data.repository,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1 +org.springframework.http,14,,77,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,14,,,,,,,,,,,,,,,,,,67,10 +org.springframework.jdbc.core,19,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,19,,,,,,,,,,,,,,,,, +org.springframework.jdbc.datasource,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,4,,,,,,,,,,,,,,,,,,, +org.springframework.jdbc.object,9,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,,,,,,,,,,,,,,,,, +org.springframework.jndi,1,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.springframework.ldap,47,,,,,,,,,,,,,,,,,,,33,,14,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.springframework.security.core.userdetails,2,,,,,,1,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +org.springframework.security.web.savedrequest,,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,, +org.springframework.ui,,,32,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,32 +org.springframework.util,10,,142,,,,,,,,,,,,,,,,,,,,,,,,9,1,,,,,,,,,,,,,,,,,,,,,,,,,,,90,52 +org.springframework.validation,,,13,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,13, +org.springframework.web.client,13,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,13,,,,,,,,,,,,,,,,,3,, +org.springframework.web.context.request,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,, +org.springframework.web.multipart,,12,12,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,12,12, +org.springframework.web.portlet,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,, +org.springframework.web.reactive.function.client,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,,,,,,,,,,,,,,,,,,, +org.springframework.web.servlet,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,, +org.springframework.web.socket,,8,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,8,6, +org.springframework.web.util,,9,159,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,9,134,25 +org.thymeleaf,2,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,2, +org.xml.sax,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +org.xmlpull.v1,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3,, +org.yaml.snakeyaml,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1, +play.libs.ws,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,2,,,,,,,,,,,,,,,,,,, +play.mvc,1,13,24,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,13,24, +ratpack.core.form,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3, +ratpack.core.handling,,6,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4, +ratpack.core.http,,10,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10,10, +ratpack.exec,,,48,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,48 +ratpack.form,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,3, +ratpack.func,,,35,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35 +ratpack.handling,,6,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,6,4, +ratpack.http,,10,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,10,10, +ratpack.util,,,35,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,35 +retrofit2,1,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,1,,,,,,,,,,,,,,,,,,1, +software.amazon.awssdk.transfer.s3.model,8,,,,,,,,,,,,,,,,,,,,,,,,,,8,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.jvmstat.perfdata.monitor.protocol.local,3,,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.jvmstat.perfdata.monitor.protocol.rmi,1,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.misc,3,,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.net.ftp,5,,,,,,2,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.net.www.protocol.http,3,,,,,,2,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.security.acl,1,,,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.security.jgss.krb5,2,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.security.krb5,9,,,,,3,6,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.security.pkcs,4,,,,,4,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.security.pkcs11,3,,,,,1,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.security.provider,2,,,,,,2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.security.ssl,3,,,,,,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.security.x509,1,,,,,1,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, +sun.tools.jconsole,28,,,,,,13,15,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, diff --git a/java/documentation/library-coverage/coverage.rst b/java/documentation/library-coverage/coverage.rst index 28f862d87432..cc3da4594a66 100644 --- a/java/documentation/library-coverage/coverage.rst +++ b/java/documentation/library-coverage/coverage.rst @@ -40,6 +40,6 @@ Java framework & library support `Spring `_,``org.springframework.*``,46,494,144,26,,28,14,,36 `Thymeleaf `_,``org.thymeleaf``,,2,2,,,,,, `jOOQ `_,``org.jooq``,,,1,,,1,,, - Others,"``actions.osgi``, ``antlr``, ``ch.ethz.ssh2``, ``cn.hutool.core.codec``, ``com.alibaba.com.caucho.hessian.io``, ``com.alibaba.druid.sql``, ``com.alibaba.fastjson2``, ``com.amazonaws.auth``, ``com.auth0.jwt.algorithms``, ``com.azure.identity``, ``com.caucho.burlap.io``, ``com.caucho.hessian.io``, ``com.cedarsoftware.util.io``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.esotericsoftware.yamlbeans``, ``com.hubspot.jinjava``, ``com.jcraft.jsch``, ``com.microsoft.sqlserver.jdbc``, ``com.mitchellbosecke.pebble``, ``com.opensymphony.xwork2``, ``com.sshtools.j2ssh.authentication``, ``com.sun.crypto.provider``, ``com.sun.jndi.ldap``, ``com.sun.net.httpserver``, ``com.sun.net.ssl``, ``com.sun.rowset``, ``com.sun.security.auth.module``, ``com.sun.security.ntlm``, ``com.sun.security.sasl.digest``, ``com.thoughtworks.xstream``, ``com.trilead.ssh2``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``hudson``, ``io.jsonwebtoken``, ``io.undertow.server.handlers.resource``, ``javafx.scene.web``, ``jenkins``, ``jodd.json``, ``liquibase.database.jvm``, ``liquibase.statement.core``, ``net.lingala.zip4j``, ``net.schmizz.sshj``, ``net.sf.json``, ``net.sf.saxon.s9api``, ``ognl``, ``org.acegisecurity``, ``org.antlr.runtime``, ``org.apache.avro``, ``org.apache.commons.codec``, ``org.apache.commons.compress.archivers.tar``, ``org.apache.commons.exec``, ``org.apache.commons.fileupload``, ``org.apache.commons.httpclient.util``, ``org.apache.commons.jelly``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.lang``, ``org.apache.commons.logging``, ``org.apache.commons.net``, ``org.apache.commons.ognl``, ``org.apache.cxf.catalog``, ``org.apache.cxf.common.classloader``, ``org.apache.cxf.common.jaxb``, ``org.apache.cxf.common.logging``, ``org.apache.cxf.configuration.jsse``, ``org.apache.cxf.helpers``, ``org.apache.cxf.resource``, ``org.apache.cxf.staxutils``, ``org.apache.cxf.tools.corba.utils``, ``org.apache.cxf.tools.util``, ``org.apache.cxf.transform``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.fs``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hadoop.hive.ql.exec``, ``org.apache.hadoop.hive.ql.metadata``, ``org.apache.hc.client5.http.async.methods``, ``org.apache.hc.client5.http.classic.methods``, ``org.apache.hc.client5.http.fluent``, ``org.apache.hc.client5.http.protocol``, ``org.apache.hc.client5.http.utils``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.ibatis.mapping``, ``org.apache.log4j``, ``org.apache.shiro.authc``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.shiro.mgt``, ``org.apache.sshd.client.session``, ``org.apache.tools.ant``, ``org.apache.tools.zip``, ``org.codehaus.cargo.container.installer``, ``org.dom4j``, ``org.exolab.castor.xml``, ``org.fusesource.leveldbjni``, ``org.geogebra.web.full.main``, ``org.gradle.api.file``, ``org.ho.yaml``, ``org.influxdb``, ``org.jabsorb``, ``org.jboss.vfs``, ``org.jdbi.v3.core``, ``org.jenkins.ui.icon``, ``org.jenkins.ui.symbol``, ``org.keycloak.models.map.storage``, ``org.kohsuke.stapler``, ``org.lastaflute.web``, ``org.mvel2``, ``org.openjdk.jmh.runner.options``, ``org.owasp.esapi``, ``org.pac4j.jwt.config.encryption``, ``org.pac4j.jwt.config.signature``, ``org.scijava.log``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.libs.ws``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``software.amazon.awssdk.transfer.s3.model``, ``sun.jvmstat.perfdata.monitor.protocol.local``, ``sun.jvmstat.perfdata.monitor.protocol.rmi``, ``sun.misc``, ``sun.net.ftp``, ``sun.net.www.protocol.http``, ``sun.security.acl``, ``sun.security.jgss.krb5``, ``sun.security.krb5``, ``sun.security.pkcs``, ``sun.security.pkcs11``, ``sun.security.provider``, ``sun.security.ssl``, ``sun.security.x509``, ``sun.tools.jconsole``",127,6042,775,148,6,14,18,,186 - Totals,,382,26411,2708,421,16,137,33,1,416 + Others,"``actions.osgi``, ``antlr``, ``ch.ethz.ssh2``, ``cn.hutool.core.codec``, ``com.alibaba.com.caucho.hessian.io``, ``com.alibaba.druid.sql``, ``com.alibaba.fastjson2``, ``com.amazonaws.auth``, ``com.auth0.jwt.algorithms``, ``com.azure.identity``, ``com.caucho.burlap.io``, ``com.caucho.hessian.io``, ``com.cedarsoftware.util.io``, ``com.esotericsoftware.kryo.io``, ``com.esotericsoftware.kryo5.io``, ``com.esotericsoftware.yamlbeans``, ``com.hubspot.jinjava``, ``com.jcraft.jsch``, ``com.microsoft.sqlserver.jdbc``, ``com.mitchellbosecke.pebble``, ``com.opensymphony.xwork2``, ``com.sshtools.j2ssh.authentication``, ``com.sun.crypto.provider``, ``com.sun.jndi.ldap``, ``com.sun.net.httpserver``, ``com.sun.net.ssl``, ``com.sun.rowset``, ``com.sun.security.auth.module``, ``com.sun.security.ntlm``, ``com.sun.security.sasl.digest``, ``com.thoughtworks.xstream``, ``com.trilead.ssh2``, ``com.unboundid.ldap.sdk``, ``com.zaxxer.hikari``, ``flexjson``, ``hudson``, ``io.jsonwebtoken``, ``io.undertow.server.handlers.resource``, ``javafx.scene.web``, ``jenkins``, ``jodd.json``, ``liquibase.database.jvm``, ``liquibase.statement.core``, ``net.lingala.zip4j``, ``net.schmizz.sshj``, ``net.sf.json``, ``net.sf.saxon.s9api``, ``ognl``, ``org.acegisecurity``, ``org.antlr.runtime``, ``org.apache.avro``, ``org.apache.commons.codec``, ``org.apache.commons.compress.archivers.tar``, ``org.apache.commons.exec``, ``org.apache.commons.fileupload``, ``org.apache.commons.httpclient.util``, ``org.apache.commons.jelly``, ``org.apache.commons.jexl2``, ``org.apache.commons.jexl3``, ``org.apache.commons.lang``, ``org.apache.commons.logging``, ``org.apache.commons.net``, ``org.apache.commons.ognl``, ``org.apache.cxf.catalog``, ``org.apache.cxf.common.classloader``, ``org.apache.cxf.common.jaxb``, ``org.apache.cxf.common.logging``, ``org.apache.cxf.configuration.jsse``, ``org.apache.cxf.helpers``, ``org.apache.cxf.resource``, ``org.apache.cxf.staxutils``, ``org.apache.cxf.tools.corba.utils``, ``org.apache.cxf.tools.util``, ``org.apache.cxf.transform``, ``org.apache.directory.ldap.client.api``, ``org.apache.hadoop.fs``, ``org.apache.hadoop.hive.metastore``, ``org.apache.hadoop.hive.ql.exec``, ``org.apache.hadoop.hive.ql.metadata``, ``org.apache.hc.client5.http.async.methods``, ``org.apache.hc.client5.http.classic.methods``, ``org.apache.hc.client5.http.fluent``, ``org.apache.hc.client5.http.protocol``, ``org.apache.hc.client5.http.utils``, ``org.apache.hive.hcatalog.templeton``, ``org.apache.ibatis.jdbc``, ``org.apache.ibatis.mapping``, ``org.apache.log4j``, ``org.apache.poi``, ``org.apache.shiro.authc``, ``org.apache.shiro.codec``, ``org.apache.shiro.jndi``, ``org.apache.shiro.mgt``, ``org.apache.sshd.client.session``, ``org.apache.tools.ant``, ``org.apache.tools.zip``, ``org.codehaus.cargo.container.installer``, ``org.dom4j``, ``org.exolab.castor.xml``, ``org.fusesource.leveldbjni``, ``org.geogebra.web.full.main``, ``org.gradle.api.file``, ``org.ho.yaml``, ``org.influxdb``, ``org.jabsorb``, ``org.jboss.vfs``, ``org.jdbi.v3.core``, ``org.jenkins.ui.icon``, ``org.jenkins.ui.symbol``, ``org.keycloak.models.map.storage``, ``org.kohsuke.stapler``, ``org.lastaflute.web``, ``org.mvel2``, ``org.openjdk.jmh.runner.options``, ``org.owasp.esapi``, ``org.pac4j.jwt.config.encryption``, ``org.pac4j.jwt.config.signature``, ``org.scijava.log``, ``org.xml.sax``, ``org.xmlpull.v1``, ``play.libs.ws``, ``play.mvc``, ``ratpack.core.form``, ``ratpack.core.handling``, ``ratpack.core.http``, ``ratpack.exec``, ``ratpack.form``, ``ratpack.func``, ``ratpack.handling``, ``ratpack.http``, ``ratpack.util``, ``software.amazon.awssdk.transfer.s3.model``, ``sun.jvmstat.perfdata.monitor.protocol.local``, ``sun.jvmstat.perfdata.monitor.protocol.rmi``, ``sun.misc``, ``sun.net.ftp``, ``sun.net.www.protocol.http``, ``sun.security.acl``, ``sun.security.jgss.krb5``, ``sun.security.krb5``, ``sun.security.pkcs``, ``sun.security.pkcs11``, ``sun.security.provider``, ``sun.security.ssl``, ``sun.security.x509``, ``sun.tools.jconsole``",151,8258,818,188,6,14,18,,186 + Totals,,406,28627,2751,461,16,137,33,1,416 From 985a79a45e4f76093a704d948763cc1c64992dae Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 03:03:43 +0000 Subject: [PATCH 17/60] Bump golang.org/x/tools Bumps the extractor-dependencies group in /go/extractor with 1 update: [golang.org/x/tools](https://github.com/golang/tools). Updates `golang.org/x/tools` from 0.47.0 to 0.48.0 - [Release notes](https://github.com/golang/tools/releases) - [Commits](https://github.com/golang/tools/compare/v0.47.0...v0.48.0) --- updated-dependencies: - dependency-name: golang.org/x/tools dependency-version: 0.48.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: extractor-dependencies ... Signed-off-by: dependabot[bot] --- go/extractor/go.mod | 4 ++-- go/extractor/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go/extractor/go.mod b/go/extractor/go.mod index b95ec78da344..e121c4a98a8a 100644 --- a/go/extractor/go.mod +++ b/go/extractor/go.mod @@ -10,7 +10,7 @@ toolchain go1.26.4 // bazel mod tidy require ( golang.org/x/mod v0.38.0 - golang.org/x/tools v0.47.0 + golang.org/x/tools v0.48.0 ) require github.com/stretchr/testify v1.11.1 @@ -18,6 +18,6 @@ require github.com/stretchr/testify v1.11.1 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sync v0.21.0 // indirect + golang.org/x/sync v0.22.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go/extractor/go.sum b/go/extractor/go.sum index de0d26d035b8..76fbec137b47 100644 --- a/go/extractor/go.sum +++ b/go/extractor/go.sum @@ -8,10 +8,10 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= -golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= -golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= -golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= From 059aa2da70a139c61b6eac5bfcd37645186ea767 Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Fri, 10 Jul 2026 00:04:33 -0400 Subject: [PATCH 18/60] Add missing changenote --- .../change-notes/2026-07-09-environment-check-alteration.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 actions/ql/lib/change-notes/2026-07-09-environment-check-alteration.md diff --git a/actions/ql/lib/change-notes/2026-07-09-environment-check-alteration.md b/actions/ql/lib/change-notes/2026-07-09-environment-check-alteration.md new file mode 100644 index 000000000000..e7b49abddf71 --- /dev/null +++ b/actions/ql/lib/change-notes/2026-07-09-environment-check-alteration.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Altered the logic of `EnvironmentCheck` to make sure it is a check that protects only for non-toctou. This change will result in more results being found by the queries: `actions/untrusted-checkout-toctou/high` and `actions/untrusted-checkout-toctou/critical`. \ No newline at end of file From 89e11e5cfcea4d935a78fd8885603c94698dd2b7 Mon Sep 17 00:00:00 2001 From: Taus Date: Mon, 6 Jul 2026 21:19:45 +0000 Subject: [PATCH 19/60] unified: Add `swift-syntax-rs` Adds an initial prototype of an interface from Rust to Swift, which enables us to use the `swift-syntax` package for parsing. At present, the parsed AST is passed between Swift and Rust as a JSON string. --- Cargo.lock | 4 + Cargo.toml | 1 + MODULE.bazel | 35 +++- unified/swift-syntax-rs/.gitignore | 2 + unified/swift-syntax-rs/.swift-version | 1 + unified/swift-syntax-rs/BUILD.bazel | 55 ++++++ unified/swift-syntax-rs/Cargo.toml | 14 ++ unified/swift-syntax-rs/README.md | 180 ++++++++++++++++++ unified/swift-syntax-rs/build.rs | 102 ++++++++++ unified/swift-syntax-rs/src/lib.rs | 125 ++++++++++++ unified/swift-syntax-rs/src/main.rs | 42 ++++ .../swift-syntax-rs/swift/Package.resolved | 15 ++ unified/swift-syntax-rs/swift/Package.swift | 30 +++ .../SwiftSyntaxFFI/SwiftSyntaxFFI.swift | 145 ++++++++++++++ 14 files changed, 750 insertions(+), 1 deletion(-) create mode 100644 unified/swift-syntax-rs/.gitignore create mode 100644 unified/swift-syntax-rs/.swift-version create mode 100644 unified/swift-syntax-rs/BUILD.bazel create mode 100644 unified/swift-syntax-rs/Cargo.toml create mode 100644 unified/swift-syntax-rs/README.md create mode 100644 unified/swift-syntax-rs/build.rs create mode 100644 unified/swift-syntax-rs/src/lib.rs create mode 100644 unified/swift-syntax-rs/src/main.rs create mode 100644 unified/swift-syntax-rs/swift/Package.resolved create mode 100644 unified/swift-syntax-rs/swift/Package.swift create mode 100644 unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift diff --git a/Cargo.lock b/Cargo.lock index 76043ec0a439..7a9f19667911 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2850,6 +2850,10 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "swift-syntax-rs" +version = "0.1.0" + [[package]] name = "syn" version = "2.0.106" diff --git a/Cargo.toml b/Cargo.toml index 9c15b486062b..9f3780bb1d77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "ruby/extractor", "unified/extractor", "unified/extractor/tree-sitter-swift", + "unified/swift-syntax-rs", "rust/extractor", "rust/extractor/macros", "rust/ast-generator", diff --git a/MODULE.bazel b/MODULE.bazel index e6f1ddb893dd..aa1f6555e0ca 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -24,13 +24,15 @@ bazel_dep(name = "rules_python", version = "1.9.0") bazel_dep(name = "rules_shell", version = "0.7.1") bazel_dep(name = "bazel_skylib", version = "1.9.0") bazel_dep(name = "abseil-cpp", version = "20260107.1", repo_name = "absl") -bazel_dep(name = "nlohmann_json", version = "3.11.3", repo_name = "json") +bazel_dep(name = "nlohmann_json", version = "3.12.0.bcr.1", repo_name = "json") bazel_dep(name = "fmt", version = "12.1.0-codeql.1") bazel_dep(name = "rules_kotlin", version = "2.2.2-codeql.1") bazel_dep(name = "gazelle", version = "0.50.0") bazel_dep(name = "rules_dotnet", version = "0.21.5-codeql.1") bazel_dep(name = "googletest", version = "1.17.0.bcr.2") bazel_dep(name = "rules_rust", version = "0.69.0") +bazel_dep(name = "rules_swift", version = "4.0.0-rc4") +bazel_dep(name = "swift-syntax", version = "602.0.0.bcr.2") bazel_dep(name = "zstd", version = "1.5.7.bcr.1") bazel_dep(name = "buildifier_prebuilt", version = "6.4.0", dev_dependency = True) @@ -219,6 +221,37 @@ use_repo( "swift-resource-dir-macos", ) +# Hermetic Swift toolchain (from swift.org) for building the `swift-syntax` +# based Rust wrapper in `unified/swift-syntax-rs`. This is the official +# `rules_swift` standalone-toolchain extension and is independent of the +# patched prebuilt toolchain wired up via `swift_deps` above. +# +# Bazel cannot auto-select between Linux distributions, so the toolchain is +# registered explicitly for the distribution our CI runs on (ubuntu24.04, +# x86_64). Add further platforms here if CI grows to cover them. +# +# We register the `exec` toolchain (normal host compilation, targeting +# linux/x86_64) rather than the `embedded` one, which targets `os:none` +# (Embedded Swift) and would not match a normal host build. +# +# The Swift version is read from `unified/swift-syntax-rs/.swift-version`, which +# is the single source of truth shared with the local `cargo` build (via +# swiftly / any Swift install) and `swift/Package.swift`. +swift = use_extension("@rules_swift//swift:extensions.bzl", "swift") +swift.toolchain( + name = "swift_toolchain", + swift_version_file = "//unified/swift-syntax-rs:.swift-version", +) +use_repo( + swift, + "swift_toolchain", + "swift_toolchain_ubuntu24.04", +) + +register_toolchains( + "@swift_toolchain//:swift_toolchain_exec_ubuntu24.04", +) + node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node") node.toolchain( name = "nodejs", diff --git a/unified/swift-syntax-rs/.gitignore b/unified/swift-syntax-rs/.gitignore new file mode 100644 index 000000000000..0b6496865408 --- /dev/null +++ b/unified/swift-syntax-rs/.gitignore @@ -0,0 +1,2 @@ +/target +/swift/.build diff --git a/unified/swift-syntax-rs/.swift-version b/unified/swift-syntax-rs/.swift-version new file mode 100644 index 000000000000..42cc526d6ca7 --- /dev/null +++ b/unified/swift-syntax-rs/.swift-version @@ -0,0 +1 @@ +6.2.4 diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel new file mode 100644 index 000000000000..10d11a1bec11 --- /dev/null +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -0,0 +1,55 @@ +load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") +load("@rules_swift//swift:swift.bzl", "swift_library") + +package(default_visibility = ["//visibility:public"]) + +# The pinned Swift version, shared with the local `cargo` build and +# `swift/Package.swift`. Referenced by the `swift.toolchain` extension in +# //:MODULE.bazel via `swift_version_file`. +exports_files([".swift-version"]) + +# The Swift FFI shim: wraps swift-syntax and exposes a small C ABI +# (`ssr_parse_json` / `ssr_string_free`). Built with the hermetic Swift +# toolchain registered in //:MODULE.bazel; provides a `CcInfo` that the Rust +# targets below link against. +swift_library( + name = "swift_syntax_ffi", + srcs = ["swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift"], + module_name = "SwiftSyntaxFFI", + deps = [ + "@swift-syntax//:SwiftParser", + "@swift-syntax//:SwiftSyntax", + ], +) + +# Safe Rust bindings on top of the C ABI. `build.rs` is intentionally *not* +# wired up here (no `cargo_build_script`): under Bazel the Swift side is +# provided by `:swift_syntax_ffi`, whereas `build.rs` only exists to build the +# Swift shim in the local `cargo` workflow. +rust_library( + name = "swift_syntax_rs", + srcs = ["src/lib.rs"], + edition = "2024", + deps = [":swift_syntax_ffi"], +) + +rust_binary( + name = "swift-syntax-parse", + srcs = ["src/main.rs"], + # The Swift toolchain propagates a runfiles-relative RPATH to its runtime + # `.so`s via `swift_syntax_ffi`'s `CcInfo`, but (unlike `swift_binary`) + # `rust_binary` does not copy those libraries into runfiles. Add the + # toolchain files as `data` so the runtime is found at execution time. + # (rules_swift does not yet support statically linking the runtime.) + data = ["@swift_toolchain_ubuntu24.04//:files"], + edition = "2024", + deps = [":swift_syntax_rs"], +) + +rust_test( + name = "swift_syntax_rs_test", + size = "small", + crate = ":swift_syntax_rs", + data = ["@swift_toolchain_ubuntu24.04//:files"], + edition = "2024", +) diff --git a/unified/swift-syntax-rs/Cargo.toml b/unified/swift-syntax-rs/Cargo.toml new file mode 100644 index 000000000000..6957fb0e50e9 --- /dev/null +++ b/unified/swift-syntax-rs/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "swift-syntax-rs" +description = "Rust wrapper around the swift-syntax package for parsing Swift source" +version = "0.1.0" +authors = ["GitHub"] +edition = "2024" + +[lib] +name = "swift_syntax_rs" +path = "src/lib.rs" + +[[bin]] +name = "swift-syntax-parse" +path = "src/main.rs" diff --git a/unified/swift-syntax-rs/README.md b/unified/swift-syntax-rs/README.md new file mode 100644 index 000000000000..08796b3a6a7d --- /dev/null +++ b/unified/swift-syntax-rs/README.md @@ -0,0 +1,180 @@ +# swift-syntax-rs + +A Rust wrapper around the [swift-syntax](https://github.com/swiftlang/swift-syntax) +package, allowing Swift source code to be parsed from Rust. + +Parsing is delegated to a small Swift shim (in [`swift/`](swift/)) that links +against `SwiftSyntax`/`SwiftParser` and exposes a tiny C ABI. The Rust crate +builds that shim (via `build.rs`) and provides safe bindings on top of it. + +## Output format + +The emitted JSON tree preserves the AST's named structure. Every node has a +`kind` and a `range` with `start`/`end` positions (UTF-8 `offset` plus 1-based +`line`/`column`). Beyond that: + +- **Tokens** carry `text`, `tokenKind`, and `leadingTrivia`/`trailingTrivia` + arrays of `{ kind, text }` pieces. +- **Layout nodes** (e.g. `functionDecl`) embed their children directly as + members keyed by the child's name in the parent (`name`, `signature`, + `body`, …), alongside `kind`/`range`. Absent optional children are omitted. +- **Collection nodes** (e.g. `codeBlockItemList`) are elided: a list-valued + field is simply a JSON array of its elements (e.g. `parameters`, `statements`). + This drops the collection node's own `kind`/`range`. + +Only meaningful trivia is kept — the four comment kinds (`lineComment`, +`blockComment`, `docLineComment`, `docBlockComment`) and `unexpectedText` +(source the parser skipped). Whitespace trivia is dropped, since node ranges +already encode positions. + +### Example + +Parsing `let x = 1 // c` produces the following (each `range` object is +abbreviated here as `…`): + +```jsonc +{ + "kind": "sourceFile", + "range": …, + "statements": [ // collection node elided to an array + { + "kind": "codeBlockItem", + "range": …, + "item": { + "kind": "variableDecl", + "range": …, + "attributes": [], // empty collection → empty array + "modifiers": [], + "bindingSpecifier": { // a token + "kind": "token", + "text": "let", + "tokenKind": "keyword(SwiftSyntax.Keyword.let)", + "range": …, + "leadingTrivia": [], + "trailingTrivia": [] + }, + "bindings": [ + { + "kind": "patternBinding", + "range": …, + "pattern": { + "kind": "identifierPattern", + "range": …, + "identifier": { "kind": "token", "text": "x", "tokenKind": "identifier(\"x\")", "range": …, "leadingTrivia": [], "trailingTrivia": [] } + }, + "initializer": { + "kind": "initializerClause", + "range": …, + "equal": { "kind": "token", "text": "=", "tokenKind": "equal", "range": …, "leadingTrivia": [], "trailingTrivia": [] }, + "value": { + "kind": "integerLiteralExpr", + "range": …, + "literal": { + "kind": "token", + "text": "1", + "tokenKind": "integerLiteral(\"1\")", + "range": …, + "leadingTrivia": [], + "trailingTrivia": [ { "kind": "lineComment", "text": "// c" } ] + } + } + } + } + ] + } + } + ], + "endOfFileToken": { "kind": "token", "text": "", "tokenKind": "endOfFile", "range": …, "leadingTrivia": [], "trailingTrivia": [] } +} +``` + +Note how `statements`, `bindings`, `attributes`, and `modifiers` are plain +arrays (their collection nodes are elided), layout children such as +`bindingSpecifier` and `initializer` are embedded by name, and the `// c` +comment rides along as `trailingTrivia` on the token it follows. + +## Prerequisites + +The build does not depend on any particular version manager. You need: + +- **Rust** — pinned to `1.88` by the repo-root [`rust-toolchain.toml`](../../rust-toolchain.toml), + which `rustup` picks up automatically. +- **Swift** — pinned to the version in [`.swift-version`](.swift-version) + (currently `6.2.4`), used to build `swift-syntax` `602.0.0`. Install it any way + you like — [swift.org](https://www.swift.org/install/) or + [swiftly](https://www.swift.org/swiftly/) (which reads `.swift-version`), or a + system package. Just make sure `swift` is on your `PATH` (or point `build.rs` + at it with the `SWIFT` environment variable). + +On Debian/Ubuntu the Swift runtime also needs `libncurses6` (and related libs) +available on the system. + +## Building & testing + +With `cargo` and `swift` on `PATH`: + +```sh +cargo build +cargo test +``` + +If your `swift`/`swiftc` are not on `PATH`, point the build at them explicitly: + +```sh +SWIFT=/path/to/swift SWIFTC=/path/to/swiftc cargo build +``` + +The first build compiles `swift-syntax` and can take several minutes. + +## Building with Bazel (CI) + +CI builds this crate hermetically with Bazel. A Swift toolchain is downloaded +from swift.org by the official `rules_swift` standalone toolchain extension +(wired up in the repo-root `MODULE.bazel`), `swift-syntax` is pulled from the +Bazel Central Registry, and the FFI shim is compiled as a `swift_library` that +the Rust targets link against. `build.rs` is not used under Bazel; it only +builds the Swift shim for the local `cargo` workflow. + +```sh +bazel build //unified/swift-syntax-rs:swift-syntax-parse +bazel test //unified/swift-syntax-rs:swift_syntax_rs_test +bazel run //unified/swift-syntax-rs:swift-syntax-parse < some.swift +``` + +Requirements: + +- **`clang`** must be installed on the runner. `rules_swift` requires the Bazel + CC toolchain to use clang; the repo's `.bazelrc` already sets + `--repo_env=CC=clang`, so no extra flags are needed. +- The registered Swift toolchain currently targets **ubuntu24.04 / x86_64** + only (Bazel cannot auto-select between Linux distributions). Add more + platforms in `MODULE.bazel` (`swift.toolchain` + `register_toolchains`) if CI + grows to cover them. + +The Swift compiler version is read from [`.swift-version`](.swift-version) by +both the Bazel toolchain (`swift.toolchain(swift_version_file = …)`) and the +local build, and is kept in sync with the `swift-syntax` release pinned in +`swift/Package.swift`, so local and CI builds behave identically. + +## Usage + +Library: + +```rust +let json = swift_syntax_rs::parse_to_json("let x = 1")?; +println!("{json}"); +``` + +CLI (reads a file argument or stdin, prints the syntax tree as JSON): + +```sh +echo 'let x = 1' | cargo run --bin swift-syntax-parse +``` + +## Layout + +- `swift/` — Swift package exposing the `ssr_parse_json` / `ssr_string_free` C ABI. +- `build.rs` — builds the Swift package and emits link/rpath flags (local `cargo` only). +- `BUILD.bazel` — Bazel targets for the hermetic CI build (swift_library + rust targets). +- `src/lib.rs` — safe Rust bindings (`parse_to_json`). +- `src/main.rs` — demo CLI. diff --git a/unified/swift-syntax-rs/build.rs b/unified/swift-syntax-rs/build.rs new file mode 100644 index 000000000000..0adb140140dc --- /dev/null +++ b/unified/swift-syntax-rs/build.rs @@ -0,0 +1,102 @@ +use std::env; +use std::path::PathBuf; +use std::process::Command; + +fn main() { + let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); + let swift_dir = manifest_dir.join("swift"); + + println!( + "cargo:rerun-if-changed={}", + swift_dir.join("Package.swift").display() + ); + println!( + "cargo:rerun-if-changed={}", + swift_dir + .join("Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift") + .display() + ); + println!("cargo:rerun-if-env-changed=SWIFT"); + println!("cargo:rerun-if-env-changed=SWIFTC"); + + // Build the Swift FFI package as a release dynamic library. + let mut command = Command::new(swift_bin()); + command + .args(["build", "-c", "release"]) + .current_dir(&swift_dir); + apply_bare_repository_workaround(&mut command); + let status = command.status().unwrap_or_else(|e| { + panic!( + "failed to run `{swift} build`: {e}\n\ + Install a Swift toolchain (see https://www.swift.org/install/, e.g. via \ + swiftly) and ensure `swift` is on PATH, or set the `SWIFT` environment \ + variable to the `swift` executable. The pinned version is in `.swift-version`.", + swift = swift_bin(), + ) + }); + assert!(status.success(), "`swift build` failed"); + + // Link against the freshly built dynamic library. + let build_dir = swift_dir.join(".build/release"); + println!("cargo:rustc-link-search=native={}", build_dir.display()); + println!("cargo:rustc-link-lib=dylib=SwiftSyntaxFFI"); + println!("cargo:rustc-link-arg=-Wl,-rpath,{}", build_dir.display()); + + // The executable also needs to find the Swift runtime libraries at run time. + if let Some(runtime) = swift_runtime_dir() { + println!("cargo:rustc-link-search=native={}", runtime.display()); + println!("cargo:rustc-link-arg=-Wl,-rpath,{}", runtime.display()); + } +} + +/// Query the active Swift toolchain for the directory containing its runtime +/// shared libraries (e.g. `libswiftCore.so`). +fn swift_runtime_dir() -> Option { + let output = Command::new(swiftc_bin()) + .arg("-print-target-info") + .output() + .ok()?; + if !output.status.success() { + return None; + } + let info = String::from_utf8_lossy(&output.stdout); + + // Extract the value of `"runtimeResourcePath": "..."` without pulling in a + // JSON dependency. + let key = "\"runtimeResourcePath\""; + let start = info.find(key)?; + let rest = &info[start + key.len()..]; + let colon = rest.find(':')?; + let after = &rest[colon + 1..]; + let open = after.find('"')?; + let value_start = &after[open + 1..]; + let close = value_start.find('"')?; + let resource_path = &value_start[..close]; + + Some(PathBuf::from(resource_path).join("linux")) +} + +/// The `swift` driver to invoke: `$SWIFT` if set, otherwise `swift` from `PATH`. +/// This keeps the build tool-agnostic — any Swift install works; no particular +/// version manager is required. +fn swift_bin() -> String { + env::var("SWIFT").unwrap_or_else(|_| "swift".to_string()) +} + +/// The `swiftc` compiler to invoke: `$SWIFTC` if set, otherwise `swiftc` from +/// `PATH`. +fn swiftc_bin() -> String { + env::var("SWIFTC").unwrap_or_else(|_| "swiftc".to_string()) +} + +/// Some environments (notably GitHub Codespaces) inject +/// `GIT_CONFIG_KEY_0=safe.bareRepository` / `GIT_CONFIG_VALUE_0=explicit`, which +/// breaks the cached bare git repositories `swift build` uses. When exactly that +/// key is present, relax it to `all` for the `swift build` subprocess only +/// (rather than unconditionally, which could clobber an unrelated +/// `GIT_CONFIG_VALUE_0`). +fn apply_bare_repository_workaround(command: &mut Command) { + if env::var("GIT_CONFIG_KEY_0").as_deref() == Ok("safe.bareRepository") { + command.env("GIT_CONFIG_VALUE_0", "all"); + } +} diff --git a/unified/swift-syntax-rs/src/lib.rs b/unified/swift-syntax-rs/src/lib.rs new file mode 100644 index 000000000000..83dc5e2c6fbd --- /dev/null +++ b/unified/swift-syntax-rs/src/lib.rs @@ -0,0 +1,125 @@ +//! Rust wrapper around the [swift-syntax](https://github.com/swiftlang/swift-syntax) +//! package, allowing Swift source code to be parsed from Rust. +//! +//! The heavy lifting is done by a small Swift shim (see `swift/`) that links +//! against `SwiftSyntax`/`SwiftParser` and exposes a tiny C ABI. This module +//! provides safe Rust bindings on top of that ABI. + +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + +// C ABI exported by the `SwiftSyntaxFFI` dynamic library. +unsafe extern "C" { + /// Parse a NUL-terminated Swift source string, returning a heap-allocated + /// NUL-terminated JSON string (or null on failure). The caller owns the + /// returned pointer and must release it with `ssr_string_free`. + fn ssr_parse_json(source: *const c_char) -> *mut c_char; + + /// Free a string previously returned by `ssr_parse_json`. + fn ssr_string_free(ptr: *mut c_char); +} + +/// Errors that can occur while parsing Swift source. +#[derive(Debug)] +pub enum ParseError { + /// The provided source contained an interior NUL byte. + NulByte, + /// The Swift side failed to produce a result. + SwiftFailure, +} + +impl std::fmt::Display for ParseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ParseError::NulByte => write!(f, "source contained an interior NUL byte"), + ParseError::SwiftFailure => write!(f, "swift-syntax failed to parse the source"), + } + } +} + +impl std::error::Error for ParseError {} + +/// Parse the given Swift `source` and return a JSON representation of its +/// syntax tree. +/// +/// # Example +/// ```no_run +/// let json = swift_syntax_rs::parse_to_json("let x = 1").unwrap(); +/// println!("{json}"); +/// ``` +pub fn parse_to_json(source: &str) -> Result { + let c_source = CString::new(source).map_err(|_| ParseError::NulByte)?; + + // SAFETY: `c_source` is a valid NUL-terminated string for the duration of + // the call. The returned pointer, if non-null, is owned by us and freed via + // `ssr_string_free` before returning. + unsafe { + let ptr = ssr_parse_json(c_source.as_ptr()); + if ptr.is_null() { + return Err(ParseError::SwiftFailure); + } + let json = CStr::from_ptr(ptr).to_string_lossy().into_owned(); + ssr_string_free(ptr); + Ok(json) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_simple_declaration() { + let json = parse_to_json("let x = 1").expect("parsing should succeed"); + assert!( + json.contains("\"kind\":\"sourceFile\""), + "unexpected tree: {json}" + ); + assert!(json.contains("\"text\":\"x\""), "unexpected tree: {json}"); + // Source ranges are emitted for every node. + assert!(json.contains("\"range\""), "missing ranges: {json}"); + assert!( + json.contains("\"line\"") && json.contains("\"column\"") && json.contains("\"offset\""), + "missing location fields: {json}" + ); + } + + #[test] + fn preserves_named_structure() { + let json = parse_to_json("let x = 1").expect("parsing should succeed"); + // Layout children are embedded directly under their field name. + assert!( + json.contains("\"initializer\""), + "missing initializer field: {json}" + ); + // Collection-valued fields are elided to plain JSON arrays. + assert!( + json.contains("\"statements\":["), + "statements should be an array: {json}" + ); + assert!( + json.contains("\"bindings\":["), + "bindings should be an array: {json}" + ); + } + + #[test] + fn parses_empty_source() { + let json = parse_to_json("").expect("parsing empty source should succeed"); + assert!( + json.contains("\"kind\":\"sourceFile\""), + "unexpected tree: {json}" + ); + } + + #[test] + fn captures_trivia() { + // A leading comment is kept as trivia on the token it precedes. + let json = parse_to_json("// hi\nlet x = 1").expect("parsing should succeed"); + assert!(json.contains("\"leadingTrivia\""), "missing trivia: {json}"); + assert!( + json.contains("lineComment"), + "comment trivia not captured: {json}" + ); + } +} diff --git a/unified/swift-syntax-rs/src/main.rs b/unified/swift-syntax-rs/src/main.rs new file mode 100644 index 000000000000..d79cd2ffef1f --- /dev/null +++ b/unified/swift-syntax-rs/src/main.rs @@ -0,0 +1,42 @@ +//! Small demo CLI: parse Swift source and print the syntax tree as JSON. +//! +//! Usage: +//! swift-syntax-parse [FILE] +//! +//! Reads from FILE if given, otherwise from standard input. + +use std::io::Read; +use std::process::ExitCode; + +fn main() -> ExitCode { + let source = match read_source() { + Ok(s) => s, + Err(e) => { + eprintln!("error reading source: {e}"); + return ExitCode::FAILURE; + } + }; + + match swift_syntax_rs::parse_to_json(&source) { + Ok(json) => { + println!("{json}"); + ExitCode::SUCCESS + } + Err(e) => { + eprintln!("error: {e}"); + ExitCode::FAILURE + } + } +} + +fn read_source() -> std::io::Result { + let mut args = std::env::args().skip(1); + match args.next() { + Some(path) => std::fs::read_to_string(path), + None => { + let mut buf = String::new(); + std::io::stdin().read_to_string(&mut buf)?; + Ok(buf) + } + } +} diff --git a/unified/swift-syntax-rs/swift/Package.resolved b/unified/swift-syntax-rs/swift/Package.resolved new file mode 100644 index 000000000000..9cebf34ec543 --- /dev/null +++ b/unified/swift-syntax-rs/swift/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "ad84ad340ee01aa6e38b877dd29e38fa9cb40603dfdefa4e6a69e27f2db208d1", + "pins" : [ + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax.git", + "state" : { + "revision" : "4799286537280063c85a32f09884cfbca301b1a1", + "version" : "602.0.0" + } + } + ], + "version" : 3 +} diff --git a/unified/swift-syntax-rs/swift/Package.swift b/unified/swift-syntax-rs/swift/Package.swift new file mode 100644 index 000000000000..1b0111364ce7 --- /dev/null +++ b/unified/swift-syntax-rs/swift/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:6.0 +import PackageDescription + +let package = Package( + name: "SwiftSyntaxFFI", + products: [ + // Dynamic library so the produced .so records its dependency on the + // Swift runtime libraries, keeping the Rust link step simple. + .library( + name: "SwiftSyntaxFFI", + type: .dynamic, + targets: ["SwiftSyntaxFFI"] + ) + ], + dependencies: [ + .package( + url: "https://github.com/swiftlang/swift-syntax.git", + exact: "602.0.0" + ) + ], + targets: [ + .target( + name: "SwiftSyntaxFFI", + dependencies: [ + .product(name: "SwiftSyntax", package: "swift-syntax"), + .product(name: "SwiftParser", package: "swift-syntax"), + ] + ) + ] +) diff --git a/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift b/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift new file mode 100644 index 000000000000..177e8ff319d9 --- /dev/null +++ b/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift @@ -0,0 +1,145 @@ +import Foundation +import SwiftParser + +// `@_spi(RawSyntax)` exposes the `childName(_:)` helper that maps a child's +// key path to its field name in the parent layout; used to emit named fields. +@_spi(RawSyntax) import SwiftSyntax + +#if canImport(Glibc) + import Glibc +#elseif canImport(Darwin) + import Darwin +#endif + +/// Convert an absolute position into an `{ offset, line, column }` dictionary. +/// +/// `offset` is a UTF-8 byte offset; `line`/`column` are 1-based. +private func location( + _ position: AbsolutePosition, + _ converter: SourceLocationConverter +) -> [String: Any] { + let loc = converter.location(for: position) + return [ + "offset": position.utf8Offset, + "line": loc.line, + "column": loc.column, + ] +} + +/// Trivia kinds worth preserving in the serialized tree. Comments carry +/// developer intent (including doc comments), and `unexpectedText` flags source +/// the parser had to skip. Whitespace and multi-line-string escape markers are +/// dropped: node ranges already encode positions, so they would only bloat the +/// output. +private let keptTriviaKinds: Set = [ + "lineComment", + "blockComment", + "docLineComment", + "docBlockComment", + "unexpectedText", +] + +/// Serialize a trivia collection into an array of `{ kind, text }` pieces, +/// keeping only the kinds in `keptTriviaKinds`. +private func serializeTrivia(_ trivia: Trivia) -> [Any] { + trivia.pieces.compactMap { piece -> [String: Any]? in + // The label of an enum case mirror is the case name (e.g. "spaces", + // "lineComment"), which gives us a stable kind without an exhaustive + // switch over every `TriviaPiece` case. + let kind = Mirror(reflecting: piece).children.first?.label ?? "\(piece)" + guard keptTriviaKinds.contains(kind) else { return nil } + return [ + "kind": kind, + "text": Trivia(pieces: [piece]).description, + ] + } +} + +/// Recursively convert a SwiftSyntax node into a JSON-serializable value. +/// +/// * Tokens and layout nodes (e.g. `functionDecl`) become objects carrying +/// `kind` and source `range`. Layout nodes additionally embed their children +/// directly as members keyed by the child's name in the parent (e.g. `name`, +/// `signature`, `body`); absent optional children are omitted. Field names +/// never collide with `kind`/`range`. +/// * Collection nodes (e.g. `codeBlockItemList`) are *elided*: they become a +/// plain array of their serialized elements, taking the place of the +/// collection node itself. A list-valued layout field (e.g. `parameters`) is +/// therefore simply a JSON array. This drops the collection node's own +/// `kind`/`range`, which are unnamed and largely recoverable from the +/// elements. +private func serialize( + _ node: Syntax, + _ converter: SourceLocationConverter +) -> Any { + if node.kind.isSyntaxCollection { + return node.children(viewMode: .sourceAccurate).map { + serialize($0, converter) + } + } + + // Source range covering the node's content, excluding surrounding trivia. + let range: [String: Any] = [ + "start": location(node.positionAfterSkippingLeadingTrivia, converter), + "end": location(node.endPositionBeforeTrailingTrivia, converter), + ] + + if let token = node.as(TokenSyntax.self) { + return [ + "kind": "token", + "tokenKind": "\(token.tokenKind)", + "text": token.text, + "range": range, + "leadingTrivia": serializeTrivia(token.leadingTrivia), + "trailingTrivia": serializeTrivia(token.trailingTrivia), + ] + } + + var result: [String: Any] = [ + "kind": "\(node.kind)", + "range": range, + ] + var unnamed = 0 + for child in node.children(viewMode: .sourceAccurate) { + // Layout children are named; embed each under its field name in the + // parent (the same mechanism SwiftSyntax uses for its debug dump). A + // child that is a collection serializes to an array (see above). + if let keyPath = child.keyPathInParent, let name = childName(keyPath) { + result[name] = serialize(child, converter) + } else { + // Defensive fallback for any unnamed layout child. + result["child\(unnamed)"] = serialize(child, converter) + unnamed += 1 + } + } + return result +} + +/// Parse the given NUL-terminated Swift source string and return a +/// heap-allocated, NUL-terminated JSON representation of the syntax tree. +/// +/// The returned pointer is owned by the caller and MUST be released with +/// `ssr_string_free`. Returns `nil` on failure. +@_cdecl("ssr_parse_json") +public func ssr_parse_json(_ source: UnsafePointer?) -> UnsafeMutablePointer? { + guard let source = source else { return nil } + let code = String(cString: source) + let tree = Parser.parse(source: code) + let converter = SourceLocationConverter(fileName: "", tree: tree) + let json = serialize(Syntax(tree), converter) + + guard + let data = try? JSONSerialization.data( + withJSONObject: json, options: [.sortedKeys]), + let string = String(data: data, encoding: .utf8) + else { + return nil + } + return strdup(string) +} + +/// Free a string previously returned by this library. +@_cdecl("ssr_string_free") +public func ssr_string_free(_ ptr: UnsafeMutablePointer?) { + free(ptr) +} From 87c81731252678b5fe3cb7d14a0a19c65e4243f9 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 7 Jul 2026 13:35:07 +0000 Subject: [PATCH 20/60] unified: Don't emit empty trivia These were taking up roughly 20% of the JSON payload. --- .../SwiftSyntaxFFI/SwiftSyntaxFFI.swift | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift b/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift index 177e8ff319d9..3dcfcb2c3895 100644 --- a/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift +++ b/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift @@ -57,11 +57,14 @@ private func serializeTrivia(_ trivia: Trivia) -> [Any] { /// Recursively convert a SwiftSyntax node into a JSON-serializable value. /// -/// * Tokens and layout nodes (e.g. `functionDecl`) become objects carrying -/// `kind` and source `range`. Layout nodes additionally embed their children -/// directly as members keyed by the child's name in the parent (e.g. `name`, -/// `signature`, `body`); absent optional children are omitted. Field names -/// never collide with `kind`/`range`. +/// * Tokens carry `kind`, `tokenKind`, `text`, and `range`, plus +/// `leadingTrivia`/`trailingTrivia` — but only when non-empty (after +/// filtering, most tokens have no trivia, so the keys are simply absent). +/// * Layout nodes (e.g. `functionDecl`) carry `kind` and source `range`, and +/// additionally embed their children directly as members keyed by the +/// child's name in the parent (e.g. `name`, `signature`, `body`); absent +/// optional children are omitted. Field names never collide with +/// `kind`/`range`. /// * Collection nodes (e.g. `codeBlockItemList`) are *elided*: they become a /// plain array of their serialized elements, taking the place of the /// collection node itself. A list-valued layout field (e.g. `parameters`) is @@ -85,14 +88,22 @@ private func serialize( ] if let token = node.as(TokenSyntax.self) { - return [ + var result: [String: Any] = [ "kind": "token", "tokenKind": "\(token.tokenKind)", "text": token.text, "range": range, - "leadingTrivia": serializeTrivia(token.leadingTrivia), - "trailingTrivia": serializeTrivia(token.trailingTrivia), ] + // Only emit trivia when present; after filtering, most tokens have none. + let leading = serializeTrivia(token.leadingTrivia) + if !leading.isEmpty { + result["leadingTrivia"] = leading + } + let trailing = serializeTrivia(token.trailingTrivia) + if !trailing.isEmpty { + result["trailingTrivia"] = trailing + } + return result } var result: [String: Any] = [ From 446ff7c463bf3d8496b13458fc1a6749b4c2eb43 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 7 Jul 2026 13:36:10 +0000 Subject: [PATCH 21/60] unified: Add mapping from swift-syntax JSON to yeast AST Adds a preliminary mapping that decodes the JSON into the yeast AST. The JSON AST itself is still very verbose, but it would be useful to see if it actually contains all of the things we want it to contain. --- Cargo.lock | 4 + shared/yeast/src/lib.rs | 42 ++- unified/swift-syntax-rs/BUILD.bazel | 11 +- unified/swift-syntax-rs/Cargo.toml | 4 + unified/swift-syntax-rs/README.md | 37 ++- unified/swift-syntax-rs/src/lib.rs | 2 + unified/swift-syntax-rs/src/yeast_adapter.rs | 282 +++++++++++++++++++ 7 files changed, 369 insertions(+), 13 deletions(-) create mode 100644 unified/swift-syntax-rs/src/yeast_adapter.rs diff --git a/Cargo.lock b/Cargo.lock index 7a9f19667911..24885fea8563 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2853,6 +2853,10 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "swift-syntax-rs" version = "0.1.0" +dependencies = [ + "serde_json", + "yeast", +] [[package]] name = "syn" diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index e6759f1ddb92..fa9074805b88 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -333,6 +333,28 @@ impl Ast { ast } + /// Construct an empty AST backed by `schema`, for building a tree + /// programmatically from a source other than a tree-sitter parse (e.g. an + /// external parser's output). Populate it with [`Ast::create_node`] / + /// [`Ast::create_node_with_range`] and then designate the root with + /// [`Ast::set_root`]. The `schema` must already have every node kind and + /// field name registered (see [`schema::Schema`]). + pub fn with_schema(schema: schema::Schema) -> Self { + Self { + root: Id(0), + nodes: Vec::new(), + schema, + source: Vec::new(), + } + } + + /// Set the original source bytes, used to resolve `NodeContent::Range` + /// nodes to text. Nodes built with inline `NodeContent::DynamicString` + /// content do not require this. + pub fn set_source(&mut self, source: Vec) { + self.source = source; + } + /// Returns the source text for `id`, resolving `NodeContent::Range` /// against the stored source bytes when available. pub fn source_text(&self, id: Id) -> String { @@ -463,6 +485,24 @@ impl Ast { Id(id) } + /// Register a named node kind, returning its id (idempotent). Lets callers + /// build an AST in a single pass, registering kinds as nodes are created + /// rather than pre-populating the schema. + pub fn register_kind(&mut self, name: &str) -> KindId { + self.schema.register_kind(name) + } + + /// Register an anonymous (unnamed) token kind, returning its id + /// (idempotent). Anonymous tokens are keyed by their text (e.g. `"func"`). + pub fn register_unnamed_kind(&mut self, name: &str) -> KindId { + self.schema.register_unnamed_kind(name) + } + + /// Register a field name, returning its id (idempotent). + pub fn register_field(&mut self, name: &str) -> FieldId { + self.schema.register_field(name) + } + fn union_source_range_of_children( &self, fields: &BTreeMap>, @@ -604,7 +644,7 @@ impl Ast { } } - fn id_for_unnamed_node_kind(&self, kind: &str) -> Option { + pub fn id_for_unnamed_node_kind(&self, kind: &str) -> Option { let id = self.schema.id_for_unnamed_node_kind(kind).unwrap_or(0); if id == 0 { None diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel index 10d11a1bec11..3a2e5ed0796b 100644 --- a/unified/swift-syntax-rs/BUILD.bazel +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -28,9 +28,16 @@ swift_library( # Swift shim in the local `cargo` workflow. rust_library( name = "swift_syntax_rs", - srcs = ["src/lib.rs"], + srcs = glob( + ["src/**/*.rs"], + exclude = ["src/main.rs"], + ), edition = "2024", - deps = [":swift_syntax_ffi"], + deps = [ + ":swift_syntax_ffi", + "//shared/yeast", + "@vendor_ts__serde_json-1.0.145//:serde_json", + ], ) rust_binary( diff --git a/unified/swift-syntax-rs/Cargo.toml b/unified/swift-syntax-rs/Cargo.toml index 6957fb0e50e9..576246468480 100644 --- a/unified/swift-syntax-rs/Cargo.toml +++ b/unified/swift-syntax-rs/Cargo.toml @@ -12,3 +12,7 @@ path = "src/lib.rs" [[bin]] name = "swift-syntax-parse" path = "src/main.rs" + +[dependencies] +serde_json = "1.0" +yeast = { path = "../../shared/yeast" } diff --git a/unified/swift-syntax-rs/README.md b/unified/swift-syntax-rs/README.md index 08796b3a6a7d..bb4f7f667a5d 100644 --- a/unified/swift-syntax-rs/README.md +++ b/unified/swift-syntax-rs/README.md @@ -13,8 +13,8 @@ The emitted JSON tree preserves the AST's named structure. Every node has a `kind` and a `range` with `start`/`end` positions (UTF-8 `offset` plus 1-based `line`/`column`). Beyond that: -- **Tokens** carry `text`, `tokenKind`, and `leadingTrivia`/`trailingTrivia` - arrays of `{ kind, text }` pieces. +- **Tokens** carry `text`, `tokenKind`, and — only when non-empty — + `leadingTrivia`/`trailingTrivia` arrays of `{ kind, text }` pieces. - **Layout nodes** (e.g. `functionDecl`) embed their children directly as members keyed by the child's name in the parent (`name`, `signature`, `body`, …), alongside `kind`/`range`. Absent optional children are omitted. @@ -49,9 +49,7 @@ abbreviated here as `…`): "kind": "token", "text": "let", "tokenKind": "keyword(SwiftSyntax.Keyword.let)", - "range": …, - "leadingTrivia": [], - "trailingTrivia": [] + "range": … }, "bindings": [ { @@ -60,12 +58,12 @@ abbreviated here as `…`): "pattern": { "kind": "identifierPattern", "range": …, - "identifier": { "kind": "token", "text": "x", "tokenKind": "identifier(\"x\")", "range": …, "leadingTrivia": [], "trailingTrivia": [] } + "identifier": { "kind": "token", "text": "x", "tokenKind": "identifier(\"x\")", "range": … } }, "initializer": { "kind": "initializerClause", "range": …, - "equal": { "kind": "token", "text": "=", "tokenKind": "equal", "range": …, "leadingTrivia": [], "trailingTrivia": [] }, + "equal": { "kind": "token", "text": "=", "tokenKind": "equal", "range": … }, "value": { "kind": "integerLiteralExpr", "range": …, @@ -74,7 +72,6 @@ abbreviated here as `…`): "text": "1", "tokenKind": "integerLiteral(\"1\")", "range": …, - "leadingTrivia": [], "trailingTrivia": [ { "kind": "lineComment", "text": "// c" } ] } } @@ -84,14 +81,15 @@ abbreviated here as `…`): } } ], - "endOfFileToken": { "kind": "token", "text": "", "tokenKind": "endOfFile", "range": …, "leadingTrivia": [], "trailingTrivia": [] } + "endOfFileToken": { "kind": "token", "text": "", "tokenKind": "endOfFile", "range": … } } ``` Note how `statements`, `bindings`, `attributes`, and `modifiers` are plain arrays (their collection nodes are elided), layout children such as `bindingSpecifier` and `initializer` are embedded by name, and the `// c` -comment rides along as `trailingTrivia` on the token it follows. +comment rides along as `trailingTrivia` on the token it follows. Tokens without +trivia (most of them) simply omit the `leadingTrivia`/`trailingTrivia` keys. ## Prerequisites @@ -171,10 +169,29 @@ CLI (reads a file argument or stdin, prints the syntax tree as JSON): echo 'let x = 1' | cargo run --bin swift-syntax-parse ``` +## Converting to a yeast AST + +For use in the CodeQL extractor, the JSON tree can be converted into a +[`yeast::Ast`](../../shared/yeast) — the in-memory format the extractor's +rewrite rules operate on — via [`yeast_adapter::json_to_ast`](src/yeast_adapter.rs): + +```rust +let json = swift_syntax_rs::parse_to_json("let x = 1")?; +let ast = swift_syntax_rs::yeast_adapter::json_to_ast(&json)?; +``` + +The adapter mirrors tree-sitter's node model, which is what yeast expects: +layout nodes and varying tokens (identifiers, literals, operators) become +**named** nodes; fixed tokens (keywords, punctuation) become **anonymous** +nodes keyed by their text. It preserves swift-syntax's own kind/field names — +aligning them with the tree-sitter-swift schema so the existing rewrite rules +fire is a separate, later step. + ## Layout - `swift/` — Swift package exposing the `ssr_parse_json` / `ssr_string_free` C ABI. - `build.rs` — builds the Swift package and emits link/rpath flags (local `cargo` only). - `BUILD.bazel` — Bazel targets for the hermetic CI build (swift_library + rust targets). - `src/lib.rs` — safe Rust bindings (`parse_to_json`). +- `src/yeast_adapter.rs` — converts the JSON tree into a `yeast::Ast`. - `src/main.rs` — demo CLI. diff --git a/unified/swift-syntax-rs/src/lib.rs b/unified/swift-syntax-rs/src/lib.rs index 83dc5e2c6fbd..aa711e322a49 100644 --- a/unified/swift-syntax-rs/src/lib.rs +++ b/unified/swift-syntax-rs/src/lib.rs @@ -5,6 +5,8 @@ //! against `SwiftSyntax`/`SwiftParser` and exposes a tiny C ABI. This module //! provides safe Rust bindings on top of that ABI. +pub mod yeast_adapter; + use std::ffi::{CStr, CString}; use std::os::raw::c_char; diff --git a/unified/swift-syntax-rs/src/yeast_adapter.rs b/unified/swift-syntax-rs/src/yeast_adapter.rs new file mode 100644 index 000000000000..5f84e1304bba --- /dev/null +++ b/unified/swift-syntax-rs/src/yeast_adapter.rs @@ -0,0 +1,282 @@ +//! Adapter that converts the swift-syntax JSON tree (see [`crate::parse_to_json`]) +//! into a [`yeast::Ast`], the in-memory format the CodeQL desugaring rules +//! operate on. +//! +//! The mapping mirrors tree-sitter's node model, which is what yeast (and the +//! extractor's rewrite rules) expect: +//! +//! * **Layout nodes** (e.g. `functionDecl`) and **varying tokens** (identifiers, +//! literals, operators — the ones whose text is not determined by their kind) +//! become **named** nodes, keyed by their kind name. +//! * **Fixed tokens** (keywords and punctuation, whose text is fully determined +//! by their kind) become **anonymous** nodes, keyed by their text — exactly +//! how tree-sitter models anonymous tokens (e.g. `"func"`, `"->"`). +//! * Collection nodes are already elided to JSON arrays upstream, so a +//! list-valued field maps directly to that field holding several children. +//! +//! Note: this preserves swift-syntax's own kind/field names. Aligning those +//! names with the tree-sitter-swift schema (so the existing rewrite rules fire) +//! is a separate, later step; this module is only concerned with getting the +//! tree into yeast's format. + +use std::collections::BTreeMap; + +use serde_json::Value; +use yeast::schema::Schema; +use yeast::{Ast, Id, NodeContent}; + +/// swift-syntax `TokenKind` cases whose text is *not* determined by the kind +/// (i.e. `TokenKind.defaultText == nil`). These carry varying information and +/// are modelled as named leaf nodes; every other token is a fixed +/// keyword/punctuation token modelled as an anonymous token keyed by its text. +const VARYING_TOKEN_KINDS: &[&str] = &[ + "identifier", + "integerLiteral", + "floatLiteral", + "stringSegment", + "binaryOperator", + "prefixOperator", + "postfixOperator", + "dollarIdentifier", + "regexLiteralPattern", + "rawStringPoundDelimiter", + "regexPoundDelimiter", + "shebang", + "unknown", +]; + +/// Keys of a node object that carry metadata rather than a structural child. +fn is_metadata_key(key: &str) -> bool { + matches!( + key, + "kind" | "range" | "tokenKind" | "text" | "leadingTrivia" | "trailingTrivia" + ) +} + +/// The classification of a JSON node into a yeast kind name and named-ness. +struct KindInfo { + /// The name under which the kind is registered in the schema. + name: String, + /// `true` for named nodes (layout nodes + varying tokens), `false` for + /// anonymous tokens (fixed keywords/punctuation). + is_named: bool, + /// The leaf text for tokens (empty for layout nodes). + text: String, +} + +/// Determine the kind name / named-ness / text for a JSON node object. +fn classify(node: &Value) -> Result { + let kind = node + .get("kind") + .and_then(Value::as_str) + .ok_or("node object is missing a string `kind`")?; + + if kind != "token" { + // Layout node: named, keyed by its kind, no leaf text. + return Ok(KindInfo { + name: kind.to_string(), + is_named: true, + text: String::new(), + }); + } + + let token_kind = node + .get("tokenKind") + .and_then(Value::as_str) + .ok_or("token is missing a string `tokenKind`")?; + let text = node + .get("text") + .and_then(Value::as_str) + .unwrap_or("") + .to_string(); + + // The case name is the part before any `(payload)` in the debug rendering. + let case_name = token_kind.split('(').next().unwrap_or(token_kind); + + if VARYING_TOKEN_KINDS.contains(&case_name) { + // Varying token: named leaf, keyed by its case name. + Ok(KindInfo { + name: case_name.to_string(), + is_named: true, + text, + }) + } else { + // Fixed token: anonymous, keyed by its text (as tree-sitter does). + // `endOfFile` has empty text, so fall back to the case name there. + let name = if text.is_empty() { + case_name.to_string() + } else { + text.clone() + }; + Ok(KindInfo { + name, + is_named: false, + text, + }) + } +} + +/// Iterate over a node object's structural (field, value) pairs in a stable +/// order, skipping metadata keys. +fn field_entries(node: &Value) -> Vec<(&str, &Value)> { + node.as_object() + .map(|map| { + map.iter() + .filter(|(k, _)| !is_metadata_key(k)) + .map(|(k, v)| (k.as_str(), v)) + .collect() + }) + .unwrap_or_default() +} + +/// The child node objects held by a field value, which is either a single node +/// object or an array of them (an elided collection). +fn children_of(value: &Value) -> Vec<&Value> { + match value { + Value::Array(items) => items.iter().collect(), + other => vec![other], + } +} + +/// Recursively build `node` (and its descendants) into `ast`, returning its id. +/// +/// This is a single traversal: each node's kind and field names are registered +/// in the schema on the fly, immediately before the node is created. Children +/// are built first so a parent's field lists reference existing ids. +fn build(node: &Value, ast: &mut Ast) -> Result { + let info = classify(node)?; + + let mut fields: BTreeMap> = BTreeMap::new(); + for (field, value) in field_entries(node) { + let field_id = ast.register_field(field); + let mut ids = Vec::new(); + for child in children_of(value) { + ids.push(build(child, ast)?); + } + fields.insert(field_id, ids); + } + + let kind_id = if info.is_named { + ast.register_kind(&info.name) + } else { + ast.register_unnamed_kind(&info.name) + }; + + Ok(ast.create_node_with_range( + kind_id, + NodeContent::DynamicString(info.text), + fields, + info.is_named, + None, + )) +} + +/// Convert a swift-syntax JSON tree (as produced by [`crate::parse_to_json`]) +/// into a [`yeast::Ast`]. +pub fn json_to_ast(json: &str) -> Result { + let root: Value = serde_json::from_str(json).map_err(|e| format!("invalid JSON: {e}"))?; + + let mut ast = Ast::with_schema(Schema::new()); + let root_id = build(&root, &mut ast)?; + ast.set_root(root_id); + Ok(ast) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A hand-written JSON tree exercising layout nodes, a named (varying) + /// token, a fixed keyword token, and an elided collection field — so the + /// adapter is tested without needing the Swift toolchain. + fn sample_json() -> &'static str { + r#"{ + "kind": "sourceFile", + "range": {"start":{"offset":0,"line":1,"column":1},"end":{"offset":9,"line":1,"column":10}}, + "statements": [ + { + "kind": "variableDecl", + "range": {"start":{"offset":0,"line":1,"column":1},"end":{"offset":9,"line":1,"column":10}}, + "bindingSpecifier": { + "kind": "token", + "tokenKind": "keyword(SwiftSyntax.Keyword.let)", + "text": "let", + "range": {"start":{"offset":0,"line":1,"column":1},"end":{"offset":3,"line":1,"column":4}} + }, + "name": { + "kind": "token", + "tokenKind": "identifier(\"x\")", + "text": "x", + "range": {"start":{"offset":4,"line":1,"column":5},"end":{"offset":5,"line":1,"column":6}} + } + } + ] + }"# + } + + #[test] + fn builds_ast_from_json() { + let ast = json_to_ast(sample_json()).expect("adapter should succeed"); + let root = ast.get_root(); + let root_node = ast.get_node(root).expect("root exists"); + assert_eq!(root_node.kind_name(), "sourceFile"); + assert!(root_node.is_named()); + } + + #[test] + fn classifies_named_and_anonymous_tokens() { + let ast = json_to_ast(sample_json()).expect("adapter should succeed"); + // Walk all nodes and collect (kind_name, is_named) for the two tokens. + let mut let_named = None; + let mut ident_named = None; + for node in ast.nodes() { + match node.kind_name() { + "let" => let_named = Some(node.is_named()), + "identifier" => ident_named = Some(node.is_named()), + _ => {} + } + } + // Fixed keyword `let` is anonymous (keyed by its text "let"). + assert_eq!(let_named, Some(false)); + // Varying `identifier` is a named leaf. + assert_eq!(ident_named, Some(true)); + } + + #[test] + fn preserves_leaf_text() { + let ast = json_to_ast(sample_json()).expect("adapter should succeed"); + let ident = ast + .nodes() + .iter() + .enumerate() + .find(|(_, n)| n.kind_name() == "identifier") + .map(|(i, _)| Id(i)) + .expect("identifier node exists"); + assert_eq!(ast.source_text(ident), "x"); + } + + /// End-to-end: real Swift source parsed by the shim, then adapted into a + /// `yeast::Ast`. Requires the Swift toolchain (like the crate's FFI tests). + #[test] + fn end_to_end_from_swift_source() { + let json = crate::parse_to_json("func f(n: Int) -> Int { return n }") + .expect("parsing should succeed"); + let ast = json_to_ast(&json).expect("adapter should succeed"); + + let root = ast.get_node(ast.get_root()).expect("root exists"); + assert_eq!(root.kind_name(), "sourceFile"); + + // The tree contains a `functionDecl` layout node and an anonymous + // `func` keyword token keyed by its text. + let mut kinds: Vec<&str> = ast.nodes().iter().map(|n| n.kind_name()).collect(); + kinds.sort_unstable(); + assert!( + kinds.contains(&"functionDecl"), + "expected a functionDecl node, got kinds: {kinds:?}" + ); + assert!( + kinds.contains(&"func"), + "expected an anonymous `func` token, got kinds: {kinds:?}" + ); + } +} From 8909fae81e7f8b93df2822b0a4c1e3ddce7a4dae Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 7 Jul 2026 14:28:19 +0000 Subject: [PATCH 22/60] unified: Extract locations from `swift-syntax` AST Introduces new yeast types for `Point`s and `Range`s (corresponding to the tree-sitter equivalents), since it seemed silly to have `swift-syntax-rs` pull in `tree-sitter` just to have those types available. This _does_ mean there is a slight overhead in the shared extractor when converting between these types, but I think this is negligible. --- .../src/extractor/mod.rs | 12 +++- shared/yeast-macros/src/parse.rs | 2 +- shared/yeast/src/build.rs | 10 +-- shared/yeast/src/lib.rs | 61 +++++++++++++------ shared/yeast/src/range.rs | 29 ++++++--- unified/swift-syntax-rs/src/yeast_adapter.rs | 49 ++++++++++++++- 6 files changed, 124 insertions(+), 39 deletions(-) diff --git a/shared/tree-sitter-extractor/src/extractor/mod.rs b/shared/tree-sitter-extractor/src/extractor/mod.rs index 54b01ba5146e..474dc096a2a6 100644 --- a/shared/tree-sitter-extractor/src/extractor/mod.rs +++ b/shared/tree-sitter-extractor/src/extractor/mod.rs @@ -81,10 +81,18 @@ impl AstNode for yeast::Node { yeast::Node::is_extra(self) } fn start_position(&self) -> tree_sitter::Point { - yeast::Node::start_position(self) + let p = yeast::Node::start_position(self); + tree_sitter::Point { + row: p.row, + column: p.column, + } } fn end_position(&self) -> tree_sitter::Point { - yeast::Node::end_position(self) + let p = yeast::Node::end_position(self); + tree_sitter::Point { + row: p.row, + column: p.column, + } } fn byte_range(&self) -> std::ops::Range { yeast::Node::byte_range(self) diff --git a/shared/yeast-macros/src/parse.rs b/shared/yeast-macros/src/parse.rs index 01c0b574b1cf..55ada3588491 100644 --- a/shared/yeast-macros/src/parse.rs +++ b/shared/yeast-macros/src/parse.rs @@ -903,7 +903,7 @@ pub fn parse_rule_top(input: TokenStream) -> Result { Ok(quote! { { let __query = #query_code; - yeast::Rule::new(__query, Box::new(|__ast: &mut yeast::Ast, mut __captures: yeast::captures::Captures, __fresh: &yeast::tree_builder::FreshScope, __source_range: Option, __user_ctx: &mut _, __translator: yeast::TranslatorHandle<'_, _>| { + yeast::Rule::new(__query, Box::new(|__ast: &mut yeast::Ast, mut __captures: yeast::captures::Captures, __fresh: &yeast::tree_builder::FreshScope, __source_range: Option, __user_ctx: &mut _, __translator: yeast::TranslatorHandle<'_, _>| { // Auto-translation prefix: recursively translate every // captured node before invoking the user's transform body, // except for `@@name` captures listed in `__skip` which the diff --git a/shared/yeast/src/build.rs b/shared/yeast/src/build.rs index 68a4c883242a..508a1e731346 100644 --- a/shared/yeast/src/build.rs +++ b/shared/yeast/src/build.rs @@ -2,7 +2,7 @@ use std::collections::BTreeMap; use crate::captures::Captures; use crate::tree_builder::FreshScope; -use crate::{Ast, FieldId, Id, NodeContent, TranslatorHandle}; +use crate::{Ast, FieldId, Id, NodeContent, Range, TranslatorHandle}; /// Context for building new AST nodes during a transformation. /// @@ -34,7 +34,7 @@ pub struct BuildCtx<'a, C: 'a = ()> { pub captures: &'a Captures, pub fresh: &'a FreshScope, /// Source range of the matched node, inherited by synthetic nodes. - pub source_range: Option, + pub source_range: Option, /// User-supplied context, accessible directly via `ctx.field` (via Deref). pub user_ctx: &'a mut C, /// Optional translator handle, populated when the context is built by @@ -63,7 +63,7 @@ impl<'a, C> BuildCtx<'a, C> { ast: &'a mut Ast, captures: &'a Captures, fresh: &'a FreshScope, - source_range: Option, + source_range: Option, user_ctx: &'a mut C, ) -> Self { Self { @@ -82,7 +82,7 @@ impl<'a, C> BuildCtx<'a, C> { ast: &'a mut Ast, captures: &'a Captures, fresh: &'a FreshScope, - source_range: Option, + source_range: Option, user_ctx: &'a mut C, translator: TranslatorHandle<'a, C>, ) -> Self { @@ -143,7 +143,7 @@ impl<'a, C> BuildCtx<'a, C> { &mut self, kind: &'static str, value: &str, - source_range: Option, + source_range: Option, ) -> Id { self.ast.create_named_token_with_range( kind, diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index fa9074805b88..b2709ab406ee 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -15,11 +15,32 @@ pub mod schema; pub mod tree_builder; mod visitor; +pub use range::{Point, Range}; pub use yeast_macros::{query, rule, rules, tree, trees}; use captures::Captures; use query::QueryNode; +impl From for Point { + fn from(p: tree_sitter::Point) -> Self { + Point { + row: p.row, + column: p.column, + } + } +} + +impl From for Range { + fn from(r: tree_sitter::Range) -> Self { + Range { + start_byte: r.start_byte, + end_byte: r.end_byte, + start_point: r.start_point.into(), + end_point: r.end_point.into(), + } + } +} + /// Node id: an index into the [`Ast`] arena. A newtype around `usize` /// rather than a bare alias so that it can carry its own /// [`YeastDisplay`] / [`YeastSourceRange`] / [`IntoFieldIds`] impls @@ -106,7 +127,7 @@ pub trait YeastDisplay { /// rule's source range. `Id` returns the referenced node's range, letting /// `(kind #{capture})` carry the captured node's location. pub trait YeastSourceRange { - fn yeast_source_range(&self, ast: &Ast) -> Option; + fn yeast_source_range(&self, ast: &Ast) -> Option; } impl YeastDisplay for Id { @@ -116,9 +137,9 @@ impl YeastDisplay for Id { } impl YeastSourceRange for Id { - fn yeast_source_range(&self, ast: &Ast) -> Option { + fn yeast_source_range(&self, ast: &Ast) -> Option { ast.get_node(*self).and_then(|n| match &n.content { - NodeContent::Range(r) => Some(r.clone()), + NodeContent::Range(r) => Some(*r), _ => n.source_range, }) } @@ -134,7 +155,7 @@ macro_rules! impl_yeast_display_via_display { } impl YeastSourceRange for $t { - fn yeast_source_range(&self, _ast: &Ast) -> Option { + fn yeast_source_range(&self, _ast: &Ast) -> Option { None } } @@ -157,7 +178,7 @@ impl YeastDisplay for &T { } impl YeastSourceRange for &T { - fn yeast_source_range(&self, ast: &Ast) -> Option { + fn yeast_source_range(&self, ast: &Ast) -> Option { (**self).yeast_source_range(ast) } } @@ -361,7 +382,7 @@ impl Ast { let Some(node) = self.get_node(id) else { return String::new(); }; - let read_range = |range: &tree_sitter::Range| { + let read_range = |range: &Range| { let start = range.start_byte; let end = range.end_byte; if end <= self.source.len() && start <= end { @@ -459,7 +480,7 @@ impl Ast { content: NodeContent, fields: BTreeMap>, is_named: bool, - source_range: Option, + source_range: Option, ) -> Id { let source_range = match &content { // Parsed nodes already carry an exact source range in their content. @@ -506,11 +527,11 @@ impl Ast { fn union_source_range_of_children( &self, fields: &BTreeMap>, - ) -> Option { + ) -> Option { let mut start_byte: Option = None; let mut end_byte: Option = None; - let mut start_point = tree_sitter::Point { row: 0, column: 0 }; - let mut end_point = tree_sitter::Point { row: 0, column: 0 }; + let mut start_point = Point { row: 0, column: 0 }; + let mut end_point = Point { row: 0, column: 0 }; for child_ids in fields.values() { for &child_id in child_ids { @@ -553,7 +574,7 @@ impl Ast { } match (start_byte, end_byte) { - (Some(start_byte), Some(end_byte)) => Some(tree_sitter::Range { + (Some(start_byte), Some(end_byte)) => Some(Range { start_byte, end_byte, start_point, @@ -571,7 +592,7 @@ impl Ast { &mut self, kind: &'static str, content: String, - source_range: Option, + source_range: Option, ) -> Id { let kind_id = self.schema.id_for_node_kind(kind).unwrap_or_else(|| { panic!("create_named_token: node kind '{kind}' not found in schema") @@ -664,7 +685,7 @@ pub struct Node { /// For synthetic nodes, the source range of the original node they /// were desugared from. Used for location information in TRAP output. #[serde(skip)] - source_range: Option, + source_range: Option, is_named: bool, is_missing: bool, is_extra: bool, @@ -692,11 +713,11 @@ impl Node { self.is_error } - fn fake_point(&self) -> tree_sitter::Point { - tree_sitter::Point { row: 0, column: 0 } + fn fake_point(&self) -> Point { + Point { row: 0, column: 0 } } - pub fn start_position(&self) -> tree_sitter::Point { + pub fn start_position(&self) -> Point { match self.content { NodeContent::Range(range) => range.start_point, _ => self @@ -705,7 +726,7 @@ impl Node { } } - pub fn end_position(&self) -> tree_sitter::Point { + pub fn end_position(&self) -> Point { match self.content { NodeContent::Range(range) => range.end_point, _ => self @@ -754,7 +775,7 @@ impl Node { /// or a new string if the node is synthesized. #[derive(PartialEq, Eq, Debug, Clone, Serialize)] pub enum NodeContent { - Range(#[serde(with = "range::Range")] tree_sitter::Range), + Range(Range), String(&'static str), DynamicString(String), } @@ -767,7 +788,7 @@ impl From<&'static str> for NodeContent { impl From for NodeContent { fn from(value: tree_sitter::Range) -> Self { - NodeContent::Range(value) + NodeContent::Range(value.into()) } } @@ -894,7 +915,7 @@ pub type Transform = Box< &mut Ast, Captures, &tree_builder::FreshScope, - Option, + Option, &mut C, TranslatorHandle<'_, C>, ) -> Result, String> diff --git a/shared/yeast/src/range.rs b/shared/yeast/src/range.rs index ec670b438d56..e56bbcc6fb85 100644 --- a/shared/yeast/src/range.rs +++ b/shared/yeast/src/range.rs @@ -1,21 +1,32 @@ -//! (de)-serialize helpers for tree_sitter::Range +//! Location types for yeast ASTs. +//! +//! These are plain owned structs (mirroring tree-sitter's `Point`/`Range`) so +//! that code building an AST programmatically can populate node locations +//! without depending on tree-sitter. Conversions from the tree-sitter types +//! live in the crate root, next to the `from_tree` construction path. use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize)] -#[serde(remote = "tree_sitter::Point")] +/// A position in a source file: a 0-based `row` (line) and 0-based `column` +/// (UTF-8 byte offset within the line), matching tree-sitter's convention. +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub struct Point { pub row: usize, pub column: usize, } -#[derive(Serialize, Deserialize)] -#[serde(remote = "tree_sitter::Range")] +impl Point { + pub fn new(row: usize, column: usize) -> Self { + Self { row, column } + } +} + +/// A half-open source range: byte offsets plus start/end [`Point`]s. The end is +/// exclusive, matching tree-sitter's convention. +#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] pub struct Range { pub start_byte: usize, pub end_byte: usize, - #[serde(with = "Point")] - pub start_point: tree_sitter::Point, - #[serde(with = "Point")] - pub end_point: tree_sitter::Point, + pub start_point: Point, + pub end_point: Point, } diff --git a/unified/swift-syntax-rs/src/yeast_adapter.rs b/unified/swift-syntax-rs/src/yeast_adapter.rs index 5f84e1304bba..8a0baf7a4cd6 100644 --- a/unified/swift-syntax-rs/src/yeast_adapter.rs +++ b/unified/swift-syntax-rs/src/yeast_adapter.rs @@ -23,7 +23,7 @@ use std::collections::BTreeMap; use serde_json::Value; use yeast::schema::Schema; -use yeast::{Ast, Id, NodeContent}; +use yeast::{Ast, Id, NodeContent, Point, Range}; /// swift-syntax `TokenKind` cases whose text is *not* determined by the kind /// (i.e. `TokenKind.defaultText == nil`). These carry varying information and @@ -167,10 +167,39 @@ fn build(node: &Value, ast: &mut Ast) -> Result { NodeContent::DynamicString(info.text), fields, info.is_named, - None, + parse_range(node), )) } +/// Parse a node's `range` into a [`yeast::Range`]. +/// +/// The JSON carries, for `start` and `end`, a 0-based UTF-8 file byte `offset`, +/// a 1-based `line`, and a 1-based UTF-8 byte `column`. yeast (like tree-sitter) +/// uses byte offsets with 0-based rows/columns and an exclusive end, so the +/// line/column are shifted down by one. swift-syntax's end position is already +/// exclusive, so the byte offsets map across directly. +fn parse_range(node: &Value) -> Option { + let range = node.get("range")?; + let point = |key: &str| -> Option<(usize, Point)> { + let p = range.get(key)?; + let offset = p.get("offset")?.as_u64()? as usize; + let line = p.get("line")?.as_u64()? as usize; + let column = p.get("column")?.as_u64()? as usize; + Some(( + offset, + Point::new(line.saturating_sub(1), column.saturating_sub(1)), + )) + }; + let (start_byte, start_point) = point("start")?; + let (end_byte, end_point) = point("end")?; + Some(Range { + start_byte, + end_byte, + start_point, + end_point, + }) +} + /// Convert a swift-syntax JSON tree (as produced by [`crate::parse_to_json`]) /// into a [`yeast::Ast`]. pub fn json_to_ast(json: &str) -> Result { @@ -255,6 +284,22 @@ mod tests { assert_eq!(ast.source_text(ident), "x"); } + #[test] + fn maps_source_locations() { + let ast = json_to_ast(sample_json()).expect("adapter should succeed"); + let ident = ast + .nodes() + .iter() + .find(|n| n.kind_name() == "identifier") + .expect("identifier node exists"); + // `x` is at file offset 4..5, line 1, column 5 (1-based) in the JSON, + // which maps to 0-based row 0, column 4 and byte range 4..5. + assert_eq!(ident.start_byte(), 4); + assert_eq!(ident.end_byte(), 5); + assert_eq!(ident.start_position(), Point::new(0, 4)); + assert_eq!(ident.end_position(), Point::new(0, 5)); + } + /// End-to-end: real Swift source parsed by the shim, then adapted into a /// `yeast::Ast`. Requires the Swift toolchain (like the crate's FFI tests). #[test] From ddab6ccb35691f368d09ef0dfa63a5e1bb47c104 Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 7 Jul 2026 14:43:59 +0000 Subject: [PATCH 23/60] unified: Emit locations for comments Also gathers these into a separate side-channel during the initial AST construction. That way, we don't encounter these as weird extra nodes while running yeast. --- unified/swift-syntax-rs/README.md | 13 +- unified/swift-syntax-rs/src/yeast_adapter.rs | 147 ++++++++++++++++-- .../SwiftSyntaxFFI/SwiftSyntaxFFI.swift | 47 ++++-- 3 files changed, 179 insertions(+), 28 deletions(-) diff --git a/unified/swift-syntax-rs/README.md b/unified/swift-syntax-rs/README.md index bb4f7f667a5d..08cabeb736b0 100644 --- a/unified/swift-syntax-rs/README.md +++ b/unified/swift-syntax-rs/README.md @@ -177,15 +177,20 @@ rewrite rules operate on — via [`yeast_adapter::json_to_ast`](src/yeast_adapte ```rust let json = swift_syntax_rs::parse_to_json("let x = 1")?; -let ast = swift_syntax_rs::yeast_adapter::json_to_ast(&json)?; +let adapted = swift_syntax_rs::yeast_adapter::json_to_ast(&json)?; +let ast = adapted.ast; // the yeast::Ast +let comments = adapted.trivia; // side-channel comment/unexpectedText tokens ``` The adapter mirrors tree-sitter's node model, which is what yeast expects: layout nodes and varying tokens (identifiers, literals, operators) become **named** nodes; fixed tokens (keywords, punctuation) become **anonymous** -nodes keyed by their text. It preserves swift-syntax's own kind/field names — -aligning them with the tree-sitter-swift schema so the existing rewrite rules -fire is a separate, later step. +nodes keyed by their text. Comments (and `unexpectedText`) are harvested into a +side channel (`adapted.trivia`) during the same traversal rather than embedded +in the tree, matching how the extractor treats tree-sitter `extra` nodes. It +preserves swift-syntax's own kind/field names — aligning them with the +tree-sitter-swift schema so the existing rewrite rules fire is a separate, +later step. ## Layout diff --git a/unified/swift-syntax-rs/src/yeast_adapter.rs b/unified/swift-syntax-rs/src/yeast_adapter.rs index 8a0baf7a4cd6..e2adf1df5a6a 100644 --- a/unified/swift-syntax-rs/src/yeast_adapter.rs +++ b/unified/swift-syntax-rs/src/yeast_adapter.rs @@ -25,6 +25,29 @@ use serde_json::Value; use yeast::schema::Schema; use yeast::{Ast, Id, NodeContent, Point, Range}; +/// A comment (or `unexpectedText`) recovered from the syntax tree's trivia. +/// +/// These are collected into a side channel rather than embedded in the +/// [`yeast::Ast`], mirroring how the extractor treats tree-sitter `extra` +/// nodes: they carry a location and text but are not attached to a parent. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TriviaToken { + /// The trivia kind (e.g. `lineComment`, `blockComment`, `docLineComment`, + /// `docBlockComment`, `unexpectedText`). + pub kind: String, + /// The verbatim source text of the piece (e.g. `// comment`). + pub text: String, + /// The source range the piece occupies. + pub range: Range, +} + +/// The result of adapting a swift-syntax JSON tree: the [`yeast::Ast`] plus the +/// comment/`unexpectedText` trivia harvested from it (in source order). +pub struct AdaptedTree { + pub ast: Ast, + pub trivia: Vec, +} + /// swift-syntax `TokenKind` cases whose text is *not* determined by the kind /// (i.e. `TokenKind.defaultText == nil`). These carry varying information and /// are modelled as named leaf nodes; every other token is a fixed @@ -142,16 +165,19 @@ fn children_of(value: &Value) -> Vec<&Value> { /// /// This is a single traversal: each node's kind and field names are registered /// in the schema on the fly, immediately before the node is created. Children -/// are built first so a parent's field lists reference existing ids. -fn build(node: &Value, ast: &mut Ast) -> Result { +/// are built first so a parent's field lists reference existing ids. Any +/// comment/`unexpectedText` trivia carried by a token is harvested into +/// `trivia` during the same pass rather than embedded in the tree. +fn build(node: &Value, ast: &mut Ast, trivia: &mut Vec) -> Result { let info = classify(node)?; + collect_trivia(node, trivia); let mut fields: BTreeMap> = BTreeMap::new(); for (field, value) in field_entries(node) { let field_id = ast.register_field(field); let mut ids = Vec::new(); for child in children_of(value) { - ids.push(build(child, ast)?); + ids.push(build(child, ast, trivia)?); } fields.insert(field_id, ids); } @@ -171,6 +197,35 @@ fn build(node: &Value, ast: &mut Ast) -> Result { )) } +/// Harvest a token's `leadingTrivia`/`trailingTrivia` pieces (each already +/// filtered to comments/`unexpectedText` upstream) into `out`. Non-token nodes +/// have no trivia keys, so this is a no-op for them. +fn collect_trivia(node: &Value, out: &mut Vec) { + for key in ["leadingTrivia", "trailingTrivia"] { + let Some(Value::Array(pieces)) = node.get(key) else { + continue; + }; + for piece in pieces { + let (Some(kind), Some(range)) = ( + piece.get("kind").and_then(Value::as_str), + parse_range(piece), + ) else { + continue; + }; + let text = piece + .get("text") + .and_then(Value::as_str) + .unwrap_or("") + .to_string(); + out.push(TriviaToken { + kind: kind.to_string(), + text, + range, + }); + } + } +} + /// Parse a node's `range` into a [`yeast::Range`]. /// /// The JSON carries, for `start` and `end`, a 0-based UTF-8 file byte `offset`, @@ -201,14 +256,20 @@ fn parse_range(node: &Value) -> Option { } /// Convert a swift-syntax JSON tree (as produced by [`crate::parse_to_json`]) -/// into a [`yeast::Ast`]. -pub fn json_to_ast(json: &str) -> Result { +/// into a [`yeast::Ast`] plus the comment/`unexpectedText` trivia harvested +/// from it. Both are produced in a single traversal. +pub fn json_to_ast(json: &str) -> Result { let root: Value = serde_json::from_str(json).map_err(|e| format!("invalid JSON: {e}"))?; let mut ast = Ast::with_schema(Schema::new()); - let root_id = build(&root, &mut ast)?; + let mut trivia = Vec::new(); + let root_id = build(&root, &mut ast, &mut trivia)?; ast.set_root(root_id); - Ok(ast) + + // Emit trivia in source order (the traversal visits nodes bottom-up). + trivia.sort_by_key(|t| t.range.start_byte); + + Ok(AdaptedTree { ast, trivia }) } #[cfg(test)] @@ -245,7 +306,9 @@ mod tests { #[test] fn builds_ast_from_json() { - let ast = json_to_ast(sample_json()).expect("adapter should succeed"); + let ast = json_to_ast(sample_json()) + .expect("adapter should succeed") + .ast; let root = ast.get_root(); let root_node = ast.get_node(root).expect("root exists"); assert_eq!(root_node.kind_name(), "sourceFile"); @@ -254,7 +317,9 @@ mod tests { #[test] fn classifies_named_and_anonymous_tokens() { - let ast = json_to_ast(sample_json()).expect("adapter should succeed"); + let ast = json_to_ast(sample_json()) + .expect("adapter should succeed") + .ast; // Walk all nodes and collect (kind_name, is_named) for the two tokens. let mut let_named = None; let mut ident_named = None; @@ -273,7 +338,9 @@ mod tests { #[test] fn preserves_leaf_text() { - let ast = json_to_ast(sample_json()).expect("adapter should succeed"); + let ast = json_to_ast(sample_json()) + .expect("adapter should succeed") + .ast; let ident = ast .nodes() .iter() @@ -286,7 +353,9 @@ mod tests { #[test] fn maps_source_locations() { - let ast = json_to_ast(sample_json()).expect("adapter should succeed"); + let ast = json_to_ast(sample_json()) + .expect("adapter should succeed") + .ast; let ident = ast .nodes() .iter() @@ -300,13 +369,55 @@ mod tests { assert_eq!(ident.end_position(), Point::new(0, 5)); } + #[test] + fn collects_trivia_into_side_channel() { + // A token carrying a trailing line comment in its trivia. + let json = r#"{ + "kind": "sourceFile", + "range": {"start":{"offset":0,"line":1,"column":1},"end":{"offset":14,"line":1,"column":15}}, + "value": { + "kind": "token", + "tokenKind": "integerLiteral(\"1\")", + "text": "1", + "range": {"start":{"offset":0,"line":1,"column":1},"end":{"offset":1,"line":1,"column":2}}, + "trailingTrivia": [ + { + "kind": "lineComment", + "text": "// c", + "range": {"start":{"offset":2,"line":1,"column":3},"end":{"offset":6,"line":1,"column":7}} + } + ] + } + }"#; + let adapted = json_to_ast(json).expect("adapter should succeed"); + + // The comment is in the side channel, with its text and location. + assert_eq!(adapted.trivia.len(), 1); + let comment = &adapted.trivia[0]; + assert_eq!(comment.kind, "lineComment"); + assert_eq!(comment.text, "// c"); + assert_eq!(comment.range.start_byte, 2); + assert_eq!(comment.range.end_byte, 6); + + // It is not embedded in the AST as a node. + assert!( + adapted + .ast + .nodes() + .iter() + .all(|n| n.kind_name() != "lineComment"), + "comment should not appear as an AST node" + ); + } + /// End-to-end: real Swift source parsed by the shim, then adapted into a /// `yeast::Ast`. Requires the Swift toolchain (like the crate's FFI tests). #[test] fn end_to_end_from_swift_source() { - let json = crate::parse_to_json("func f(n: Int) -> Int { return n }") + let json = crate::parse_to_json("func f(n: Int) -> Int { return n } // trailing") .expect("parsing should succeed"); - let ast = json_to_ast(&json).expect("adapter should succeed"); + let adapted = json_to_ast(&json).expect("adapter should succeed"); + let ast = &adapted.ast; let root = ast.get_node(ast.get_root()).expect("root exists"); assert_eq!(root.kind_name(), "sourceFile"); @@ -323,5 +434,15 @@ mod tests { kinds.contains(&"func"), "expected an anonymous `func` token, got kinds: {kinds:?}" ); + + // The trailing comment is recovered into the side channel. + assert!( + adapted + .trivia + .iter() + .any(|t| t.kind == "lineComment" && t.text == "// trailing"), + "expected the trailing comment in the trivia side channel, got: {:?}", + adapted.trivia + ); } } diff --git a/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift b/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift index 3dcfcb2c3895..6305b1610d06 100644 --- a/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift +++ b/unified/swift-syntax-rs/swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift @@ -39,20 +39,39 @@ private let keptTriviaKinds: Set = [ "unexpectedText", ] -/// Serialize a trivia collection into an array of `{ kind, text }` pieces, -/// keeping only the kinds in `keptTriviaKinds`. -private func serializeTrivia(_ trivia: Trivia) -> [Any] { - trivia.pieces.compactMap { piece -> [String: Any]? in +/// Serialize a trivia collection into an array of `{ kind, text, range }` +/// pieces, keeping only the kinds in `keptTriviaKinds`. +/// +/// `start` is the absolute position of the first piece (a token's leading +/// trivia starts at `token.position`; its trailing trivia at +/// `token.endPositionBeforeTrailingTrivia`). Each piece's range is derived by +/// accumulating piece lengths, so kept pieces carry an exact source location. +private func serializeTrivia( + _ trivia: Trivia, + startingAt start: AbsolutePosition, + _ converter: SourceLocationConverter +) -> [Any] { + var result: [Any] = [] + var offset = start.utf8Offset + for piece in trivia.pieces { + let length = piece.sourceLength.utf8Length // The label of an enum case mirror is the case name (e.g. "spaces", // "lineComment"), which gives us a stable kind without an exhaustive // switch over every `TriviaPiece` case. let kind = Mirror(reflecting: piece).children.first?.label ?? "\(piece)" - guard keptTriviaKinds.contains(kind) else { return nil } - return [ - "kind": kind, - "text": Trivia(pieces: [piece]).description, - ] + if keptTriviaKinds.contains(kind) { + result.append([ + "kind": kind, + "text": Trivia(pieces: [piece]).description, + "range": [ + "start": location(AbsolutePosition(utf8Offset: offset), converter), + "end": location(AbsolutePosition(utf8Offset: offset + length), converter), + ], + ]) + } + offset += length } + return result } /// Recursively convert a SwiftSyntax node into a JSON-serializable value. @@ -95,11 +114,17 @@ private func serialize( "range": range, ] // Only emit trivia when present; after filtering, most tokens have none. - let leading = serializeTrivia(token.leadingTrivia) + // Leading trivia starts at the token's own position; trailing trivia + // starts just after the token's content. + let leading = serializeTrivia( + token.leadingTrivia, startingAt: token.position, converter) if !leading.isEmpty { result["leadingTrivia"] = leading } - let trailing = serializeTrivia(token.trailingTrivia) + let trailing = serializeTrivia( + token.trailingTrivia, + startingAt: token.endPositionBeforeTrailingTrivia, + converter) if !trailing.isEmpty { result["trailingTrivia"] = trailing } From 58ddeacde08e66e31f5dd8a05b39bcceecfedd4f Mon Sep 17 00:00:00 2001 From: Taus Date: Tue, 7 Jul 2026 15:56:56 +0000 Subject: [PATCH 24/60] unified: Hook up swift-syntax AST At this point it's only a proof-of-concept translation -- it translates `sourceFile` nodes, but everything else gets mapped to 'unsupported_node`. --- Cargo.lock | 4 - shared/yeast-schema/src/schema.rs | 22 ++ shared/yeast/src/lib.rs | 39 +++- shared/yeast/tests/test.rs | 53 +++++ unified/extractor/src/languages/mod.rs | 9 + .../src/languages/swift/adapter.rs} | 51 +---- .../extractor/src/languages/swift/swift.rs | 19 ++ .../tests/fixtures/let_x.swiftsyntax.json | 196 ++++++++++++++++++ .../extractor/tests/swift_syntax_pipeline.rs | 49 +++++ unified/swift-syntax-rs/BUILD.bazel | 2 - unified/swift-syntax-rs/Cargo.toml | 4 - unified/swift-syntax-rs/README.md | 27 +-- unified/swift-syntax-rs/src/lib.rs | 9 +- 13 files changed, 404 insertions(+), 80 deletions(-) rename unified/{swift-syntax-rs/src/yeast_adapter.rs => extractor/src/languages/swift/adapter.rs} (88%) create mode 100644 unified/extractor/tests/fixtures/let_x.swiftsyntax.json create mode 100644 unified/extractor/tests/swift_syntax_pipeline.rs diff --git a/Cargo.lock b/Cargo.lock index 24885fea8563..7a9f19667911 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2853,10 +2853,6 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "swift-syntax-rs" version = "0.1.0" -dependencies = [ - "serde_json", - "yeast", -] [[package]] name = "syn" diff --git a/shared/yeast-schema/src/schema.rs b/shared/yeast-schema/src/schema.rs index 4acd14377a4d..9c438d7c22e8 100644 --- a/shared/yeast-schema/src/schema.rs +++ b/shared/yeast-schema/src/schema.rs @@ -167,6 +167,28 @@ impl Schema { id } + /// Register every kind (named and unnamed) and field *name* from `other` + /// into this schema (idempotent). Ids are assigned in this schema's own id + /// space; existing ids are unchanged. + /// + /// This is used when running desugaring rules over an AST that was built + /// against a different schema (e.g. from an external parser): the rules + /// build output nodes whose kind/field names come from `other`, and those + /// names must resolve in the AST's own schema. Only names are needed — the + /// rule engine resolves kinds/fields by name and does not consult + /// `other`'s field-type or supertype information. + pub fn register_names_from(&mut self, other: &Schema) { + for name in other.kind_ids.keys() { + self.register_kind(name); + } + for name in other.unnamed_kind_ids.keys() { + self.register_unnamed_kind(name); + } + for name in other.field_ids.keys() { + self.register_field(name); + } + } + /// Track a name for a kind ID without registering it as named or /// unnamed. Useful when importing tree-sitter ID tables that may /// contain duplicate IDs across the named/unnamed split. diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index b2709ab406ee..89facfaea99f 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -524,10 +524,15 @@ impl Ast { self.schema.register_field(name) } - fn union_source_range_of_children( - &self, - fields: &BTreeMap>, - ) -> Option { + /// Register every kind and field name from `schema` into this AST's schema + /// (idempotent). Used before desugaring an externally-built AST so that + /// rules can build output nodes whose kind/field names come from the + /// desugarer's output schema. + pub fn register_names_from_schema(&mut self, schema: &schema::Schema) { + self.schema.register_names_from(schema); + } + + fn union_source_range_of_children(&self, fields: &BTreeMap>) -> Option { let mut start_byte: Option = None; let mut end_byte: Option = None; let mut start_point = Point { row: 0, column: 0 }; @@ -1448,6 +1453,18 @@ impl<'a, C: Clone + Default> Runner<'a, C> { let mut user_ctx = C::default(); self.run_with_ctx(input, &mut user_ctx) } + + /// Run all phases over an already-built `ast`, using the default context + /// (`C::default()`). Unlike [`run_from_tree`](Self::run_from_tree), the AST + /// is supplied by the caller (e.g. built from an external parser's output) + /// rather than constructed from a tree-sitter tree. The caller is + /// responsible for ensuring the AST's schema knows any output kind/field + /// names the rules will build (see [`Ast::register_names_from_schema`]). + pub fn run_from_ast(&self, mut ast: Ast) -> Result { + let mut user_ctx = C::default(); + self.run_phases(&mut ast, &mut user_ctx)?; + Ok(ast) + } } // --------------------------------------------------------------------------- @@ -1470,6 +1487,12 @@ pub trait Desugarer: Send + Sync { /// Parse `tree` against `source` and run the desugaring pipeline. /// Each call constructs a fresh default user context internally. fn run_from_tree(&self, tree: &tree_sitter::Tree, source: &[u8]) -> Result; + + /// Run the desugaring pipeline over an already-built `ast` (e.g. produced + /// by an external parser rather than tree-sitter). The desugarer ensures + /// the AST's schema knows its output kind/field names before running the + /// rules. Each call constructs a fresh default user context internally. + fn run_from_ast(&self, ast: Ast) -> Result; } /// A concrete [`Desugarer`] backed by a [`DesugaringConfig`] for a @@ -1507,4 +1530,12 @@ impl Desugarer for ConcreteDesugarer let runner = Runner::with_schema(self.language.clone(), &self.schema, &self.config.phases); runner.run_from_tree(tree, source) } + + fn run_from_ast(&self, mut ast: Ast) -> Result { + // The AST was built against its own (external) schema; make sure the + // output kind/field names the rules build are resolvable in it. + ast.register_names_from_schema(&self.schema); + let runner = Runner::with_schema(self.language.clone(), &self.schema, &self.config.phases); + runner.run_from_ast(ast) + } } diff --git a/shared/yeast/tests/test.rs b/shared/yeast/tests/test.rs index 3a24709dd9ff..756e219bd890 100644 --- a/shared/yeast/tests/test.rs +++ b/shared/yeast/tests/test.rs @@ -266,6 +266,59 @@ fn test_query_match() { assert!(captures.get_var("right").is_ok()); } +#[test] +fn test_run_from_ast_desugars_hand_built_tree() { + use std::collections::BTreeMap; + + // Output schema for the desugared tree. Its kind/field names must become + // resolvable in the hand-built AST's schema for the rule to build them. + let schema_yaml = r#" +named: + assignment: + left: leaf + leaf: +"#; + + // A rule over an *input* kind (`wrapper`) that is not in the output schema, + // rewriting to an output `assignment` node. + let rules: Vec = vec![yeast::rule!( + (wrapper) + => + (assignment left: (leaf "lit")) + )]; + + let lang: tree_sitter::Language = tree_sitter_ruby::LANGUAGE.into(); + let config = DesugaringConfig::<()>::new() + .add_phase("test", PhaseKind::OneShot, rules) + .with_output_node_types_yaml(schema_yaml); + let desugarer = ConcreteDesugarer::new(lang, config).unwrap(); + + // Build the input AST by hand, as an external parser adapter would. The + // schema starts empty and gains the `wrapper` input kind on the fly. + let mut ast = Ast::with_schema(yeast::schema::Schema::new()); + let wrapper_kind = ast.register_kind("wrapper"); + let root = ast.create_node_with_range( + wrapper_kind, + NodeContent::DynamicString(String::new()), + BTreeMap::new(), + true, + None, + ); + ast.set_root(root); + + // Desugaring the hand-built AST applies the rule, producing `assignment` + // even though the AST was built against a schema with no output kinds. + let out = desugarer + .run_from_ast(ast) + .expect("run_from_ast should succeed"); + let out_root = out.get_node(out.get_root()).expect("root exists"); + assert_eq!(out_root.kind_name(), "assignment"); + let dump = dump_ast(&out, out.get_root(), ""); + assert!(dump.contains("assignment"), "unexpected dump: {dump}"); + assert!(dump.contains("left"), "unexpected dump: {dump}"); + assert!(dump.contains("leaf"), "unexpected dump: {dump}"); +} + #[test] fn test_query_no_match() { let runner: Runner = Runner::new(tree_sitter_ruby::LANGUAGE.into(), &[]); diff --git a/unified/extractor/src/languages/mod.rs b/unified/extractor/src/languages/mod.rs index 20ad599edfb6..52a1bd40ffc7 100644 --- a/unified/extractor/src/languages/mod.rs +++ b/unified/extractor/src/languages/mod.rs @@ -3,6 +3,15 @@ use codeql_extractor::extractor::simple; #[path = "swift/swift.rs"] mod swift; +/// swift-syntax JSON -> `yeast::Ast` adapter for the Swift front-end. +/// +/// Currently exercised by tests and the forthcoming runtime extraction path; +/// `allow(dead_code)` because this is a binary crate, so its public API isn't +/// counted as used until the binary itself calls it. +#[path = "swift/adapter.rs"] +#[allow(dead_code)] +pub mod swift_adapter; + /// Shared YEAST output AST schema for all languages. pub(crate) const OUTPUT_AST_SCHEMA: &str = include_str!("../../ast_types.yml"); diff --git a/unified/swift-syntax-rs/src/yeast_adapter.rs b/unified/extractor/src/languages/swift/adapter.rs similarity index 88% rename from unified/swift-syntax-rs/src/yeast_adapter.rs rename to unified/extractor/src/languages/swift/adapter.rs index e2adf1df5a6a..37d5aac00d2d 100644 --- a/unified/swift-syntax-rs/src/yeast_adapter.rs +++ b/unified/extractor/src/languages/swift/adapter.rs @@ -1,6 +1,10 @@ -//! Adapter that converts the swift-syntax JSON tree (see [`crate::parse_to_json`]) -//! into a [`yeast::Ast`], the in-memory format the CodeQL desugaring rules -//! operate on. +//! Converts the swift-syntax JSON syntax tree into a [`yeast::Ast`], the +//! in-memory format the CodeQL desugaring rules operate on. +//! +//! The JSON tree is produced by the `swift-syntax-rs` crate's Swift FFI shim +//! (`parse_to_json`). This module is pure Rust (only `yeast` + `serde_json`), +//! so the extractor consumes swift-syntax output without pulling in the Swift +//! toolchain (the JSON is produced out-of-process). //! //! The mapping mirrors tree-sitter's node model, which is what yeast (and the //! extractor's rewrite rules) expect: @@ -15,9 +19,8 @@ //! list-valued field maps directly to that field holding several children. //! //! Note: this preserves swift-syntax's own kind/field names. Aligning those -//! names with the tree-sitter-swift schema (so the existing rewrite rules fire) -//! is a separate, later step; this module is only concerned with getting the -//! tree into yeast's format. +//! names with the tree-sitter-swift schema (so the rewrite rules in +//! [`super::swift`] fire) is done incrementally in the rules. use std::collections::BTreeMap; @@ -409,40 +412,4 @@ mod tests { "comment should not appear as an AST node" ); } - - /// End-to-end: real Swift source parsed by the shim, then adapted into a - /// `yeast::Ast`. Requires the Swift toolchain (like the crate's FFI tests). - #[test] - fn end_to_end_from_swift_source() { - let json = crate::parse_to_json("func f(n: Int) -> Int { return n } // trailing") - .expect("parsing should succeed"); - let adapted = json_to_ast(&json).expect("adapter should succeed"); - let ast = &adapted.ast; - - let root = ast.get_node(ast.get_root()).expect("root exists"); - assert_eq!(root.kind_name(), "sourceFile"); - - // The tree contains a `functionDecl` layout node and an anonymous - // `func` keyword token keyed by its text. - let mut kinds: Vec<&str> = ast.nodes().iter().map(|n| n.kind_name()).collect(); - kinds.sort_unstable(); - assert!( - kinds.contains(&"functionDecl"), - "expected a functionDecl node, got kinds: {kinds:?}" - ); - assert!( - kinds.contains(&"func"), - "expected an anonymous `func` token, got kinds: {kinds:?}" - ); - - // The trailing comment is recovered into the side channel. - assert!( - adapted - .trivia - .iter() - .any(|t| t.kind == "lineComment" && t.text == "// trailing"), - "expected the trailing comment in the trivia side channel, got: {:?}", - adapted.trivia - ); - } } diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 74809ea18e41..13f1a6beadf0 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -135,6 +135,25 @@ fn translation_rules() -> Vec> { // Declarations may be wrapped in local/global wrapper nodes. rule!((global_declaration _ @inner) => stmt { inner }), rule!((local_declaration _ @inner) => stmt { inner }), + // ---- swift-syntax front-end (minimal hook-up) ---- + // These rules target the swift-syntax AST (camelCase kind names), + // produced by the sibling `adapter` module. They coexist with the + // tree-sitter rules (snake_case names): rules are dispatched by exact + // kind name, and the two name spaces never collide, so these are inert + // on the tree-sitter path. Only the minimal top-level mapping lives here + // to demonstrate the pipeline end-to-end; the full translation is added + // separately. Unmatched swift-syntax nodes fall through to the + // `unsupported_node` fallback at the end. + // + // `sourceFile` holds its top-level statements in an (elided) + // `statements` collection; each element is a `codeBlockItem` wrapping + // the real node. + rule!( + (sourceFile statements: _* @items) + => + (top_level body: (block stmt: {items})) + ), + rule!((codeBlockItem item: @item) => stmt { item }), // ---- Literals ---- rule!((integer_literal) => (int_literal)), rule!((hex_literal) => (int_literal)), diff --git a/unified/extractor/tests/fixtures/let_x.swiftsyntax.json b/unified/extractor/tests/fixtures/let_x.swiftsyntax.json new file mode 100644 index 000000000000..6c3d18e2fef0 --- /dev/null +++ b/unified/extractor/tests/fixtures/let_x.swiftsyntax.json @@ -0,0 +1,196 @@ +{ + "endOfFileToken": { + "kind": "token", + "range": { + "end": { + "column": 1, + "line": 2, + "offset": 10 + }, + "start": { + "column": 1, + "line": 2, + "offset": 10 + } + }, + "text": "", + "tokenKind": "endOfFile" + }, + "kind": "sourceFile", + "range": { + "end": { + "column": 1, + "line": 2, + "offset": 10 + }, + "start": { + "column": 1, + "line": 1, + "offset": 0 + } + }, + "statements": [ + { + "item": { + "attributes": [], + "bindings": [ + { + "initializer": { + "equal": { + "kind": "token", + "range": { + "end": { + "column": 8, + "line": 1, + "offset": 7 + }, + "start": { + "column": 7, + "line": 1, + "offset": 6 + } + }, + "text": "=", + "tokenKind": "equal" + }, + "kind": "initializerClause", + "range": { + "end": { + "column": 10, + "line": 1, + "offset": 9 + }, + "start": { + "column": 7, + "line": 1, + "offset": 6 + } + }, + "value": { + "kind": "integerLiteralExpr", + "literal": { + "kind": "token", + "range": { + "end": { + "column": 10, + "line": 1, + "offset": 9 + }, + "start": { + "column": 9, + "line": 1, + "offset": 8 + } + }, + "text": "1", + "tokenKind": "integerLiteral(\"1\")" + }, + "range": { + "end": { + "column": 10, + "line": 1, + "offset": 9 + }, + "start": { + "column": 9, + "line": 1, + "offset": 8 + } + } + } + }, + "kind": "patternBinding", + "pattern": { + "identifier": { + "kind": "token", + "range": { + "end": { + "column": 6, + "line": 1, + "offset": 5 + }, + "start": { + "column": 5, + "line": 1, + "offset": 4 + } + }, + "text": "x", + "tokenKind": "identifier(\"x\")" + }, + "kind": "identifierPattern", + "range": { + "end": { + "column": 6, + "line": 1, + "offset": 5 + }, + "start": { + "column": 5, + "line": 1, + "offset": 4 + } + } + }, + "range": { + "end": { + "column": 10, + "line": 1, + "offset": 9 + }, + "start": { + "column": 5, + "line": 1, + "offset": 4 + } + } + } + ], + "bindingSpecifier": { + "kind": "token", + "range": { + "end": { + "column": 4, + "line": 1, + "offset": 3 + }, + "start": { + "column": 1, + "line": 1, + "offset": 0 + } + }, + "text": "let", + "tokenKind": "keyword(SwiftSyntax.Keyword.let)" + }, + "kind": "variableDecl", + "modifiers": [], + "range": { + "end": { + "column": 10, + "line": 1, + "offset": 9 + }, + "start": { + "column": 1, + "line": 1, + "offset": 0 + } + } + }, + "kind": "codeBlockItem", + "range": { + "end": { + "column": 10, + "line": 1, + "offset": 9 + }, + "start": { + "column": 1, + "line": 1, + "offset": 0 + } + } + } + ] +} diff --git a/unified/extractor/tests/swift_syntax_pipeline.rs b/unified/extractor/tests/swift_syntax_pipeline.rs new file mode 100644 index 000000000000..cdaae1f47c6e --- /dev/null +++ b/unified/extractor/tests/swift_syntax_pipeline.rs @@ -0,0 +1,49 @@ +//! Integration test for the swift-syntax front-end pipeline: +//! +//! swift-syntax JSON -> `swift_adapter::json_to_ast` -> yeast `Ast` +//! -> `Desugarer::run_from_ast` (the real Swift translation rules) -> dump. +//! +//! This exercises the whole chain *without* the Swift toolchain: the JSON +//! fixture is a real `parse_to_json` dump (see the file header) fed through the +//! pure-Rust adapter module. It verifies the desugarer runs end-to-end over an +//! externally-built AST. + +use yeast::dump::dump_ast; + +#[path = "../src/languages/mod.rs"] +mod languages; + +/// A real `swift-syntax-rs` JSON dump of the Swift source `let x = 1`. +const LET_X_JSON: &str = include_str!("fixtures/let_x.swiftsyntax.json"); + +#[test] +fn swift_syntax_json_runs_through_the_desugarer() { + let lang = languages::all_language_specs() + .into_iter() + .find(|l| l.file_globs.iter().any(|g| g.contains("swift"))) + .expect("swift language spec"); + let desugarer = lang.desugar.as_deref().expect("swift desugarer"); + + // Adapt the swift-syntax JSON into a yeast AST (pure Rust, no Swift FFI). + let adapted = + languages::swift_adapter::json_to_ast(LET_X_JSON).expect("adapter should succeed"); + assert_eq!( + adapted + .ast + .get_node(adapted.ast.get_root()) + .unwrap() + .kind_name(), + "sourceFile" + ); + + // Run the real Swift desugaring rules over the externally-built AST. The + // top-level swift-syntax rules map `sourceFile` to `top_level`/`block`; + // kinds without swift-syntax rules yet fall back to `unsupported_node`. + let desugared = desugarer + .run_from_ast(adapted.ast) + .expect("desugaring an externally-built AST should not error"); + + let dump = dump_ast(&desugared, desugared.get_root(), ""); + assert!(dump.contains("top_level"), "unexpected dump: {dump}"); + assert!(dump.contains("block"), "unexpected dump: {dump}"); +} diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel index 3a2e5ed0796b..9db40d4db30c 100644 --- a/unified/swift-syntax-rs/BUILD.bazel +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -35,8 +35,6 @@ rust_library( edition = "2024", deps = [ ":swift_syntax_ffi", - "//shared/yeast", - "@vendor_ts__serde_json-1.0.145//:serde_json", ], ) diff --git a/unified/swift-syntax-rs/Cargo.toml b/unified/swift-syntax-rs/Cargo.toml index 576246468480..6957fb0e50e9 100644 --- a/unified/swift-syntax-rs/Cargo.toml +++ b/unified/swift-syntax-rs/Cargo.toml @@ -12,7 +12,3 @@ path = "src/lib.rs" [[bin]] name = "swift-syntax-parse" path = "src/main.rs" - -[dependencies] -serde_json = "1.0" -yeast = { path = "../../shared/yeast" } diff --git a/unified/swift-syntax-rs/README.md b/unified/swift-syntax-rs/README.md index 08cabeb736b0..a2d66495a541 100644 --- a/unified/swift-syntax-rs/README.md +++ b/unified/swift-syntax-rs/README.md @@ -171,26 +171,12 @@ echo 'let x = 1' | cargo run --bin swift-syntax-parse ## Converting to a yeast AST -For use in the CodeQL extractor, the JSON tree can be converted into a -[`yeast::Ast`](../../shared/yeast) — the in-memory format the extractor's -rewrite rules operate on — via [`yeast_adapter::json_to_ast`](src/yeast_adapter.rs): - -```rust -let json = swift_syntax_rs::parse_to_json("let x = 1")?; -let adapted = swift_syntax_rs::yeast_adapter::json_to_ast(&json)?; -let ast = adapted.ast; // the yeast::Ast -let comments = adapted.trivia; // side-channel comment/unexpectedText tokens -``` - -The adapter mirrors tree-sitter's node model, which is what yeast expects: -layout nodes and varying tokens (identifiers, literals, operators) become -**named** nodes; fixed tokens (keywords, punctuation) become **anonymous** -nodes keyed by their text. Comments (and `unexpectedText`) are harvested into a -side channel (`adapted.trivia`) during the same traversal rather than embedded -in the tree, matching how the extractor treats tree-sitter `extra` nodes. It -preserves swift-syntax's own kind/field names — aligning them with the -tree-sitter-swift schema so the existing rewrite rules fire is a separate, -later step. +The JSON tree is consumed by the CodeQL extractor, which converts it into a +[`yeast::Ast`](../../shared/yeast) — the in-memory format its rewrite rules +operate on. That adapter is a pure-Rust module living in the extractor +(`unified/extractor/src/languages/swift/adapter.rs`), so the extractor never +needs the Swift toolchain: it consumes the JSON produced out-of-process by this +crate's `parse_to_json` / the `swift-syntax-parse` binary. ## Layout @@ -198,5 +184,4 @@ later step. - `build.rs` — builds the Swift package and emits link/rpath flags (local `cargo` only). - `BUILD.bazel` — Bazel targets for the hermetic CI build (swift_library + rust targets). - `src/lib.rs` — safe Rust bindings (`parse_to_json`). -- `src/yeast_adapter.rs` — converts the JSON tree into a `yeast::Ast`. - `src/main.rs` — demo CLI. diff --git a/unified/swift-syntax-rs/src/lib.rs b/unified/swift-syntax-rs/src/lib.rs index aa711e322a49..2544c0bc408d 100644 --- a/unified/swift-syntax-rs/src/lib.rs +++ b/unified/swift-syntax-rs/src/lib.rs @@ -3,9 +3,12 @@ //! //! The heavy lifting is done by a small Swift shim (see `swift/`) that links //! against `SwiftSyntax`/`SwiftParser` and exposes a tiny C ABI. This module -//! provides safe Rust bindings on top of that ABI. - -pub mod yeast_adapter; +//! provides safe Rust bindings on top of that ABI, exposing [`parse_to_json`] +//! which turns Swift source into a JSON syntax tree. +//! +//! Converting that JSON into a `yeast::Ast` (for the CodeQL extractor) is done +//! by the extractor's own pure-Rust adapter module, keeping the Swift toolchain +//! out of the extractor's build. use std::ffi::{CStr, CString}; use std::os::raw::c_char; From 33261f441e1ed81de8841cde84c072b32adcd132 Mon Sep 17 00:00:00 2001 From: Anders Schack-Mulligen Date: Fri, 10 Jul 2026 15:32:44 +0200 Subject: [PATCH 25/60] Java: Improve join order. Before: ``` [2026-07-10 15:15:58] Evaluated non-recursive predicate ControlFlowGraph::NonReturningCalls::EffectivelyNonVirtualMethod.getAnAccess/0#dispred#efac59ed@090459qt in 4303ms (size: 186002). Evaluated relational algebra for predicate ControlFlowGraph::NonReturningCalls::EffectivelyNonVirtualMethod.getAnAccess/0#dispred#efac59ed@090459qt with tuple counts: 790592 ~0% {2} r1 = JOIN `Expr::MethodCall.getMethod/0#dispred#41989dc9_10#join_rhs` WITH `Member::Method.getSourceDeclaration/0#dispred#93e6cdf8` ON FIRST 1 OUTPUT Rhs.1, Lhs.1 539755681 ~2% {2} | JOIN WITH `Member::SrcMethod.getAPossibleImplementationOfSrcMethod/0#dispred#4f4317e6#bf` ON FIRST 1 OUTPUT Rhs.1, Lhs.1 186002 ~2% {2} | JOIN WITH ControlFlowGraph::NonReturningCalls::EffectivelyNonVirtualMethod#86c19e07 ON FIRST 1 OUTPUT Lhs.0, Lhs.1 return r1 ``` After: ``` [2026-07-10 15:29:39] Evaluated non-recursive predicate ControlFlowGraph::NonReturningCalls::EffectivelyNonVirtualMethod.getAnAccess/0#dispred#efac59ed@32fb2e61 in 10ms (size: 186002). Evaluated relational algebra for predicate ControlFlowGraph::NonReturningCalls::EffectivelyNonVirtualMethod.getAnAccess/0#dispred#efac59ed@32fb2e61 with tuple counts: 122765 ~0% {2} r1 = SCAN ControlFlowGraph::NonReturningCalls::EffectivelyNonVirtualMethod#86c19e07 OUTPUT In.0, In.0 122766 ~0% {2} | JOIN WITH `Member::SrcMethod.getAPossibleImplementationOfSrcMethod/0#dispred#4f4317e6_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Lhs.1 126911 ~0% {2} | JOIN WITH `Member::Method.getSourceDeclaration/0#dispred#93e6cdf8_10#join_rhs` ON FIRST 1 OUTPUT Rhs.1, Lhs.1 186002 ~2% {2} | JOIN WITH `Expr::MethodCall.getMethod/0#dispred#41989dc9_10#join_rhs` ON FIRST 1 OUTPUT Lhs.1, Rhs.1 return r1 ``` --- java/ql/lib/semmle/code/java/ControlFlowGraph.qll | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll index 7390be9cb3cf..c4f0c44b3813 100644 --- a/java/ql/lib/semmle/code/java/ControlFlowGraph.qll +++ b/java/ql/lib/semmle/code/java/ControlFlowGraph.qll @@ -406,7 +406,9 @@ private module NonReturningCalls { } /** Gets a `MethodCall` that calls this method. */ - MethodCall getAnAccess() { result.getMethod().getAPossibleImplementation() = this } + MethodCall getAnAccess() { + result.getMethod().getAPossibleImplementation() = pragma[only_bind_out](this) + } } /** Holds if a call to `m` indicates that `m` is expected to return. */ From 2cd9220b741975a11f3db63639f456ab7933b14d Mon Sep 17 00:00:00 2001 From: Kristen Newbury Date: Fri, 10 Jul 2026 09:36:53 -0400 Subject: [PATCH 26/60] Format --- actions/ql/lib/codeql/actions/security/ControlChecks.qll | 3 ++- .../ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.ql | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actions/ql/lib/codeql/actions/security/ControlChecks.qll b/actions/ql/lib/codeql/actions/security/ControlChecks.qll index 900f62556b05..ed8d6c2d050c 100644 --- a/actions/ql/lib/codeql/actions/security/ControlChecks.qll +++ b/actions/ql/lib/codeql/actions/security/ControlChecks.qll @@ -144,7 +144,8 @@ class EnvironmentCheck extends ControlCheck instanceof Environment { // Environment checks are not effective against any mutable attacks // they do actually protect against untrusted code execution (sha) override predicate protectsCategoryAndEvent(string category, string event) { - event = [actor_is_attacker_event(), actor_not_attacker_event()] and category = non_toctou_category() + event = [actor_is_attacker_event(), actor_not_attacker_event()] and + category = non_toctou_category() } } diff --git a/actions/ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.ql b/actions/ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.ql index 14a6c9a4c569..2aacf20b35fc 100644 --- a/actions/ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.ql +++ b/actions/ql/src/Security/CWE-367/UntrustedCheckoutTOCTOUCritical.ql @@ -23,8 +23,7 @@ where // the checked-out code may lead to arbitrary code execution checkout.getAFollowingStep() = step and // the checkout occurs in a privileged context - inPrivilegedContext(checkout, event) - and + inPrivilegedContext(checkout, event) and // the mutable checkout step is protected by an Insufficient access check exists(ControlCheck check1 | check1.protects(checkout, event, "untrusted-checkout")) and not exists(ControlCheck check2 | check2.protects(checkout, event, "untrusted-checkout-toctou")) From 6057b4b6e9c187732b2f985366d2dc081e8dadbb Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Sat, 11 Jul 2026 07:28:14 +0100 Subject: [PATCH 27/60] Fix path sanitizer --- .../2026-07-11-file-getname-path-sanitizer.md | 4 +++ java/ql/lib/ext/java.io.model.yml | 5 ---- .../code/java/security/PathSanitizer.qll | 20 ++++++++++--- .../CWE-022/semmle/tests/TaintedPath.expected | 26 +++++++++++++++-- .../CWE-022/semmle/tests/TaintedPath.java | 28 ++++++++++++++++--- 5 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 java/ql/lib/change-notes/2026-07-11-file-getname-path-sanitizer.md diff --git a/java/ql/lib/change-notes/2026-07-11-file-getname-path-sanitizer.md b/java/ql/lib/change-notes/2026-07-11-file-getname-path-sanitizer.md new file mode 100644 index 000000000000..dddcae521ff3 --- /dev/null +++ b/java/ql/lib/change-notes/2026-07-11-file-getname-path-sanitizer.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* `java.io.File.getName()` is no longer treated as a complete sanitizer for `java/path-injection`, since it does not remove a `..` path component (for example `new File("..").getName()` returns `".."`). It is now only recognized as a sanitizer when combined with a subsequent check for `..` components, which may result in new alerts. diff --git a/java/ql/lib/ext/java.io.model.yml b/java/ql/lib/ext/java.io.model.yml index dd47342d590a..f7c236de4eeb 100644 --- a/java/ql/lib/ext/java.io.model.yml +++ b/java/ql/lib/ext/java.io.model.yml @@ -162,8 +162,3 @@ extensions: extensible: sourceModel data: - ["java.io", "FileInputStream", True, "FileInputStream", "", "", "Argument[this]", "file", "manual"] - - addsTo: - pack: codeql/java-all - extensible: barrierModel - data: - - ["java.io", "File", True, "getName", "()", "", "ReturnValue", "path-injection", "manual"] diff --git a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll index 42304bb72dc9..7c428a6691cd 100644 --- a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll +++ b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll @@ -97,16 +97,28 @@ private class AllowedPrefixSanitizer extends PathInjectionSanitizer { } } +/** A call to `java.io.File.getName`, which returns the final component of a path. */ +private class FileGetNameCall extends MethodCall { + FileGetNameCall() { this.getMethod().hasQualifiedName("java.io", "File", "getName") } +} + /** * Holds if `g` is a guard that considers a path safe because it is checked for `..` components, having previously - * been checked for a trusted prefix. + * been checked for a trusted prefix or been reduced to its final path component by `File.getName`. */ private predicate dotDotCheckGuard(Guard g, Expr e, boolean branch) { pathTraversalGuard(g, e, branch) and - exists(Guard previousGuard | - previousGuard.(AllowedPrefixGuard).controls(g.getBasicBlock(), true) + ( + exists(Guard previousGuard | + previousGuard.(AllowedPrefixGuard).controls(g.getBasicBlock(), true) + or + previousGuard.(BlockListGuard).controls(g.getBasicBlock(), false) + ) or - previousGuard.(BlockListGuard).controls(g.getBasicBlock(), false) + // `File.getName` strips any directory prefix, returning only the final path + // component. The only remaining path traversal risk is when that component is + // itself `..`, so a check for `..` components completes the sanitization. + exists(FileGetNameCall getName | localTaintFlowToPathGuard(getName, g)) ) } diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected index bfeb4d730c25..b44b294edc47 100644 --- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected +++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.expected @@ -1,6 +1,7 @@ #select | SanitizationTests2.java:16:59:16:62 | path | SanitizationTests2.java:14:59:14:91 | path : String | SanitizationTests2.java:16:59:16:62 | path | This path depends on a $@. | SanitizationTests2.java:14:59:14:91 | path | user-provided value | | TaintedPath.java:16:71:16:78 | filename | TaintedPath.java:13:58:13:78 | getInputStream(...) : InputStream | TaintedPath.java:16:71:16:78 | filename | This path depends on a $@. | TaintedPath.java:13:58:13:78 | getInputStream(...) | user-provided value | +| TaintedPath.java:105:71:105:78 | baseName | TaintedPath.java:98:58:98:78 | getInputStream(...) : InputStream | TaintedPath.java:105:71:105:78 | baseName | This path depends on a $@. | TaintedPath.java:98:58:98:78 | getInputStream(...) | user-provided value | | Test.java:37:52:37:68 | (...)... | Test.java:32:16:32:45 | getParameter(...) : String | Test.java:37:52:37:68 | (...)... | This path depends on a $@. | Test.java:32:16:32:45 | getParameter(...) | user-provided value | | Test.java:39:32:39:48 | (...)... | Test.java:32:16:32:45 | getParameter(...) : String | Test.java:39:32:39:48 | (...)... | This path depends on a $@. | Test.java:32:16:32:45 | getParameter(...) | user-provided value | | Test.java:41:47:41:63 | (...)... | Test.java:32:16:32:45 | getParameter(...) : String | Test.java:41:47:41:63 | (...)... | This path depends on a $@. | Test.java:32:16:32:45 | getParameter(...) | user-provided value | @@ -81,9 +82,18 @@ edges | SanitizationTests2.java:14:59:14:91 | path : String | SanitizationTests2.java:16:59:16:62 | path | provenance | Sink:MaD:23 | | TaintedPath.java:13:17:13:89 | new BufferedReader(...) : BufferedReader | TaintedPath.java:14:27:14:40 | filenameReader : BufferedReader | provenance | | | TaintedPath.java:13:36:13:88 | new InputStreamReader(...) : InputStreamReader | TaintedPath.java:13:17:13:89 | new BufferedReader(...) : BufferedReader | provenance | MaD:74 | -| TaintedPath.java:13:58:13:78 | getInputStream(...) : InputStream | TaintedPath.java:13:36:13:88 | new InputStreamReader(...) : InputStreamReader | provenance | Src:MaD:72 MaD:76 | +| TaintedPath.java:13:58:13:78 | getInputStream(...) : InputStream | TaintedPath.java:13:36:13:88 | new InputStreamReader(...) : InputStreamReader | provenance | Src:MaD:72 MaD:78 | | TaintedPath.java:14:27:14:40 | filenameReader : BufferedReader | TaintedPath.java:14:27:14:51 | readLine(...) : String | provenance | MaD:75 | | TaintedPath.java:14:27:14:51 | readLine(...) : String | TaintedPath.java:16:71:16:78 | filename | provenance | Sink:MaD:27 | +| TaintedPath.java:98:17:98:89 | new BufferedReader(...) : BufferedReader | TaintedPath.java:99:27:99:40 | filenameReader : BufferedReader | provenance | | +| TaintedPath.java:98:36:98:88 | new InputStreamReader(...) : InputStreamReader | TaintedPath.java:98:17:98:89 | new BufferedReader(...) : BufferedReader | provenance | MaD:74 | +| TaintedPath.java:98:58:98:78 | getInputStream(...) : InputStream | TaintedPath.java:98:36:98:88 | new InputStreamReader(...) : InputStreamReader | provenance | Src:MaD:72 MaD:78 | +| TaintedPath.java:99:27:99:40 | filenameReader : BufferedReader | TaintedPath.java:99:27:99:51 | readLine(...) : String | provenance | MaD:75 | +| TaintedPath.java:99:27:99:51 | readLine(...) : String | TaintedPath.java:100:30:100:37 | filename : String | provenance | | +| TaintedPath.java:100:21:100:38 | new File(...) : File | TaintedPath.java:101:27:101:30 | file : File | provenance | | +| TaintedPath.java:100:30:100:37 | filename : String | TaintedPath.java:100:21:100:38 | new File(...) : File | provenance | MaD:76 | +| TaintedPath.java:101:27:101:30 | file : File | TaintedPath.java:101:27:101:40 | getName(...) : String | provenance | MaD:77 | +| TaintedPath.java:101:27:101:40 | getName(...) : String | TaintedPath.java:105:71:105:78 | baseName | provenance | Sink:MaD:27 | | Test.java:32:16:32:45 | getParameter(...) : String | Test.java:37:61:37:68 | source(...) : String | provenance | Src:MaD:73 | | Test.java:32:16:32:45 | getParameter(...) : String | Test.java:39:41:39:48 | source(...) : String | provenance | Src:MaD:73 | | Test.java:32:16:32:45 | getParameter(...) : String | Test.java:41:56:41:63 | source(...) : String | provenance | Src:MaD:73 | @@ -312,7 +322,9 @@ models | 73 | Source: javax.servlet; ServletRequest; false; getParameter; (String); ; ReturnValue; remote; manual | | 74 | Summary: java.io; BufferedReader; false; BufferedReader; ; ; Argument[0]; Argument[this]; taint; manual | | 75 | Summary: java.io; BufferedReader; true; readLine; ; ; Argument[this]; ReturnValue; taint; manual | -| 76 | Summary: java.io; InputStreamReader; false; InputStreamReader; ; ; Argument[0]; Argument[this]; taint; manual | +| 76 | Summary: java.io; File; false; File; ; ; Argument[0]; Argument[this]; taint; manual | +| 77 | Summary: java.io; File; true; getName; (); ; Argument[this]; ReturnValue; taint; manual | +| 78 | Summary: java.io; InputStreamReader; false; InputStreamReader; ; ; Argument[0]; Argument[this]; taint; manual | nodes | SanitizationTests2.java:14:59:14:91 | path : String | semmle.label | path : String | | SanitizationTests2.java:16:59:16:62 | path | semmle.label | path | @@ -322,6 +334,16 @@ nodes | TaintedPath.java:14:27:14:40 | filenameReader : BufferedReader | semmle.label | filenameReader : BufferedReader | | TaintedPath.java:14:27:14:51 | readLine(...) : String | semmle.label | readLine(...) : String | | TaintedPath.java:16:71:16:78 | filename | semmle.label | filename | +| TaintedPath.java:98:17:98:89 | new BufferedReader(...) : BufferedReader | semmle.label | new BufferedReader(...) : BufferedReader | +| TaintedPath.java:98:36:98:88 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader | +| TaintedPath.java:98:58:98:78 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream | +| TaintedPath.java:99:27:99:40 | filenameReader : BufferedReader | semmle.label | filenameReader : BufferedReader | +| TaintedPath.java:99:27:99:51 | readLine(...) : String | semmle.label | readLine(...) : String | +| TaintedPath.java:100:21:100:38 | new File(...) : File | semmle.label | new File(...) : File | +| TaintedPath.java:100:30:100:37 | filename : String | semmle.label | filename : String | +| TaintedPath.java:101:27:101:30 | file : File | semmle.label | file : File | +| TaintedPath.java:101:27:101:40 | getName(...) : String | semmle.label | getName(...) : String | +| TaintedPath.java:105:71:105:78 | baseName | semmle.label | baseName | | Test.java:32:16:32:45 | getParameter(...) : String | semmle.label | getParameter(...) : String | | Test.java:37:52:37:68 | (...)... | semmle.label | (...)... | | Test.java:37:61:37:68 | source(...) : String | semmle.label | source(...) : String | diff --git a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java index fffb93c62916..fc01e1d23568 100644 --- a/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java +++ b/java/ql/test/query-tests/security/CWE-022/semmle/tests/TaintedPath.java @@ -93,18 +93,38 @@ public void sendUserFileGood5(Socket sock, String user) throws Exception { } } - public void sendUserFileGood4(Socket sock, String user) throws IOException { + public void sendUserFileBad2(Socket sock, String user) throws IOException { BufferedReader filenameReader = - new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8")); + new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8")); // $ Source[java/path-injection] String filename = filenameReader.readLine(); File file = new File(filename); String baseName = file.getName(); - // GOOD: only use the final component of the user provided path - BufferedReader fileReader = new BufferedReader(new FileReader(baseName)); + // BAD: `getName()` strips directory separators but does not remove a `..` + // component (`new File("..").getName()` returns ".."), so it is not a + // complete path injection sanitizer. + BufferedReader fileReader = new BufferedReader(new FileReader(baseName)); // $ Alert[java/path-injection] String fileLine = fileReader.readLine(); while (fileLine != null) { sock.getOutputStream().write(fileLine.getBytes()); fileLine = fileReader.readLine(); } } + + public void sendUserFileGood4(Socket sock, String user) throws IOException { + BufferedReader filenameReader = + new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8")); + String filename = filenameReader.readLine(); + File file = new File(filename); + String baseName = file.getName(); + // GOOD: `getName()` strips directory separators and the `..` check removes + // the only remaining traversal possibility. + if (!baseName.contains("..")) { + BufferedReader fileReader = new BufferedReader(new FileReader(baseName)); + String fileLine = fileReader.readLine(); + while (fileLine != null) { + sock.getOutputStream().write(fileLine.getBytes()); + fileLine = fileReader.readLine(); + } + } + } } From 63ff22abd6cad01854c0c087456489c82acb0dd5 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Mon, 13 Jul 2026 00:08:51 +0200 Subject: [PATCH 28/60] Go: Remove dead `ObjectsOverride` --- go/extractor/extractor.go | 31 ------------------------------- go/extractor/trap/trapwriter.go | 20 +++++++++----------- 2 files changed, 9 insertions(+), 42 deletions(-) diff --git a/go/extractor/extractor.go b/go/extractor/extractor.go index 158f0029704d..f4188cfb6816 100644 --- a/go/extractor/extractor.go +++ b/go/extractor/extractor.go @@ -1644,7 +1644,6 @@ func extractType(tw *trap.Writer, tp types.Type) trap.Label { dbscheme.TypeNameTable.Emit(tw, lbl, origintp.Obj().Name()) underlying := origintp.Underlying() extractUnderlyingType(tw, lbl, underlying) - trackInstantiatedStructFields(tw, tp, origintp) entitylbl, exists := tw.Labeler.LookupObjectID(origintp.Obj(), lbl) if entitylbl == trap.InvalidLabel { @@ -2035,36 +2034,6 @@ func getObjectBeingUsed(tw *trap.Writer, ident *ast.Ident) types.Object { } } -// trackInstantiatedStructFields tries to give the fields of an instantiated -// struct type underlying `tp` the same labels as the corresponding fields of -// the generic struct type. This is so that when we come across the -// instantiated field in `tw.Package.TypesInfo.Uses` we will get the label for -// the generic field instead. -func trackInstantiatedStructFields(tw *trap.Writer, tp, origintp *types.Named) { - if tp == origintp { - return - } - - if instantiatedStruct, ok := tp.Underlying().(*types.Struct); ok { - genericStruct, ok2 := origintp.Underlying().(*types.Struct) - if !ok2 { - log.Fatalf( - "Error: underlying type of instantiated type is a struct but underlying type of generic type is %s", - origintp.Underlying()) - } - - if instantiatedStruct.NumFields() != genericStruct.NumFields() { - log.Fatalf( - "Error: instantiated struct %s has different number of fields than the generic version %s (%d != %d)", - instantiatedStruct, genericStruct, instantiatedStruct.NumFields(), genericStruct.NumFields()) - } - - for i := 0; i < instantiatedStruct.NumFields(); i++ { - tw.ObjectsOverride[instantiatedStruct.Field(i)] = genericStruct.Field(i) - } - } -} - func getTypeParamParentLabel(tw *trap.Writer, tp *types.TypeParam) trap.Label { parent, exists := typeParamParent[tp] if !exists { diff --git a/go/extractor/trap/trapwriter.go b/go/extractor/trap/trapwriter.go index 50763719f50c..c565f343665b 100644 --- a/go/extractor/trap/trapwriter.go +++ b/go/extractor/trap/trapwriter.go @@ -17,16 +17,15 @@ import ( // A Writer provides methods for writing data to a TRAP file type Writer struct { - zip *gzip.Writer - wzip *bufio.Writer - wfile *bufio.Writer - file *os.File - Labeler *Labeler - path string - trapFilePath string - Package *packages.Package - TypesOverride map[ast.Expr]types.Type - ObjectsOverride map[types.Object]types.Object + zip *gzip.Writer + wzip *bufio.Writer + wfile *bufio.Writer + file *os.File + Labeler *Labeler + path string + trapFilePath string + Package *packages.Package + TypesOverride map[ast.Expr]types.Type } func FileFor(path string) (string, error) { @@ -67,7 +66,6 @@ func NewWriter(path string, pkg *packages.Package) (*Writer, error) { trapFilePath, pkg, make(map[ast.Expr]types.Type), - make(map[types.Object]types.Object), } tw.Labeler = newLabeler(tw) return tw, nil From 559f9f38500dbaff9ba091e1af725e2b14592450 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Mon, 13 Jul 2026 00:38:58 +0200 Subject: [PATCH 29/60] Go: Remove dead `isAlias` --- go/extractor/extractor.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/go/extractor/extractor.go b/go/extractor/extractor.go index f4188cfb6816..0e7fd199e7ce 100644 --- a/go/extractor/extractor.go +++ b/go/extractor/extractor.go @@ -1529,12 +1529,6 @@ func extractSpec(tw *trap.Writer, spec ast.Spec, parent trap.Label, idx int) { extractNodeLocation(tw, spec, lbl) } -// Determines whether the given type is an alias. -func isAlias(tp types.Type) bool { - _, ok := tp.(*types.Alias) - return ok -} - // extractType extracts type information for `tp` and returns its associated label; // types are only extracted once, so the second time `extractType` is invoked it simply returns the label func extractType(tw *trap.Writer, tp types.Type) trap.Label { From 0584ca09dca8dfde7f7fd309be033f95b51a5429 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2026 04:12:46 +0000 Subject: [PATCH 30/60] Go: Update to 1.26.5 --- MODULE.bazel | 2 +- go/actions/test/action.yml | 2 +- go/extractor/go.mod | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index e6f1ddb893dd..9f9a2125da92 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -276,7 +276,7 @@ use_repo( ) go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") -go_sdk.download(version = "1.26.4") +go_sdk.download(version = "1.26.5") go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") go_deps.from_file(go_mod = "//go/extractor:go.mod") diff --git a/go/actions/test/action.yml b/go/actions/test/action.yml index 3cc3334d39ed..d4c391cd0103 100644 --- a/go/actions/test/action.yml +++ b/go/actions/test/action.yml @@ -4,7 +4,7 @@ inputs: go-test-version: description: Which Go version to use for running the tests required: false - default: "~1.26.4" + default: "~1.26.5" run-code-checks: description: Whether to run formatting, code and qhelp generation checks required: false diff --git a/go/extractor/go.mod b/go/extractor/go.mod index e121c4a98a8a..b45815abbc95 100644 --- a/go/extractor/go.mod +++ b/go/extractor/go.mod @@ -2,7 +2,7 @@ module github.com/github/codeql-go/extractor go 1.26 -toolchain go1.26.4 +toolchain go1.26.5 // when updating this, run // bazel run @rules_go//go -- mod tidy From 032636cf347366b163fc3a8b4c60ffb5ba65fea3 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Sun, 12 Jul 2026 21:22:53 +0200 Subject: [PATCH 31/60] Go: Track whether a type parameter was defined as part of a receiver --- go/extractor/dbscheme/dbscheme.go | 5 +++ go/extractor/dbscheme/tables.go | 3 +- go/extractor/extractor.go | 59 +++++++++++++++++++------------ go/extractor/trap/trapwriter.go | 2 ++ go/ql/lib/go.dbscheme | 6 ++-- go/ql/lib/go.dbscheme.stats | 4 +++ go/ql/lib/semmle/go/Types.qll | 8 ++--- 7 files changed, 56 insertions(+), 31 deletions(-) diff --git a/go/extractor/dbscheme/dbscheme.go b/go/extractor/dbscheme/dbscheme.go index 550c7920c711..45aea39b4e54 100644 --- a/go/extractor/dbscheme/dbscheme.go +++ b/go/extractor/dbscheme/dbscheme.go @@ -277,6 +277,11 @@ func FloatColumn(columnName string) Column { return Column{columnName, FLOAT, false, true} } +// BooleanColumn constructs a column with name `columnName` holding boolean values +func BooleanColumn(columnName string) Column { + return Column{columnName, BOOLEAN, false, true} +} + // A Table represents a database table type Table struct { name string diff --git a/go/extractor/dbscheme/tables.go b/go/extractor/dbscheme/tables.go index 9c537fbaf89f..b72c33795182 100644 --- a/go/extractor/dbscheme/tables.go +++ b/go/extractor/dbscheme/tables.go @@ -1241,4 +1241,5 @@ var TypeParamTable = NewTable("typeparam", EntityColumn(CompositeType, "bound"), EntityColumn(TypeParamParentObjectType, "parent"), IntColumn("idx"), -).KeySet("parent", "idx") + BooleanColumn("is_from_recv"), +).KeySet("parent", "idx", "is_from_recv") diff --git a/go/extractor/extractor.go b/go/extractor/extractor.go index 0e7fd199e7ce..f421688e52ad 100644 --- a/go/extractor/extractor.go +++ b/go/extractor/extractor.go @@ -32,7 +32,13 @@ import ( ) var MaxGoRoutines int -var typeParamParent map[*types.TypeParam]types.Object = make(map[*types.TypeParam]types.Object) + +type typeParamParentEntry struct { + parent types.Object + isFromReceiver bool +} + +var typeParamParent map[*types.TypeParam]typeParamParentEntry = make(map[*types.TypeParam]typeParamParentEntry) func init() { // this sets the number of threads that the Go runtime will spawn; this is separate @@ -530,8 +536,7 @@ func extractObjects(tw *trap.Writer, scope *types.Scope, scopeLabel trap.Label) // do not appear as objects in any scope, so they have to be dealt // with separately in extractMethods. if funcObj, ok := obj.(*types.Func); ok { - populateTypeParamParents(funcObj.Type().(*types.Signature).TypeParams(), obj) - populateTypeParamParents(funcObj.Type().(*types.Signature).RecvTypeParams(), obj) + populateTypeParamParentsFromFunction(funcObj) } // Populate type parameter parents for defined types and alias types. if typeNameObj, ok := obj.(*types.TypeName); ok { @@ -542,9 +547,9 @@ func extractObjects(tw *trap.Writer, scope *types.Scope, scopeLabel trap.Label) // careful with alias types because before Go 1.24 they would // return the underlying type. if tp, ok := typeNameObj.Type().(*types.Named); ok && !typeNameObj.IsAlias() { - populateTypeParamParents(tp.TypeParams(), obj) + populateTypeParamParents(tp.TypeParams(), obj, false) } else if tp, ok := typeNameObj.Type().(*types.Alias); ok { - populateTypeParamParents(tp.TypeParams(), obj) + populateTypeParamParents(tp.TypeParams(), obj, false) } } extractObject(tw, obj, lbl) @@ -570,8 +575,7 @@ func extractMethod(tw *trap.Writer, meth *types.Func) trap.Label { if !exists { // Populate type parameter parents for methods. They do not appear as // objects in any scope, so they have to be dealt with separately here. - populateTypeParamParents(meth.Type().(*types.Signature).TypeParams(), meth) - populateTypeParamParents(meth.Type().(*types.Signature).RecvTypeParams(), meth) + populateTypeParamParentsFromFunction(meth) extractObject(tw, meth, methlbl) } @@ -1677,9 +1681,9 @@ func extractType(tw *trap.Writer, tp types.Type) trap.Label { } case *types.TypeParam: kind = dbscheme.TypeParamType.Index() - parentlbl := getTypeParamParentLabel(tw, tp) + parentlbl, isReceiverChild := getTypeParamParentLabel(tw, tp) constraintLabel := extractType(tw, tp.Constraint()) - dbscheme.TypeParamTable.Emit(tw, lbl, tp.Obj().Name(), constraintLabel, parentlbl, tp.Index()) + dbscheme.TypeParamTable.Emit(tw, lbl, tp.Obj().Name(), constraintLabel, parentlbl, tp.Index(), isReceiverChild) case *types.Union: kind = dbscheme.TypeSetLiteral.Index() for i := 0; i < tp.Len(); i++ { @@ -1819,9 +1823,9 @@ func getTypeLabel(tw *trap.Writer, tp types.Type) (trap.Label, bool) { } lbl = tw.Labeler.GlobalID(fmt.Sprintf("{%s};definedtype", entitylbl)) case *types.TypeParam: - parentlbl := getTypeParamParentLabel(tw, tp) + parentlbl, isReceiverChild := getTypeParamParentLabel(tw, tp) idx := tp.Index() - lbl = tw.Labeler.GlobalID(fmt.Sprintf("{%v},%d,%s;typeparamtype", parentlbl, idx, tp.Obj().Name())) + lbl = tw.Labeler.GlobalID(fmt.Sprintf("{%v},%d,%t,%s;typeparamtype", parentlbl, idx, isReceiverChild, tp.Obj().Name())) case *types.Union: var b strings.Builder for i := 0; i < tp.Len(); i++ { @@ -2005,11 +2009,20 @@ func extractTypeParamDecls(tw *trap.Writer, fields *ast.FieldList, parent trap.L } } +func populateTypeParamParentsFromFunction(funcObj *types.Func) { + recvTypeParams := funcObj.Type().(*types.Signature).RecvTypeParams() + populateTypeParamParents(recvTypeParams, funcObj, true) + typeParams := funcObj.Type().(*types.Signature).TypeParams() + populateTypeParamParents(typeParams, funcObj, false) + +} + // populateTypeParamParents sets `parent` as the parent of the elements of `typeparams` -func populateTypeParamParents(typeparams *types.TypeParamList, parent types.Object) { +// and records whether elements are defined in a receiver. +func populateTypeParamParents(typeparams *types.TypeParamList, parent types.Object, isFromReceiver bool) { if typeparams != nil { - for idx := 0; idx < typeparams.Len(); idx++ { - setTypeParamParent(typeparams.At(idx), parent) + for tparam := range typeparams.TypeParams() { + setTypeParamParent(tparam, parent, isFromReceiver) } } } @@ -2028,24 +2041,24 @@ func getObjectBeingUsed(tw *trap.Writer, ident *ast.Ident) types.Object { } } -func getTypeParamParentLabel(tw *trap.Writer, tp *types.TypeParam) trap.Label { - parent, exists := typeParamParent[tp] +func getTypeParamParentLabel(tw *trap.Writer, tp *types.TypeParam) (trap.Label, bool) { + entry, exists := typeParamParent[tp] if !exists { log.Fatalf("Parent of type parameter does not exist: %s %s", tp.String(), tp.Constraint().String()) } - parentlbl, _ := tw.Labeler.ScopedObjectID(parent, func() trap.Label { + parentlbl, _ := tw.Labeler.ScopedObjectID(entry.parent, func() trap.Label { log.Fatalf("getTypeLabel() called for parent of type parameter %s", tp.String()) return trap.InvalidLabel }) - return parentlbl + return parentlbl, entry.isFromReceiver } -func setTypeParamParent(tp *types.TypeParam, newobj types.Object) { - obj, exists := typeParamParent[tp] +func setTypeParamParent(tp *types.TypeParam, newobj types.Object, isFromReceiver bool) { + entry, exists := typeParamParent[tp] if !exists { - typeParamParent[tp] = newobj - } else if newobj != obj { - log.Fatalf("Parent of type parameter '%s %s' being set to a different value: '%s' vs '%s'", tp.String(), tp.Constraint().String(), obj, newobj) + typeParamParent[tp] = typeParamParentEntry{newobj, isFromReceiver} + } else if entry.parent != newobj { + log.Fatalf("Parent of type parameter '%s %s' being set to a different value: '%s' vs '%s'", tp.String(), tp.Constraint().String(), entry.parent, newobj) } } diff --git a/go/extractor/trap/trapwriter.go b/go/extractor/trap/trapwriter.go index c565f343665b..fa7436be48bf 100644 --- a/go/extractor/trap/trapwriter.go +++ b/go/extractor/trap/trapwriter.go @@ -165,6 +165,8 @@ func (tw *Writer) Emit(table string, values []interface{}) error { fmt.Fprintf(tw.wzip, "%d", value) case float64: fmt.Fprintf(tw.wzip, "%e", value) + case bool: + fmt.Fprintf(tw.wzip, "%t", value) default: return errors.New("Cannot emit value") } diff --git a/go/ql/lib/go.dbscheme b/go/ql/lib/go.dbscheme index b1341734d687..5ff5325d274a 100644 --- a/go/ql/lib/go.dbscheme +++ b/go/ql/lib/go.dbscheme @@ -244,9 +244,9 @@ has_ellipsis(int id: @callorconversionexpr ref); variadic(int id: @signaturetype ref); -#keyset[parent, idx] -typeparam(unique int tp: @typeparamtype ref, string name: string ref, int bound: @compositetype ref, - int parent: @typeparamparentobject ref, int idx: int ref); +#keyset[parent, idx, is_from_recv] +typeparam(unique int tp: @typeparamtype ref, string name: string ref, + int bound: @compositetype ref, int parent: @typeparamparentobject ref, int idx: int ref, boolean is_from_recv: boolean ref); @container = @file | @folder; diff --git a/go/ql/lib/go.dbscheme.stats b/go/ql/lib/go.dbscheme.stats index 126bbff00f7f..dd9440b09982 100644 --- a/go/ql/lib/go.dbscheme.stats +++ b/go/ql/lib/go.dbscheme.stats @@ -17791,6 +17791,10 @@ idx 3126 + + is_from_recv + 0 + diff --git a/go/ql/lib/semmle/go/Types.qll b/go/ql/lib/semmle/go/Types.qll index 574d75685439..217ac5d211c8 100644 --- a/go/ql/lib/semmle/go/Types.qll +++ b/go/ql/lib/semmle/go/Types.qll @@ -382,18 +382,18 @@ class CompositeType extends @compositetype, Type { } /** A type that comes from a type parameter. */ class TypeParamType extends @typeparamtype, CompositeType { /** Gets the name of this type parameter type. */ - string getParamName() { typeparam(this, result, _, _, _) } + string getParamName() { typeparam(this, result, _, _, _, _) } /** Gets the constraint of this type parameter type. */ - Type getConstraint() { typeparam(this, _, result, _, _) } + Type getConstraint() { typeparam(this, _, result, _, _, _) } override InterfaceType getUnderlyingType() { result = this.getConstraint().getUnderlyingType() } /** Gets the parent object of this type parameter type. */ - TypeParamParentEntity getParent() { typeparam(this, _, _, result, _) } + TypeParamParentEntity getParent() { typeparam(this, _, _, result, _, _) } /** Gets the index of this type parameter type. */ - int getIndex() { typeparam(this, _, _, _, result) } + int getIndex() { typeparam(this, _, _, _, result, _) } override string pp() { result = this.getParamName() } From 37454ab5f647692fee2c6dde530d64c464b7a8df Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Mon, 13 Jul 2026 11:53:26 +0200 Subject: [PATCH 32/60] Go: Add upgrade and downgrade scripts --- .../go.dbscheme | 563 ++++++++++++++++++ .../old.dbscheme | 563 ++++++++++++++++++ .../typeparam.ql | 15 + .../upgrade.properties | 3 + .../go.dbscheme | 563 ++++++++++++++++++ .../old.dbscheme | 563 ++++++++++++++++++ .../typeparam.ql | 15 + .../upgrade.properties | 3 + 8 files changed, 2288 insertions(+) create mode 100644 go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/go.dbscheme create mode 100644 go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/old.dbscheme create mode 100644 go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/typeparam.ql create mode 100644 go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/upgrade.properties create mode 100644 go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/go.dbscheme create mode 100644 go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/old.dbscheme create mode 100644 go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/typeparam.ql create mode 100644 go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/upgrade.properties diff --git a/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/go.dbscheme b/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/go.dbscheme new file mode 100644 index 000000000000..b1341734d687 --- /dev/null +++ b/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/go.dbscheme @@ -0,0 +1,563 @@ +/** Auto-generated dbscheme; do not edit. Run `make gen` in directory `go/` to regenerate. */ + + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +/** Overlay support **/ + +databaseMetadata( + string metadataKey: string ref, + string value: string ref +); + +overlayChangedFiles( + string path: string ref +); + + +/* + * XML Files + */ + +xmlEncoding( + unique int id: @file ref, + string encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +compilations(unique int id: @compilation, string cwd: string ref); + +#keyset[id, num] +compilation_args(int id: @compilation ref, int num: int ref, string arg: string ref); + +#keyset[id, num, kind] +compilation_time(int id: @compilation ref, int num: int ref, int kind: int ref, float secs: float ref); + +diagnostic_for(unique int diagnostic: @diagnostic ref, int compilation: @compilation ref, int file_number: int ref, int file_number_diagnostic_number: int ref); + +compilation_finished(unique int id: @compilation ref, float cpu_seconds: float ref, float elapsed_seconds: float ref); + +#keyset[id, num] +compilation_compiling_files(int id: @compilation ref, int num: int ref, int file: @file ref); + +diagnostics(unique int id: @diagnostic, int severity: int ref, string error_tag: string ref, string error_message: string ref, + string full_error_message: string ref, int location: @location ref); + +locations_default(unique int id: @location_default, int file: @file ref, int beginLine: int ref, int beginColumn: int ref, + int endLine: int ref, int endColumn: int ref); + +numlines(int element_id: @sourceline ref, int num_lines: int ref, int num_code: int ref, int num_comment: int ref); + +files(unique int id: @file, string name: string ref); + +folders(unique int id: @folder, string name: string ref); + +containerparent(int parent: @container ref, unique int child: @container ref); + +has_location(unique int locatable: @locatable ref, int location: @location ref); + +#keyset[parent, idx] +comment_groups(unique int id: @comment_group, int parent: @file ref, int idx: int ref); + +comments(unique int id: @comment, int kind: int ref, int parent: @comment_group ref, int idx: int ref, string text: string ref); + +doc_comments(unique int node: @documentable ref, int comment: @comment_group ref); + +#keyset[parent, idx] +exprs(unique int id: @expr, int kind: int ref, int parent: @exprparent ref, int idx: int ref); + +literals(unique int expr: @expr ref, string value: string ref, string raw: string ref); + +constvalues(unique int expr: @expr ref, string value: string ref, string exact: string ref); + +fields(unique int id: @field, int parent: @fieldparent ref, int idx: int ref); + +typeparamdecls(unique int id: @typeparamdecl, int parent: @typeparamdeclparent ref, int idx: int ref); + +#keyset[parent, idx] +stmts(unique int id: @stmt, int kind: int ref, int parent: @stmtparent ref, int idx: int ref); + +#keyset[parent, idx] +decls(unique int id: @decl, int kind: int ref, int parent: @declparent ref, int idx: int ref); + +#keyset[parent, idx] +specs(unique int id: @spec, int kind: int ref, int parent: @gendecl ref, int idx: int ref); + +scopes(unique int id: @scope, int kind: int ref); + +scopenesting(unique int inner: @scope ref, int outer: @scope ref); + +scopenodes(unique int node: @scopenode ref, int scope: @localscope ref); + +objects(unique int id: @object, int kind: int ref, string name: string ref); + +objectscopes(unique int object: @object ref, int scope: @scope ref); + +objecttypes(unique int object: @object ref, int tp: @type ref); + +methodreceivers(unique int method: @object ref, int receiver: @object ref); + +fieldstructs(unique int field: @object ref, int struct: @structtype ref); + +methodhosts(int method: @object ref, int host: @definedtype ref); + +defs(int ident: @ident ref, int object: @object ref); + +uses(int ident: @ident ref, int object: @object ref); + +types(unique int id: @type, int kind: int ref); + +type_of(unique int expr: @expr ref, int tp: @type ref); + +typename(unique int tp: @type ref, string name: string ref); + +key_type(unique int map: @maptype ref, int tp: @type ref); + +element_type(unique int container: @containertype ref, int tp: @type ref); + +base_type(unique int ptr: @pointertype ref, int tp: @type ref); + +underlying_type(unique int defined: @definedtype ref, int tp: @type ref); + +#keyset[parent, index] +component_types(int parent: @compositetype ref, int index: int ref, string name: string ref, int tp: @type ref); + +#keyset[parent, index] +struct_tags(int parent: @structtype ref, int index: int ref, string tag: string ref); + +#keyset[interface, index] +interface_private_method_ids(int interface: @interfacetype ref, int index: int ref, string id: string ref); + +array_length(unique int tp: @arraytype ref, string len: string ref); + +type_objects(unique int tp: @type ref, int object: @object ref); + +packages(unique int id: @package, string name: string ref, string path: string ref, int scope: @packagescope ref); + +#keyset[parent, idx] +modexprs(unique int id: @modexpr, int kind: int ref, int parent: @modexprparent ref, int idx: int ref); + +#keyset[parent, idx] +modtokens(string token: string ref, int parent: @modexpr ref, int idx: int ref); + +#keyset[package, idx] +errors(unique int id: @error, int kind: int ref, string msg: string ref, string rawpos: string ref, + string file: string ref, int line: int ref, int col: int ref, int package: @package ref, int idx: int ref); + +has_ellipsis(int id: @callorconversionexpr ref); + +variadic(int id: @signaturetype ref); + +#keyset[parent, idx] +typeparam(unique int tp: @typeparamtype ref, string name: string ref, int bound: @compositetype ref, + int parent: @typeparamparentobject ref, int idx: int ref); + +@container = @file | @folder; + +@locatable = @xmllocatable | @node | @localscope; + +@node = @documentable | @exprparent | @modexprparent | @fieldparent | @stmtparent | @declparent | @typeparamdeclparent + | @scopenode | @comment_group | @comment; + +@documentable = @file | @field | @typeparamdecl | @spec | @gendecl | @funcdecl | @modexpr; + +@exprparent = @funcdef | @file | @expr | @field | @stmt | @decl | @typeparamdecl | @spec; + +@modexprparent = @file | @modexpr; + +@fieldparent = @decl | @structtypeexpr | @functypeexpr | @interfacetypeexpr; + +@stmtparent = @funcdef | @stmt | @decl; + +@declparent = @file | @declstmt; + +@typeparamdeclparent = @funcdecl | @typespec; + +@funcdef = @funclit | @funcdecl; + +@scopenode = @file | @functypeexpr | @blockstmt | @ifstmt | @caseclause | @switchstmt | @commclause | @loopstmt; + +@location = @location_default; + +@sourceline = @locatable; + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment; + +case @expr.kind of + 0 = @badexpr +| 1 = @ident +| 2 = @ellipsis +| 3 = @intlit +| 4 = @floatlit +| 5 = @imaglit +| 6 = @charlit +| 7 = @stringlit +| 8 = @funclit +| 9 = @compositelit +| 10 = @parenexpr +| 11 = @selectorexpr +| 12 = @indexexpr +| 13 = @genericfunctioninstantiationexpr +| 14 = @generictypeinstantiationexpr +| 15 = @sliceexpr +| 16 = @typeassertexpr +| 17 = @callorconversionexpr +| 18 = @starexpr +| 19 = @keyvalueexpr +| 20 = @arraytypeexpr +| 21 = @structtypeexpr +| 22 = @functypeexpr +| 23 = @interfacetypeexpr +| 24 = @maptypeexpr +| 25 = @typesetliteralexpr +| 26 = @plusexpr +| 27 = @minusexpr +| 28 = @notexpr +| 29 = @complementexpr +| 30 = @derefexpr +| 31 = @addressexpr +| 32 = @arrowexpr +| 33 = @lorexpr +| 34 = @landexpr +| 35 = @eqlexpr +| 36 = @neqexpr +| 37 = @lssexpr +| 38 = @leqexpr +| 39 = @gtrexpr +| 40 = @geqexpr +| 41 = @addexpr +| 42 = @subexpr +| 43 = @orexpr +| 44 = @xorexpr +| 45 = @mulexpr +| 46 = @quoexpr +| 47 = @remexpr +| 48 = @shlexpr +| 49 = @shrexpr +| 50 = @andexpr +| 51 = @andnotexpr +| 52 = @sendchantypeexpr +| 53 = @recvchantypeexpr +| 54 = @sendrcvchantypeexpr; + +@basiclit = @intlit | @floatlit | @imaglit | @charlit | @stringlit; + +@operatorexpr = @logicalexpr | @arithmeticexpr | @bitwiseexpr | @unaryexpr | @binaryexpr; + +@logicalexpr = @logicalunaryexpr | @logicalbinaryexpr; + +@arithmeticexpr = @arithmeticunaryexpr | @arithmeticbinaryexpr; + +@bitwiseexpr = @bitwiseunaryexpr | @bitwisebinaryexpr; + +@unaryexpr = @logicalunaryexpr | @bitwiseunaryexpr | @arithmeticunaryexpr | @derefexpr | @addressexpr | @arrowexpr; + +@logicalunaryexpr = @notexpr; + +@bitwiseunaryexpr = @complementexpr; + +@arithmeticunaryexpr = @plusexpr | @minusexpr; + +@binaryexpr = @logicalbinaryexpr | @bitwisebinaryexpr | @arithmeticbinaryexpr | @comparison; + +@logicalbinaryexpr = @lorexpr | @landexpr; + +@bitwisebinaryexpr = @shiftexpr | @orexpr | @xorexpr | @andexpr | @andnotexpr; + +@arithmeticbinaryexpr = @addexpr | @subexpr | @mulexpr | @quoexpr | @remexpr; + +@shiftexpr = @shlexpr | @shrexpr; + +@comparison = @equalitytest | @relationalcomparison; + +@equalitytest = @eqlexpr | @neqexpr; + +@relationalcomparison = @lssexpr | @leqexpr | @gtrexpr | @geqexpr; + +@chantypeexpr = @sendchantypeexpr | @recvchantypeexpr | @sendrcvchantypeexpr; + +case @stmt.kind of + 0 = @badstmt +| 1 = @declstmt +| 2 = @emptystmt +| 3 = @labeledstmt +| 4 = @exprstmt +| 5 = @sendstmt +| 6 = @incstmt +| 7 = @decstmt +| 8 = @gostmt +| 9 = @deferstmt +| 10 = @returnstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @gotostmt +| 14 = @fallthroughstmt +| 15 = @blockstmt +| 16 = @ifstmt +| 17 = @caseclause +| 18 = @exprswitchstmt +| 19 = @typeswitchstmt +| 20 = @commclause +| 21 = @selectstmt +| 22 = @forstmt +| 23 = @rangestmt +| 24 = @assignstmt +| 25 = @definestmt +| 26 = @addassignstmt +| 27 = @subassignstmt +| 28 = @mulassignstmt +| 29 = @quoassignstmt +| 30 = @remassignstmt +| 31 = @andassignstmt +| 32 = @orassignstmt +| 33 = @xorassignstmt +| 34 = @shlassignstmt +| 35 = @shrassignstmt +| 36 = @andnotassignstmt; + +@incdecstmt = @incstmt | @decstmt; + +@assignment = @simpleassignstmt | @compoundassignstmt; + +@simpleassignstmt = @assignstmt | @definestmt; + +@compoundassignstmt = @addassignstmt | @subassignstmt | @mulassignstmt | @quoassignstmt | @remassignstmt + | @andassignstmt | @orassignstmt | @xorassignstmt | @shlassignstmt | @shrassignstmt | @andnotassignstmt; + +@branchstmt = @breakstmt | @continuestmt | @gotostmt | @fallthroughstmt; + +@switchstmt = @exprswitchstmt | @typeswitchstmt; + +@loopstmt = @forstmt | @rangestmt; + +case @decl.kind of + 0 = @baddecl +| 1 = @importdecl +| 2 = @constdecl +| 3 = @typedecl +| 4 = @vardecl +| 5 = @funcdecl; + +@gendecl = @importdecl | @constdecl | @typedecl | @vardecl; + +case @spec.kind of + 0 = @importspec +| 1 = @valuespec +| 2 = @typedefspec +| 3 = @aliasspec; + +@typespec = @typedefspec | @aliasspec; + +case @object.kind of + 0 = @pkgobject +| 1 = @decltypeobject +| 2 = @builtintypeobject +| 3 = @declconstobject +| 4 = @builtinconstobject +| 5 = @declvarobject +| 6 = @declfunctionobject +| 7 = @builtinfunctionobject +| 8 = @labelobject; + +@typeparamparentobject = @decltypeobject | @declfunctionobject; + +@declobject = @decltypeobject | @declconstobject | @declvarobject | @declfunctionobject; + +@builtinobject = @builtintypeobject | @builtinconstobject | @builtinfunctionobject; + +@typeobject = @decltypeobject | @builtintypeobject; + +@valueobject = @constobject | @varobject | @functionobject; + +@constobject = @declconstobject | @builtinconstobject; + +@varobject = @declvarobject; + +@functionobject = @declfunctionobject | @builtinfunctionobject; + +case @scope.kind of + 0 = @universescope +| 1 = @packagescope +| 2 = @localscope; + +case @type.kind of + 0 = @invalidtype +| 1 = @boolexprtype +| 2 = @inttype +| 3 = @int8type +| 4 = @int16type +| 5 = @int32type +| 6 = @int64type +| 7 = @uinttype +| 8 = @uint8type +| 9 = @uint16type +| 10 = @uint32type +| 11 = @uint64type +| 12 = @uintptrtype +| 13 = @float32type +| 14 = @float64type +| 15 = @complex64type +| 16 = @complex128type +| 17 = @stringexprtype +| 18 = @unsafepointertype +| 19 = @boolliteraltype +| 20 = @intliteraltype +| 21 = @runeliteraltype +| 22 = @floatliteraltype +| 23 = @complexliteraltype +| 24 = @stringliteraltype +| 25 = @nilliteraltype +| 26 = @typeparamtype +| 27 = @arraytype +| 28 = @slicetype +| 29 = @structtype +| 30 = @pointertype +| 31 = @interfacetype +| 32 = @tupletype +| 33 = @signaturetype +| 34 = @maptype +| 35 = @sendchantype +| 36 = @recvchantype +| 37 = @sendrcvchantype +| 38 = @definedtype +| 39 = @typesetliteraltype; + +@basictype = @booltype | @numerictype | @stringtype | @literaltype | @invalidtype | @unsafepointertype; + +@booltype = @boolexprtype | @boolliteraltype; + +@numerictype = @integertype | @floattype | @complextype; + +@integertype = @signedintegertype | @unsignedintegertype; + +@signedintegertype = @inttype | @int8type | @int16type | @int32type | @int64type | @intliteraltype | @runeliteraltype; + +@unsignedintegertype = @uinttype | @uint8type | @uint16type | @uint32type | @uint64type | @uintptrtype; + +@floattype = @float32type | @float64type | @floatliteraltype; + +@complextype = @complex64type | @complex128type | @complexliteraltype; + +@stringtype = @stringexprtype | @stringliteraltype; + +@literaltype = @boolliteraltype | @intliteraltype | @runeliteraltype | @floatliteraltype | @complexliteraltype + | @stringliteraltype | @nilliteraltype; + +@compositetype = @typeparamtype | @containertype | @structtype | @pointertype | @interfacetype | @tupletype + | @signaturetype | @definedtype | @typesetliteraltype; + +@containertype = @arraytype | @slicetype | @maptype | @chantype; + +@chantype = @sendchantype | @recvchantype | @sendrcvchantype; + +case @modexpr.kind of + 0 = @modcommentblock +| 1 = @modline +| 2 = @modlineblock +| 3 = @modlparen +| 4 = @modrparen; + +case @error.kind of + 0 = @unknownerror +| 1 = @listerror +| 2 = @parseerror +| 3 = @typeerror; + diff --git a/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/old.dbscheme b/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/old.dbscheme new file mode 100644 index 000000000000..5ff5325d274a --- /dev/null +++ b/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/old.dbscheme @@ -0,0 +1,563 @@ +/** Auto-generated dbscheme; do not edit. Run `make gen` in directory `go/` to regenerate. */ + + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +/** Overlay support **/ + +databaseMetadata( + string metadataKey: string ref, + string value: string ref +); + +overlayChangedFiles( + string path: string ref +); + + +/* + * XML Files + */ + +xmlEncoding( + unique int id: @file ref, + string encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +compilations(unique int id: @compilation, string cwd: string ref); + +#keyset[id, num] +compilation_args(int id: @compilation ref, int num: int ref, string arg: string ref); + +#keyset[id, num, kind] +compilation_time(int id: @compilation ref, int num: int ref, int kind: int ref, float secs: float ref); + +diagnostic_for(unique int diagnostic: @diagnostic ref, int compilation: @compilation ref, int file_number: int ref, int file_number_diagnostic_number: int ref); + +compilation_finished(unique int id: @compilation ref, float cpu_seconds: float ref, float elapsed_seconds: float ref); + +#keyset[id, num] +compilation_compiling_files(int id: @compilation ref, int num: int ref, int file: @file ref); + +diagnostics(unique int id: @diagnostic, int severity: int ref, string error_tag: string ref, string error_message: string ref, + string full_error_message: string ref, int location: @location ref); + +locations_default(unique int id: @location_default, int file: @file ref, int beginLine: int ref, int beginColumn: int ref, + int endLine: int ref, int endColumn: int ref); + +numlines(int element_id: @sourceline ref, int num_lines: int ref, int num_code: int ref, int num_comment: int ref); + +files(unique int id: @file, string name: string ref); + +folders(unique int id: @folder, string name: string ref); + +containerparent(int parent: @container ref, unique int child: @container ref); + +has_location(unique int locatable: @locatable ref, int location: @location ref); + +#keyset[parent, idx] +comment_groups(unique int id: @comment_group, int parent: @file ref, int idx: int ref); + +comments(unique int id: @comment, int kind: int ref, int parent: @comment_group ref, int idx: int ref, string text: string ref); + +doc_comments(unique int node: @documentable ref, int comment: @comment_group ref); + +#keyset[parent, idx] +exprs(unique int id: @expr, int kind: int ref, int parent: @exprparent ref, int idx: int ref); + +literals(unique int expr: @expr ref, string value: string ref, string raw: string ref); + +constvalues(unique int expr: @expr ref, string value: string ref, string exact: string ref); + +fields(unique int id: @field, int parent: @fieldparent ref, int idx: int ref); + +typeparamdecls(unique int id: @typeparamdecl, int parent: @typeparamdeclparent ref, int idx: int ref); + +#keyset[parent, idx] +stmts(unique int id: @stmt, int kind: int ref, int parent: @stmtparent ref, int idx: int ref); + +#keyset[parent, idx] +decls(unique int id: @decl, int kind: int ref, int parent: @declparent ref, int idx: int ref); + +#keyset[parent, idx] +specs(unique int id: @spec, int kind: int ref, int parent: @gendecl ref, int idx: int ref); + +scopes(unique int id: @scope, int kind: int ref); + +scopenesting(unique int inner: @scope ref, int outer: @scope ref); + +scopenodes(unique int node: @scopenode ref, int scope: @localscope ref); + +objects(unique int id: @object, int kind: int ref, string name: string ref); + +objectscopes(unique int object: @object ref, int scope: @scope ref); + +objecttypes(unique int object: @object ref, int tp: @type ref); + +methodreceivers(unique int method: @object ref, int receiver: @object ref); + +fieldstructs(unique int field: @object ref, int struct: @structtype ref); + +methodhosts(int method: @object ref, int host: @definedtype ref); + +defs(int ident: @ident ref, int object: @object ref); + +uses(int ident: @ident ref, int object: @object ref); + +types(unique int id: @type, int kind: int ref); + +type_of(unique int expr: @expr ref, int tp: @type ref); + +typename(unique int tp: @type ref, string name: string ref); + +key_type(unique int map: @maptype ref, int tp: @type ref); + +element_type(unique int container: @containertype ref, int tp: @type ref); + +base_type(unique int ptr: @pointertype ref, int tp: @type ref); + +underlying_type(unique int defined: @definedtype ref, int tp: @type ref); + +#keyset[parent, index] +component_types(int parent: @compositetype ref, int index: int ref, string name: string ref, int tp: @type ref); + +#keyset[parent, index] +struct_tags(int parent: @structtype ref, int index: int ref, string tag: string ref); + +#keyset[interface, index] +interface_private_method_ids(int interface: @interfacetype ref, int index: int ref, string id: string ref); + +array_length(unique int tp: @arraytype ref, string len: string ref); + +type_objects(unique int tp: @type ref, int object: @object ref); + +packages(unique int id: @package, string name: string ref, string path: string ref, int scope: @packagescope ref); + +#keyset[parent, idx] +modexprs(unique int id: @modexpr, int kind: int ref, int parent: @modexprparent ref, int idx: int ref); + +#keyset[parent, idx] +modtokens(string token: string ref, int parent: @modexpr ref, int idx: int ref); + +#keyset[package, idx] +errors(unique int id: @error, int kind: int ref, string msg: string ref, string rawpos: string ref, + string file: string ref, int line: int ref, int col: int ref, int package: @package ref, int idx: int ref); + +has_ellipsis(int id: @callorconversionexpr ref); + +variadic(int id: @signaturetype ref); + +#keyset[parent, idx, is_from_recv] +typeparam(unique int tp: @typeparamtype ref, string name: string ref, + int bound: @compositetype ref, int parent: @typeparamparentobject ref, int idx: int ref, boolean is_from_recv: boolean ref); + +@container = @file | @folder; + +@locatable = @xmllocatable | @node | @localscope; + +@node = @documentable | @exprparent | @modexprparent | @fieldparent | @stmtparent | @declparent | @typeparamdeclparent + | @scopenode | @comment_group | @comment; + +@documentable = @file | @field | @typeparamdecl | @spec | @gendecl | @funcdecl | @modexpr; + +@exprparent = @funcdef | @file | @expr | @field | @stmt | @decl | @typeparamdecl | @spec; + +@modexprparent = @file | @modexpr; + +@fieldparent = @decl | @structtypeexpr | @functypeexpr | @interfacetypeexpr; + +@stmtparent = @funcdef | @stmt | @decl; + +@declparent = @file | @declstmt; + +@typeparamdeclparent = @funcdecl | @typespec; + +@funcdef = @funclit | @funcdecl; + +@scopenode = @file | @functypeexpr | @blockstmt | @ifstmt | @caseclause | @switchstmt | @commclause | @loopstmt; + +@location = @location_default; + +@sourceline = @locatable; + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment; + +case @expr.kind of + 0 = @badexpr +| 1 = @ident +| 2 = @ellipsis +| 3 = @intlit +| 4 = @floatlit +| 5 = @imaglit +| 6 = @charlit +| 7 = @stringlit +| 8 = @funclit +| 9 = @compositelit +| 10 = @parenexpr +| 11 = @selectorexpr +| 12 = @indexexpr +| 13 = @genericfunctioninstantiationexpr +| 14 = @generictypeinstantiationexpr +| 15 = @sliceexpr +| 16 = @typeassertexpr +| 17 = @callorconversionexpr +| 18 = @starexpr +| 19 = @keyvalueexpr +| 20 = @arraytypeexpr +| 21 = @structtypeexpr +| 22 = @functypeexpr +| 23 = @interfacetypeexpr +| 24 = @maptypeexpr +| 25 = @typesetliteralexpr +| 26 = @plusexpr +| 27 = @minusexpr +| 28 = @notexpr +| 29 = @complementexpr +| 30 = @derefexpr +| 31 = @addressexpr +| 32 = @arrowexpr +| 33 = @lorexpr +| 34 = @landexpr +| 35 = @eqlexpr +| 36 = @neqexpr +| 37 = @lssexpr +| 38 = @leqexpr +| 39 = @gtrexpr +| 40 = @geqexpr +| 41 = @addexpr +| 42 = @subexpr +| 43 = @orexpr +| 44 = @xorexpr +| 45 = @mulexpr +| 46 = @quoexpr +| 47 = @remexpr +| 48 = @shlexpr +| 49 = @shrexpr +| 50 = @andexpr +| 51 = @andnotexpr +| 52 = @sendchantypeexpr +| 53 = @recvchantypeexpr +| 54 = @sendrcvchantypeexpr; + +@basiclit = @intlit | @floatlit | @imaglit | @charlit | @stringlit; + +@operatorexpr = @logicalexpr | @arithmeticexpr | @bitwiseexpr | @unaryexpr | @binaryexpr; + +@logicalexpr = @logicalunaryexpr | @logicalbinaryexpr; + +@arithmeticexpr = @arithmeticunaryexpr | @arithmeticbinaryexpr; + +@bitwiseexpr = @bitwiseunaryexpr | @bitwisebinaryexpr; + +@unaryexpr = @logicalunaryexpr | @bitwiseunaryexpr | @arithmeticunaryexpr | @derefexpr | @addressexpr | @arrowexpr; + +@logicalunaryexpr = @notexpr; + +@bitwiseunaryexpr = @complementexpr; + +@arithmeticunaryexpr = @plusexpr | @minusexpr; + +@binaryexpr = @logicalbinaryexpr | @bitwisebinaryexpr | @arithmeticbinaryexpr | @comparison; + +@logicalbinaryexpr = @lorexpr | @landexpr; + +@bitwisebinaryexpr = @shiftexpr | @orexpr | @xorexpr | @andexpr | @andnotexpr; + +@arithmeticbinaryexpr = @addexpr | @subexpr | @mulexpr | @quoexpr | @remexpr; + +@shiftexpr = @shlexpr | @shrexpr; + +@comparison = @equalitytest | @relationalcomparison; + +@equalitytest = @eqlexpr | @neqexpr; + +@relationalcomparison = @lssexpr | @leqexpr | @gtrexpr | @geqexpr; + +@chantypeexpr = @sendchantypeexpr | @recvchantypeexpr | @sendrcvchantypeexpr; + +case @stmt.kind of + 0 = @badstmt +| 1 = @declstmt +| 2 = @emptystmt +| 3 = @labeledstmt +| 4 = @exprstmt +| 5 = @sendstmt +| 6 = @incstmt +| 7 = @decstmt +| 8 = @gostmt +| 9 = @deferstmt +| 10 = @returnstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @gotostmt +| 14 = @fallthroughstmt +| 15 = @blockstmt +| 16 = @ifstmt +| 17 = @caseclause +| 18 = @exprswitchstmt +| 19 = @typeswitchstmt +| 20 = @commclause +| 21 = @selectstmt +| 22 = @forstmt +| 23 = @rangestmt +| 24 = @assignstmt +| 25 = @definestmt +| 26 = @addassignstmt +| 27 = @subassignstmt +| 28 = @mulassignstmt +| 29 = @quoassignstmt +| 30 = @remassignstmt +| 31 = @andassignstmt +| 32 = @orassignstmt +| 33 = @xorassignstmt +| 34 = @shlassignstmt +| 35 = @shrassignstmt +| 36 = @andnotassignstmt; + +@incdecstmt = @incstmt | @decstmt; + +@assignment = @simpleassignstmt | @compoundassignstmt; + +@simpleassignstmt = @assignstmt | @definestmt; + +@compoundassignstmt = @addassignstmt | @subassignstmt | @mulassignstmt | @quoassignstmt | @remassignstmt + | @andassignstmt | @orassignstmt | @xorassignstmt | @shlassignstmt | @shrassignstmt | @andnotassignstmt; + +@branchstmt = @breakstmt | @continuestmt | @gotostmt | @fallthroughstmt; + +@switchstmt = @exprswitchstmt | @typeswitchstmt; + +@loopstmt = @forstmt | @rangestmt; + +case @decl.kind of + 0 = @baddecl +| 1 = @importdecl +| 2 = @constdecl +| 3 = @typedecl +| 4 = @vardecl +| 5 = @funcdecl; + +@gendecl = @importdecl | @constdecl | @typedecl | @vardecl; + +case @spec.kind of + 0 = @importspec +| 1 = @valuespec +| 2 = @typedefspec +| 3 = @aliasspec; + +@typespec = @typedefspec | @aliasspec; + +case @object.kind of + 0 = @pkgobject +| 1 = @decltypeobject +| 2 = @builtintypeobject +| 3 = @declconstobject +| 4 = @builtinconstobject +| 5 = @declvarobject +| 6 = @declfunctionobject +| 7 = @builtinfunctionobject +| 8 = @labelobject; + +@typeparamparentobject = @decltypeobject | @declfunctionobject; + +@declobject = @decltypeobject | @declconstobject | @declvarobject | @declfunctionobject; + +@builtinobject = @builtintypeobject | @builtinconstobject | @builtinfunctionobject; + +@typeobject = @decltypeobject | @builtintypeobject; + +@valueobject = @constobject | @varobject | @functionobject; + +@constobject = @declconstobject | @builtinconstobject; + +@varobject = @declvarobject; + +@functionobject = @declfunctionobject | @builtinfunctionobject; + +case @scope.kind of + 0 = @universescope +| 1 = @packagescope +| 2 = @localscope; + +case @type.kind of + 0 = @invalidtype +| 1 = @boolexprtype +| 2 = @inttype +| 3 = @int8type +| 4 = @int16type +| 5 = @int32type +| 6 = @int64type +| 7 = @uinttype +| 8 = @uint8type +| 9 = @uint16type +| 10 = @uint32type +| 11 = @uint64type +| 12 = @uintptrtype +| 13 = @float32type +| 14 = @float64type +| 15 = @complex64type +| 16 = @complex128type +| 17 = @stringexprtype +| 18 = @unsafepointertype +| 19 = @boolliteraltype +| 20 = @intliteraltype +| 21 = @runeliteraltype +| 22 = @floatliteraltype +| 23 = @complexliteraltype +| 24 = @stringliteraltype +| 25 = @nilliteraltype +| 26 = @typeparamtype +| 27 = @arraytype +| 28 = @slicetype +| 29 = @structtype +| 30 = @pointertype +| 31 = @interfacetype +| 32 = @tupletype +| 33 = @signaturetype +| 34 = @maptype +| 35 = @sendchantype +| 36 = @recvchantype +| 37 = @sendrcvchantype +| 38 = @definedtype +| 39 = @typesetliteraltype; + +@basictype = @booltype | @numerictype | @stringtype | @literaltype | @invalidtype | @unsafepointertype; + +@booltype = @boolexprtype | @boolliteraltype; + +@numerictype = @integertype | @floattype | @complextype; + +@integertype = @signedintegertype | @unsignedintegertype; + +@signedintegertype = @inttype | @int8type | @int16type | @int32type | @int64type | @intliteraltype | @runeliteraltype; + +@unsignedintegertype = @uinttype | @uint8type | @uint16type | @uint32type | @uint64type | @uintptrtype; + +@floattype = @float32type | @float64type | @floatliteraltype; + +@complextype = @complex64type | @complex128type | @complexliteraltype; + +@stringtype = @stringexprtype | @stringliteraltype; + +@literaltype = @boolliteraltype | @intliteraltype | @runeliteraltype | @floatliteraltype | @complexliteraltype + | @stringliteraltype | @nilliteraltype; + +@compositetype = @typeparamtype | @containertype | @structtype | @pointertype | @interfacetype | @tupletype + | @signaturetype | @definedtype | @typesetliteraltype; + +@containertype = @arraytype | @slicetype | @maptype | @chantype; + +@chantype = @sendchantype | @recvchantype | @sendrcvchantype; + +case @modexpr.kind of + 0 = @modcommentblock +| 1 = @modline +| 2 = @modlineblock +| 3 = @modlparen +| 4 = @modrparen; + +case @error.kind of + 0 = @unknownerror +| 1 = @listerror +| 2 = @parseerror +| 3 = @typeerror; + diff --git a/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/typeparam.ql b/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/typeparam.ql new file mode 100644 index 000000000000..00c17208e6ea --- /dev/null +++ b/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/typeparam.ql @@ -0,0 +1,15 @@ +class TypeParamType extends @typeparamtype { + string toString() { none() } +} + +class CompositeType extends @compositetype { + string toString() { none() } +} + +class TypeParamParentObject extends @typeparamparentobject { + string toString() { none() } +} + +from TypeParamType tp, string name, CompositeType bound, TypeParamParentObject parent, int idx, boolean is_from_recv +where typeparam(tp, name, bound, parent, idx, is_from_recv) +select tp, name, bound, parent, idx diff --git a/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/upgrade.properties b/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/upgrade.properties new file mode 100644 index 000000000000..d383bdf4ad30 --- /dev/null +++ b/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/upgrade.properties @@ -0,0 +1,3 @@ +description: Track whether a type parameter is from a receiver +compatibility: partial +typeparam.rel: run typeparam.qlo diff --git a/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/go.dbscheme b/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/go.dbscheme new file mode 100644 index 000000000000..5ff5325d274a --- /dev/null +++ b/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/go.dbscheme @@ -0,0 +1,563 @@ +/** Auto-generated dbscheme; do not edit. Run `make gen` in directory `go/` to regenerate. */ + + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +/** Overlay support **/ + +databaseMetadata( + string metadataKey: string ref, + string value: string ref +); + +overlayChangedFiles( + string path: string ref +); + + +/* + * XML Files + */ + +xmlEncoding( + unique int id: @file ref, + string encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +compilations(unique int id: @compilation, string cwd: string ref); + +#keyset[id, num] +compilation_args(int id: @compilation ref, int num: int ref, string arg: string ref); + +#keyset[id, num, kind] +compilation_time(int id: @compilation ref, int num: int ref, int kind: int ref, float secs: float ref); + +diagnostic_for(unique int diagnostic: @diagnostic ref, int compilation: @compilation ref, int file_number: int ref, int file_number_diagnostic_number: int ref); + +compilation_finished(unique int id: @compilation ref, float cpu_seconds: float ref, float elapsed_seconds: float ref); + +#keyset[id, num] +compilation_compiling_files(int id: @compilation ref, int num: int ref, int file: @file ref); + +diagnostics(unique int id: @diagnostic, int severity: int ref, string error_tag: string ref, string error_message: string ref, + string full_error_message: string ref, int location: @location ref); + +locations_default(unique int id: @location_default, int file: @file ref, int beginLine: int ref, int beginColumn: int ref, + int endLine: int ref, int endColumn: int ref); + +numlines(int element_id: @sourceline ref, int num_lines: int ref, int num_code: int ref, int num_comment: int ref); + +files(unique int id: @file, string name: string ref); + +folders(unique int id: @folder, string name: string ref); + +containerparent(int parent: @container ref, unique int child: @container ref); + +has_location(unique int locatable: @locatable ref, int location: @location ref); + +#keyset[parent, idx] +comment_groups(unique int id: @comment_group, int parent: @file ref, int idx: int ref); + +comments(unique int id: @comment, int kind: int ref, int parent: @comment_group ref, int idx: int ref, string text: string ref); + +doc_comments(unique int node: @documentable ref, int comment: @comment_group ref); + +#keyset[parent, idx] +exprs(unique int id: @expr, int kind: int ref, int parent: @exprparent ref, int idx: int ref); + +literals(unique int expr: @expr ref, string value: string ref, string raw: string ref); + +constvalues(unique int expr: @expr ref, string value: string ref, string exact: string ref); + +fields(unique int id: @field, int parent: @fieldparent ref, int idx: int ref); + +typeparamdecls(unique int id: @typeparamdecl, int parent: @typeparamdeclparent ref, int idx: int ref); + +#keyset[parent, idx] +stmts(unique int id: @stmt, int kind: int ref, int parent: @stmtparent ref, int idx: int ref); + +#keyset[parent, idx] +decls(unique int id: @decl, int kind: int ref, int parent: @declparent ref, int idx: int ref); + +#keyset[parent, idx] +specs(unique int id: @spec, int kind: int ref, int parent: @gendecl ref, int idx: int ref); + +scopes(unique int id: @scope, int kind: int ref); + +scopenesting(unique int inner: @scope ref, int outer: @scope ref); + +scopenodes(unique int node: @scopenode ref, int scope: @localscope ref); + +objects(unique int id: @object, int kind: int ref, string name: string ref); + +objectscopes(unique int object: @object ref, int scope: @scope ref); + +objecttypes(unique int object: @object ref, int tp: @type ref); + +methodreceivers(unique int method: @object ref, int receiver: @object ref); + +fieldstructs(unique int field: @object ref, int struct: @structtype ref); + +methodhosts(int method: @object ref, int host: @definedtype ref); + +defs(int ident: @ident ref, int object: @object ref); + +uses(int ident: @ident ref, int object: @object ref); + +types(unique int id: @type, int kind: int ref); + +type_of(unique int expr: @expr ref, int tp: @type ref); + +typename(unique int tp: @type ref, string name: string ref); + +key_type(unique int map: @maptype ref, int tp: @type ref); + +element_type(unique int container: @containertype ref, int tp: @type ref); + +base_type(unique int ptr: @pointertype ref, int tp: @type ref); + +underlying_type(unique int defined: @definedtype ref, int tp: @type ref); + +#keyset[parent, index] +component_types(int parent: @compositetype ref, int index: int ref, string name: string ref, int tp: @type ref); + +#keyset[parent, index] +struct_tags(int parent: @structtype ref, int index: int ref, string tag: string ref); + +#keyset[interface, index] +interface_private_method_ids(int interface: @interfacetype ref, int index: int ref, string id: string ref); + +array_length(unique int tp: @arraytype ref, string len: string ref); + +type_objects(unique int tp: @type ref, int object: @object ref); + +packages(unique int id: @package, string name: string ref, string path: string ref, int scope: @packagescope ref); + +#keyset[parent, idx] +modexprs(unique int id: @modexpr, int kind: int ref, int parent: @modexprparent ref, int idx: int ref); + +#keyset[parent, idx] +modtokens(string token: string ref, int parent: @modexpr ref, int idx: int ref); + +#keyset[package, idx] +errors(unique int id: @error, int kind: int ref, string msg: string ref, string rawpos: string ref, + string file: string ref, int line: int ref, int col: int ref, int package: @package ref, int idx: int ref); + +has_ellipsis(int id: @callorconversionexpr ref); + +variadic(int id: @signaturetype ref); + +#keyset[parent, idx, is_from_recv] +typeparam(unique int tp: @typeparamtype ref, string name: string ref, + int bound: @compositetype ref, int parent: @typeparamparentobject ref, int idx: int ref, boolean is_from_recv: boolean ref); + +@container = @file | @folder; + +@locatable = @xmllocatable | @node | @localscope; + +@node = @documentable | @exprparent | @modexprparent | @fieldparent | @stmtparent | @declparent | @typeparamdeclparent + | @scopenode | @comment_group | @comment; + +@documentable = @file | @field | @typeparamdecl | @spec | @gendecl | @funcdecl | @modexpr; + +@exprparent = @funcdef | @file | @expr | @field | @stmt | @decl | @typeparamdecl | @spec; + +@modexprparent = @file | @modexpr; + +@fieldparent = @decl | @structtypeexpr | @functypeexpr | @interfacetypeexpr; + +@stmtparent = @funcdef | @stmt | @decl; + +@declparent = @file | @declstmt; + +@typeparamdeclparent = @funcdecl | @typespec; + +@funcdef = @funclit | @funcdecl; + +@scopenode = @file | @functypeexpr | @blockstmt | @ifstmt | @caseclause | @switchstmt | @commclause | @loopstmt; + +@location = @location_default; + +@sourceline = @locatable; + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment; + +case @expr.kind of + 0 = @badexpr +| 1 = @ident +| 2 = @ellipsis +| 3 = @intlit +| 4 = @floatlit +| 5 = @imaglit +| 6 = @charlit +| 7 = @stringlit +| 8 = @funclit +| 9 = @compositelit +| 10 = @parenexpr +| 11 = @selectorexpr +| 12 = @indexexpr +| 13 = @genericfunctioninstantiationexpr +| 14 = @generictypeinstantiationexpr +| 15 = @sliceexpr +| 16 = @typeassertexpr +| 17 = @callorconversionexpr +| 18 = @starexpr +| 19 = @keyvalueexpr +| 20 = @arraytypeexpr +| 21 = @structtypeexpr +| 22 = @functypeexpr +| 23 = @interfacetypeexpr +| 24 = @maptypeexpr +| 25 = @typesetliteralexpr +| 26 = @plusexpr +| 27 = @minusexpr +| 28 = @notexpr +| 29 = @complementexpr +| 30 = @derefexpr +| 31 = @addressexpr +| 32 = @arrowexpr +| 33 = @lorexpr +| 34 = @landexpr +| 35 = @eqlexpr +| 36 = @neqexpr +| 37 = @lssexpr +| 38 = @leqexpr +| 39 = @gtrexpr +| 40 = @geqexpr +| 41 = @addexpr +| 42 = @subexpr +| 43 = @orexpr +| 44 = @xorexpr +| 45 = @mulexpr +| 46 = @quoexpr +| 47 = @remexpr +| 48 = @shlexpr +| 49 = @shrexpr +| 50 = @andexpr +| 51 = @andnotexpr +| 52 = @sendchantypeexpr +| 53 = @recvchantypeexpr +| 54 = @sendrcvchantypeexpr; + +@basiclit = @intlit | @floatlit | @imaglit | @charlit | @stringlit; + +@operatorexpr = @logicalexpr | @arithmeticexpr | @bitwiseexpr | @unaryexpr | @binaryexpr; + +@logicalexpr = @logicalunaryexpr | @logicalbinaryexpr; + +@arithmeticexpr = @arithmeticunaryexpr | @arithmeticbinaryexpr; + +@bitwiseexpr = @bitwiseunaryexpr | @bitwisebinaryexpr; + +@unaryexpr = @logicalunaryexpr | @bitwiseunaryexpr | @arithmeticunaryexpr | @derefexpr | @addressexpr | @arrowexpr; + +@logicalunaryexpr = @notexpr; + +@bitwiseunaryexpr = @complementexpr; + +@arithmeticunaryexpr = @plusexpr | @minusexpr; + +@binaryexpr = @logicalbinaryexpr | @bitwisebinaryexpr | @arithmeticbinaryexpr | @comparison; + +@logicalbinaryexpr = @lorexpr | @landexpr; + +@bitwisebinaryexpr = @shiftexpr | @orexpr | @xorexpr | @andexpr | @andnotexpr; + +@arithmeticbinaryexpr = @addexpr | @subexpr | @mulexpr | @quoexpr | @remexpr; + +@shiftexpr = @shlexpr | @shrexpr; + +@comparison = @equalitytest | @relationalcomparison; + +@equalitytest = @eqlexpr | @neqexpr; + +@relationalcomparison = @lssexpr | @leqexpr | @gtrexpr | @geqexpr; + +@chantypeexpr = @sendchantypeexpr | @recvchantypeexpr | @sendrcvchantypeexpr; + +case @stmt.kind of + 0 = @badstmt +| 1 = @declstmt +| 2 = @emptystmt +| 3 = @labeledstmt +| 4 = @exprstmt +| 5 = @sendstmt +| 6 = @incstmt +| 7 = @decstmt +| 8 = @gostmt +| 9 = @deferstmt +| 10 = @returnstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @gotostmt +| 14 = @fallthroughstmt +| 15 = @blockstmt +| 16 = @ifstmt +| 17 = @caseclause +| 18 = @exprswitchstmt +| 19 = @typeswitchstmt +| 20 = @commclause +| 21 = @selectstmt +| 22 = @forstmt +| 23 = @rangestmt +| 24 = @assignstmt +| 25 = @definestmt +| 26 = @addassignstmt +| 27 = @subassignstmt +| 28 = @mulassignstmt +| 29 = @quoassignstmt +| 30 = @remassignstmt +| 31 = @andassignstmt +| 32 = @orassignstmt +| 33 = @xorassignstmt +| 34 = @shlassignstmt +| 35 = @shrassignstmt +| 36 = @andnotassignstmt; + +@incdecstmt = @incstmt | @decstmt; + +@assignment = @simpleassignstmt | @compoundassignstmt; + +@simpleassignstmt = @assignstmt | @definestmt; + +@compoundassignstmt = @addassignstmt | @subassignstmt | @mulassignstmt | @quoassignstmt | @remassignstmt + | @andassignstmt | @orassignstmt | @xorassignstmt | @shlassignstmt | @shrassignstmt | @andnotassignstmt; + +@branchstmt = @breakstmt | @continuestmt | @gotostmt | @fallthroughstmt; + +@switchstmt = @exprswitchstmt | @typeswitchstmt; + +@loopstmt = @forstmt | @rangestmt; + +case @decl.kind of + 0 = @baddecl +| 1 = @importdecl +| 2 = @constdecl +| 3 = @typedecl +| 4 = @vardecl +| 5 = @funcdecl; + +@gendecl = @importdecl | @constdecl | @typedecl | @vardecl; + +case @spec.kind of + 0 = @importspec +| 1 = @valuespec +| 2 = @typedefspec +| 3 = @aliasspec; + +@typespec = @typedefspec | @aliasspec; + +case @object.kind of + 0 = @pkgobject +| 1 = @decltypeobject +| 2 = @builtintypeobject +| 3 = @declconstobject +| 4 = @builtinconstobject +| 5 = @declvarobject +| 6 = @declfunctionobject +| 7 = @builtinfunctionobject +| 8 = @labelobject; + +@typeparamparentobject = @decltypeobject | @declfunctionobject; + +@declobject = @decltypeobject | @declconstobject | @declvarobject | @declfunctionobject; + +@builtinobject = @builtintypeobject | @builtinconstobject | @builtinfunctionobject; + +@typeobject = @decltypeobject | @builtintypeobject; + +@valueobject = @constobject | @varobject | @functionobject; + +@constobject = @declconstobject | @builtinconstobject; + +@varobject = @declvarobject; + +@functionobject = @declfunctionobject | @builtinfunctionobject; + +case @scope.kind of + 0 = @universescope +| 1 = @packagescope +| 2 = @localscope; + +case @type.kind of + 0 = @invalidtype +| 1 = @boolexprtype +| 2 = @inttype +| 3 = @int8type +| 4 = @int16type +| 5 = @int32type +| 6 = @int64type +| 7 = @uinttype +| 8 = @uint8type +| 9 = @uint16type +| 10 = @uint32type +| 11 = @uint64type +| 12 = @uintptrtype +| 13 = @float32type +| 14 = @float64type +| 15 = @complex64type +| 16 = @complex128type +| 17 = @stringexprtype +| 18 = @unsafepointertype +| 19 = @boolliteraltype +| 20 = @intliteraltype +| 21 = @runeliteraltype +| 22 = @floatliteraltype +| 23 = @complexliteraltype +| 24 = @stringliteraltype +| 25 = @nilliteraltype +| 26 = @typeparamtype +| 27 = @arraytype +| 28 = @slicetype +| 29 = @structtype +| 30 = @pointertype +| 31 = @interfacetype +| 32 = @tupletype +| 33 = @signaturetype +| 34 = @maptype +| 35 = @sendchantype +| 36 = @recvchantype +| 37 = @sendrcvchantype +| 38 = @definedtype +| 39 = @typesetliteraltype; + +@basictype = @booltype | @numerictype | @stringtype | @literaltype | @invalidtype | @unsafepointertype; + +@booltype = @boolexprtype | @boolliteraltype; + +@numerictype = @integertype | @floattype | @complextype; + +@integertype = @signedintegertype | @unsignedintegertype; + +@signedintegertype = @inttype | @int8type | @int16type | @int32type | @int64type | @intliteraltype | @runeliteraltype; + +@unsignedintegertype = @uinttype | @uint8type | @uint16type | @uint32type | @uint64type | @uintptrtype; + +@floattype = @float32type | @float64type | @floatliteraltype; + +@complextype = @complex64type | @complex128type | @complexliteraltype; + +@stringtype = @stringexprtype | @stringliteraltype; + +@literaltype = @boolliteraltype | @intliteraltype | @runeliteraltype | @floatliteraltype | @complexliteraltype + | @stringliteraltype | @nilliteraltype; + +@compositetype = @typeparamtype | @containertype | @structtype | @pointertype | @interfacetype | @tupletype + | @signaturetype | @definedtype | @typesetliteraltype; + +@containertype = @arraytype | @slicetype | @maptype | @chantype; + +@chantype = @sendchantype | @recvchantype | @sendrcvchantype; + +case @modexpr.kind of + 0 = @modcommentblock +| 1 = @modline +| 2 = @modlineblock +| 3 = @modlparen +| 4 = @modrparen; + +case @error.kind of + 0 = @unknownerror +| 1 = @listerror +| 2 = @parseerror +| 3 = @typeerror; + diff --git a/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/old.dbscheme b/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/old.dbscheme new file mode 100644 index 000000000000..b1341734d687 --- /dev/null +++ b/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/old.dbscheme @@ -0,0 +1,563 @@ +/** Auto-generated dbscheme; do not edit. Run `make gen` in directory `go/` to regenerate. */ + + +/** Duplicate code **/ + +duplicateCode( + unique int id : @duplication, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +similarCode( + unique int id : @similarity, + varchar(900) relativePath : string ref, + int equivClass : int ref); + +@duplication_or_similarity = @duplication | @similarity; + +tokens( + int id : @duplication_or_similarity ref, + int offset : int ref, + int beginLine : int ref, + int beginColumn : int ref, + int endLine : int ref, + int endColumn : int ref); + +/** External data **/ + +externalData( + int id : @externalDataElement, + varchar(900) path : string ref, + int column: int ref, + varchar(900) value : string ref +); + +snapshotDate(unique date snapshotDate : date ref); + +sourceLocationPrefix(varchar(900) prefix : string ref); + +/** Overlay support **/ + +databaseMetadata( + string metadataKey: string ref, + string value: string ref +); + +overlayChangedFiles( + string path: string ref +); + + +/* + * XML Files + */ + +xmlEncoding( + unique int id: @file ref, + string encoding: string ref +); + +xmlDTDs( + unique int id: @xmldtd, + string root: string ref, + string publicId: string ref, + string systemId: string ref, + int fileid: @file ref +); + +xmlElements( + unique int id: @xmlelement, + string name: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int fileid: @file ref +); + +xmlAttrs( + unique int id: @xmlattribute, + int elementid: @xmlelement ref, + string name: string ref, + string value: string ref, + int idx: int ref, + int fileid: @file ref +); + +xmlNs( + int id: @xmlnamespace, + string prefixName: string ref, + string URI: string ref, + int fileid: @file ref +); + +xmlHasNs( + int elementId: @xmlnamespaceable ref, + int nsId: @xmlnamespace ref, + int fileid: @file ref +); + +xmlComments( + unique int id: @xmlcomment, + string text: string ref, + int parentid: @xmlparent ref, + int fileid: @file ref +); + +xmlChars( + unique int id: @xmlcharacters, + string text: string ref, + int parentid: @xmlparent ref, + int idx: int ref, + int isCDATA: int ref, + int fileid: @file ref +); + +@xmlparent = @file | @xmlelement; +@xmlnamespaceable = @xmlelement | @xmlattribute; + +xmllocations( + int xmlElement: @xmllocatable ref, + int location: @location_default ref +); + +@xmllocatable = @xmlcharacters | @xmlelement | @xmlcomment | @xmlattribute | @xmldtd | @file | @xmlnamespace; + +compilations(unique int id: @compilation, string cwd: string ref); + +#keyset[id, num] +compilation_args(int id: @compilation ref, int num: int ref, string arg: string ref); + +#keyset[id, num, kind] +compilation_time(int id: @compilation ref, int num: int ref, int kind: int ref, float secs: float ref); + +diagnostic_for(unique int diagnostic: @diagnostic ref, int compilation: @compilation ref, int file_number: int ref, int file_number_diagnostic_number: int ref); + +compilation_finished(unique int id: @compilation ref, float cpu_seconds: float ref, float elapsed_seconds: float ref); + +#keyset[id, num] +compilation_compiling_files(int id: @compilation ref, int num: int ref, int file: @file ref); + +diagnostics(unique int id: @diagnostic, int severity: int ref, string error_tag: string ref, string error_message: string ref, + string full_error_message: string ref, int location: @location ref); + +locations_default(unique int id: @location_default, int file: @file ref, int beginLine: int ref, int beginColumn: int ref, + int endLine: int ref, int endColumn: int ref); + +numlines(int element_id: @sourceline ref, int num_lines: int ref, int num_code: int ref, int num_comment: int ref); + +files(unique int id: @file, string name: string ref); + +folders(unique int id: @folder, string name: string ref); + +containerparent(int parent: @container ref, unique int child: @container ref); + +has_location(unique int locatable: @locatable ref, int location: @location ref); + +#keyset[parent, idx] +comment_groups(unique int id: @comment_group, int parent: @file ref, int idx: int ref); + +comments(unique int id: @comment, int kind: int ref, int parent: @comment_group ref, int idx: int ref, string text: string ref); + +doc_comments(unique int node: @documentable ref, int comment: @comment_group ref); + +#keyset[parent, idx] +exprs(unique int id: @expr, int kind: int ref, int parent: @exprparent ref, int idx: int ref); + +literals(unique int expr: @expr ref, string value: string ref, string raw: string ref); + +constvalues(unique int expr: @expr ref, string value: string ref, string exact: string ref); + +fields(unique int id: @field, int parent: @fieldparent ref, int idx: int ref); + +typeparamdecls(unique int id: @typeparamdecl, int parent: @typeparamdeclparent ref, int idx: int ref); + +#keyset[parent, idx] +stmts(unique int id: @stmt, int kind: int ref, int parent: @stmtparent ref, int idx: int ref); + +#keyset[parent, idx] +decls(unique int id: @decl, int kind: int ref, int parent: @declparent ref, int idx: int ref); + +#keyset[parent, idx] +specs(unique int id: @spec, int kind: int ref, int parent: @gendecl ref, int idx: int ref); + +scopes(unique int id: @scope, int kind: int ref); + +scopenesting(unique int inner: @scope ref, int outer: @scope ref); + +scopenodes(unique int node: @scopenode ref, int scope: @localscope ref); + +objects(unique int id: @object, int kind: int ref, string name: string ref); + +objectscopes(unique int object: @object ref, int scope: @scope ref); + +objecttypes(unique int object: @object ref, int tp: @type ref); + +methodreceivers(unique int method: @object ref, int receiver: @object ref); + +fieldstructs(unique int field: @object ref, int struct: @structtype ref); + +methodhosts(int method: @object ref, int host: @definedtype ref); + +defs(int ident: @ident ref, int object: @object ref); + +uses(int ident: @ident ref, int object: @object ref); + +types(unique int id: @type, int kind: int ref); + +type_of(unique int expr: @expr ref, int tp: @type ref); + +typename(unique int tp: @type ref, string name: string ref); + +key_type(unique int map: @maptype ref, int tp: @type ref); + +element_type(unique int container: @containertype ref, int tp: @type ref); + +base_type(unique int ptr: @pointertype ref, int tp: @type ref); + +underlying_type(unique int defined: @definedtype ref, int tp: @type ref); + +#keyset[parent, index] +component_types(int parent: @compositetype ref, int index: int ref, string name: string ref, int tp: @type ref); + +#keyset[parent, index] +struct_tags(int parent: @structtype ref, int index: int ref, string tag: string ref); + +#keyset[interface, index] +interface_private_method_ids(int interface: @interfacetype ref, int index: int ref, string id: string ref); + +array_length(unique int tp: @arraytype ref, string len: string ref); + +type_objects(unique int tp: @type ref, int object: @object ref); + +packages(unique int id: @package, string name: string ref, string path: string ref, int scope: @packagescope ref); + +#keyset[parent, idx] +modexprs(unique int id: @modexpr, int kind: int ref, int parent: @modexprparent ref, int idx: int ref); + +#keyset[parent, idx] +modtokens(string token: string ref, int parent: @modexpr ref, int idx: int ref); + +#keyset[package, idx] +errors(unique int id: @error, int kind: int ref, string msg: string ref, string rawpos: string ref, + string file: string ref, int line: int ref, int col: int ref, int package: @package ref, int idx: int ref); + +has_ellipsis(int id: @callorconversionexpr ref); + +variadic(int id: @signaturetype ref); + +#keyset[parent, idx] +typeparam(unique int tp: @typeparamtype ref, string name: string ref, int bound: @compositetype ref, + int parent: @typeparamparentobject ref, int idx: int ref); + +@container = @file | @folder; + +@locatable = @xmllocatable | @node | @localscope; + +@node = @documentable | @exprparent | @modexprparent | @fieldparent | @stmtparent | @declparent | @typeparamdeclparent + | @scopenode | @comment_group | @comment; + +@documentable = @file | @field | @typeparamdecl | @spec | @gendecl | @funcdecl | @modexpr; + +@exprparent = @funcdef | @file | @expr | @field | @stmt | @decl | @typeparamdecl | @spec; + +@modexprparent = @file | @modexpr; + +@fieldparent = @decl | @structtypeexpr | @functypeexpr | @interfacetypeexpr; + +@stmtparent = @funcdef | @stmt | @decl; + +@declparent = @file | @declstmt; + +@typeparamdeclparent = @funcdecl | @typespec; + +@funcdef = @funclit | @funcdecl; + +@scopenode = @file | @functypeexpr | @blockstmt | @ifstmt | @caseclause | @switchstmt | @commclause | @loopstmt; + +@location = @location_default; + +@sourceline = @locatable; + +case @comment.kind of + 0 = @slashslashcomment +| 1 = @slashstarcomment; + +case @expr.kind of + 0 = @badexpr +| 1 = @ident +| 2 = @ellipsis +| 3 = @intlit +| 4 = @floatlit +| 5 = @imaglit +| 6 = @charlit +| 7 = @stringlit +| 8 = @funclit +| 9 = @compositelit +| 10 = @parenexpr +| 11 = @selectorexpr +| 12 = @indexexpr +| 13 = @genericfunctioninstantiationexpr +| 14 = @generictypeinstantiationexpr +| 15 = @sliceexpr +| 16 = @typeassertexpr +| 17 = @callorconversionexpr +| 18 = @starexpr +| 19 = @keyvalueexpr +| 20 = @arraytypeexpr +| 21 = @structtypeexpr +| 22 = @functypeexpr +| 23 = @interfacetypeexpr +| 24 = @maptypeexpr +| 25 = @typesetliteralexpr +| 26 = @plusexpr +| 27 = @minusexpr +| 28 = @notexpr +| 29 = @complementexpr +| 30 = @derefexpr +| 31 = @addressexpr +| 32 = @arrowexpr +| 33 = @lorexpr +| 34 = @landexpr +| 35 = @eqlexpr +| 36 = @neqexpr +| 37 = @lssexpr +| 38 = @leqexpr +| 39 = @gtrexpr +| 40 = @geqexpr +| 41 = @addexpr +| 42 = @subexpr +| 43 = @orexpr +| 44 = @xorexpr +| 45 = @mulexpr +| 46 = @quoexpr +| 47 = @remexpr +| 48 = @shlexpr +| 49 = @shrexpr +| 50 = @andexpr +| 51 = @andnotexpr +| 52 = @sendchantypeexpr +| 53 = @recvchantypeexpr +| 54 = @sendrcvchantypeexpr; + +@basiclit = @intlit | @floatlit | @imaglit | @charlit | @stringlit; + +@operatorexpr = @logicalexpr | @arithmeticexpr | @bitwiseexpr | @unaryexpr | @binaryexpr; + +@logicalexpr = @logicalunaryexpr | @logicalbinaryexpr; + +@arithmeticexpr = @arithmeticunaryexpr | @arithmeticbinaryexpr; + +@bitwiseexpr = @bitwiseunaryexpr | @bitwisebinaryexpr; + +@unaryexpr = @logicalunaryexpr | @bitwiseunaryexpr | @arithmeticunaryexpr | @derefexpr | @addressexpr | @arrowexpr; + +@logicalunaryexpr = @notexpr; + +@bitwiseunaryexpr = @complementexpr; + +@arithmeticunaryexpr = @plusexpr | @minusexpr; + +@binaryexpr = @logicalbinaryexpr | @bitwisebinaryexpr | @arithmeticbinaryexpr | @comparison; + +@logicalbinaryexpr = @lorexpr | @landexpr; + +@bitwisebinaryexpr = @shiftexpr | @orexpr | @xorexpr | @andexpr | @andnotexpr; + +@arithmeticbinaryexpr = @addexpr | @subexpr | @mulexpr | @quoexpr | @remexpr; + +@shiftexpr = @shlexpr | @shrexpr; + +@comparison = @equalitytest | @relationalcomparison; + +@equalitytest = @eqlexpr | @neqexpr; + +@relationalcomparison = @lssexpr | @leqexpr | @gtrexpr | @geqexpr; + +@chantypeexpr = @sendchantypeexpr | @recvchantypeexpr | @sendrcvchantypeexpr; + +case @stmt.kind of + 0 = @badstmt +| 1 = @declstmt +| 2 = @emptystmt +| 3 = @labeledstmt +| 4 = @exprstmt +| 5 = @sendstmt +| 6 = @incstmt +| 7 = @decstmt +| 8 = @gostmt +| 9 = @deferstmt +| 10 = @returnstmt +| 11 = @breakstmt +| 12 = @continuestmt +| 13 = @gotostmt +| 14 = @fallthroughstmt +| 15 = @blockstmt +| 16 = @ifstmt +| 17 = @caseclause +| 18 = @exprswitchstmt +| 19 = @typeswitchstmt +| 20 = @commclause +| 21 = @selectstmt +| 22 = @forstmt +| 23 = @rangestmt +| 24 = @assignstmt +| 25 = @definestmt +| 26 = @addassignstmt +| 27 = @subassignstmt +| 28 = @mulassignstmt +| 29 = @quoassignstmt +| 30 = @remassignstmt +| 31 = @andassignstmt +| 32 = @orassignstmt +| 33 = @xorassignstmt +| 34 = @shlassignstmt +| 35 = @shrassignstmt +| 36 = @andnotassignstmt; + +@incdecstmt = @incstmt | @decstmt; + +@assignment = @simpleassignstmt | @compoundassignstmt; + +@simpleassignstmt = @assignstmt | @definestmt; + +@compoundassignstmt = @addassignstmt | @subassignstmt | @mulassignstmt | @quoassignstmt | @remassignstmt + | @andassignstmt | @orassignstmt | @xorassignstmt | @shlassignstmt | @shrassignstmt | @andnotassignstmt; + +@branchstmt = @breakstmt | @continuestmt | @gotostmt | @fallthroughstmt; + +@switchstmt = @exprswitchstmt | @typeswitchstmt; + +@loopstmt = @forstmt | @rangestmt; + +case @decl.kind of + 0 = @baddecl +| 1 = @importdecl +| 2 = @constdecl +| 3 = @typedecl +| 4 = @vardecl +| 5 = @funcdecl; + +@gendecl = @importdecl | @constdecl | @typedecl | @vardecl; + +case @spec.kind of + 0 = @importspec +| 1 = @valuespec +| 2 = @typedefspec +| 3 = @aliasspec; + +@typespec = @typedefspec | @aliasspec; + +case @object.kind of + 0 = @pkgobject +| 1 = @decltypeobject +| 2 = @builtintypeobject +| 3 = @declconstobject +| 4 = @builtinconstobject +| 5 = @declvarobject +| 6 = @declfunctionobject +| 7 = @builtinfunctionobject +| 8 = @labelobject; + +@typeparamparentobject = @decltypeobject | @declfunctionobject; + +@declobject = @decltypeobject | @declconstobject | @declvarobject | @declfunctionobject; + +@builtinobject = @builtintypeobject | @builtinconstobject | @builtinfunctionobject; + +@typeobject = @decltypeobject | @builtintypeobject; + +@valueobject = @constobject | @varobject | @functionobject; + +@constobject = @declconstobject | @builtinconstobject; + +@varobject = @declvarobject; + +@functionobject = @declfunctionobject | @builtinfunctionobject; + +case @scope.kind of + 0 = @universescope +| 1 = @packagescope +| 2 = @localscope; + +case @type.kind of + 0 = @invalidtype +| 1 = @boolexprtype +| 2 = @inttype +| 3 = @int8type +| 4 = @int16type +| 5 = @int32type +| 6 = @int64type +| 7 = @uinttype +| 8 = @uint8type +| 9 = @uint16type +| 10 = @uint32type +| 11 = @uint64type +| 12 = @uintptrtype +| 13 = @float32type +| 14 = @float64type +| 15 = @complex64type +| 16 = @complex128type +| 17 = @stringexprtype +| 18 = @unsafepointertype +| 19 = @boolliteraltype +| 20 = @intliteraltype +| 21 = @runeliteraltype +| 22 = @floatliteraltype +| 23 = @complexliteraltype +| 24 = @stringliteraltype +| 25 = @nilliteraltype +| 26 = @typeparamtype +| 27 = @arraytype +| 28 = @slicetype +| 29 = @structtype +| 30 = @pointertype +| 31 = @interfacetype +| 32 = @tupletype +| 33 = @signaturetype +| 34 = @maptype +| 35 = @sendchantype +| 36 = @recvchantype +| 37 = @sendrcvchantype +| 38 = @definedtype +| 39 = @typesetliteraltype; + +@basictype = @booltype | @numerictype | @stringtype | @literaltype | @invalidtype | @unsafepointertype; + +@booltype = @boolexprtype | @boolliteraltype; + +@numerictype = @integertype | @floattype | @complextype; + +@integertype = @signedintegertype | @unsignedintegertype; + +@signedintegertype = @inttype | @int8type | @int16type | @int32type | @int64type | @intliteraltype | @runeliteraltype; + +@unsignedintegertype = @uinttype | @uint8type | @uint16type | @uint32type | @uint64type | @uintptrtype; + +@floattype = @float32type | @float64type | @floatliteraltype; + +@complextype = @complex64type | @complex128type | @complexliteraltype; + +@stringtype = @stringexprtype | @stringliteraltype; + +@literaltype = @boolliteraltype | @intliteraltype | @runeliteraltype | @floatliteraltype | @complexliteraltype + | @stringliteraltype | @nilliteraltype; + +@compositetype = @typeparamtype | @containertype | @structtype | @pointertype | @interfacetype | @tupletype + | @signaturetype | @definedtype | @typesetliteraltype; + +@containertype = @arraytype | @slicetype | @maptype | @chantype; + +@chantype = @sendchantype | @recvchantype | @sendrcvchantype; + +case @modexpr.kind of + 0 = @modcommentblock +| 1 = @modline +| 2 = @modlineblock +| 3 = @modlparen +| 4 = @modrparen; + +case @error.kind of + 0 = @unknownerror +| 1 = @listerror +| 2 = @parseerror +| 3 = @typeerror; + diff --git a/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/typeparam.ql b/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/typeparam.ql new file mode 100644 index 000000000000..041c33577f75 --- /dev/null +++ b/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/typeparam.ql @@ -0,0 +1,15 @@ +class TypeParamType extends @typeparamtype { + string toString() { none() } +} + +class CompositeType extends @compositetype { + string toString() { none() } +} + +class TypeParamParentObject extends @typeparamparentobject { + string toString() { none() } +} + +from TypeParamType tp, string name, CompositeType bound, TypeParamParentObject parent, int idx +where typeparam(tp, name, bound, parent, idx) +select tp, name, bound, parent, idx, false diff --git a/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/upgrade.properties b/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/upgrade.properties new file mode 100644 index 000000000000..d383bdf4ad30 --- /dev/null +++ b/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/upgrade.properties @@ -0,0 +1,3 @@ +description: Track whether a type parameter is from a receiver +compatibility: partial +typeparam.rel: run typeparam.qlo From e17508ca5118b2298785fc41d4c7bcc9f91ba21d Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Mon, 13 Jul 2026 12:34:16 +0200 Subject: [PATCH 33/60] Go: Expose new TypeParamType column and use in test --- go/ql/lib/semmle/go/Types.qll | 3 + .../semmle/go/Function/TypeParamType.expected | 66 +++++++++---------- .../semmle/go/Function/TypeParamType.ql | 3 +- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/go/ql/lib/semmle/go/Types.qll b/go/ql/lib/semmle/go/Types.qll index 217ac5d211c8..e1b5c70f0959 100644 --- a/go/ql/lib/semmle/go/Types.qll +++ b/go/ql/lib/semmle/go/Types.qll @@ -395,6 +395,9 @@ class TypeParamType extends @typeparamtype, CompositeType { /** Gets the index of this type parameter type. */ int getIndex() { typeparam(this, _, _, _, result, _) } + /** Holds if this type parameter type is declared as part of a receiver */ + boolean isFromReceiver() { typeparam(this, _, _, _, _, result) } + override string pp() { result = this.getParamName() } /** diff --git a/go/ql/test/library-tests/semmle/go/Function/TypeParamType.expected b/go/ql/test/library-tests/semmle/go/Function/TypeParamType.expected index bda7c1517975..42be6e28ab3b 100644 --- a/go/ql/test/library-tests/semmle/go/Function/TypeParamType.expected +++ b/go/ql/test/library-tests/semmle/go/Function/TypeParamType.expected @@ -20,36 +20,36 @@ numberOfTypeParameters | genericFunctions.go:152:6:152:36 | multipleAnonymousTypeParamsType | 3 | | genericFunctions.go:154:51:154:51 | f | 3 | #select -| codeql-go-tests/function.EdgeConstraint | 0 | Node | interface { } | -| codeql-go-tests/function.Element | 0 | S | interface { } | -| codeql-go-tests/function.GenericFunctionInAnotherFile | 0 | T | interface { } | -| codeql-go-tests/function.GenericFunctionOneTypeParam | 0 | T | interface { } | -| codeql-go-tests/function.GenericFunctionTwoTypeParams | 0 | K | comparable | -| codeql-go-tests/function.GenericFunctionTwoTypeParams | 1 | V | interface { int64 \| float64 } | -| codeql-go-tests/function.GenericStruct1 | 0 | T | interface { } | -| codeql-go-tests/function.GenericStruct1.f1 | 0 | TF1 | interface { } | -| codeql-go-tests/function.GenericStruct1.g1 | 0 | TG1 | interface { } | -| codeql-go-tests/function.GenericStruct2 | 0 | S | interface { } | -| codeql-go-tests/function.GenericStruct2 | 1 | T | interface { } | -| codeql-go-tests/function.GenericStruct2.f2 | 0 | SF2 | interface { } | -| codeql-go-tests/function.GenericStruct2.f2 | 1 | TF2 | interface { } | -| codeql-go-tests/function.GenericStruct2.g2 | 0 | SG2 | interface { } | -| codeql-go-tests/function.GenericStruct2.g2 | 1 | TG2 | interface { } | -| codeql-go-tests/function.Graph | 0 | Node | NodeConstraint | -| codeql-go-tests/function.Graph | 1 | Edge | EdgeConstraint | -| codeql-go-tests/function.Graph.ShortestPath | 0 | Node | NodeConstraint | -| codeql-go-tests/function.Graph.ShortestPath | 1 | Edge | EdgeConstraint | -| codeql-go-tests/function.List | 0 | T | interface { } | -| codeql-go-tests/function.List.MyLen | 0 | U | interface { } | -| codeql-go-tests/function.New | 0 | Node | NodeConstraint | -| codeql-go-tests/function.New | 1 | Edge | EdgeConstraint | -| codeql-go-tests/function.NodeConstraint | 0 | Edge | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 0 | _ | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 1 | _ | interface { string } | -| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 2 | _ | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType | 0 | _ | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType | 1 | _ | interface { string } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType | 2 | _ | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 0 | _ | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 1 | _ | interface { string } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 2 | _ | interface { } | +| codeql-go-tests/function.EdgeConstraint | 0 | false | Node | interface { } | +| codeql-go-tests/function.Element | 0 | false | S | interface { } | +| codeql-go-tests/function.GenericFunctionInAnotherFile | 0 | false | T | interface { } | +| codeql-go-tests/function.GenericFunctionOneTypeParam | 0 | false | T | interface { } | +| codeql-go-tests/function.GenericFunctionTwoTypeParams | 0 | false | K | comparable | +| codeql-go-tests/function.GenericFunctionTwoTypeParams | 1 | false | V | interface { int64 \| float64 } | +| codeql-go-tests/function.GenericStruct1 | 0 | false | T | interface { } | +| codeql-go-tests/function.GenericStruct1.f1 | 0 | true | TF1 | interface { } | +| codeql-go-tests/function.GenericStruct1.g1 | 0 | true | TG1 | interface { } | +| codeql-go-tests/function.GenericStruct2 | 0 | false | S | interface { } | +| codeql-go-tests/function.GenericStruct2 | 1 | false | T | interface { } | +| codeql-go-tests/function.GenericStruct2.f2 | 0 | true | SF2 | interface { } | +| codeql-go-tests/function.GenericStruct2.f2 | 1 | true | TF2 | interface { } | +| codeql-go-tests/function.GenericStruct2.g2 | 0 | true | SG2 | interface { } | +| codeql-go-tests/function.GenericStruct2.g2 | 1 | true | TG2 | interface { } | +| codeql-go-tests/function.Graph | 0 | false | Node | NodeConstraint | +| codeql-go-tests/function.Graph | 1 | false | Edge | EdgeConstraint | +| codeql-go-tests/function.Graph.ShortestPath | 0 | true | Node | NodeConstraint | +| codeql-go-tests/function.Graph.ShortestPath | 1 | true | Edge | EdgeConstraint | +| codeql-go-tests/function.List | 0 | false | T | interface { } | +| codeql-go-tests/function.List.MyLen | 0 | true | U | interface { } | +| codeql-go-tests/function.New | 0 | false | Node | NodeConstraint | +| codeql-go-tests/function.New | 1 | false | Edge | EdgeConstraint | +| codeql-go-tests/function.NodeConstraint | 0 | false | Edge | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 0 | false | _ | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 1 | false | _ | interface { string } | +| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 2 | false | _ | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType | 0 | false | _ | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType | 1 | false | _ | interface { string } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType | 2 | false | _ | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 0 | true | _ | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 1 | true | _ | interface { string } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 2 | true | _ | interface { } | diff --git a/go/ql/test/library-tests/semmle/go/Function/TypeParamType.ql b/go/ql/test/library-tests/semmle/go/Function/TypeParamType.ql index 02dec23d2a86..365f58493f1b 100644 --- a/go/ql/test/library-tests/semmle/go/Function/TypeParamType.ql +++ b/go/ql/test/library-tests/semmle/go/Function/TypeParamType.ql @@ -11,4 +11,5 @@ where // Note that we cannot use the location of `tpt` itself as we currently fail // to extract an object for type parameters for methods on generic structs. exists(ty.getLocation()) -select ty.getQualifiedName(), tpt.getIndex(), tpt.getParamName(), tpt.getConstraint().pp() +select ty.getQualifiedName(), tpt.getIndex(), tpt.isFromReceiver(), tpt.getParamName(), + tpt.getConstraint().pp() From 9c1f5c9be2fb6e9561d014d715e21a263e03084c Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Mon, 13 Jul 2026 12:47:17 +0200 Subject: [PATCH 34/60] Go: Fix dowgrade script formatting --- .../5ff5325d274ae4f86defa195577bc7c1370b72fa/typeparam.ql | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/typeparam.ql b/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/typeparam.ql index 00c17208e6ea..ad7685ac16a2 100644 --- a/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/typeparam.ql +++ b/go/downgrades/5ff5325d274ae4f86defa195577bc7c1370b72fa/typeparam.ql @@ -10,6 +10,8 @@ class TypeParamParentObject extends @typeparamparentobject { string toString() { none() } } -from TypeParamType tp, string name, CompositeType bound, TypeParamParentObject parent, int idx, boolean is_from_recv +from + TypeParamType tp, string name, CompositeType bound, TypeParamParentObject parent, int idx, + boolean is_from_recv where typeparam(tp, name, bound, parent, idx, is_from_recv) select tp, name, bound, parent, idx From 9efcc49af706de6e342e577b1d8c932d7ca89c42 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Mon, 13 Jul 2026 13:06:15 +0200 Subject: [PATCH 35/60] C++: Update the CWE tag of `cpp/new-free-mismatch` --- cpp/ql/src/Critical/NewFreeMismatch.ql | 2 +- cpp/ql/src/change-notes/2026-07-13-new-free.md | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 cpp/ql/src/change-notes/2026-07-13-new-free.md diff --git a/cpp/ql/src/Critical/NewFreeMismatch.ql b/cpp/ql/src/Critical/NewFreeMismatch.ql index 19b9b197214a..7443ad97731e 100644 --- a/cpp/ql/src/Critical/NewFreeMismatch.ql +++ b/cpp/ql/src/Critical/NewFreeMismatch.ql @@ -8,7 +8,7 @@ * @id cpp/new-free-mismatch * @tags reliability * security - * external/cwe/cwe-401 + * external/cwe/cwe-762 */ import NewDelete diff --git a/cpp/ql/src/change-notes/2026-07-13-new-free.md b/cpp/ql/src/change-notes/2026-07-13-new-free.md new file mode 100644 index 000000000000..aef5a58aa480 --- /dev/null +++ b/cpp/ql/src/change-notes/2026-07-13-new-free.md @@ -0,0 +1,4 @@ +--- +category: queryMetadata +--- +* Added the tag `external/cwe/cwe-762` to `cpp/new-free-mismatch`, and removed the tag `external/cwe/cwe-401`. This better matches the behavior of the query. From e0f0987b81a126d21a91451e2d7a0f4c774ed237 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Mon, 13 Jul 2026 13:24:23 +0200 Subject: [PATCH 36/60] Go: Make `isFromReceiver` and actual `predicate` --- go/ql/lib/semmle/go/Types.qll | 2 +- .../semmle/go/Function/TypeParamType.expected | 66 +++++++++---------- .../semmle/go/Function/TypeParamType.ql | 8 +-- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/go/ql/lib/semmle/go/Types.qll b/go/ql/lib/semmle/go/Types.qll index e1b5c70f0959..1ea5b5c65c1d 100644 --- a/go/ql/lib/semmle/go/Types.qll +++ b/go/ql/lib/semmle/go/Types.qll @@ -396,7 +396,7 @@ class TypeParamType extends @typeparamtype, CompositeType { int getIndex() { typeparam(this, _, _, _, result, _) } /** Holds if this type parameter type is declared as part of a receiver */ - boolean isFromReceiver() { typeparam(this, _, _, _, _, result) } + predicate isFromReceiver() { typeparam(this, _, _, _, _, true) } override string pp() { result = this.getParamName() } diff --git a/go/ql/test/library-tests/semmle/go/Function/TypeParamType.expected b/go/ql/test/library-tests/semmle/go/Function/TypeParamType.expected index 42be6e28ab3b..4addfa50f349 100644 --- a/go/ql/test/library-tests/semmle/go/Function/TypeParamType.expected +++ b/go/ql/test/library-tests/semmle/go/Function/TypeParamType.expected @@ -20,36 +20,36 @@ numberOfTypeParameters | genericFunctions.go:152:6:152:36 | multipleAnonymousTypeParamsType | 3 | | genericFunctions.go:154:51:154:51 | f | 3 | #select -| codeql-go-tests/function.EdgeConstraint | 0 | false | Node | interface { } | -| codeql-go-tests/function.Element | 0 | false | S | interface { } | -| codeql-go-tests/function.GenericFunctionInAnotherFile | 0 | false | T | interface { } | -| codeql-go-tests/function.GenericFunctionOneTypeParam | 0 | false | T | interface { } | -| codeql-go-tests/function.GenericFunctionTwoTypeParams | 0 | false | K | comparable | -| codeql-go-tests/function.GenericFunctionTwoTypeParams | 1 | false | V | interface { int64 \| float64 } | -| codeql-go-tests/function.GenericStruct1 | 0 | false | T | interface { } | -| codeql-go-tests/function.GenericStruct1.f1 | 0 | true | TF1 | interface { } | -| codeql-go-tests/function.GenericStruct1.g1 | 0 | true | TG1 | interface { } | -| codeql-go-tests/function.GenericStruct2 | 0 | false | S | interface { } | -| codeql-go-tests/function.GenericStruct2 | 1 | false | T | interface { } | -| codeql-go-tests/function.GenericStruct2.f2 | 0 | true | SF2 | interface { } | -| codeql-go-tests/function.GenericStruct2.f2 | 1 | true | TF2 | interface { } | -| codeql-go-tests/function.GenericStruct2.g2 | 0 | true | SG2 | interface { } | -| codeql-go-tests/function.GenericStruct2.g2 | 1 | true | TG2 | interface { } | -| codeql-go-tests/function.Graph | 0 | false | Node | NodeConstraint | -| codeql-go-tests/function.Graph | 1 | false | Edge | EdgeConstraint | -| codeql-go-tests/function.Graph.ShortestPath | 0 | true | Node | NodeConstraint | -| codeql-go-tests/function.Graph.ShortestPath | 1 | true | Edge | EdgeConstraint | -| codeql-go-tests/function.List | 0 | false | T | interface { } | -| codeql-go-tests/function.List.MyLen | 0 | true | U | interface { } | -| codeql-go-tests/function.New | 0 | false | Node | NodeConstraint | -| codeql-go-tests/function.New | 1 | false | Edge | EdgeConstraint | -| codeql-go-tests/function.NodeConstraint | 0 | false | Edge | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 0 | false | _ | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 1 | false | _ | interface { string } | -| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 2 | false | _ | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType | 0 | false | _ | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType | 1 | false | _ | interface { string } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType | 2 | false | _ | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 0 | true | _ | interface { } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 1 | true | _ | interface { string } | -| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 2 | true | _ | interface { } | +| codeql-go-tests/function.EdgeConstraint | 0 | | Node | interface { } | +| codeql-go-tests/function.Element | 0 | | S | interface { } | +| codeql-go-tests/function.GenericFunctionInAnotherFile | 0 | | T | interface { } | +| codeql-go-tests/function.GenericFunctionOneTypeParam | 0 | | T | interface { } | +| codeql-go-tests/function.GenericFunctionTwoTypeParams | 0 | | K | comparable | +| codeql-go-tests/function.GenericFunctionTwoTypeParams | 1 | | V | interface { int64 \| float64 } | +| codeql-go-tests/function.GenericStruct1 | 0 | | T | interface { } | +| codeql-go-tests/function.GenericStruct1.f1 | 0 | from receiver | TF1 | interface { } | +| codeql-go-tests/function.GenericStruct1.g1 | 0 | from receiver | TG1 | interface { } | +| codeql-go-tests/function.GenericStruct2 | 0 | | S | interface { } | +| codeql-go-tests/function.GenericStruct2 | 1 | | T | interface { } | +| codeql-go-tests/function.GenericStruct2.f2 | 0 | from receiver | SF2 | interface { } | +| codeql-go-tests/function.GenericStruct2.f2 | 1 | from receiver | TF2 | interface { } | +| codeql-go-tests/function.GenericStruct2.g2 | 0 | from receiver | SG2 | interface { } | +| codeql-go-tests/function.GenericStruct2.g2 | 1 | from receiver | TG2 | interface { } | +| codeql-go-tests/function.Graph | 0 | | Node | NodeConstraint | +| codeql-go-tests/function.Graph | 1 | | Edge | EdgeConstraint | +| codeql-go-tests/function.Graph.ShortestPath | 0 | from receiver | Node | NodeConstraint | +| codeql-go-tests/function.Graph.ShortestPath | 1 | from receiver | Edge | EdgeConstraint | +| codeql-go-tests/function.List | 0 | | T | interface { } | +| codeql-go-tests/function.List.MyLen | 0 | from receiver | U | interface { } | +| codeql-go-tests/function.New | 0 | | Node | NodeConstraint | +| codeql-go-tests/function.New | 1 | | Edge | EdgeConstraint | +| codeql-go-tests/function.NodeConstraint | 0 | | Edge | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 0 | | _ | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 1 | | _ | interface { string } | +| codeql-go-tests/function.multipleAnonymousTypeParamsFunc | 2 | | _ | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType | 0 | | _ | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType | 1 | | _ | interface { string } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType | 2 | | _ | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 0 | from receiver | _ | interface { } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 1 | from receiver | _ | interface { string } | +| codeql-go-tests/function.multipleAnonymousTypeParamsType.f | 2 | from receiver | _ | interface { } | diff --git a/go/ql/test/library-tests/semmle/go/Function/TypeParamType.ql b/go/ql/test/library-tests/semmle/go/Function/TypeParamType.ql index 365f58493f1b..323defa04934 100644 --- a/go/ql/test/library-tests/semmle/go/Function/TypeParamType.ql +++ b/go/ql/test/library-tests/semmle/go/Function/TypeParamType.ql @@ -5,11 +5,11 @@ query predicate numberOfTypeParameters(TypeParamParentEntity parent, int n) { n = strictcount(TypeParamType tpt | tpt.getParent() = parent) } -from TypeParamType tpt, TypeParamParentEntity ty +from TypeParamType tpt, TypeParamParentEntity ty, string fr where ty = tpt.getParent() and // Note that we cannot use the location of `tpt` itself as we currently fail // to extract an object for type parameters for methods on generic structs. - exists(ty.getLocation()) -select ty.getQualifiedName(), tpt.getIndex(), tpt.isFromReceiver(), tpt.getParamName(), - tpt.getConstraint().pp() + exists(ty.getLocation()) and + if tpt.isFromReceiver() then fr = "from receiver" else fr = "" +select ty.getQualifiedName(), tpt.getIndex(), fr, tpt.getParamName(), tpt.getConstraint().pp() From a8885aeebd465ec65db7913b452d44a8179f9d00 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Mon, 13 Jul 2026 15:18:38 +0200 Subject: [PATCH 37/60] C#: Remove the HttpRequest.RawUrl barrier model. --- csharp/ql/lib/ext/System.Web.model.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/csharp/ql/lib/ext/System.Web.model.yml b/csharp/ql/lib/ext/System.Web.model.yml index 63c539fbe5ec..9e24158bc092 100644 --- a/csharp/ql/lib/ext/System.Web.model.yml +++ b/csharp/ql/lib/ext/System.Web.model.yml @@ -1,10 +1,4 @@ extensions: - - addsTo: - pack: codeql/csharp-all - extensible: barrierModel - data: - # The RawUrl property is considered to be safe for URL redirects - - ["System.Web", "HttpRequest", False, "get_RawUrl", "()", "", "ReturnValue", "url-redirection", "manual"] - addsTo: pack: codeql/csharp-all extensible: sinkModel From 03e44f548fcc56c8ccf07cf4b489e8fb63f10c57 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Mon, 13 Jul 2026 15:19:04 +0200 Subject: [PATCH 38/60] C#: Update test and expected output. --- .../Security Features/CWE-601/UrlRedirect/UrlRedirect.cs | 4 ++-- .../CWE-601/UrlRedirect/UrlRedirect.expected | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.cs b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.cs index 498b004505e3..efb190365337 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.cs +++ b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.cs @@ -38,8 +38,8 @@ public void ProcessRequest(HttpContext ctx) ctx.Response.AddHeader("Location", ctx.Request.QueryString["page"]); // $ Alert=r3 Alert=r3 ctx.Response.AppendHeader("Location", ctx.Request.QueryString["page"]); // $ Alert=r4 Alert=r4 - // GOOD: Redirecting to the RawUrl only reloads the current Url - ctx.Response.Redirect(ctx.Request.RawUrl); + // BAD: The RawUrl contains the un-normalized request line. + ctx.Response.Redirect(ctx.Request.RawUrl); // $ Alert // GOOD: The attacker can only control the parameters, not the location ctx.Response.Redirect("foo.asp?param=" + url); diff --git a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.expected b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.expected index e7fced7fde3f..b5ff8e88234a 100644 --- a/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.expected +++ b/csharp/ql/test/query-tests/Security Features/CWE-601/UrlRedirect/UrlRedirect.expected @@ -3,6 +3,7 @@ | UrlRedirect.cs:13:31:13:61 | access to indexer | UrlRedirect.cs:13:31:13:53 | access to property QueryString : NameValueCollection | UrlRedirect.cs:13:31:13:61 | access to indexer | Untrusted URL redirection due to $@. | UrlRedirect.cs:13:31:13:53 | access to property QueryString | user-provided value | | UrlRedirect.cs:38:44:38:74 | access to indexer | UrlRedirect.cs:38:44:38:66 | access to property QueryString : NameValueCollection | UrlRedirect.cs:38:44:38:74 | access to indexer | Untrusted URL redirection due to $@. | UrlRedirect.cs:38:44:38:66 | access to property QueryString | user-provided value | | UrlRedirect.cs:39:47:39:77 | access to indexer | UrlRedirect.cs:39:47:39:69 | access to property QueryString : NameValueCollection | UrlRedirect.cs:39:47:39:77 | access to indexer | Untrusted URL redirection due to $@. | UrlRedirect.cs:39:47:39:69 | access to property QueryString | user-provided value | +| UrlRedirect.cs:42:31:42:48 | access to property RawUrl | UrlRedirect.cs:42:31:42:48 | access to property RawUrl | UrlRedirect.cs:42:31:42:48 | access to property RawUrl | Untrusted URL redirection due to $@. | UrlRedirect.cs:42:31:42:48 | access to property RawUrl | user-provided value | | UrlRedirect.cs:48:29:48:31 | access to local variable url | UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:48:29:48:31 | access to local variable url | Untrusted URL redirection due to $@. | UrlRedirect.cs:23:22:23:44 | access to property QueryString | user-provided value | | UrlRedirect.cs:64:31:64:52 | $"..." | UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:64:31:64:52 | $"..." | Untrusted URL redirection due to $@. | UrlRedirect.cs:23:22:23:44 | access to property QueryString | user-provided value | | UrlRedirect.cs:70:31:70:69 | call to method Format | UrlRedirect.cs:23:22:23:44 | access to property QueryString : NameValueCollection | UrlRedirect.cs:70:31:70:69 | call to method Format | Untrusted URL redirection due to $@. | UrlRedirect.cs:23:22:23:44 | access to property QueryString | user-provided value | @@ -66,6 +67,7 @@ nodes | UrlRedirect.cs:38:44:38:74 | access to indexer | semmle.label | access to indexer | | UrlRedirect.cs:39:47:39:69 | access to property QueryString : NameValueCollection | semmle.label | access to property QueryString : NameValueCollection | | UrlRedirect.cs:39:47:39:77 | access to indexer | semmle.label | access to indexer | +| UrlRedirect.cs:42:31:42:48 | access to property RawUrl | semmle.label | access to property RawUrl | | UrlRedirect.cs:48:29:48:31 | access to local variable url | semmle.label | access to local variable url | | UrlRedirect.cs:64:31:64:52 | $"..." | semmle.label | $"..." | | UrlRedirect.cs:70:31:70:69 | call to method Format | semmle.label | call to method Format | From ae06b778e5203ee3773b987c34bfa151bce6727a Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Mon, 13 Jul 2026 15:29:18 +0200 Subject: [PATCH 39/60] C#: Add change-note. --- csharp/ql/src/change-notes/2026-07-13-CWE-601.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 csharp/ql/src/change-notes/2026-07-13-CWE-601.md diff --git a/csharp/ql/src/change-notes/2026-07-13-CWE-601.md b/csharp/ql/src/change-notes/2026-07-13-CWE-601.md new file mode 100644 index 000000000000..237ee562a529 --- /dev/null +++ b/csharp/ql/src/change-notes/2026-07-13-CWE-601.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* `System.Web.HttpRequest.RawUrl` is no longer treated as a sanitizer for `cs/web/unvalidated-url-redirection`, since it contains the un-normalized request line. This may lead to more results. From 2d0095826b8082e43b613c08779119dffe18b460 Mon Sep 17 00:00:00 2001 From: Owen Mansel-Chan Date: Mon, 13 Jul 2026 19:40:31 +0100 Subject: [PATCH 40/60] Address review comment --- .../code/java/security/PathSanitizer.qll | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll index 7c428a6691cd..b4344c50cea3 100644 --- a/java/ql/lib/semmle/code/java/security/PathSanitizer.qll +++ b/java/ql/lib/semmle/code/java/security/PathSanitizer.qll @@ -108,17 +108,19 @@ private class FileGetNameCall extends MethodCall { */ private predicate dotDotCheckGuard(Guard g, Expr e, boolean branch) { pathTraversalGuard(g, e, branch) and - ( - exists(Guard previousGuard | - previousGuard.(AllowedPrefixGuard).controls(g.getBasicBlock(), true) - or - previousGuard.(BlockListGuard).controls(g.getBasicBlock(), false) - ) + exists(Guard previousGuard | + previousGuard.(AllowedPrefixGuard).controls(g.getBasicBlock(), true) or - // `File.getName` strips any directory prefix, returning only the final path - // component. The only remaining path traversal risk is when that component is - // itself `..`, so a check for `..` components completes the sanitization. - exists(FileGetNameCall getName | localTaintFlowToPathGuard(getName, g)) + previousGuard.(BlockListGuard).controls(g.getBasicBlock(), false) + ) + or + // `File.getName` strips any directory prefix, returning only the final path + // component. The only remaining path traversal risk is when that component is + // itself `..`, so a check for `..` components on the result of `getName` + // completes the sanitization. + exists(FileGetNameCall getName | + pathTraversalGuard(g, getName, branch) and + TaintTracking::localExprTaint(getName, e) ) } From 09da46d8bd583d289ee9559deafbfbc339810b19 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Tue, 14 Jul 2026 10:23:43 +0200 Subject: [PATCH 41/60] Go: Address review comments --- go/extractor/extractor.go | 18 +++++++++--------- .../typeparam.ql | 8 +++++++- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/go/extractor/extractor.go b/go/extractor/extractor.go index f421688e52ad..8c2178376141 100644 --- a/go/extractor/extractor.go +++ b/go/extractor/extractor.go @@ -2010,11 +2010,9 @@ func extractTypeParamDecls(tw *trap.Writer, fields *ast.FieldList, parent trap.L } func populateTypeParamParentsFromFunction(funcObj *types.Func) { - recvTypeParams := funcObj.Type().(*types.Signature).RecvTypeParams() - populateTypeParamParents(recvTypeParams, funcObj, true) - typeParams := funcObj.Type().(*types.Signature).TypeParams() - populateTypeParamParents(typeParams, funcObj, false) - + signature := funcObj.Type().(*types.Signature) + populateTypeParamParents(signature.RecvTypeParams(), funcObj, true) + populateTypeParamParents(signature.TypeParams(), funcObj, false) } // populateTypeParamParents sets `parent` as the parent of the elements of `typeparams` @@ -2053,12 +2051,14 @@ func getTypeParamParentLabel(tw *trap.Writer, tp *types.TypeParam) (trap.Label, return parentlbl, entry.isFromReceiver } -func setTypeParamParent(tp *types.TypeParam, newobj types.Object, isFromReceiver bool) { +func setTypeParamParent(tp *types.TypeParam, parent types.Object, isFromReceiver bool) { entry, exists := typeParamParent[tp] + newEntry := typeParamParentEntry{parent, isFromReceiver} if !exists { - typeParamParent[tp] = typeParamParentEntry{newobj, isFromReceiver} - } else if entry.parent != newobj { - log.Fatalf("Parent of type parameter '%s %s' being set to a different value: '%s' vs '%s'", tp.String(), tp.Constraint().String(), entry.parent, newobj) + typeParamParent[tp] = newEntry + } else if entry != newEntry { + log.Fatalf("Parent of type parameter '%s %s' being set to a different value: {'%s', %t}' vs {'%s', %t}", + tp.String(), tp.Constraint().String(), entry.parent, entry.isFromReceiver, parent, isFromReceiver) } } diff --git a/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/typeparam.ql b/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/typeparam.ql index 041c33577f75..05ceab703446 100644 --- a/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/typeparam.ql +++ b/go/ql/lib/upgrades/b1341734d6870b105e5c9d168ce7dec25d7f72d0/typeparam.ql @@ -10,6 +10,12 @@ class TypeParamParentObject extends @typeparamparentobject { string toString() { none() } } +// In Go 1.26 and below, a type parameter is from a receiver exactly when its +// parent is a method. +boolean isFromReceiver(TypeParamParentObject parent) { + if methodreceivers(parent, _) then result = true else result = false +} + from TypeParamType tp, string name, CompositeType bound, TypeParamParentObject parent, int idx where typeparam(tp, name, bound, parent, idx) -select tp, name, bound, parent, idx, false +select tp, name, bound, parent, idx, isFromReceiver(parent) From 3e2daf2258da21c31f6a657295de20c4da0a9708 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Jul 2026 14:12:30 +0000 Subject: [PATCH 42/60] Support building swift-syntax-rs on macOS --- MODULE.bazel | 34 +++---- unified/swift-syntax-rs/BUILD.bazel | 53 ++++++----- unified/swift-syntax-rs/README.md | 14 ++- unified/swift-syntax-rs/build.rs | 2 +- unified/swift-syntax-rs/swift/Package.swift | 5 ++ unified/swift-syntax-rs/xcode_transition.bzl | 94 ++++++++++++++++++++ 6 files changed, 161 insertions(+), 41 deletions(-) create mode 100644 unified/swift-syntax-rs/xcode_transition.bzl diff --git a/MODULE.bazel b/MODULE.bazel index aa1f6555e0ca..4c9e3316c018 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -33,6 +33,11 @@ bazel_dep(name = "googletest", version = "1.17.0.bcr.2") bazel_dep(name = "rules_rust", version = "0.69.0") bazel_dep(name = "rules_swift", version = "4.0.0-rc4") bazel_dep(name = "swift-syntax", version = "602.0.0.bcr.2") + +# Needed so we can `use_repo` `local_config_xcode` and +# `local_config_apple_cc_toolchains` below (referenced by the per-target +# Xcode-config transition in `unified/swift-syntax-rs/xcode_transition.bzl`). +bazel_dep(name = "apple_support", version = "2.6.1") bazel_dep(name = "zstd", version = "1.5.7.bcr.1") bazel_dep(name = "buildifier_prebuilt", version = "6.4.0", dev_dependency = True) @@ -221,22 +226,11 @@ use_repo( "swift-resource-dir-macos", ) -# Hermetic Swift toolchain (from swift.org) for building the `swift-syntax` -# based Rust wrapper in `unified/swift-syntax-rs`. This is the official -# `rules_swift` standalone-toolchain extension and is independent of the -# patched prebuilt toolchain wired up via `swift_deps` above. -# -# Bazel cannot auto-select between Linux distributions, so the toolchain is -# registered explicitly for the distribution our CI runs on (ubuntu24.04, -# x86_64). Add further platforms here if CI grows to cover them. -# -# We register the `exec` toolchain (normal host compilation, targeting -# linux/x86_64) rather than the `embedded` one, which targets `os:none` -# (Embedded Swift) and would not match a normal host build. -# -# The Swift version is read from `unified/swift-syntax-rs/.swift-version`, which -# is the single source of truth shared with the local `cargo` build (via -# swiftly / any Swift install) and `swift/Package.swift`. +# Swift toolchain for building `unified/swift-syntax-rs`. On Linux we register +# a hermetic swift.org toolchain as the exec toolchain; on macOS `rules_swift` +# auto-registers `xcode_swift_toolchain` (host Xcode + OS-provided Swift +# runtime), which is not hermetic. Version comes from +# `unified/swift-syntax-rs/.swift-version`. swift = use_extension("@rules_swift//swift:extensions.bzl", "swift") swift.toolchain( name = "swift_toolchain", @@ -252,6 +246,14 @@ register_toolchains( "@swift_toolchain//:swift_toolchain_exec_ubuntu24.04", ) +# `apple_support`'s xcode_config and CC toolchains, needed by the Xcode +# transition in `unified/swift-syntax-rs/xcode_transition.bzl`. +xcode_configure = use_extension("@apple_support//xcode:xcode_configure.bzl", "xcode_configure_extension") +use_repo(xcode_configure, "local_config_xcode") + +apple_cc_configure = use_extension("@apple_support//crosstool:setup.bzl", "apple_cc_configure_extension") +use_repo(apple_cc_configure, "local_config_apple_cc_toolchains") + node = use_extension("@rules_nodejs//nodejs:extensions.bzl", "node") node.toolchain( name = "nodejs", diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel index 9db40d4db30c..be7d41eaf871 100644 --- a/unified/swift-syntax-rs/BUILD.bazel +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -1,31 +1,36 @@ load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") -load("@rules_swift//swift:swift.bzl", "swift_library") +load(":xcode_transition.bzl", "xcode_transition_swift_library") package(default_visibility = ["//visibility:public"]) -# The pinned Swift version, shared with the local `cargo` build and -# `swift/Package.swift`. Referenced by the `swift.toolchain` extension in -# //:MODULE.bazel via `swift_version_file`. +# Pinned Swift version, shared with `cargo` and `swift/Package.swift`. +# Referenced by `swift.toolchain(swift_version_file = ...)` in //:MODULE.bazel. exports_files([".swift-version"]) -# The Swift FFI shim: wraps swift-syntax and exposes a small C ABI -# (`ssr_parse_json` / `ssr_string_free`). Built with the hermetic Swift -# toolchain registered in //:MODULE.bazel; provides a `CcInfo` that the Rust -# targets below link against. -swift_library( +# Targets in this package require a Swift toolchain (Linux or macOS). +# `select()` gives us OR-of-OSes; other platforms get marked incompatible +# so `bazel build/test //...` skips them cleanly. +_SWIFT_SUPPORTED_PLATFORMS = select({ + "@platforms//os:linux": [], + "@platforms//os:macos": [], + "//conditions:default": ["@platforms//:incompatible"], +}) + +# Swift FFI shim: wraps swift-syntax and exposes a small C ABI. The Rust +# targets below link against its `CcInfo`. +xcode_transition_swift_library( name = "swift_syntax_ffi", srcs = ["swift/Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift"], module_name = "SwiftSyntaxFFI", + target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS, deps = [ "@swift-syntax//:SwiftParser", "@swift-syntax//:SwiftSyntax", ], ) -# Safe Rust bindings on top of the C ABI. `build.rs` is intentionally *not* -# wired up here (no `cargo_build_script`): under Bazel the Swift side is -# provided by `:swift_syntax_ffi`, whereas `build.rs` only exists to build the -# Swift shim in the local `cargo` workflow. +# Safe Rust bindings on top of the C ABI. Under Bazel the Swift side comes +# from `:swift_syntax_ffi`; `build.rs` is only used by the `cargo` workflow. rust_library( name = "swift_syntax_rs", srcs = glob( @@ -33,6 +38,7 @@ rust_library( exclude = ["src/main.rs"], ), edition = "2024", + target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS, deps = [ ":swift_syntax_ffi", ], @@ -41,13 +47,16 @@ rust_library( rust_binary( name = "swift-syntax-parse", srcs = ["src/main.rs"], - # The Swift toolchain propagates a runfiles-relative RPATH to its runtime - # `.so`s via `swift_syntax_ffi`'s `CcInfo`, but (unlike `swift_binary`) - # `rust_binary` does not copy those libraries into runfiles. Add the - # toolchain files as `data` so the runtime is found at execution time. - # (rules_swift does not yet support statically linking the runtime.) - data = ["@swift_toolchain_ubuntu24.04//:files"], + # `rust_binary` doesn't copy the Swift runtime into runfiles the way + # `swift_binary` does. On Linux, ship the standalone toolchain's runtime; + # on macOS the OS provides it at `/usr/lib/swift` (rpath'd by + # `xcode_swift_toolchain`). + data = select({ + "@platforms//os:macos": [], + "@platforms//os:linux": ["@swift_toolchain_ubuntu24.04//:files"], + }), edition = "2024", + target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS, deps = [":swift_syntax_rs"], ) @@ -55,6 +64,10 @@ rust_test( name = "swift_syntax_rs_test", size = "small", crate = ":swift_syntax_rs", - data = ["@swift_toolchain_ubuntu24.04//:files"], + data = select({ + "@platforms//os:macos": [], + "@platforms//os:linux": ["@swift_toolchain_ubuntu24.04//:files"], + }), edition = "2024", + target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS, ) diff --git a/unified/swift-syntax-rs/README.md b/unified/swift-syntax-rs/README.md index a2d66495a541..6a714137d8c7 100644 --- a/unified/swift-syntax-rs/README.md +++ b/unified/swift-syntax-rs/README.md @@ -144,10 +144,16 @@ Requirements: - **`clang`** must be installed on the runner. `rules_swift` requires the Bazel CC toolchain to use clang; the repo's `.bazelrc` already sets `--repo_env=CC=clang`, so no extra flags are needed. -- The registered Swift toolchain currently targets **ubuntu24.04 / x86_64** - only (Bazel cannot auto-select between Linux distributions). Add more - platforms in `MODULE.bazel` (`swift.toolchain` + `register_toolchains`) if CI - grows to cover them. +- The registered Swift toolchains cover **ubuntu24.04 / x86_64** and + **macOS / `xcode`** (Apple Silicon and Intel). Bazel selects the toolchain + matching the host. Targets are marked `target_compatible_with` these two + OSes, so on Windows Bazel skips them cleanly. +- **macOS only:** the Swift toolchain comes from the host Xcode installation + (`rules_swift` auto-registers `xcode_swift_toolchain`), which also needs + Xcode's CC toolchain and xcode_config; these are applied to the Swift + target via an incoming-edge Starlark transition (see + [`xcode_transition.bzl`](xcode_transition.bzl)), so other targets on macOS + keep using Bazel's default CC toolchain. The Swift compiler version is read from [`.swift-version`](.swift-version) by both the Bazel toolchain (`swift.toolchain(swift_version_file = …)`) and the diff --git a/unified/swift-syntax-rs/build.rs b/unified/swift-syntax-rs/build.rs index 0adb140140dc..3cd2e2858742 100644 --- a/unified/swift-syntax-rs/build.rs +++ b/unified/swift-syntax-rs/build.rs @@ -73,7 +73,7 @@ fn swift_runtime_dir() -> Option { let close = value_start.find('"')?; let resource_path = &value_start[..close]; - Some(PathBuf::from(resource_path).join("linux")) + Some(PathBuf::from(resource_path).join(if cfg!(target_os = "macos") { "macosx" } else { "linux" })) } /// The `swift` driver to invoke: `$SWIFT` if set, otherwise `swift` from `PATH`. diff --git a/unified/swift-syntax-rs/swift/Package.swift b/unified/swift-syntax-rs/swift/Package.swift index 1b0111364ce7..fcd9fcc07bca 100644 --- a/unified/swift-syntax-rs/swift/Package.swift +++ b/unified/swift-syntax-rs/swift/Package.swift @@ -3,6 +3,11 @@ import PackageDescription let package = Package( name: "SwiftSyntaxFFI", + platforms: [ + // swift-syntax 602 requires macOS 10.15; declare it explicitly + // rather than relying on the swift-tools-version default (10.13). + .macOS(.v10_15), + ], products: [ // Dynamic library so the produced .so records its dependency on the // Swift runtime libraries, keeping the Rust link step simple. diff --git a/unified/swift-syntax-rs/xcode_transition.bzl b/unified/swift-syntax-rs/xcode_transition.bzl new file mode 100644 index 000000000000..c6f293a0452c --- /dev/null +++ b/unified/swift-syntax-rs/xcode_transition.bzl @@ -0,0 +1,94 @@ +"""Per-target Xcode configuration for `//unified/swift-syntax-rs` on macOS. + +`rules_swift`'s auto-registered `xcode_swift_toolchain` reads +`cc_toolchain.target_gnu_system_name`, which is literally "local" on +Bazel's built-in `local_config_cc`. To make it usable we need: + +- `--xcode_version_config=@local_config_xcode//:host_xcodes` — selects + `apple_support`'s xcode_config (matches `rules_swift`'s `system_sdk` keys). +- `--extra_toolchains=@local_config_apple_cc_toolchains//:all` — forces + `apple_support`'s CC toolchain ahead of `local_config_cc`. + +Applied via an incoming-edge Starlark transition on the `swift_library` +target only (via the `xcode_transition_swift_library` macro), so downstream +Rust targets and the rest of the repo stay on the default CC toolchain and +the `@local_config_*` repos are not materialized otherwise. No-op off macOS. +""" + +load("@rules_cc//cc/common:cc_info.bzl", "CcInfo") +load("@rules_swift//swift:swift.bzl", "swift_library") +load("//misc/bazel:os.bzl", "os_select") + +_XCODE_VERSION_CONFIG = "//command_line_option:xcode_version_config" +_EXTRA_TOOLCHAINS = "//command_line_option:extra_toolchains" + +def _transition_impl(settings, attr): + if attr.os != "macos": + # Preserve inputs so the configuration is identical to the incoming one. + return { + _XCODE_VERSION_CONFIG: settings[_XCODE_VERSION_CONFIG], + _EXTRA_TOOLCHAINS: settings[_EXTRA_TOOLCHAINS], + } + return { + _XCODE_VERSION_CONFIG: "@local_config_xcode//:host_xcodes", + _EXTRA_TOOLCHAINS: ( + list(settings[_EXTRA_TOOLCHAINS]) + + ["@local_config_apple_cc_toolchains//:all"] + ), + } + +_xcode_transition = transition( + implementation = _transition_impl, + inputs = [_XCODE_VERSION_CONFIG, _EXTRA_TOOLCHAINS], + outputs = [_XCODE_VERSION_CONFIG, _EXTRA_TOOLCHAINS], +) + +def _wrapper_impl(ctx): + src = ctx.attr.actual[0] + # Forward the providers a downstream `rust_*` target reads from `deps`: + # `DefaultInfo`, `CcInfo` (linking info), and `OutputGroupInfo`. + providers = [src[DefaultInfo]] + for p in (CcInfo, OutputGroupInfo): + if p in src: + providers.append(src[p]) + return providers + +_xcode_transition_swift_library_rule = rule( + implementation = _wrapper_impl, + attrs = { + "actual": attr.label( + mandatory = True, + cfg = _xcode_transition, + providers = [CcInfo], + ), + "os": attr.string(mandatory = True), + "_allowlist_function_transition": attr.label( + default = "@bazel_tools//tools/allowlists/function_transition_allowlist", + ), + }, +) + +def xcode_transition_swift_library(name, visibility = None, tags = None, target_compatible_with = None, **kwargs): + """`swift_library` wrapped in the macOS Xcode-config transition. + + Emits a private inner `_impl_` `swift_library` (tagged `manual`) + and a public `name` that applies the transition on macOS and forwards + the inner target's providers. Downstream `rust_*` targets depend on + `name` as usual; only the `swift_library` sub-graph flips toolchain. + """ + inner_name = "_impl_%s" % name + swift_library( + name = inner_name, + visibility = ["//visibility:private"], + tags = (tags or []) + ["manual"], + target_compatible_with = target_compatible_with, + **kwargs + ) + _xcode_transition_swift_library_rule( + name = name, + visibility = visibility, + tags = tags, + target_compatible_with = target_compatible_with, + actual = ":" + inner_name, + os = os_select(linux = "linux", macos = "macos", default = "other"), + ) From b15c243ecad2acab4cb1ee1e2c33e4a7c2f15fa2 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 15 Jul 2026 08:36:49 +0200 Subject: [PATCH 43/60] C#: Exclude cs/useless-assignment-to-local from the code-quality suite. --- csharp/ql/src/codeql-suites/csharp-code-quality.qls | 3 +++ 1 file changed, 3 insertions(+) diff --git a/csharp/ql/src/codeql-suites/csharp-code-quality.qls b/csharp/ql/src/codeql-suites/csharp-code-quality.qls index 2074f9378cfd..26c7e7437ee5 100644 --- a/csharp/ql/src/codeql-suites/csharp-code-quality.qls +++ b/csharp/ql/src/codeql-suites/csharp-code-quality.qls @@ -1,3 +1,6 @@ - queries: . - apply: code-quality-selectors.yml from: codeql/suite-helpers +- exclude: + id: + - cs/useless-assignment-to-local From 2395053ed6e9c2a14b06986276e53f8aedca9238 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 15 Jul 2026 08:54:56 +0200 Subject: [PATCH 44/60] C#: Update integration test expected output. --- .../posix/query-suite/csharp-code-quality.qls.expected | 1 - 1 file changed, 1 deletion(-) diff --git a/csharp/ql/integration-tests/posix/query-suite/csharp-code-quality.qls.expected b/csharp/ql/integration-tests/posix/query-suite/csharp-code-quality.qls.expected index 893eaeb75607..b944b848df8e 100644 --- a/csharp/ql/integration-tests/posix/query-suite/csharp-code-quality.qls.expected +++ b/csharp/ql/integration-tests/posix/query-suite/csharp-code-quality.qls.expected @@ -22,7 +22,6 @@ ql/csharp/ql/src/Concurrency/FutileSyncOnField.ql ql/csharp/ql/src/Concurrency/LockOrder.ql ql/csharp/ql/src/Concurrency/LockThis.ql ql/csharp/ql/src/Concurrency/LockedWait.ql -ql/csharp/ql/src/Dead Code/DeadStoreOfLocal.ql ql/csharp/ql/src/Language Abuse/CastThisToTypeParameter.ql ql/csharp/ql/src/Language Abuse/CatchOfGenericException.ql ql/csharp/ql/src/Language Abuse/DubiousDowncastOfThis.ql From cb3f8a8394825fb42b6e6b7f5e98033e4d6b04b9 Mon Sep 17 00:00:00 2001 From: Michael Nebel Date: Wed, 15 Jul 2026 09:03:34 +0200 Subject: [PATCH 45/60] C#: Add change-note. --- .../2026-07-15-code-quality-useless-assignment.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 csharp/ql/src/change-notes/2026-07-15-code-quality-useless-assignment.md diff --git a/csharp/ql/src/change-notes/2026-07-15-code-quality-useless-assignment.md b/csharp/ql/src/change-notes/2026-07-15-code-quality-useless-assignment.md new file mode 100644 index 000000000000..d1fa51ea3bfd --- /dev/null +++ b/csharp/ql/src/change-notes/2026-07-15-code-quality-useless-assignment.md @@ -0,0 +1,4 @@ +--- +category: queryMetadata +--- +* The query `cs/useless-assignment-to-local` has been removed from the `code-quality` suite, but it remains in the `code-quality-extended` suite. From 574ef4d1aca5ddce03cb4167a8e524b74077f357 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Wed, 15 Jul 2026 10:17:58 +0200 Subject: [PATCH 46/60] Kotlin: Support Kotlin 2.4.10 --- docs/codeql/reusables/supported-versions-compilers.rst | 2 +- java/kotlin-extractor/dev/wrapper.py | 2 +- .../diagnostics/kotlin-version-too-new/diagnostics.expected | 2 +- java/ql/lib/change-notes/2026-07-15-kotlin-2.4.10.md | 4 ++++ 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 java/ql/lib/change-notes/2026-07-15-kotlin-2.4.10.md diff --git a/docs/codeql/reusables/supported-versions-compilers.rst b/docs/codeql/reusables/supported-versions-compilers.rst index 7a88dd035c2a..69def9ffeb19 100644 --- a/docs/codeql/reusables/supported-versions-compilers.rst +++ b/docs/codeql/reusables/supported-versions-compilers.rst @@ -21,7 +21,7 @@ Java,"Java 7 to 26 [6]_","javac (OpenJDK and Oracle JDK), Eclipse compiler for Java (ECJ) [7]_",``.java`` - Kotlin,"Kotlin 1.8.0 to 2.4.0\ *x*","kotlinc",``.kt`` + Kotlin,"Kotlin 1.8.0 to 2.4.1\ *x*","kotlinc",``.kt`` JavaScript,ECMAScript 2022 or lower,Not applicable,"``.js``, ``.jsx``, ``.mjs``, ``.es``, ``.es6``, ``.htm``, ``.html``, ``.xhtm``, ``.xhtml``, ``.vue``, ``.hbs``, ``.ejs``, ``.njk``, ``.json``, ``.yaml``, ``.yml``, ``.raml``, ``.xml`` [8]_" Python [9]_,"2.7, 3.5, 3.6, 3.7, 3.8, 3.9, 3.10, 3.11, 3.12, 3.13",Not applicable,``.py`` Ruby [10]_,"up to 3.3",Not applicable,"``.rb``, ``.erb``, ``.gemspec``, ``Gemfile``" diff --git a/java/kotlin-extractor/dev/wrapper.py b/java/kotlin-extractor/dev/wrapper.py index 34b9d6b9425e..1b29de23f766 100755 --- a/java/kotlin-extractor/dev/wrapper.py +++ b/java/kotlin-extractor/dev/wrapper.py @@ -27,7 +27,7 @@ import io import os -DEFAULT_VERSION = "2.4.0" +DEFAULT_VERSION = "2.4.10" def options(): diff --git a/java/ql/integration-tests/kotlin/all-platforms/diagnostics/kotlin-version-too-new/diagnostics.expected b/java/ql/integration-tests/kotlin/all-platforms/diagnostics/kotlin-version-too-new/diagnostics.expected index 33ef093cb9a2..09429027c5d6 100644 --- a/java/ql/integration-tests/kotlin/all-platforms/diagnostics/kotlin-version-too-new/diagnostics.expected +++ b/java/ql/integration-tests/kotlin/all-platforms/diagnostics/kotlin-version-too-new/diagnostics.expected @@ -1,5 +1,5 @@ { - "markdownMessage": "The Kotlin version installed (`999.999.999`) is too recent for this version of CodeQL. Install a version lower than 2.4.10.", + "markdownMessage": "The Kotlin version installed (`999.999.999`) is too recent for this version of CodeQL. Install a version lower than 2.4.20.", "severity": "error", "source": { "extractorName": "java", diff --git a/java/ql/lib/change-notes/2026-07-15-kotlin-2.4.10.md b/java/ql/lib/change-notes/2026-07-15-kotlin-2.4.10.md new file mode 100644 index 000000000000..43aa417d9593 --- /dev/null +++ b/java/ql/lib/change-notes/2026-07-15-kotlin-2.4.10.md @@ -0,0 +1,4 @@ +--- +category: minorAnalysis +--- +* Kotlin versions up to 2.4.10 are now supported. From 1577d82ebd9d4f528ebf5ca579b1af76956dac4f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 14 Jul 2026 14:45:46 +0000 Subject: [PATCH 47/60] Upgrade swift-syntax-rs to swift-syntax 603.0.2 / Swift 6.3.2 --- MODULE.bazel | 2 +- unified/swift-syntax-rs/.swift-version | 2 +- unified/swift-syntax-rs/README.md | 2 +- unified/swift-syntax-rs/swift/Package.resolved | 6 +++--- unified/swift-syntax-rs/swift/Package.swift | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 4c9e3316c018..37a71583198f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -32,7 +32,7 @@ bazel_dep(name = "rules_dotnet", version = "0.21.5-codeql.1") bazel_dep(name = "googletest", version = "1.17.0.bcr.2") bazel_dep(name = "rules_rust", version = "0.69.0") bazel_dep(name = "rules_swift", version = "4.0.0-rc4") -bazel_dep(name = "swift-syntax", version = "602.0.0.bcr.2") +bazel_dep(name = "swift-syntax", version = "603.0.2") # Needed so we can `use_repo` `local_config_xcode` and # `local_config_apple_cc_toolchains` below (referenced by the per-target diff --git a/unified/swift-syntax-rs/.swift-version b/unified/swift-syntax-rs/.swift-version index 42cc526d6ca7..91e4a9f26224 100644 --- a/unified/swift-syntax-rs/.swift-version +++ b/unified/swift-syntax-rs/.swift-version @@ -1 +1 @@ -6.2.4 +6.3.2 diff --git a/unified/swift-syntax-rs/README.md b/unified/swift-syntax-rs/README.md index 6a714137d8c7..9b14d4026f37 100644 --- a/unified/swift-syntax-rs/README.md +++ b/unified/swift-syntax-rs/README.md @@ -98,7 +98,7 @@ The build does not depend on any particular version manager. You need: - **Rust** — pinned to `1.88` by the repo-root [`rust-toolchain.toml`](../../rust-toolchain.toml), which `rustup` picks up automatically. - **Swift** — pinned to the version in [`.swift-version`](.swift-version) - (currently `6.2.4`), used to build `swift-syntax` `602.0.0`. Install it any way + (currently `6.3.2`), used to build `swift-syntax` `603.0.2`. Install it any way you like — [swift.org](https://www.swift.org/install/) or [swiftly](https://www.swift.org/swiftly/) (which reads `.swift-version`), or a system package. Just make sure `swift` is on your `PATH` (or point `build.rs` diff --git a/unified/swift-syntax-rs/swift/Package.resolved b/unified/swift-syntax-rs/swift/Package.resolved index 9cebf34ec543..dafd46e961b5 100644 --- a/unified/swift-syntax-rs/swift/Package.resolved +++ b/unified/swift-syntax-rs/swift/Package.resolved @@ -1,13 +1,13 @@ { - "originHash" : "ad84ad340ee01aa6e38b877dd29e38fa9cb40603dfdefa4e6a69e27f2db208d1", + "originHash" : "169957f8fbd882866f8f9f1c68cde813dca3560577939a8ffec366e3d77548e4", "pins" : [ { "identity" : "swift-syntax", "kind" : "remoteSourceControl", "location" : "https://github.com/swiftlang/swift-syntax.git", "state" : { - "revision" : "4799286537280063c85a32f09884cfbca301b1a1", - "version" : "602.0.0" + "revision" : "79e4b74a295b6eb74a8b585e3a39d29e70c1dbd1", + "version" : "603.0.2" } } ], diff --git a/unified/swift-syntax-rs/swift/Package.swift b/unified/swift-syntax-rs/swift/Package.swift index fcd9fcc07bca..a58b7c1a479b 100644 --- a/unified/swift-syntax-rs/swift/Package.swift +++ b/unified/swift-syntax-rs/swift/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "SwiftSyntaxFFI", platforms: [ - // swift-syntax 602 requires macOS 10.15; declare it explicitly + // swift-syntax 603 requires macOS 10.15; declare it explicitly // rather than relying on the swift-tools-version default (10.13). .macOS(.v10_15), ], @@ -20,7 +20,7 @@ let package = Package( dependencies: [ .package( url: "https://github.com/swiftlang/swift-syntax.git", - exact: "602.0.0" + exact: "603.0.2" ) ], targets: [ From a701922dbd0678aba6cc091b543873c5beaef448 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Tue, 14 Jul 2026 21:58:36 +0200 Subject: [PATCH 48/60] Swift: Update to Swift 6.3.3 --- swift/ql/lib/change-notes/2026-07-15-swift-6.3.3.md | 4 ++++ swift/third_party/resources/resource-dir-linux.zip | 4 ++-- swift/third_party/resources/resource-dir-macos.zip | 4 ++-- swift/third_party/resources/swift-prebuilt-linux.tar.zst | 4 ++-- swift/third_party/resources/swift-prebuilt-macos.tar.zst | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) create mode 100644 swift/ql/lib/change-notes/2026-07-15-swift-6.3.3.md diff --git a/swift/ql/lib/change-notes/2026-07-15-swift-6.3.3.md b/swift/ql/lib/change-notes/2026-07-15-swift-6.3.3.md new file mode 100644 index 000000000000..1542f2f68dc5 --- /dev/null +++ b/swift/ql/lib/change-notes/2026-07-15-swift-6.3.3.md @@ -0,0 +1,4 @@ +--- +category: majorAnalysis +--- +* Upgraded to allow analysis of Swift 6.3.3. diff --git a/swift/third_party/resources/resource-dir-linux.zip b/swift/third_party/resources/resource-dir-linux.zip index da93aefcc706..1fbff22aa74c 100644 --- a/swift/third_party/resources/resource-dir-linux.zip +++ b/swift/third_party/resources/resource-dir-linux.zip @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd132a4fb44688913eff72f94110e2745048ceda3354ba199d8338750881e0e5 -size 408312701 +oid sha256:68da3c1158d6682f09644e4557b7bdbe74e3e7110d1a64cb61ec26b3a91d0b0e +size 408306334 diff --git a/swift/third_party/resources/resource-dir-macos.zip b/swift/third_party/resources/resource-dir-macos.zip index 11ac3ddf0d7d..2a606b4771fe 100644 --- a/swift/third_party/resources/resource-dir-macos.zip +++ b/swift/third_party/resources/resource-dir-macos.zip @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd4eaa3a688849279e990da69768da796f6a130ca6e01572d022142ff09e4868 -size 548155566 +oid sha256:da5cbdd44ee4728606f2dc0dd0718e3c7c8748ae1ec8d6261365282d54ba8db9 +size 548170081 diff --git a/swift/third_party/resources/swift-prebuilt-linux.tar.zst b/swift/third_party/resources/swift-prebuilt-linux.tar.zst index 2adf23cda687..95eabd7a2a9d 100644 --- a/swift/third_party/resources/swift-prebuilt-linux.tar.zst +++ b/swift/third_party/resources/swift-prebuilt-linux.tar.zst @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e00f464ad0b793c8e14df844aecbeec5cdd587e8f34a85915593a470dba1beda -size 143508114 +oid sha256:e79d3f333cbbbeb39c226f5ea605f6a88f4f32f83e38b8c0a082d18d9caf095d +size 143468582 diff --git a/swift/third_party/resources/swift-prebuilt-macos.tar.zst b/swift/third_party/resources/swift-prebuilt-macos.tar.zst index f63c93f1caa9..1914d1048a02 100644 --- a/swift/third_party/resources/swift-prebuilt-macos.tar.zst +++ b/swift/third_party/resources/swift-prebuilt-macos.tar.zst @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:212229f7f0545aab03e3034f55d13a665bc568633a5defc72a662c83a06e90cc -size 125133134 +oid sha256:f5fddabc442a5c7664be4f022f09889177f420477060c1199eb714394987ad93 +size 125119625 From 3eb353ecf76a7f61b6193129e64a9c803ecb7605 Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 15 Jul 2026 12:10:50 +0000 Subject: [PATCH 49/60] swift-syntax-rs: Address PR review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BUILD.bazel: require x86_64 on the Linux branch of the compatibility select. The only registered standalone Linux Swift toolchain is x86_64-only, so without the CPU constraint Linux/aarch64 targets stayed "compatible" and then failed toolchain resolution instead of being skipped cleanly. - build.rs: emitting `rerun-if-changed` disables Cargo's default whole-package scan, so list every input to `swift build` — the package manifests, `Package.resolved`, `.swift-version`, and the whole Sources tree — so stale shim/toolchain output can't linger. (`.build/` is left unwatched, as it is this build's own output.) - src/lib.rs: a null result from the shim is not a parse failure — SwiftParser recovers from invalid syntax and always yields a tree — so it means the shim failed to serialize/allocate the JSON. Fix the error message and the variant doc accordingly. - README.md: `.swift-version` is not honored by the macOS Bazel build (which uses the host Xcode toolchain); document that it pins the Linux and local builds only. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- unified/swift-syntax-rs/BUILD.bazel | 15 ++++++++++++++- unified/swift-syntax-rs/README.md | 13 +++++++++---- unified/swift-syntax-rs/build.rs | 24 ++++++++++++++---------- unified/swift-syntax-rs/src/lib.rs | 9 +++++++-- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel index be7d41eaf871..fed7df4e611c 100644 --- a/unified/swift-syntax-rs/BUILD.bazel +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -10,8 +10,21 @@ exports_files([".swift-version"]) # Targets in this package require a Swift toolchain (Linux or macOS). # `select()` gives us OR-of-OSes; other platforms get marked incompatible # so `bazel build/test //...` skips them cleanly. +# +# The Linux branch additionally requires x86_64: the only registered standalone +# Linux Swift toolchain (`swift_toolchain_exec_ubuntu24.04`, see //:MODULE.bazel) +# is x86_64-only. Without the CPU constraint, Linux/aarch64 targets would stay +# "compatible" and then fail toolchain resolution instead of being skipped. +config_setting( + name = "linux_x86_64", + constraint_values = [ + "@platforms//os:linux", + "@platforms//cpu:x86_64", + ], +) + _SWIFT_SUPPORTED_PLATFORMS = select({ - "@platforms//os:linux": [], + ":linux_x86_64": [], "@platforms//os:macos": [], "//conditions:default": ["@platforms//:incompatible"], }) diff --git a/unified/swift-syntax-rs/README.md b/unified/swift-syntax-rs/README.md index 9b14d4026f37..990af77d26fd 100644 --- a/unified/swift-syntax-rs/README.md +++ b/unified/swift-syntax-rs/README.md @@ -155,10 +155,15 @@ Requirements: [`xcode_transition.bzl`](xcode_transition.bzl)), so other targets on macOS keep using Bazel's default CC toolchain. -The Swift compiler version is read from [`.swift-version`](.swift-version) by -both the Bazel toolchain (`swift.toolchain(swift_version_file = …)`) and the -local build, and is kept in sync with the `swift-syntax` release pinned in -`swift/Package.swift`, so local and CI builds behave identically. +The Swift compiler version in [`.swift-version`](.swift-version) is kept in sync +with the `swift-syntax` release pinned in `swift/Package.swift`. It is honored on +**Linux**, where the hermetic swift.org Bazel toolchain +(`swift.toolchain(swift_version_file = …)`) and the local `cargo`/`swift build` +both read it. On **macOS** it is *not* honored by the Bazel build: `rules_swift` +auto-registers the host `xcode_swift_toolchain`, which uses whichever Swift ships +with the installed Xcode and ignores `.swift-version`. So the pinned version +governs Linux (and local) builds, while the macOS compiler version depends on the +host Xcode. ## Usage diff --git a/unified/swift-syntax-rs/build.rs b/unified/swift-syntax-rs/build.rs index 3cd2e2858742..c599cdf4e70b 100644 --- a/unified/swift-syntax-rs/build.rs +++ b/unified/swift-syntax-rs/build.rs @@ -6,16 +6,20 @@ fn main() { let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()); let swift_dir = manifest_dir.join("swift"); - println!( - "cargo:rerun-if-changed={}", - swift_dir.join("Package.swift").display() - ); - println!( - "cargo:rerun-if-changed={}", - swift_dir - .join("Sources/SwiftSyntaxFFI/SwiftSyntaxFFI.swift") - .display() - ); + // Emitting any `rerun-if-changed` disables Cargo's default whole-package + // scan, so we must list every input to the `swift build` below. Watch the + // package manifests, the pinned dependency lockfile, the pinned compiler + // version, and the entire Swift sources tree (a directory is scanned + // recursively). `.build/` is intentionally *not* watched (it is this + // build's output; watching it would cause perpetual rebuilds). + for input in [ + swift_dir.join("Package.swift"), + swift_dir.join("Package.resolved"), + swift_dir.join("Sources"), + manifest_dir.join(".swift-version"), + ] { + println!("cargo:rerun-if-changed={}", input.display()); + } println!("cargo:rerun-if-env-changed=SWIFT"); println!("cargo:rerun-if-env-changed=SWIFTC"); diff --git a/unified/swift-syntax-rs/src/lib.rs b/unified/swift-syntax-rs/src/lib.rs index 2544c0bc408d..2a8fe0411af8 100644 --- a/unified/swift-syntax-rs/src/lib.rs +++ b/unified/swift-syntax-rs/src/lib.rs @@ -29,7 +29,10 @@ unsafe extern "C" { pub enum ParseError { /// The provided source contained an interior NUL byte. NulByte, - /// The Swift side failed to produce a result. + /// The Swift shim returned no result. `SwiftParser` recovers from invalid + /// syntax (it always produces a tree, possibly with error nodes), so this + /// does *not* indicate a syntax error in the source — it means the shim + /// failed to serialize the tree to JSON or to allocate the result string. SwiftFailure, } @@ -37,7 +40,9 @@ impl std::fmt::Display for ParseError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ParseError::NulByte => write!(f, "source contained an interior NUL byte"), - ParseError::SwiftFailure => write!(f, "swift-syntax failed to parse the source"), + ParseError::SwiftFailure => { + write!(f, "the swift-syntax shim failed to produce a JSON result") + } } } } From 46d6a118f4598833a89be79cdf6f06816146500b Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 15 Jul 2026 12:10:50 +0000 Subject: [PATCH 50/60] yeast: Clarify Ast::with_schema registration doc The doc claimed the schema passed to `Ast::with_schema` must already have every node kind and field name registered, contradicting the registration methods just below and the swift-syntax adapter, which starts from `Schema::new()` and registers names on demand during construction. Document that up-front registration is optional. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- shared/yeast/src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/shared/yeast/src/lib.rs b/shared/yeast/src/lib.rs index 89facfaea99f..6719a5498e2b 100644 --- a/shared/yeast/src/lib.rs +++ b/shared/yeast/src/lib.rs @@ -358,8 +358,14 @@ impl Ast { /// programmatically from a source other than a tree-sitter parse (e.g. an /// external parser's output). Populate it with [`Ast::create_node`] / /// [`Ast::create_node_with_range`] and then designate the root with - /// [`Ast::set_root`]. The `schema` must already have every node kind and - /// field name registered (see [`schema::Schema`]). + /// [`Ast::set_root`]. + /// + /// Node kind and field names may be registered in `schema` up front, or on + /// demand while building via [`Ast::register_kind`], + /// [`Ast::register_unnamed_kind`], and [`Ast::register_field`] (which return + /// the ids to pass to [`Ast::create_node_with_range`]). Passing a fresh + /// [`schema::Schema::new`] and registering names during construction is + /// therefore fine — see the swift-syntax adapter for an example. pub fn with_schema(schema: schema::Schema) -> Self { Self { root: Id(0), From 205c9a934674b327dccaca3ef4d09ba9c5ff3c11 Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 15 Jul 2026 12:19:25 +0000 Subject: [PATCH 51/60] swift-syntax-rs: Fix bazel formatting aCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- unified/swift-syntax-rs/xcode_transition.bzl | 1 + 1 file changed, 1 insertion(+) diff --git a/unified/swift-syntax-rs/xcode_transition.bzl b/unified/swift-syntax-rs/xcode_transition.bzl index c6f293a0452c..b1612e762b15 100644 --- a/unified/swift-syntax-rs/xcode_transition.bzl +++ b/unified/swift-syntax-rs/xcode_transition.bzl @@ -45,6 +45,7 @@ _xcode_transition = transition( def _wrapper_impl(ctx): src = ctx.attr.actual[0] + # Forward the providers a downstream `rust_*` target reads from `deps`: # `DefaultInfo`, `CcInfo` (linking info), and `OutputGroupInfo`. providers = [src[DefaultInfo]] From 7474a33132cec2331e75c278567416f11f300606 Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 15 Jul 2026 12:47:26 +0000 Subject: [PATCH 52/60] unified: Hardcode swift_version It seems that using swift_version_file has some issues when `codeql` is consumed as a dependency module. I'm hoping hardcoding the version instead will fix this, but ideally we should find a more robust solution. --- MODULE.bazel | 13 ++++++++++--- unified/swift-syntax-rs/BUILD.bazel | 4 ---- unified/swift-syntax-rs/README.md | 23 ++++++++++++++--------- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 37a71583198f..3638a07289b8 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -229,12 +229,19 @@ use_repo( # Swift toolchain for building `unified/swift-syntax-rs`. On Linux we register # a hermetic swift.org toolchain as the exec toolchain; on macOS `rules_swift` # auto-registers `xcode_swift_toolchain` (host Xcode + OS-provided Swift -# runtime), which is not hermetic. Version comes from -# `unified/swift-syntax-rs/.swift-version`. +# runtime), which is not hermetic. +# +# The version is pinned as a literal rather than read from +# `unified/swift-syntax-rs/.swift-version` via `swift_version_file`: the latter +# makes the extension `module_ctx.read` a `//unified/...` label, which fails to +# resolve when this repo is consumed as a dependency module (`@@ql+`) whose +# `unified/swift-syntax-rs` package is not loadable in that context. Keep this +# in sync with `unified/swift-syntax-rs/.swift-version` (used by the `cargo` +# build) and the `swift-syntax` release in `swift/Package.swift`. swift = use_extension("@rules_swift//swift:extensions.bzl", "swift") swift.toolchain( name = "swift_toolchain", - swift_version_file = "//unified/swift-syntax-rs:.swift-version", + swift_version = "6.3.2", ) use_repo( swift, diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel index fed7df4e611c..02fda50cf502 100644 --- a/unified/swift-syntax-rs/BUILD.bazel +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -3,10 +3,6 @@ load(":xcode_transition.bzl", "xcode_transition_swift_library") package(default_visibility = ["//visibility:public"]) -# Pinned Swift version, shared with `cargo` and `swift/Package.swift`. -# Referenced by `swift.toolchain(swift_version_file = ...)` in //:MODULE.bazel. -exports_files([".swift-version"]) - # Targets in this package require a Swift toolchain (Linux or macOS). # `select()` gives us OR-of-OSes; other platforms get marked incompatible # so `bazel build/test //...` skips them cleanly. diff --git a/unified/swift-syntax-rs/README.md b/unified/swift-syntax-rs/README.md index 990af77d26fd..d43969199f91 100644 --- a/unified/swift-syntax-rs/README.md +++ b/unified/swift-syntax-rs/README.md @@ -155,15 +155,20 @@ Requirements: [`xcode_transition.bzl`](xcode_transition.bzl)), so other targets on macOS keep using Bazel's default CC toolchain. -The Swift compiler version in [`.swift-version`](.swift-version) is kept in sync -with the `swift-syntax` release pinned in `swift/Package.swift`. It is honored on -**Linux**, where the hermetic swift.org Bazel toolchain -(`swift.toolchain(swift_version_file = …)`) and the local `cargo`/`swift build` -both read it. On **macOS** it is *not* honored by the Bazel build: `rules_swift` -auto-registers the host `xcode_swift_toolchain`, which uses whichever Swift ships -with the installed Xcode and ignores `.swift-version`. So the pinned version -governs Linux (and local) builds, while the macOS compiler version depends on the -host Xcode. +The Swift compiler version is kept in sync across three places: the +[`.swift-version`](.swift-version) file (read by the local `cargo`/`swift build` +and by [swiftly](https://www.swift.org/swiftly/)), the literal `swift_version` +pinned on `swift.toolchain(...)` in the root `MODULE.bazel` (the hermetic +swift.org **Linux** Bazel toolchain), and the `swift-syntax` release in +`swift/Package.swift`. On **macOS** the version is *not* pinned by the Bazel +build: `rules_swift` auto-registers the host `xcode_swift_toolchain`, which uses +whichever Swift ships with the installed Xcode. So the pin governs Linux (and +local) builds, while the macOS compiler version depends on the host Xcode. + +(The Bazel toolchain pins a literal rather than reading `.swift-version` via +`swift_version_file`, because the latter makes the module extension read a +`//unified/...` label, which fails when this repo is consumed as a dependency +module.) ## Usage From da1bbb7fac53fb8097e317ae4f2c22ffaec6fc7d Mon Sep 17 00:00:00 2001 From: Taus Date: Wed, 15 Jul 2026 13:07:06 +0000 Subject: [PATCH 53/60] Bazel: regenerate vendored cargo dependencies Registers the `unified/swift-syntax-rs` workspace member (added in "unified: Add swift-syntax-rs") in the tree-sitter extractors crate universe. It has no external Rust dependencies, so its dependency entries are empty. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../tree_sitter_extractors_deps/BUILD.bazel | 12 ++++++++ .../tree_sitter_extractors_deps/defs.bzl | 28 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/misc/bazel/3rdparty/tree_sitter_extractors_deps/BUILD.bazel b/misc/bazel/3rdparty/tree_sitter_extractors_deps/BUILD.bazel index bb32aa97a5e2..ce1d79a772b9 100644 --- a/misc/bazel/3rdparty/tree_sitter_extractors_deps/BUILD.bazel +++ b/misc/bazel/3rdparty/tree_sitter_extractors_deps/BUILD.bazel @@ -553,6 +553,18 @@ alias( tags = ["manual"], ) +alias( + name = "swift-syntax-rs-0.1.0", + actual = "@vendor_ts__swift-syntax-rs-0.1.0//:swift_syntax_rs", + tags = ["manual"], +) + +alias( + name = "swift-syntax-rs", + actual = "@vendor_ts__swift-syntax-rs-0.1.0//:swift_syntax_rs", + tags = ["manual"], +) + alias( name = "syn-2.0.106", actual = "@vendor_ts__syn-2.0.106//:syn", diff --git a/misc/bazel/3rdparty/tree_sitter_extractors_deps/defs.bzl b/misc/bazel/3rdparty/tree_sitter_extractors_deps/defs.bzl index 7fbdfc4bbd4b..f3b5edb59cd6 100644 --- a/misc/bazel/3rdparty/tree_sitter_extractors_deps/defs.bzl +++ b/misc/bazel/3rdparty/tree_sitter_extractors_deps/defs.bzl @@ -429,6 +429,10 @@ _NORMAL_DEPENDENCIES = { "tree-sitter-language": Label("@vendor_ts__tree-sitter-language-0.1.5//:tree_sitter_language"), }, }, + "unified/swift-syntax-rs": { + _COMMON_CONDITION: { + }, + }, } _NORMAL_ALIASES = { @@ -475,6 +479,10 @@ _NORMAL_ALIASES = { _COMMON_CONDITION: { }, }, + "unified/swift-syntax-rs": { + _COMMON_CONDITION: { + }, + }, } _NORMAL_DEV_DEPENDENCIES = { @@ -505,6 +513,8 @@ _NORMAL_DEV_DEPENDENCIES = { }, "unified/extractor/tree-sitter-swift": { }, + "unified/swift-syntax-rs": { + }, } _NORMAL_DEV_ALIASES = { @@ -532,6 +542,8 @@ _NORMAL_DEV_ALIASES = { }, "unified/extractor/tree-sitter-swift": { }, + "unified/swift-syntax-rs": { + }, } _PROC_MACRO_DEPENDENCIES = { @@ -557,6 +569,8 @@ _PROC_MACRO_DEPENDENCIES = { }, "unified/extractor/tree-sitter-swift": { }, + "unified/swift-syntax-rs": { + }, } _PROC_MACRO_ALIASES = { @@ -582,6 +596,8 @@ _PROC_MACRO_ALIASES = { }, "unified/extractor/tree-sitter-swift": { }, + "unified/swift-syntax-rs": { + }, } _PROC_MACRO_DEV_DEPENDENCIES = { @@ -607,6 +623,8 @@ _PROC_MACRO_DEV_DEPENDENCIES = { }, "unified/extractor/tree-sitter-swift": { }, + "unified/swift-syntax-rs": { + }, } _PROC_MACRO_DEV_ALIASES = { @@ -634,6 +652,8 @@ _PROC_MACRO_DEV_ALIASES = { }, "unified/extractor/tree-sitter-swift": { }, + "unified/swift-syntax-rs": { + }, } _BUILD_DEPENDENCIES = { @@ -663,6 +683,8 @@ _BUILD_DEPENDENCIES = { "tree-sitter-generate": Label("@vendor_ts__tree-sitter-generate-0.26.8//:tree_sitter_generate"), }, }, + "unified/swift-syntax-rs": { + }, } _BUILD_ALIASES = { @@ -690,6 +712,8 @@ _BUILD_ALIASES = { _COMMON_CONDITION: { }, }, + "unified/swift-syntax-rs": { + }, } _BUILD_PROC_MACRO_DEPENDENCIES = { @@ -715,6 +739,8 @@ _BUILD_PROC_MACRO_DEPENDENCIES = { }, "unified/extractor/tree-sitter-swift": { }, + "unified/swift-syntax-rs": { + }, } _BUILD_PROC_MACRO_ALIASES = { @@ -740,6 +766,8 @@ _BUILD_PROC_MACRO_ALIASES = { }, "unified/extractor/tree-sitter-swift": { }, + "unified/swift-syntax-rs": { + }, } _CONDITIONS = { From e9e360ae445c4a7cbaf00d006db7867fef2128bc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 16 Jul 2026 11:18:13 +0000 Subject: [PATCH 54/60] update codeql documentation --- .../codeql-changelog/codeql-cli-2.26.1.rst | 98 +++++++++++++++++++ .../codeql-changelog/index.rst | 1 + 2 files changed, 99 insertions(+) create mode 100644 docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.26.1.rst diff --git a/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.26.1.rst b/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.26.1.rst new file mode 100644 index 000000000000..47d9bf781482 --- /dev/null +++ b/docs/codeql/codeql-overview/codeql-changelog/codeql-cli-2.26.1.rst @@ -0,0 +1,98 @@ +.. _codeql-cli-2.26.1: + +========================== +CodeQL 2.26.1 (2026-07-15) +========================== + +.. contents:: Contents + :depth: 2 + :local: + :backlinks: none + +This is an overview of changes in the CodeQL CLI and relevant CodeQL query and library packs. For additional updates on changes to the CodeQL code scanning experience, check out the `code scanning section on the GitHub blog `__, `relevant GitHub Changelog updates `__, `changes in the CodeQL extension for Visual Studio Code `__, and the `CodeQL Action changelog `__. + +Security Coverage +----------------- + +CodeQL 2.26.1 runs a total of 497 security queries when configured with the Default suite (covering 170 CWE). The Extended suite enables an additional 131 queries (covering 32 more CWE). + +CodeQL CLI +---------- + +There are no user-facing CLI changes in this release. + +Query Packs +----------- + +Minor Analysis Improvements +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Rust +"""" + +* The :code:`rust/hard-coded-cryptographic-value` query now treats arithmetic and bitwise operations, including string append operations, as barriers. This addresses false positive results where hard-coded constants are combined with non-constant data, such as incrementing a nonce or appending variable data to a constant prefix. + +Query Metadata Changes +~~~~~~~~~~~~~~~~~~~~~~ + +C/C++ +""""" + +* Added the tags :code:`external/cwe/cwe-073` and :code:`external/cwe/cwe-078` to :code:`cpp/uncontrolled-process-operation`. + +C# +"" + +* Added the tag :code:`external/cwe/cwe-073` to :code:`cs/assembly-path-injection`. + +Language Libraries +------------------ + +Breaking Changes +~~~~~~~~~~~~~~~~ + +C/C++ +""""" + +* Removed support for using variables as sources and sinks in models-as-data. Users of this feature should convert such sources and sinks to models defined using the QL language. + +Major Analysis Improvements +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +C# +"" + +* Simplified and streamlined the use of NuGet sources when downloading dependencies via :code:`[mono] nuget.exe` in :code:`build-mode: none`\ : NuGet sources are now supplied via the :code:`-Source` flag instead of moving or creating :code:`nuget.config` files in the checked-out repository, private registries are used if configured, and only reachable feeds are used when NuGet feed checking is enabled (the default). + +Minor Analysis Improvements +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Golang +"""""" + +* Improved models for the :code:`log/slog` package (Go 1.21+), including :code:`*slog.Logger` methods, :code:`With`\ /\ :code:`WithGroup`, and :code:`Attr`\ /\ :code:`Value` helpers, improving coverage for the :code:`go/log-injection` and :code:`go/clear-text-logging` queries. + +Java/Kotlin +""""""""""" + +* Regular expression checks via annotation with :code:`@javax.validation.constraints.Pattern` are now recognized as sanitizers for :code:`java/path-injection`. +* Added summary and LLM-generated source and sink models for :code:`org.apache.poi`. +* The first argument of the :code:`uri` method of :code:`WebClient$UriSpec` in :code:`org.springframework.web.reactive.function.client` is now considered a request forgery sink. Previously only the first arguments of the :code:`WebClient.create` and :code:`WebClient$Builder.baseUrl` methods were considered. This may lead to more alerts for the query :code:`java/ssrf` (Server-side request forgery). + +JavaScript/TypeScript +""""""""""""""""""""" + +* Added support for Angular's :code:`@HostListener('window:message', ...)` and :code:`@HostListener('document:message', ...)` decorators as :code:`postMessage` event handlers. The decorated method's event parameter is now recognized as a client-side remote flow source, and is considered by the :code:`js/missing-origin-check` query. + +Python +"""""" + +* :code:`Flask::FlaskApp::instance()` will now also return instances of subclasses defined in the source tree. Previously, these were filtered out. :code:`Flask::FlaskApp::classRef()` has been deprecated in favor of :code:`Flask::FlaskApp::subclassRef()` since it already returned some subclasses. + +Deprecated APIs +~~~~~~~~~~~~~~~ + +C/C++ +""""" + +* Models-as-data flow summaries now use fully qualified field names (for example, :code:`MyNamespace::MyStruct::myField`) instead of unqualified field names such as :code:`myField`. We recommend updating existing flow summaries to use fully qualified field names. Unqualified field names are still supported, but that support will be removed in a future release. diff --git a/docs/codeql/codeql-overview/codeql-changelog/index.rst b/docs/codeql/codeql-overview/codeql-changelog/index.rst index 301adddeedbc..837fa757368d 100644 --- a/docs/codeql/codeql-overview/codeql-changelog/index.rst +++ b/docs/codeql/codeql-overview/codeql-changelog/index.rst @@ -11,6 +11,7 @@ A list of queries for each suite and language `is available here Date: Fri, 17 Jul 2026 17:22:11 +0200 Subject: [PATCH 55/60] Shared: Add missing QLdoc --- shared/util/codeql/util/Strings.qll | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/util/codeql/util/Strings.qll b/shared/util/codeql/util/Strings.qll index c82c23a9988b..b032bb73672b 100644 --- a/shared/util/codeql/util/Strings.qll +++ b/shared/util/codeql/util/Strings.qll @@ -1,3 +1,4 @@ +/** Provides predicates for working with strings. */ overlay[local?] module; From f384e291d70fea0f021f4f5522a7f3adad70c99d Mon Sep 17 00:00:00 2001 From: Taus Date: Mon, 20 Jul 2026 13:10:02 +0200 Subject: [PATCH 56/60] unified: Clean up build Co-authored-by: Jeroen Ketema <93738568+jketema@users.noreply.github.com> --- unified/swift-syntax-rs/BUILD.bazel | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel index 02fda50cf502..216c232c888b 100644 --- a/unified/swift-syntax-rs/BUILD.bazel +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -7,23 +7,11 @@ package(default_visibility = ["//visibility:public"]) # `select()` gives us OR-of-OSes; other platforms get marked incompatible # so `bazel build/test //...` skips them cleanly. # -# The Linux branch additionally requires x86_64: the only registered standalone -# Linux Swift toolchain (`swift_toolchain_exec_ubuntu24.04`, see //:MODULE.bazel) -# is x86_64-only. Without the CPU constraint, Linux/aarch64 targets would stay -# "compatible" and then fail toolchain resolution instead of being skipped. -config_setting( - name = "linux_x86_64", - constraint_values = [ - "@platforms//os:linux", - "@platforms//cpu:x86_64", - ], -) - -_SWIFT_SUPPORTED_PLATFORMS = select({ - ":linux_x86_64": [], - "@platforms//os:macos": [], - "//conditions:default": ["@platforms//:incompatible"], -}) +_SWIFT_SUPPORTED_PLATFORMS = select({ + "@platforms//os:linux": [], + "@platforms//os:macos": [], + "//conditions:default": ["@platforms//:incompatible"], +}) # Swift FFI shim: wraps swift-syntax and exposes a small C ABI. The Rust # targets below link against its `CcInfo`. From 0a1139c2307232ab1df24b2dc9731213806a5ee3 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Tue, 21 Jul 2026 10:49:18 +0200 Subject: [PATCH 57/60] Fix Bazel formatting --- unified/swift-syntax-rs/BUILD.bazel | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel index 216c232c888b..58c34d80b789 100644 --- a/unified/swift-syntax-rs/BUILD.bazel +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -7,11 +7,11 @@ package(default_visibility = ["//visibility:public"]) # `select()` gives us OR-of-OSes; other platforms get marked incompatible # so `bazel build/test //...` skips them cleanly. # -_SWIFT_SUPPORTED_PLATFORMS = select({ - "@platforms//os:linux": [], - "@platforms//os:macos": [], - "//conditions:default": ["@platforms//:incompatible"], -}) +_SWIFT_SUPPORTED_PLATFORMS = select({ + "@platforms//os:linux": [], + "@platforms//os:macos": [], + "//conditions:default": ["@platforms//:incompatible"], +}) # Swift FFI shim: wraps swift-syntax and exposes a small C ABI. The Rust # targets below link against its `CcInfo`. From bf575868bce83ad64fa43f03766b3362dc27e1b8 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Tue, 21 Jul 2026 10:53:03 +0200 Subject: [PATCH 58/60] Simplify Xcode transition per review comments --- unified/swift-syntax-rs/xcode_transition.bzl | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/unified/swift-syntax-rs/xcode_transition.bzl b/unified/swift-syntax-rs/xcode_transition.bzl index b1612e762b15..9783c0fc8719 100644 --- a/unified/swift-syntax-rs/xcode_transition.bzl +++ b/unified/swift-syntax-rs/xcode_transition.bzl @@ -24,22 +24,19 @@ _EXTRA_TOOLCHAINS = "//command_line_option:extra_toolchains" def _transition_impl(settings, attr): if attr.os != "macos": - # Preserve inputs so the configuration is identical to the incoming one. + return {} + else: return { - _XCODE_VERSION_CONFIG: settings[_XCODE_VERSION_CONFIG], - _EXTRA_TOOLCHAINS: settings[_EXTRA_TOOLCHAINS], + _XCODE_VERSION_CONFIG: "@local_config_xcode//:host_xcodes", + _EXTRA_TOOLCHAINS: ( + list(settings[_EXTRA_TOOLCHAINS]) + + ["@local_config_apple_cc_toolchains//:all"] + ), } - return { - _XCODE_VERSION_CONFIG: "@local_config_xcode//:host_xcodes", - _EXTRA_TOOLCHAINS: ( - list(settings[_EXTRA_TOOLCHAINS]) + - ["@local_config_apple_cc_toolchains//:all"] - ), - } _xcode_transition = transition( implementation = _transition_impl, - inputs = [_XCODE_VERSION_CONFIG, _EXTRA_TOOLCHAINS], + inputs = [_EXTRA_TOOLCHAINS], outputs = [_XCODE_VERSION_CONFIG, _EXTRA_TOOLCHAINS], ) From cf8eeb8e44a353411f4e2ed58aa7025093ac7045 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Tue, 21 Jul 2026 11:01:37 +0200 Subject: [PATCH 59/60] Only depend on the runtime libraries from the Swift toolchain --- unified/swift-syntax-rs/BUILD.bazel | 10 ++++++++-- unified/swift-syntax-rs/swift_runtime.bzl | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 unified/swift-syntax-rs/swift_runtime.bzl diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel index 58c34d80b789..34bf97a313fd 100644 --- a/unified/swift-syntax-rs/BUILD.bazel +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -1,8 +1,14 @@ load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_library", "rust_test") +load(":swift_runtime.bzl", "swift_runtime_libs") load(":xcode_transition.bzl", "xcode_transition_swift_library") package(default_visibility = ["//visibility:public"]) +swift_runtime_libs( + name = "swift_runtime_libs", + toolchain = "@swift_toolchain_ubuntu24.04//:files", +) + # Targets in this package require a Swift toolchain (Linux or macOS). # `select()` gives us OR-of-OSes; other platforms get marked incompatible # so `bazel build/test //...` skips them cleanly. @@ -50,7 +56,7 @@ rust_binary( # `xcode_swift_toolchain`). data = select({ "@platforms//os:macos": [], - "@platforms//os:linux": ["@swift_toolchain_ubuntu24.04//:files"], + "@platforms//os:linux": [":swift_runtime_libs"], }), edition = "2024", target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS, @@ -63,7 +69,7 @@ rust_test( crate = ":swift_syntax_rs", data = select({ "@platforms//os:macos": [], - "@platforms//os:linux": ["@swift_toolchain_ubuntu24.04//:files"], + "@platforms//os:linux": [":swift_runtime_libs"], }), edition = "2024", target_compatible_with = _SWIFT_SUPPORTED_PLATFORMS, diff --git a/unified/swift-syntax-rs/swift_runtime.bzl b/unified/swift-syntax-rs/swift_runtime.bzl new file mode 100644 index 000000000000..899d97d71162 --- /dev/null +++ b/unified/swift-syntax-rs/swift_runtime.bzl @@ -0,0 +1,19 @@ +"""Filter the Swift toolchain down to just its Linux runtime shared objects. + +The standalone toolchain's `:files` bundles the compiler, host libraries, clang +runtime, plugins, etc. For running a Swift-linked binary we only need the +runtime shared objects in `usr/lib/swift/linux/`. +""" + +def _swift_runtime_libs_impl(ctx): + libs = [ + f + for f in ctx.files.toolchain + if "/usr/lib/swift/linux/" in f.path and f.path.endswith(".so") + ] + return [DefaultInfo(files = depset(libs))] + +swift_runtime_libs = rule( + implementation = _swift_runtime_libs_impl, + attrs = {"toolchain": attr.label(allow_files = True)}, +) From 38c9c84dcbf34489c3ee06db986aa0d866ad6992 Mon Sep 17 00:00:00 2001 From: Jeroen Ketema Date: Tue, 21 Jul 2026 11:08:46 +0200 Subject: [PATCH 60/60] Use 22.04 Swift toolchain --- MODULE.bazel | 4 ++-- unified/swift-syntax-rs/BUILD.bazel | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 3638a07289b8..bf2446dcc72f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -246,11 +246,11 @@ swift.toolchain( use_repo( swift, "swift_toolchain", - "swift_toolchain_ubuntu24.04", + "swift_toolchain_ubuntu22.04", ) register_toolchains( - "@swift_toolchain//:swift_toolchain_exec_ubuntu24.04", + "@swift_toolchain//:swift_toolchain_exec_ubuntu22.04", ) # `apple_support`'s xcode_config and CC toolchains, needed by the Xcode diff --git a/unified/swift-syntax-rs/BUILD.bazel b/unified/swift-syntax-rs/BUILD.bazel index 34bf97a313fd..5c2360d4deba 100644 --- a/unified/swift-syntax-rs/BUILD.bazel +++ b/unified/swift-syntax-rs/BUILD.bazel @@ -6,7 +6,7 @@ package(default_visibility = ["//visibility:public"]) swift_runtime_libs( name = "swift_runtime_libs", - toolchain = "@swift_toolchain_ubuntu24.04//:files", + toolchain = "@swift_toolchain_ubuntu22.04//:files", ) # Targets in this package require a Swift toolchain (Linux or macOS).