Repository navigation
Conversation
Main sources still compile against empty trees and symbols. Tests are expected to fail until a semsitter parser fills them in.
Pin udg-model and prove a single tree-sitter rule parses, before attempting a Java grammar.
❌ ErrorsYour PR has failed checks. Please review the issues below and take necessary action before merging. 🚦 8 Pipeline jobs failed
Useful? React with 👍 / 👎 This comment will be updated automatically if new data arrives.🔗 Commit SHA: 755b0cf | Docs | View more details | Give us feedback! |
The shaded jdt-package was most of the 19 MB minimum. The plugin is now about 11 MB.
|
|
||
| private ModifierTree convertModifier(org.eclipse.jdt.core.dom.Modifier node) { | ||
| switch (node.getKeyword().toString()) { | ||
| case "public": | ||
| return new ModifierKeywordTreeImpl(Modifier.PUBLIC, firstTokenIn(node, TerminalToken.TokenNamepublic)); | ||
| case "protected": | ||
| return new ModifierKeywordTreeImpl(Modifier.PROTECTED, firstTokenIn(node, TerminalToken.TokenNameprotected)); | ||
| case "private": | ||
| return new ModifierKeywordTreeImpl(Modifier.PRIVATE, firstTokenIn(node, TerminalToken.TokenNameprivate)); | ||
| case "static": | ||
| return new ModifierKeywordTreeImpl(Modifier.STATIC, firstTokenIn(node, TerminalToken.TokenNamestatic)); | ||
| case "abstract": | ||
| return new ModifierKeywordTreeImpl(Modifier.ABSTRACT, firstTokenIn(node, TerminalToken.TokenNameabstract)); | ||
| case "final": | ||
| return new ModifierKeywordTreeImpl(Modifier.FINAL, firstTokenIn(node, TerminalToken.TokenNamefinal)); | ||
| case "native": | ||
| return new ModifierKeywordTreeImpl(Modifier.NATIVE, firstTokenIn(node, TerminalToken.TokenNamenative)); | ||
| case "synchronized": | ||
| return new ModifierKeywordTreeImpl(Modifier.SYNCHRONIZED, firstTokenIn(node, TerminalToken.TokenNamesynchronized)); | ||
| case "transient": | ||
| return new ModifierKeywordTreeImpl(Modifier.TRANSIENT, firstTokenIn(node, TerminalToken.TokenNametransient)); | ||
| case "volatile": | ||
| return new ModifierKeywordTreeImpl(Modifier.VOLATILE, firstTokenIn(node, TerminalToken.TokenNamevolatile)); | ||
| case "strictfp": | ||
| return new ModifierKeywordTreeImpl(Modifier.STRICTFP, firstTokenIn(node, TerminalToken.TokenNamestrictfp)); | ||
| case "default": | ||
| return new ModifierKeywordTreeImpl(Modifier.DEFAULT, firstTokenIn(node, TerminalToken.TokenNamedefault)); | ||
| case "sealed": | ||
| return new ModifierKeywordTreeImpl(Modifier.SEALED, firstTokenIn(node, TerminalToken.TokenNameRestrictedIdentifiersealed)); | ||
| case "non-sealed": { | ||
| return new ModifierKeywordTreeImpl(Modifier.NON_SEALED, firstTokenIn(node, TerminalToken.TokenNamenon_sealed)); | ||
| column++; | ||
| } | ||
| default: | ||
| throw new IllegalStateException(node.getKeyword().toString()); | ||
| } | ||
| } | ||
|
|
||
| private static final TerminalToken ANY_TOKEN = TerminalToken.TokenNameInvalid; | ||
|
|
||
| private static final Map<Object, Op> operators = new HashMap<>(); | ||
|
|
||
| private static class Op { | ||
| final Tree.Kind kind; | ||
|
|
||
| /** | ||
| * {@link TerminalToken} | ||
| */ | ||
| final TerminalToken tokenType; | ||
|
|
||
| Op(Tree.Kind kind, TerminalToken tokenType) { | ||
| this.kind = kind; | ||
| this.tokenType = tokenType; | ||
| } | ||
| } | ||
|
|
||
| static { | ||
| operators.put(PrefixExpression.Operator.PLUS, new Op(Tree.Kind.UNARY_PLUS, TerminalToken.TokenNamePLUS)); | ||
| operators.put(PrefixExpression.Operator.MINUS, new Op(Tree.Kind.UNARY_MINUS, TerminalToken.TokenNameMINUS)); | ||
| operators.put(PrefixExpression.Operator.NOT, new Op(Tree.Kind.LOGICAL_COMPLEMENT, TerminalToken.TokenNameNOT)); | ||
| operators.put(PrefixExpression.Operator.COMPLEMENT, new Op(Tree.Kind.BITWISE_COMPLEMENT, TerminalToken.TokenNameTWIDDLE)); | ||
| operators.put(PrefixExpression.Operator.DECREMENT, new Op(Tree.Kind.PREFIX_DECREMENT, TerminalToken.TokenNameMINUS_MINUS)); | ||
| operators.put(PrefixExpression.Operator.INCREMENT, new Op(Tree.Kind.PREFIX_INCREMENT, TerminalToken.TokenNamePLUS_PLUS)); | ||
|
|
||
| operators.put(PostfixExpression.Operator.DECREMENT, new Op(Tree.Kind.POSTFIX_DECREMENT, TerminalToken.TokenNameMINUS_MINUS)); | ||
| operators.put(PostfixExpression.Operator.INCREMENT, new Op(Tree.Kind.POSTFIX_INCREMENT, TerminalToken.TokenNamePLUS_PLUS)); | ||
|
|
||
| operators.put(InfixExpression.Operator.TIMES, new Op(Tree.Kind.MULTIPLY, TerminalToken.TokenNameMULTIPLY)); | ||
| operators.put(InfixExpression.Operator.DIVIDE, new Op(Tree.Kind.DIVIDE, TerminalToken.TokenNameDIVIDE)); | ||
| operators.put(InfixExpression.Operator.REMAINDER, new Op(Tree.Kind.REMAINDER, TerminalToken.TokenNameREMAINDER)); | ||
| operators.put(InfixExpression.Operator.PLUS, new Op(Tree.Kind.PLUS, TerminalToken.TokenNamePLUS)); | ||
| operators.put(InfixExpression.Operator.MINUS, new Op(Tree.Kind.MINUS, TerminalToken.TokenNameMINUS)); | ||
| operators.put(InfixExpression.Operator.LEFT_SHIFT, new Op(Tree.Kind.LEFT_SHIFT, TerminalToken.TokenNameLEFT_SHIFT)); | ||
| operators.put(InfixExpression.Operator.RIGHT_SHIFT_SIGNED, new Op(Tree.Kind.RIGHT_SHIFT, TerminalToken.TokenNameRIGHT_SHIFT)); | ||
| operators.put(InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED, new Op(Tree.Kind.UNSIGNED_RIGHT_SHIFT, TerminalToken.TokenNameUNSIGNED_RIGHT_SHIFT)); | ||
| operators.put(InfixExpression.Operator.LESS, new Op(Tree.Kind.LESS_THAN, TerminalToken.TokenNameLESS)); | ||
| operators.put(InfixExpression.Operator.GREATER, new Op(Tree.Kind.GREATER_THAN, TerminalToken.TokenNameGREATER)); | ||
| operators.put(InfixExpression.Operator.LESS_EQUALS, new Op(Tree.Kind.LESS_THAN_OR_EQUAL_TO, TerminalToken.TokenNameLESS_EQUAL)); | ||
| operators.put(InfixExpression.Operator.GREATER_EQUALS, new Op(Tree.Kind.GREATER_THAN_OR_EQUAL_TO, TerminalToken.TokenNameGREATER_EQUAL)); | ||
| operators.put(InfixExpression.Operator.EQUALS, new Op(Tree.Kind.EQUAL_TO, TerminalToken.TokenNameEQUAL_EQUAL)); | ||
| operators.put(InfixExpression.Operator.NOT_EQUALS, new Op(Tree.Kind.NOT_EQUAL_TO, TerminalToken.TokenNameNOT_EQUAL)); | ||
| operators.put(InfixExpression.Operator.XOR, new Op(Tree.Kind.XOR, TerminalToken.TokenNameXOR)); | ||
| operators.put(InfixExpression.Operator.OR, new Op(Tree.Kind.OR, TerminalToken.TokenNameOR)); | ||
| operators.put(InfixExpression.Operator.AND, new Op(Tree.Kind.AND, TerminalToken.TokenNameAND)); | ||
| operators.put(InfixExpression.Operator.CONDITIONAL_OR, new Op(Tree.Kind.CONDITIONAL_OR, TerminalToken.TokenNameOR_OR)); | ||
| operators.put(InfixExpression.Operator.CONDITIONAL_AND, new Op(Tree.Kind.CONDITIONAL_AND, TerminalToken.TokenNameAND_AND)); | ||
|
|
||
| operators.put(Assignment.Operator.ASSIGN, new Op(Tree.Kind.ASSIGNMENT, TerminalToken.TokenNameEQUAL)); | ||
| operators.put(Assignment.Operator.PLUS_ASSIGN, new Op(Tree.Kind.PLUS_ASSIGNMENT, TerminalToken.TokenNamePLUS_EQUAL)); | ||
| operators.put(Assignment.Operator.MINUS_ASSIGN, new Op(Tree.Kind.MINUS_ASSIGNMENT, TerminalToken.TokenNameMINUS_EQUAL)); | ||
| operators.put(Assignment.Operator.TIMES_ASSIGN, new Op(Tree.Kind.MULTIPLY_ASSIGNMENT, TerminalToken.TokenNameMULTIPLY_EQUAL)); | ||
| operators.put(Assignment.Operator.DIVIDE_ASSIGN, new Op(Tree.Kind.DIVIDE_ASSIGNMENT, TerminalToken.TokenNameDIVIDE_EQUAL)); | ||
| operators.put(Assignment.Operator.BIT_AND_ASSIGN, new Op(Tree.Kind.AND_ASSIGNMENT, TerminalToken.TokenNameAND_EQUAL)); | ||
| operators.put(Assignment.Operator.BIT_OR_ASSIGN, new Op(Tree.Kind.OR_ASSIGNMENT, TerminalToken.TokenNameOR_EQUAL)); | ||
| operators.put(Assignment.Operator.BIT_XOR_ASSIGN, new Op(Tree.Kind.XOR_ASSIGNMENT, TerminalToken.TokenNameXOR_EQUAL)); | ||
| operators.put(Assignment.Operator.REMAINDER_ASSIGN, new Op(Tree.Kind.REMAINDER_ASSIGNMENT, TerminalToken.TokenNameREMAINDER_EQUAL)); | ||
| operators.put(Assignment.Operator.LEFT_SHIFT_ASSIGN, new Op(Tree.Kind.LEFT_SHIFT_ASSIGNMENT, TerminalToken.TokenNameLEFT_SHIFT_EQUAL)); | ||
| operators.put(Assignment.Operator.RIGHT_SHIFT_SIGNED_ASSIGN, new Op(Tree.Kind.RIGHT_SHIFT_ASSIGNMENT, TerminalToken.TokenNameRIGHT_SHIFT_EQUAL)); | ||
| operators.put(Assignment.Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN, new Op(Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, TerminalToken.TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL)); | ||
| InternalSyntaxToken eof = new InternalSyntaxToken(line, column, "", Collections.emptyList(), true); |
There was a problem hiding this comment.
💡 Cross-Repo: Removed JParser.parse(ASTParser,…) breaks sonar-java-symbolic-execution tests
Fix here and in SonarSource/sonar-java-symbolic-execution
Breaks in: SonarSource/sonar-java-symbolic-execution - java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/utils/JParserTestUtils.java - JParserTestUtils.parse
This PR replaces the four-argument JParser.parse(ASTParser, String, String, String) with a three-argument parse(version, unitName, source) that returns an empty compilation unit. It also removes the ECJ astParser() factory from JParserConfig. In SonarSource/sonar-java-symbolic-execution, JParserTestUtils.parse (java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/utils/JParserTestUtils.java) calls JParserConfig.Mode.FILE_BY_FILE.create(...).astParser() and then the four-argument JParser.parse. Once that repo bumps sonar.java.version (pinned today to 8.44.0.48651) to a release with this change, its test helper won't compile. Even after porting it to the new signature, every parsed tree would be empty and have no semantics, so all SE tests built on it would stop checking anything. The PR is a draft PoC and the break is on purpose. The SE repo still needs a coordinated migration before it adopts this frontend.
Here: Before releasing this frontend, record the removal of JParser.parse(ASTParser, …) and JParserConfig.astParser() in the changelog or migration notes, and tell sonar-java-symbolic-execution about the new three-argument entry point. Coordinate the release so the SE repo does not bump to it until the new parser returns real trees.
There: In JParserTestUtils.parse, drop the astParser() call and use the three-argument JParser.parse(version, unitName, source). Keep sonar.java.version on the current release until the semsitter-based parser produces populated trees with semantics.
In SonarSource/sonar-java-symbolic-execution - java-symbolic-execution/java-symbolic-execution-plugin/src/test/java/org/sonar/java/se/utils/JParserTestUtils.java:
public static CompilationUnitTree parse(String unitName, String source) {
return JParser.parse(JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION.effectiveJavaVersionAsString(), unitName, source);
}
Was this helpful? React with 👍 / 👎
|
⚖️ Ruling update ready for review: #6409 |
Ruling Diff SummaryDetected changes in 18 rule files: 0 issues removed, 15708 issues added. S1220 (
|
Java starts the native executable and exchanges one JSON object per line, so a parse tree can be logged without a binary protocol.
|
⚖️ Ruling update ready for review: #6409 |
| public synchronized String parse(String source) throws IOException { | ||
| JsonObject request = new JsonObject(); | ||
| request.addProperty("id", nextId); | ||
| request.addProperty("op", "parse"); | ||
| request.addProperty("source", source); | ||
| nextId++; | ||
| return exchange(request); | ||
| } |
There was a problem hiding this comment.
⚠️ Bug: close() cannot kill a hung bridge: it's synchronized and waits forever
parse() and close() are both synchronized, and exchange() blocks on fromBridge.readLine() with no timeout. Say the bridge stalls on a parse (a pathological input, or an infinite loop in a grammar). The thread stuck in parse() keeps the monitor, so close(), for example from try-with-resources on another thread or a shutdown path, waits on the lock forever. It never gets to destroy()/destroyForcibly(). Even without a second thread, close() first runs a blocking quit exchange. A bridge that has stopped answering hangs close() on readLine() and is never killed. The escape path should not depend on the bridge cooperating: either kill the process before taking the lock, or put a deadline on the quit read and fall through to destroyForcibly().
Make close() not depend on the parse lock or on the bridge answering: send quit best-effort without reading the reply, then destroy with a timeout.:
@Override
public void close() {
if (closed) { return; }
closed = true;
try {
toBridge.write("{"op":"quit"}
");
toBridge.flush();
toBridge.close();
} catch (IOException ignored) {
// bridge already gone
}
process.destroy();
// waitFor(2s) then destroyForcibly() as before
}
- Apply fix
Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎
| fn node_json(node: tree_sitter::Node, depth: usize) -> serde_json::Value { | ||
| if depth > 64 { | ||
| return serde_json::json!({"truncated": true}); | ||
| } |
There was a problem hiding this comment.
💡 Edge Case: Trees deeper than 64 are silently cut off but still report ok:true
node_json returns {"truncated": true} for any node deeper than 64. That placeholder has no kind, positions or children. The response still says ok: true, and hasError comes from the full root, so nothing at the response level tells the Java client the tree is incomplete. The SemsitterBridge Javadoc and the handle_line docs describe the tree without mentioning truncation. Once the planned Java grammar is in, ordinary code goes past depth 64: a left-recursive chain of 60+ + string concatenations or long fluent call chains will do it. A consumer that reads kind on every node would then hit nulls, or silently drop subtrees. Either raise the limit, or switch to iterative serialization with a TreeCursor to avoid stack overflow. Failing that, report truncation explicitly, for example with a top-level "truncated": true flag or ok: false.
Was this helpful? React with 👍 / 👎
CI failed: Two failures from the semsitter/ECJ replacement PoC: a SanityTest assertion (scan_non_compiling_files expected 4 results, got 0) that is marked change-related, and a mall ruling analysis failing on unanalyzed expected files whose relation to the change is unclear; no infrastructure failures identified.OverviewFive unique error templates across 16 logs were analyzed. Two distinct failure patterns surfaced in the provided findings: a Sanity Test assertion failure and a ruling-test expected-directory mismatch for the mall project. Both are test failures, not infrastructure issues. The remaining templates were not included in the per-template findings provided, so this synthesis covers only the two findings present. FailuresSanityTest.scan_non_compiling_files returns 0 results instead of 4 (confidence: high)
Ruling analysis for mall: expected files not analyzed (confidence: medium)
Summary
Code Review
|
| Auto-apply | Compact | Unblock |
|
|
|
Was this helpful? React with 👍 / 👎 | Gitar
Part of SONARJAVA-7234
Draft, do not merge. Stops before a Java grammar.
Summary
jdt-package(ECJ) dependency. Parser and semantic types are empty stubs. Main sources compile. Tests are not expected to pass.semsitter-sample, a Cargo crate pinned to semsitterudg-modelatd606e04b766dc4f506c20c3c37f26d1511b5f065, with one tree-sitter rule (source_filematcheshello).cargo testin that directory passes.How SonarSource uses semsitter
sonar-context-augmentationdepends onudg-genfromgithub.com/SonarSource/semsitter(git rev pin, optionalnavigationfeature) plus tree-sitter grammars. Cargo needs GitHub auth for the private repo (gh auth setup-git, orCARGO_NET_GIT_FETCH_WITH_CLI=true).sonar-skunkdepends onudg-modelthe same way (different rev). Its Java side is a JNI bridge; the project graph still uses an inactive stub.sonarqube-mcp-serveronly togglesSONAR_CAG_USE_SEMSITTER.udg-modelonly, over SSH, so it does not pulludg-gendefault features (lang-delphineeds privatesonar-skunk).Test plan
mvn -pl java-frontend,java-checks,java-checks-common,java-jsp,sonar-java-plugin -am compile -DskipTests(check-listprocess-classesfirst, soGeneratedCheckListexists)cargo testinsemsitter-sample