Conversation
added 2 commits
September 15, 2026 19:53
Modern Java source lowering (javasrc2cpg AstCreator). The bundled
JavaParser already parses every construct through Java 26 syntax; the
AstCreator dropped the ones below into unknown nodes, which broke
callgraphs, CFG, dataflow and reachable flows on any post-8 codebase
that uses them:
- Switch expressions (14) lower to nested <operator>.conditional calls,
so the declared conditional semantic gives the construct its JLS
14.11.2 dataflow: taint in any arm value flows to the expression's
value. Multi-label arms OR their tests; a `when` guard ANDs in.
- yield (14) contributes the arm value; a yield reached in plain
statement position lowers to an <operator>.yield call.
- instanceof type patterns (16) and record patterns (21) bind their
variables as locals plus an assignment from the tested/matched value,
carrying the JLS 6.3 definite-assignment taint. The LOCAL is
registered on a new scope channel (Scope.registerPatternLocalAst) and
attached as a direct child of the body BLOCK, where method.local and
the dataflow engine expect a method's locals; the binding assignments
return as leading ASTs which astsForVariableDecl/astForReturnNode
hoist into statement position (under the conditional they were
invisible to reaching definitions, and ARGUMENT edges into LOCAL
violate the schema).
- Method references lower to METHOD_REF nodes (the lambda shape), so a
router registration like router.get("/x").handler(this::h) exposes
its handler for call-graph and route resolution.
- Local records/classes (16) become full TYPE_DECLs under the METHOD.
- `native` methods keep their NATIVE modifier.
- String-valued annotation members additionally lower to a real LITERAL
duplicate under the parameter assignment: the ANNOTATION_LITERAL value
node supports no TAGGED_BY edges and is invisible to literal
traversals, so routes (@GetMapping("/users")) and statements
(@query("SELECT ...")) that existed only there were untaggable.
AnnotationTests pins both shapes.
New fixtures ModernJavaSyntaxTests / ModernJavaDataflowTests /
JavaFrameworkTagsTest measure and pin all of the above; before this
change 34 of their 39 tests fail, after it all pass.
Java framework boundaries (ChennaiTagsPass.tagJavaRoutes, EasyTagsPass):
- HTTP: Spring mapping annotations and request-data parameters,
JAX-RS/Jakarta (import-gated), Micronaut, servlet overrides
(recognised by servlet parameter TYPES so a user service() stays
untagged), and router DSLs (Vert.x/Javalin/Spark - route literal
plus handler resolution through METHOD_REF).
- gRPC: StreamObserver service methods are grpc-service entrypoints;
the observer parameter and onNext/onError/onCompleted calls are
framework-output.
- Databases: JDBC/JPA/JdbcTemplate calls and @Query/@Select/...
annotation values tagged sql; document stores import-gated.
- AI/LLM: LangChain4j, Spring AI, OpenAI, Gemini, Bedrock, Azure -
invocations ai-llm+ai-invoke, prompt builders ai-llm+ai-prompt (the
Python tagger's vocabulary).
- MCP: @tool methods are mcp-tool entrypoints with client-facing
parameters; exchange parameters are framework-input.
- Cloud: AWS/GCP/Azure calls cloud; RequestHandler.handleRequest
implementations are event-facing entrypoints.
- Native: System.loadLibrary, NATIVE methods and the java.lang.foreign
(FFM) machinery tagged native.
- Messaging/SDK: @KafkaListener/@JmsListener/@RabbitListener methods
are queue-driven inputs; OkHttp/Retrofit/Feign/java.net.http calls
are http-client.
- EasyTagsPass.tagJavaPatterns gains the Python arm's sink families
(code-execution for Runtime.exec/ProcessBuilder.start, ssrf, file-io,
unsafe-deserialization, reflection) so a Java graph has tagged sinks
without a hand-written chennai.json.
The shapes and collision gates live in
x2cpg/passes/taggers/java/JavaFrameworks.scala.
Java framework flow semantics (dataflowengineoss):
- New JavaFrameworkSemantics documents the vocabulary: fully-qualified
sanitizers (OWASP Encoder, Spring HtmlUtils/UriUtils, commons-text
escapers, URLEncoder) clear taint in the language-neutral javaFlows;
Jackson/Gson deserialisation carriers pass input taint into the
returned object; String.format/concat declare explicit index maps.
- The bare-name request readers (getParameter, getHeader, ... - the
returned value IS request data, receiver 0 to return -1) are gated on
JVM languages through DefaultSemantics.flowsForLanguage, mirroring
the PHP sanitizer gating; LanguageScopedSemanticsTests pins the split.
Version 3.3.0 -> 3.4.0 (minor: new language coverage and taggers).
Docs: README language/framework summary, lesson 2 (modern Java lowering
table and the two invariants that were bugs first, framework tagging
and semantics), lesson 11 (ChennaiTagsPass family list), lesson 14
(language-scoped and framework semantics).
Signed-off-by: ZCode <zcode@appthreat.com>
Signed-off-by: Team AppThreat <cloud@appthreat.com>
… and taggers
Build: the new taggers.java sub-package made every plain `import java.io.*`
/ `java.util.*` in the parent package resolve `java` against the sibling
package (Scala 3 relative package resolution), breaking CdxPass and
TrackersTagsPass on a clean build - incremental compilation had masked it.
JavaFrameworks now lives directly in the taggers package and the `_root_`
prefixes in ChennaiTagsPass are reverted to plain imports.
Review fixes, each with a regression test:
- Pattern-local ownership: the scope channel was a flat buffer drained by
method/lambda body builders, so a lambda built after a pattern binding
stole the enclosing method's local, and constructor bodies and lambda
BLOCK bodies never drained at all. The drain now happens per BLOCK in
astForBlockStatement (innermost first), which is the lexically correct
granularity for every case. PatternLocalOwnershipTests pins all three
shapes; note javasrc2cpg models a lambda as capturing a copy of every
in-scope local, so a lambda listing a binding is the closure model, not
the channel - ownership is asserted on the enclosing method.
- Condition edges: astForIf took the FIRST condition AST, which for a
switch-expression condition (`if (switch (x) {...})`) is a hoisted
binding, not the value. The CONDITION edge now binds the VALUE (the
last AST); same for while/do via conditionAstsFor.
- Plain assignments (`value = switch (...)`) now hoist the switch's
leading statements like declarations and returns already did, and the
assignment's type comes from the VALUE instead of the first leading
binding. New ModernJavaDataflowTests case pins the flow.
- Tagger precision (bare-name noise):
- `load` dropped from the JNI library loaders (every loader method
matched); System.load is matched through java.lang.System.load* on
the resolved methodFullName instead.
- `allocate` dropped from the FFM names (every buffer builder
matched) and the remaining bare FFM names are import-gated; resolved
calls were already covered by the java.lang.foreign.* prefix.
- JPA split: createQuery/createNativeQuery/createStoredProcedureQuery
stay ungated (distinctive); persist/merge/remove are everyday
collection/cache names and are now import-gated on
jakarta/javax.persistence, org.hibernate, spring-orm.
- The JdbcTemplate block additionally requires "Template" in the
resolved name, matching the document-store arm's receiver gate.
- AI prompt builders trimmed to distinctive names (prompt,
systemMessage, userMessage, aiMessage, messages, template) - bare
system/user/parameters fabricated prompt sinks on unrelated helpers.
- software.amazon.awssdk dropped from the ssrf patterns; AWS calls are
tagged cloud, and their URLs are not user-buildable.
Cleanup: unused `given DiffGraphBuilder` lines and the unused
`asExpressionValue` parameter removed; stale comments corrected; lesson
doc paths updated for the moved file.
Verified: sbt clean stage test createDistribution publishLocal passes;
the full suite is 3272 tests / 0 failures across all modules; atom's Java
reachables fixtures pass against the republished 3.4.0.
Signed-off-by: ZCode <zcode@appthreat.com>
Signed-off-by: Team AppThreat <cloud@appthreat.com>
Contributor
Author
|
Closing - reviewing via the pushed branch directly instead. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Java support was lagging behind the other frontends in two places: the
AstCreatordropped key Java 14-26 constructs into unknown nodes (breaking callgraphs, CFG, dataflow and reachable flows on modern code), and Java had no framework tagging at all -ChennaiTagsPasscovered JS/Python/PHP/Ruby/C, and a Java graph needed a hand-writtenchennai.jsonto find any source or sink.Changes
Modern Java lowering (
AstCreator,Scope) - the bundled JavaParser already parses all of it; this PR lowers it:<operator>.conditional, giving the JLS 14.11.2 arm-value dataflow through the declared conditional semantic; multi-label arms OR,whenguards ANDyieldas the arm value (plus an<operator>.yieldcall in stray statement position)instanceoftype patterns (16) and record patterns (21): bindings become locals + an assignment from the tested value (JLS 6.3 taint), with the LOCAL delivered through a scope channel that attaches it as a direct child of the enclosing BLOCK - drained per block, so ownership is lexically correct even across lambdas, constructors and nested blocksrouter.get("/x").handler(this::h)resolves its handlernativemethods keep their NATIVE modifier@GetMapping("/users")routes and@Query("SELECT ...")statements taggableFramework taggers (
ChennaiTagsPass.tagJavaRoutes, vocabulary inJavaFrameworks.scala): Spring MVC/WebFlux, JAX-RS/Jakarta, Micronaut, servlets (recognised by servlet parameter types), Vert.x/Javalin/Spark router DSLs, gRPCStreamObserverservices, JDBC/JPA/MyBatis/Mongo/Redis, AI SDKs (LangChain4j, Spring AI, OpenAI, Gemini, Bedrock, Azure -ai-invoke/ai-prompton the Python tagger's vocabulary), MCP@Toolmethods, AWS Lambda + cloud SDKs, JNI/FFM natives, Kafka/JMS/RabbitMQ listeners, http-client SDKs.EasyTagsPassgains the Python arm's sink families (code-execution,ssrf,file-io,unsafe-deserialization,reflection). Bare-name collisions are gated (imports, receiver types, annotation names) - see the review pass notes in the second commit.Flow semantics (
JavaFrameworkSemantics): fully-qualified sanitizers (OWASP Encoder, Spring HtmlUtils/UriUtils, commons-text, URLEncoder) and Jackson/Gson deserialisation carriers in the language-neutraljavaFlows; bare-name request readers (getParameter,getHeader, ... receiver->return) language-gated throughflowsForLanguage, mirroring the PHP sanitizer gating.Version: 3.3.0 -> 3.4.0 (minor). Docs: README, lessons 2 (modern Java lowering table + framework tagging/semantics), 11, 14.
Testing
ModernJavaSyntaxTests,ModernJavaDataflowTests(incl. parameter sources through pattern switches and return/assignment position),JavaFrameworkTagsTest,PatternLocalOwnershipTestsAnnotationTestsupdated for the dual annotation-value shapesbt clean stage test createDistribution publishLocalpasses; full suite 3272 tests / 0 failures across all moduleschennai.json): verified from atom's companion PR (Switch to the new @cdxgen/cdxgen package namespace atom#261)