From 15be2e38e48164323b0df2a5a4b5d9c6d0d8355e Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Sat, 4 Apr 2026 06:41:57 +0000 Subject: [PATCH 1/3] fix(sonar): S2142 re-interrupt thread on InterruptedException + S3923 dead branch + S2175 Boolean key - S2142: Split catch(Exception) blocks at 5 sites (RepositoryIdentity, Analyzer, FileDiscovery x2, BundleCommand) so InterruptedException is caught separately and Thread.currentThread().interrupt() is called before returning. - S3923: Remove dead if-else in CodeIqApplication.java where both branches set the same system property; replaced with a single unconditional assignment. - S2175: Replace data.get(Boolean.TRUE) in GitHubActionsDetector with a stream over entrySet() to avoid passing a Boolean key into a Map. Co-Authored-By: Claude Sonnet 4.6 --- .../github/randomcodespace/iq/CodeIqApplication.java | 11 +++-------- .../github/randomcodespace/iq/analyzer/Analyzer.java | 4 ++++ .../randomcodespace/iq/analyzer/FileDiscovery.java | 11 +++++++++-- .../github/randomcodespace/iq/cli/BundleCommand.java | 2 ++ .../iq/detector/config/GitHubActionsDetector.java | 7 ++++++- .../iq/intelligence/RepositoryIdentity.java | 3 +++ 6 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/main/java/io/github/randomcodespace/iq/CodeIqApplication.java b/src/main/java/io/github/randomcodespace/iq/CodeIqApplication.java index d2d304ed..53c8e5ca 100644 --- a/src/main/java/io/github/randomcodespace/iq/CodeIqApplication.java +++ b/src/main/java/io/github/randomcodespace/iq/CodeIqApplication.java @@ -93,14 +93,9 @@ public static void main(String[] args) { graphDbPath = root.resolve(".osscodeiq/graph.db"); } - if (java.nio.file.Files.isDirectory(graphDbPath)) { - // Enriched Neo4j graph exists -- point Neo4j config to it - System.setProperty("codeiq.graph.path", graphDbPath.toString()); - } else { - // No enriched graph -- Neo4j will start with an empty db, - // GraphBootstrapper will auto-load from H2 cache if available - System.setProperty("codeiq.graph.path", graphDbPath.toString()); - } + // Point Neo4j config to the graph path (enriched or new empty db). + // GraphBootstrapper will auto-load from H2 cache if no enriched graph exists. + System.setProperty("codeiq.graph.path", graphDbPath.toString()); } else if (isIndex) { app.setAdditionalProfiles("indexing"); // Index command: no web server, no Neo4j diff --git a/src/main/java/io/github/randomcodespace/iq/analyzer/Analyzer.java b/src/main/java/io/github/randomcodespace/iq/analyzer/Analyzer.java index 93037261..2093b74a 100644 --- a/src/main/java/io/github/randomcodespace/iq/analyzer/Analyzer.java +++ b/src/main/java/io/github/randomcodespace/iq/analyzer/Analyzer.java @@ -1297,6 +1297,10 @@ private String getGitHead(Path repoPath) { if (exitCode == 0 && sha.length() >= 7) { return sha; } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.debug("Could not determine git HEAD", e); + return null; } catch (Exception e) { log.debug("Could not determine git HEAD", e); } diff --git a/src/main/java/io/github/randomcodespace/iq/analyzer/FileDiscovery.java b/src/main/java/io/github/randomcodespace/iq/analyzer/FileDiscovery.java index 860618b2..ba0d623c 100644 --- a/src/main/java/io/github/randomcodespace/iq/analyzer/FileDiscovery.java +++ b/src/main/java/io/github/randomcodespace/iq/analyzer/FileDiscovery.java @@ -106,7 +106,10 @@ private boolean isGitRepo(Path root) { int exitCode = process.waitFor(); process.getInputStream().close(); return exitCode == 0; - } catch (IOException | InterruptedException e) { + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return false; + } catch (IOException e) { return false; } } @@ -152,7 +155,11 @@ private List discoverViaGit(Path root) { } return result; - } catch (IOException | InterruptedException e) { + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + log.warn("git ls-files failed, falling back to filesystem walk", e); + return discoverViaWalk(root); + } catch (IOException e) { log.warn("git ls-files failed, falling back to filesystem walk", e); return discoverViaWalk(root); } diff --git a/src/main/java/io/github/randomcodespace/iq/cli/BundleCommand.java b/src/main/java/io/github/randomcodespace/iq/cli/BundleCommand.java index 4cabc4fa..dbe05936 100644 --- a/src/main/java/io/github/randomcodespace/iq/cli/BundleCommand.java +++ b/src/main/java/io/github/randomcodespace/iq/cli/BundleCommand.java @@ -382,6 +382,8 @@ private int bundleSourceFiles(Path root, ZipOutputStream zos) { } return count; } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } catch (Exception ignored) { // Not a git repo or git not available } diff --git a/src/main/java/io/github/randomcodespace/iq/detector/config/GitHubActionsDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/config/GitHubActionsDetector.java index 007938c5..ce2cc684 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/config/GitHubActionsDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/config/GitHubActionsDetector.java @@ -78,7 +78,12 @@ public DetectorResult detect(DetectorContext ctx) { // YAML parses bare "on" as Boolean.TRUE Object onTriggers = data.get("on"); if (onTriggers == null) { - onTriggers = data.get(Boolean.TRUE); + // SnakeYAML may parse bare 'on' key as Boolean.TRUE — search by entry value + onTriggers = data.entrySet().stream() + .filter(e -> Boolean.TRUE.equals(e.getKey())) + .map(java.util.Map.Entry::getValue) + .findFirst() + .orElse(null); } if (onTriggers == null) { onTriggers = data.get("true"); diff --git a/src/main/java/io/github/randomcodespace/iq/intelligence/RepositoryIdentity.java b/src/main/java/io/github/randomcodespace/iq/intelligence/RepositoryIdentity.java index 5c6880f8..622e4235 100644 --- a/src/main/java/io/github/randomcodespace/iq/intelligence/RepositoryIdentity.java +++ b/src/main/java/io/github/randomcodespace/iq/intelligence/RepositoryIdentity.java @@ -48,6 +48,9 @@ private static String runGit(java.nio.file.Path repoPath, String... args) { } finally { proc.destroy(); } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + return null; } catch (Exception e) { return null; } From 5fa6b17c47608c5998982ab57e47ed20e9066d81 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Sat, 4 Apr 2026 06:42:37 +0000 Subject: [PATCH 2/3] fix(sonar): S1751 conditional break + S5850 regex grouping + S5855 redundant alternative + S5841 test emptiness Co-Authored-By: Claude Sonnet 4.6 --- .../iq/detector/csharp/CSharpStructuresDetector.java | 2 +- .../randomcodespace/iq/detector/java/GrpcServiceDetector.java | 2 +- .../randomcodespace/iq/detector/java/JpaEntityDetector.java | 2 +- .../iq/intelligence/query/CapabilityMatrixTest.java | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/github/randomcodespace/iq/detector/csharp/CSharpStructuresDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/csharp/CSharpStructuresDetector.java index ab50fa7b..7286bd5a 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/csharp/CSharpStructuresDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/csharp/CSharpStructuresDetector.java @@ -211,7 +211,7 @@ protected DetectorResult detectWithRegex(DetectorContext ctx) { String path = httpPath != null ? httpPath : ""; String fullPath; if (finalClassRoute != null) { - fullPath = "/" + finalClassRoute.replaceAll("^/+|/+$", ""); + fullPath = "/" + finalClassRoute.replaceAll("(^/+|/+$)", ""); if (!path.isEmpty()) fullPath = fullPath + "/" + path.replaceAll("^/+", ""); } else { fullPath = !path.isEmpty() ? "/" + path.replaceAll("^/+", "") : "/"; diff --git a/src/main/java/io/github/randomcodespace/iq/detector/java/GrpcServiceDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/java/GrpcServiceDetector.java index b6492cff..d73ff670 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/java/GrpcServiceDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/java/GrpcServiceDetector.java @@ -35,7 +35,7 @@ public class GrpcServiceDetector extends AbstractRegexDetector { private static final Pattern METHOD_RE = Pattern.compile( "public\\s+(?:void|[\\w<>\\[\\]]+)\\s+(\\w+)\\s*\\(\\s*(\\w+)"); private static final Pattern GRPC_STUB_RE = Pattern.compile( - "(\\w+)Grpc\\.new(?:Blocking|Future|)Stub\\s*\\("); + "(\\w+)Grpc\\.new(?:Blocking|Future)?Stub\\s*\\("); @Override public String getName() { diff --git a/src/main/java/io/github/randomcodespace/iq/detector/java/JpaEntityDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/java/JpaEntityDetector.java index 26819f5a..00643190 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/java/JpaEntityDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/java/JpaEntityDetector.java @@ -176,8 +176,8 @@ private DetectorResult detectWithAst(CompilationUnit cu, DetectorContext ctx) { } else { targetEntity = cit.getNameAsString(); } + break; } - break; } } diff --git a/src/test/java/io/github/randomcodespace/iq/intelligence/query/CapabilityMatrixTest.java b/src/test/java/io/github/randomcodespace/iq/intelligence/query/CapabilityMatrixTest.java index 2c6468b5..a99904f6 100644 --- a/src/test/java/io/github/randomcodespace/iq/intelligence/query/CapabilityMatrixTest.java +++ b/src/test/java/io/github/randomcodespace/iq/intelligence/query/CapabilityMatrixTest.java @@ -102,13 +102,13 @@ void shell_ormEntityMapping_isUnsupported() { @Test void unknownLanguage_allUnsupported() { Map caps = CapabilityMatrix.forLanguage("brainfuck"); - assertThat(caps.values()).allMatch(l -> l == CapabilityLevel.UNSUPPORTED); + assertThat(caps.values()).isNotEmpty().allMatch(l -> l == CapabilityLevel.UNSUPPORTED); } @Test void nullLanguage_allUnsupported() { Map caps = CapabilityMatrix.forLanguage(null); - assertThat(caps.values()).allMatch(l -> l == CapabilityLevel.UNSUPPORTED); + assertThat(caps.values()).isNotEmpty().allMatch(l -> l == CapabilityLevel.UNSUPPORTED); } @Test From 3482b222efc8ab39f5118928b3b29b1990da68fe Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Sat, 4 Apr 2026 06:42:56 +0000 Subject: [PATCH 3/3] fix(sonar): S5998 ReDoS regex bounds + S3077 AtomicReference for thread safety + S6856 bind path variable Co-Authored-By: Claude Sonnet 4.6 --- .../iq/api/TopologyController.java | 64 ++++++++++--------- .../iq/detector/java/JdbcDetector.java | 2 +- .../iq/detector/java/RawSqlDetector.java | 6 +- .../typescript/NestJSControllerDetector.java | 4 +- .../randomcodespace/iq/web/SpaController.java | 3 +- 5 files changed, 42 insertions(+), 37 deletions(-) diff --git a/src/main/java/io/github/randomcodespace/iq/api/TopologyController.java b/src/main/java/io/github/randomcodespace/iq/api/TopologyController.java index 707f1b95..d6d6ae8f 100644 --- a/src/main/java/io/github/randomcodespace/iq/api/TopologyController.java +++ b/src/main/java/io/github/randomcodespace/iq/api/TopologyController.java @@ -20,6 +20,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; /** * REST API controller for service topology queries. @@ -32,8 +33,8 @@ public class TopologyController { private final TopologyService topologyService; private final GraphStore graphStore; private final CodeIqConfig config; - private volatile List cachedNodes; - private volatile List cachedEdges; + private final AtomicReference> cachedNodes = new AtomicReference<>(); + private final AtomicReference> cachedEdges = new AtomicReference<>(); public TopologyController(TopologyService topologyService, @Autowired(required = false) GraphStore graphStore, @@ -65,15 +66,16 @@ private boolean hasNeo4jData() { * Load data from Neo4j if available, otherwise from H2 cache. */ private synchronized void ensureDataLoaded() { - if (cachedNodes != null) return; + if (cachedNodes.get() != null) return; // Try Neo4j first (has enriched data with SERVICE nodes) if (hasNeo4jData()) { - cachedNodes = graphStore.findAll(); + List nodes = graphStore.findAll(); + cachedNodes.set(nodes); // Collect edges from all nodes' relationship lists - cachedEdges = cachedNodes.stream() + cachedEdges.set(nodes.stream() .flatMap(n -> n.getEdges().stream()) - .toList(); + .toList()); return; } @@ -83,8 +85,8 @@ private synchronized void ensureDataLoaded() { Path h2File = root.resolve(config.getCacheDir()).resolve("analysis-cache.mv.db"); if (!Files.exists(h2File)) return; try (AnalysisCache cache = new AnalysisCache(cachePath)) { - cachedNodes = cache.loadAllNodes(); - cachedEdges = cache.loadAllEdges(); + cachedNodes.set(cache.loadAllNodes()); + cachedEdges.set(cache.loadAllEdges()); } } @@ -92,44 +94,44 @@ private synchronized void ensureDataLoaded() { * Invalidate the in-memory cache (e.g. after re-analysis). */ public synchronized void invalidateCache() { - cachedNodes = null; - cachedEdges = null; + cachedNodes.set(null); + cachedEdges.set(null); neo4jHasData = null; } @GetMapping public Map getTopology() { ensureDataLoaded(); - requireCache(); - return topologyService.getTopology(cachedNodes, cachedEdges); + List nodes = requireCache(); + return topologyService.getTopology(nodes, cachedEdges.get()); } @GetMapping("/services/{name}") public Map serviceDetail(@PathVariable String name) { ensureDataLoaded(); - requireCache(); - return topologyService.serviceDetail(name, cachedNodes, cachedEdges); + List nodes = requireCache(); + return topologyService.serviceDetail(name, nodes, cachedEdges.get()); } @GetMapping("/services/{name}/deps") public Map serviceDependencies(@PathVariable String name) { ensureDataLoaded(); - requireCache(); - return topologyService.serviceDependencies(name, cachedNodes, cachedEdges); + List nodes = requireCache(); + return topologyService.serviceDependencies(name, nodes, cachedEdges.get()); } @GetMapping("/services/{name}/dependents") public Map serviceDependents(@PathVariable String name) { ensureDataLoaded(); - requireCache(); - return topologyService.serviceDependents(name, cachedNodes, cachedEdges); + List nodes = requireCache(); + return topologyService.serviceDependents(name, nodes, cachedEdges.get()); } @GetMapping("/blast-radius/{nodeId}") public Map blastRadius(@PathVariable String nodeId) { ensureDataLoaded(); - requireCache(); - return topologyService.blastRadius(nodeId, cachedNodes, cachedEdges); + List nodes = requireCache(); + return topologyService.blastRadius(nodeId, nodes, cachedEdges.get()); } @GetMapping("/path") @@ -137,35 +139,37 @@ public List> findPath( @RequestParam("from") String source, @RequestParam("to") String target) { ensureDataLoaded(); - requireCache(); - return topologyService.findPath(source, target, cachedNodes, cachedEdges); + List nodes = requireCache(); + return topologyService.findPath(source, target, nodes, cachedEdges.get()); } @GetMapping("/bottlenecks") public List> findBottlenecks() { ensureDataLoaded(); - requireCache(); - return topologyService.findBottlenecks(cachedNodes, cachedEdges); + List nodes = requireCache(); + return topologyService.findBottlenecks(nodes, cachedEdges.get()); } @GetMapping("/circular") public List> findCircularDeps() { ensureDataLoaded(); - requireCache(); - return topologyService.findCircularDeps(cachedNodes, cachedEdges); + List nodes = requireCache(); + return topologyService.findCircularDeps(nodes, cachedEdges.get()); } @GetMapping("/dead") public List> findDeadServices() { ensureDataLoaded(); - requireCache(); - return topologyService.findDeadServices(cachedNodes, cachedEdges); + List nodes = requireCache(); + return topologyService.findDeadServices(nodes, cachedEdges.get()); } - private void requireCache() { - if (cachedNodes == null) { + private List requireCache() { + List nodes = cachedNodes.get(); + if (nodes == null) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "No analysis cache found. Run analyze first."); } + return nodes; } } diff --git a/src/main/java/io/github/randomcodespace/iq/detector/java/JdbcDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/java/JdbcDetector.java index f59046dc..56efab65 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/java/JdbcDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/java/JdbcDetector.java @@ -33,7 +33,7 @@ public class JdbcDetector extends AbstractRegexDetector { private static final Pattern DRIVER_MANAGER_RE = Pattern.compile( "DriverManager\\s*\\.\\s*getConnection\\s*\\(\\s*\"(jdbc:[^\"]+)\""); private static final Pattern JDBC_TEMPLATE_RE = Pattern.compile( - "(?:private|protected|public|final|\\s)+" + "(?:private|protected|public|final)\\s+" + "(?:final\\s+)?" + "(JdbcTemplate|NamedParameterJdbcTemplate|JdbcClient)" + "\\s+\\w+"); diff --git a/src/main/java/io/github/randomcodespace/iq/detector/java/RawSqlDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/java/RawSqlDetector.java index 6f402d65..1d4e48fd 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/java/RawSqlDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/java/RawSqlDetector.java @@ -27,14 +27,14 @@ public class RawSqlDetector extends AbstractRegexDetector { private static final Pattern CLASS_RE = Pattern.compile("(?:public\\s+)?class\\s+(\\w+)"); private static final Pattern QUERY_ANNO_RE = Pattern.compile( - "@Query\\s*\\(\\s*(?:value\\s*=\\s*)?\"((?:[^\"\\\\]|\\\\.)*)\"", Pattern.DOTALL); + "@Query\\s*\\(\\s*(?:value\\s*=\\s*)?\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"", Pattern.DOTALL); private static final Pattern NATIVE_QUERY_RE = Pattern.compile("nativeQuery\\s*=\\s*true"); private static final Pattern JDBC_TEMPLATE_RE = Pattern.compile( "(?:jdbcTemplate|namedParameterJdbcTemplate|JdbcTemplate)\\s*\\." + "(?:query|queryForObject|queryForList|queryForMap|update|execute|batchUpdate)" - + "\\s*\\(\\s*\"((?:[^\"\\\\]|\\\\.)*)\"", Pattern.DOTALL); + + "\\s*\\(\\s*\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"", Pattern.DOTALL); private static final Pattern EM_QUERY_RE = Pattern.compile( - "(?:entityManager|em)\\s*\\.(?:createNativeQuery|createQuery)\\s*\\(\\s*\"((?:[^\"\\\\]|\\\\.)*)\"", + "(?:entityManager|em)\\s*\\.(?:createNativeQuery|createQuery)\\s*\\(\\s*\"([^\"\\\\]*(?:\\\\.[^\"\\\\]*)*)\"", Pattern.DOTALL); private static final Pattern TABLE_REF_RE = Pattern.compile( "\\b(?:FROM|JOIN|INTO|UPDATE|TABLE)\\s+(\\w+)", Pattern.CASE_INSENSITIVE); diff --git a/src/main/java/io/github/randomcodespace/iq/detector/typescript/NestJSControllerDetector.java b/src/main/java/io/github/randomcodespace/iq/detector/typescript/NestJSControllerDetector.java index 593b0372..155dc8cb 100644 --- a/src/main/java/io/github/randomcodespace/iq/detector/typescript/NestJSControllerDetector.java +++ b/src/main/java/io/github/randomcodespace/iq/detector/typescript/NestJSControllerDetector.java @@ -41,11 +41,11 @@ public class NestJSControllerDetector extends AbstractAntlrDetector { private static final Pattern NESTJS_IMPORT = Pattern.compile("from\\s+['\"]@nestjs/"); private static final Pattern CONTROLLER_PATTERN = Pattern.compile( - "@Controller\\(\\s*['\"`]?([^'\"`\\)\\s]*)['\"`]?\\s*\\)(?:\\s*@\\w+\\([^)]*\\))*\\s*\\n\\s*(?:export\\s+)?class\\s+(\\w+)" + "@Controller\\(\\s*['\"`]?([^'\"`\\)\\s]*)['\"`]?\\s*\\)(?:\\s*@\\w+\\([^)]{0,200}\\))*\\s*\\n\\s*(?:export\\s+)?class\\s+(\\w+)" ); private static final Pattern ROUTE_PATTERN = Pattern.compile( - "@(Get|Post|Put|Delete|Patch|Options|Head)\\(\\s*['\"`]?([^'\"`\\)\\s]*)['\"`]?\\s*\\)(?:\\s*@\\w+\\([^)]*\\))*\\s*\\n\\s*(?:async\\s+)?(\\w+)" + "@(Get|Post|Put|Delete|Patch|Options|Head)\\(\\s*['\"`]?([^'\"`\\)\\s]*)['\"`]?\\s*\\)(?:\\s*@\\w+\\([^)]{0,200}\\))*\\s*\\n\\s*(?:async\\s+)?(\\w+)" ); @Override diff --git a/src/main/java/io/github/randomcodespace/iq/web/SpaController.java b/src/main/java/io/github/randomcodespace/iq/web/SpaController.java index cd198449..be9c7d50 100644 --- a/src/main/java/io/github/randomcodespace/iq/web/SpaController.java +++ b/src/main/java/io/github/randomcodespace/iq/web/SpaController.java @@ -4,6 +4,7 @@ import org.springframework.context.annotation.Profile; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; /** * Catch-all controller that forwards unmatched routes to index.html @@ -40,7 +41,7 @@ public String forward() { * Does NOT match /api/**, /mcp/**, /actuator/**, or static assets. */ @GetMapping("/{path:[^\\.]*}") - public String catchAll() { + public String catchAll(@PathVariable String path) { return "forward:/index.html"; } }