Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions java-checks-test-sources/default/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
3) Several plugins are disabled bellow to not generate jars
-->
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>1.8.0</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.jspecify/jspecify -->
<dependency>
<groupId>org.jspecify</groupId>
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.regex.Pattern;
import org.sonar.check.Rule;
import org.sonar.check.RuleProperty;
import org.sonar.java.checks.helpers.GeneratedStringLiteralRecognizer;
import org.sonar.java.model.LiteralUtils;
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
import org.sonar.plugins.java.api.tree.LiteralTree;
Expand Down Expand Up @@ -70,14 +71,14 @@ public List<Tree.Kind> nodesToVisit() {
public void visitNode(Tree tree) {
LiteralTree literal = (LiteralTree) tree;
String literalValue = LiteralUtils.getAsStringValue(literal);
Matcher matcher = null;
Matcher matcher;
if (allowTabsInTextBlocks && tree.is(Tree.Kind.TEXT_BLOCK)) {
matcher = CONTROL_CHARACTERS_WITHOUT_TABS_PATTERN.matcher(literalValue);
} else {
matcher = CONTROL_CHARACTERS_PATTERN.matcher(literalValue);
}
if (matcher.find()) {
reportIssue(literal, String.format(MESSAGE_FORMAT, literalValue.codePointAt(matcher.start())));
if (matcher.find() && !GeneratedStringLiteralRecognizer.isGenerated(literal)) {
reportIssue(literal, String.format(MESSAGE_FORMAT, literalValue.codePointAt(matcher.start())));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks.helpers;

import org.sonar.java.model.ExpressionUtils;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.AnnotationTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.LiteralTree;
import org.sonar.plugins.java.api.tree.Tree;

/**
* Recognizes strings that are most likely not hand-written and maintained by developers.
*
* Currently we ignore:
*
* <ul>
* <li>{@code d1} strings in {@code kotlin.Metadata} annotations</li>
* <li>All strings in {@code kotlin.jvm.internal.SourceDebugExtension} annotations</li>
* <li>All strings in {@code kotlin.coroutines.jvm.internal.DebugMetadata} annotations</li>
* </ul>
*/
public final class GeneratedStringLiteralRecognizer {

private static final String KOTLIN_METADATA = "kotlin.Metadata";
private static final String KOTLIN_SOURCE_DEBUG_EXTENSION = "kotlin.jvm.internal.SourceDebugExtension";
private static final String KOTLIN_DEBUG_METADATA = "kotlin.coroutines.jvm.internal.DebugMetadata";

private GeneratedStringLiteralRecognizer() {
}

/**
* Checks whether a string or text block literal is (most likely) generated by a known tool.
* and not hand-written by developers. Useful for rule suppression for rules that raise on such literals.
*
* @param literal the literal to check
* @return {@code true} when the literal is recognized as generated
*/
public static boolean isGenerated(LiteralTree literal) {
Comment thread
lijun-chen-sonarsource marked this conversation as resolved.
if (!literal.is(Tree.Kind.STRING_LITERAL, Tree.Kind.TEXT_BLOCK)) {
return false;
}

Tree annotationArgument = literal;
while (annotationArgument.parent() != null && !annotationArgument.parent().is(Tree.Kind.ARGUMENTS)) {
annotationArgument = annotationArgument.parent();
}

Tree arguments = annotationArgument.parent();
if (arguments == null || !(arguments.parent() instanceof AnnotationTree annotation)) {
return false;
}

Comment thread
gitar-bot[bot] marked this conversation as resolved.
return isAnnotationType(annotation, KOTLIN_SOURCE_DEBUG_EXTENSION)
|| isAnnotationType(annotation, KOTLIN_DEBUG_METADATA)
|| (annotationArgument instanceof ExpressionTree argument
&& "d1".equals(ExpressionUtils.annotationAttributeName(argument))
&& isAnnotationType(annotation, KOTLIN_METADATA));
}

private static boolean isAnnotationType(AnnotationTree annotation, String fullyQualifiedName) {
Type annotationType = annotation.symbolType();
String unqualifiedName = fullyQualifiedName.substring(fullyQualifiedName.lastIndexOf('.') + 1);
return annotationType.is(fullyQualifiedName)
|| (annotationType.isUnknown() && annotationType.name().equals(unqualifiedName));
}
Comment thread
gitar-bot[bot] marked this conversation as resolved.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/*
* SonarQube Java
* Copyright (C) SonarSource Sàrl
* mailto:info AT sonarsource DOT com
*
* You can redistribute and/or modify this program under the terms of
* the Sonar Source-Available License Version 1, as published by SonarSource Sàrl.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the Sonar Source-Available License for more details.
*
* You should have received a copy of the Sonar Source-Available License
* along with this program; if not, see https://sonarsource.com/license/ssal/
*/
package org.sonar.java.checks.helpers;

import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.LiteralTree;

import static org.assertj.core.api.Assertions.assertThat;

class GeneratedStringLiteralRecognizerTest {
Comment thread
lijun-chen-sonarsource marked this conversation as resolved.

@Test
void recognizes_generated_literals() {
assertGenerated("""
package kotlin;
@interface Metadata {
String[] d1();
}
@Metadata(d1 = {"generated"})
class A {}
""");

assertGenerated("""
package kotlin.jvm.internal;
@interface SourceDebugExtension {
String[] value();
}
@SourceDebugExtension({
"generated",
\"""
generated text block
\"""})
class A {}
""");

assertGenerated("""
package kotlin.coroutines.jvm.internal;
@interface DebugMetadata {
String c();
String f();
String m();
String[] n();
}
@DebugMetadata(c = "c", f = "f", m = "m", n = {"n"})
class A {}
""");
}

@Test
void recognizes_generated_literals_without_dependencies() {
assertGenerated("""
@Metadata(d1 = {"generated"})
class A {}
""");

assertGenerated("""
@kotlin.jvm.internal.SourceDebugExtension({"generated"})
class A {}
""");
}

@Test
void does_not_recognize_other_literals() {
assertNotGenerated("""
package kotlin;
@interface Metadata {
String[] d2();
}
@Metadata(d2 = {"not generated"})
class A {}
""");

assertNotGenerated("""
@interface NotKotlinMetadata {
String[] d1();
}
@NotKotlinMetadata(d1 = {"not generated"})
class A {
String value = "not generated";
char character = 'a';
}
""");
}

@Test
void does_not_recognize_shadowed_kotlin_metadata() {
assertNotGenerated("""
import kotlin.Metadata;
class A {
@interface Metadata {
String[] d1();
}
@Metadata(d1 = {"not generated"})
class B {}
}
""");
}

private static void assertGenerated(String source) {
assertThat(literals(source)).allMatch(GeneratedStringLiteralRecognizer::isGenerated);
}

private static void assertNotGenerated(String source) {
assertThat(literals(source)).noneMatch(GeneratedStringLiteralRecognizer::isGenerated);
}

private static List<LiteralTree> literals(String source) {
List<LiteralTree> literals = new ArrayList<>();
JParserTestUtils.parse(source).accept(new BaseTreeVisitor() {
@Override
public void visitLiteral(LiteralTree tree) {
literals.add(tree);
}
});
assertThat(literals).isNotEmpty();
return literals;
}
}
Loading