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-Bit Server VM 警告:选项 -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 已被弃用。
...