From 0c5c0d7fbf45d91a09681862343cf87a3ecfad46 Mon Sep 17 00:00:00 2001 From: Archana Iyer Date: Fri, 12 Jun 2026 18:45:42 +0530 Subject: [PATCH 1/3] New common file added for PSI Method call operations --- .../lsp4ij/codeAction/common/PsiMethodCallUtils.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/common/PsiMethodCallUtils.java diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/common/PsiMethodCallUtils.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/common/PsiMethodCallUtils.java new file mode 100644 index 000000000..332a3e2d3 --- /dev/null +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/common/PsiMethodCallUtils.java @@ -0,0 +1,4 @@ +package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.common; + +public class PsiMethodCallUtils { +} From 38657850ba5c4f05da5e80415bac2672a4f50e85 Mon Sep 17 00:00:00 2001 From: Archana Iyer Date: Fri, 12 Jun 2026 18:46:19 +0530 Subject: [PATCH 2/3] New common file changes for PSI Method call operations --- .../codeAction/common/PsiMethodCallUtils.java | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/common/PsiMethodCallUtils.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/common/PsiMethodCallUtils.java index 332a3e2d3..340e925b9 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/common/PsiMethodCallUtils.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/codeAction/common/PsiMethodCallUtils.java @@ -1,4 +1,59 @@ package io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.common; +import com.intellij.psi.*; +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.jsonb.JsonbConstants; +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.jsonp.JsonpConstants; +import org.apache.commons.lang3.StringUtils; + +/** + * Utility class for common PSI method call operations. + */ public class PsiMethodCallUtils { + + /** + * Checks if an expression is a null literal or a cast expression containing a null literal. + * This is useful for detecting null arguments passed to methods that don't accept null parameters. + * + * @param arg the expression to check + * @return true if the expression is null or a cast of null, false otherwise + */ + public static boolean isInvalidNullArgument(PsiExpression arg) { + return (arg instanceof PsiLiteralExpression lit && lit.getValue() == null) + || (arg instanceof PsiTypeCastExpression cast + && cast.getOperand() instanceof PsiLiteralExpression + && ((PsiLiteralExpression) cast.getOperand()).getValue() == null); + } + + /** + * isMatchedMethodFQName + * Method is used to identify passed method invocations + * + * @param mce + * @param methodParentTypeFQ + * @return boolean + */ + public static boolean isMatchedMethodFQName(PsiMethodCallExpression mce, String methodParentTypeFQ) { + PsiMethod method = mce.resolveMethod(); + if(getMethodName(method).equals(JsonpConstants.CREATE_POINTER)){ + return mce.getArgumentList().getExpressionCount() == JsonpConstants.EXPRESSION_COUNT_CREATE_POINTER + && methodParentTypeFQ.equals(method.getContainingClass().getQualifiedName()); + } else if(getMethodName(method).equals(JsonpConstants.JAKARTA_JSON_BUILDER_ADD_METHOD) || + getMethodName(method).equals(JsonbConstants.FROM_JSON_METHOD)){ + return methodParentTypeFQ.equals(method.getContainingClass().getQualifiedName()); + } + return false; + } + + /** + * Check if valid method exists + * + * @param method + * @return + */ + public static String getMethodName(PsiMethod method) { + if(method != null && method.getClass() != null){ + return method.getName(); + } + return StringUtils.EMPTY; + } } From 49c43df9ce5d5a6dd9e3326b63ec2fa31a992ae3 Mon Sep 17 00:00:00 2001 From: Archana Iyer Date: Fri, 12 Jun 2026 18:52:06 +0530 Subject: [PATCH 3/3] Changes for fromJson Invalid Null param --- .../lsp4ij/jsonb/JsonbConstants.java | 4 + .../jsonb/JsonbDiagnosticsCollector.java | 46 +++++++++- .../jsonp/JsonpDiagnosticCollector.java | 59 ++---------- .../lsp4jakarta/messages/messages.properties | 1 + .../jsonb/JsonbDiagnosticsCollectorTest.java | 25 ++++++ .../jsonb/JsonbFromJsonNullParameter.java | 90 +++++++++++++++++++ 6 files changed, 171 insertions(+), 54 deletions(-) create mode 100644 src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/jsonb/JsonbFromJsonNullParameter.java diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonb/JsonbConstants.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonb/JsonbConstants.java index 354470f3f..21a933197 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonb/JsonbConstants.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonb/JsonbConstants.java @@ -28,6 +28,7 @@ public class JsonbConstants { public static final String DIAGNOSTIC_CODE_NO_ARGS_CONSTRUCTOR_MISSING = "InvalidJsonBNoArgsConstructorMissing"; public static final String DIAGNOSTIC_CODE_NON_STATIC_INNER_CLASS = "InvalidJsonBNonStaticInnerClass"; public static final String DIAGNOSTIC_CODE_NON_PUBLIC_PROTECTED_STATIC_NESTED_CLASS = "InvalidJsonBNonPublicProtectedStaticNestedClass"; + public static final String DIAGNOSTIC_CODE_FROM_JSON_NULL_PARAMETER = "InvalidJsonbFromJsonNullParameter"; /* Annotation Constants */ public static final String JSONB_PACKAGE = "jakarta.json.bind.annotation."; @@ -56,4 +57,7 @@ public class JsonbConstants { JSONB_DATE_FORMAT, JSONB_NILLABLE, JSONB_NUMBER_FORMAT, JSONB_PROPERTY, JSONB_PROPERTY_ORDER, JSONB_TYPE_ADAPTER, JSONB_TYPE_DESERIALIZER, JSONB_TYPE_SERIALIZER, JSONB_VISIBILITY); + /* Jsonb fromJson constants */ + public static final String JSONB_FROM_JSON_PACKAGE = "jakarta.json.bind.Jsonb"; + public static final String FROM_JSON_METHOD = "fromJson"; } diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonb/JsonbDiagnosticsCollector.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonb/JsonbDiagnosticsCollector.java index b061164c4..300f51ac7 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonb/JsonbDiagnosticsCollector.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonb/JsonbDiagnosticsCollector.java @@ -17,14 +17,18 @@ import com.intellij.psi.*; import com.intellij.psi.impl.PsiClassImplUtil; +import com.intellij.psi.util.PsiTreeUtil; import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.AbstractDiagnosticsCollector; import com.google.gson.Gson; import com.google.gson.JsonArray; import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.JDTUtils; import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.JsonPropertyUtils; import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages; +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.PositionUtils; +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.common.PsiMethodCallUtils; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.DiagnosticSeverity; +import org.eclipse.lsp4j.Range; /** * This class contains logic for Jsonb diagnostics: @@ -132,8 +136,11 @@ public void collectDiagnostics(PsiJavaFile unit, List diagnostics) { // Jsonb deseriazation diagnostics generateJsonbDeserializerDiagnostics(unit, diagnostics, jsonbtypeParent, false, missingParentNoArgsConstructor, false, type); - } - } + } + + // Collect diagnostics for Jsonb.fromJson() method invocations with null parameters + collectJsonbFromJsonNullParameterDiagnostics(unit, diagnostics); + } /** * @param element @@ -351,4 +358,39 @@ private boolean hasJsonbAnnotationOtherThanTransient(List jsonbAnnotatio return true; return false; } + + /** + * Collects diagnostics for Jsonb.fromJson() method invocations where null is passed as a parameter. + * According to the Jakarta JSON Binding specification, the fromJson() method must not accept null parameters. + * + * @param unit the compilation unit + * @param diagnostics the list to add diagnostics to + */ + private void collectJsonbFromJsonNullParameterDiagnostics(PsiJavaFile unit, List diagnostics) { + if (unit == null) { + return; + } + // Find all method call expressions in the file + Collection allMethodInvocations = PsiTreeUtil.findChildrenOfType(unit, PsiMethodCallExpression.class); + List fromJsonInvocations = new ArrayList<>(); + // Filter for fromJson() method calls + for (PsiMethodCallExpression mi : allMethodInvocations) { + if (PsiMethodCallUtils.isMatchedMethodFQName(mi, JsonbConstants.JSONB_FROM_JSON_PACKAGE)) { + fromJsonInvocations.add(mi); + } + } + // Create diagnostics for fromJson() calls with null parameters + for (PsiMethodCallExpression methodCall : fromJsonInvocations) { + PsiExpression[] args = methodCall.getArgumentList().getExpressions(); + for (PsiExpression arg : args) { + if (PsiMethodCallUtils.isInvalidNullArgument(arg)) { + String msg = Messages.getMessage("ErrorMessageJsonbFromJsonNullParameter"); + Range range = PositionUtils.toNameRange(arg); + Diagnostic diagnostic = new Diagnostic(range, msg); + completeDiagnostic(diagnostic, JsonbConstants.DIAGNOSTIC_CODE_FROM_JSON_NULL_PARAMETER); + diagnostics.add(diagnostic); + } + } + } + } } diff --git a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonp/JsonpDiagnosticCollector.java b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonp/JsonpDiagnosticCollector.java index 9826abc5a..ceafdf81c 100644 --- a/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonp/JsonpDiagnosticCollector.java +++ b/src/main/java/io/openliberty/tools/intellij/lsp4jakarta/lsp4ij/jsonp/JsonpDiagnosticCollector.java @@ -18,10 +18,10 @@ import java.util.List; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; -import org.apache.commons.lang3.StringUtils; import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.AbstractDiagnosticsCollector; import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.Messages; import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.PositionUtils; +import io.openliberty.tools.intellij.lsp4jakarta.lsp4ij.codeAction.common.PsiMethodCallUtils; import org.eclipse.lsp4j.Diagnostic; import org.eclipse.lsp4j.Range; @@ -49,13 +49,13 @@ public void collectDiagnostics(PsiJavaFile unit, List diagnostics) { List createArrayBuilderMethodInvocations = new ArrayList<>(); for (PsiMethodCallExpression mi : allMethodInvocations) { - if (isMatchedMethodFQName(mi, JsonpConstants.JSON_FQ_NAME)) { + if (PsiMethodCallUtils.isMatchedMethodFQName(mi, JsonpConstants.JSON_FQ_NAME)) { createPointerInvocations.add(mi); } - if (isMatchedMethodFQName(mi, JsonpConstants.JAKARTA_JSON_OBJECT_BUILDER_FQ_NAME)){ + if (PsiMethodCallUtils.isMatchedMethodFQName(mi, JsonpConstants.JAKARTA_JSON_OBJECT_BUILDER_FQ_NAME)){ createObjectBuilderMethodInvocations.add(mi); } - if (isMatchedMethodFQName(mi, JsonpConstants.JAKARTA_JSON_ARRAY_BUILDER_FQ_NAME)){ + if (PsiMethodCallUtils.isMatchedMethodFQName(mi, JsonpConstants.JAKARTA_JSON_ARRAY_BUILDER_FQ_NAME)){ createArrayBuilderMethodInvocations.add(mi); } } @@ -94,15 +94,15 @@ private void createDiagnosticsForMethodInvocations(List diagnostics, List builderMethodInvocations, String msg, String errCode) { for(PsiMethodCallExpression m: builderMethodInvocations){ - if(getMethodName(m.resolveMethod()).equals(JsonpConstants.CREATE_POINTER)){ + if(PsiMethodCallUtils.getMethodName(m.resolveMethod()).equals(JsonpConstants.CREATE_POINTER)){ PsiExpression arg = m.getArgumentList().getExpressions()[0]; if(isInvalidArgumentCreatePointer(arg)) { buildInvalidArgumentDiagnostic(diagnostics, msg, errCode, arg); } - } else if(getMethodName(m.resolveMethod()).equals(JsonpConstants.JAKARTA_JSON_BUILDER_ADD_METHOD)){ + } else if(PsiMethodCallUtils.getMethodName(m.resolveMethod()).equals(JsonpConstants.JAKARTA_JSON_BUILDER_ADD_METHOD)){ PsiExpression[] args = m.getArgumentList().getExpressions(); for(PsiExpression arg : args) { - if(isInvalidNullArgument(arg)) { + if(PsiMethodCallUtils.isInvalidNullArgument(arg)) { buildInvalidArgumentDiagnostic(diagnostics, msg, errCode, arg); } } @@ -125,51 +125,6 @@ private void buildInvalidArgumentDiagnostic(List diagnostics, String diagnostics.add(diagnostic); } - /** - * Method is used to check if value of arg passed or Cast Expression inside passed arg is null - * - * @param arg - * @return - */ - private boolean isInvalidNullArgument(PsiExpression arg) { - return (arg instanceof PsiLiteralExpression lit && lit.getValue() == null) - || (arg instanceof PsiTypeCastExpression cast - && cast.getOperand() instanceof PsiLiteralExpression - && ((PsiLiteralExpression) cast.getOperand()).getValue() == null); - } - - /** - * isMatchedMethodFQName - * Method is used to identify passed method invocations - * - * @param mce - * @param methodParentTypeFQ - * @return boolean - */ - private boolean isMatchedMethodFQName(PsiMethodCallExpression mce, String methodParentTypeFQ) { - PsiMethod method = mce.resolveMethod(); - if(getMethodName(method).equals(JsonpConstants.CREATE_POINTER)){ - return mce.getArgumentList().getExpressionCount() == JsonpConstants.EXPRESSION_COUNT_CREATE_POINTER - && methodParentTypeFQ.equals(method.getContainingClass().getQualifiedName()); - } else if(getMethodName(method).equals(JsonpConstants.JAKARTA_JSON_BUILDER_ADD_METHOD)){ - return methodParentTypeFQ.equals(method.getContainingClass().getQualifiedName()); - } - return false; - } - - /** - * Check if valid method exists - * - * @param method - * @return - */ - private String getMethodName(PsiMethod method) { - if(method != null && method.getClass() != null){ - return method.getName(); - } - return StringUtils.EMPTY; - } - private boolean isInvalidArgumentCreatePointer(PsiExpression arg) { if (arg instanceof PsiLiteralExpression) { if (((PsiLiteralExpression) arg).getValue() instanceof String) { diff --git a/src/main/resources/io/openliberty/tools/intellij/lsp4jakarta/messages/messages.properties b/src/main/resources/io/openliberty/tools/intellij/lsp4jakarta/messages/messages.properties index fe4503500..ddff84393 100644 --- a/src/main/resources/io/openliberty/tools/intellij/lsp4jakarta/messages/messages.properties +++ b/src/main/resources/io/openliberty/tools/intellij/lsp4jakarta/messages/messages.properties @@ -175,6 +175,7 @@ ErrorMessageJsonbPropertyUniquenessField = Multiple fields or properties with @J ErrorMessageJsonbNoArgConstructorMissing = Missing Public or Protected NoArgsConstructor: Class {0} uses JSON Binding annotations, but does not declare a public or protected no-argument constructor. ErrorMessageJsonbInnerNonStatic = Cannot deserialize class {0} because it is not static. Please declare the class as static for JSONB deserialization. ErrorMessageJsonbNonPublicProtectedStaticNestedClass = Static nested class {0} must be public or protected for JSON Binding deserialization. Private and packaged private static nested classes are not supported. +ErrorMessageJsonbFromJsonNullParameter = The parameter of the fromJson() method must not be null. # JsonpDiagnosticCollector CreatePointerErrorMessage = Json.createPointer target must be a sequence of '/' prefixed tokens or an empty String. diff --git a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/jsonb/JsonbDiagnosticsCollectorTest.java b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/jsonb/JsonbDiagnosticsCollectorTest.java index 00a97bd74..2c4b5222d 100644 --- a/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/jsonb/JsonbDiagnosticsCollectorTest.java +++ b/src/test/java/io/openliberty/tools/intellij/lsp4jakarta/it/jsonb/JsonbDiagnosticsCollectorTest.java @@ -2647,4 +2647,29 @@ public void JsonbDeserialization() throws Exception { CodeAction insertNoArgPubConstructorChild = JakartaForJavaAssert.ca(uri, Messages.getMessage("AddNoArgPublicConstructor"), missingPubOrProConstructorChild, addPublicConstructorChildEdit); JakartaForJavaAssert.assertJavaCodeAction(codeActionParams2, utils, insertNoArgProConstructorChild, insertNoArgPubConstructorChild); } + + @Test + public void testJsonbFromJsonNullParameters() throws Exception { + Module module = createMavenModule(new File("src/test/resources/projects/maven/jakarta-sample")); + IPsiUtils utils = PsiUtilsLSImpl.getInstance(getProject()); + + VirtualFile javaFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(ModuleUtilCore.getModuleDirPath(module) + + "/src/main/java/io/openliberty/sample/jakarta/jsonb/JsonbFromJsonNullParameter.java"); + String uri = VfsUtilCore.virtualToIoFile(javaFile).toURI().toString(); + + JakartaJavaDiagnosticsParams diagnosticsParams = new JakartaJavaDiagnosticsParams(); + diagnosticsParams.setUris(Arrays.asList(uri)); + + // Test null first parameter with cast: jsonb.fromJson((String) null, Person.class) + Diagnostic nullFirstParamWithCast = JakartaForJavaAssert.d(47, 38, 51, + "The parameter of the fromJson() method must not be null.", + DiagnosticSeverity.Error, "jakarta-jsonb", "InvalidJsonbFromJsonNullParameter"); + + // Test null second parameter: jsonb.fromJson(json, null) + Diagnostic nullSecondParam = JakartaForJavaAssert.d(57, 46, 50, + "The parameter of the fromJson() method must not be null.", + DiagnosticSeverity.Error, "jakarta-jsonb", "InvalidJsonbFromJsonNullParameter"); + + JakartaForJavaAssert.assertJavaDiagnostics(diagnosticsParams, utils, nullFirstParamWithCast, nullSecondParam); + } } diff --git a/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/jsonb/JsonbFromJsonNullParameter.java b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/jsonb/JsonbFromJsonNullParameter.java new file mode 100644 index 000000000..a7f909e93 --- /dev/null +++ b/src/test/resources/projects/maven/jakarta-sample/src/main/java/io/openliberty/sample/jakarta/jsonb/JsonbFromJsonNullParameter.java @@ -0,0 +1,90 @@ +/******************************************************************************* + * Copyright (c) 2026 IBM Corporation and others. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ +package io.openliberty.sample.jakarta.jsonb; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; + +/** + * Test class for Jsonb.fromJson() null parameter diagnostics. + * According to Jakarta JSON Binding specification, fromJson() must not accept null parameters. + */ +public class JsonbFromJsonNullParameter { + + static class Person { + private int id; + private String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } + + public void testNullFirstParameter() { + try (Jsonb jsonb = JsonbBuilder.create()) { + // ERROR: First parameter (JSON input) is null + Person p = jsonb.fromJson((String) null, Person.class); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void testNullSecondParameter() { + try (Jsonb jsonb = JsonbBuilder.create()) { + String json = "{ \"id\": 1, \"name\": \"Test\" }"; + // ERROR: Second parameter (target type) is null + Object obj = jsonb.fromJson(json, null); + } catch (Exception ignored) { + } + } + + public void testBothParametersNull() { + try (Jsonb jsonb = JsonbBuilder.create()) { + // ERROR: Both parameters are null + Object obj = jsonb.fromJson(null, null); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void testNullWithoutCast() { + try (Jsonb jsonb = JsonbBuilder.create()) { + // ERROR: First parameter is null without cast + Person p = jsonb.fromJson(null, Person.class); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void testValidUsage() { + try (Jsonb jsonb = JsonbBuilder.create()) { + String json = "{ \"id\": 1, \"name\": \"Valid\" }"; + // VALID: Both parameters are non-null + Person p = jsonb.fromJson(json, Person.class); + } catch (Exception e) { + e.printStackTrace(); + } + } +}