2024年Clojure状态调查!中分享您的看法。

欢迎!请访问关于页面以获取更多关于如何使用此信息。

+4
编译器
编辑

我最近参与了一个升级CongoMongo到MongoDB Java库最新版本的项目的项目,进展缓慢,因为没有方法可以确定CongoMongo在哪里调用已弃用的Java API。

我希望Clojure能在编译器中对已标记为java.lang.Deprecated注释的Java方法调用的地方发出警告。这将使我能够使用lein check快速列出所有对已弃用函数的调用。

3个答案

+1

如今,我认为这可以通过类似clj-kondolsp的linter来解决。

我刚刚在VSCode中用Calva打开了一个有问题的命名空间,Calva自带`clj-kondo`和`lsp`,它们都对废弃函数的调用没有报告任何问题。
+1

编辑了

或许您可以使用jdeprscan之类的工具来检查您生成的字节码。

https://docs.oracle.com/javase/9/tools/jdeprscan.htm

$ mkdir -p src/foo
$ echo '(ns foo.core (:gen-class)) (Boolean. "true")' > src/foo/core.clj
$ mkdir -p classes
$ clojure -M -e "(compile 'foo.core)"
foo.core
$ $GRAALVM_HOME/bin/jdeprscan --class-path $(clojure -Spath) classes
Directory classes:
class foo/core__init uses deprecated method java/lang/Boolean::<init>(Ljava/lang/String;)V
0

我已编写了一个补丁作为概念验证。

 From 1c92777379bf74468a466dfe406825f5d5c8c4ba Mon Sep 17 00:00:00 2001
 From: Marc O'Morain <[email protected]>
 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
 
这个补丁工作得很正常,但最好能打印出废弃的完全限定类 + 方法。

     marc@blaster ~/dev/congomongo $ lein check  2>&1 | grep deprecated
     OpenJDK 64位服务器虚拟机警告:选项-Xverify:none和-noverify在JDK 13中被弃用,预计未来某个版本将删除。
     弃用警告,somnium/congomongo.clj:149:21 - getDB方法已弃用。
     弃用警告,somnium/congomongo.clj:158:33 - getDB方法已弃用。
     弃用警告,somnium/congomongo.clj:319:5 - getTime方法已弃用。
     弃用警告,somnium/congomongo.clj:492:16 - getCount方法已弃用。
     弃用警告,somnium/congomongo.clj:508:22 - hint方法已弃用。
     弃用警告,somnium/congomongo.clj:511:20 - setOptions方法已弃用。
     弃用警告,somnium/congomongo.clj:729:52 - outputMode方法已弃用。
     弃用警告,somnium/congomongo.clj:755:15 - getDB方法已弃用。
     弃用警告,somnium/congomongo.clj:763:8 - getDatabaseNames方法已弃用。
...