Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ private Value<?> getAsValue(String keyName) {
return null;
}

@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "deprecation"}) // deprecation: EXTENDED_ATTRIBUTES
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's slightly more strict than javac here, but this does align with Intellij's warnings, so seems good

@Nullable
private static Value<?> asValue(ExtendedAttributeType type, Object value) {
switch (type) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public <T> ExtendedAttributesBuilder put(ExtendedAttributeKey<T> key, T value) {
return this;
}

@SuppressWarnings("unchecked")
@SuppressWarnings({"unchecked", "deprecation"}) // deprecation: EXTENDED_ATTRIBUTES
private void putValue(ExtendedAttributeKey<?> key, Value<?> valueObj) {
// Convert VALUE type to narrower type when possible
String keyName = key.getKey();
Expand Down
4 changes: 4 additions & 0 deletions buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ tasks {
"-Xlint:-options",
"-Xlint:-serial",
"-Xlint:-this-escape",
// We suppress the "deprecation" warning because --release 8 causes javac to warn on
// importing deprecated classes (fixed in JDK 9+, see https://bugs.openjdk.org/browse/JDK-8032211).
// We use a custom Error Prone check instead (OtelDeprecatedApiUsage).
"-Xlint:-deprecation",
// Fail build on any warning
"-Werror",
),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.gradle.customchecks;

import static com.google.errorprone.BugPattern.SeverityLevel.ERROR;

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.util.ASTHelpers;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.Tree;
import com.sun.tools.javac.code.Symbol;
Comment thread
trask marked this conversation as resolved.
import javax.annotation.Nullable;

/**
* Error Prone check that detects usage of deprecated APIs.
*
* <p>This is similar to javac's -Xlint:deprecation but properly honors {@code @SuppressWarnings}
* (including on import statements, which javac doesn't support with --release 8 due to <a
* href="https://bugs.openjdk.org/browse/JDK-8032211">JDK-8032211</a>).
*
* <p>Can be suppressed with {@code @SuppressWarnings("deprecation")}.
*/
@BugPattern(
summary = "Use of deprecated API",
severity = ERROR,
altNames = "deprecation", // so it can be suppressed with @SuppressWarnings("deprecation")
suppressionAnnotations = SuppressWarnings.class)
public class OtelDeprecatedApiUsage extends BugChecker
implements BugChecker.MethodInvocationTreeMatcher,
BugChecker.NewClassTreeMatcher,
BugChecker.MemberSelectTreeMatcher,
BugChecker.MemberReferenceTreeMatcher,
BugChecker.IdentifierTreeMatcher {

private static final long serialVersionUID = 1L;

@Override
public Description matchMethodInvocation(MethodInvocationTree tree, VisitorState state) {
Symbol sym = ASTHelpers.getSymbol(tree);
Comment thread
trask marked this conversation as resolved.
return checkDeprecated(sym, tree, state);
}

@Override
public Description matchNewClass(NewClassTree tree, VisitorState state) {
Symbol sym = ASTHelpers.getSymbol(tree);
Comment thread
trask marked this conversation as resolved.
return checkDeprecated(sym, tree, state);
}

@Override
public Description matchMemberSelect(MemberSelectTree tree, VisitorState state) {
Symbol sym = ASTHelpers.getSymbol(tree);
Comment thread
trask marked this conversation as resolved.
return checkDeprecated(sym, tree, state);
}

@Override
public Description matchMemberReference(MemberReferenceTree tree, VisitorState state) {
Symbol sym = ASTHelpers.getSymbol(tree);
Comment thread
trask marked this conversation as resolved.
return checkDeprecated(sym, tree, state);
}

@Override
public Description matchIdentifier(IdentifierTree tree, VisitorState state) {
Symbol sym = ASTHelpers.getSymbol(tree);
Comment thread
trask marked this conversation as resolved.
return checkDeprecated(sym, tree, state);
}

private Description checkDeprecated(Symbol sym, Tree tree, VisitorState state) {
Comment thread
trask marked this conversation as resolved.
if (sym == null) {
return Description.NO_MATCH;
}

// Don't warn on import statements
if (isInsideImport(state)) {
return Description.NO_MATCH;
}

// Check if the symbol itself is deprecated
if (!isDeprecated(sym, state)) {
return Description.NO_MATCH;
}

// Don't warn if we're inside a deprecated context (class, method, etc.)
if (isInsideDeprecatedCode(state)) {
return Description.NO_MATCH;
}

// Don't warn if the deprecated symbol is in the same top-level class (matches javac behavior)
if (isInSameTopLevelClass(sym, state)) {
return Description.NO_MATCH;
}

return buildDescription(tree).setMessage("Use of deprecated API: " + sym).build();
Comment thread
jack-berg marked this conversation as resolved.
}

private static boolean isInsideImport(VisitorState state) {
for (Tree tree : state.getPath()) {
if (tree instanceof ImportTree) {
return true;
}
}
return false;
}

private static boolean isDeprecated(Symbol sym, VisitorState state) {
Comment thread
trask marked this conversation as resolved.
// First try the symbol's isDeprecated() method
if (sym.isDeprecated()) {
return true;
}
// Also check for @Deprecated annotation (some symbols may not have flag set)
return ASTHelpers.hasAnnotation(sym, "java.lang.Deprecated", state);
}

private static boolean isInsideDeprecatedCode(VisitorState state) {
// Check enclosing elements (method, class) for @Deprecated
// Skip the first element which is the current node being checked
boolean first = true;
for (Tree tree : state.getPath()) {
if (first) {
first = false;
continue;
}
Symbol sym = ASTHelpers.getSymbol(tree);
Comment thread
trask marked this conversation as resolved.
if (sym != null && isDeprecated(sym, state)) {
return true;
}
}
return false;
}

private static boolean isInSameTopLevelClass(Symbol sym, VisitorState state) {
Comment thread
trask marked this conversation as resolved.
// Get the top-level class containing the deprecated symbol
Symbol.ClassSymbol deprecatedTopLevel = getTopLevelClass(sym);
Comment thread
trask marked this conversation as resolved.
Comment thread
trask marked this conversation as resolved.
if (deprecatedTopLevel == null) {
return false;
}

// Get the top-level class containing the current usage by walking the tree path
// Skip the first element (the node being checked) to find the enclosing class
Symbol.ClassSymbol usageTopLevel = null;
Comment thread
trask marked this conversation as resolved.
Comment thread
trask marked this conversation as resolved.
boolean first = true;
for (Tree tree : state.getPath()) {
if (first) {
first = false;
continue;
}
Symbol treeSym = ASTHelpers.getSymbol(tree);
Comment thread
trask marked this conversation as resolved.
if (treeSym instanceof Symbol.ClassSymbol classSymbol) {
Comment thread
trask marked this conversation as resolved.
Comment thread
trask marked this conversation as resolved.
usageTopLevel = getTopLevelClass(classSymbol);
if (usageTopLevel != null) {
break;
}
}
}
if (usageTopLevel == null) {
return false;
}

return deprecatedTopLevel.equals(usageTopLevel);
}

@Nullable
private static Symbol.ClassSymbol getTopLevelClass(Symbol sym) {
Comment thread
trask marked this conversation as resolved.
Comment thread
trask marked this conversation as resolved.
Comment thread
trask marked this conversation as resolved.
Symbol current = sym;
Comment thread
trask marked this conversation as resolved.
while (current != null) {
if (current instanceof Symbol.ClassSymbol classSymbol) {
Comment thread
trask marked this conversation as resolved.
Comment thread
trask marked this conversation as resolved.
Symbol owner = classSymbol.owner;
Comment thread
trask marked this conversation as resolved.
// Top-level class is owned by a package
if (owner instanceof Symbol.PackageSymbol) {
Comment thread
trask marked this conversation as resolved.
Comment thread
trask marked this conversation as resolved.
return classSymbol;
}
}
current = current.owner;
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
io.opentelemetry.gradle.customchecks.OtelDeprecatedApiUsage
io.opentelemetry.gradle.customchecks.OtelInternalJavadoc
io.opentelemetry.gradle.customchecks.OtelPrivateConstructorForUtilityClass
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.gradle.customchecks;

import com.google.errorprone.CompilationTestHelper;
import org.junit.jupiter.api.Test;

class OtelDeprecatedApiUsageTest {

@Test
void positiveCases() {
CompilationTestHelper.newInstance(
OtelDeprecatedApiUsage.class, OtelDeprecatedApiUsageTest.class)
.addSourceLines(
"DeprecatedClass.java",
"package test;",
"@Deprecated",
"public class DeprecatedClass {",
" @Deprecated",
" public static void deprecatedMethod() {}",
" @Deprecated",
" public static String DEPRECATED_FIELD = \"old\";",
"}")
.addSourceLines(
"PositiveCases.java",
"package test;",
"public class PositiveCases {",
" void method() {",
" // BUG: Diagnostic contains: Use of deprecated API: deprecatedMethod()",
" DeprecatedClass.deprecatedMethod();",
" // BUG: Diagnostic contains: Use of deprecated API: DEPRECATED_FIELD",
" String s = DeprecatedClass.DEPRECATED_FIELD;",
" }",
"}")
.doTest();
}

@Test
void positiveCases_classAsFieldType() {
CompilationTestHelper.newInstance(
OtelDeprecatedApiUsage.class, OtelDeprecatedApiUsageTest.class)
.addSourceLines(
"DeprecatedClass.java",
"package test;",
"@Deprecated",
"public class DeprecatedClass {}")
.addSourceLines(
"PositiveCases.java",
"package test;",
"public class PositiveCases {",
" // BUG: Diagnostic contains: Use of deprecated API: test.DeprecatedClass",
" DeprecatedClass obj;",
"}")
.doTest();
}

@Test
void negativeCases_suppressWarningsDeprecation() {
CompilationTestHelper.newInstance(
OtelDeprecatedApiUsage.class, OtelDeprecatedApiUsageTest.class)
.addSourceLines(
"DeprecatedClass.java",
"package test;",
"@Deprecated",
"public class DeprecatedClass {",
" @Deprecated",
" public static void deprecatedMethod() {}",
"}")
.addSourceLines(
"NegativeCases.java",
"package test;",
"@SuppressWarnings(\"deprecation\")",
"public class NegativeCases {",
" DeprecatedClass obj;",
" void method() {",
" DeprecatedClass.deprecatedMethod();",
" }",
"}")
.doTest();
}

@Test
void negativeCases_suppressWarningsCheckName() {
CompilationTestHelper.newInstance(
OtelDeprecatedApiUsage.class, OtelDeprecatedApiUsageTest.class)
.addSourceLines(
"DeprecatedClass.java",
"package test;",
"@Deprecated",
"public class DeprecatedClass {}")
.addSourceLines(
"NegativeCases.java",
"package test;",
"@SuppressWarnings(\"OtelDeprecatedApiUsage\")",
"public class NegativeCases {",
" DeprecatedClass obj;",
"}")
.doTest();
}

@Test
void negativeCases_insideDeprecatedClass() {
CompilationTestHelper.newInstance(
OtelDeprecatedApiUsage.class, OtelDeprecatedApiUsageTest.class)
.addSourceLines(
"DeprecatedClass.java",
"package test;",
"@Deprecated",
"public class DeprecatedClass {",
" @Deprecated",
" public static void deprecatedMethod() {}",
"}")
.addSourceLines(
"NegativeCases.java",
"package test;",
"@Deprecated",
"public class NegativeCases {",
" // Inside a deprecated class, using other deprecated APIs is fine",
" DeprecatedClass obj;",
" void method() {",
" DeprecatedClass.deprecatedMethod();",
" }",
"}")
.doTest();
}

@Test
void negativeCases_sameClassDeprecation() {
// Matches javac behavior: using deprecated members within the same class doesn't warn
CompilationTestHelper.newInstance(
OtelDeprecatedApiUsage.class, OtelDeprecatedApiUsageTest.class)
.addSourceLines(
"SameClass.java",
"package test;",
"public class SameClass {",
" @Deprecated",
" public SameClass() {}",
" public static SameClass create() {",
" // No warning: deprecated constructor is in same class",
" return new SameClass();",
" }",
" @Deprecated",
" public static void deprecatedMethod() {}",
" public void caller() {",
" // No warning: deprecated method is in same class",
" deprecatedMethod();",
" }",
"}")
.doTest();
}

@Test
void positiveCases_externalDeprecatedApi() {
// Verify the check detects deprecated APIs from external code (JDK in this case)
CompilationTestHelper.newInstance(
OtelDeprecatedApiUsage.class, OtelDeprecatedApiUsageTest.class)
.addSourceLines(
"ExternalDeprecated.java",
"package test;",
"public class ExternalDeprecated {",
" void method(Thread t) {",
" // BUG: Diagnostic contains: Use of deprecated API",
" t.stop();",
" }",
"}")
.doTest();
}
}
Comment thread
trask marked this conversation as resolved.
Loading
Loading