Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an errorprone check and typed annotation for Javax -> Jakarta #2366

Merged
merged 13 commits into from
Aug 25, 2022
Merged
2 changes: 2 additions & 0 deletions baseline-error-prone/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ dependencies {
implementation 'com.google.errorprone:error_prone_core'
// Ensure a new enough version of dataflow-errorprone is available
implementation 'org.checkerframework:dataflow-errorprone'
// for ForbidJavax
implementation 'com.palantir.conjure.java.runtime:conjure-java-annotations'

testImplementation gradleApi()
testImplementation 'com.palantir.tokens:auth-tokens'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* (c) Copyright 2022 Palantir Technologies Inc. All rights reserved.
*
* 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.
*/

package com.palantir.baseline.errorprone;

import com.google.auto.service.AutoService;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.BugPattern;
import com.google.errorprone.VisitorState;
import com.google.errorprone.bugpatterns.BugChecker;
import com.google.errorprone.matchers.Description;
import com.google.errorprone.predicates.TypePredicate;
import com.google.errorprone.predicates.TypePredicates;
import com.google.errorprone.util.ASTHelpers;
import com.palantir.errorprone.ForbidJavax;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Type;
import java.lang.annotation.Annotation;
import java.util.List;
import java.util.function.Predicate;

@AutoService(BugChecker.class)
@BugPattern(
link = "https://github.com/palantir/gradle-baseline#baseline-error-prone-checks",
linkType = BugPattern.LinkType.CUSTOM,
severity = BugPattern.SeverityLevel.ERROR,
summary = "Supplying an object which uses legacy javax types, such as javax.ws.rs to a\n"
+ "method which requires newer jakarta types is a runtime error. This check ensures\n"
+ "that you only supply proper types to these methods which generally just take an\n"
+ "untyped Object. There is no auto-fix for this check, you must fix it manually")
public final class ForbidJavaxParameterType extends BugChecker implements BugChecker.MethodInvocationTreeMatcher {

private static final Predicate<Symbol> HAS_JAXRS_ANNOTATION =
SymbolPredicates.hasAnnotationWithPackage("javax.ws.rs");

private static final TypePredicate IMPLEMENTS_FEATURE = TypePredicates.isDescendantOf("javax.ws.rs.core.Feature");

@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
List<? extends ExpressionTree> arguments = tree.getArguments();
MethodSymbol methodSymbol = ASTHelpers.getSymbol(tree);
List<VarSymbol> typeParameters = methodSymbol.getParameters();

if (typeParameters == null
|| typeParameters.isEmpty()
|| arguments == null
|| arguments.isEmpty()
|| arguments.size() != typeParameters.size()) {
return Description.NO_MATCH;
}

for (int i = 0; i < typeParameters.size(); ++i) {
VarSymbol parameter = typeParameters.get(i);
ExpressionTree argument = arguments.get(i);

Annotation maybeRequireJakarta = parameter.getAnnotation(ForbidJavax.class);
if (maybeRequireJakarta == null) {
continue;
}

Type resultType = ASTHelpers.getResultType(argument);
if (hasJavaxInclusions(resultType, state)) {
// we know that resultType is not-null here since hasJavaxInclusions returns false for null
return buildDescription(tree)
.setMessage(resultType.asElement().getQualifiedName().toString()
+ " registers legacy javax imports but is being supplied to a method which"
+ " requires jakarta")
.build();
}
}

return Description.NO_MATCH;
}

private boolean hasJavaxInclusions(Type resultType, VisitorState state) {
if (resultType == null) {
return false;
}

return hasJavaxInclusionsOnType(resultType.asElement(), state);
}

private boolean hasJavaxInclusionsOnType(TypeSymbol symbol, VisitorState state) {
if (symbol instanceof ClassSymbol) {
ClassSymbol classType = (ClassSymbol) symbol;

if (IMPLEMENTS_FEATURE.apply(classType.type, state)) {
return true;
}

if (HAS_JAXRS_ANNOTATION.test(classType)) {
return true;
}

// this obviously fails if there is > 1 level of hierarchy where the JaxRS
// annotations exist. Fortunately this should be fine for most use cases
List<Type> thisAndParents = ImmutableList.<Type>builder()
.add(classType.type)
.addAll(classType.getInterfaces())
.build();
for (Type t : thisAndParents) {
for (Symbol sym : t.tsym.getEnclosedElements()) {
if (HAS_JAXRS_ANNOTATION.test(sym)) {
return true;
}
}
}
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.code.Attribute.Compound;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.util.Name;
import java.util.Locale;
import javax.lang.model.element.Modifier;

Expand Down Expand Up @@ -74,6 +76,19 @@ static <T extends ClassTree> Matcher<T> classEnclosingClass(Matcher<ClassTree> m
};
}

static <T extends Tree> Matcher<T> hasAnnotationWithPackagePrefix(String prefix) {
return (classTree, state) -> {
Name packageName = state.getName(prefix);
Symbol sym = ASTHelpers.getSymbol(classTree);
for (Compound a : sym.getRawAttributes()) {
if (a.type.tsym.flatName().startsWith(packageName)) {
return true;
}
}
return false;
};
}

/**
* Works similarly to {@link Matchers#hasModifier(Modifier)}, but only matches elements which explicitly list the
* modifier. For example, all components nested in an interface are public by default, but they don't necessarily
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* (c) Copyright 2022 Palantir Technologies Inc. All rights reserved.
*
* 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.
*/

package com.palantir.baseline.errorprone;

import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Type.ClassType;
import java.util.function.Predicate;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.type.DeclaredType;

public final class SymbolPredicates {
private SymbolPredicates() {}

public static Predicate<Symbol> hasAnnotationWithPackage(String packagePrefix) {
return symbol -> {
for (AnnotationMirror mirror : symbol.getAnnotationMirrors()) {
DeclaredType annotationType = mirror.getAnnotationType();
if (annotationType instanceof ClassType
&& annotationType.toString().startsWith(packagePrefix)) {
return true;
}
}

return false;
};
}
}
Loading