From a49085a70e87424e4e135f0705b1060cafd78949 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Mon, 21 Oct 2024 16:52:06 +0530 Subject: [PATCH 01/11] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 3b2102795a..66f8ee6cbc 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -255,7 +255,7 @@ modules = [ [[package]] org = "ballerina" name = "mime" -version = "2.10.1" +version = "2.10.0" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, From dbe767f69591c61e66baf9cdca9bab61e7e26e61 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Wed, 23 Oct 2024 12:09:43 +0530 Subject: [PATCH 02/11] [Automated] Update the native jar versions --- ballerina/Dependencies.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 072f064454..9e2dd49f9d 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -255,7 +255,7 @@ modules = [ [[package]] org = "ballerina" name = "mime" -version = "2.10.0" +version = "2.10.1" dependencies = [ {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, From 68333fb039934ac3c22105884e8711eea597f1d4 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Fri, 25 Oct 2024 17:58:41 +0530 Subject: [PATCH 03/11] Add static code analyzer rules --- .gitignore | 2 + compiler-plugin-tests/build.gradle | 1 + .../StaticCodeAnalyzerTest.java | 120 +++++++++++++ .../ballerina_packages/rule1/Ballerina.toml | 4 + .../ballerina_packages/rule1/service.bal | 27 +++ .../rule1/service_class.bal | 29 ++++ .../rule1/service_object.bal | 29 ++++ .../ballerina_packages/rule2/Ballerina.toml | 4 + .../ballerina_packages/rule2/service.bal | 34 ++++ .../rule2/service_class.bal | 30 ++++ .../rule2/service_object.bal | 49 ++++++ .../expected_output/rule1.json | 162 ++++++++++++++++++ .../expected_output/rule2.json | 142 +++++++++++++++ compiler-plugin/build.gradle | 2 + .../stdlib/http/compiler/Constants.java | 2 + .../http/compiler/HttpCompilerPlugin.java | 8 + .../http/compiler/HttpCompilerPluginUtil.java | 76 ++++++++ .../HttpAnnotationAnalyzer.java | 97 +++++++++++ .../compiler/staticcodeanalyzer/HttpRule.java | 52 ++++++ .../HttpServiceAnalyzer.java | 46 +++++ .../HttpServiceClassAnalyzer.java | 41 +++++ .../HttpServiceDeclarationAnalyzer.java | 41 +++++ .../HttpServiceObjectTypeAnalyzer.java | 43 +++++ .../HttpStaticCodeAnalyzer.java | 47 +++++ .../staticcodeanalyzer/RuleFactory.java | 31 ++++ .../compiler/staticcodeanalyzer/RuleImpl.java | 54 ++++++ .../src/main/java/module-info.java | 1 + compiler-plugin/src/main/resources/rules.json | 12 ++ gradle.properties | 2 + 29 files changed, 1188 insertions(+) create mode 100644 compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_class.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_object.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/Ballerina.toml create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_class.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_object.bal create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json create mode 100644 compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule2.json create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpAnnotationAnalyzer.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpRule.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceAnalyzer.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceClassAnalyzer.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceDeclarationAnalyzer.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceObjectTypeAnalyzer.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpStaticCodeAnalyzer.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/RuleFactory.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/RuleImpl.java create mode 100644 compiler-plugin/src/main/resources/rules.json diff --git a/.gitignore b/.gitignore index 7cb31afdd1..80cbfc54d3 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ load-tests/**/Dependencies.toml # Ballerina related ignores Ballerina.lock velocity.log* + +compiler-plugin-tests/**/target diff --git a/compiler-plugin-tests/build.gradle b/compiler-plugin-tests/build.gradle index 73c3f3cd25..11b9ca0de0 100644 --- a/compiler-plugin-tests/build.gradle +++ b/compiler-plugin-tests/build.gradle @@ -90,6 +90,7 @@ test { systemProperty "ballerina.offline.flag", "true" useTestNG() finalizedBy jacocoTestReport + testLogging.showStandardStreams = true } jacocoTestReport { diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java new file mode 100644 index 0000000000..31db4e29ff --- /dev/null +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import org.testng.Assert; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.Test; +import org.testng.internal.ExitCode; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.stream.Collectors; + +/** + * This class includes tests for Ballerina Http static code analyzer. + */ +class StaticCodeAnalyzerTest { + + private static final Path RESOURCE_PACKAGES_DIRECTORY = Paths + .get("src", "test", "resources", "static_code_analyzer", "ballerina_packages").toAbsolutePath(); + private static final Path EXPECTED_JSON_OUTPUT_DIRECTORY = Paths. + get("src", "test", "resources", "static_code_analyzer", "expected_output").toAbsolutePath(); + private static final Path BALLERINA_PATH = Paths + .get("../", "target", "ballerina-runtime", "bin", "bal").toAbsolutePath(); + private static final Path JSON_RULES_FILE_PATH = Paths + .get("../", "compiler-plugin", "src", "main", "resources", "rules.json").toAbsolutePath(); + private static final String SCAN_COMMAND = "scan"; + + @BeforeSuite + public void pullScanTool() throws IOException, InterruptedException { + String scanToolVersion = "0.1.0"; + ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), + "tool", "pull", SCAN_COMMAND + ":" + scanToolVersion, "--repository=local"); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); + String output = convertInputStreamToString(process.getInputStream()); + if (output.startsWith("tool '" + SCAN_COMMAND + ":" + scanToolVersion + "' is already active.")) { + return; + } + Assert.assertFalse(ExitCode.hasFailure(exitCode)); + } + + @Test + public void validateRulesJson() throws IOException { + String expectedRules = "[" + Arrays.stream(HttpRule.values()) + .map(HttpRule::toString).collect(Collectors.joining(",")) + "]"; + String actualRules = Files.readString(JSON_RULES_FILE_PATH); + assertJsonEqual(normalizeJson(actualRules), normalizeJson(expectedRules)); + } + + @Test + public void testStaticCodeRules() throws IOException, InterruptedException { + for (HttpRule rule : HttpRule.values()) { + String targetPackageName = "rule" + rule.getId(); + String actualJsonReport = StaticCodeAnalyzerTest.executeScanProcess(targetPackageName); + String expectedJsonReport = Files + .readString(EXPECTED_JSON_OUTPUT_DIRECTORY.resolve(targetPackageName + ".json")); + assertJsonEqual(actualJsonReport, expectedJsonReport); + } + } + + public static String executeScanProcess(String targetPackage) throws IOException, InterruptedException { + ProcessBuilder processBuilder2 = new ProcessBuilder(BALLERINA_PATH.toString(), SCAN_COMMAND); + processBuilder2.directory(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage).toFile()); + Process process2 = processBuilder2.start(); + int exitCode = process2.waitFor(); + Assert.assertFalse(ExitCode.hasFailure(exitCode)); + return Files.readString(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage) + .resolve("target").resolve("report").resolve("scan_results.json")); + } + + public static String convertInputStreamToString(InputStream inputStream) throws IOException { + StringBuilder stringBuilder = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line).append(System.lineSeparator()); + } + } + return stringBuilder.toString(); + } + + private void assertJsonEqual(String actual, String expected) { + Assert.assertEquals(normalizeJson(actual), normalizeJson(expected)); + } + + private static String normalizeJson(String json) { + return json.replaceAll("\\s*\"\\s*", "\"") + .replaceAll("\\s*:\\s*", ":") + .replaceAll("\\s*,\\s*", ",") + .replaceAll("\\s*\\{\\s*", "{") + .replaceAll("\\s*}\\s*", "}") + .replaceAll("\\s*\\[\\s*", "[") + .replaceAll("\\s*]\\s*", "]") + .replaceAll("\n", ""); + } +} diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml new file mode 100644 index 0000000000..a2262d50af --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "ballerina" +name = "rule1" +version = "0.1.0" diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service.bal new file mode 100644 index 0000000000..2490eefb23 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service.bal @@ -0,0 +1,27 @@ +// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; + +service on new http:Listener(8080) { + resource function default .() returns string? { + return; + } + + resource function default greet() returns string? { + return; + } +}; diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_class.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_class.bal new file mode 100644 index 0000000000..9020df9eef --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_class.bal @@ -0,0 +1,29 @@ +// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; + +service class GreetingService { + *http:Service; + + resource function default .() returns string { + return ""; + } + + resource function default greet() returns string { + return ""; + } +}; diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_object.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_object.bal new file mode 100644 index 0000000000..9ede2843d3 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_object.bal @@ -0,0 +1,29 @@ +// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; + +type ServiceContract service object { + *http:Service; + resource function default .() returns string; + resource function default greet() returns string; +}; + +type MyContract service object { + *http:ServiceContract; + resource function default .() returns string; + resource function default greet() returns string; +}; diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/Ballerina.toml b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/Ballerina.toml new file mode 100644 index 0000000000..adf0cc8b06 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/Ballerina.toml @@ -0,0 +1,4 @@ +[package] +org = "ballerina" +name = "rule2" +version = "0.1.0" diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service.bal new file mode 100644 index 0000000000..8e095f9d41 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service.bal @@ -0,0 +1,34 @@ +// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; + +@http:ServiceConfig { + cors: { + allowOrigins: ["*"] + } +} +service on new http:Listener(8080) { + + @http:ResourceConfig { + cors: { + allowOrigins: ["*"] + } + } + resource function get greet() returns string? { + return; + } +}; diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_class.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_class.bal new file mode 100644 index 0000000000..c8779ff6a1 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_class.bal @@ -0,0 +1,30 @@ +// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; + +service class GreetingService { + *http:Service; + + @http:ResourceConfig { + cors: { + allowOrigins: ["*"] + } + } + resource function get .() returns string? { + return; + } +}; diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_object.bal b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_object.bal new file mode 100644 index 0000000000..a7c1f6ccd8 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_object.bal @@ -0,0 +1,49 @@ +// Copyright (c) 2024 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/http; + +@http:ServiceConfig { + cors: { + allowOrigins: ["*"] + } +} +type ServiceContract service object { + *http:Service; + + @http:ResourceConfig { + cors: { + allowOrigins: ["*"] + } + } + resource function get greet() returns string; +}; + +@http:ServiceConfig { + cors: { + allowOrigins: ["*"] + } +} +type MyContract service object { + *http:ServiceContract; + + @http:ResourceConfig { + cors: { + allowOrigins: ["*"] + } + } + resource function get greet() returns string; +}; diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json new file mode 100644 index 0000000000..0e9afd7895 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule1.json @@ -0,0 +1,162 @@ +[ + { + "location": { + "filePath": "service.bal", + "startLine": 19, + "endLine": 19, + "startColumn": 22, + "endColumn": 29, + "startOffset": 728, + "length": 7 + }, + "rule": { + "id": "ballerina/http:1", + "numericId": 1, + "description": "Avoid allowing default resource accessor", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/service.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service.bal" + }, + { + "location": { + "filePath": "service.bal", + "startLine": 23, + "endLine": 23, + "startColumn": 22, + "endColumn": 29, + "startOffset": 803, + "length": 7 + }, + "rule": { + "id": "ballerina/http:1", + "numericId": 1, + "description": "Avoid allowing default resource accessor", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/service.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service.bal" + }, + { + "location": { + "filePath": "service_class.bal", + "startLine": 21, + "endLine": 21, + "startColumn": 22, + "endColumn": 29, + "startOffset": 743, + "length": 7 + }, + "rule": { + "id": "ballerina/http:1", + "numericId": 1, + "description": "Avoid allowing default resource accessor", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/service_class.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_class.bal" + }, + { + "location": { + "filePath": "service_class.bal", + "startLine": 25, + "endLine": 25, + "startColumn": 22, + "endColumn": 29, + "startOffset": 820, + "length": 7 + }, + "rule": { + "id": "ballerina/http:1", + "numericId": 1, + "description": "Avoid allowing default resource accessor", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/service_class.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_class.bal" + }, + { + "location": { + "filePath": "service_object.bal", + "startLine": 20, + "endLine": 20, + "startColumn": 22, + "endColumn": 29, + "startOffset": 748, + "length": 7 + }, + "rule": { + "id": "ballerina/http:1", + "numericId": 1, + "description": "Avoid allowing default resource accessor", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/service_object.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_object.bal" + }, + { + "location": { + "filePath": "service_object.bal", + "startLine": 21, + "endLine": 21, + "startColumn": 22, + "endColumn": 29, + "startOffset": 798, + "length": 7 + }, + "rule": { + "id": "ballerina/http:1", + "numericId": 1, + "description": "Avoid allowing default resource accessor", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/service_object.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_object.bal" + }, + { + "location": { + "filePath": "service_object.bal", + "startLine": 26, + "endLine": 26, + "startColumn": 22, + "endColumn": 29, + "startOffset": 916, + "length": 7 + }, + "rule": { + "id": "ballerina/http:1", + "numericId": 1, + "description": "Avoid allowing default resource accessor", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/service_object.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_object.bal" + }, + { + "location": { + "filePath": "service_object.bal", + "startLine": 27, + "endLine": 27, + "startColumn": 22, + "endColumn": 29, + "startOffset": 966, + "length": 7 + }, + "rule": { + "id": "ballerina/http:1", + "numericId": 1, + "description": "Avoid allowing default resource accessor", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule1/service_object.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule1/service_object.bal" + } +] diff --git a/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule2.json b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule2.json new file mode 100644 index 0000000000..00608dc943 --- /dev/null +++ b/compiler-plugin-tests/src/test/resources/static_code_analyzer/expected_output/rule2.json @@ -0,0 +1,142 @@ +[ + { + "location": { + "filePath": "service.bal", + "startLine": 20, + "endLine": 20, + "startColumn": 23, + "endColumn": 26, + "startOffset": 726, + "length": 3 + }, + "rule": { + "id": "ballerina/http:2", + "numericId": 2, + "description": "Avoid permissive Cross-Origin Resource Sharing", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/service.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service.bal" + }, + { + "location": { + "filePath": "service.bal", + "startLine": 27, + "endLine": 27, + "startColumn": 27, + "endColumn": 30, + "startOffset": 847, + "length": 3 + }, + "rule": { + "id": "ballerina/http:2", + "numericId": 2, + "description": "Avoid permissive Cross-Origin Resource Sharing", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/service.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service.bal" + }, + { + "location": { + "filePath": "service_class.bal", + "startLine": 23, + "endLine": 23, + "startColumn": 27, + "endColumn": 30, + "startOffset": 791, + "length": 3 + }, + "rule": { + "id": "ballerina/http:2", + "numericId": 2, + "description": "Avoid permissive Cross-Origin Resource Sharing", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/service_class.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_class.bal" + }, + { + "location": { + "filePath": "service_object.bal", + "startLine": 20, + "endLine": 20, + "startColumn": 23, + "endColumn": 26, + "startOffset": 726, + "length": 3 + }, + "rule": { + "id": "ballerina/http:2", + "numericId": 2, + "description": "Avoid permissive Cross-Origin Resource Sharing", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/service_object.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_object.bal" + }, + { + "location": { + "filePath": "service_object.bal", + "startLine": 28, + "endLine": 28, + "startColumn": 27, + "endColumn": 30, + "startOffset": 867, + "length": 3 + }, + "rule": { + "id": "ballerina/http:2", + "numericId": 2, + "description": "Avoid permissive Cross-Origin Resource Sharing", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/service_object.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_object.bal" + }, + { + "location": { + "filePath": "service_object.bal", + "startLine": 36, + "endLine": 36, + "startColumn": 23, + "endColumn": 26, + "startOffset": 999, + "length": 3 + }, + "rule": { + "id": "ballerina/http:2", + "numericId": 2, + "description": "Avoid permissive Cross-Origin Resource Sharing", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/service_object.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_object.bal" + }, + { + "location": { + "filePath": "service_object.bal", + "startLine": 44, + "endLine": 44, + "startColumn": 27, + "endColumn": 30, + "startOffset": 1143, + "length": 3 + }, + "rule": { + "id": "ballerina/http:2", + "numericId": 2, + "description": "Avoid permissive Cross-Origin Resource Sharing", + "ruleKind": "VULNERABILITY" + }, + "source": "BUILT_IN", + "fileName": "rule2/service_object.bal", + "filePath": "/Users/admin/Desktop/WORKSPACE/module-ballerina-http/compiler-plugin-tests/src/test/resources/static_code_analyzer/ballerina_packages/rule2/service_object.bal" + } +] diff --git a/compiler-plugin/build.gradle b/compiler-plugin/build.gradle index e1c254571c..3578555a5f 100644 --- a/compiler-plugin/build.gradle +++ b/compiler-plugin/build.gradle @@ -38,6 +38,8 @@ dependencies { implementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}" implementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}" implementation group: 'io.ballerina.openapi', name: 'ballerina-to-openapi', version: "${ballerinaToOpenApiVersion}" + // TODO: publish it to github, currenlty obtained from mavenLocal() + implementation group: 'io.ballerina.scan', name: 'scan-command', version: "${balScanVersion}" externalJars group: 'io.ballerina.openapi', name: 'ballerina-to-openapi', version: "${ballerinaToOpenApiVersion}" } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/Constants.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/Constants.java index 640e9c37d9..8e51c2557a 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/Constants.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/Constants.java @@ -125,4 +125,6 @@ private Constants() {} public static final String SUFFIX_SEPARATOR_REGEX = "\\+"; public static final String MEDIA_TYPE_SUBTYPE_REGEX = "^(\\w)+(\\s*\\.\\s*(\\w)+)*(\\s*\\+\\s*(\\w)+)*"; public static final String UNNECESSARY_CHARS_REGEX = "^'|\"|\\n"; + + public static final String SCANNER_CONTEXT = "ScannerContext"; } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java index 5936bfa6a1..1fd8cb019d 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPlugin.java @@ -23,6 +23,7 @@ import io.ballerina.projects.plugins.CompilerPluginContext; import io.ballerina.projects.plugins.codeaction.CodeAction; import io.ballerina.projects.plugins.completion.CompletionProvider; +import io.ballerina.scan.ScannerContext; import io.ballerina.stdlib.http.compiler.codeaction.AddHeaderParameterCodeAction; import io.ballerina.stdlib.http.compiler.codeaction.AddInterceptorRemoteMethodCodeAction; import io.ballerina.stdlib.http.compiler.codeaction.AddInterceptorResourceMethodCodeAction; @@ -35,10 +36,13 @@ import io.ballerina.stdlib.http.compiler.codeaction.ImplementServiceContract; import io.ballerina.stdlib.http.compiler.codemodifier.HttpServiceModifier; import io.ballerina.stdlib.http.compiler.completion.HttpServiceBodyContextProvider; +import io.ballerina.stdlib.http.compiler.staticcodeanalyzer.HttpStaticCodeAnalyzer; import java.util.List; import java.util.Map; +import static io.ballerina.stdlib.http.compiler.Constants.SCANNER_CONTEXT; + /** * The compiler plugin implementation for Ballerina Http package. */ @@ -52,6 +56,10 @@ public void init(CompilerPluginContext context) { context.addCodeAnalyzer(new HttpServiceAnalyzer(ctxData)); getCodeActions().forEach(context::addCodeAction); getCompletionProviders().forEach(context::addCompletionProvider); + Object object = context.userData().get(SCANNER_CONTEXT); + if (object instanceof ScannerContext scannerContext) { + context.addCodeAnalyzer(new HttpStaticCodeAnalyzer(scannerContext.getReporter())); + } } private List getCodeActions() { diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java index 2db9d66682..b82355265b 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java @@ -20,6 +20,7 @@ import io.ballerina.compiler.api.SemanticModel; import io.ballerina.compiler.api.Types; +import io.ballerina.compiler.api.symbols.ClassSymbol; import io.ballerina.compiler.api.symbols.FunctionSymbol; import io.ballerina.compiler.api.symbols.FunctionTypeSymbol; import io.ballerina.compiler.api.symbols.IntersectionTypeSymbol; @@ -32,13 +33,16 @@ import io.ballerina.compiler.api.symbols.TypeSymbol; import io.ballerina.compiler.api.symbols.UnionTypeSymbol; import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.ClassDefinitionNode; import io.ballerina.compiler.syntax.tree.FunctionDefinitionNode; +import io.ballerina.compiler.syntax.tree.MethodDeclarationNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.ReturnTypeDescriptorNode; import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.projects.Document; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; import io.ballerina.tools.diagnostics.Diagnostic; import io.ballerina.tools.diagnostics.DiagnosticFactory; @@ -47,12 +51,19 @@ import io.ballerina.tools.diagnostics.DiagnosticSeverity; import io.ballerina.tools.diagnostics.Location; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; +import static io.ballerina.compiler.api.symbols.SymbolKind.TYPE_DEFINITION; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.ANNOTATION; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.AT_TOKEN; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.CLASS_DEFINITION; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.RESOURCE_ACCESSOR_DECLARATION; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.RESOURCE_ACCESSOR_DEFINITION; import static io.ballerina.stdlib.http.compiler.Constants.ANYDATA; import static io.ballerina.stdlib.http.compiler.Constants.ARRAY_OF_MAP_OF_ANYDATA; import static io.ballerina.stdlib.http.compiler.Constants.BALLERINA; @@ -60,8 +71,10 @@ import static io.ballerina.stdlib.http.compiler.Constants.BOOLEAN_ARRAY; import static io.ballerina.stdlib.http.compiler.Constants.BYTE_ARRAY; import static io.ballerina.stdlib.http.compiler.Constants.CALLER_OBJ_NAME; +import static io.ballerina.stdlib.http.compiler.Constants.COLON; import static io.ballerina.stdlib.http.compiler.Constants.DECIMAL; import static io.ballerina.stdlib.http.compiler.Constants.DECIMAL_ARRAY; +import static io.ballerina.stdlib.http.compiler.Constants.DEFAULT; import static io.ballerina.stdlib.http.compiler.Constants.EMPTY; import static io.ballerina.stdlib.http.compiler.Constants.ERROR; import static io.ballerina.stdlib.http.compiler.Constants.FLOAT; @@ -92,6 +105,7 @@ import static io.ballerina.stdlib.http.compiler.Constants.REQUEST_OBJ_NAME; import static io.ballerina.stdlib.http.compiler.Constants.RESOURCE_RETURN_TYPE; import static io.ballerina.stdlib.http.compiler.Constants.RESPONSE_OBJ_NAME; +import static io.ballerina.stdlib.http.compiler.Constants.SERVICE_KEYWORD; import static io.ballerina.stdlib.http.compiler.Constants.STRING; import static io.ballerina.stdlib.http.compiler.Constants.STRING_ARRAY; import static io.ballerina.stdlib.http.compiler.Constants.STRUCTURED_ARRAY; @@ -348,6 +362,47 @@ public static ServiceDeclarationNode getServiceDeclarationNode(Node node, Semant return serviceDeclarationNode; } + public static ClassDefinitionNode getServiceClassDefinitionNode(SyntaxNodeAnalysisContext ctx) { + if (ctx.node().kind() != CLASS_DEFINITION) { + return null; + } + ClassDefinitionNode classDefinitionNode = (ClassDefinitionNode) ctx.node(); + Optional serviceContractType = ctx.semanticModel().types() + .getTypeByName(BALLERINA, HTTP, EMPTY, HTTP_SERVICE_TYPE); + if (!hasServiceKeyWord(classDefinitionNode) || serviceContractType.isEmpty()) { + return null; + } + Optional symbol = ctx.semanticModel().symbol(classDefinitionNode); + if (symbol.isEmpty() || serviceContractType.get().kind() != TYPE_DEFINITION) { + return null; + } + ClassSymbol classSymbol = (ClassSymbol) symbol.get(); + TypeSymbol serviceType = ((TypeDefinitionSymbol) serviceContractType.get()).typeDescriptor(); + return classSymbol.subtypeOf(serviceType) ? classDefinitionNode : null; + } + + public static AnnotationNode getAnnotationNode(SyntaxNodeAnalysisContext context) { + if (context.node().kind() != ANNOTATION) { + return null; + } + Optional symbol = context.semanticModel().symbol(context.node()); + if (symbol.isPresent()) { + Optional module = symbol.get().getModule(); + if (module.isEmpty() || !isHttpModule(module.get())) { + return null; + } + return (AnnotationNode) context.node(); + } + // Added as a workaround for: https://github.com/ballerina-platform/ballerina-lang/issues/43525 + return context.node().toSourceCode().trim().startsWith(AT_TOKEN.stringValue() + HTTP + COLON) ? + (AnnotationNode) context.node() : null; + } + + private static boolean hasServiceKeyWord(ClassDefinitionNode classDefinitionNode) { + return classDefinitionNode.classTypeQualifiers() + .stream().anyMatch(token -> SERVICE_KEYWORD.equals(token.text().trim())); + } + private static boolean isListenerBelongsToHttpModule(TypeSymbol listenerType) { if (listenerType.typeKind() == TypeDescKind.UNION) { return ((UnionTypeSymbol) listenerType).memberTypeDescriptors().stream() @@ -387,4 +442,25 @@ public static boolean isHttpServiceType(SemanticModel semanticModel, Node typeNo return serviceObjTypeDef.typeDescriptor().subtypeOf(serviceContractTypeDef.typeDescriptor()); } + + + public static List getResourceMethodWithDefaultAccessor(NodeList members) { + List resourceFunctions = new ArrayList<>(); + for (Node member : members) { + if (member.kind() != RESOURCE_ACCESSOR_DEFINITION && member.kind() != RESOURCE_ACCESSOR_DECLARATION) { + continue; + } + ResourceFunction resourceFunction = member.kind() == RESOURCE_ACCESSOR_DEFINITION + ? new ResourceFunctionDefinition((FunctionDefinitionNode) member) + : new ResourceFunctionDeclaration((MethodDeclarationNode) member); + if (DEFAULT.equalsIgnoreCase(resourceFunction.functionName().text().trim())) { + resourceFunctions.add(resourceFunction); + } + } + return resourceFunctions; + } + + public static Document getDocument(SyntaxNodeAnalysisContext context) { + return context.currentPackage().module(context.moduleId()).document(context.documentId()); + } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpAnnotationAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpAnnotationAnalyzer.java new file mode 100644 index 0000000000..6b12328f52 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpAnnotationAnalyzer.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import io.ballerina.compiler.syntax.tree.AnnotationNode; +import io.ballerina.compiler.syntax.tree.ExpressionNode; +import io.ballerina.compiler.syntax.tree.ListConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.MappingConstructorExpressionNode; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.SpecificFieldNode; +import io.ballerina.compiler.syntax.tree.SyntaxKind; +import io.ballerina.projects.Document; +import io.ballerina.projects.plugins.AnalysisTask; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.scan.Reporter; +import io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil; + +import java.util.Optional; +import java.util.regex.Pattern; + +import static io.ballerina.stdlib.http.compiler.staticcodeanalyzer.HttpRule.AVOID_PERMISSIVE_CORS; + +class HttpAnnotationAnalyzer implements AnalysisTask { + private final Reporter reporter; + private static final String CORS_FIELD_NAME = "cors"; + private static final String ALLOW_ORIGINS_FIELD_NAME = "allowOrigins"; + public static final Pattern WILDCARD_ORIGIN = Pattern.compile("\"(\s*)\\*(\s*)\""); + + public HttpAnnotationAnalyzer(Reporter reporter) { + this.reporter = reporter; + } + + @Override + public void perform(SyntaxNodeAnalysisContext context) { + AnnotationNode annotationNode = HttpCompilerPluginUtil.getAnnotationNode(context); + if (annotationNode == null) { + return; + } + Optional annotationValue = annotationNode.annotValue(); + if (annotationValue.isEmpty()) { + return; + } + Document document = HttpCompilerPluginUtil.getDocument(context); + validateAnnotationValue(annotationValue.get(), document); + } + + private void validateAnnotationValue(MappingConstructorExpressionNode annotationValueMap, Document document) { + Optional corsField = findSpecificField(annotationValueMap, CORS_FIELD_NAME); + if (corsField.isEmpty() || corsField.get().valueExpr().isEmpty()) { + return; + } + ExpressionNode corsVal = corsField.get().valueExpr().get(); + if (corsVal.kind() != SyntaxKind.MAPPING_CONSTRUCTOR) { + return; + } + MappingConstructorExpressionNode corsMap = (MappingConstructorExpressionNode) corsVal; + Optional allowOrigins = findSpecificField(corsMap, ALLOW_ORIGINS_FIELD_NAME); + if (allowOrigins.isEmpty() || allowOrigins.get().valueExpr().isEmpty()) { + return; + } + ExpressionNode allowOriginsValue = allowOrigins.get().valueExpr().get(); + if (allowOriginsValue.kind() != SyntaxKind.LIST_CONSTRUCTOR) { + return; + } + checkForPermissiveCors((ListConstructorExpressionNode) allowOriginsValue, document); + } + + private Optional findSpecificField(MappingConstructorExpressionNode mapNode, String fieldName) { + return mapNode.fields().stream() + .filter(field -> field.kind() == SyntaxKind.SPECIFIC_FIELD).map(field -> (SpecificFieldNode) field) + .filter(field -> fieldName.equals(field.fieldName().toSourceCode().trim())).findFirst(); + } + + private void checkForPermissiveCors(ListConstructorExpressionNode allowedOrigins, Document document) { + for (Node exp : allowedOrigins.expressions()) { + if (WILDCARD_ORIGIN.matcher(exp.toSourceCode().trim()).find()) { + this.reporter.reportIssue(document, exp.location(), AVOID_PERMISSIVE_CORS.getId()); + } + } + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpRule.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpRule.java new file mode 100644 index 0000000000..2bba0a8cc4 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpRule.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import io.ballerina.scan.Rule; + +import static io.ballerina.scan.RuleKind.VULNERABILITY; +import static io.ballerina.stdlib.http.compiler.staticcodeanalyzer.RuleFactory.createRule; + +/** + * Represents static code rules specific to the Ballerina Http package. + */ +public enum HttpRule { + AVOID_DEFAULT_RESOURCE_ACCESSOR(createRule(1, "Avoid allowing default resource accessor", VULNERABILITY)), + AVOID_PERMISSIVE_CORS(createRule(2, "Avoid permissive Cross-Origin Resource Sharing", VULNERABILITY)); + + private final Rule rule; + + HttpRule(Rule rule) { + this.rule = rule; + } + + public int getId() { + return this.rule.numericId(); + } + + public Rule getRule() { + return this.rule; + } + + @Override + public String toString() { + return "{\"id\":" + this.getId() + ", \"kind\":\"" + this.rule.kind() + "\"," + + " \"description\" : \"" + this.rule.description() + "\"}"; + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceAnalyzer.java new file mode 100644 index 0000000000..87defdc497 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceAnalyzer.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.projects.Document; +import io.ballerina.projects.plugins.AnalysisTask; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.scan.Reporter; +import io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil; +import io.ballerina.tools.diagnostics.Location; + +import static io.ballerina.stdlib.http.compiler.staticcodeanalyzer.HttpRule.AVOID_DEFAULT_RESOURCE_ACCESSOR; + +abstract class HttpServiceAnalyzer implements AnalysisTask { + private final Reporter reporter; + + public HttpServiceAnalyzer(Reporter reporter) { + this.reporter = reporter; + } + + public void validateServiceMembers(NodeList members, Document document) { + // TODO: fix location, currently getting always -1 than expected + HttpCompilerPluginUtil.getResourceMethodWithDefaultAccessor(members).forEach(definition -> { + Location accessorLocation = definition.functionName().location(); + this.reporter.reportIssue(document, accessorLocation, AVOID_DEFAULT_RESOURCE_ACCESSOR.getId()); + }); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceClassAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceClassAnalyzer.java new file mode 100644 index 0000000000..a6e6842738 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceClassAnalyzer.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import io.ballerina.compiler.syntax.tree.ClassDefinitionNode; +import io.ballerina.projects.Document; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.scan.Reporter; +import io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil; + +class HttpServiceClassAnalyzer extends HttpServiceAnalyzer { + public HttpServiceClassAnalyzer(Reporter reporter) { + super(reporter); + } + + @Override + public void perform(SyntaxNodeAnalysisContext context) { + ClassDefinitionNode serviceClassDefinitionNode = HttpCompilerPluginUtil.getServiceClassDefinitionNode(context); + if (serviceClassDefinitionNode == null) { + return; + } + Document document = HttpCompilerPluginUtil.getDocument(context); + validateServiceMembers(serviceClassDefinitionNode.members(), document); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceDeclarationAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceDeclarationAnalyzer.java new file mode 100644 index 0000000000..a04988dcdc --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceDeclarationAnalyzer.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; +import io.ballerina.projects.Document; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.scan.Reporter; +import io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil; + +class HttpServiceDeclarationAnalyzer extends HttpServiceAnalyzer { + public HttpServiceDeclarationAnalyzer(Reporter reporter) { + super(reporter); + } + + @Override + public void perform(SyntaxNodeAnalysisContext context) { + ServiceDeclarationNode serviceDeclarationNode = HttpCompilerPluginUtil.getServiceDeclarationNode(context); + if (serviceDeclarationNode == null) { + return; + } + Document document = HttpCompilerPluginUtil.getDocument(context); + validateServiceMembers(serviceDeclarationNode.members(), document); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceObjectTypeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceObjectTypeAnalyzer.java new file mode 100644 index 0000000000..f9a46c1730 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceObjectTypeAnalyzer.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; +import io.ballerina.projects.Document; +import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; +import io.ballerina.scan.Reporter; +import io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil; + +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpServiceType; + +class HttpServiceObjectTypeAnalyzer extends HttpServiceAnalyzer { + public HttpServiceObjectTypeAnalyzer(Reporter reporter) { + super(reporter); + } + + @Override + public void perform(SyntaxNodeAnalysisContext context) { + if (!isHttpServiceType(context.semanticModel(), context.node())) { + return; + } + ObjectTypeDescriptorNode serviceObjectType = (ObjectTypeDescriptorNode) context.node(); + Document document = HttpCompilerPluginUtil.getDocument(context); + validateServiceMembers(serviceObjectType.members(), document); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpStaticCodeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpStaticCodeAnalyzer.java new file mode 100644 index 0000000000..22f03a3174 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpStaticCodeAnalyzer.java @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import io.ballerina.projects.plugins.CodeAnalysisContext; +import io.ballerina.projects.plugins.CodeAnalyzer; +import io.ballerina.scan.Reporter; + +import static io.ballerina.compiler.syntax.tree.SyntaxKind.ANNOTATION; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.CLASS_DEFINITION; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.OBJECT_TYPE_DESC; +import static io.ballerina.compiler.syntax.tree.SyntaxKind.SERVICE_DECLARATION; + +/** + * The static code analyzer implementation for Ballerina Http package. + */ +public class HttpStaticCodeAnalyzer extends CodeAnalyzer { + private final Reporter reporter; + + public HttpStaticCodeAnalyzer(Reporter reporter) { + this.reporter = reporter; + } + + @Override + public void init(CodeAnalysisContext analysisContext) { + analysisContext.addSyntaxNodeAnalysisTask(new HttpServiceDeclarationAnalyzer(reporter), SERVICE_DECLARATION); + analysisContext.addSyntaxNodeAnalysisTask(new HttpServiceObjectTypeAnalyzer(reporter), OBJECT_TYPE_DESC); + analysisContext.addSyntaxNodeAnalysisTask(new HttpServiceClassAnalyzer(reporter), CLASS_DEFINITION); + analysisContext.addSyntaxNodeAnalysisTask(new HttpAnnotationAnalyzer(reporter), ANNOTATION); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/RuleFactory.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/RuleFactory.java new file mode 100644 index 0000000000..81becd5e16 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/RuleFactory.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import io.ballerina.scan.Rule; +import io.ballerina.scan.RuleKind; + +/** + * {@code RuleFactory} contains the logic to create a {@link Rule}. + */ +public class RuleFactory { + public static Rule createRule(int id, String description, RuleKind kind) { + return new RuleImpl(id, description, kind); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/RuleImpl.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/RuleImpl.java new file mode 100644 index 0000000000..c03f2156c6 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/RuleImpl.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import io.ballerina.scan.Rule; +import io.ballerina.scan.RuleKind; + +class RuleImpl implements Rule { + private final int id; + private final String description; + private final RuleKind kind; + + RuleImpl(int id, String description, RuleKind kind) { + this.id = id; + this.description = description; + this.kind = kind; + } + + @Override + public String id() { + return Integer.toString(this.id); + } + + @Override + public int numericId() { + return this.id; + } + + @Override + public String description() { + return this.description; + } + + @Override + public RuleKind kind() { + return this.kind; + } +} diff --git a/compiler-plugin/src/main/java/module-info.java b/compiler-plugin/src/main/java/module-info.java index 66d065826f..29131747e7 100644 --- a/compiler-plugin/src/main/java/module-info.java +++ b/compiler-plugin/src/main/java/module-info.java @@ -23,4 +23,5 @@ requires io.swagger.v3.core; requires io.swagger.v3.oas.models; requires io.ballerina.openapi.service; + requires io.ballerina.scan; } diff --git a/compiler-plugin/src/main/resources/rules.json b/compiler-plugin/src/main/resources/rules.json new file mode 100644 index 0000000000..2774fb7821 --- /dev/null +++ b/compiler-plugin/src/main/resources/rules.json @@ -0,0 +1,12 @@ +[ + { + "id": 1, + "kind": "VULNERABILITY", + "description": "Avoid allowing default resource accessor" + }, + { + "id": 2, + "kind": "VULNERABILITY", + "description": "Avoid permissive Cross-Origin Resource Sharing" + } +] diff --git a/gradle.properties b/gradle.properties index c083c50c46..86b83b023e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -44,5 +44,7 @@ stdlibAuthVersion=2.12.0 stdlibJwtVersion=2.13.0 stdlibOAuth2Version=2.12.0 +balScanVersion=0.1.0 + observeVersion=1.3.0 observeInternalVersion=1.3.0 From edca1f0b6a1d36ac9be8b304e9b731e2780c8ac7 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Tue, 5 Nov 2024 14:09:44 +0530 Subject: [PATCH 04/11] Update scan tool version --- .../staticcodeanalyzer/StaticCodeAnalyzerTest.java | 9 +++++---- compiler-plugin/build.gradle | 2 +- gradle.properties | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java index 31db4e29ff..1b46e033ab 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java @@ -32,6 +32,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -51,13 +52,13 @@ class StaticCodeAnalyzerTest { @BeforeSuite public void pullScanTool() throws IOException, InterruptedException { - String scanToolVersion = "0.1.0"; - ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), - "tool", "pull", SCAN_COMMAND + ":" + scanToolVersion, "--repository=local"); + ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), "tool", "pull", SCAN_COMMAND); Process process = processBuilder.start(); int exitCode = process.waitFor(); String output = convertInputStreamToString(process.getInputStream()); - if (output.startsWith("tool '" + SCAN_COMMAND + ":" + scanToolVersion + "' is already active.")) { + if (Pattern.compile("tool 'scan:.+\\..+\\..+' successfully set as the active version\\.") + .matcher(output).find() || Pattern.compile("tool 'scan:.+\\..+\\..+' is already active\\.") + .matcher(output).find()) { return; } Assert.assertFalse(ExitCode.hasFailure(exitCode)); diff --git a/compiler-plugin/build.gradle b/compiler-plugin/build.gradle index 3578555a5f..066c263354 100644 --- a/compiler-plugin/build.gradle +++ b/compiler-plugin/build.gradle @@ -38,7 +38,7 @@ dependencies { implementation group: 'org.ballerinalang', name: 'ballerina-tools-api', version: "${ballerinaLangVersion}" implementation group: 'org.ballerinalang', name: 'ballerina-parser', version: "${ballerinaLangVersion}" implementation group: 'io.ballerina.openapi', name: 'ballerina-to-openapi', version: "${ballerinaToOpenApiVersion}" - // TODO: publish it to github, currenlty obtained from mavenLocal() + implementation group: 'io.ballerina.scan', name: 'scan-command', version: "${balScanVersion}" externalJars group: 'io.ballerina.openapi', name: 'ballerina-to-openapi', version: "${ballerinaToOpenApiVersion}" diff --git a/gradle.properties b/gradle.properties index 86b83b023e..bbe28df2c5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -44,7 +44,7 @@ stdlibAuthVersion=2.12.0 stdlibJwtVersion=2.13.0 stdlibOAuth2Version=2.12.0 -balScanVersion=0.1.0 +balScanVersion=0.5.0 observeVersion=1.3.0 observeInternalVersion=1.3.0 From 2aa962f18f1fcf259c65a9deae84606a1b2b4dad Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Wed, 6 Nov 2024 10:09:03 +0530 Subject: [PATCH 05/11] Rename serviceContractType to serviceType --- .../http/compiler/HttpCompilerPluginUtil.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java index b82355265b..7142858cb2 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/HttpCompilerPluginUtil.java @@ -367,18 +367,18 @@ public static ClassDefinitionNode getServiceClassDefinitionNode(SyntaxNodeAnalys return null; } ClassDefinitionNode classDefinitionNode = (ClassDefinitionNode) ctx.node(); - Optional serviceContractType = ctx.semanticModel().types() + Optional serviceType = ctx.semanticModel().types() .getTypeByName(BALLERINA, HTTP, EMPTY, HTTP_SERVICE_TYPE); - if (!hasServiceKeyWord(classDefinitionNode) || serviceContractType.isEmpty()) { + if (!hasServiceKeyWord(classDefinitionNode) || serviceType.isEmpty()) { return null; } Optional symbol = ctx.semanticModel().symbol(classDefinitionNode); - if (symbol.isEmpty() || serviceContractType.get().kind() != TYPE_DEFINITION) { + if (symbol.isEmpty() || serviceType.get().kind() != TYPE_DEFINITION) { return null; } ClassSymbol classSymbol = (ClassSymbol) symbol.get(); - TypeSymbol serviceType = ((TypeDefinitionSymbol) serviceContractType.get()).typeDescriptor(); - return classSymbol.subtypeOf(serviceType) ? classDefinitionNode : null; + TypeSymbol serviceTypeSymbol = ((TypeDefinitionSymbol) serviceType.get()).typeDescriptor(); + return classSymbol.subtypeOf(serviceTypeSymbol) ? classDefinitionNode : null; } public static AnnotationNode getAnnotationNode(SyntaxNodeAnalysisContext context) { @@ -433,17 +433,15 @@ public static boolean isHttpServiceType(SemanticModel semanticModel, Node typeNo return false; } - Optional serviceContractType = semanticModel.types().getTypeByName(BALLERINA, HTTP, EMPTY, + Optional serviceType = semanticModel.types().getTypeByName(BALLERINA, HTTP, EMPTY, HTTP_SERVICE_TYPE); - if (serviceContractType.isEmpty() || - !(serviceContractType.get() instanceof TypeDefinitionSymbol serviceContractTypeDef)) { + if (serviceType.isEmpty() || + !(serviceType.get() instanceof TypeDefinitionSymbol serviceTypeDef)) { return false; } - - return serviceObjTypeDef.typeDescriptor().subtypeOf(serviceContractTypeDef.typeDescriptor()); + return serviceObjTypeDef.typeDescriptor().subtypeOf(serviceTypeDef.typeDescriptor()); } - public static List getResourceMethodWithDefaultAccessor(NodeList members) { List resourceFunctions = new ArrayList<>(); for (Node member : members) { From fbacef622ed8e1a207fcc895f368b0ad26ba0351 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Wed, 6 Nov 2024 12:06:25 +0530 Subject: [PATCH 06/11] Add logic to remoe file path prefix when normalizing json --- .../compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java index 1b46e033ab..396bbd2bf7 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java @@ -116,6 +116,7 @@ private static String normalizeJson(String json) { .replaceAll("\\s*}\\s*", "}") .replaceAll("\\s*\\[\\s*", "[") .replaceAll("\\s*]\\s*", "]") - .replaceAll("\n", ""); + .replaceAll("\n", "") + .replaceAll(":\".*module-ballerina-http", ":\"module-ballerina-http"); } } From 57deb166482e9b9068df72e7c6b3fa6d0374275a Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Wed, 6 Nov 2024 12:57:24 +0530 Subject: [PATCH 07/11] Refactor java code --- ...nalyzer.java => HttpHttpServiceClass.java} | 23 ++++------ ...r.java => HttpHttpServiceDeclaration.java} | 23 ++++------ .../HttpHttpServiceObjectType.java | 36 ++++++++++++++++ .../staticcodeanalyzer/HttpService.java | 26 +++++++++++ .../HttpServiceAnalyzer.java | 36 +++++++++++++++- .../HttpServiceObjectTypeAnalyzer.java | 43 ------------------- .../HttpStaticCodeAnalyzer.java | 7 +-- 7 files changed, 118 insertions(+), 76 deletions(-) rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/{HttpServiceClassAnalyzer.java => HttpHttpServiceClass.java} (52%) rename compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/{HttpServiceDeclarationAnalyzer.java => HttpHttpServiceDeclaration.java} (52%) create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpHttpServiceObjectType.java create mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpService.java delete mode 100644 compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceObjectTypeAnalyzer.java diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceClassAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpHttpServiceClass.java similarity index 52% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceClassAnalyzer.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpHttpServiceClass.java index a6e6842738..cf3e702382 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceClassAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpHttpServiceClass.java @@ -19,23 +19,18 @@ package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; import io.ballerina.compiler.syntax.tree.ClassDefinitionNode; -import io.ballerina.projects.Document; -import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; -import io.ballerina.scan.Reporter; -import io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; -class HttpServiceClassAnalyzer extends HttpServiceAnalyzer { - public HttpServiceClassAnalyzer(Reporter reporter) { - super(reporter); +class HttpHttpServiceClass implements HttpService { + private final ClassDefinitionNode classDefinitionNode; + + public HttpHttpServiceClass(ClassDefinitionNode classDefinitionNode) { + this.classDefinitionNode = classDefinitionNode; } @Override - public void perform(SyntaxNodeAnalysisContext context) { - ClassDefinitionNode serviceClassDefinitionNode = HttpCompilerPluginUtil.getServiceClassDefinitionNode(context); - if (serviceClassDefinitionNode == null) { - return; - } - Document document = HttpCompilerPluginUtil.getDocument(context); - validateServiceMembers(serviceClassDefinitionNode.members(), document); + public NodeList members() { + return this.classDefinitionNode.members(); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceDeclarationAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpHttpServiceDeclaration.java similarity index 52% rename from compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceDeclarationAnalyzer.java rename to compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpHttpServiceDeclaration.java index a04988dcdc..e12a6590fb 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceDeclarationAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpHttpServiceDeclaration.java @@ -18,24 +18,19 @@ package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; -import io.ballerina.projects.Document; -import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; -import io.ballerina.scan.Reporter; -import io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil; -class HttpServiceDeclarationAnalyzer extends HttpServiceAnalyzer { - public HttpServiceDeclarationAnalyzer(Reporter reporter) { - super(reporter); +class HttpHttpServiceDeclaration implements HttpService { + private final ServiceDeclarationNode serviceDeclarationNode; + + public HttpHttpServiceDeclaration(ServiceDeclarationNode serviceDeclarationNode) { + this.serviceDeclarationNode = serviceDeclarationNode; } @Override - public void perform(SyntaxNodeAnalysisContext context) { - ServiceDeclarationNode serviceDeclarationNode = HttpCompilerPluginUtil.getServiceDeclarationNode(context); - if (serviceDeclarationNode == null) { - return; - } - Document document = HttpCompilerPluginUtil.getDocument(context); - validateServiceMembers(serviceDeclarationNode.members(), document); + public NodeList members() { + return this.serviceDeclarationNode.members(); } } diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpHttpServiceObjectType.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpHttpServiceObjectType.java new file mode 100644 index 0000000000..5df6a9b0b5 --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpHttpServiceObjectType.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; + +class HttpHttpServiceObjectType implements HttpService { + private final ObjectTypeDescriptorNode objectTypeDescriptorNode; + + public HttpHttpServiceObjectType(ObjectTypeDescriptorNode objectTypeDescriptorNode) { + this.objectTypeDescriptorNode = objectTypeDescriptorNode; + } + + @Override + public NodeList members() { + return this.objectTypeDescriptorNode.members(); + } +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpService.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpService.java new file mode 100644 index 0000000000..5696cabe4f --- /dev/null +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpService.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import io.ballerina.compiler.syntax.tree.Node; +import io.ballerina.compiler.syntax.tree.NodeList; + +public interface HttpService { + NodeList members(); +} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceAnalyzer.java index 87defdc497..f97e9a018e 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceAnalyzer.java @@ -18,8 +18,11 @@ package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; +import io.ballerina.compiler.syntax.tree.ClassDefinitionNode; import io.ballerina.compiler.syntax.tree.Node; import io.ballerina.compiler.syntax.tree.NodeList; +import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; +import io.ballerina.compiler.syntax.tree.ServiceDeclarationNode; import io.ballerina.projects.Document; import io.ballerina.projects.plugins.AnalysisTask; import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; @@ -27,16 +30,45 @@ import io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil; import io.ballerina.tools.diagnostics.Location; +import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpServiceType; import static io.ballerina.stdlib.http.compiler.staticcodeanalyzer.HttpRule.AVOID_DEFAULT_RESOURCE_ACCESSOR; -abstract class HttpServiceAnalyzer implements AnalysisTask { +class HttpServiceAnalyzer implements AnalysisTask { private final Reporter reporter; public HttpServiceAnalyzer(Reporter reporter) { this.reporter = reporter; } - public void validateServiceMembers(NodeList members, Document document) { + @Override + public void perform(SyntaxNodeAnalysisContext context) { + HttpService service = getService(context); + if (service == null) { + return; + } + Document document = HttpCompilerPluginUtil.getDocument(context); + validateServiceMembers(service.members(), document); + } + + private HttpService getService(SyntaxNodeAnalysisContext context) { + return switch (context.node().kind()) { + case SERVICE_DECLARATION -> { + ServiceDeclarationNode serviceDeclarationNode = HttpCompilerPluginUtil + .getServiceDeclarationNode(context); + yield serviceDeclarationNode == null ? null : new HttpHttpServiceDeclaration(serviceDeclarationNode); + } + case OBJECT_TYPE_DESC -> isHttpServiceType(context.semanticModel(), context.node()) ? + new HttpHttpServiceObjectType((ObjectTypeDescriptorNode) context.node()) : null; + case CLASS_DEFINITION -> { + ClassDefinitionNode serviceClassDefinitionNode = HttpCompilerPluginUtil + .getServiceClassDefinitionNode(context); + yield serviceClassDefinitionNode == null ? null : new HttpHttpServiceClass(serviceClassDefinitionNode); + } + default -> null; + }; + } + + private void validateServiceMembers(NodeList members, Document document) { // TODO: fix location, currently getting always -1 than expected HttpCompilerPluginUtil.getResourceMethodWithDefaultAccessor(members).forEach(definition -> { Location accessorLocation = definition.functionName().location(); diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceObjectTypeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceObjectTypeAnalyzer.java deleted file mode 100644 index f9a46c1730..0000000000 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpServiceObjectTypeAnalyzer.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; - -import io.ballerina.compiler.syntax.tree.ObjectTypeDescriptorNode; -import io.ballerina.projects.Document; -import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext; -import io.ballerina.scan.Reporter; -import io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil; - -import static io.ballerina.stdlib.http.compiler.HttpCompilerPluginUtil.isHttpServiceType; - -class HttpServiceObjectTypeAnalyzer extends HttpServiceAnalyzer { - public HttpServiceObjectTypeAnalyzer(Reporter reporter) { - super(reporter); - } - - @Override - public void perform(SyntaxNodeAnalysisContext context) { - if (!isHttpServiceType(context.semanticModel(), context.node())) { - return; - } - ObjectTypeDescriptorNode serviceObjectType = (ObjectTypeDescriptorNode) context.node(); - Document document = HttpCompilerPluginUtil.getDocument(context); - validateServiceMembers(serviceObjectType.members(), document); - } -} diff --git a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpStaticCodeAnalyzer.java b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpStaticCodeAnalyzer.java index 22f03a3174..e41e6bdbca 100644 --- a/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpStaticCodeAnalyzer.java +++ b/compiler-plugin/src/main/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/HttpStaticCodeAnalyzer.java @@ -22,6 +22,8 @@ import io.ballerina.projects.plugins.CodeAnalyzer; import io.ballerina.scan.Reporter; +import java.util.List; + import static io.ballerina.compiler.syntax.tree.SyntaxKind.ANNOTATION; import static io.ballerina.compiler.syntax.tree.SyntaxKind.CLASS_DEFINITION; import static io.ballerina.compiler.syntax.tree.SyntaxKind.OBJECT_TYPE_DESC; @@ -39,9 +41,8 @@ public HttpStaticCodeAnalyzer(Reporter reporter) { @Override public void init(CodeAnalysisContext analysisContext) { - analysisContext.addSyntaxNodeAnalysisTask(new HttpServiceDeclarationAnalyzer(reporter), SERVICE_DECLARATION); - analysisContext.addSyntaxNodeAnalysisTask(new HttpServiceObjectTypeAnalyzer(reporter), OBJECT_TYPE_DESC); - analysisContext.addSyntaxNodeAnalysisTask(new HttpServiceClassAnalyzer(reporter), CLASS_DEFINITION); + analysisContext.addSyntaxNodeAnalysisTask(new HttpServiceAnalyzer(reporter), + List.of(SERVICE_DECLARATION, OBJECT_TYPE_DESC, CLASS_DEFINITION)); analysisContext.addSyntaxNodeAnalysisTask(new HttpAnnotationAnalyzer(reporter), ANNOTATION); } } From 70687b9da9c2803656984d98c8d1f80a92bb1209 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Wed, 6 Nov 2024 13:10:49 +0530 Subject: [PATCH 08/11] Update bal path for windows --- .../staticcodeanalyzer/StaticCodeAnalyzerTest.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java index 396bbd2bf7..979d583b7d 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java @@ -32,6 +32,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Arrays; +import java.util.Locale; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -44,12 +45,17 @@ class StaticCodeAnalyzerTest { .get("src", "test", "resources", "static_code_analyzer", "ballerina_packages").toAbsolutePath(); private static final Path EXPECTED_JSON_OUTPUT_DIRECTORY = Paths. get("src", "test", "resources", "static_code_analyzer", "expected_output").toAbsolutePath(); - private static final Path BALLERINA_PATH = Paths - .get("../", "target", "ballerina-runtime", "bin", "bal").toAbsolutePath(); + private static final Path BALLERINA_PATH = getBalCommandPath(); private static final Path JSON_RULES_FILE_PATH = Paths .get("../", "compiler-plugin", "src", "main", "resources", "rules.json").toAbsolutePath(); private static final String SCAN_COMMAND = "scan"; + private static Path getBalCommandPath() { + String os = System.getProperty("os.name"); + String balCommand = os.toLowerCase(Locale.ENGLISH).startsWith("windows") ? "bal.bat" : "bal"; + return Paths.get("../", "target", "ballerina-runtime", "bin", balCommand).toAbsolutePath(); + } + @BeforeSuite public void pullScanTool() throws IOException, InterruptedException { ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), "tool", "pull", SCAN_COMMAND); From 20074921726342392438f7ea9209d6ca324beb86 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Wed, 6 Nov 2024 13:24:43 +0530 Subject: [PATCH 09/11] Update changelog --- changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/changelog.md b/changelog.md index 4935b7c73b..de6f87b363 100644 --- a/changelog.md +++ b/changelog.md @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - [Improve `@http:Query` annotation to overwrite the query parameter name in service](https://github.com/ballerina-platform/ballerina-library/issues/7006) - [Add header name mapping support in record fields](https://github.com/ballerina-platform/ballerina-library/issues/7018) - [Introduce util functions to convert query and header record with the `http:Query` and the `http:Header` annotations](https://github.com/ballerina-platform/ballerina-library/issues/7019) +- [Add static code rules](https://github.com/ballerina-platform/ballerina-library/issues/7283) ### Fixed From cdfa4441ad364df737c1f29d19020476a8bf246e Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Fri, 8 Nov 2024 14:11:10 +0530 Subject: [PATCH 10/11] Rename variables in java code --- .../staticcodeanalyzer/StaticCodeAnalyzerTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java index 979d583b7d..8f5ffcf59d 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java @@ -90,10 +90,10 @@ public void testStaticCodeRules() throws IOException, InterruptedException { } public static String executeScanProcess(String targetPackage) throws IOException, InterruptedException { - ProcessBuilder processBuilder2 = new ProcessBuilder(BALLERINA_PATH.toString(), SCAN_COMMAND); - processBuilder2.directory(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage).toFile()); - Process process2 = processBuilder2.start(); - int exitCode = process2.waitFor(); + ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), SCAN_COMMAND); + processBuilder.directory(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage).toFile()); + Process process = processBuilder.start(); + int exitCode = process.waitFor(); Assert.assertFalse(ExitCode.hasFailure(exitCode)); return Files.readString(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage) .resolve("target").resolve("report").resolve("scan_results.json")); From 4aa2ac836f56cd9c4b0a9376b5b61bfc038e9a73 Mon Sep 17 00:00:00 2001 From: MohamedSabthar Date: Fri, 8 Nov 2024 16:00:29 +0530 Subject: [PATCH 11/11] Fix static code test hanging in windows --- .../ProcessOutputGobbler.java | 64 +++++++++++++++++++ .../StaticCodeAnalyzerTest.java | 51 +++++++-------- 2 files changed, 90 insertions(+), 25 deletions(-) create mode 100644 compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/ProcessOutputGobbler.java diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/ProcessOutputGobbler.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/ProcessOutputGobbler.java new file mode 100644 index 0000000000..462b43de08 --- /dev/null +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/ProcessOutputGobbler.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.stdlib.http.compiler.staticcodeanalyzer; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +/** + * Helper class to consume the process streams. + */ +class ProcessOutputGobbler implements Runnable { + private final InputStream inputStream; + private final StringBuilder output; + private int exitCode; + + public ProcessOutputGobbler(InputStream inputStream) { + this.inputStream = inputStream; + this.output = new StringBuilder(); + } + + @Override + public void run() { + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + } catch (IOException e) { + this.output.append(e.getMessage()); + } + } + + public String getOutput() { + return output.toString(); + } + + public int getExitCode() { + return exitCode; + } + + public void setExitCode(int exitCode) { + this.exitCode = exitCode; + } +} diff --git a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java index 8f5ffcf59d..07feda9311 100644 --- a/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java +++ b/compiler-plugin-tests/src/test/java/io/ballerina/stdlib/http/compiler/staticcodeanalyzer/StaticCodeAnalyzerTest.java @@ -23,11 +23,7 @@ import org.testng.annotations.Test; import org.testng.internal.ExitCode; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -51,23 +47,20 @@ class StaticCodeAnalyzerTest { private static final String SCAN_COMMAND = "scan"; private static Path getBalCommandPath() { - String os = System.getProperty("os.name"); - String balCommand = os.toLowerCase(Locale.ENGLISH).startsWith("windows") ? "bal.bat" : "bal"; + String balCommand = isWindows() ? "bal.bat" : "bal"; return Paths.get("../", "target", "ballerina-runtime", "bin", balCommand).toAbsolutePath(); } @BeforeSuite public void pullScanTool() throws IOException, InterruptedException { ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), "tool", "pull", SCAN_COMMAND); - Process process = processBuilder.start(); - int exitCode = process.waitFor(); - String output = convertInputStreamToString(process.getInputStream()); + ProcessOutputGobbler output = getOutput(processBuilder.start()); if (Pattern.compile("tool 'scan:.+\\..+\\..+' successfully set as the active version\\.") - .matcher(output).find() || Pattern.compile("tool 'scan:.+\\..+\\..+' is already active\\.") - .matcher(output).find()) { + .matcher(output.getOutput()).find() || Pattern.compile("tool 'scan:.+\\..+\\..+' is already active\\.") + .matcher(output.getOutput()).find()) { return; } - Assert.assertFalse(ExitCode.hasFailure(exitCode)); + Assert.assertFalse(ExitCode.hasFailure(output.getExitCode())); } @Test @@ -92,22 +85,25 @@ public void testStaticCodeRules() throws IOException, InterruptedException { public static String executeScanProcess(String targetPackage) throws IOException, InterruptedException { ProcessBuilder processBuilder = new ProcessBuilder(BALLERINA_PATH.toString(), SCAN_COMMAND); processBuilder.directory(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage).toFile()); - Process process = processBuilder.start(); - int exitCode = process.waitFor(); - Assert.assertFalse(ExitCode.hasFailure(exitCode)); + ProcessOutputGobbler output = getOutput(processBuilder.start()); + Assert.assertFalse(ExitCode.hasFailure(output.getExitCode())); return Files.readString(RESOURCE_PACKAGES_DIRECTORY.resolve(targetPackage) .resolve("target").resolve("report").resolve("scan_results.json")); } - public static String convertInputStreamToString(InputStream inputStream) throws IOException { - StringBuilder stringBuilder = new StringBuilder(); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) { - String line; - while ((line = reader.readLine()) != null) { - stringBuilder.append(line).append(System.lineSeparator()); - } - } - return stringBuilder.toString(); + private static ProcessOutputGobbler getOutput(Process process) throws InterruptedException { + ProcessOutputGobbler outputGobbler = new ProcessOutputGobbler(process.getInputStream()); + ProcessOutputGobbler errorGobbler = new ProcessOutputGobbler(process.getErrorStream()); + Thread outputThread = new Thread(outputGobbler); + Thread errorThread = new Thread(errorGobbler); + outputThread.start(); + errorThread.start(); + int exitCode = process.waitFor(); + outputGobbler.setExitCode(exitCode); + errorGobbler.setExitCode(exitCode); + outputThread.join(); + errorThread.join(); + return outputGobbler; } private void assertJsonEqual(String actual, String expected) { @@ -115,7 +111,7 @@ private void assertJsonEqual(String actual, String expected) { } private static String normalizeJson(String json) { - return json.replaceAll("\\s*\"\\s*", "\"") + String normalizedJson = json.replaceAll("\\s*\"\\s*", "\"") .replaceAll("\\s*:\\s*", ":") .replaceAll("\\s*,\\s*", ",") .replaceAll("\\s*\\{\\s*", "{") @@ -124,5 +120,10 @@ private static String normalizeJson(String json) { .replaceAll("\\s*]\\s*", "]") .replaceAll("\n", "") .replaceAll(":\".*module-ballerina-http", ":\"module-ballerina-http"); + return isWindows() ? normalizedJson.replaceAll("/", "\\\\\\\\") : normalizedJson; + } + + private static boolean isWindows() { + return System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows"); } }