2

I've been attempting to write a custom rules plugin for Sonarqube ~5.4, and while I've gotten a few rules implemented and working, the ones that rely on types outside the standard libraries rely on various kinds of acrobatic string matching.

I'm using the sonar-packaging-maven-plugin to do the packaging:

<plugin>
    <groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId>
    <artifactId>sonar-packaging-maven-plugin</artifactId>
    <version>1.16</version>
    <configuration>
        <pluginClass>${project.groupId}.sonar.BravuraRulesPlugin</pluginClass>
        <pluginKey>SonarPluginBravura</pluginKey>
        <skipDependenciesPackaging>false</skipDependenciesPackaging>
        <basePlugin>java</basePlugin>
    </configuration>
    <executions>

        <execution>
            <phase>package</phase>
            <goals>
                <goal>sonar-plugin</goal>
            </goals>
        </execution>

    </executions>
</plugin>

And am running the various checks using the following helper extension (kotlin):

fun <T : JavaFileScanner> T.verify() {

    val workDir = System.getProperty("user.dir");
    val folder = Paths.get(workDir, "src/test/samples", this.javaClass.simpleName);

    Files.list(folder).forEach { sample ->
        try {
            if (sample.toString().endsWith(".clean.java")) {
                JavaCheckVerifier.verifyNoIssue(sample.toString(), this);

            } else {
                JavaCheckVerifier.verify(sample.toString(), this);
            }

        } catch (error: Exception) {
            throw VerificationFailedException(sample, error);
        }
    }

};

class VerificationFailedException(path: Path, error: Exception)
        : Exception("Failed to verify $path.", error);

I create an IssuableSubscriptionVisitor subclass for the rule, and visit Tree.Kind.METHOD_INVOCATION, looking for uses of a static MAX, MIN, ASC, or DESC sql builder method being passed an AutoLongColumn. This is to stop the identifier field being used for ordering purposes.

Unfortunately, even though I have the requisite library on the maven 'test' classpath, when I try and get any of the types, they just show as !unknown!.

override fun visitNode(tree: Tree) {

    if (tree !is MethodInvocationTree) {
        return;
    }

    val methodSelect = tree.methodSelect();
    if (methodSelect !is IdentifierTree || methodSelect.name() !in setOf("MAX", "MIN", "ASC", "DESC")) {
        return;
    }

val firstArg = statement.arguments().first();
    if (firstArg !is MethodInvocationTree) {
        return;
    }

    val firstArgSelect = firstArg.methodSelect();
    if (firstArgSelect !is MemberSelectExpressionTree) {
        return;
    }

    if (firstArgSelect.type is UnknownType) {
        throw TableFlipException("(ノಥ益ಥ)ノ ┻━┻");
    }

    // It never gets here.

}

I'm sure I'm missing some vital piece of the puzzle, and I'd appreciate if someone can tell me where I'm going wrong.

EDIT: I'm using org.sonarsource.java:sonar-java-plugin:3.14 for the analyser, and while I can't release all the code for the analysis target (commercial IP and all that), here's something structurally identical to the key part:

import static com.library.UtilClass.MAX;

...

query.SELECT(biggestId = MAX(address._id())) // Noncompliant
        .FROM(address)
        .WHERE(address.user_id().EQ(userId)
                .AND(address.type_id().EQ(typeId)));
...

The type of address.id() is an com.library.Identifier that wraps a long. I'd like to be able to visit all the method invocations, check if they match com.library.UtilCLass.MAX, and if so, make sure that the first parameter isn't a com.library.Identifier. Without the type information, I have to do a regex match on _id method references, which is prone to potentially missing things.

tzrlk
  • 848
  • 1
  • 13
  • 30
  • Would you mind sharing the code you analyze ? There might be something hidden there. – benzonico Aug 22 '16 at 05:45
  • 1
    you also may want to precise which sonar java analyzer version you are using. – benzonico Aug 22 '16 at 06:03
  • I've added an update to the question. – tzrlk Aug 23 '16 at 03:30
  • First thing to try out : I can only recommend you to upgrade to latest version 4.1 (and so LTS version of SQ 5.6.1) – benzonico Aug 24 '16 at 15:59
  • Yup, updated the dependency to 4.1, but it still only picks up types from the java standard library. Moving the library I'm getting `AutoLongColumn` from, from `test` to `compile` scope makes no difference, either. – tzrlk Aug 29 '16 at 00:39
  • Found the solution and added my answer. Man, you'd think it'd just grab what it needed off the classpath its running under, but nope. – tzrlk Aug 29 '16 at 04:56

1 Answers1

2

So, turns out that the way to get the types available is by using maven (or whatever tool you're using) to copy the needed jars into a directory, then turn the lot into a list of files, and pass that to the test verifier.

For example, lets pretend we're trying to find usages of joda-time:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-dependency-plugin</artifactId>
    <version>2.10</version>
    <executions>

        <execution>
            <id>copy-libs</id>
            <phase>generate-test-resources</phase>
            <goals>
                <goal>copy</goal>
            </goals>
            <configuration>

                <artifactItems>

                    <artifactItem>
                        <groupId>joda-time</groupId>
                        <artifactId>joda-time</artifactId>
                        <version>2.9.4</version>
                    </artifactItem>

                </artifactItems>

            </configuration>
        </execution>

    <executions>
</plugin>

This execution will put the joda-time jar into the target/dependency directory. Next, you make sure to enumerate the jars in that directory, and add them to your test verification (we're assuming you named your verifier 'JodaCheck'):

// Not at all necessary, but it makes the code later on a lot easier to read.
fun <T> Stream<T>.toList(): List<T> = this.collect({
    mutableListOf()

}, { list, item ->
    list.add(item)

}, { list, otherList ->
    list.addAll(otherList)

})

...

val workDir = System.getProperty("user.dir")
val sampleFile = Paths.get(workDir, "src/test/samples/JodaSample.java").toString()
val dependencies = Files.list(Paths.get(workDir, "target/dependency"))
        .map { it.toFile() }.toList()

JavaCheckVerifier.verify(sampleFile, JodaChecker(), dependencies)

Once you've done that, debugging through the tests will show that the joda-time classes are available during analysis.

tzrlk
  • 848
  • 1
  • 13
  • 30
  • One thing to note : there is a good reason for this : you might not want to add dependency to your project (even in tests, because of eventual conflicts of class versions) because the types you want to tests may not be part of your custom rule project (for instance, testing JPA, etc.) – benzonico Aug 29 '16 at 07:20