diff --git a/its/ruling/src/test/java/org/sonar/java/it/AutoScanTest.java b/its/ruling/src/test/java/org/sonar/java/it/AutoScanTest.java index c5587a5e2ff..c544e87874c 100644 --- a/its/ruling/src/test/java/org/sonar/java/it/AutoScanTest.java +++ b/its/ruling/src/test/java/org/sonar/java/it/AutoScanTest.java @@ -188,7 +188,7 @@ public void javaCheckTestSources() throws Exception { * No differences would mean that we find the same issues with and without the bytecode and libraries */ String differences = Files.readString(pathFor(TARGET_ACTUAL + PROJECT_KEY + "-no-binaries_differences")); - assertThat(differences).isEqualTo("Issues differences: 3254"); + assertThat(differences).isEqualTo("Issues differences: 3294"); } private static Path pathFor(String path) { diff --git a/its/ruling/src/test/resources/autoscan/autoscan-diff-by-rules.json b/its/ruling/src/test/resources/autoscan/autoscan-diff-by-rules.json index 58a0998f703..87526dce847 100644 --- a/its/ruling/src/test/resources/autoscan/autoscan-diff-by-rules.json +++ b/its/ruling/src/test/resources/autoscan/autoscan-diff-by-rules.json @@ -302,7 +302,7 @@ { "ruleKey": "S1161", "hasTruePositives": true, - "falseNegatives": 6, + "falseNegatives": 7, "falsePositives": 0 }, { @@ -338,7 +338,7 @@ { "ruleKey": "S1172", "hasTruePositives": true, - "falseNegatives": 11, + "falseNegatives": 13, "falsePositives": 0 }, { @@ -698,7 +698,7 @@ { "ruleKey": "S1874", "hasTruePositives": true, - "falseNegatives": 89, + "falseNegatives": 102, "falsePositives": 0 }, { @@ -974,7 +974,7 @@ { "ruleKey": "S2160", "hasTruePositives": true, - "falseNegatives": 0, + "falseNegatives": 1, "falsePositives": 0 }, { @@ -1322,7 +1322,7 @@ { "ruleKey": "S2637", "hasTruePositives": true, - "falseNegatives": 19, + "falseNegatives": 21, "falsePositives": 0 }, { @@ -1610,7 +1610,7 @@ { "ruleKey": "S3330", "hasTruePositives": true, - "falseNegatives": 30, + "falseNegatives": 51, "falsePositives": 0 }, { diff --git a/java-checks-test-sources/pom.xml b/java-checks-test-sources/pom.xml index fe524eddeca..3bedb4d89cf 100644 --- a/java-checks-test-sources/pom.xml +++ b/java-checks-test-sources/pom.xml @@ -386,6 +386,18 @@ jar provided + + jakarta.ws.rs + jakarta.ws.rs-api + 3.1.0 + provided + + + jakarta.servlet + jakarta.servlet-api + 6.0.0 + provided + javax.inject javax.inject diff --git a/java-checks-test-sources/src/main/files/non-compiling/checks/security/CookieHttpOnlyCheck.java b/java-checks-test-sources/src/main/files/non-compiling/checks/security/CookieHttpOnlyCheck.java index 72c8d403454..5e8f3e83e62 100644 --- a/java-checks-test-sources/src/main/files/non-compiling/checks/security/CookieHttpOnlyCheck.java +++ b/java-checks-test-sources/src/main/files/non-compiling/checks/security/CookieHttpOnlyCheck.java @@ -83,3 +83,43 @@ void baw() { Unknown.unkown(() -> { Class v = unknown(); }); } } + +class JakartaCookieHttpOnlyCheck { + + jakarta.servlet.http.Cookie field4; + jakarta.servlet.http.Cookie field6; + + void servletCookie(boolean param, jakarta.servlet.http.Cookie c0) { + field6.setHttpOnly(false); // Noncompliant + + jakarta.servlet.http.Cookie c7 = new UnknownCookie("name", "value"); // Noncompliant + Object c8 = new jakarta.servlet.http.Cookie("name", "value"); // Noncompliant + + jakarta.servlet.http.Cookie c13; + c13 = new UnknownCookie("name", "value"); // Noncompliant + + field4 = new jakarta.servlet.http.Cookie("name, value"); // FN + } + + jakarta.servlet.http.Cookie getC0() { + return new UnknownCookie("name", "value"); // FN + } + + void compliant(jakarta.ws.rs.core.Cookie c) { + c.isHttpOnly(); + } +} + +class JakartaCookieHttpOnlyCheckCookie extends jakarta.servlet.http.Cookie { + public jakarta.servlet.http.Cookie c; + public void setHttpOnly(boolean isHttpOnly) { } + void foo() { + setHttpOnly(false); // Noncompliant + } + void bar(boolean x) { + setHttpOnly(x); + } + void baz() { + setHttpOnly(true); + } +} diff --git a/java-checks-test-sources/src/main/java/annotations/nullability/no_default/JakartaNullabilityAnnotation.java b/java-checks-test-sources/src/main/java/annotations/nullability/no_default/JakartaNullabilityAnnotation.java new file mode 100644 index 00000000000..2769bfae648 --- /dev/null +++ b/java-checks-test-sources/src/main/java/annotations/nullability/no_default/JakartaNullabilityAnnotation.java @@ -0,0 +1,11 @@ +package annotations.nullability.no_default; + +import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; + +public class JakartaNullabilityAnnotation { + @Nullable + Object id9002_type_WEAK_NULLABLE_level_VARIABLE; + @Nonnull + Object id9003_type_NON_NULL_level_VARIABLE; +} diff --git a/java-checks-test-sources/src/main/java/checks/LeastSpecificTypeCheck.java b/java-checks-test-sources/src/main/java/checks/LeastSpecificTypeCheck.java index e9894414697..30ddcb0a614 100644 --- a/java-checks-test-sources/src/main/java/checks/LeastSpecificTypeCheck.java +++ b/java-checks-test-sources/src/main/java/checks/LeastSpecificTypeCheck.java @@ -90,6 +90,33 @@ public void autowiredMethod3(List list) { // Compliant - List interface list.size(); } + @jakarta.annotation.Resource + public void jakartaResourceAnnotatedMethod3(List list) { // Noncompliant {{Use 'java.util.Collection' here; it is a more general type than 'List'.}} + for (Object o : list) { + o.toString(); + } + } + + @jakarta.inject.Inject + public void jakartaInjectAnnotatedMethod1(List list) { // Noncompliant {{Use 'java.util.Collection' here; it is a more general type than 'List'.}} + for (Object o : list) { + o.toString(); + } + } + @jakarta.annotation.Resource + public void jakartaRAnnotatedMethod4(Collection list) { // Compliant - since Spring annotated methods cannot take 'Iterable' as argument + for (Object o : list) { + o.toString(); + } + } + + @jakarta.inject.Inject + public void jakartaInjectAnnotatedMethod2(Collection list) { // Compliant - since Spring annotated methods cannot take 'Iterable' as argument + for (Object o : list) { + o.toString(); + } + } + public static void staticMethod(List list) { // Noncompliant {{Use 'java.util.Collection' here; it is a more general type than 'List'.}} list.size(); } diff --git a/java-checks-test-sources/src/main/java/checks/regex/RedosCheck.java b/java-checks-test-sources/src/main/java/checks/regex/RedosCheck.java index 9f10e971c68..0e975f964d1 100644 --- a/java-checks-test-sources/src/main/java/checks/regex/RedosCheck.java +++ b/java-checks-test-sources/src/main/java/checks/regex/RedosCheck.java @@ -8,6 +8,9 @@ public class RedosCheck { @Email(regexp = "(.*-)*@.*") // Noncompliant [[sc=4;ec=9]] {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}} String email; + @jakarta.validation.constraints.Email(regexp = "(.*-)*@.*") // Noncompliant [[sc=4;ec=40]] {{Make sure the regex used here, which is vulnerable to polynomial runtime due to backtracking, cannot lead to denial of service.}} + String email2; + void realWorldExamples(String str) { String cloudflareAttack = "(?:(?:\"|'|\\]|\\}|\\\\|\\d|(?:nan|infinity|true|false|null|undefined|symbol|math)|\\`|\\-|\\+)+[)]*;?((?:\\s|-|~|!|\\{\\}|\\|\\||\\+)*.*(?:.*=.*)))"; String stackOverflowAttack = "^[\\s\\u200c]+|[\\s\\u200c]+$"; diff --git a/java-checks-test-sources/src/main/java/checks/regex/RedosCheckJava8.java b/java-checks-test-sources/src/main/java/checks/regex/RedosCheckJava8.java index 81e7bb5c267..3a3bc9b8bcb 100644 --- a/java-checks-test-sources/src/main/java/checks/regex/RedosCheckJava8.java +++ b/java-checks-test-sources/src/main/java/checks/regex/RedosCheckJava8.java @@ -7,6 +7,9 @@ public class RedosCheckJava8 { @Email(regexp = "(.*-)*@.*") // Noncompliant [[sc=4;ec=9]] {{Make sure the regex used here, which is vulnerable to exponential runtime due to backtracking, cannot lead to denial of service.}} String email; + @jakarta.validation.constraints.Email(regexp = "(.*-)*@.*") // Noncompliant [[sc=4;ec=40]] {{Make sure the regex used here, which is vulnerable to exponential runtime due to backtracking, cannot lead to denial of service.}} + String email2; + void alwaysExponential(String str) { str.matches("(.*,)*?"); // Noncompliant [[sc=9;ec=16]] {{Make sure the regex used here, which is vulnerable to exponential runtime due to backtracking, cannot lead to denial of service.}} str.matches("(.?,)*?"); // Noncompliant {{Make sure the regex used here, which is vulnerable to exponential runtime due to backtracking, cannot lead to denial of service.}} diff --git a/java-checks-test-sources/src/main/java/checks/security/CookieHttpOnlyCheck.java b/java-checks-test-sources/src/main/java/checks/security/CookieHttpOnlyCheck.java index 40db92bb853..f17c5c77451 100644 --- a/java-checks-test-sources/src/main/java/checks/security/CookieHttpOnlyCheck.java +++ b/java-checks-test-sources/src/main/java/checks/security/CookieHttpOnlyCheck.java @@ -256,17 +256,175 @@ void baz() { class CookieHttpOnlyCheckCookieB { CookieHttpOnlyCheckCookieA a; - public void setHttpOnly(boolean isHttpOnly) { } + + public void setHttpOnly(boolean isHttpOnly) { + } + void foo() { setHttpOnly(false); } - void bar() { return; } + + void bar() { + return; + } + CookieHttpOnlyCheckCookieA getA() { return new CookieHttpOnlyCheckCookieA(); // Noncompliant } + void baw() { int i; i = 1; a.c = new Cookie("1", "2"); // FN } } + +class JakartaCookieHttpOnlyCheckCookieACookieHttpOnlyCheck { + + private static final boolean FALSE_CONSTANT = false; + + jakarta.servlet.http.Cookie field1 = new jakarta.servlet.http.Cookie("name", "value"); // FN + jakarta.ws.rs.core.Cookie field3 = new jakarta.ws.rs.core.Cookie("name", "value"); // FN + jakarta.servlet.http.Cookie field6; + void servletCookie(boolean param, jakarta.servlet.http.Cookie c0) { + c0.setHttpOnly(false); // Noncompliant [[sc=19;ec=26]] {{Make sure creating this cookie without the "HttpOnly" flag is safe.}} + field6.setHttpOnly(false); // Noncompliant + + jakarta.servlet.http.Cookie c1 = new jakarta.servlet.http.Cookie("name", "value"); + if (param) { + c1.setHttpOnly(false); // Noncompliant + } else { + c1.setHttpOnly(true); + } + + jakarta.servlet.http.Cookie c2 = new jakarta.servlet.http.Cookie("name", "value"); // Noncompliant [[sc=42;ec=69]] + + c1.setHttpOnly(false); // Noncompliant + + c1.setHttpOnly(FALSE_CONSTANT); // Noncompliant + + boolean b = false; + c1.setHttpOnly(b); // Noncompliant + + c1.setHttpOnly(param); + + jakarta.servlet.http.Cookie c3; + c3 = new jakarta.servlet.http.Cookie("name", "value"); + c3.setHttpOnly(false); // Noncompliant + + c3.setHttpOnly(true); + + boolean bValue = true; + c3.setHttpOnly(!bValue); // FN + } + + jakarta.servlet.http.Cookie getC1() { + return new jakarta.servlet.http.Cookie("name", "value"); // Noncompliant [[sc=16;ec=43]] + } + + jakarta.servlet.http.Cookie returnHttpCookie(jakarta.servlet.http.HttpServletResponse response) { + jakarta.servlet.http.Cookie cookie = new jakarta.servlet.http.Cookie("name", "value"); // Noncompliant + response.addCookie(new jakarta.servlet.http.Cookie("name", "value")); // Noncompliant + return new jakarta.servlet.http.Cookie("name", "value"); // Noncompliant + } + + void jakartaRsCookie() { + jakarta.ws.rs.core.Cookie c1 = new jakarta.ws.rs.core.Cookie("name", "value"); // Noncompliant + jakarta.ws.rs.core.Cookie c2 = new jakarta.ws.rs.core.Cookie("name", "value", "path", "domain"); // Noncompliant + } + + void jakartaRsNewCookie(jakarta.ws.rs.core.Cookie cookie) { + jakarta.ws.rs.core.NewCookie c1 = new jakarta.ws.rs.core.NewCookie("name", "value", "path", "domain", "comment", 1, true); // Noncompliant + jakarta.ws.rs.core.NewCookie c2 = new jakarta.ws.rs.core.NewCookie(cookie, "comment", 2, true); // Noncompliant + jakarta.ws.rs.core.NewCookie c3 = new jakarta.ws.rs.core.NewCookie(cookie); // Noncompliant + jakarta.ws.rs.core.NewCookie c4 = new jakarta.ws.rs.core.NewCookie(cookie, "c", 1, true); // Noncompliant + + jakarta.ws.rs.core.NewCookie c5 = new jakarta.ws.rs.core.NewCookie(cookie, "c", 1, new Date(), false, true); // last param is HttpOnly + jakarta.ws.rs.core.NewCookie c6 = new jakarta.ws.rs.core.NewCookie("1", "2", "3", "4", 5, "6", 7, new Date(), false, true); + jakarta.ws.rs.core.NewCookie c7 = new jakarta.ws.rs.core.NewCookie("1", "2", "3", "4", "5", 6, false, true); + } + + jakarta.ws.rs.core.NewCookie getC3() { + return new jakarta.ws.rs.core.NewCookie("name", "value", "path", "domain", "comment", 1, true); // Noncompliant + } + + // SONARJAVA-2772 + jakarta.servlet.http.Cookie xsfrToken() { + String cookieName = "XSRF-TOKEN"; + + jakarta.servlet.http.Cookie xsfrToken = new jakarta.servlet.http.Cookie("XSRF-TOKEN", "value"); // OK, used to implement XSRF + xsfrToken.setHttpOnly(false); + + jakarta.servlet.http.Cookie xsfrToken2 = new jakarta.servlet.http.Cookie("XSRF-TOKEN", "value"); + xsfrToken2.setHttpOnly(true); + + jakarta.servlet.http.Cookie xsfrToken3 = new jakarta.servlet.http.Cookie("XSRF-TOKEN", "value"); + + jakarta.ws.rs.core.Cookie xsfrToken6 = new jakarta.ws.rs.core.Cookie("XSRF-TOKEN", "value"); + + jakarta.ws.rs.core.Cookie xsfrToken7 = new jakarta.ws.rs.core.Cookie("XSRF-TOKEN", "value", "path", "domain"); + + play.mvc.Http.CookieBuilder xsfrToken10; + xsfrToken10 = play.mvc.Http.Cookie.builder("XSRF-TOKEN", "2"); + xsfrToken10.withHttpOnly(false); + + play.mvc.Http.CookieBuilder xsfrToken11 = play.mvc.Http.Cookie.builder("XSRF-TOKEN", "2"); + xsfrToken11.withHttpOnly(false); + + jakarta.servlet.http.Cookie xsfrToken12 = new jakarta.servlet.http.Cookie("CSRFToken", "value"); + xsfrToken12.setHttpOnly(false); + + jakarta.servlet.http.Cookie xsfrToken13 = new jakarta.servlet.http.Cookie("Csrf-token", "value"); + xsfrToken13.setHttpOnly(false); + + return new jakarta.servlet.http.Cookie("XSRF-TOKEN", "value"); + } +} + +class JakartaCookieHttpOnlyCheckCookieA extends jakarta.servlet.http.Cookie { + public jakarta.servlet.http.Cookie c; + + public JakartaCookieHttpOnlyCheckCookieA() { + super("name", "value"); + } + + public void setHttpOnly(boolean isHttpOnly) { + } + + void foo() { + setHttpOnly(false); // Noncompliant + } + + void bar(boolean x) { + setHttpOnly(x); + } + + void baz() { + setHttpOnly(true); + } +} + +class JakartaCookieHttpOnlyCheckCookieB { + JakartaCookieHttpOnlyCheckCookieA a; + + public void setHttpOnly(boolean isHttpOnly) { + } + + void foo() { + setHttpOnly(false); + } + + void bar() { + return; + } + + JakartaCookieHttpOnlyCheckCookieA getC() { + return new JakartaCookieHttpOnlyCheckCookieA(); // Noncompliant + } + + void baw() { + int i; + i = 1; + a.c = new jakarta.servlet.http.Cookie("1", "2"); // FN + } +} diff --git a/java-checks-test-sources/src/main/java/symbolicexecution/checks/NonNullSetToNullCheck/noDefault/NonNullSetToNullCheck.java b/java-checks-test-sources/src/main/java/symbolicexecution/checks/NonNullSetToNullCheck/noDefault/NonNullSetToNullCheck.java index 3bbca110cb6..3d83253b027 100644 --- a/java-checks-test-sources/src/main/java/symbolicexecution/checks/NonNullSetToNullCheck/noDefault/NonNullSetToNullCheck.java +++ b/java-checks-test-sources/src/main/java/symbolicexecution/checks/NonNullSetToNullCheck/noDefault/NonNullSetToNullCheck.java @@ -537,3 +537,73 @@ public static void f() { new TestSonar(null, null, null, null, 0L, null); // Noncompliant } } + +// ============ jakarta annotations ============ +class JakartaSpringJavaBean { + + @jakarta.validation.constraints.NotNull // This annotation will be used by Bean Validation + private String field; + + private JakartaSpringJavaBean() { // Compliant + // Java Bean's fields will be initialized and validated later + } + + public String getField() { + return field; + } + + public void setField(String field) { + this.field = field; + } +} +@jakarta.persistence.Entity +class JakartaJpaEntityInvalidDefault { + + @jakarta.annotation.Nonnull + private String itemName; + + @jakarta.annotation.Nonnull + private String primary; + private String secondary; + + private String otherField; + + public JakartaJpaEntityInvalidDefault() { // Noncompliant {{"itemName" is marked "@Nonnull" but is not initialized in this constructor.}} + otherField = "test"; + } + public void setColors2(@jakarta.annotation.Nullable String color) { + primary = color; // Noncompliant {{"primary" is marked "@Nonnull" but is set to null.}} + secondary = color; // Compliant, secondary is not Nonnull + } +} + +@jakarta.persistence.Embeddable +class JakartaJpaEmbeddable { + + @jakarta.annotation.Nonnull + private String itemName; + + public JakartaJpaEmbeddable() { // Compliant + // Default constructor for JPA + } + + public JakartaJpaEmbeddable(String name) { + itemName = name; + } +} + +@jakarta.persistence.MappedSuperclass +class JakartaJpaMappedSuperClass { + + @jakarta.annotation.Nonnull + private String itemName; + + public JakartaJpaMappedSuperClass() { // Compliant + // Default constructor for JPA + } + + public JakartaJpaMappedSuperClass(String name) { + itemName = name; + } +} + diff --git a/java-checks-test-sources/src/main/java/symbolicexecution/checks/NonNullSetToNullCheck/packageNonNull/NonNullSetToNullCheck.java b/java-checks-test-sources/src/main/java/symbolicexecution/checks/NonNullSetToNullCheck/packageNonNull/NonNullSetToNullCheck.java index 6f6c0f0dc4c..a249853f720 100644 --- a/java-checks-test-sources/src/main/java/symbolicexecution/checks/NonNullSetToNullCheck/packageNonNull/NonNullSetToNullCheck.java +++ b/java-checks-test-sources/src/main/java/symbolicexecution/checks/NonNullSetToNullCheck/packageNonNull/NonNullSetToNullCheck.java @@ -34,6 +34,14 @@ public void nonNullMethod(@Nonnull String s) { public void nullableMethod(@Nullable String s) { nullableMethod(null); // Compliant } + + public void jakartaNonNullMethod(@jakarta.annotation.Nonnull String s) { + nonNullMethod(null); // Noncompliant {{Parameter 1 to this call is marked "@Nonnull" but null could be passed.}} + } + + public void jakartaNullableMethod(@jakarta.annotation.Nonnull String s) { + nullableMethod(null); // Compliant + } } class HandleNonNullByDefaultPrimitives { diff --git a/java-checks/src/main/java/org/sonar/java/checks/LeastSpecificTypeCheck.java b/java-checks/src/main/java/org/sonar/java/checks/LeastSpecificTypeCheck.java index b6aebd6f597..3bc1ee6c83c 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/LeastSpecificTypeCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/LeastSpecificTypeCheck.java @@ -25,6 +25,7 @@ import java.util.Iterator; import java.util.List; import java.util.Optional; +import java.util.Set; import org.sonar.check.Rule; import org.sonar.java.model.JUtils; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; @@ -43,6 +44,14 @@ @Rule(key = "S3242") public class LeastSpecificTypeCheck extends IssuableSubscriptionVisitor { + private static final Set SPRING_INJECT_ANNOTATIONS = Set.of( + "org.springframework.beans.factory.annotation.Autowired", + "javax.inject.Inject", + "jakarta.inject.Inject", + "javax.annotation.Resource", + "jakarta.annotation.Resource" + ); + @Override public List nodesToVisit() { return Collections.singletonList(Tree.Kind.METHOD); @@ -232,9 +241,7 @@ private static boolean isMethodInvocationOnParameter(Symbol parameter, MethodInv } private static boolean isSpringInjectionAnnotated(SymbolMetadata metadata) { - return metadata.isAnnotatedWith("org.springframework.beans.factory.annotation.Autowired") - || metadata.isAnnotatedWith("javax.inject.Inject") - || metadata.isAnnotatedWith("javax.annotation.Resource"); + return SPRING_INJECT_ANNOTATIONS.stream().anyMatch(metadata::isAnnotatedWith); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java b/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java index 7f02ffc22df..84add6d48bb 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/security/CookieHttpOnlyCheck.java @@ -32,7 +32,6 @@ import javax.annotation.Nullable; import org.sonar.check.Rule; import org.sonar.java.checks.helpers.ExpressionsHelper; -import org.sonarsource.analyzer.commons.collections.SetUtils; import org.sonar.java.model.LiteralUtils; import org.sonar.plugins.java.api.IssuableSubscriptionVisitor; import org.sonar.plugins.java.api.JavaFileScannerContext; @@ -71,20 +70,25 @@ public class CookieHttpOnlyCheck extends IssuableSubscriptionVisitor { private static final class ClassName { private static final String SERVLET_COOKIE = "javax.servlet.http.Cookie"; + private static final String JAKARTA_SERVLET_COOKIE = "jakarta.servlet.http.Cookie"; private static final String NET_HTTP_COOKIE = "java.net.HttpCookie"; private static final String JAX_RS_COOKIE = "javax.ws.rs.core.Cookie"; + private static final String JAKARTA_RS_COOKIE = "jakarta.ws.rs.core.Cookie"; private static final String JAX_RS_NEW_COOKIE = "javax.ws.rs.core.NewCookie"; + private static final String JAKARTA_RS_NEW_COOKIE = "jakarta.ws.rs.core.NewCookie"; private static final String SHIRO_COOKIE = "org.apache.shiro.web.servlet.SimpleCookie"; private static final String PLAY_COOKIE = "play.mvc.Http$Cookie"; private static final String PLAY_COOKIE_BUILDER = "play.mvc.Http$CookieBuilder"; } - private static final Set SETTER_NAMES = SetUtils.immutableSetOf("setHttpOnly", "withHttpOnly"); + private static final Set SETTER_NAMES = Set.of("setHttpOnly", "withHttpOnly"); - private static final Set CLASSES = SetUtils.immutableSetOf( + private static final Set CLASSES = Set.of( ClassName.SERVLET_COOKIE, + ClassName.JAKARTA_SERVLET_COOKIE, ClassName.NET_HTTP_COOKIE, ClassName.JAX_RS_COOKIE, + ClassName.JAKARTA_RS_COOKIE, ClassName.SHIRO_COOKIE, ClassName.PLAY_COOKIE, ClassName.PLAY_COOKIE_BUILDER); @@ -99,17 +103,22 @@ private static final class ClassName { .addParametersMatcher(ClassName.JAX_RS_COOKIE, JAVA_LANG_STRING, INT, JAVA_UTIL_DATE, BOOLEAN, BOOLEAN) .build(), MethodMatchers.create() - .ofSubTypes(ClassName.JAX_RS_NEW_COOKIE) + .ofSubTypes(ClassName.JAKARTA_RS_NEW_COOKIE) + .constructor() + .addParametersMatcher(ClassName.JAKARTA_RS_COOKIE, JAVA_LANG_STRING, INT, JAVA_UTIL_DATE, BOOLEAN, BOOLEAN) + .build(), + MethodMatchers.create() + .ofSubTypes(ClassName.JAX_RS_NEW_COOKIE, ClassName.JAKARTA_RS_NEW_COOKIE) .constructor() .addParametersMatcher(JAVA_LANG_STRING, JAVA_LANG_STRING, JAVA_LANG_STRING, JAVA_LANG_STRING, INT, JAVA_LANG_STRING, INT, JAVA_UTIL_DATE, BOOLEAN, BOOLEAN) .build(), MethodMatchers.create() - .ofSubTypes(ClassName.JAX_RS_NEW_COOKIE) + .ofSubTypes(ClassName.JAX_RS_NEW_COOKIE, ClassName.JAKARTA_RS_NEW_COOKIE) .constructor() .addParametersMatcher(JAVA_LANG_STRING, JAVA_LANG_STRING, JAVA_LANG_STRING, JAVA_LANG_STRING, JAVA_LANG_STRING, INT, BOOLEAN, BOOLEAN) .build(), MethodMatchers.create() - .ofSubTypes(ClassName.PLAY_COOKIE) + .ofSubTypes(ClassName.PLAY_COOKIE, ClassName.JAKARTA_RS_NEW_COOKIE) .constructor() .addParametersMatcher(JAVA_LANG_STRING, JAVA_LANG_STRING, "java.lang.Integer", JAVA_LANG_STRING, JAVA_LANG_STRING, BOOLEAN, BOOLEAN) .build()); diff --git a/java-checks/src/test/java/org/sonar/java/checks/SanityTest.java b/java-checks/src/test/java/org/sonar/java/checks/SanityTest.java index af5529d7058..424711cd7d3 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/SanityTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/SanityTest.java @@ -132,7 +132,7 @@ void test() throws Exception { .filter(SanityTest::isTypeResolutionError) .collect(Collectors.toList()); - assertThat(errorLogs).hasSize(28); + assertThat(errorLogs).hasSize(30); List remainingErrors = new ArrayList<>(errorLogs); remainingErrors.removeAll(parsingErrors); @@ -140,7 +140,7 @@ void test() throws Exception { assertThat(remainingErrors).isEmpty(); assertThat(typeResolutionErrors) - .hasSize(16) + .hasSize(18) .map(LogAndArguments::getFormattedMsg) .map(log -> log.substring("ECJ Unable to resolve type ".length())) // FIXME investigate root cause (seems to be a conflict of version, with classes from JDK not resolved correctly @@ -148,6 +148,8 @@ void test() throws Exception { "javax.servlet.http.Cookie", "javax.validation.ConstraintValidator", "javax.ws.rs.core.Cookie", + "jakarta.ws.rs.core.Cookie", + "jakarta.servlet.http.Cookie", "javax.ws.rs.core.NewCookie", "junit.framework.TestCase", "org.apache.commons.lang.math.RandomUtils", diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java index c5f95e0b8dc..f7821a30d3b 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java @@ -67,6 +67,7 @@ private JSymbolMetadataNullabilityHelper() { */ private static final Set STRONG_NULLABLE_ANNOTATIONS = SetUtils.immutableSetOf( "javax.annotation.CheckForNull", + "jakarta.annotation.CheckForNull", "edu.umd.cs.findbugs.annotations.CheckForNull", "org.netbeans.api.annotations.common.CheckForNull", // Despite the name, some Nullable annotations are meant to be used as CheckForNull @@ -91,6 +92,7 @@ private JSymbolMetadataNullabilityHelper() { "io.reactivex.annotations.Nullable", "io.reactivex.rxjava3.annotations.Nullable", "javax.annotation.Nullable", + "jakarta.annotation.Nullable", "org.checkerframework.checker.nullness.compatqual.NullableDecl", "org.checkerframework.checker.nullness.compatqual.NullableType", "org.checkerframework.checker.nullness.qual.Nullable", @@ -120,6 +122,7 @@ private JSymbolMetadataNullabilityHelper() { "io.reactivex.annotations.NonNull", "io.reactivex.rxjava3.annotations.NonNull", "javax.validation.constraints.NotNull", + "jakarta.validation.constraints.NotNull", "lombok.NonNull", "org.checkerframework.checker.nullness.compatqual.NonNullDecl", "org.checkerframework.checker.nullness.compatqual.NonNullType", @@ -140,6 +143,8 @@ private JSymbolMetadataNullabilityHelper() { */ private static final String JAVAX_ANNOTATION_NONNULL = "javax.annotation.Nonnull"; + private static final String JAKARTA_ANNOTATION_NONNULL = "jakarta.annotation.Nonnull"; + /** * Target parameters and return values. * Only applicable to package. @@ -156,11 +161,13 @@ private JSymbolMetadataNullabilityHelper() { * Target parameters only. */ private static final String JAVAX_ANNOTATION_PARAMETERS_ARE_NONNULL_BY_DEFAULT = "javax.annotation.ParametersAreNonnullByDefault"; + private static final String JAKARTA_ANNOTATION_PARAMETERS_ARE_NONNULL_BY_DEFAULT = "jakarta.annotation.ParametersAreNonnullByDefault"; /** * Target parameters only. */ private static final String JAVAX_ANNOTATION_PARAMETERS_ARE_NULLABLE_BY_DEFAULT = "javax.annotation.ParametersAreNullableByDefault"; + private static final String JAKARTA_ANNOTATION_PARAMETERS_ARE_NULLABLE_BY_DEFAULT = "jakarta.annotation.ParametersAreNullableByDefault"; /** * Target fields only. @@ -214,6 +221,11 @@ private JSymbolMetadataNullabilityHelper() { configureAnnotation(JAVAX_ANNOTATION_PARAMETERS_ARE_NULLABLE_BY_DEFAULT, WEAK_NULLABLE, Collections.singletonList(PARAMETER), Arrays.asList(NullabilityLevel.METHOD, CLASS, PACKAGE)); + configureAnnotation(JAKARTA_ANNOTATION_PARAMETERS_ARE_NONNULL_BY_DEFAULT, NON_NULL, + Collections.singletonList(PARAMETER), Arrays.asList(NullabilityLevel.METHOD, CLASS, PACKAGE)); + configureAnnotation(JAKARTA_ANNOTATION_PARAMETERS_ARE_NULLABLE_BY_DEFAULT, WEAK_NULLABLE, + Collections.singletonList(PARAMETER), Arrays.asList(NullabilityLevel.METHOD, CLASS, PACKAGE)); + configureAnnotation(ORG_SPRINGFRAMEWORK_LANG_NON_NULL_FIELDS, NON_NULL, Collections.singletonList(FIELD), Collections.singletonList(PACKAGE)); @@ -227,10 +239,13 @@ private JSymbolMetadataNullabilityHelper() { // Add all annotations to the set of known annotations KNOWN_ANNOTATIONS.add(JAVAX_ANNOTATION_NONNULL); + KNOWN_ANNOTATIONS.add(JAKARTA_ANNOTATION_NONNULL); KNOWN_ANNOTATIONS.add(COM_MONGO_DB_LANG_NON_NULL_API); KNOWN_ANNOTATIONS.add(ORG_SPRINGFRAMEWORK_LANG_NON_NULL_API); KNOWN_ANNOTATIONS.add(JAVAX_ANNOTATION_PARAMETERS_ARE_NONNULL_BY_DEFAULT); + KNOWN_ANNOTATIONS.add(JAKARTA_ANNOTATION_PARAMETERS_ARE_NONNULL_BY_DEFAULT); KNOWN_ANNOTATIONS.add(JAVAX_ANNOTATION_PARAMETERS_ARE_NULLABLE_BY_DEFAULT); + KNOWN_ANNOTATIONS.add(JAKARTA_ANNOTATION_PARAMETERS_ARE_NULLABLE_BY_DEFAULT); KNOWN_ANNOTATIONS.add(ORG_SPRINGFRAMEWORK_LANG_NON_NULL_FIELDS); KNOWN_ANNOTATIONS.add(ORG_ECLIPSE_JDT_ANNOTATION_NON_NULL_BY_DEFAULT); } @@ -338,7 +353,8 @@ private static boolean isNonNullAnnotation(Type type) { } private static NullabilityType getTypeFromNonNull(AnnotationInstance annotation) { - if (JAVAX_ANNOTATION_NONNULL.equals(annotationType(annotation).fullyQualifiedName())) { + if (JAVAX_ANNOTATION_NONNULL.equals(annotationType(annotation).fullyQualifiedName()) + || JAKARTA_ANNOTATION_NONNULL.equals(annotationType(annotation).fullyQualifiedName())) { List values = annotation.values(); if (values.isEmpty() || checkAnnotationParameter(values, "when", "ALWAYS")) { return NON_NULL; diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java index 12ca9bf13bd..8fe2404ed04 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JSymbolMetadataTest.java @@ -203,6 +203,13 @@ void meta_annotation_nullability() throws IOException { ); } + @Test + void jakarta_annotation_nullability() throws IOException { + assertNullability( + NULLABILITY_SOURCE_DIR.resolve(Paths.get("no_default", "JakartaNullabilityAnnotation.java")) + ); + } + @Test void generics_nullability() throws IOException { Path sourceFile = NULLABILITY_SOURCE_DIR.resolve(Paths.get("no_default", "NullabilityWithGenerics.java")); diff --git a/java-symbolic-execution/src/main/java/org/sonar/java/se/checks/NonNullSetToNullCheck.java b/java-symbolic-execution/src/main/java/org/sonar/java/se/checks/NonNullSetToNullCheck.java index 61ff67f35ed..0b6749c815c 100644 --- a/java-symbolic-execution/src/main/java/org/sonar/java/se/checks/NonNullSetToNullCheck.java +++ b/java-symbolic-execution/src/main/java/org/sonar/java/se/checks/NonNullSetToNullCheck.java @@ -66,7 +66,10 @@ public class NonNullSetToNullCheck extends SECheck { private static final String[] JPA_ANNOTATIONS = { "javax.persistence.Entity", "javax.persistence.Embeddable", - "javax.persistence.MappedSuperclass" + "javax.persistence.MappedSuperclass", + "jakarta.persistence.Entity", + "jakarta.persistence.Embeddable", + "jakarta.persistence.MappedSuperclass" }; private Deque methodTrees = new ArrayDeque<>(); @@ -155,7 +158,7 @@ private static boolean callsThisConstructor(MethodTree constructor) { private void checkVariable(CheckerContext context, MethodTree tree, final Symbol symbol) { Optional nonnullAnnotationAsString = getNonnullAnnotationAsString(symbol); - if (nonnullAnnotationAsString.isEmpty() || isJavaxValidationConstraint(symbol) || symbol.isStatic()) { + if (nonnullAnnotationAsString.isEmpty() || isJavaxOrJakartaValidationConstraint(symbol) || symbol.isStatic()) { return; } if (isUndefinedOrNull(context, symbol)) { @@ -164,9 +167,10 @@ private void checkVariable(CheckerContext context, MethodTree tree, final Symbol } } - private static boolean isJavaxValidationConstraint(Symbol symbol) { + private static boolean isJavaxOrJakartaValidationConstraint(Symbol symbol) { SymbolMetadata.AnnotationInstance annotation = symbol.metadata().nullabilityData().annotation(); - return annotation != null && annotation.symbol().type().fullyQualifiedName().startsWith("javax.validation.constraints."); + return annotation != null + && annotation.symbol().type().fullyQualifiedName().matches("^(javax|jakarta)\\.validation\\.constraints\\..*"); } private static boolean isUndefinedOrNull(CheckerContext context, Symbol symbol) {