Skip to content

Commit

Permalink
Merge pull request #58 from poorna2152/service_restrict
Browse files Browse the repository at this point in the history
Disallow service declarations and dynamic service register when connector imported
  • Loading branch information
gimantha authored Nov 5, 2024
2 parents b337cf6 + 460672f commit 704c8a8
Show file tree
Hide file tree
Showing 7 changed files with 354 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,23 @@
import io.ballerina.projects.plugins.CodeAnalysisContext;
import io.ballerina.projects.plugins.CodeAnalyzer;

import java.util.List;

/**
* Compiler plugin for Ballerina MI Mediator
*
* @since 0.1.0
*/
public class BalMediatorCodeAnalyzer extends CodeAnalyzer {

@Override
public void init(CodeAnalysisContext codeAnalysisContext) {
codeAnalysisContext.addSyntaxNodeAnalysisTask(
new AnnotationAnalysisTask(), SyntaxKind.ANNOTATION
);
codeAnalysisContext.addSyntaxNodeAnalysisTask(new ListenerAndServiceDefAnalysisTask(),
List.of(SyntaxKind.SERVICE_DECLARATION, SyntaxKind.LISTENER_DECLARATION));
codeAnalysisContext.addSyntaxNodeAnalysisTask(new VariableDeclarationAnalysisTask(),
List.of(SyntaxKind.MODULE_VAR_DECL, SyntaxKind.LOCAL_VAR_DECL));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
public enum DiagnosticErrorCode {
UNSUPPORTED_PARAM_TYPE("MIE001", "unsupported parameter type found"),
UNSUPPORTED_RETURN_TYPE("MIE002", "unsupported return type found"),
SERVICE_DEF_NOT_ALLOWED("MIE003",
"service definition is not allowed when `ballerinax/mi` connector is in use"),
LISTENER_DECLARATION_NOT_ALLOWED("MIE004",
"listener declaration is not allowed when `ballerinax/mi` connector is in use"),
LISTENER_SHAPE_VAR_NOT_ALLOWED("MIE005",
"defining variables with a type that has the shape of `Listener` is not allowed when the `ballerinax/mi` " +
"connector is in use.");
;

private final String diagnosticId;
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*/

package io.ballerina.stdlib.mi.plugin;

import io.ballerina.compiler.syntax.tree.Node;
import io.ballerina.compiler.syntax.tree.SyntaxKind;
import io.ballerina.projects.plugins.AnalysisTask;
import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext;
import io.ballerina.tools.diagnostics.DiagnosticFactory;
import io.ballerina.tools.diagnostics.DiagnosticInfo;
import io.ballerina.tools.diagnostics.DiagnosticSeverity;

/**
* Analysis task that emit error diagnostic for service and listener declarations found in the code.
*
* @since 0.1.3
*/
public class ListenerAndServiceDefAnalysisTask implements AnalysisTask<SyntaxNodeAnalysisContext> {

@Override
public void perform(SyntaxNodeAnalysisContext context) {
DiagnosticErrorCode diagnosticCode;
Node node = context.node();
if (node.kind() == SyntaxKind.SERVICE_DECLARATION) {
diagnosticCode = DiagnosticErrorCode.SERVICE_DEF_NOT_ALLOWED;
} else {
diagnosticCode = DiagnosticErrorCode.LISTENER_DECLARATION_NOT_ALLOWED;
}
DiagnosticInfo diagnosticInfo =
new DiagnosticInfo(diagnosticCode.diagnosticId(), diagnosticCode.message(), DiagnosticSeverity.ERROR);
context.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, node.location()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* 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.
*/

package io.ballerina.stdlib.mi.plugin;

import io.ballerina.compiler.api.impl.symbols.BallerinaUnionTypeSymbol;
import io.ballerina.compiler.api.symbols.IntersectionTypeSymbol;
import io.ballerina.compiler.api.symbols.ObjectTypeSymbol;
import io.ballerina.compiler.api.symbols.Symbol;
import io.ballerina.compiler.api.symbols.TypeDescKind;
import io.ballerina.compiler.api.symbols.TypeReferenceTypeSymbol;
import io.ballerina.compiler.api.symbols.TypeSymbol;
import io.ballerina.compiler.api.symbols.VariableSymbol;
import io.ballerina.compiler.syntax.tree.Node;
import io.ballerina.projects.plugins.AnalysisTask;
import io.ballerina.projects.plugins.SyntaxNodeAnalysisContext;
import io.ballerina.tools.diagnostics.DiagnosticFactory;
import io.ballerina.tools.diagnostics.DiagnosticInfo;
import io.ballerina.tools.diagnostics.DiagnosticSeverity;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
* Analysis task that emits error diagnostics for local and module-level variable declarations with a type that match
* the shape of the `Listener` type found in the code.
*
* @since 0.1.3
*/
public class VariableDeclarationAnalysisTask implements AnalysisTask<SyntaxNodeAnalysisContext> {

private final List<String> methods = List.of("start", "gracefulStop", "immediateStop", "attach", "detach");

@Override
public void perform(SyntaxNodeAnalysisContext context) {
Node node = context.node();
Optional<Symbol> symbol = context.semanticModel().symbol(node);
if (symbol.isEmpty() || !(symbol.get() instanceof VariableSymbol)) {
return;
}

TypeSymbol typeSymbol = getRawType(((VariableSymbol) symbol.get()).typeDescriptor());
List<ObjectTypeSymbol> objectTypeSymbols = new ArrayList<>();
TypeDescKind typeDescKind = typeSymbol.typeKind();
if (typeDescKind == TypeDescKind.UNION) {
objectTypeSymbols = getObjectTypeMembers((BallerinaUnionTypeSymbol) typeSymbol);
} else if (typeDescKind == TypeDescKind.OBJECT) {
objectTypeSymbols.add((ObjectTypeSymbol) typeSymbol);
}

for (ObjectTypeSymbol objectTypeSymbol : objectTypeSymbols) {
List<String> classMethods = objectTypeSymbol.methods().keySet().stream().toList();
if (!classMethods.containsAll(methods)) {
continue;
}
DiagnosticErrorCode diagnosticCode = DiagnosticErrorCode.LISTENER_SHAPE_VAR_NOT_ALLOWED;
DiagnosticInfo diagnosticInfo =
new DiagnosticInfo(diagnosticCode.diagnosticId(), diagnosticCode.message(),
DiagnosticSeverity.ERROR);
context.reportDiagnostic(DiagnosticFactory.createDiagnostic(diagnosticInfo, node.location()));
return;
}
}

private TypeSymbol getRawType(TypeSymbol typeDescriptor) {
TypeDescKind typeDescKind = typeDescriptor.typeKind();
if (typeDescKind == TypeDescKind.INTERSECTION) {
return getRawType(((IntersectionTypeSymbol) typeDescriptor).effectiveTypeDescriptor());
}
if (typeDescKind == TypeDescKind.TYPE_REFERENCE) {
TypeSymbol typeSymbol = ((TypeReferenceTypeSymbol) typeDescriptor).typeDescriptor();
TypeDescKind refTypeDescKind = typeSymbol.typeKind();
if (refTypeDescKind == TypeDescKind.INTERSECTION) {
return getRawType(((IntersectionTypeSymbol) typeSymbol).effectiveTypeDescriptor());
}
if (refTypeDescKind == TypeDescKind.TYPE_REFERENCE) {
return getRawType(typeSymbol);
}
return typeSymbol;
}
return typeDescriptor;
}

private List<ObjectTypeSymbol> getObjectTypeMembers(BallerinaUnionTypeSymbol unionTypeSymbol) {
ArrayList<ObjectTypeSymbol> objectTypes = new ArrayList<>();
for (TypeSymbol member : unionTypeSymbol.memberTypeDescriptors()) {
member = getRawType(member);
TypeDescKind typeDescKind = member.typeKind();
if (typeDescKind == TypeDescKind.OBJECT) {
objectTypes.add((ObjectTypeSymbol) member);
} else if (typeDescKind == TypeDescKind.UNION) {
objectTypes.addAll(getObjectTypeMembers((BallerinaUnionTypeSymbol) member));
}
}
return objectTypes;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@
import io.ballerina.projects.Package;
import io.ballerina.projects.directory.ProjectLoader;
import io.ballerina.tools.diagnostics.Diagnostic;
import io.ballerina.tools.diagnostics.DiagnosticInfo;
import io.ballerina.tools.diagnostics.DiagnosticSeverity;
import io.ballerina.tools.text.LinePosition;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;

public class TestMediatorGenNegativeTests {
public static final Path RES_DIR = Paths.get("src/test/resources/ballerina").toAbsolutePath();
Expand All @@ -45,6 +49,40 @@ public void test() {
validateError(diagnostics, 1, "unsupported parameter type found", 12, 35);
}

@Test
public void testErrorsOnServiceDefinition() {
Path path = RES_DIR.resolve("project5");
Project project = ProjectLoader.loadProject(path);
Package pkg = project.currentPackage();
PackageCompilation packageCompilation = pkg.getCompilation();
DiagnosticResult diagnosticResult = packageCompilation.diagnosticResult();
Diagnostic[] errorDiagnosticsList = diagnosticResult.diagnostics().stream()
.filter(r -> r.diagnosticInfo().severity().equals(DiagnosticSeverity.ERROR))
.toArray(Diagnostic[]::new);
Assert.assertEquals(errorDiagnosticsList.length, 8);
validateError(errorDiagnosticsList, 0, "service definition is not allowed when `ballerinax/mi` connector is in use",
22, 1);
validateError(errorDiagnosticsList, 1, "service definition is not allowed when `ballerinax/mi` connector is in use",
28, 1);
validateError(errorDiagnosticsList, 2,
"listener declaration is not allowed when `ballerinax/mi` connector is in use", 40, 1);
validateError(errorDiagnosticsList, 3,
"defining variables with a type that has the shape of `Listener` is not allowed when the " +
"`ballerinax/mi` connector is in use.", 41, 1);
validateError(errorDiagnosticsList, 4,
"defining variables with a type that has the shape of `Listener` is not allowed when the " +
"`ballerinax/mi` connector is in use.", 49, 1);
validateError(errorDiagnosticsList, 5,
"defining variables with a type that has the shape of `Listener` is not allowed when the " +
"`ballerinax/mi` connector is in use.", 76, 1);
validateError(errorDiagnosticsList, 6,
"defining variables with a type that has the shape of `Listener` is not allowed when the " +
"`ballerinax/mi` connector is in use.", 117, 5);
validateError(errorDiagnosticsList, 7,
"defining variables with a type that has the shape of `Listener` is not allowed when the " +
"`ballerinax/mi` connector is in use.", 121, 5);
}

private void validateError(Diagnostic[] diagnostics, int errorIndex, String expectedErrMsg, int expectedErrLine,
int expectedErrCol) {
Diagnostic diagnostic = diagnostics[errorIndex];
Expand Down
14 changes: 14 additions & 0 deletions tests/src/test/resources/ballerina/project5/Ballerina.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
org = "testOrg"
name = "project5"
version = "1.0.0"
distribution = "2201.9.2"

[build-options]
observabilityIncluded = true

[[dependency]]
org = "ballerinax"
name = "mi"
version = "0.1.2"
repository = "local"
122 changes: 122 additions & 0 deletions tests/src/test/resources/ballerina/project5/main.bal
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// 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/graphql;
import ballerina/http;
import ballerina/lang.runtime;
import ballerinax/mi;

service / on new http:Listener(9090) {
resource function get greeting() returns string {
return "ballerina mi connector http service!";
}
}

service /graphql on new graphql:Listener(9091) {
resource function get greeting() returns string {
return "ballerina mi connector graphql service";
}
}

service class EchoService {
remote function greeting() returns string {
return "hello";
}
}

listener http:Listener httpListener = check new (9090);
http:Listener httpListener2 = check new (9091);

http:Service helloService = service object {
resource function get greeting() returns string {
return "dynamic listener!";
}
};

var obj1 = object {
public function 'start() returns error? {
}

public function gracefulStop() returns error? {
}

public function immediateStop() returns error? {
}

public function attach() returns error? {
}

public function detach() returns error? {
}
};


var obj2 = object {
public function 'start() returns error? {
}

public function gracefulStop() returns error? {
}

};

var obj3 = object {
public function 'start() returns error? {
}

public function gracefulStop() returns error? {
}

public function immediateStop() returns error? {
}

public function attach() returns error? {
}

public function detach() returns error? {
}

public function bar() {

}
};

class ListenerClass {
public function 'start() returns error? {
}

public function gracefulStop() returns error? {
}

public function immediateStop() returns error? {
}

public function attach() returns error? {
}

public function detach() returns error? {
}
}

@mi:ConnectorInfo {
}
function registerServiceDynamically() {
http:Listener|error? httpListener3 = new (9092);
error? e = httpListener2.attach(helloService, "foo/bar");
e = httpListener2.'start();
runtime:registerListener(httpListener2);
ListenerClass cls = new();
}

0 comments on commit 704c8a8

Please sign in to comment.