[#5899] jOOQ-checker should provide both checker-framework and ErrorProne checks

This commit is contained in:
lukaseder 2019-02-14 12:33:27 +01:00
parent a546e58996
commit e837ad67f0
5 changed files with 460 additions and 9 deletions

View File

@ -0,0 +1,79 @@
/*
* Licensed 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.checker;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import org.jooq.checker.Tools.Printer;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.bugpatterns.BugChecker.MethodInvocationTreeMatcher;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.Tree;
/**
* Common base class for matchers.
*
* @author Lukas Eder
*/
abstract class AbstractMatcher extends BugChecker implements MethodInvocationTreeMatcher {
/**
* Generated UID
*/
private static final long serialVersionUID = -3955673252940475227L;
Description error(Tree node, String message) {
return buildDescription(node).setMessage(message).build();
}
static Description print(Printer printer) {
try (PrintWriter writer = new PrintWriter(new FileWriter("error.txt"))){
writer.println("This is probably a bug in jOOQ-checker.");
writer.println("If you think this is a bug in jOOQ, please report it here: https://github.com/jOOQ/jOOQ/issues/new");
writer.println("---------------------------------------------------------------------");
printer.print(writer);
}
catch (IOException ignore) {}
return null;
}
}

View File

@ -0,0 +1,81 @@
/*
* Licensed 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.checker;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import org.jooq.PlainSQL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.MethodInvocationTree;
/**
* A checker to disallow usage of {@link PlainSQL} API, except where allowed
* explicitly.
*
* @author Lukas Eder
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "PlainSQLMatcher",
summary = "jOOQ Plain SQL usage where this is not allowed",
severity = ERROR,
linkType = NONE
)
public class PlainSQLMatcher extends AbstractMatcher {
/**
* Generated UID
*/
private static final long serialVersionUID = -690695693050288771L;
@Override
public Description matchMethodInvocation(MethodInvocationTree node, VisitorState state) {
return Tools.checkPlainSQL(
node,
() -> Tools.enclosing(state.getPath()),
message -> error(node, message),
printer -> print(printer)
);
}
}

View File

@ -0,0 +1,81 @@
/*
* Licensed 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.checker;
import static com.google.errorprone.BugPattern.LinkType.NONE;
import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;
import org.jooq.PlainSQL;
import com.google.auto.service.AutoService;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.matchers.Description;
import com.sun.source.tree.MethodInvocationTree;
/**
* A checker to disallow usage of {@link PlainSQL} API, except where allowed
* explicitly.
*
* @author Lukas Eder
*/
@AutoService(BugChecker.class)
@BugPattern(
name = "SQLDialectMatcher",
summary = "jOOQ API was used which is not supported by your SQLDialect",
severity = ERROR,
linkType = NONE
)
public class SQLDialectMatcher extends AbstractMatcher {
/**
* Generated UID
*/
private static final long serialVersionUID = -690695693050288771L;
@Override
public Description matchMethodInvocation(MethodInvocationTree node, VisitorState state) {
return Tools.checkSQLDialect(
node,
() -> Tools.enclosing(state.getPath()),
message -> error(node, message),
printer -> print(printer)
);
}
}

View File

@ -0,0 +1,215 @@
/*
* Licensed 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.
*
* Other licenses:
* -----------------------------------------------------------------------------
* Commercial licenses for this work are available. These replace the above
* ASL 2.0 and offer limited warranties, support, maintenance, and commercial
* database integrations.
*
* For more information, please visit: http://www.jooq.org/licenses
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*/
package org.jooq.checker;
import static java.util.Arrays.asList;
import static org.checkerframework.javacutil.TreeUtils.elementFromDeclaration;
import static org.checkerframework.javacutil.TreeUtils.elementFromUse;
import static org.checkerframework.javacutil.TreeUtils.enclosingClass;
import static org.checkerframework.javacutil.TreeUtils.enclosingMethod;
import java.io.PrintWriter;
import java.util.EnumSet;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import org.jooq.Allow;
import org.jooq.PlainSQL;
import org.jooq.Require;
import org.jooq.SQLDialect;
import org.jooq.Support;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.util.TreePath;
/**
* The actual implementation of the checkers / matchers.
*
* @author Lukas Eder
*/
final class Tools {
static final <T> T checkSQLDialect(
MethodInvocationTree node,
Supplier<Element> enclosingSupplier,
Function<? super String, ? extends T> error,
Function<? super Printer, ? extends T> print
) {
try {
ExecutableElement elementFromUse = elementFromUse(node);
Support support = elementFromUse.getAnnotation(Support.class);
// In the absence of a @Support annotation, all jOOQ API method calls will type check.
if (support != null) {
Element enclosing = enclosingSupplier.get();
// [#7929] "Empty" @Support annotations expand to all SQLDialects
EnumSet<SQLDialect> supported = EnumSet.copyOf(
support.value().length > 0
? asList(support.value())
: asList(SQLDialect.values())
);
EnumSet<SQLDialect> allowed = EnumSet.noneOf(SQLDialect.class);
EnumSet<SQLDialect> required = EnumSet.noneOf(SQLDialect.class);
boolean evaluateRequire = true;
while (enclosing != null) {
Allow allow = enclosing.getAnnotation(Allow.class);
if (allow != null)
allowed.addAll(asList(allow.value()));
if (evaluateRequire) {
Require require = enclosing.getAnnotation(Require.class);
if (require != null) {
evaluateRequire = false;
required.clear();
required.addAll(asList(require.value()));
}
}
enclosing = enclosing.getEnclosingElement();
}
if (allowed.isEmpty())
return error.apply("No jOOQ API usage is allowed at current scope. Use @Allow.");
boolean allowedFail = true;
allowedLoop:
for (SQLDialect a : allowed) {
for (SQLDialect s : supported) {
if (a.supports(s)) {
allowedFail = false;
break allowedLoop;
}
}
}
if (allowedFail)
return error.apply("The allowed dialects in scope " + allowed + " do not include any of the supported dialects: " + supported);
boolean requiredFail = false;
requiredLoop:
for (SQLDialect r : required) {
for (SQLDialect s : supported)
if (r.supports(s))
continue requiredLoop;
requiredFail = true;
break requiredLoop;
}
if (requiredFail)
return error.apply("Not all of the required dialects " + required + " from the current scope are supported " + supported);
}
}
catch (final Exception e) {
return print.apply(new Printer() {
@Override
public void print(PrintWriter t) {
e.printStackTrace(t);
}
});
}
return null;
}
static final <T> T checkPlainSQL(
MethodInvocationTree node,
Supplier<Element> enclosingSupplier,
Function<? super String, ? extends T> error,
Function<? super Printer, ? extends T> print
) {
try {
ExecutableElement elementFromUse = elementFromUse(node);
PlainSQL plainSQL = elementFromUse.getAnnotation(PlainSQL.class);
// In the absence of a @PlainSQL annotation,
// all jOOQ API method calls will type check.
if (plainSQL != null) {
boolean allowed = false;
Element enclosing = enclosingSupplier.get();
moveUpEnclosingLoop:
while (enclosing != null) {
if (enclosing.getAnnotation(Allow.PlainSQL.class) != null) {
allowed = true;
break moveUpEnclosingLoop;
}
enclosing = enclosing.getEnclosingElement();
}
if (!allowed)
return error.apply("Plain SQL usage not allowed at current scope. Use @Allow.PlainSQL.");
}
}
catch (final Exception e) {
return print.apply(new Printer() {
@Override
public void print(PrintWriter t) {
e.printStackTrace(t);
}
});
}
return null;
}
static Element enclosing(TreePath path) {
MethodTree enclosingMethod = enclosingMethod(path);
if (enclosingMethod != null)
return elementFromDeclaration(enclosingMethod);
ClassTree enclosingClass = enclosingClass(path);
return elementFromDeclaration(enclosingClass);
}
interface Printer {
void print(PrintWriter writer);
}
}

View File

@ -19,7 +19,6 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<org.jooq.version>3.12.0-SNAPSHOT</org.jooq.version>
<java.version>1.8</java.version>
</properties>
<dependencies>
@ -60,16 +59,13 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<source>9</source>
<target>9</target>
<fork>true</fork>
<annotationProcessors>
<annotationProcessor>org.jooq.checker.SQLDialectChecker</annotationProcessor>
<!-- <annotationProcessor>org.jooq.checker.PlainSQLChecker</annotationProcessor> -->
</annotationProcessors>
<compilerArgs>
<arg>-Xbootclasspath/p:1.8</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
@ -88,8 +84,8 @@
<configuration>
<compilerId>javac-with-errorprone</compilerId>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
<source>${java.version}</source>
<target>${java.version}</target>
<source>9</source>
<target>9</target>
<annotationProcessorPaths>
<path>
<groupId>org.jooq</groupId>
@ -98,7 +94,6 @@
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Xbootclasspath/p:1.8</arg>
<arg>-XDcompilePolicy=simple</arg>
<arg>-Xplugin:ErrorProne</arg>
</compilerArgs>