I've written a patch as a proof of concept:
From 1c92777379bf74468a466dfe406825f5d5c8c4ba Mon Sep 17 00:00:00 2001
From: Marc O'Morain <marc@circleci.com>
Date: Wed, 29 Sep 2021 13:28:57 +0100
Subject: [PATCH] Emit a warning when calling deprecated Java APIs
Detect calls to decrecated Java methods, classes, and static methods.
---
src/jvm/clojure/lang/Compiler.java | 28 +++++++++++++++++++
test/clojure/test_clojure/rt.clj | 18 +++++++++++-
.../ClassWithDeprecatedMethod.java | 22 +++++++++++++++
3 files changed, 67 insertions(+), 1 deletion(-)
create mode 100644 test/java/compilation/ClassWithDeprecatedMethod.java
diff --git a/src/jvm/clojure/lang/Compiler.java b/src/jvm/clojure/lang/Compiler.java
index 041786e8..bb2172b6 100644
--- a/src/jvm/clojure/lang/Compiler.java
+++ b/src/jvm/clojure/lang/Compiler.java
@@ -1151,16 +1151,19 @@ static class InstanceFieldExpr extends FieldExpr implements AssignableExpr{
this.target = target;
this.targetClass = target.hasJavaClass() ? target.getJavaClass() : null;
this.field = targetClass != null ? Reflector.getField(targetClass, fieldName, false) : null;
this.fieldName = fieldName;
this.line = line;
this.column = column;
this.tag = tag;
this.requireField = requireField;
+
+ // TODO: deprecation warning.
+
if(field == null && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
{
if(targetClass == null)
{
RT.errPrintWriter()
.format("Reflection warning, %s:%d:%d - reference to field %s can't be resolved.\n",
SOURCE_PATH.deref(), line, column, fieldName);
}
@@ -1523,16 +1526,29 @@ static class InstanceMethodExpr extends MethodExpr{
method = m;
if(method == null && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
{
RT.errPrintWriter()
.format("Reflection warning, %s:%d:%d - call to method %s on %s can't be resolved (argument types: %s).\n",
SOURCE_PATH.deref(), line, column, methodName, target.getJavaClass().getName(), getTypeStringForArgs(args));
}
}
+
+ if (isDeprecated(target.getJavaClass()) && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
+ {
+ RT.errPrintWriter()
+ .format("Deprecation warning, %s:%d:%d - class %s is deprecated.\n",
+ SOURCE_PATH.deref(), line, column, target.getJavaClass().getSimpleName());
+ }
+ if (isDeprecated(method) && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
+ {
+ RT.errPrintWriter()
+ .format("Deprecation warning, %s:%d:%d - method %s is deprecated.\n",
+ SOURCE_PATH.deref(), line, column, methodName);
+ }
}
else
{
method = null;
if(RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
{
RT.errPrintWriter()
.format("Reflection warning, %s:%d:%d - call to method %s can't be resolved (target class is unknown).\n",
@@ -1699,16 +1715,23 @@ static class StaticMethodExpr extends MethodExpr{
SOURCE_PATH.deref(), line, column, methodName, c.getName(), getTypeStringForArgs(args));
}
if(method != null && warnOnBoxedKeyword.equals(RT.UNCHECKED_MATH.deref()) && isBoxedMath(method))
{
RT.errPrintWriter()
.format("Boxed math warning, %s:%d:%d - call: %s.\n",
SOURCE_PATH.deref(), line, column, method.toString());
}
+
+ if (isDeprecated(method) && RT.booleanCast(RT.WARN_ON_REFLECTION.deref()))
+ {
+ RT.errPrintWriter()
+ .format("Deprecation warning, %s:%d:%d - static method %s is deprecated.\n",
+ SOURCE_PATH.deref(), line, column, methodName);
+ }
}
public static boolean isBoxedMath(java.lang.reflect.Method m) {
Class c = m.getDeclaringClass();
if(c.equals(Numbers.class))
{
WarnBoxedMath boxedMath = m.getAnnotation(WarnBoxedMath.class);
if(boxedMath != null)
@@ -9084,9 +9107,14 @@ static IPersistentCollection emptyVarCallSites(){return PersistentHashSet.EMPTY;
// RT.errPrintWriter()
// .format("stack map frame \"%s\" and \"%s\" on %s:%d:%d \n",
// type1, type2,
// SOURCE_PATH.deref(), LINE.deref(), COLUMN.deref());
// }
}
};
}
+
+static boolean isDeprecated(java.lang.reflect.AnnotatedElement ae) {
+ return ae!= null && ae.getDeclaredAnnotation(Deprecated.class) != null;
+}
+
}
diff --git a/test/clojure/test_clojure/rt.clj b/test/clojure/test_clojure/rt.clj
index 39526975..7c7bff3c 100644
--- a/test/clojure/test_clojure/rt.clj
+++ b/test/clojure/test_clojure/rt.clj
@@ -61,17 +61,33 @@
(defn foo [x] (.zap x 1))))
(testing "reflection cannot resolve static method"
(should-print-err-message
#"Reflection warning, .*:\d+:\d+ - call to static method valueOf on java\.lang\.Integer can't be resolved \(argument types: java\.util\.regex\.Pattern\)\.\r?\n"
(defn foo [] (Integer/valueOf #"boom"))))
(testing "reflection cannot resolve constructor"
(should-print-err-message
#"Reflection warning, .*:\d+:\d+ - call to java\.lang\.String ctor can't be resolved\.\r?\n"
- (defn foo [] (String. 1 2 3)))))
+ (defn foo [] (String. 1 2 3))))
+ (testing "call to deprecated method"
+ (should-print-err-message
+ #"Deprecation warning, .*:\d+:\d+ - method increment is deprecated\.\r?\n"
+ (defn foo []
+ (.increment (compilation.ClassWithDeprecatedMethod.) 1))))
+ (testing "call to method in deprecated class"
+ (should-print-err-message
+ #"Deprecation warning, .*:\d+:\d+ - class Inner is deprecated\.\r?\n"
+ (defn foo []
+ (.decrement (compilation.ClassWithDeprecatedMethod$Inner.) 1))))
+ (testing "call to deprecated static method"
+ (should-print-err-message
+ #"Deprecation warning, .*:\d+:\d+ - static method empty is deprecate\.\r?\n"
+ (defn foo []
+ (compilation.ClassWithDeprecatedMethod/empty))))
+ )
(def example-var)
(deftest binding-root-clears-macro-metadata
(alter-meta! #'example-var assoc :macro true)
(is (contains? (meta #'example-var) :macro))
(.bindRoot #'example-var 0)
(is (not (contains? (meta #'example-var) :macro))))
diff --git a/test/java/compilation/ClassWithDeprecatedMethod.java b/test/java/compilation/ClassWithDeprecatedMethod.java
new file mode 100644
index 00000000..dab925aa
--- /dev/null
+++ b/test/java/compilation/ClassWithDeprecatedMethod.java
@@ -0,0 +1,22 @@
+package compilation;
+
+public class ClassWithDeprecatedMethod {
+
+ @Deprecated
+ public int increment(int x) {
+ return x + 1;
+ }
+
+
+ @Deprecated
+ public static void empty() {
+ }
+
+
+ @Deprecated
+ public static class Inner {
+ public int decrement(int x) {
+ return x - 1;
+ }
+ }
+}
--
2.31.1