diff --git a/TestProject/Agrona-0.4.13.jar b/TestProject/Agrona-0.4.13.jar new file mode 100644 index 0000000..954336c Binary files /dev/null and b/TestProject/Agrona-0.4.13.jar differ diff --git a/TestProject/agrona-agent-2.3.2.jar b/TestProject/agrona-agent-2.3.2.jar new file mode 100644 index 0000000..fbede7a Binary files /dev/null and b/TestProject/agrona-agent-2.3.2.jar differ diff --git a/TestProject/arcCommon-0.16.jar b/TestProject/arcCommon-0.16.jar new file mode 100644 index 0000000..43be685 Binary files /dev/null and b/TestProject/arcCommon-0.16.jar differ diff --git a/TestProject/awaitility-4.2.0.jar b/TestProject/awaitility-4.2.0.jar new file mode 100644 index 0000000..1d76304 Binary files /dev/null and b/TestProject/awaitility-4.2.0.jar differ diff --git a/TestProject/awaitility-test-support-3.1.6.jar b/TestProject/awaitility-test-support-3.1.6.jar new file mode 100644 index 0000000..8505042 Binary files /dev/null and b/TestProject/awaitility-test-support-3.1.6.jar differ diff --git a/TestProject/disruptor-2.10.4.jar b/TestProject/disruptor-2.10.4.jar new file mode 100644 index 0000000..bc2d65e Binary files /dev/null and b/TestProject/disruptor-2.10.4.jar differ diff --git a/TestProject/hippo4j-monitor-base-1.5.0.jar b/TestProject/hippo4j-monitor-base-1.5.0.jar new file mode 100644 index 0000000..cefd813 Binary files /dev/null and b/TestProject/hippo4j-monitor-base-1.5.0.jar differ diff --git a/TestProject/jetty-alpn-java-server-12.0.16.jar b/TestProject/jetty-alpn-java-server-12.0.16.jar new file mode 100644 index 0000000..461793c Binary files /dev/null and b/TestProject/jetty-alpn-java-server-12.0.16.jar differ diff --git a/TestProject/jetty-ee10-websocket-jetty-server-12.0.23.jar b/TestProject/jetty-ee10-websocket-jetty-server-12.0.23.jar new file mode 100644 index 0000000..c82c566 Binary files /dev/null and b/TestProject/jetty-ee10-websocket-jetty-server-12.0.23.jar differ diff --git a/TestProject/jetty-websocket-jetty-server-12.0.15.jar b/TestProject/jetty-websocket-jetty-server-12.0.15.jar new file mode 100644 index 0000000..19a083b Binary files /dev/null and b/TestProject/jetty-websocket-jetty-server-12.0.15.jar differ diff --git a/TestProject/litesockets-http-protocol-0.27.jar b/TestProject/litesockets-http-protocol-0.27.jar new file mode 100644 index 0000000..e65c74e Binary files /dev/null and b/TestProject/litesockets-http-protocol-0.27.jar differ diff --git a/TestProject/log4j-core-2.17.1.jar b/TestProject/log4j-core-2.17.1.jar new file mode 100644 index 0000000..bbead12 Binary files /dev/null and b/TestProject/log4j-core-2.17.1.jar differ diff --git a/TestProject/netty-common-4.1.77.Final.jar b/TestProject/netty-common-4.1.77.Final.jar new file mode 100644 index 0000000..d4761d8 Binary files /dev/null and b/TestProject/netty-common-4.1.77.Final.jar differ diff --git a/TestProject/rxjava-core-0.19.6.jar b/TestProject/rxjava-core-0.19.6.jar new file mode 100644 index 0000000..9b6cbfa Binary files /dev/null and b/TestProject/rxjava-core-0.19.6.jar differ diff --git a/TestProject/rxjava-math-1.0.0.jar b/TestProject/rxjava-math-1.0.0.jar new file mode 100644 index 0000000..277a269 Binary files /dev/null and b/TestProject/rxjava-math-1.0.0.jar differ diff --git a/TestProject/shenyu-disruptor-2.6.0.jar b/TestProject/shenyu-disruptor-2.6.0.jar new file mode 100644 index 0000000..7d0309f Binary files /dev/null and b/TestProject/shenyu-disruptor-2.6.0.jar differ diff --git a/TestProject/taskflow-core-1.0.0-SNAPSHOT.jar b/TestProject/taskflow-core-1.0.0-SNAPSHOT.jar new file mode 100644 index 0000000..140cff8 Binary files /dev/null and b/TestProject/taskflow-core-1.0.0-SNAPSHOT.jar differ diff --git a/TestProject/transmittable-thread-local-2.14.5.jar b/TestProject/transmittable-thread-local-2.14.5.jar new file mode 100644 index 0000000..2673815 Binary files /dev/null and b/TestProject/transmittable-thread-local-2.14.5.jar differ diff --git a/TestProject/websocket-jetty-server-11.0.15.jar b/TestProject/websocket-jetty-server-11.0.15.jar new file mode 100644 index 0000000..5b9ff6c Binary files /dev/null and b/TestProject/websocket-jetty-server-11.0.15.jar differ diff --git a/TestProject/xorcery-disruptor-0.10.1.jar b/TestProject/xorcery-disruptor-0.10.1.jar new file mode 100644 index 0000000..4a8a555 Binary files /dev/null and b/TestProject/xorcery-disruptor-0.10.1.jar differ diff --git a/src/Mytest/CoreMax_ThreadPool.java b/src/Mytest/CoreMax_ThreadPool.java new file mode 100644 index 0000000..e9d4d8f --- /dev/null +++ b/src/Mytest/CoreMax_ThreadPool.java @@ -0,0 +1,40 @@ +package Mytest; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class CoreMax_ThreadPool { + public static void main(String[] args) { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + Integer.MAX_VALUE, + 10, // 直接使用 Integer.MAX_VALUE + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue(100), + new ThreadPoolExecutor.AbortPolicy()); + + threadPoolExecutor.execute(() -> { + while(!threadPoolExecutor.isShutdown()) { + try { + Thread.sleep(1000); + System.out.println("This is CoreMax_ThreadPool..."); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); + + // 主线程做 + System.out.println("Main Thread is to Set_Max_Value..."); +// threadPoolExecutor.setMaximumPoolSize(Integer.MAX_VALUE); + + try { + Thread.sleep(3000); + threadPoolExecutor.shutdown(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/src/Mytest/CoreMax_ThreadPool2.java b/src/Mytest/CoreMax_ThreadPool2.java new file mode 100644 index 0000000..66712d2 --- /dev/null +++ b/src/Mytest/CoreMax_ThreadPool2.java @@ -0,0 +1,39 @@ +package Mytest; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class CoreMax_ThreadPool2 { public static void main(String[] args) { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + 5, + 10, // 直接使用 Integer.MAX_VALUE + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue(100), + new ThreadPoolExecutor.AbortPolicy()); + + threadPoolExecutor.execute(() -> { + while(!threadPoolExecutor.isShutdown()) { + try { + Thread.sleep(1000); + System.out.println("This is CoreMax_ThreadPool..."); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); + + // 主线程做 + System.out.println("Main Thread is to Set_Max_Value..."); + threadPoolExecutor.setCorePoolSize(Integer.MAX_VALUE); + + try { + Thread.sleep(3000); + threadPoolExecutor.shutdown(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + +} +} diff --git a/src/Mytest/DPSETest_ThreadPool.java b/src/Mytest/DPSETest_ThreadPool.java new file mode 100644 index 0000000..7d9101b --- /dev/null +++ b/src/Mytest/DPSETest_ThreadPool.java @@ -0,0 +1,35 @@ +package Mytest; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.RejectedExecutionHandler; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class DPSETest_ThreadPool { + public static void main(String[] args) { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + 5, + 10, + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue(100), + new ThreadPoolExecutor.AbortPolicy()); + + threadPoolExecutor.execute(() -> { + while(!threadPoolExecutor.isShutdown()) { + try { + Thread.sleep(1000); + System.out.println("This is threadPoolExecutor..."); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); +// 主线程做 + System.out.println("Main Thread is to SetRejectedExecutionHandler..."); + threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); +// threadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); + + threadPoolExecutor.shutdown(); + } +} diff --git a/src/Mytest/Excp_ThreadPool.java b/src/Mytest/Excp_ThreadPool.java new file mode 100644 index 0000000..8341dc6 --- /dev/null +++ b/src/Mytest/Excp_ThreadPool.java @@ -0,0 +1,25 @@ +package Mytest; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class Excp_ThreadPool { + public static void main(String[] args) { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + 5, + 10, // 直接使用 Integer.MAX_VALUE + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue(100), + new ThreadPoolExecutor.AbortPolicy()); + + threadPoolExecutor.submit(()->{ + int result= 10/0; +// 异常未处理 +// System.out.println("This is Excp_ThreadPool"); + }); + + threadPoolExecutor.shutdown(); + } +} diff --git a/src/Mytest/ILTest_ThreadPool.java b/src/Mytest/ILTest_ThreadPool.java new file mode 100644 index 0000000..bbb6c51 --- /dev/null +++ b/src/Mytest/ILTest_ThreadPool.java @@ -0,0 +1,31 @@ +package Mytest; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +// IL存在误报!!!还是指针分析的问题; +//这个测试用例中,有shutdown,没有shutdown +public class ILTest_ThreadPool { + public static void main(String[] args) { +// 构建线程池对象,使用 execute() 提交 Runnable,submit() 提交 Callable + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + 5, + 10, + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue(100), + new ThreadPoolExecutor.AbortPolicy()); + + threadPoolExecutor.execute(()->{ + // ILChecker会检测到这种情况: + while (!threadPoolExecutor.isShutdown()) { // 触发检测 + System.out.println("Running..,"); + } + }); + +// threadPoolExecutor.shutdown(); + } +} + diff --git a/src/Mytest/IntMax_ThreadPool.java b/src/Mytest/IntMax_ThreadPool.java new file mode 100644 index 0000000..c4358c8 --- /dev/null +++ b/src/Mytest/IntMax_ThreadPool.java @@ -0,0 +1,42 @@ +package Mytest; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +// 这里测试——直接使用intmax的情况 +public class IntMax_ThreadPool { + public static void main(String[] args) { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + 5, + Integer.MAX_VALUE, // 直接使用 Integer.MAX_VALUE + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue(100), + new ThreadPoolExecutor.AbortPolicy()); + + threadPoolExecutor.execute(() -> { + while(!threadPoolExecutor.isShutdown()) { + try { + Thread.sleep(1000); + System.out.println("This is IntMax_ThreadPool..."); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); + + // 主线程做 +// System.out.println("Main Thread is to Set_Max_Value..."); +// threadPoolExecutor.setMaximumPoolSize(Integer.MAX_VALUE); + try { + Thread.sleep(3000); + threadPoolExecutor.shutdown(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + + } + +} diff --git a/src/Mytest/IntMax_ThreadPool2.java b/src/Mytest/IntMax_ThreadPool2.java new file mode 100644 index 0000000..6b6eefb --- /dev/null +++ b/src/Mytest/IntMax_ThreadPool2.java @@ -0,0 +1,43 @@ +package Mytest; + + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +//这里测试用set方法赋值intmax的情况 +public class IntMax_ThreadPool2 { + public static void main(String[] args) { + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + 5, + 10, // 直接使用 Integer.MAX_VALUE + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue(100), + new ThreadPoolExecutor.AbortPolicy()); + + threadPoolExecutor.execute(() -> { + while(!threadPoolExecutor.isShutdown()) { + try { + Thread.sleep(1000); + System.out.println("This is IntMax_ThreadPool..."); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + }); + + // 主线程做 + System.out.println("Main Thread is to Set_Max_Value..."); +// 这个set表达式,是属于Value形式; + threadPoolExecutor.setMaximumPoolSize(Integer.MAX_VALUE); + + try { + Thread.sleep(3000); + threadPoolExecutor.shutdown(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + } +} diff --git a/src/Mytest/Mytest/ILTest.class b/src/Mytest/Mytest/ILTest.class new file mode 100644 index 0000000..4bcd59f Binary files /dev/null and b/src/Mytest/Mytest/ILTest.class differ diff --git a/src/Mytest/Mytest/ILThread.class b/src/Mytest/Mytest/ILThread.class new file mode 100644 index 0000000..bed50ff Binary files /dev/null and b/src/Mytest/Mytest/ILThread.class differ diff --git a/src/Mytest/RepeatedCreate_ThreadPool.java b/src/Mytest/RepeatedCreate_ThreadPool.java new file mode 100644 index 0000000..265e8bf --- /dev/null +++ b/src/Mytest/RepeatedCreate_ThreadPool.java @@ -0,0 +1,50 @@ +package Mytest; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +public class RepeatedCreate_ThreadPool { + public static void main(String[] args) { + + ThreadPoolUtils threadPoolUtils = new ThreadPoolUtils(); + ThreadPoolExecutor myPool = threadPoolUtils.createMyPool(); + myPool.execute(()->{ + System.out.println("mypool is running"); + }); + myPool.shutdown(); + ThreadPoolExecutor myPool1 = threadPoolUtils.createMyPool(); + myPool1.execute(()->{ + System.out.println("mypool1 is running"); + }); + myPool1.shutdown(); + + +// 循环误用模式检测! +// int i=0; +// while (i<10){ +// ThreadPoolExecutor myPool = ThreadPoolUtils.createMyPool(); +// myPool.execute(()->{ +// System.out.println("running..."); +// }); +// i++; +// myPool.shutdown(); +// +// } + + } + +} + +class ThreadPoolUtils { + // 这是一个创建点 (InitPoint) + public static ThreadPoolExecutor createMyPool() { + return new ThreadPoolExecutor( + 5, + 10, // 直接使用 Integer.MAX_VALUE + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue(100), + new ThreadPoolExecutor.AbortPolicy()); + } +} \ No newline at end of file diff --git a/src/Mytest/SetName_ThreadPool.java b/src/Mytest/SetName_ThreadPool.java new file mode 100644 index 0000000..e0977b9 --- /dev/null +++ b/src/Mytest/SetName_ThreadPool.java @@ -0,0 +1,33 @@ +package Mytest; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +//静态分析时机问题:您的检测器进行的是静态代码分析,它只能分析代码的结构和调用关系 +//无法知道运行时execute()方法内部的setName()调用。 +//不要再线程运行的时候设置 execute +public class SetName_ThreadPool { + public static void main(String[] args) { + + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + 5, + 10, // 直接使用 Integer.MAX_VALUE + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue(100), + new ThreadPoolExecutor.AbortPolicy()); + threadPoolExecutor.execute(() -> { + try { + Thread.sleep(1000); + System.out.println("This is SetNameThreadPool"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + }); + threadPoolExecutor.shutdown(); + } +} diff --git a/src/Mytest/SetName_ThreadPool2.java b/src/Mytest/SetName_ThreadPool2.java new file mode 100644 index 0000000..b719812 --- /dev/null +++ b/src/Mytest/SetName_ThreadPool2.java @@ -0,0 +1,49 @@ +package Mytest; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +// 创建一个自定义的 ThreadFactory 来设置线程名称 +public class SetName_ThreadPool2 { + public static void main(String[] args) { + + // 创建一个自定义的线程工厂,设置线程名称 + ThreadFactory namedThreadFactory = new ThreadFactory() { + private final AtomicInteger threadCount = new AtomicInteger(1); + + @Override + public Thread newThread(Runnable r) { + Thread thread = new Thread(r); + thread.setName("Qyc-thread-" + threadCount.getAndIncrement()); + return thread; + } + }; + + // 使用自定义线程工厂创建线程池 + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + 5, + 10, // 最大线程数 + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue(100), + namedThreadFactory, // 设置线程工厂 + new ThreadPoolExecutor.AbortPolicy() + ); + + // 提交任务 + threadPoolExecutor.execute(() -> { + try { + Thread.currentThread().setName("qyc-thread1"); + Thread.sleep(1000); + System.out.println("This is SetNameThreadPool"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + }); + + threadPoolExecutor.shutdown(); + } +} diff --git a/src/Mytest/Unrefactored_ThreadPool.java b/src/Mytest/Unrefactored_ThreadPool.java new file mode 100644 index 0000000..05242d7 --- /dev/null +++ b/src/Mytest/Unrefactored_ThreadPool.java @@ -0,0 +1,30 @@ +package Mytest; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +//分析问题: +//submitPoint.getParaLocalPossiableTypes()返回的是空的! + +//Soot 的 指针分析(Pointer Analysis) 或者 变量类型传播(VTA)。 是指针分析的问题!; +// 日志显示这个列表是空的,这意味着分析框架没能计算出传递给 submit 方法的参数到底指向哪个具体的类。 +public class Unrefactored_ThreadPool { + public static void main(String[] args) { + ThreadLocal currentUserId = new ThreadLocal<>(); + ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( + 5, + 10, // 直接使用 Integer.MAX_VALUE + 60, + TimeUnit.SECONDS, + new ArrayBlockingQueue(100), + new ThreadPoolExecutor.AbortPolicy()); + + threadPoolExecutor.submit(()->{ + currentUserId.set(1); + System.out.println("The number has been set is "+currentUserId.get()); +// currentUserId.remove(); + }); + + threadPoolExecutor.shutdown(); + } +} diff --git a/src/Mytest/out/Mytest/ILTest_ThreadPool.class b/src/Mytest/out/Mytest/ILTest_ThreadPool.class new file mode 100644 index 0000000..7d83f8c Binary files /dev/null and b/src/Mytest/out/Mytest/ILTest_ThreadPool.class differ diff --git a/src/Mytest/out10/Mytest/SetName_ThreadPool.class b/src/Mytest/out10/Mytest/SetName_ThreadPool.class new file mode 100644 index 0000000..d8632fe Binary files /dev/null and b/src/Mytest/out10/Mytest/SetName_ThreadPool.class differ diff --git a/src/Mytest/out11/Mytest/SetName_ThreadPool2$1.class b/src/Mytest/out11/Mytest/SetName_ThreadPool2$1.class new file mode 100644 index 0000000..850ab10 Binary files /dev/null and b/src/Mytest/out11/Mytest/SetName_ThreadPool2$1.class differ diff --git a/src/Mytest/out11/Mytest/SetName_ThreadPool2.class b/src/Mytest/out11/Mytest/SetName_ThreadPool2.class new file mode 100644 index 0000000..5a83550 Binary files /dev/null and b/src/Mytest/out11/Mytest/SetName_ThreadPool2.class differ diff --git a/src/Mytest/out2/Mytest/ILTest_ThreadPool2.class b/src/Mytest/out2/Mytest/ILTest_ThreadPool2.class new file mode 100644 index 0000000..209d6fb Binary files /dev/null and b/src/Mytest/out2/Mytest/ILTest_ThreadPool2.class differ diff --git a/src/Mytest/out3/Mytest/DPSETest_ThreadPool.class b/src/Mytest/out3/Mytest/DPSETest_ThreadPool.class new file mode 100644 index 0000000..781eb81 Binary files /dev/null and b/src/Mytest/out3/Mytest/DPSETest_ThreadPool.class differ diff --git a/src/Mytest/out4/Mytest/IntMax_ThreadPool.class b/src/Mytest/out4/Mytest/IntMax_ThreadPool.class new file mode 100644 index 0000000..46757b3 Binary files /dev/null and b/src/Mytest/out4/Mytest/IntMax_ThreadPool.class differ diff --git a/src/Mytest/out4_1/Mytest/IntMax_ThreadPool2.class b/src/Mytest/out4_1/Mytest/IntMax_ThreadPool2.class new file mode 100644 index 0000000..3b9d949 Binary files /dev/null and b/src/Mytest/out4_1/Mytest/IntMax_ThreadPool2.class differ diff --git a/src/Mytest/out5/Mytest/Excp_ThreadPool.class b/src/Mytest/out5/Mytest/Excp_ThreadPool.class new file mode 100644 index 0000000..a255d23 Binary files /dev/null and b/src/Mytest/out5/Mytest/Excp_ThreadPool.class differ diff --git a/src/Mytest/out6/Mytest/RepeatedCreate_ThreadPool.class b/src/Mytest/out6/Mytest/RepeatedCreate_ThreadPool.class new file mode 100644 index 0000000..54b2c7b Binary files /dev/null and b/src/Mytest/out6/Mytest/RepeatedCreate_ThreadPool.class differ diff --git a/src/Mytest/out6/Mytest/ThreadPoolUtils.class b/src/Mytest/out6/Mytest/ThreadPoolUtils.class new file mode 100644 index 0000000..2d62732 Binary files /dev/null and b/src/Mytest/out6/Mytest/ThreadPoolUtils.class differ diff --git a/src/Mytest/out7/Mytest/Unrefactored_ThreadPool.class b/src/Mytest/out7/Mytest/Unrefactored_ThreadPool.class new file mode 100644 index 0000000..f762ea1 Binary files /dev/null and b/src/Mytest/out7/Mytest/Unrefactored_ThreadPool.class differ diff --git a/src/Mytest/out8/Mytest/CoreMax_ThreadPool.class b/src/Mytest/out8/Mytest/CoreMax_ThreadPool.class new file mode 100644 index 0000000..019e392 Binary files /dev/null and b/src/Mytest/out8/Mytest/CoreMax_ThreadPool.class differ diff --git a/src/Mytest/out9/Mytest/CoreMax_ThreadPool2.class b/src/Mytest/out9/Mytest/CoreMax_ThreadPool2.class new file mode 100644 index 0000000..8859469 Binary files /dev/null and b/src/Mytest/out9/Mytest/CoreMax_ThreadPool2.class differ diff --git a/src/Mytest/test_coremax.jar b/src/Mytest/test_coremax.jar new file mode 100644 index 0000000..415c23f Binary files /dev/null and b/src/Mytest/test_coremax.jar differ diff --git a/src/Mytest/test_coremax2.jar b/src/Mytest/test_coremax2.jar new file mode 100644 index 0000000..59315a7 Binary files /dev/null and b/src/Mytest/test_coremax2.jar differ diff --git a/src/Mytest/test_dpse.jar b/src/Mytest/test_dpse.jar new file mode 100644 index 0000000..dc59ef8 Binary files /dev/null and b/src/Mytest/test_dpse.jar differ diff --git a/src/Mytest/test_excp.jar b/src/Mytest/test_excp.jar new file mode 100644 index 0000000..fe08125 Binary files /dev/null and b/src/Mytest/test_excp.jar differ diff --git a/src/Mytest/test_il.jar b/src/Mytest/test_il.jar new file mode 100644 index 0000000..b4f5188 Binary files /dev/null and b/src/Mytest/test_il.jar differ diff --git a/src/Mytest/test_intmax.jar b/src/Mytest/test_intmax.jar new file mode 100644 index 0000000..77fa8ff Binary files /dev/null and b/src/Mytest/test_intmax.jar differ diff --git a/src/Mytest/test_intmax2.jar b/src/Mytest/test_intmax2.jar new file mode 100644 index 0000000..cebd885 Binary files /dev/null and b/src/Mytest/test_intmax2.jar differ diff --git a/src/Mytest/test_repeated.jar b/src/Mytest/test_repeated.jar new file mode 100644 index 0000000..508e916 Binary files /dev/null and b/src/Mytest/test_repeated.jar differ diff --git a/src/Mytest/test_setname.jar b/src/Mytest/test_setname.jar new file mode 100644 index 0000000..77f6c79 Binary files /dev/null and b/src/Mytest/test_setname.jar differ diff --git a/src/Mytest/test_setname2.jar b/src/Mytest/test_setname2.jar new file mode 100644 index 0000000..df829c1 Binary files /dev/null and b/src/Mytest/test_setname2.jar differ diff --git a/src/Mytest/test_unrefactored.jar b/src/Mytest/test_unrefactored.jar new file mode 100644 index 0000000..8e393d1 Binary files /dev/null and b/src/Mytest/test_unrefactored.jar differ diff --git a/src/ac/component/PointCollectorAsyncTask.java b/src/ac/component/PointCollectorAsyncTask.java index ca943cd..6d6d58a 100644 --- a/src/ac/component/PointCollectorAsyncTask.java +++ b/src/ac/component/PointCollectorAsyncTask.java @@ -122,6 +122,7 @@ protected OneParaValueKeyPoint newSetCoreThreadSizePoint(SootMethod method, Stmt return null; } + @Override protected boolean isSetMaxThreadSizePoint(Stmt stmt) { return false; @@ -132,4 +133,5 @@ protected boolean isSetCoreThreadSizePoint(Stmt stmt) { return false; } + } diff --git a/src/ac/component/PointCollectorExecutor.java b/src/ac/component/PointCollectorExecutor.java index 34b6042..42dff83 100644 --- a/src/ac/component/PointCollectorExecutor.java +++ b/src/ac/component/PointCollectorExecutor.java @@ -13,85 +13,131 @@ import soot.jimple.DefinitionStmt; import soot.jimple.InvokeExpr; import soot.jimple.Stmt; - +//线程池执行器专用关键点收集器 +//专门处理 Java Executor 框架相关的异步编程模式,包括线程池的创建、配置、任务提交和生命周期管理。 public class PointCollectorExecutor extends PointCollector { - + // 识别线程池创建,完整覆盖Java线程池的所有创建方式 @Override - protected KeyPoint newSetUncaughtExceptionHandlerPoint(SootMethod method, Stmt stmt) { - return KeyPoint.newPoint(method, stmt); + protected InitPoint newInitPoint(SootMethod method, Stmt stmt) { + return InitPoint.newInitPoint(method, stmt); } - + // 识别线程池执行器的初始化点,支持两种创建方式。 +// 不匹配的情况包括: +// 非方法调用语句 +// 非构造函数且非静态方法 +// 静态方法调用但没有赋值给Executor类型变量 +// 构造函数但不是Executor家族 +// 赋值语句但左值类型不是Executor @Override - protected OneParaKeyPoint newRejectedExecutionHandlerPoint(SootMethod method, Stmt stmt) { - int index = getParaIndexByType(ExecutorSig.CLASS_RejectedExecutionHandler, stmt); - return OneParaKeyPoint.newOneParaKeyPoint(method, stmt, index); + protected boolean isInitPoint(Stmt stmt) { +// invokeExpr: 获取语句中的方法调用表达式,invokedMethod: 提取被调用的具体方法 + InvokeExpr invokeExpr = stmt.getInvokeExpr(); + SootMethod invokedMethod = invokeExpr.getMethod(); +// 检查是否是构造函数;检查声明类是否继承自Executor + if (invokedMethod.isConstructor() && AsyncInherit.isInheritedFromExecutor(invokedMethod.getDeclaringClass())) { + return true; + } +// 检查是否是静态方法、检查是否是赋值语句、检查赋值左值的类型 + else if (invokedMethod.isStatic()) + { + if (stmt instanceof DefinitionStmt) { + DefinitionStmt definitionStmt = (DefinitionStmt) stmt; + if (AsyncInherit.isInheritedFromExecutor(definitionStmt.getLeftOp().getType())) { + return true; + } + } + } + return false; } + // 识别任务提交操作,检测线程池的任务提交模式 +// 覆盖所有提交方式: +// submit(Callable) +// submit(Runnable) +// submit(Runnable, T) +// execute(Runnable) @Override - protected OneParaKeyPoint newSetFactoryPoint(SootMethod method, Stmt stmt) { - int index = getParaIndexByType(ExecutorSig.CLASS_ThreadFactory, stmt); - return OneParaKeyPoint.newOneParaKeyPoint(method, stmt, index); + protected OneParaKeyPoint newSubmitPoint(SootMethod method, Stmt stmt) { + return OneParaKeyPoint.newOneParaKeyPoint(method, stmt, 0); } - @Override - protected KeyPoint newIsTerminatedPoint(SootMethod method, Stmt stmt) { - return KeyPoint.newPoint(method, stmt); + protected boolean isSubmitPoint(Stmt stmt) { + String subSig = stmt.getInvokeExpr().getMethod().getSubSignature(); + return ExecutorSig.METHOD_SUBSIG_SUBMIT_CALLABLE.equals(subSig) + || ExecutorSig.METHOD_SUBSIG_SUBMIT_RUNNABLE.equals(subSig) + || ExecutorSig.METHOD_SUBSIG_SUBMIT_RUNNABLE_T.equals(subSig) + || ExecutorSig.METHOD_SUBSIG_EXECUTE.equals(subSig); } - + // 识别线程池关闭操作 +// 优雅关闭:shutdown() - 等待任务完成 +// 强制关闭:shutdownNow() - 立即终止 @Override protected KeyPoint newShutdownPoint(SootMethod method, Stmt stmt) { return KeyPoint.newPoint(method, stmt); } - @Override protected KeyPoint newShutdownNowPoint(SootMethod method, Stmt stmt) { return KeyPoint.newPoint(method, stmt); } - @Override - protected OneParaKeyPoint newSubmitPoint(SootMethod method, Stmt stmt) { - return OneParaKeyPoint.newOneParaKeyPoint(method, stmt, 0); + protected boolean isShutdownPoint(Stmt stmt) { + return ExecutorSig.METHOD_SUBSIG_SHUT_DOWN.equals(stmt.getInvokeExpr().getMethod().getSubSignature()); } - @Override - protected InitPoint newInitPoint(SootMethod method, Stmt stmt) { - return InitPoint.newInitPoint(method, stmt); + protected boolean isShutdownNowPoint(Stmt stmt) { + return ExecutorSig.METHOD_SUBSIG_SHUT_DOWN_NOW.equals(stmt.getInvokeExpr().getMethod().getSubSignature()); } + // 识别线程池启动 +// 在Executor中,任务提交即视为启动 @Override protected KeyPoint newStartPoint(SootMethod method, Stmt stmt) { return KeyPoint.newPoint(method, stmt); } - @Override - protected OneParaValueKeyPoint newSetMaxThreadSizePoint(SootMethod method, Stmt stmt) { - int index = 0; - if (stmt.getInvokeExpr().getArgCount() > 1 && stmt.getInvokeExpr().getArg(1).getType() instanceof IntType) { - index = 1; - } - return OneParaValueKeyPoint.newOneParaValueKeyPoint(method, stmt, index); + protected boolean isStartPoint(Stmt stmt) { + return isSubmitPoint(stmt); } + + + // 线程池状态检查方法,识别线程池状态检查调用 +// 匹配:executor.isShutdown() 和 executor.isTerminated() @Override - protected OneParaValueKeyPoint newSetCoreThreadSizePoint(SootMethod method, Stmt stmt) { - int index = 0; - return OneParaValueKeyPoint.newOneParaValueKeyPoint(method, stmt, index); + protected KeyPoint newIsTerminatedPoint(SootMethod method, Stmt stmt) { + return KeyPoint.newPoint(method, stmt); } +// @Override +// protected boolean isIsTerminatedPoint(Stmt stmt) { +// return ExecutorSig.METHOD_SUBSIG_IS_SHUT_DOWN.equals(stmt.getInvokeExpr().getMethod().getSubSignature()) +// || ExecutorSig.METHOD_SUBSIG_IS_TERMINATED.equals(stmt.getInvokeExpr().getMethod().getSubSignature()); +// } @Override - protected boolean isSetUncaughtExceptionHandlerPoint(Stmt stmt) { - return (isInitPoint(stmt) && stmt.getInvokeExpr().getMethod().getParameterTypes().toString() - .contains(ThreadSig.METHOD_SIG_setUncaughtExceptionHandler)) - || ThreadSig.METHOD_SIG_setUncaughtExceptionHandler - .equals(stmt.getInvokeExpr().getMethod().getSignature()); - } + protected boolean isIsTerminatedPoint(Stmt stmt) { + SootMethod method = stmt.getInvokeExpr().getMethod(); + String methodName = method.getName(); + + // [修复 3] 使用方法名匹配,而不是严格的子签名匹配 + // 只要方法名叫 isShutdown 且没有参数,就认为是目标 + if (methodName.equals("isShutdown") || methodName.equals("isTerminated")) { + if (method.getParameterCount() == 0) { + // 打印日志验证是否被这里捕获 + System.out.println("DEBUG: 捕获到终止检查点: " + methodName); + return true; + } + } + // 保留你原有的逻辑作为备份 + return ExecutorSig.METHOD_SUBSIG_IS_SHUT_DOWN.equals(method.getSubSignature()) + || ExecutorSig.METHOD_SUBSIG_IS_TERMINATED.equals(method.getSubSignature()); + } + // 识别线程工厂设置,检测线程创建和命名的自定义逻辑 +// 基于 ThreadFactory 类型识别参数 @Override - protected boolean isRejectedExecutionHandlerPoint(Stmt stmt) { - return (isInitPoint(stmt) && stmt.getInvokeExpr().getMethod().getParameterTypes().toString() - .contains(ExecutorSig.METHOD_SIG_setRejectedExecutionHandler)) - || ExecutorSig.METHOD_SIG_setRejectedExecutionHandler - .equals(stmt.getInvokeExpr().getMethod().getSignature()); + protected OneParaKeyPoint newSetFactoryPoint(SootMethod method, Stmt stmt) { + int index = getParaIndexByType(ExecutorSig.CLASS_ThreadFactory, stmt); + return OneParaKeyPoint.newOneParaKeyPoint(method, stmt, index); } @Override @@ -101,59 +147,54 @@ protected boolean isSetFactoryPoint(Stmt stmt) { || ExecutorSig.METHOD_SIG_setThreadFactory.equals(stmt.getInvokeExpr().getMethod().getSignature()); } + // 识别拒绝策略处理器设置,检测线程池的任务拒绝处理策略 +// 通过类型找到 RejectedExecutionHandler 参数位置 @Override - protected boolean isIsTerminatedPoint(Stmt stmt) { - return ExecutorSig.METHOD_SUBSIG_IS_SHUT_DOWN.equals(stmt.getInvokeExpr().getMethod().getSubSignature()) - || ExecutorSig.METHOD_SUBSIG_IS_TERMINATED.equals(stmt.getInvokeExpr().getMethod().getSubSignature()); + protected OneParaKeyPoint newRejectedExecutionHandlerPoint(SootMethod method, Stmt stmt) { + int index = getParaIndexByType(ExecutorSig.CLASS_RejectedExecutionHandler, stmt); + return OneParaKeyPoint.newOneParaKeyPoint(method, stmt, index); } - @Override - protected boolean isShutdownPoint(Stmt stmt) { - return ExecutorSig.METHOD_SUBSIG_SHUT_DOWN.equals(stmt.getInvokeExpr().getMethod().getSubSignature()); + protected boolean isRejectedExecutionHandlerPoint(Stmt stmt) { + return (isInitPoint(stmt) && stmt.getInvokeExpr().getMethod().getParameterTypes().toString() + .contains(ExecutorSig.METHOD_SIG_setRejectedExecutionHandler)) + || ExecutorSig.METHOD_SIG_setRejectedExecutionHandler + .equals(stmt.getInvokeExpr().getMethod().getSignature()); } + + + // 异常处理相关方法,识别线程池异常处理器设置 @Override - protected boolean isShutdownNowPoint(Stmt stmt) { - return ExecutorSig.METHOD_SUBSIG_SHUT_DOWN_NOW.equals(stmt.getInvokeExpr().getMethod().getSubSignature()); + protected KeyPoint newSetUncaughtExceptionHandlerPoint(SootMethod method, Stmt stmt) { + return KeyPoint.newPoint(method, stmt); } - @Override - protected boolean isSubmitPoint(Stmt stmt) { - String subSig = stmt.getInvokeExpr().getMethod().getSubSignature(); - return ExecutorSig.METHOD_SUBSIG_SUBMIT_CALLABLE.equals(subSig) - || ExecutorSig.METHOD_SUBSIG_SUBMIT_RUNNABLE.equals(subSig) - || ExecutorSig.METHOD_SUBSIG_SUBMIT_RUNNABLE_T.equals(subSig) - || ExecutorSig.METHOD_SUBSIG_EXECUTE.equals(subSig); + protected boolean isSetUncaughtExceptionHandlerPoint(Stmt stmt) { + return (isInitPoint(stmt) && stmt.getInvokeExpr().getMethod().getParameterTypes().toString() + .contains(ThreadSig.METHOD_SIG_setUncaughtExceptionHandler)) + || ThreadSig.METHOD_SIG_setUncaughtExceptionHandler + .equals(stmt.getInvokeExpr().getMethod().getSignature()); } + + +// 识别最大线程数配置 @Override - protected boolean isInitPoint(Stmt stmt) { - InvokeExpr invokeExpr = stmt.getInvokeExpr(); - SootMethod invokedMethod = invokeExpr.getMethod(); - if (invokedMethod.isConstructor() && AsyncInherit.isInheritedFromExecutor(invokedMethod.getDeclaringClass())) { - return true; - } else if (invokedMethod.isStatic()) { - if (stmt instanceof DefinitionStmt) { - DefinitionStmt definitionStmt = (DefinitionStmt) stmt; - if (AsyncInherit.isInheritedFromExecutor(definitionStmt.getLeftOp().getType())) { - return true; - } - } + protected OneParaValueKeyPoint newSetMaxThreadSizePoint(SootMethod method, Stmt stmt) { + int index = 0; + if (stmt.getInvokeExpr().getArgCount() > 1 && stmt.getInvokeExpr().getArg(1).getType() instanceof IntType) { + index = 1; } - return false; + return OneParaValueKeyPoint.newOneParaValueKeyPoint(method, stmt, index); } - +// 识别核心线程数配置,显示方式为setCorePoolSize(coreSize) @Override - protected boolean isStartPoint(Stmt stmt) { - return isSubmitPoint(stmt); + protected OneParaValueKeyPoint newSetCoreThreadSizePoint(SootMethod method, Stmt stmt) { + int index = 0; + return OneParaValueKeyPoint.newOneParaValueKeyPoint(method, stmt, index); } - @Override - protected boolean isSetMaxThreadSizePoint(Stmt stmt) { - return ExecutorSig.METHOD_SIG_setMaximumPoolSize.equals(stmt.getInvokeExpr().getMethod().getSignature()) - || (!ExecutorSig.METHOD_SIG_setCorePoolSize.equals(stmt.getInvokeExpr().getMethod().getSignature()) - && isSetCoreThreadSizePoint(stmt)); - } @Override protected boolean isSetCoreThreadSizePoint(Stmt stmt) { @@ -170,4 +211,16 @@ protected boolean isSetCoreThreadSizePoint(Stmt stmt) { return false; } + + + @Override + protected boolean isSetMaxThreadSizePoint(Stmt stmt) { + return ExecutorSig.METHOD_SIG_setMaximumPoolSize.equals(stmt.getInvokeExpr().getMethod().getSignature()) + || (!ExecutorSig.METHOD_SIG_setCorePoolSize.equals(stmt.getInvokeExpr().getMethod().getSignature()) + && isSetCoreThreadSizePoint(stmt)); + } + + + + } diff --git a/src/ac/component/PointCollectorThread.java b/src/ac/component/PointCollectorThread.java index 26d5504..d139066 100644 --- a/src/ac/component/PointCollectorThread.java +++ b/src/ac/component/PointCollectorThread.java @@ -11,12 +11,21 @@ import soot.SootMethod; import soot.jimple.Stmt; +//Thread和Runnable专用关键点收集器 +//专门处理直接使用 Thread 和 Runnable 的异步编程模式,将底层的 Thread 操作映射到统一的关键点体系中。 public class PointCollectorThread extends PointCollector { +// 识别线程未捕获异常处理器的设置 +// 检测线程异常处理机制,避免未处理异常导致程序崩溃 @Override protected KeyPoint newSetUncaughtExceptionHandlerPoint(SootMethod method, Stmt stmt) { return KeyPoint.newPoint(method, stmt); } + @Override + protected boolean isSetUncaughtExceptionHandlerPoint(Stmt stmt) { + return ThreadSig.METHOD_SIG_setUncaughtExceptionHandler + .equals(stmt.getInvokeExpr().getMethod().getSignature()); + } @Override protected OneParaKeyPoint newRejectedExecutionHandlerPoint(SootMethod method, Stmt stmt) { @@ -30,22 +39,37 @@ protected OneParaKeyPoint newSetFactoryPoint(SootMethod method, Stmt stmt) { return null; } +// 线程状态检查方法,识别线程中断状态检查调用 @Override protected KeyPoint newIsTerminatedPoint(SootMethod method, Stmt stmt) { return KeyPoint.newPoint(method, stmt); } + @Override + protected boolean isIsTerminatedPoint(Stmt stmt) { + return ThreadSig.METHOD_SUBSIG_IS_INTERRUPTED.equals(stmt.getInvokeExpr().getMethod().getSubSignature()); + } + @Override protected KeyPoint newShutdownPoint(SootMethod method, Stmt stmt) { return null; } +// 识别线程关闭shutdownnow操作,检测线程的强制终止操作 @Override protected KeyPoint newShutdownNowPoint(SootMethod method, Stmt stmt) { // TODO Auto-generated method stub return KeyPoint.newPoint(method, stmt); } + @Override + protected boolean isShutdownNowPoint(Stmt stmt) { + return ThreadSig.METHOD_SUBSIG_INTERRUPT.equals(stmt.getInvokeExpr().getMethod().getSubSignature()) + || ThreadSig.METHOD_SUBSIG_INTERRUPT_SAFELY.equals(stmt.getInvokeExpr().getMethod().getSubSignature()); + } + +// 任务提交点处理——————创建线程提交关键点(特殊设计) +// 将Thread构造函数统一为"提交点"概念 @Override protected OneParaKeyPoint newSubmitPoint(SootMethod method, Stmt stmt) { int index = getParaIndexByType(ThreadSig.CLASS_RUNNABLE, stmt); @@ -62,15 +86,16 @@ protected OneParaKeyPoint newSubmitPoint(SootMethod method, Stmt stmt) { return point; } +// 线程初始化方法,识别线程对象创建 +// 通过 AsyncInherit.isInheritedFromThread() 确保只处理Thread家族 @Override protected InitPoint newInitPoint(SootMethod method, Stmt stmt) { return InitPoint.newInitPoint(method, stmt); } - @Override - protected boolean isSetUncaughtExceptionHandlerPoint(Stmt stmt) { - return ThreadSig.METHOD_SIG_setUncaughtExceptionHandler - .equals(stmt.getInvokeExpr().getMethod().getSignature()); + protected boolean isInitPoint(Stmt stmt) { + SootMethod invokedMethod = stmt.getInvokeExpr().getMethod(); + return invokedMethod .isConstructor() && AsyncInherit.isInheritedFromThread(invokedMethod.getDeclaringClass()); } @Override @@ -83,43 +108,31 @@ protected boolean isSetFactoryPoint(Stmt stmt) { return false; } - @Override - protected boolean isIsTerminatedPoint(Stmt stmt) { - return ThreadSig.METHOD_SUBSIG_IS_INTERRUPTED.equals(stmt.getInvokeExpr().getMethod().getSubSignature()); - } + @Override protected boolean isShutdownPoint(Stmt stmt) { return false; } - @Override - protected boolean isShutdownNowPoint(Stmt stmt) { - return ThreadSig.METHOD_SUBSIG_INTERRUPT.equals(stmt.getInvokeExpr().getMethod().getSubSignature()) - || ThreadSig.METHOD_SUBSIG_INTERRUPT_SAFELY.equals(stmt.getInvokeExpr().getMethod().getSubSignature()); - } - @Override protected boolean isSubmitPoint(Stmt stmt) { return isInitPoint(stmt) ; } - @Override - protected boolean isInitPoint(Stmt stmt) { - SootMethod invokedMethod = stmt.getInvokeExpr().getMethod(); - return invokedMethod .isConstructor() && AsyncInherit.isInheritedFromThread(invokedMethod.getDeclaringClass()); - } - + +// 识别线程启动调用 +// 匹配thread.start() @Override protected KeyPoint newStartPoint(SootMethod method, Stmt stmt) { return KeyPoint.newPoint(method, stmt); } - @Override protected boolean isStartPoint(Stmt stmt) { return ThreadSig.METHOD_SIG_START.equals(stmt.getInvokeExpr().getMethod().getSignature()); } + @Override protected OneParaValueKeyPoint newSetMaxThreadSizePoint(SootMethod method, Stmt stmt) { return null; @@ -130,6 +143,8 @@ protected OneParaValueKeyPoint newSetCoreThreadSizePoint(SootMethod method, Stmt return null; } + + @Override protected boolean isSetMaxThreadSizePoint(Stmt stmt) { return false; diff --git a/src/ac/constant/AsyncTaskSig.java b/src/ac/constant/AsyncTaskSig.java index f2883df..b9bfc08 100644 --- a/src/ac/constant/AsyncTaskSig.java +++ b/src/ac/constant/AsyncTaskSig.java @@ -1,5 +1,9 @@ package ac.constant; + +//所有 AsyncTask 相关签名集中在一个地方 +//在代码中直接使用有意义的常量名 + public class AsyncTaskSig { diff --git a/src/ac/constant/ExecutorSig.java b/src/ac/constant/ExecutorSig.java index 2ec4023..995ed13 100644 --- a/src/ac/constant/ExecutorSig.java +++ b/src/ac/constant/ExecutorSig.java @@ -10,9 +10,16 @@ import soot.jimple.InvokeExpr; import soot.jimple.NewExpr; import soot.jimple.Stmt; +//线程池执行器签名常量定义类,专门用于在 Android +// 异步组件检测工具中管理线程池相关的类名、方法签名和线程池对象识别逻辑。 +//getNewThreadPoolValue(Stmt stmt) 方法 +//这个方法专门用于识别和提取新创建的线程池对象 + +//这个类使得检测工具能够精确识别和分析 Java 线程池的使用模式, +//与 AsyncTask 检测相结合,提供全面的 Android 异步编程 misuse 检测能力。 public class ExecutorSig { - + // for ExecutorService public final static String METHOD_SUBSIG_SHUT_DOWN_NOW = "void shutdownNow()"; public final static String METHOD_SUBSIG_SHUT_DOWN = "void shutdown()"; @@ -27,7 +34,9 @@ public class ExecutorSig { public final static String METHOD_SIG_setRejectedExecutionHandler = ""; public final static String METHOD_SIG_setCorePoolSize = ""; public final static String METHOD_SIG_setMaximumPoolSize = ""; - + public final static String METHOD_SIG_setMaxQueueSizePoints = ""; + + public final static String METHOD_SIG_setThreadFactory = ""; public static final String METHOD_SUBSIG_awaitTermination = "boolean awaitTermination(long,java.util.concurrent.TimeUnit)"; diff --git a/src/ac/constant/Signature.java b/src/ac/constant/Signature.java index 3e0a2d0..0dc5b64 100644 --- a/src/ac/constant/Signature.java +++ b/src/ac/constant/Signature.java @@ -4,10 +4,10 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; +//Signature 类是一个任务启动方法签名集合类 +//专门用于在 Android 异步组件检测工具中定义和集中管理所有能够启动异步任务的方法签名。 public class Signature { - - public static final String[] startMethods = { ExecutorSig.METHOD_SUBSIG_SUBMIT_CALLABLE, ExecutorSig.METHOD_SUBSIG_SUBMIT_RUNNABLE, ExecutorSig.METHOD_SUBSIG_SUBMIT_RUNNABLE_T, ExecutorSig.METHOD_SUBSIG_EXECUTE }; diff --git a/src/ac/constant/ThreadSig.java b/src/ac/constant/ThreadSig.java index 05b7111..3f673f4 100644 --- a/src/ac/constant/ThreadSig.java +++ b/src/ac/constant/ThreadSig.java @@ -1,10 +1,13 @@ package ac.constant; + +// ThreadSig 类是一个线程相关签名常量定义类 +// 专门用于在 Android 异步组件检测工具中管理 Java 线程相关的类名和方法签名。 public class ThreadSig { public static final String METHOD_SUBSIG_RUN = "void run()"; - public final static String METHOD_JOIN_PREFIX = " " + targetFolder); + System.err.println("请回到 MainEntry.java 代码中修改 'targetFolder' 变量!"); + return; + } + + System.out.println("正在分析文件夹: " + targetFolder); + + ArrayList tools = new ArrayList<>(); + + // 启动分析逻辑 + StatisticsTool tool = new StatisticsTool(rootFile); + tool.start(); + tools.add(tool); + + // 构建 CSV 格式输出 + StringBuffer stringBuffer = new StringBuffer(); + + // 定义每一列的格式:%-30s 表示左对齐占30格,%10s 表示右对齐占10格 + // Project名通常比较长,给多一点空间;数字给少一点 + String rowFormat = "%-35s %-6s %-6s %-6s %-6s %-6s %-6s %-6s %-6s %-6s %-6s %-6s %-12s %-12s %-12s\n"; + for (StatisticsTool t : tools) { + // 1. 打印表头 + // 注意:表头中间用空格分开,不再用分号 + stringBuffer.append(String.format(rowFormat, + "Project", "HTR", "INR", "NTT", "IL", + "NT", "ENC", "UNT", "DPSE", "RCTP", "UBNT", "UTL", + "SootTime", "LeoTime", "TotalTime" + )); + + // 添加一条分隔线,看起来更像表格 + stringBuffer.append("-----------------------------------------------------------------------------------------------------------------------------------------------------\n"); + + for (Result result : t.getResultSet()) { + // 记得先计算! + result.calculateCounts(); + + // 2. 打印每一行数据 + // 将所有数据填入格式中 + stringBuffer.append(String.format(rowFormat, + result.appName, + String.valueOf(result.HTRCount), + String.valueOf(result.INRCount), + String.valueOf(result.NTTCount), + String.valueOf(result.ILCount), + String.valueOf(result.NTCount), + String.valueOf(result.ENCCount), + String.valueOf(result.UNTCount), + String.valueOf(result.DPSECount), + String.valueOf(result.RCTPCount), + String.valueOf(result.UBNTCount), + String.valueOf(result.UTLCount), + String.valueOf(result.sootTime), + String.valueOf(result.leopardTime), + String.valueOf(result.sootTime + result.leopardTime) + )); + } + stringBuffer.append("\r\n"); + } + + // 保存文件 + initFolder(STATISTICS_RESULT_FOLDER); + String fullOutputPath = STATISTICS_RESULT_FOLDER + File.separator + STATISTICS_RESULT_FILE_NAME; + save2File(fullOutputPath, stringBuffer.toString()); + + System.out.println("分析完成!"); + System.out.println("统计结果已保存到项目根目录: " + fullOutputPath); + // 如果是在 IDE 中运行,通常可以在左侧项目栏刷新一下看到生成的文件 + } + + private static void initFolder(String folder) { + File file = new File(folder); + if (!file.exists()) { + file.mkdirs(); + } + } + + private static void save2File(String filePath, String content) { + File file = new File(filePath); + BufferedWriter out = null; + try { + if (!file.exists()) { + file.createNewFile(); + } + // 这里改为了 false,表示每次运行覆盖旧结果,方便调试。如果想保留历史记录改成 true。 + out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, false))); + out.write(content); + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (out != null) { + try { + out.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } +} \ No newline at end of file diff --git a/src/ac/count/Result.java b/src/ac/count/Result.java new file mode 100644 index 0000000..7ef48f3 --- /dev/null +++ b/src/ac/count/Result.java @@ -0,0 +1,123 @@ +package ac.count; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +public class Result { + + // ... ErrorInfo 内部类保持不变,这里省略 ... + static class ErrorInfo { + // (保持你原来的代码内容不变) + String asyncTaskClass = null; + ArrayList errorHTRSumWithExcutePath = new ArrayList<>(); + ArrayList errorNTTSumWithExcutePath = new ArrayList<>(); + ArrayList errorINRSumWithExcutePath = new ArrayList<>(); + ArrayList errorILSumWithExcutePath = new ArrayList<>(); + ArrayList errorNTSumWithExcutePath = new ArrayList<>(); + ArrayList errorENCSumWithExcutePath = new ArrayList<>(); + ArrayList errorUNTSumWithExcutePath = new ArrayList<>(); + ArrayList errorDPSESumWithExcutePath = new ArrayList<>(); + ArrayList errorRCTPSumWithExcutePath = new ArrayList<>(); + ArrayList errorUBNTSumWithExcutePath = new ArrayList<>(); + ArrayList errorUTLSumWithExcutePath = new ArrayList<>(); + Set errorHTRSumWithoutExcutePath = new HashSet<>(); + Set errorNTTSumWithoutExcutePath = new HashSet<>(); + Set errorINRSumWithoutExcutePath = new HashSet<>(); + Set errorILSumWithoutExcutePath = new HashSet<>(); + Set errorNTSumWithoutExcutePath = new HashSet<>(); + Set errorENCSumWithoutExcutePath = new HashSet<>(); + Set errorUNTSumWithoutExcutePath = new HashSet<>(); + Set errorDPSESumWithoutExcutePath = new HashSet<>(); + Set errorRCTPSumWithoutExcutePath = new HashSet<>(); + Set errorUBNTSumWithoutExcutePath = new HashSet<>(); + Set errorUTLSumWithoutExcutePath = new HashSet<>(); + + // ... addError 方法保持不变 ... + void addHTRError(String key) { errorHTRSumWithExcutePath.add(key); errorHTRSumWithoutExcutePath.add(key); } + void addNTTError(String key) { errorNTTSumWithExcutePath.add(key); errorNTTSumWithoutExcutePath.add(key); } + void addINRError(String key) { errorINRSumWithExcutePath.add(key); errorINRSumWithoutExcutePath.add(key); } + void addILError(String key) { errorILSumWithExcutePath.add(key); errorILSumWithoutExcutePath.add(key); } + void addNTError(String key) { errorNTSumWithExcutePath.add(key); errorNTSumWithoutExcutePath.add(key); } + void addENCError(String key) { errorENCSumWithExcutePath.add(key); errorENCSumWithoutExcutePath.add(key); } + void addUNTError(String key) { errorUNTSumWithExcutePath.add(key); errorUNTSumWithoutExcutePath.add(key); } + void addDPSEError(String key) { errorDPSESumWithExcutePath.add(key); errorDPSESumWithoutExcutePath.add(key); } + void addRCTPError(String key) { errorRCTPSumWithExcutePath.add(key); errorRCTPSumWithoutExcutePath.add(key); } + void addUBNTError(String key) { errorUBNTSumWithExcutePath.add(key); errorUBNTSumWithoutExcutePath.add(key); } + void addUTLError(String key) { errorUTLSumWithExcutePath.add(key); errorUTLSumWithoutExcutePath.add(key); } + } + // ... ErrorInfo 结束 ... + + String appName = null; + ArrayList errorSum = new ArrayList<>(); + ArrayList rightSum = new ArrayList<>(); + HashMap asyncClass = new HashMap<>(); + HashSet rightInstanceHashSet = new HashSet<>(); + HashSet errorHashSet = new HashSet<>(); + int sootTime = 0; + int leopardTime = 0; + + // 计数器 + int HTRCount = 0; int NTTCount = 0; int INRCount = 0; int ILCount = 0; + int NTCount = 0; int ENCCount = 0; int UNTCount = 0; int DPSECount = 0; + int RCTPCount = 0; int UBNTCount = 0; int UTLCount = 0; + + Result(String apkName) { appName = apkName; } + + // ... 省略中间的 addRight, addError, setSootTime 等 setter/getter,保持不变 ... + public void addRight(String key) { rightSum.add(key); rightInstanceHashSet.add(key); } + public void addError(String key) { errorSum.add(key); errorHashSet.add(key); } + public void setSootTime(int sootTime) { this.sootTime = sootTime; } + public void setLeopardTime(int leopardTime) { this.leopardTime = leopardTime; } + public void addINR(String className, String key) { getErrorInfo(className).addINRError(key); } + public void addHTR(String className, String key) { getErrorInfo(className).addHTRError(key); } + public void addIL(String className, String key) { getErrorInfo(className).addILError(key); } + public void addNTT(String className, String key) { getErrorInfo(className).addNTTError(key); } + public void addNT(String className, String key) { getErrorInfo(className).addNTError(key); } + public void addENC(String className, String key) { getErrorInfo(className).addENCError(key); } + public void addUNT(String className, String key) { getErrorInfo(className).addUNTError(key); } + public void addDPSE(String className, String key) { getErrorInfo(className).addDPSEError(key); } + public void addRCTP(String className, String key) { getErrorInfo(className).addRCTPError(key); } + public void addUBNT(String className, String key) { getErrorInfo(className).addUBNTError(key); } + public void addUTL(String className, String key) { getErrorInfo(className).addUTLError(key); } + + public ErrorInfo getErrorInfo(String className) { + if (asyncClass.containsKey(className)) { return asyncClass.get(className); } + ErrorInfo errorInfo = new ErrorInfo(); + errorInfo.asyncTaskClass = className; + asyncClass.put(className, errorInfo); + return errorInfo; + } + + // 【修正核心】添加这个新方法,显式计算所有统计数据 + public void calculateCounts() { + // 重置 + HTRCount = 0; NTTCount = 0; INRCount = 0; ILCount = 0; + NTCount = 0; ENCCount = 0; UNTCount = 0; DPSECount = 0; + RCTPCount = 0; UBNTCount = 0; UTLCount = 0; + + for (String key : asyncClass.keySet()) { + ErrorInfo errorInfo = asyncClass.get(key); + + HTRCount += errorInfo.errorHTRSumWithoutExcutePath.size(); + NTTCount += errorInfo.errorNTTSumWithoutExcutePath.size(); + INRCount += errorInfo.errorINRSumWithoutExcutePath.size(); + ILCount += errorInfo.errorILSumWithoutExcutePath.size(); + + NTCount += errorInfo.errorNTSumWithoutExcutePath.size(); + ENCCount += errorInfo.errorENCSumWithoutExcutePath.size(); + UNTCount += errorInfo.errorUNTSumWithoutExcutePath.size(); + DPSECount += errorInfo.errorDPSESumWithoutExcutePath.size(); + RCTPCount += errorInfo.errorRCTPSumWithoutExcutePath.size(); + UBNTCount += errorInfo.errorUBNTSumWithoutExcutePath.size(); + UTLCount += errorInfo.errorUTLSumWithoutExcutePath.size(); + } + } + + @Override + public String toString() { + calculateCounts(); // 为了兼容旧代码,这里调用一下 + return ""; + } +} \ No newline at end of file diff --git a/src/ac/count/StatisticsTool.java b/src/ac/count/StatisticsTool.java new file mode 100644 index 0000000..c0d0a75 --- /dev/null +++ b/src/ac/count/StatisticsTool.java @@ -0,0 +1,243 @@ +package ac.count; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class StatisticsTool { + + // 内部类保持不变 + public static class DataSetResult { + int HTRCount = 0; int NTTCount = 0; int INRCount = 0; int ILCount = 0; + int NTCount = 0; int ENCCount = 0; int UNTCount = 0; int DPSECount = 0; + int RCTPCount = 0; int UBNTCount = 0; int UTLCount = 0; + int error = 0; int right = 0; + } + + private static final String PATH = File.separator + "path" + File.separator; + private static final String ERROR = File.separator + "error" + File.separator; + + // --- 文件名定义 (与你上传的截图一致) --- + private String ERROR_DICT_HTR = "ExecuteService-HTR-sum.txt"; + private String ERROR_DICT_INR = "ExecuteService-INR-sum.txt"; + private String ERROR_DICT_NTT = "ExecuteService-NTT-sum.txt"; + private String ERROR_DICT_IL = "ExecuteService-IL-sum.txt"; + private String ERROR_DICT_NT = "ExecuteService-NT-sum.txt"; + private String ERROR_DICT_ENC = "ExecuteService-NonExceptionHandler-sum.txt"; + private String ERROR_DICT_UNT = "ExecuteService-UNT-sum.txt"; + private String ERROR_DICT_DPSE = "ExecuteService-CallerRuns-sum.txt"; + private String ERROR_DICT_RCTP = "ExecuteService-RCTP-sum.txt"; + private String ERROR_DICT_UBNT = "ExecuteService-UBNT-sum.txt"; + private String ERROR_DICT_UTL = "ExecuteService-UTL-sum.txt"; + + private String ERROR_SUM = "error-async.txt"; + private String RIGHT_SUM = "right-async.txt"; + private String TIME_FILE_NAME = "log.txt"; + + private File mDict = null; + private String currentApk = null; + private Set mSet = new HashSet(); + + // 计数器 + private int HTRCount = 0, NTTCount = 0, INRCount = 0, ILCount = 0; + private int NTCount = 0, ENCCount = 0, UNTCount = 0, DPSECount = 0, RCTPCount = 0, UBNTCount = 0, UTLCount = 0; + private int error = 0, right = 0; + + public StatisticsTool(File file) { + mDict = file; + } + + public Set getResultSet() { return mSet; } + + public int start() { + String[] appNames = mDict.list(); + if (appNames != null) { + for (String apkDict : appNames) { + if (apkDict.startsWith(".")) continue; + File f = new File(mDict, apkDict); + if (f.isDirectory()) { + System.out.println("Processing Project: " + apkDict); + processEachAppDict(apkDict); + } + } + } else { + System.err.println("错误: 指定的路径下没有找到任何子文件夹/文件 -> " + mDict.getAbsolutePath()); + } + return 0; + } + + private void processEachAppDict(String apkDict) { + currentApk = apkDict; + Result result = new Result(apkDict); + + // 调用处理方法 + procesHTRError(result); + procesNTTError(result); + procesINRError(result); + procesILError(result); + procesNTError(result); + procesENCError(result); + procesUNTError(result); + procesDPSEError(result); + procesRCTPError(result); + procesUBNTError(result); + procesUTLError(result); + + processError(result); + processRight(result); + procesTime(result); + + mSet.add(result); + } + + // --- [核心修正] 通用的解析逻辑 --- + // 自动处理 items.length >= 2 的情况,并使用 items[1] 作为 Key + private void parseAndAdd(Result result, String fileName, java.util.function.BiConsumer adder) { + String filePath = getAbsoluteFilePath(ERROR, fileName); + File checkFile = new File(filePath); + + // 读取文件 + List lines = processEachFile(filePath); + + if (!lines.isEmpty()) { + int addedCount = 0; + for (String line : lines) { + String[] items = line.split(";"); + // [修正点1] 放宽条件:只要 >= 2 列就读取 + if (items.length >= 2) { + // [修正点2] 读取 items[1] (指令内容) 作为Key,而不是 items[2] (null) + String key = items[1]; + adder.accept(items[0], key); + addedCount++; + } + } + // [调试信息] 如果文件有内容,打印读取了多少行 + if (addedCount > 0) { + // System.out.println(" -> [" + fileName + "] added " + addedCount + " errors."); + } + } else { + // 如果文件不存在,这里不报错,因为不是每个项目都有所有错误 + } + } + + // --- 各个类型的具体处理 (全部使用上面的通用逻辑) --- + private void procesHTRError(Result result) { parseAndAdd(result, ERROR_DICT_HTR, result::addHTR); } + private void procesNTTError(Result result) { parseAndAdd(result, ERROR_DICT_NTT, result::addNTT); } + private void procesINRError(Result result) { parseAndAdd(result, ERROR_DICT_INR, result::addINR); } + private void procesILError(Result result) { parseAndAdd(result, ERROR_DICT_IL, result::addIL); } + + private void procesNTError(Result result) { parseAndAdd(result, ERROR_DICT_NT, result::addNT); } + private void procesENCError(Result result) { parseAndAdd(result, ERROR_DICT_ENC, result::addENC); } + private void procesUNTError(Result result) { parseAndAdd(result, ERROR_DICT_UNT, result::addUNT); } + private void procesDPSEError(Result result) { parseAndAdd(result, ERROR_DICT_DPSE, result::addDPSE); } + private void procesRCTPError(Result result) { parseAndAdd(result, ERROR_DICT_RCTP, result::addRCTP); } + private void procesUBNTError(Result result) { parseAndAdd(result, ERROR_DICT_UBNT, result::addUBNT); } + private void procesUTLError(Result result) { parseAndAdd(result, ERROR_DICT_UTL, result::addUTL); } + + private String getAbsoluteFilePath(String parentPath, String relativePath) { + return mDict + File.separator + currentApk + parentPath + relativePath; + } + + public interface LineParseListener { + public void after(String[] items); + } + + private void addError(String filePath, LineParseListener listener) { + // 保留旧方法以兼容可能遗漏的调用,但主要逻辑已移至 parseAndAdd + List lines = processEachFile(filePath); + if (lines.isEmpty() || listener == null) return; + for (String line : lines) { + String[] items = line.split(";"); + listener.after(items); + } + } + + private void procesTime(Result result) { + String path = getAbsoluteFilePath(PATH, TIME_FILE_NAME); + List lines = processEachFile(path); + for (String line : lines) { + String lowerLine = line.toLowerCase(); + if (lowerLine.contains("soottime")) { + result.setSootTime(getInteger(line)); + } + // 【修正】添加 "felidaetime" 匹配你的 log.txt + else if (lowerLine.contains("androlic time") || + lowerLine.contains("leopard time") || + lowerLine.contains("felidaetime") || + lowerLine.contains("totaltime")) { + result.setLeopardTime(getInteger(line)); + } + } + } + + private int getInteger(String source) { + String[] strs = source.split(":"); + if (strs.length < 2) return 0; + int i = 0; + try { i = Integer.parseInt(strs[1].trim()); } catch (Exception e) {} + return i; + } + + private void processError(Result result) { + List errorList = processEachFile(getAbsoluteFilePath(PATH, ERROR_SUM)); + for (String error : errorList) result.addError(error); + } + + private void processRight(Result result) { + List rightList = processEachFile(getAbsoluteFilePath(PATH, RIGHT_SUM)); + for (String right : rightList) { + if (result.errorHashSet.contains(right)) continue; + result.addRight(right); + } + } + + private List processEachFile(String errorFile) { + File file = new File(errorFile); + List lines = new ArrayList<>(); + if (file.exists()) { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.trim().length() > 0) lines.add(line); + } + } catch (IOException e) { e.printStackTrace(); } + } else { + // [调试] 如果找不到文件,不报错,因为这很正常 + // 但如果你确定文件必须存在,可以取消下面的注释 + // System.out.println(" [Info] File missing: " + file.getName()); + } + return lines; + } + + @Override + public String toString() { + // 重置计数 + HTRCount = 0; NTTCount = 0; INRCount = 0; ILCount = 0; + NTCount = 0; ENCCount = 0; UNTCount = 0; DPSECount = 0; + RCTPCount = 0; UBNTCount = 0; UTLCount = 0; + error = 0; right = 0; + + for (Result result : mSet) { + result.toString(); // 触发Result内部计数计算 + HTRCount += result.HTRCount; + NTTCount += result.NTTCount; + INRCount += result.INRCount; + ILCount += result.ILCount; + NTCount += result.NTCount; + ENCCount += result.ENCCount; + UNTCount += result.UNTCount; + DPSECount += result.DPSECount; + RCTPCount += result.RCTPCount; + UBNTCount += result.UBNTCount; + UTLCount += result.UTLCount; + error += result.errorHashSet.size(); + right += result.rightSum.size(); + } + return ""; + } +} \ No newline at end of file diff --git a/src/ac/pool/PoolMain.java b/src/ac/pool/PoolMain.java index af3db38..b46f659 100644 --- a/src/ac/pool/PoolMain.java +++ b/src/ac/pool/PoolMain.java @@ -41,41 +41,54 @@ import soot.jimple.infoflow.InfoflowConfiguration.SootIntegrationMode; import soot.jimple.infoflow.android.InfoflowAndroidConfiguration.CallbackAnalyzer; import soot.jimple.infoflow.android.SetupApplication; +import soot.jimple.infoflow.entryPointCreators.DefaultEntryPointCreator; import soot.jimple.infoflow.sourcesSinks.manager.ISourceSinkManager; import soot.jimple.toolkits.callgraph.Edge; import soot.options.Options; +//负责整个分析流程的初始化和调度 +//整个线程池和并发问题静态分析工具的主控制类 +//分析目标识别(APK/JAR/目录) +//Soot分析环境配置 +//调用图构建 +//并发问题检测调度 +//结果记录 public class PoolMain { - public static long startTime = 0; + public static long startTime = 0; /// 程序开始时间 - public static long preprocessStartTime = 0; + public static long preprocessStartTime = 0; // 预处理开始时间 - public static String Android_Platforms = "/home/cuibaoquan/android-platforms"; - - static String inputPath = "/Users/cbq/Desktop/apks/app.fedilab.lite.apk"; + // 安卓平台路径 + public static String Android_Platforms = "/Users/qiuyucheng/Library/Android/sdk/platforms"; + // 默认输入路径(单个apk文件) + static String inputPath = "/Users/qiuyucheng/Downloads/F-Droid.apk"; + // 输出目录 public static final String Output = "./Felidae-ThreadPool-output/"; // public static boolean refinement = true; + // 当前分析文件是不是apk文件 ? private static boolean isApk = false; - + // jar文件列表... private static List jars = new ArrayList(); - + // 最大内存使用量 private static long maxUsedMemory = 0; - + // 检测到的问题数量 public static int misuseNum = 0; + // 监控分析过程中的内存使用情况,防止内存溢出 static class MemoryThread extends Thread { @Override public void run() { - while (true && !isInterrupted()) { + while (true && !isInterrupted()) {//表示没有被中断。 try { +// maxUsedMemory_t表示已经使用的内存 long maxUsedMemory_t = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); maxUsedMemory = Math.max(maxUsedMemory, maxUsedMemory_t); sleep(2 * 100); - } catch (InterruptedException e) { + } catch (InterruptedException e) {//抛出一个异常中断,然后执行下面的逻辑 long maxUsedMemory_t = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); maxUsedMemory = Math.max(maxUsedMemory, maxUsedMemory_t); break; @@ -85,9 +98,14 @@ public void run() { } public static void main(String[] args) { - - args = new String[]{"D:\\cbq\\EclipseWorkspace\\ADailyTest\\target\\classes"}; +// args : args[0] "D:\\cbq\\EclipseWorkspace\\ADailyTest\\target\\classes" +// args[1]、args[2]。。。 +// args = new String[]{"/Users/qiuyucheng/Downloads/Felidae-ThreadPool-main/target/classes"}; +// args = new String[]{"/Users/qiuyucheng/Downloads/Felidae-ThreadPool-main/src/Mytest/test_il.jar"}; + args = new String[]{"/Users/qiuyucheng/Downloads/Felidae-ThreadPool-main/TestProject/jetty-server-12.1.4.jar"}; + Thread thread = new MemoryThread(); + thread.setDaemon(true); // 守护线程! thread.start(); startTime = System.currentTimeMillis(); inputPath = args[0]; @@ -95,83 +113,125 @@ public static void main(String[] args) { if (args.length > 1) { Android_Platforms = args[1]; } - +// 根据文件类型选择分析方式(支持 apk文件、jar文件、 if (inputPath.toLowerCase().endsWith(".apk")) { isApk = true; startApk(); - } else if (inputPath.toLowerCase().endsWith(".jar")) { + } + else if (inputPath.toLowerCase().endsWith(".jar")) { jars.add(inputPath); startJars(); - } else { + } + else //既不是apk文件,也不是jar文件 + { +// 收集目录下所有jar文件(认为是一个目录) jars.addAll(getJars(inputPath)); if(jars.isEmpty()) { jars.add(inputPath); } startJars(); } - try { - thread.join(); - Log.i("maxUsedMemory = ", maxUsedMemory / (1024 * 1024)); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + +// // 内存监控结束和统计 +// try { +// thread.join(); //等待该thread内存线程执行完成。阻塞当前线程,直到被调用的线程执行完毕 +// Log.i("maxUsedMemory = ", maxUsedMemory / (1024 * 1024)); +// +// } catch (InterruptedException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } + try{ + thread.interrupt(); + }catch(Exception e){ + throw new RuntimeException(); } +// ThreadErrorRecord.recordTime("", "", preprocessStartTime, startTime); Log.i("## end ", inputPath); } + // 开始分析Jar文件 private static void startJars() { +// 一系列初始化工作. G.reset(); Options.v().set_src_prec(Options.src_prec_c); Options.v().set_process_dir(jars); Options.v().set_no_bodies_for_excluded(true); Options.v().set_no_writeout_body_releasing(true); // must be set to true if we want to access method bodies - // after writing output to jimple + // after writing output to jimple Options.v().set_output_format(Options.output_format_none); Options.v().allow_phantom_refs(); Options.v().set_whole_program(true); Options.v().set_exclude(getExcludeList()); - Pack p1 = PackManager.v().getPack("jtp"); - String phaseName = "jtp.bt"; - - List entryMethods = new ArrayList<>(); - - Transform t1 = new Transform(phaseName, new BodyTransformer() { - @Override - protected void internalTransform(Body b, String phase, Map options) { - b.getMethod().setActiveBody(b); - if (b.getMethod().isPrivate() || b.getMethod().isProtected()) { - return; - } - synchronized (entryMethods) { - entryMethods.add(b.getMethod()); - } - } - }); - - p1.add(t1); + // 开启一些选项以确保 SPARK 能更好工作 + Options.v().setPhaseOption("cg.spark", "on"); + Options.v().setPhaseOption("cg.spark", "string-constants:true"); soot.Main.v().autoSetOptions(); - try { + // 2. 加载类 (移除之前的 jtp Transform 代码,因为不需要在那一步收集了) + try {// 运行soot进行全分析。 Scene.v().loadNecessaryClasses(); - PackManager.v().runPacks(); + // 注意:不需要 runPacks(),因为我们不需要运行 jtp 变换来收集方法, + // 我们会在下面直接从 Scene 中获取。 } catch (Throwable e) { e.printStackTrace(); } + // 3. 构建虚拟入口 (Dummy Main) + // 收集所有 Application Class 中的 Public 方法 + List methodsToCall = new ArrayList<>(); + for (SootClass sc : Scene.v().getApplicationClasses()) { + // 跳过接口、抽象类、非具体的类 + if (sc.isInterface() || sc.isPhantom() || sc.isAbstract()) { + continue; + } + + for (SootMethod sm : sc.getMethods()) { + // 筛选具体的、public 的方法作为潜在入口 + if (sm.isConcrete() && sm.isPublic() && !sm.isConstructor()) { + methodsToCall.add(sm.getSignature()); + } + } + } + + // 使用 FlowDroid 的工具生成虚拟 Main 方法 + // 这个 DummyMain 会实例化这些类,并调用这些方法 + SootMethod dummyMain = null; + if (!methodsToCall.isEmpty()) { + // DefaultEntryPointCreator 是 FlowDroid 提供的工具 + DefaultEntryPointCreator creator = new DefaultEntryPointCreator(methodsToCall); + dummyMain = creator.createDummyMain(); + Scene.v().addBasicClass(dummyMain.getDeclaringClass().getName()); + Scene.v().setEntryPoints(Collections.singletonList(dummyMain)); + System.out.println("虚拟入口已构建,包含调用目标: " + methodsToCall.size() + " 个"); + } else { + System.err.println("错误:未找到任何可调用的 Public 方法,无法构建入口。"); + return; + } + +// 使用FlowDroid工具构建调用图的程序段,Infoflow是FlowDroid的主分析对象 +// false表示不使用Android回调分析 Infoflow infoflow = new Infoflow(inputPath, false); +// 配置Soot(Java字节码分析框架)的集成模式 infoflow.getConfig().setSootIntegrationMode(SootIntegrationMode.UseExistingInstance); +// 选择调用图构建算法,SPARK算法:一种精确的指针分析算法,能生成较精确的调用图,而非完整的数据流图。 infoflow.getConfig().setCallgraphAlgorithm(CallgraphAlgorithm.SPARK); +// 关闭污点分析功能,只需要构建调用图 infoflow.getConfig().setTaintAnalysisEnabled(false); +// 禁止代码消除优化,确保所有代码都被分析,不进行任何优化删除 infoflow.getConfig().setCodeEliminationMode(CodeEliminationMode.NoCodeElimination); - Scene.v().setEntryPoints(entryMethods); try { +// 通过反射获取runAnalysis私有方法 Method constructCG = Infoflow.class.getDeclaredMethod("runAnalysis", ISourceSinkManager.class); +// 突破Java的访问限制,允许调用私有方法 constructCG.setAccessible(true); +// 实际执行调用图构建分析,null表示不使用特定的源-汇管理器 constructCG.invoke(infoflow, (ISourceSinkManager) null); +// 在调用图构建完成后,执行自定义的误用检测逻辑 detectMisuse(); } catch (Exception e) { e.printStackTrace(); @@ -180,72 +240,159 @@ protected void internalTransform(Body b, String phase, Map optio private static void startApk() { Log.i("## start apk ", inputPath); - +// 创建APK分析的核心应用程序对象 +// Android_Platforms:Android平台文件路径(包含Android.jar等) +// inputPath:目标APK文件路径 SetupApplication application = new SetupApplication(Android_Platforms, inputPath); +// 配置是否合并多个Dex文件 application.getConfig().setMergeDexFiles(true); +// 配置回调分析使用的算法,分析Android中的事件回调(如按钮点击、生命周期方法等) +// CallbackAnalyzer.Fast - 使用快速但可能不够精确的回调分析器 application.getConfig().getCallbackConfig().setCallbackAnalyzer(CallbackAnalyzer.Fast); +// 设置回调分析的最大时间限制,防止分析过程无限期运行 application.getConfig().getCallbackConfig().setCallbackAnalysisTimeout(5 * 60); +// 分析APK中所有方法的调用关系,建立完整的调用图谱 application.constructCallgraph(); +// 基于构建的调用图进行安全检测 detectMisuse(); } private static void detectMisuse() { - Log.i("----------detector starts "); +// 存储所有实现了Runnable或Callable接口的类名,Set是为了确保唯一性 final Set runnableClasses = new HashSet(); +// 专门存储"不响应中断"的类名 Set iNRClasses = new HashSet<>(); +// 获取所有应用类(排除系统库类) Set sootClasses = new HashSet<>(Scene.v().getApplicationClasses()); - for (SootClass currentClass : sootClasses) { + for (SootClass currentClass : sootClasses) {//遍历每一个应用类 +// 检查 当前这个类是否实现了Runnable接口或者Callable接口 if (AsyncInherit.isInheritedFromRunnable(currentClass) - || AsyncInherit.isInheritedFromCallable(currentClass)) { + || AsyncInherit.isInheritedFromCallable(currentClass)) + { +// 这么用runnableClasses将这个类存起来。 runnableClasses.add(currentClass.getName()); +// 检查该类中是否包含中断检查的逻辑 if (!INRChecker.hasInterruptCheck(currentClass)) { +// 如果不包含,则加入iNRClasses这个set中 iNRClasses.add(currentClass.getName()); } } } +// 输出不响应中断的类数量 Log.i("iNRClasses.size() = ", iNRClasses.size()); +// HTR检查器初始化 HTRChecker.init(); +// System.out.println("HTRChecker init is done"); +// 三种并发模型的数据收集器创建和启动 + +// PointCollectorExecutor分析ExecutorService线程池相关的代码模式 +// 收集newFixedThreadPool(), submit(), shutdown()等调用点 PointCollector executorCollector = new PointCollectorExecutor(); executorCollector.start(sootClasses); - + + // 【新增修改】过滤掉位于构造函数和dummyMainMethod中的噪音点 + filterNoisePoints(executorCollector); + +// PointCollectorThread分析原生Thread使用模式 +// 收集new Thread(), start(), interrupt()等调用点 PointCollector threadCollector = new PointCollectorThread(); threadCollector.start(sootClasses); - - PointCollector asyncTaskCollector = new PointCollectorAsyncTask(); - asyncTaskCollector.start(sootClasses); - - -// debug(pointCollector); - -// int eventSize = runnableClasses.size(); -// eventSize += pointCollector.getInitialPoints().size(); -// eventSize += pointCollector.getStartPoints().size(); -// eventSize += pointCollector.getShutDownPoints().size(); -// eventSize += pointCollector.getShutDownNowPoints().size(); -// ThreadErrorRecord.recordWorkingData(eventSize); + + // 【新增修改】过滤掉位于构造函数和dummyMainMethod中的噪音点 + filterNoisePoints(threadCollector); + +// PointCollectorAsyncTask分析Android AsyncTask使用模式 +// 收集AsyncTask.execute(), 重写的doInBackground()等方法 +// PointCollector asyncTaskCollector = new PointCollectorAsyncTask(); +// asyncTaskCollector.start(sootClasses); + +// start的作用: +// 遍历所有类的方法体 +// 识别特定的方法调用模式 +// 建立关键程序点的映射关系 + + +// debug(pointCollector); +// int eventSize = runnableClasses.size(); +// eventSize += pointCollector.getInitialPoints().size(); +// eventSize += pointCollector.getStartPoints().size(); +// eventSize += pointCollector.getShutDownPoints().size(); +// eventSize += pointCollector.getShutDownNowPoints().size(); +// ThreadErrorRecord.recordWorkingData(eventSize); // cg +// 静态分析工具可能无法准确识别通过反射或接口调用的run()方法 +// 手动添加从start()/submit()到run()的调用边,确保分析完整性 complementCGFromStartToRun(executorCollector); complementCGFromStartToRun(threadCollector); - + // check new PoolCheck(executorCollector, "ExecuteService-", iNRClasses).check(); new PoolCheck(threadCollector, "Thread-", iNRClasses).check(); - new PoolCheck(asyncTaskCollector, "AsyncTask-", iNRClasses).check(); - +// new PoolCheck(asyncTaskCollector, "AsyncTask-", iNRClasses).check(); + +// 在主线程各种关闭PoolCheck的executor线程池; long timeStart = System.currentTimeMillis(); ExecutorService executor = PoolCheck.executor; executor.shutdown(); - + +// try { +//// 等待已提交任务完成,最多等待10秒 +// executor.awaitTermination(10, TimeUnit.SECONDS); +// } catch (InterruptedException e) { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } try { - executor.awaitTermination(10, TimeUnit.SECONDS); + // 添加超时和状态检查 + if (!executor.awaitTermination(10, TimeUnit.SECONDS)) { + System.out.println("NTT检查未在10秒内完成,强制关闭线程池..."); + executor.shutdownNow(); // 强制关闭 + + // 再次等待 + if (!executor.awaitTermination(5, TimeUnit.SECONDS)) { + System.err.println("线程池无法正常关闭"); + } + } + System.out.println("所有检查完成,程序退出"); } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); + System.err.println("等待被中断,强制关闭线程池"); + executor.shutdownNow(); + Thread.currentThread().interrupt(); // 重新设置中断状态 + } + } + + // 【新增方法】用于过滤掉不需要检测的噪音点(构造函数、dummyMain) + private static void filterNoisePoints(PointCollector pointCollector) { + // 获取初始点集合(即 new ThreadPool 或 new Thread 的位置) + // 假设 getInitialPoints 返回的是支持 remove 操作的集合(如 ArrayList 或 HashSet) + // 如果 PointCollector 中没有直接暴露修改接口,可能需要修改 PointCollector 源码, + // 但通常 getter 返回的是引用,可以直接修改。 + java.util.Iterator iterator = pointCollector.getInitialPoints().iterator(); + + while (iterator.hasNext()) { + KeyPoint point = iterator.next(); + SootMethod method = point.getMethod(); // 获取该代码点所在的方法 + String methodName = method.getName(); + String methodSig = method.getSignature(); + + // 1. 忽略发生在 dummyMainMethod 中的调用(分析环境产生的噪音) + if (methodName.contains("dummyMainMethod") || methodSig.contains("dummyMainMethod")) { + iterator.remove(); + continue; + } + + // 2. 忽略发生在构造函数 中的调用 + // 在构造函数中创建线程池,静态分析往往难以追踪其完整的生命周期(如赋值给字段后在destroy中关闭), + // 这通常会导致大量的 NT 误报。 + if (methodName.equals("")) { + iterator.remove(); + continue; + } } } - + static void debug(PointCollector pointCollector) { // ExceptionHandler handler = new ExceptionHandler(); for (SootMethod sootMethod:Scene.v().getSootClass("cn.ac.ios.PoolTest").getMethods()) { @@ -253,7 +400,7 @@ static void debug(PointCollector pointCollector) { Log.i(sootMethod.getActiveBody()); } // Log.i(" Start CallerRunsChecker.. "); -// +// // for (KeyPoint point : pointCollector.getSetRejectedExecutionHandlerPoints()) { // if (CallerRunsChecker.isSetCallerRunsHandlerMisuse(point)) { // ThreadErrorRecord.recordCallerRunsChecker(point); @@ -283,10 +430,11 @@ private static List getExcludeList() { return excludeList; } + // 补充调用图中从 起点方法 到 运行方法 的边,主要处理别名调用关系 static void complementCGFromStartToRun(PointCollector pointCollector) { for (KeyPoint startPoint : pointCollector.getStartPoints()) { for (InitPoint point : pointCollector.getInitialPoints()) { - if (point.isAliasCaller(startPoint)) { + if (point.isAliasCaller(startPoint)) { //如果初始点和开始点是别名关系 for (RefType refType : point.getCallerPossibleType()) { addEdgeFromStartToRunMethod(startPoint, refType.getSootClass()); } @@ -295,6 +443,7 @@ static void complementCGFromStartToRun(PointCollector pointCollector) { } } + // 在调用图中添加从 起点方法 到 线程执行方法 的调用边。 static void addEdgeFromStartToRunMethod(KeyPoint startPoint, SootClass sootClass) { try { SootMethod runMethod = sootClass.getMethodUnsafe(ThreadSig.METHOD_SUBSIG_RUN); @@ -312,6 +461,7 @@ static void addEdgeFromStartToRunMethod(KeyPoint startPoint, SootClass sootClass } + // 递归扫描指定目录及其所有子目录,收集所有.jar文件的绝对路径。 public static Set getJars(String dic) { File file = new File(dic); Set jarList = new HashSet(); @@ -334,6 +484,7 @@ public static Set getJars(String dic) { return jarList; } + // 构建和创建输出目录路径,确保分析结果有合适的存储位置。 public static String getOutputPath(String sub) { String path = null; if (isApk || inputPath.toLowerCase().endsWith(".jar")) { @@ -363,5 +514,4 @@ public static String getApkFullPath() { public static boolean isApk() { return isApk; } - } diff --git a/src/ac/pool/ThreadErrorRecord.java b/src/ac/pool/ThreadErrorRecord.java index c8c3500..8decebd 100644 --- a/src/ac/pool/ThreadErrorRecord.java +++ b/src/ac/pool/ThreadErrorRecord.java @@ -10,7 +10,7 @@ import ac.pool.point.KeyPoint; import soot.SootClass; - +// 可以用于在PoolCheck中记录 Thread的各类错误,并且生成对应的txt错误记录文件。 public class ThreadErrorRecord { private static final String Path_FOLDER = PoolMain.getOutputPath("path") + File.separator; @@ -67,7 +67,6 @@ public synchronized static void recordIL(String component, KeyPoint newPoint) { // if (!needRecord(ILSet, newPoint, null)) { // return; // } - String filePath = ERROR_FOLDER + component + "IL-sum.txt"; recordSum(component, newPoint, null, filePath); } @@ -100,8 +99,14 @@ public static void recordUBNT(String component, InitPoint point) { String filePath = ERROR_FOLDER + component + "UBNT-sum.txt"; recordSum(component, point, null, filePath); } - - + + public static void recordUBSCQ(String component, InitPoint point) { + String filePath = ERROR_FOLDER + component + "UBSCQ-sum.txt"; + recordSum(component, point, null, filePath); + } + + + public static void recordUNT(String component, InitPoint point) { String filePath = ERROR_FOLDER + component + "UNT-sum.txt"; recordSum(component, point, null, filePath); diff --git a/src/ac/pool/checker/CallerRunsChecker.java b/src/ac/pool/checker/CallerRunsChecker.java index 1b2b43a..eeeae5c 100644 --- a/src/ac/pool/checker/CallerRunsChecker.java +++ b/src/ac/pool/checker/CallerRunsChecker.java @@ -12,23 +12,37 @@ import soot.toolkits.graph.BriefUnitGraph; import soot.toolkits.graph.UnitGraph; import soot.toolkits.scalar.SimpleLocalDefs; +//CallerRunsChecker 适合检测 通过 setRejectedExecutionHandler() 方法 +//显式设置 CallerRunsPolicy 的潜在误用情况 +//根据适合什么情况的检测,去写一个测试用例 +//检测线程池拒绝策略 CallerRunsPolicy 的误用情况。 +//CallerRunsPolicy 是一种特殊的拒绝策略,它会让调用者线程直接执行被拒绝的任务,在某些场景下可能导致性能问题或死锁 public class CallerRunsChecker { - +// 检测给定的线程池初始化点是否存在 CallerRunsPolicy 误用。 public static boolean hasMisuse(InitPoint initPoint, Set set) { +// 包含所有设置拒绝策略的关键点; for(OneParaKeyPoint setRejectedExecutionHandlerPoint: set) { +// 判断 这个初始化点和set.setRejectedExecutionHandler的这个point不是同一个point! +// 并且判断,这个setRejectedExecutionHandlerPoint中存在SetCallerRunsHandlerMisuse误用; if(initPoint.isAliasCaller(setRejectedExecutionHandlerPoint) && isSetCallerRunsHandlerMisuse(setRejectedExecutionHandlerPoint)) { return true; } } return false; } - + +// 通过数据流分析检测拒绝策略参数是否是 CallerRunsPolicy 类型。 private static boolean isSetCallerRunsHandlerMisuse(OneParaKeyPoint setRejectedExecutionHandlerPoint) { +// 为包含拒绝策略设置的方法构建控制流图 UnitGraph graph = new BriefUnitGraph(setRejectedExecutionHandlerPoint.getMethod().getActiveBody()); +// 获取设置拒绝策略时传入的参数局部变量 Local handlerLocal = setRejectedExecutionHandlerPoint.getParaLocal(); +// 使用 Soot 的 SimpleLocalDefs 进行定义-使用分析 +// 找到所有给 handlerLocal 赋值的语句 SimpleLocalDefs defs = new SimpleLocalDefs(graph); List defsOfHandlerLocal = defs.getDefsOf(handlerLocal); +// 类型检查 for (Unit unit : defsOfHandlerLocal) { if(unit instanceof DefinitionStmt) { DefinitionStmt definitionStmt = (DefinitionStmt) unit; @@ -39,5 +53,4 @@ private static boolean isSetCallerRunsHandlerMisuse(OneParaKeyPoint setRejectedE } return false; } - } diff --git a/src/ac/pool/checker/ExceptionHandlerChecker.java b/src/ac/pool/checker/ExceptionHandlerChecker.java index 9d92d93..3b9dfdc 100644 --- a/src/ac/pool/checker/ExceptionHandlerChecker.java +++ b/src/ac/pool/checker/ExceptionHandlerChecker.java @@ -26,9 +26,13 @@ import soot.toolkits.graph.ExceptionalUnitGraph; import soot.util.Chain; +//异常处理检查器,主要用于检查线程池任务中的异常处理情况。 +//核心功能是验证在异步任务执行时是否设置了适当的异常处理机制,以防止未捕获异常导致的问题。 public class ExceptionHandlerChecker { +// 判断是否存在异常处理缺失的情况 +// 如果既没有提交处理器也没有工厂处理器,则认为存在误用 public static boolean hasMisuse(InitPoint initPoint, Set setFactoryPoints, Set submitPoints, Set setUncaughtExceptionHandlerPoints) { return !hasSubmitHandler(initPoint, submitPoints, setUncaughtExceptionHandlerPoints) @@ -87,9 +91,9 @@ private static boolean hasSubmitHandler(InitPoint initPoint, Set setUncaughtExceptionHandlerPoints) { for (KeyPoint keyPoint : setUncaughtExceptionHandlerPoints) { // thread.setUncaughtExceptionHandler(); - if (keyPoint.isAliasCaller(task)) { + if (keyPoint.isAliasCaller(task)) { /** / fkkk, soot has a bug: - * + * * $r5 = new cn.ac.ios.PoolTest$2; specialinvoke $r5.(cn.ac.ios.PoolTest)>(r3); //inner Runnable @@ -106,10 +110,10 @@ private static boolean hasBackgroundMethodHandler(Local task, Set setU label1: $r8 = staticinvoke (); - - + + * then $r8 is $r5 ?? - * */ + * */ Log.e(keyPoint.getStmt()); Log.e(task); Log.e("keyPoint.isAliasCaller(task)"); @@ -126,7 +130,7 @@ private static boolean hasBackgroundMethodHandler(Local task, Set setU return false; } } - + } return true; } @@ -140,7 +144,8 @@ static SootMethod getBackgroundMethod(RefType taskPossiableType) { if(runMethod == null) { runMethod = getMethod(sootClass, AsyncTaskSig.DO_IN_BACKGROUND_NAME); } - return null; +// 这里原来是null,所以一直返回null! + return runMethod; } private static boolean hasExceptionHandler(SootMethod sootMethod) { diff --git a/src/ac/pool/checker/HTRChecker.java b/src/ac/pool/checker/HTRChecker.java index 31cd74e..8661b21 100644 --- a/src/ac/pool/checker/HTRChecker.java +++ b/src/ac/pool/checker/HTRChecker.java @@ -1,3 +1,144 @@ +//package ac.pool.checker; +// +//import java.util.ArrayList; +//import java.util.HashMap; +//import java.util.HashSet; +//import java.util.Map; +//import java.util.Set; +// +//import ac.pool.PoolMain; +//import destructible.DestructibleIdentify; +//import soot.Type; +//import soot.AnySubType; +//import soot.Body; +//import soot.Local; +//import soot.PointsToAnalysis; +//import soot.PointsToSet; +//import soot.RefType; +//import soot.Scene; +//import soot.SootClass; +//import soot.SootField; +//import soot.SootMethod; +//import soot.Unit; +//import soot.Value; +//import soot.jimple.DefinitionStmt; +//import soot.jimple.FieldRef; +//import soot.jimple.InvokeExpr; +//import soot.jimple.ParameterRef; +// +//// Hard to Release +//public class HTRChecker { +// +//// map中有该type对应的sootclass 并且 按照这个sootclass去取map中的value不为空,说明有对应的set,该类中有可销毁的字段。 +// public static boolean hasHTRMisuse(RefType type) { +// return destructibleSootFieldMap.containsKey(type.getSootClass()) +// && !destructibleSootFieldMap.get(type.getSootClass()).isEmpty(); +// } +// +//// destructibleSootFieldMap 可销毁的字段map和set +// public final static Map> destructibleSootFieldMap = new HashMap<>(); +// public final static HashSet destructibleSootFields = new HashSet<>(); +// +// public static void init() { +// Set sootClasses = new HashSet();//获取所有应用类 +// sootClasses.addAll(Scene.v().getApplicationClasses()); +// for (SootClass currentClass : sootClasses) { +//// 为每个class类在映射中创建空集合 +// destructibleSootFieldMap.put(currentClass, new HashSet<>()); +// reachingDestructibleObject(currentClass); +// } +// } +// +//// 分析指定类中的字段赋值,识别哪些字段被赋值为可销毁对象 +// private static void reachingDestructibleObject(SootClass sootClass) { +//// 对于每一个类,通过getMethods获得类中的方法,然后去遍历类中的所有的方法。 +// ArrayList methods = new ArrayList<>(sootClass.getMethods()); +// for (SootMethod sootMethod : methods) { +// if (sootMethod.hasActiveBody()) {//判断要有activeBody +//// 如果当前分析的是APK文件,并且指定的方法不在可达方法集合中,那么进行下一次循环,因为都是不可达方法了 +// if (PoolMain.isApk() && !Scene.v().getReachableMethods().contains(sootMethod)) { +// continue; +// } +//// 获得具体的方法体body(apk文件且指定的方法在可达方法中) +// Body body = sootMethod.getActiveBody(); +// for (Unit unit : body.getUnits()) {//对方法体中的每个语句unit进行遍历 +// if (unit instanceof DefinitionStmt) {//如果是赋值语句 +// DefinitionStmt definitionStmt = (DefinitionStmt) unit; +//// 问题: 你的检测器只分析——字段赋值,构造函数参数赋值未被检测; +// if (definitionStmt.getLeftOp() instanceof FieldRef) +// { //如果左边是字段引用,说明这是字段引用的赋值 +// FieldRef fieldRef = (FieldRef) definitionStmt.getLeftOp(); +// if (fieldRef.getField() != null && fieldRef.getField().isStatic()) { +// //如果左边是字段引用,并且是static静态的话,就跳过这句unit,遍历下一句unit +// //静态字段通常不涉及对象销毁管理 +// continue; +// } +//// 只分析当前类中声明的字段(排除继承的字段),不分析父类中的字段。 +// //通过字段获取DeclaringClass来获取声明类。 +// if (fieldRef.getField().getDeclaringClass().getName().equals(sootClass.getName())) +// { +//// 分析赋值右侧的值 +// Value rightOp = definitionStmt.getRightOp();//获取右操作数 +// PointsToAnalysis pointsToAnalysis = Scene.v().getPointsToAnalysis();//获取指针分析器 +// PointsToSet pointsToSet = null;//存储rightOp可能指向的所有内存对象 +// Set types = new HashSet<>();//收集rightOp可能指向对象的所有类型 +// if (rightOp instanceof Local) {//局部变量的话如 x = y 中的 y用 +// // 指针分析器去分析,可能指向的所有内存对象 +// pointsToSet = pointsToAnalysis.reachingObjects((Local) rightOp); +// } +// else if (rightOp instanceof FieldRef) +// {//字段引用,如 x = Class.staticField) +// FieldRef fieldRef2 = (FieldRef) rightOp; +//// 静态的字段引用 +// if (fieldRef2.getField() != null && fieldRef2.getField().isStatic()) { +// pointsToSet = pointsToAnalysis.reachingObjects(fieldRef2.getField()); +// } +// } +//// 处理方法调用,如 x = obj.method() +// else if (rightOp instanceof InvokeExpr) { +// InvokeExpr invokeExpr = (InvokeExpr) rightOp; +//// 返回类型中,将该方法所返回的可能性类型全存储了。 +// types.add(invokeExpr.getMethod().getReturnType()); +// } +//// 经过上面三个if-else后,如果内存对象集合和类型存储集合都不为空的话,将对象涉及到的类型全部放入内存对象 +// if (pointsToSet != null && pointsToSet.possibleTypes() != null) { +// types.addAll(pointsToSet.possibleTypes()); +// } +//// 分析Type中的类型 +// Set refTypes = new HashSet<>(); +// for (Type type : types) { +//// 引用类型 +// if (type instanceof RefType) { +// refTypes.add((RefType) type); +// } +//// 某个类型及其所有子类型,getBase()获取基类型 +// else if (type instanceof AnySubType) +// { +// refTypes.add(((AnySubType) type).getBase()); +// } +// } +//// 对于引用类型再次遍历——识别和收集可销毁字段 +// for (RefType refType : refTypes) { +//// 每个引用类型获取其类class。 +// SootClass fieldClass = refType.getSootClass(); +//// 判断该类是否为"可销毁的",两个fieldClass要检查的类和当前上下文类 +// if (DestructibleIdentify.isDestructibleClass(fieldClass, fieldClass)) { +//// 按类分组记录可销毁字段 +// destructibleSootFieldMap.get(sootClass).add(fieldRef.getField()); +//// 记录所有可销毁字段 +// destructibleSootFields.add(fieldRef.getField()); +// } +// } +// +// } +// } +// } +// } +// } +// } +// } +//} + package ac.pool.checker; import java.util.ArrayList; @@ -7,6 +148,7 @@ import java.util.Set; import ac.pool.PoolMain; +import ac.util.AsyncInherit; // 假设你需要用到这个工具类来判断是否继承自 Runnable/Callable import destructible.DestructibleIdentify; import soot.Type; import soot.AnySubType; @@ -25,26 +167,89 @@ import soot.jimple.FieldRef; import soot.jimple.InvokeExpr; +// Hard to Release public class HTRChecker { + // 【修改点 1】修改判定逻辑,增加过滤 public static boolean hasHTRMisuse(RefType type) { - return destructibleSootFieldMap.containsKey(type.getSootClass()) - && !destructibleSootFieldMap.get(type.getSootClass()).isEmpty(); + SootClass sootClass = type.getSootClass(); + + // 1. 基础检查:如果该类没有记录任何可销毁字段,直接返回 false + if (!destructibleSootFieldMap.containsKey(sootClass) + || destructibleSootFieldMap.get(sootClass).isEmpty()) { + return false; + } + + // 2. 【核心修改】通用过滤逻辑:忽略“临时任务包装器” + // 如果这个类本身就是一个 Runnable/Callable 的包装器(Lambda或内部类), + // 那么它持有字段通常是为了在 run() 中使用,而不是长期持有资源,属于安全模式。 + if (isTransientTaskWrapper(sootClass)) { + return false; + } + + return true; + } + + /** + * 【新增方法】判断一个类是否为临时的任务包装器 + * 特征: + * 1. 类名包含 "$",说明是内部类、匿名类或 Lambda 表达式编译后的合成类。 + * 2. 该类实现了 Runnable 或 Callable 接口。 + */ + private static boolean isTransientTaskWrapper(SootClass sootClass) { + // 1. 检查类名特征 + // Lambda 表达式通常编译为 ClassName$lambda$... + // 匿名内部类通常编译为 ClassName$1... + // 静态内部类通常编译为 ClassName$InnerName... + if (!sootClass.getName().contains("$")) { + return false; + } + + // 2. 检查是否为任务类 (Runnable / Callable) + // 这里复用了你项目中已有的 AsyncInherit 工具类逻辑,或者直接检查接口 + // 如果 PoolMain 中有 AsyncInherit,建议直接调用: + if (AsyncInherit.isInheritedFromRunnable(sootClass) + || AsyncInherit.isInheritedFromCallable(sootClass)) { + return true; + } + + // 如果没有 AsyncInherit,可以使用以下原生 Soot 逻辑替代: + /* + for (SootClass iface : sootClass.getInterfaces()) { + if (iface.getName().equals("java.lang.Runnable") || + iface.getName().equals("java.util.concurrent.Callable")) { + return true; + } + } + if (sootClass.hasSuperclass()) { + String superName = sootClass.getSuperclass().getName(); + // 检查是否继承自特定的任务基类(视具体情况而定) + if (superName.equals("java.util.concurrent.FutureTask")) return true; + } + */ + + return false; } + // destructibleSootFieldMap 可销毁的字段map和set public final static Map> destructibleSootFieldMap = new HashMap<>(); public final static HashSet destructibleSootFields = new HashSet<>(); public static void init() { - Set sootClasses = new HashSet(); + Set sootClasses = new HashSet();//获取所有应用类 sootClasses.addAll(Scene.v().getApplicationClasses()); for (SootClass currentClass : sootClasses) { +// 为每个class类在映射中创建空集合 destructibleSootFieldMap.put(currentClass, new HashSet<>()); reachingDestructibleObject(currentClass); } } + // ... (reachingDestructibleObject 方法保持不变) ... + // 分析指定类中的字段赋值,识别哪些字段被赋值为可销毁对象 private static void reachingDestructibleObject(SootClass sootClass) { + // 保持原样,无需修改 + // ... 代码省略,与你提供的一致 ... ArrayList methods = new ArrayList<>(sootClass.getMethods()); for (SootMethod sootMethod : methods) { if (sootMethod.hasActiveBody()) { @@ -55,24 +260,29 @@ private static void reachingDestructibleObject(SootClass sootClass) { for (Unit unit : body.getUnits()) { if (unit instanceof DefinitionStmt) { DefinitionStmt definitionStmt = (DefinitionStmt) unit; - if (definitionStmt.getLeftOp() instanceof FieldRef) { + if (definitionStmt.getLeftOp() instanceof FieldRef) + { FieldRef fieldRef = (FieldRef) definitionStmt.getLeftOp(); if (fieldRef.getField() != null && fieldRef.getField().isStatic()) { continue; } - if (fieldRef.getField().getDeclaringClass().getName().equals(sootClass.getName())) { + if (fieldRef.getField().getDeclaringClass().getName().equals(sootClass.getName())) + { Value rightOp = definitionStmt.getRightOp(); PointsToAnalysis pointsToAnalysis = Scene.v().getPointsToAnalysis(); PointsToSet pointsToSet = null; Set types = new HashSet<>(); if (rightOp instanceof Local) { pointsToSet = pointsToAnalysis.reachingObjects((Local) rightOp); - } else if (rightOp instanceof FieldRef) { + } + else if (rightOp instanceof FieldRef) + { FieldRef fieldRef2 = (FieldRef) rightOp; if (fieldRef2.getField() != null && fieldRef2.getField().isStatic()) { pointsToSet = pointsToAnalysis.reachingObjects(fieldRef2.getField()); } - } else if (rightOp instanceof InvokeExpr) { + } + else if (rightOp instanceof InvokeExpr) { InvokeExpr invokeExpr = (InvokeExpr) rightOp; types.add(invokeExpr.getMethod().getReturnType()); } @@ -83,7 +293,9 @@ private static void reachingDestructibleObject(SootClass sootClass) { for (Type type : types) { if (type instanceof RefType) { refTypes.add((RefType) type); - } else if (type instanceof AnySubType) { + } + else if (type instanceof AnySubType) + { refTypes.add(((AnySubType) type).getBase()); } } diff --git a/src/ac/pool/checker/ILChecker.java b/src/ac/pool/checker/ILChecker.java index c25f6ff..e6a6479 100644 --- a/src/ac/pool/checker/ILChecker.java +++ b/src/ac/pool/checker/ILChecker.java @@ -1,45 +1,235 @@ package ac.pool.checker; +import java.util.List; import java.util.Set; +import java.util.ArrayList; +import java.util.HashSet; +import soot.*; +import soot.jimple.IfStmt; +import soot.jimple.DefinitionStmt; +import soot.jimple.FieldRef; +import soot.jimple.IdentityStmt; +import soot.jimple.ParameterRef; +import soot.jimple.ReturnStmt; // [新增] +import soot.jimple.ReturnVoidStmt;// [新增] +import soot.jimple.ThrowStmt; // [新增] +import soot.toolkits.scalar.SimpleLocalDefs; +import soot.toolkits.graph.BriefUnitGraph; import ac.pool.point.KeyPoint; -import soot.Unit; public class ILChecker { + // 检查是否存在线程池关闭误用导致的无限循环 public static boolean isShutDownMisuse(Set shutDowns, KeyPoint isShutDownPoint) { - boolean isLoopCondition = isLoopCondition(isShutDownPoint); - if (!isLoopCondition) { + + // 1. [基础检查] 检查是否在循环条件中 + LoopConditionAnalysis analysis = new LoopConditionAnalysis(isShutDownPoint); + if (!analysis.isShutDownLoopCondition) { + return false; + } + + // 2. [新增 - 关键修复] 检查该判断条件是否直接导致了异常抛出或方法返回 + // 针对 SnowflakeIdGenerator:检测到中断后 throw exception,这不是无限循环。 + // 针对 AgentRunner:catch 块中有 return,这也算有效退出。 + if (analysis.leadsToExitOrThrow) { return false; } + + // 3. 检查是否有匹配的 shutdown 调用 for (KeyPoint shutDown : shutDowns) { + + // 步骤 A: 简单同方法变量匹配 (新增,解决 AgentRunner 同变量问题) + if (isSameMethodAndLocal(isShutDownPoint, shutDown)) { + return false; + } + + // 步骤 B: 标准别名/指针分析 if (isShutDownPoint.isAliasCaller(shutDown)) { return false; } + + // 步骤 C: Lambda 模糊匹配 + if (isLambdaAlias(isShutDownPoint, shutDown)) { + return false; + } } + + // 确实没找到匹配的关闭操作,判定为误用 return true; } - private static boolean isLoopCondition(KeyPoint isShutDown) { - return new LoopConditionAnalysis(isShutDown).isShutDownLoopCondition; + /** + * [新增] 快速检查两个点是否在同一个方法内,且使用同一个 Local 变量 + * 这比复杂的指针分析更准、更快,专门解决 AgentRunner 这类局部逻辑 + */ + private static boolean isSameMethodAndLocal(KeyPoint p1, KeyPoint p2) { + if (!p1.getMethod().equals(p2.getMethod())) { + return false; + } + Value v1 = p1.getCaller(); + Value v2 = p2.getCaller(); + // 直接比较 Jimple 的 Local 对象引用 + return v1 != null && v2 != null && v1.equals(v2); } - static class LoopConditionAnalysis extends MethodLoopAnalyzer { + private static boolean isLambdaAlias(KeyPoint lambdaPoint, KeyPoint outerPoint) { + // ... (保持你原有的代码不变) ... + SootMethod lambdaMethod = lambdaPoint.getMethod(); + + if (!lambdaMethod.getName().contains("lambda") && !lambdaMethod.getName().contains("$")) { + return false; + } + + Value outerCaller = outerPoint.getCaller(); + Value lambdaCaller = lambdaPoint.getCaller(); + + if (!lambdaMethod.hasActiveBody()) return false; + + for (Unit u : lambdaMethod.getActiveBody().getUnits()) { + if (u instanceof DefinitionStmt) { + DefinitionStmt def = (DefinitionStmt) u; + if (def.getLeftOp().equals(lambdaCaller)) { + Value rightOp = def.getRightOp(); + + if (rightOp instanceof FieldRef) { + if (outerCaller instanceof Local) { + String outerName = ((Local) outerCaller).getName(); + String fieldName = ((FieldRef) rightOp).getField().getName(); + if (fieldName.toLowerCase().contains(outerName.toLowerCase())) { + return true; + } + } + } + + if (rightOp instanceof ParameterRef) { + Type paramType = rightOp.getType(); + Type outerType = outerCaller.getType(); + if (paramType.toString().equals(outerType.toString())) { + return true; + } + } + } + } + } + return false; + } - Unit isShutDownUnit = null; + // ----------------------------------------------------------- + // 循环分析类 (增强版) + // ----------------------------------------------------------- + static class LoopConditionAnalysis { boolean isShutDownLoopCondition = false; + boolean leadsToExitOrThrow = false; // [新增] 标记是否导向退出 public LoopConditionAnalysis(KeyPoint isShutDown) { - super(isShutDown.getMethod()); - isShutDownUnit = isShutDown.getStmt(); - generation(); + analyze(isShutDown); } - @Override - protected void afterLoopAnalysis() { - isShutDownLoopCondition = mLoopHeaderList.contains(isShutDownUnit); + private void analyze(KeyPoint isShutDownPoint) { + SootMethod method = isShutDownPoint.getMethod(); + Unit isShutDownUnit = isShutDownPoint.getStmt(); + + if (!method.hasActiveBody()) return; + BriefUnitGraph graph = new BriefUnitGraph(method.getActiveBody()); + SimpleLocalDefs localDefs = new SimpleLocalDefs(graph); + + for (Unit unit : graph) { + if (unit instanceof IfStmt) { + IfStmt ifStmt = (IfStmt) unit; + Value condition = ifStmt.getCondition(); + + for (ValueBox box : condition.getUseBoxes()) { + Value value = box.getValue(); + if (value instanceof Local) { + Local conditionLocal = (Local) value; + List defs = localDefs.getDefsOfAt(conditionLocal, ifStmt); + + if (defs.contains(isShutDownUnit)) { + // 1. 确认该变量确实参与了循环条件 + if (isReachable(graph, ifStmt, isShutDownUnit)) { + isShutDownLoopCondition = true; + + // 2. [新增] 检查这个 IfStmt 的分支是否会导致退出 + if (checkExitPaths(graph, ifStmt)) { + leadsToExitOrThrow = true; + } + return; + } + } + } + } + } + } } + // [新增] 检查 IfStmt 的任意分支是否能在有限步数内到达 Return 或 Throw + private boolean checkExitPaths(BriefUnitGraph graph, IfStmt ifStmt) { + // 检查 Target 分支 (跳转分支) + if (leadsToExit(graph, ifStmt.getTarget())) return true; + + // 检查 Fallthrough 分支 (顺序执行分支) + // 这里的逻辑是:如果检测到中断/关闭,通常会进入一个特定的分支(可能是target,也可能是fallthrough) + // 只要这两个分支中有一个能迅速退出方法,我们就认为这是一个安全的检查。 + Unit succ = null; + for(Unit u : graph.getSuccsOf(ifStmt)) { + if (u != ifStmt.getTarget()) { + succ = u; + break; + } + } + if (succ != null && leadsToExit(graph, succ)) return true; + + return false; + } + + // [新增] 深度优先搜索寻找退出点 (限制深度以防性能问题) + private boolean leadsToExit(BriefUnitGraph graph, Unit startUnit) { + List queue = new java.util.LinkedList<>(); + Set visited = new HashSet<>(); + queue.add(startUnit); + + int maxDepth = 20; // 限制搜索深度,通常异常抛出或return离if很近 + int depth = 0; + + while (!queue.isEmpty() && depth < maxDepth) { + int size = queue.size(); + for(int i=0; i queue = new java.util.LinkedList<>(); + Set visited = new java.util.HashSet<>(); + queue.addAll(graph.getSuccsOf(startNode)); + + while (!queue.isEmpty()) { + Unit current = queue.remove(0); + if (visited.contains(current)) continue; + visited.add(current); + + if (current.equals(targetNode)) { + return true; + } + queue.addAll(graph.getSuccsOf(current)); + } + return false; + } } -} +} \ No newline at end of file diff --git a/src/ac/pool/checker/INRChecker.java b/src/ac/pool/checker/INRChecker.java index 005463e..4c96434 100644 --- a/src/ac/pool/checker/INRChecker.java +++ b/src/ac/pool/checker/INRChecker.java @@ -8,22 +8,35 @@ import ac.util.AsyncInherit; import soot.SootClass; import soot.SootMethod; - +// INR (Interrupt No Respond)中断不响应,在循环中没有中断检查机制。 +//INRChecker是一个中断检查器 +//循环中断检查 —— 检查循环是否包含适当的 中断检查机制,检查isInterrupt +//Runnable实现验证 —— 验证实现Runnable接口的类是否正确处理中断 +//中断误用检测 —— 检测不恰当的中断调用关系 public class INRChecker { +// isAllLoopCancelled方法——判断是否所有的loop循环都有这个取消的机制。这就也达到了目的之一(循环中断检查) +// 这个方法是针对于 SootMethod的,是去判断一个方法。 public static boolean hasInterruptCheck(SootMethod sootMethod) { return new RunMethod(sootMethod).isAllLoopCancelled(); } - +// 这个方法针对于 SootClass的,是一个类级别的终端检查方法。 +// 检查实现Runnable接口的类中的所有run方法是否都有适当的中断检查。 public static boolean hasInterruptCheck(SootClass sootClass) { boolean hasInterruptCheck = true; - if (AsyncInherit.isInheritedFromRunnable(sootClass)) { + if (AsyncInherit.isInheritedFromRunnable(sootClass)) {//判断当前这个类是不是实现Runnable接口 +// 如果这个类是实现Runnable的,那么获取这个类中所有的方法,存储在ArrayList列表中,这个列表是methods; ArrayList methods = new ArrayList<>(sootClass.getMethods()); - for (SootMethod sootMethod : methods) { + for (SootMethod sootMethod : methods) {//遍历methods中的每一个方法 +// 判断这个方法是不是是run方法(就是线程启动的方法),并且该方法有参数。 if (ThreadSig.METHOD_SUBSIG_RUN.equals(sootMethod.getSubSignature()) - && sootMethod.getParameterCount() == 0) { + && sootMethod.getParameterCount() == 0) + { +// 调用上面对于单个方法的中断检查方法。 +// 只要有一个是false就说明这个类中不是所有方法都有中断循环检查的 hasInterruptCheck = hasInterruptCheck(sootMethod); - if (!hasInterruptCheck) { + if (!hasInterruptCheck) + { return hasInterruptCheck; } } @@ -31,13 +44,31 @@ public static boolean hasInterruptCheck(SootClass sootClass) { } return hasInterruptCheck; } - + +// 中断误用检测,有初始点和中断点 +// 检测是否存在不恰当的中断调用关系,即检查初始点是否通过别名关系调用了中断点。 +// 这个方法目前还没有用。 public static boolean hasINRMisuse(KeyPoint initialPoint, Set interruptPoints) { - for (KeyPoint point : interruptPoints) { - if (initialPoint.isAliasCaller(point)) { + for (KeyPoint point : interruptPoints) {//遍历所有终端点。 + if (initialPoint.isAliasCaller(point)) {//基于指针分析(Points-To Analysis)确定对象引用关系 return true; } } return false; } -} \ No newline at end of file +} + +//============ hasINRMisuse的误用例子 +// 在分析时: +// initialPoint = TaskManager.startTask()中的worker.start() +// interruptPoint = worker.interrupt() +// 如果这两个点通过别名分析关联,则检测为误用 + +// public class TaskManager { +// public void startTask() { +// Thread worker = new Thread(new Task()); +// worker.start(); +// // 错误:立即中断刚启动的线程 +// worker.interrupt(); // interruptPoint +// } +//} \ No newline at end of file diff --git a/src/ac/pool/checker/IntMaxChecker.java b/src/ac/pool/checker/IntMaxChecker.java index 3a7e4c3..826cd6c 100644 --- a/src/ac/pool/checker/IntMaxChecker.java +++ b/src/ac/pool/checker/IntMaxChecker.java @@ -11,18 +11,29 @@ import soot.toolkits.graph.UnitGraph; import soot.toolkits.scalar.SimpleLocalDefs; +//线程池参数被设置为 Integer.MAX_VALUE 的误用。 public class IntMaxChecker { - + +// 该方法的分支1,判断point.getParaValue是否为直接整形常量,然后在判断是否为Integer.MAX_VALUE +// 分支2判断point.getParaValue是否为局部变量。紧接着在进行判断。 public static boolean hasMaxIntegerSizeMisuse(OneParaValueKeyPoint point){ +// 直接使用 Integer.MAX_VALUE 作为参数值的情况。 if(point.getParaValue() instanceof IntConstant) { IntConstant intConstant = (IntConstant) point.getParaValue(); if(intConstant.value == Integer.MAX_VALUE) { return true; } - }else if(point.getParaValue() instanceof Local) { + } + +// 通过变量间接传递 Integer.MAX_VALUE 的情况。 + else if(point.getParaValue() instanceof Local) + { Local local = (Local) point.getParaValue(); +// 基于方法体创建单位图(UnitGraph),表示方法的控制流 UnitGraph graph = new BriefUnitGraph(point.getMethod().getActiveBody()); +// 构建局部变量的定义-使用链 SimpleLocalDefs defs = new SimpleLocalDefs(graph); +// 对于上述获得的使用链,返回所有定义该局部变量local的语句 List defsOfHandlerLocal = defs.getDefsOf(local); for (Unit unit : defsOfHandlerLocal) { if(unit instanceof DefinitionStmt) { @@ -35,7 +46,6 @@ public static boolean hasMaxIntegerSizeMisuse(OneParaValueKeyPoint point){ } } } - } return false; } diff --git a/src/ac/pool/checker/MethodLoopAnalyzer.java b/src/ac/pool/checker/MethodLoopAnalyzer.java index d74b3f6..250b13a 100644 --- a/src/ac/pool/checker/MethodLoopAnalyzer.java +++ b/src/ac/pool/checker/MethodLoopAnalyzer.java @@ -16,69 +16,134 @@ * @author Baoquan Cui * @version 1.0 */ +//基于深度优先搜索的循环检测算法,用于识别方法中的循环结构。 +//循环检测 - 识别方法中的所有循环结构 +//控制流分析 - 分析方法的基本块和控制流关系 public class MethodLoopAnalyzer { - +// 存储所有基本语句(Unit)的分析信息 protected List mUnitInfos = new ArrayList(); - +// 存储所有循环头(循环的起始位置)( while 、for循环开头的那些 unit 语句 ) protected Set mLoopHeaderList = new HashSet<>(); - +// 方法的控制流图 protected UnitGraph mUnitGraph = null; + /** + * unit存储实际的Soot语句对象: 可能是赋值语句、方法调用、条件跳转等 + * 分析的基本单位,每个被分析的语句都有一个对应的UnitInfo. + */ + static class UnitInfo { + protected Unit mUnit = null; //// 对应的基本块 + protected boolean visited = false; + protected int mDeepFirstSearchPathPosition = 0;// DFS路径位置,记录在深度优先搜索中的遍历序号 + protected Unit mLoopHeaderUnit = null;// 指向控制该语句的循环头 + protected boolean isCancelled = false;// 是否可取消(用于上层分析) + + + // public boolean equals(Object obj) { +// boolean result = mUnit.equals(obj instanceof UnitInfo ? ((UnitInfo) obj).mUnit : obj); +// return result; +// } + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof UnitInfo)) { + return mUnit.equals(obj); + } + UnitInfo other = (UnitInfo) obj; + return mUnit.equals(other.mUnit); + } + + @Override + public String toString() { + StringBuffer sb = new StringBuffer(); + sb.append("unit-->" + mUnit); + sb.append("\n"); + sb.append("iloop_header-->" + mLoopHeaderUnit); + sb.append("\n"); + sb.append("DFEP_pos-->" + mDeepFirstSearchPathPosition); + return sb.toString(); + } + + } + +// 为有活动体的方法创建控制流图。 public MethodLoopAnalyzer(SootMethod methodUnderAnalysis) { if (methodUnderAnalysis.hasActiveBody()) { mUnitGraph = new BriefUnitGraph(methodUnderAnalysis.getActiveBody()); } } - +// 获取所有循环的起始位置获得所有循环头。 public Set getLoopStartUnits() { return mLoopHeaderList; } - protected void generation() { - if (mUnitGraph == null) { +// 执行完generation()后,系统会生成: +// mLoopHeaderList: 包含所有识别出的循环头语句 +// ---------什么是循环头--------- +// while (i < 10) for(int i=0;i<10;i++)类似这种,就是循环头 +// 循环头是循环的入口点;所有循环迭代都必须经过循环头;循环头控制着循环的继续或退出 + +// mUnitInfos: 每个语句单元的循环分析信息 +// 触发回调: afterLoopAnalysis()执行后续处理 + protected void generation() {//generation 是真正的控制方法。 + if (mUnitGraph == null) {//安全检查,确保控制流图已正确初始,如果没有控制流图,无法进行循环分析 return; } +// 深度优先遍历: 从每个头节点开始DFS遍历 + //获取控制流图的入口节点(通常是方法的第一个语句) for (Unit head : mUnitGraph.getHeads()) { traverUnitGraph(head, 0); } - +// 收集循环头: 从分析结果中提取所有循环头 for (UnitInfo unitInfo : mUnitInfos) { +// 从每个UnitInfo的mLoopHeaderUnit字段提取到全局的mLoopHeaderList,建立完整的循环头映射表。 mLoopHeaderList.add(unitInfo.mLoopHeaderUnit); } afterLoopAnalysis(); } - +// 在 RunMethod 中被重写,用于收集取消单元和方法调用信息。 protected void afterLoopAnalysis() { + } + +// 实现了基于深度优先搜索的循环检测,用于在控制流图中识别循环头节点 private Unit traverUnitGraph(Unit b0, int deepFirstSearchPathPosition) { // return: innermost loop header of b0 +// 初始化一些b0的信息。 + +// 得到一个unitInfo对象,如果mUnitInfos有,直接返回,如果没有则创造一个返回。 UnitInfo unitInfo = getUnitInfo(b0); unitInfo.visited = true; unitInfo.mDeepFirstSearchPathPosition = deepFirstSearchPathPosition; - for (Unit b : mUnitGraph.getSuccsOf(b0)) { + for (Unit b : mUnitGraph.getSuccsOf(b0)) {// 遍历后续所有节点,UnitGraph类型自带的方法getSuccOf UnitInfo unitInfob = getUnitInfo(b); +// 情况1:未访问的后继结点。 if (!unitInfob.visited) { - // case(A) + // case(A) 遇到未访问的后继节点,递归DFS,然后传播循环头信息 Unit nh = traverUnitGraph(b, deepFirstSearchPathPosition + 1); tagLoopHeader(b0, nh); - } else { + } + else + { if (unitInfob.mDeepFirstSearchPathPosition > 0) { - // case(B) + // case(B) 后继节点已在当前DFS路径中,将后继节点标记为循环头 unitInfob.visited = true; tagLoopHeader(b0, b); - } else if (unitInfob.mLoopHeaderUnit == null) { - // case(C) - - } else { + } + else if (unitInfob.mLoopHeaderUnit == null) { + // case(C) 节点已访问但尚未分配循环头 + } + else { Unit h = unitInfob.mLoopHeaderUnit; UnitInfo unitInfoH = getUnitInfo(h); if (unitInfoH.mDeepFirstSearchPathPosition > 0) { - // case(D) + // case(D) 后继节点在活动循环中 +// 继承相同的循环头 tagLoopHeader(b0, h); } else { - // case(E) re-entry + // case(E) re-entry 处理嵌套循环的重新进入,向上查找活动的循环头 while (unitInfoH.mLoopHeaderUnit != null) { unitInfoH = getUnitInfo(unitInfoH.mLoopHeaderUnit); if (unitInfoH.mDeepFirstSearchPathPosition > 0) { @@ -94,29 +159,37 @@ private Unit traverUnitGraph(Unit b0, int deepFirstSearchPathPosition) { return unitInfo.mLoopHeaderUnit; } +// 循环头标记算法,用于建立节点与循环头之间的层次关系 +// 确保每个节点指向最内层的循环头,处理嵌套循环情况,避免循环引用 private void tagLoopHeader(Unit b, Unit h) { - if (b == h || h == null) { +// DFS位置值:值越小表示在DFS树中越"浅"(越接近根节点) +// 循环头链:每个节点通过mLoopHeaderUnit指向其所属循环的头节点 + // b: 当前节点, h: 候选循环头节点 + if (b == h || h == null) { //排除自循环和空循环 return; } UnitInfo cur1 = getUnitInfo(b); UnitInfo cur2 = getUnitInfo(h); +// 当前节点已有循环头,需要处理循环头链的合并 while (cur1.mLoopHeaderUnit != null) { UnitInfo ih = getUnitInfo(cur1.mLoopHeaderUnit); if (ih == cur2) { return; } +// 情况A:当前循环头比候选循环头"更深"(DFS位置值更小) if (ih.mDeepFirstSearchPathPosition < cur2.mDeepFirstSearchPathPosition) { - cur1.mLoopHeaderUnit = cur2.mUnit; - cur1 = cur2; - cur2 = ih; + cur1.mLoopHeaderUnit = cur2.mUnit;// 更新为更浅的循环头 + cur1 = cur2;// 移动cur1到候选循环头 + cur2 = ih;// 移动cur2到原循环头 } else { - cur1 = ih; + cur1 = ih;// 继续向上遍历循环头链 } } +// 当cur1.mLoopHeaderUnit == null时,直接建立关联 cur1.mLoopHeaderUnit = cur2.mUnit; - } +// 获取或创建一个UnitInfo的分析信息,返回一个UnitInfo对象; protected UnitInfo getUnitInfo(Unit unit) { for (UnitInfo unitInfo : mUnitInfos) { if (unitInfo.mUnit.equals(unit)) { @@ -129,33 +202,6 @@ protected UnitInfo getUnitInfo(Unit unit) { return unitInfo; } - /** - * - */ - static class UnitInfo { - protected Unit mUnit = null; - protected boolean visited = false; - protected int mDeepFirstSearchPathPosition = 0; - protected Unit mLoopHeaderUnit = null; - protected boolean isCancelled = false; - - @Override - public boolean equals(Object obj) { - boolean result = mUnit.equals(obj instanceof UnitInfo ? ((UnitInfo) obj).mUnit : obj); - return result; - } - @Override - public String toString() { - StringBuffer sb = new StringBuffer(); - sb.append("unit-->" + mUnit); - sb.append("\n"); - sb.append("iloop_header-->" + mLoopHeaderUnit); - sb.append("\n"); - sb.append("DFEP_pos-->" + mDeepFirstSearchPathPosition); - return sb.toString(); - } - - } } diff --git a/src/ac/pool/checker/NTTChecker.java b/src/ac/pool/checker/NTTChecker.java index 2560159..d83bec8 100644 --- a/src/ac/pool/checker/NTTChecker.java +++ b/src/ac/pool/checker/NTTChecker.java @@ -24,10 +24,17 @@ import soot.Scene; import soot.SootClass; import soot.SootMethod; +import soot.Type; import soot.Unit; import soot.Value; +import soot.jimple.AssignStmt; +import soot.jimple.CastExpr; +import soot.jimple.DefinitionStmt; +import soot.jimple.FieldRef; import soot.jimple.InstanceInvokeExpr; +import soot.jimple.IntConstant; import soot.jimple.InvokeExpr; +import soot.jimple.ReturnStmt; import soot.jimple.Stmt; import soot.jimple.toolkits.callgraph.CallGraph; import soot.jimple.toolkits.callgraph.Edge; @@ -39,20 +46,41 @@ public class NTTChecker { private static CallGraph cg = Scene.v().getCallGraph(); private static PointCollector pointCollector; - + // 缓存一个方法——>UnitGraph,一个方法对应一个Graph private static Map> methodCFGMap = new ConcurrentHashMap<>(); public static boolean checkNTTMisuse(KeyPoint startPoint, Set interruptPoints, - PointCollector pointCollector) { + PointCollector pointCollector) { NTTChecker.pointCollector = pointCollector; + + // 0. 守护线程预检查 + if (isDaemonThread(startPoint)) { + return false; + } + + // [NEW FIX] 1. 工厂方法模式白名单 (Factory Pattern Heuristic) + // 如果方法名暗示它是创建者(create/new/build),且返回类型是线程池/线程,则认为对象所有权已转移。 + // 这是解决 Factory/Builder 误报最稳健的方式。 + if (isFactoryMethod(startPoint.getMethod())) { + return false; + } + + // 2. 逃逸分析检查 (Flow Analysis) + // 分析对象是否被 return, 赋值给字段, 或作为参数传递。 + if (isEscaped(startPoint)) { + return false; + } + boolean hasInterruptPoint = false; for (KeyPoint interruptEvent : interruptPoints) { - if (startPoint.isAliasCaller(interruptEvent)) { + if (startPoint.isAliasCaller(interruptEvent)) {//检查启动点和中断点是否操作 同一对象。 hasInterruptPoint = true; +// 检查销毁点是否与启动点相关 for (DestroyPoint destroyEvent : pointCollector.getDestroyPoints()) { if(!destroyEvent.isAliasCaller(startPoint)) { continue; } +// 核心的happens-before关系检查 if (HB(destroyEvent.getStmt(), interruptEvent.getStmt(), interruptEvent.getMethod())) { return true; } @@ -62,7 +90,218 @@ public static boolean checkNTTMisuse(KeyPoint startPoint, Set interrup return !hasInterruptPoint; } + // ================================================================================== + // [NEW FIX] 工厂模式判定 (解决顽固误报) + // ================================================================================== + private static boolean isFactoryMethod(SootMethod method) { + // 1. 检查方法名 + String name = method.getName().toLowerCase(); + boolean isCreatorName = name.startsWith("create") || + name.startsWith("new") || + name.startsWith("build") || + name.startsWith("get") || // 某些 getter 可能会懒加载创建 + name.startsWith("wrap") || + // [新增] 处理内部类访问外部类的合成方法 (如 access$000) + name.startsWith("access"); + + if (!isCreatorName) { + return false; + } + + // 2. 检查返回类型是否为线程或线程池相关 + Type returnType = method.getReturnType(); + String returnTypeStr = returnType.toString(); + + return returnTypeStr.contains("ExecutorService") || + returnTypeStr.contains("ThreadPoolExecutor") || + returnTypeStr.contains("Thread") || + returnTypeStr.contains("DtpExecutor") || // 针对特定框架 + returnTypeStr.contains("Future") || // 有时返回 Future 代表任务提交 + // [新增] 处理 Jetty Context 等上下文对象误报 + returnTypeStr.contains("Context"); + } + // ================================================================================== + // [Updated] 逃逸分析相关方法 (增加健壮性) + // ================================================================================== + + private static boolean isEscaped(KeyPoint point) { + Local local = extractLocalFromPoint(point); + if (local == null) return false; + + return checkEscape(point.getMethod(), local); + } + + private static Local extractLocalFromPoint(KeyPoint point) { + Unit unit = point.getStmt(); + if (unit instanceof DefinitionStmt) { + Value left = ((DefinitionStmt) unit).getLeftOp(); + if (left instanceof Local) { + return (Local) left; + } + } else if (unit instanceof Stmt) { + Stmt stmt = (Stmt) unit; + if (stmt.containsInvokeExpr()) { + InvokeExpr invokeExpr = stmt.getInvokeExpr(); + if (invokeExpr instanceof InstanceInvokeExpr && invokeExpr.getMethod().getName().equals("")) { + Value base = ((InstanceInvokeExpr) invokeExpr).getBase(); + if (base instanceof Local) { + return (Local) base; + } + } + } + } + return null; + } + + private static boolean checkEscape(SootMethod method, Local allocatedLocal) { + // [FIX] 强制尝试获取活跃 Body,防止因 Body 被释放导致分析失败 + if (!method.hasActiveBody()) { + try { + method.retrieveActiveBody(); + } catch (Exception e) { + return false; // 无法获取 Body,无法分析,保守返回 false (或可考虑在此处也应用工厂白名单) + } + } + + if (!method.hasActiveBody()) return false; + + Set trackedValues = new HashSet<>(); + Set trackedNames = new HashSet<>(); + + trackedValues.add(allocatedLocal); + trackedNames.add(allocatedLocal.getName()); + + boolean changed = true; + while (changed) { + changed = false; + for (Unit unit : method.getActiveBody().getUnits()) { + if (unit instanceof AssignStmt) { + AssignStmt assign = (AssignStmt) unit; + Value left = assign.getLeftOp(); + Value right = assign.getRightOp(); + + boolean isTracked = trackedValues.contains(right); + if (!isTracked && right instanceof Local) { + isTracked = trackedNames.contains(((Local)right).getName()); + } + + if (!isTracked && right instanceof CastExpr) { + Value castOp = ((CastExpr) right).getOp(); + if (trackedValues.contains(castOp)) isTracked = true; + else if (castOp instanceof Local && trackedNames.contains(((Local)castOp).getName())) isTracked = true; + } + + if (isTracked) { + if (trackedValues.add(left)) changed = true; + if (left instanceof Local) { + if (trackedNames.add(((Local) left).getName())) changed = true; + } + } + } + } + } + + for (Unit unit : method.getActiveBody().getUnits()) { + // A. Check Return + if (unit instanceof ReturnStmt) { + ReturnStmt returnStmt = (ReturnStmt) unit; + Value op = returnStmt.getOp(); + if (trackedValues.contains(op)) return true; + if (op instanceof Local && trackedNames.contains(((Local)op).getName())) return true; + } + // B. Check Field Assign + if (unit instanceof AssignStmt) { + AssignStmt assignStmt = (AssignStmt) unit; + if (assignStmt.getLeftOp() instanceof FieldRef) { + Value right = assignStmt.getRightOp(); + if (trackedValues.contains(right) || (right instanceof Local && trackedNames.contains(((Local)right).getName()))) { + return true; + } + } + } + // C. Check Param Passing + if (unit instanceof Stmt) { + Stmt stmt = (Stmt) unit; + if (stmt.containsInvokeExpr()) { + InvokeExpr invokeExpr = stmt.getInvokeExpr(); + boolean isSelfInit = false; + if (invokeExpr instanceof InstanceInvokeExpr) { + Value base = ((InstanceInvokeExpr) invokeExpr).getBase(); + // 判断是否是对象自身的 调用 + if (base.equals(allocatedLocal) || (base instanceof Local && trackedNames.contains(((Local)base).getName()))) { + if (invokeExpr.getMethod().getName().equals("")) { + isSelfInit = true; + } + } + } + + if (!isSelfInit) { + for (Value arg : invokeExpr.getArgs()) { + if (trackedValues.contains(arg) || (arg instanceof Local && trackedNames.contains(((Local)arg).getName()))) { + return true; + } + } + } + } + } + } + return false; + } + + // ================================================================================== + // [Existing Logic] 守护线程检查 + // ================================================================================== + private static boolean isDaemonThread(KeyPoint startPoint) { + PointsToAnalysis pta = Scene.v().getPointsToAnalysis(); + Local startLocal = extractLocalFromPoint(startPoint); + if (startLocal == null) return false; + + PointsToSet startPts = pta.reachingObjects(startLocal); + if (scanMethodForSetDaemon(startPoint.getMethod(), startPts)) return true; + + for (InitPoint initPoint : pointCollector.getInitialPoints()) { + if (initPoint.getCallerPointsToSet().hasNonEmptyIntersection(startPts)) { + if (scanMethodForSetDaemon(initPoint.getMethod(), startPts)) return true; + } + } + return false; + } + + private static boolean scanMethodForSetDaemon(SootMethod method, PointsToSet targetPts) { + // 同样增加 Body 检查 + if (!method.hasActiveBody()) { + try { method.retrieveActiveBody(); } catch(Exception e) { return false; } + } + if (!method.hasActiveBody()) return false; + + PointsToAnalysis pta = Scene.v().getPointsToAnalysis(); + for (Unit unit : method.getActiveBody().getUnits()) { + if (unit instanceof Stmt) { + Stmt stmt = (Stmt) unit; + if (stmt.containsInvokeExpr()) { + InvokeExpr invokeExpr = stmt.getInvokeExpr(); + SootMethod invokeMethod = invokeExpr.getMethod(); + if (invokeMethod.getName().equals("setDaemon") && invokeMethod.getParameterCount() == 1 && invokeExpr instanceof InstanceInvokeExpr) { + Value arg = invokeExpr.getArg(0); + boolean isTrue = false; + if (arg instanceof IntConstant && ((IntConstant) arg).value == 1) isTrue = true; + if (isTrue) { + Value base = ((InstanceInvokeExpr) invokeExpr).getBase(); + if (base instanceof Local) { + PointsToSet currentBasePts = pta.reachingObjects((Local) base); + if (currentBasePts.hasNonEmptyIntersection(targetPts)) return true; + } + } + } + } + } + } + return false; + } + + // ... (HB 方法无需修改,保持原样) ... // destroy happens before unit +// Happens-Before关系检查 private static boolean HB(Unit destroyUnit, Unit unit, SootMethod sootMethod) { ConcurrentLinkedQueue unitQueue = new ConcurrentLinkedQueue<>(); ConcurrentLinkedQueue unitToMethodQueue = new ConcurrentLinkedQueue<>(); @@ -121,13 +360,13 @@ else if (Signature.startMethods_set.contains(invokeMethod.getSubSignature())) { pushMethodUnitsToConcurrentLinkedQueue(currentMethod, visitedUnits, unitQueue, unitToMethodQueue, currentUnit); } - } return false; } private static void pushMethodUnitsToConcurrentLinkedQueue(SootMethod sootMethod, HashSet visitedUnits, - ConcurrentLinkedQueue unitQueue, ConcurrentLinkedQueue unitToMethodQueue) { + ConcurrentLinkedQueue unitQueue, ConcurrentLinkedQueue unitToMethodQueue) + { if (sootMethod.hasActiveBody()) { UnitGraph srcUnitGraph = getUnitGraph(sootMethod); List tails = srcUnitGraph.getTails(); @@ -139,7 +378,7 @@ private static void pushMethodUnitsToConcurrentLinkedQueue(SootMethod sootMethod } private static void pushMethodUnitsToConcurrentLinkedQueue(SootMethod sootMethod, HashSet visitedUnits, - ConcurrentLinkedQueue unitQueue, ConcurrentLinkedQueue unitToMethodQueue, Unit start) { + ConcurrentLinkedQueue unitQueue, ConcurrentLinkedQueue unitToMethodQueue, Unit start) { if (sootMethod.hasActiveBody()) { UnitGraph srcUnitGraph = getUnitGraph(sootMethod); List preds = srcUnitGraph.getPredsOf(start); @@ -154,8 +393,8 @@ private static void pushMethodUnitsToConcurrentLinkedQueue(SootMethod sootMethod } private static void pushMethodUnitsViaStart(InvokeExpr invokeExpr, HashSet visitedUnits, - HashSet visitedJoinCaller, ConcurrentLinkedQueue unitQueue, - ConcurrentLinkedQueue unitToMethodQueue) { + HashSet visitedJoinCaller, ConcurrentLinkedQueue unitQueue, + ConcurrentLinkedQueue unitToMethodQueue) { if (invokeExpr instanceof InstanceInvokeExpr) { InstanceInvokeExpr instanceInvokeExpr = (InstanceInvokeExpr) invokeExpr; Value caller = instanceInvokeExpr.getBase(); @@ -177,8 +416,8 @@ private static void pushMethodUnitsViaStart(InvokeExpr invokeExpr, HashSet } private static void pushMethodUnitsViaJoin(InvokeExpr invokeExpr, HashSet visitedUnits, - HashSet visitedJoinCaller, ConcurrentLinkedQueue unitQueue, - ConcurrentLinkedQueue unitToMethodQueue) { + HashSet visitedJoinCaller, ConcurrentLinkedQueue unitQueue, + ConcurrentLinkedQueue unitToMethodQueue) { if (invokeExpr instanceof InstanceInvokeExpr) { InstanceInvokeExpr instanceInvokeExpr = (InstanceInvokeExpr) invokeExpr; Value caller = instanceInvokeExpr.getBase(); @@ -191,8 +430,11 @@ private static void pushMethodUnitsViaJoin(InvokeExpr invokeExpr, HashSet } + + // 1.方法出口处理 +// 2.指定起点的前驱处理 private static void pushFindRunMethodUnitsToConcurrentLinkedQueue(Local localCaller, HashSet visitedUnits, - ConcurrentLinkedQueue unitQueue, ConcurrentLinkedQueue unitToMethodQueue) { + ConcurrentLinkedQueue unitQueue, ConcurrentLinkedQueue unitToMethodQueue) { PointsToAnalysis pta = Scene.v().getPointsToAnalysis(); PointsToSet localCallerPointsToSet = pta.reachingObjects(localCaller); for (InitPoint initPoint : pointCollector.getInitialPoints()) { @@ -207,9 +449,8 @@ private static void pushFindRunMethodUnitsToConcurrentLinkedQueue(Local localCal } } - private static void pushRunMethodUnitsToConcurrentLinkedQueue(SootClass sootClass, HashSet visitedUnits, - ConcurrentLinkedQueue unitQueue, ConcurrentLinkedQueue unitToMethodQueue) { + ConcurrentLinkedQueue unitQueue, ConcurrentLinkedQueue unitToMethodQueue) { try { SootMethod runMethod = sootClass.getMethod(ThreadSig.METHOD_SUBSIG_RUN); pushMethodUnitsToConcurrentLinkedQueue(runMethod, visitedUnits, unitQueue, unitToMethodQueue); @@ -222,16 +463,20 @@ private static void pushRunMethodUnitsToConcurrentLinkedQueue(SootClass sootClas private static UnitGraph getUnitGraph(SootMethod sootMethod) { UnitGraph unitGraph = null; +// 缓存检查 if (methodCFGMap.containsKey(sootMethod)) { unitGraph = methodCFGMap.get(sootMethod).get(); } +// 缓存未命中此方法时,再次创建此图。 if (unitGraph == null) { if (sootMethod.hasActiveBody()) { + //创建这个SootMethod对应的UnitGraph unitGraph = new BriefUnitGraph(sootMethod.getActiveBody()); +// 缓存在这个CFGMap中。 methodCFGMap.put(sootMethod, new WeakReference(unitGraph)); } } return unitGraph; } -} +} \ No newline at end of file diff --git a/src/ac/pool/checker/PoolCheck.java b/src/ac/pool/checker/PoolCheck.java index b9dea28..82b741c 100644 --- a/src/ac/pool/checker/PoolCheck.java +++ b/src/ac/pool/checker/PoolCheck.java @@ -1,11 +1,11 @@ package ac.pool.checker; -import java.util.HashSet; -import java.util.Iterator; -import java.util.Set; +import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import soot.jimple.*; +import soot.*; import ac.component.PointCollectorExecutor; import ac.pool.ThreadErrorRecord; import ac.pool.point.InitPoint; @@ -14,20 +14,21 @@ import ac.pool.point.OneParaValueKeyPoint; import ac.pool.point.PointCollector; import ac.util.Log; -import soot.RefType; -import soot.Scene; -import soot.jimple.toolkits.callgraph.CallGraph; -import soot.jimple.toolkits.callgraph.Edge; +import soot.jimple.toolkits.annotation.logic.Loop; -public class PoolCheck implements ICheck { +import soot.toolkits.graph.BriefUnitGraph; +import soot.toolkits.graph.LoopNestTree; +import soot.toolkits.graph.UnitGraph; +//线程池和并发问题检测的总控类,集成了11种不同的并发问题检查器 +public class PoolCheck implements ICheck { public static final ExecutorService executor = Executors .newFixedThreadPool(Runtime.getRuntime().availableProcessors() / 2); - + //数据收集器 protected PointCollector pointCollector = null; - + // 组件名称(用于标识) protected String component = null; - + // INR中断未响应问题相关的类集合 protected Set iNRClasses = null; public PoolCheck(PointCollector pointCollector, String component, Set iNRClasses) { @@ -35,7 +36,205 @@ public PoolCheck(PointCollector pointCollector, String component, Set iN this.component = component; this.iNRClasses = iNRClasses; } + /** + * 增强版逃逸分析:支持局部变量传递追踪 + * 解决:$r31 (allocation) -> dtpExecutor (local var) -> return dtpExecutor 这种链路 + */ + private static boolean checkEscape(SootMethod method, Local allocatedLocal) { + if (!method.hasActiveBody()) return false; + + // 1. 建立追踪集合,初始包含创建点的变量 + Set trackedValues = new HashSet<>(); + trackedValues.add(allocatedLocal); + + // 2. 迭代传播 (Fixed-point iteration) + // 目的是找出所有指向该对象的局部变量别名 + // 例如:x = new Obj(); y = x; z = y; return z; + // 这里的 x, y, z 都应该被加入 trackedValues + boolean changed = true; + while (changed) { + changed = false; + for (Unit unit : method.getActiveBody().getUnits()) { + if (unit instanceof AssignStmt) { + AssignStmt assign = (AssignStmt) unit; + Value left = assign.getLeftOp(); + Value right = assign.getRightOp(); + + // 处理直接赋值: y = x + if (trackedValues.contains(right)) { + if (trackedValues.add(left)) { + changed = true; + } + } + + // 处理类型转换: y = (Type) x + if (right instanceof CastExpr) { + Value castOp = ((CastExpr) right).getOp(); + if (trackedValues.contains(castOp)) { + if (trackedValues.add(left)) { + changed = true; + } + } + } + } + } + } + + // 3. 检查所有追踪变量是否逃逸 + for (Unit unit : method.getActiveBody().getUnits()) { + + // A. 检查 Return 语句 + if (unit instanceof ReturnStmt) { + ReturnStmt returnStmt = (ReturnStmt) unit; + if (trackedValues.contains(returnStmt.getOp())) { + return true; // 只要返回了集合中的任意一个别名,就算逃逸 + } + } + + // B. 检查赋值给 Field (this.field = x) + if (unit instanceof AssignStmt) { + AssignStmt assignStmt = (AssignStmt) unit; + if (assignStmt.getLeftOp() instanceof FieldRef) { + if (trackedValues.contains(assignStmt.getRightOp())) { + return true; + } + } + } + + // C. 检查作为参数传递 (func(x)) + if (unit instanceof Stmt) { + Stmt stmt = (Stmt) unit; + if (stmt.containsInvokeExpr()) { + InvokeExpr invokeExpr = stmt.getInvokeExpr(); + // 遍历参数列表,看是否有我们在追踪的变量 + for (Value arg : invokeExpr.getArgs()) { + if (trackedValues.contains(arg)) { + return true; + } + } + } + } + } + + return false; + } + + private static boolean isReachable(UnitGraph graph, Unit startUnit, Unit endUnit) { + if (startUnit == endUnit) return true; + Queue queue = new LinkedList<>(); + Set visited = new HashSet<>(); + + // 从起点开始 + queue.add(startUnit); + visited.add(startUnit); + + while (!queue.isEmpty()) { + Unit current = queue.poll(); + + // 获取当前节点的后继节点 (Succs) + for (Unit succ : graph.getSuccsOf(current)) { + // 如果找到了终点 + if (succ == endUnit) { + return true; + } + + // 继续搜索未访问过的路径 + if (!visited.contains(succ)) { + visited.add(succ); + queue.add(succ); + } + } + } + + // 遍历完所有可能路径都没找到终点 -> 不可达 (互斥) + return false; + } + + private static boolean isStmtInLoop(SootMethod method, Unit stmt) { + // 如果方法没有活跃的方法体(如抽象方法或native方法),无法分析 + if (!method.hasActiveBody()) { + return false; + } + + try { + Body body = method.getActiveBody(); + + // ========================= 修正点 ========================= + // 错误写法: LoopNestTree loopNestTree = new LoopNestTree(cfg); + // 正确写法: 直接传入 body,Soot 会在内部处理 CFG 构建 + LoopNestTree loopNestTree = new LoopNestTree(body); + // ========================================================= + + // 遍历分析出的所有循环 + for (Loop loop : loopNestTree) { + // getLoopStatements() 返回构成该循环体的所有指令单元 + // 如果我们的创建语句包含在循环体内,则返回 true + if (loop.getLoopStatements().contains(stmt)) { + return true; + } + } + } catch (Exception e) { + // 某些极端情况下构建循环树可能会抛出异常,保守返回 false + // e.printStackTrace(); + } + + return false; + } + private static boolean isSameMethodAndVariable(InitPoint initPoint, Value initVar, KeyPoint terminatePoint) { + // 1. 必须在同一个方法内 + if (initPoint.getMethod() != terminatePoint.getMethod()) { + return false; + } + + // 2. 提取关闭点操作的变量 + Value shutdownVar = null; + Unit termUnit = terminatePoint.getStmt(); + if (termUnit instanceof Stmt) { + Stmt stmt = (Stmt) termUnit; + if (stmt.containsInvokeExpr()) { + InvokeExpr expr = stmt.getInvokeExpr(); + // shutdown() 是实例方法,必须是 InstanceInvokeExpr + if (expr instanceof InstanceInvokeExpr) { + shutdownVar = ((InstanceInvokeExpr) expr).getBase(); + } + } + } + + // 3. 比对两个变量是否是同一个 Soot Local 对象 + // 在 Soot 中,同一个 Body 内的同名变量是同一个对象实例,可以直接 equals + if (shutdownVar != null && initVar.equals(shutdownVar)) { + return true; + } + + return false; + } + private boolean isReturned(SootMethod method, Value targetVar) { + if (!method.hasActiveBody()) { + return false; + } + + // 遍历方法体内的所有语句 + for (Unit unit : method.getActiveBody().getUnits()) { + // 检查是否是 ReturnStmt (例如: return $r1) + if (unit instanceof ReturnStmt) { + ReturnStmt returnStmt = (ReturnStmt) unit; + Value returnValue = returnStmt.getOp(); + + // 检查返回的值是否就是我们要检测的线程池对象 + if (returnValue.equals(targetVar)) { + return true; + } + + // 【可选增强】如果需要更精确的别名分析(处理 $r2 = $r1; return $r2 的情况) + // 可以在这里结合你现有的 point.isAlias(...) 逻辑, + // 但通常工厂模式生成的代码非常简单直接 ($r1 = new; return $r1),上面的 equals 足够覆盖90%情况。 + } + } + return false; + } + +// check方法就是要查11种误用方式,发现问题则记录到ThreadErrorRecord. @Override public void check() { if (pointCollector.getStartPoints().isEmpty()) { @@ -44,13 +243,19 @@ public void check() { } Log.i(component, " # InitialPoints Size ", pointCollector.getInitialPoints().size()); Log.i(component, " # StartPoints Size ", pointCollector.getStartPoints().size()); + Log.i(component, " # 1. Start HTR.. "); - for (InitPoint point : pointCollector.getInitialPoints()) { +// 开始 Hard to Release分析 + for (InitPoint point : pointCollector.getInitialPoints()) {//遍历每一个initPoint +// 遍历submit提交点。 HTRLoop: for (OneParaKeyPoint submitPoint : pointCollector.getSubmitPoints()) { if (!point.isAliasCaller(submitPoint)) { +// 判断initPoint和submitPoint是否为同一个对象,如果是则遍历下一个submitPoint continue; } +// 提取submitPoint的所有可能涉及到的局部变量类型 for (RefType refType : submitPoint.getParaLocalPossiableTypes()) { +// 检查该类型是否有HTR误用,如果有HTR误用的话,用ThreadErrorRecord生成HTR-sum.txt if (HTRChecker.hasHTRMisuse(refType)) { ThreadErrorRecord.recordHTR(component, point, refType.getSootClass()); break HTRLoop; @@ -58,6 +263,9 @@ public void check() { } } } + + +// 开始INR的分析 Log.i(component, " # 2. Start INR.. "); for (InitPoint point : pointCollector.getInitialPoints()) { INRLoop: for (OneParaKeyPoint submitPoint : pointCollector.getSubmitPoints()) { @@ -103,24 +311,315 @@ public void run() { } } +// Log.i(component, " # 3.1. Start NT.. "); +// +// for (InitPoint point : pointCollector.getInitialPoints()) { +// Set keyPoints = new HashSet(); +// keyPoints.addAll(pointCollector.getShutDownNowPoints()); +// keyPoints.addAll(pointCollector.getShutDownPoints()); +// boolean terminate = false; +// for (KeyPoint terminatePoint : keyPoints) { +// if (point.isAliasCaller(terminatePoint)) { +// terminate = true; +// break; +// } +// } +// if (!terminate) { +// ThreadErrorRecord.recordNT(component, point); +// } +// } + + +// ... + + +// // ... 在 detectMisuse 方法中 ... +// +// Log.i(component, " # 3.1. Start NT.. "); +// +// for (InitPoint point : pointCollector.getInitialPoints()) { +// +// // 1. =========================== 语义过滤逻辑 (保持不变) =========================== +// SootMethod method = point.getMethod(); +// String methodName = method.getName(); +// SootClass declaringClass = method.getDeclaringClass(); +// String className = declaringClass.getName(); +// +// boolean isLambdaContext = +// className.contains("lambda") || +// className.contains("Lambda") || +// methodName.contains("lambda$") || +// (className.contains("$") && (methodName.equals("apply") || methodName.equals("run"))); +// +// boolean isAllocation = false; +// Value allocatedLocal = null; +// +// // 提取创建点对应的变量 +// if (point.getStmt() instanceof DefinitionStmt) { +// DefinitionStmt stmt = (DefinitionStmt) point.getStmt(); +// // 兼容 NewExpr 和 staticinvoke (工厂方法返回) +// if (stmt.getRightOp() instanceof NewExpr || stmt.containsInvokeExpr()) { +// // 这里稍微放宽条件:只要是赋值语句,我们就尝试提取左值变量 +// isAllocation = stmt.getRightOp() instanceof NewExpr; +// allocatedLocal = stmt.getLeftOp(); +// } +// } else if (point.getStmt() instanceof Stmt && ((Stmt)point.getStmt()).containsInvokeExpr()) { +// // 兼容 specialinvoke $r0.... +// InvokeExpr expr = ((Stmt)point.getStmt()).getInvokeExpr(); +// if (expr instanceof InstanceInvokeExpr && expr.getMethod().getName().equals("")) { +// isAllocation = true; // 构造函数视为分配 +// allocatedLocal = ((InstanceInvokeExpr) expr).getBase(); +// } +// } +// +// // 2. =========================== 执行 Lambda/Wrapper 过滤 (保持不变) =========================== +// if (isLambdaContext && !isAllocation) { +// continue; +// } +// +// // 3. =========================== 逃逸分析 (保持不变) =========================== +// if (allocatedLocal != null) { +// if (isReturned(method, allocatedLocal)) { +// continue; +// } +// } +// +// // 4. =========================== 核心修正:Shutdown 匹配增强 =========================== +// Set keyPoints = new HashSet(); +// keyPoints.addAll(pointCollector.getShutDownNowPoints()); +// keyPoints.addAll(pointCollector.getShutDownPoints()); +// +// boolean terminate = false; +// for (KeyPoint terminatePoint : keyPoints) { +// // A. 原有的别名分析检查 +// if (point.isAliasCaller(terminatePoint)) { +// terminate = true; +// break; +// } +// +// // B. 【新增】同方法内的局部变量一致性检查 +// // 解决 main 方法中 staticinvoke 返回值与 shutdown 调用未关联的问题 +// if (allocatedLocal != null) { +// if (isSameMethodAndVariable(point, allocatedLocal, terminatePoint)) { +// terminate = true; +// break; +// } +// } +// } +// +// if (!terminate) { +// ThreadErrorRecord.recordNT(component, point); +// } +// } +// + // ... inside detectMisuse method ... + +// 2025——12——3 +// Log.i(component, " # 3.1. Start NT.. "); +// +// for (InitPoint point : pointCollector.getInitialPoints()) { +// +// // 1. =========================== 语义过滤逻辑 (保持不变) =========================== +// SootMethod method = point.getMethod(); +// String methodName = method.getName(); +// SootClass declaringClass = method.getDeclaringClass(); +// String className = declaringClass.getName(); +// +// boolean isLambdaContext = +// className.contains("lambda") || +// className.contains("Lambda") || +// methodName.contains("lambda$") || +// (className.contains("$") && (methodName.equals("apply") || methodName.equals("run"))); +// +// boolean isAllocation = false; +// Local allocatedLocal = null; // 类型改为 Local,方便后续处理 +// +// // 提取创建点对应的变量 +// if (point.getStmt() instanceof DefinitionStmt) { +// DefinitionStmt stmt = (DefinitionStmt) point.getStmt(); +// if (stmt.getRightOp() instanceof NewExpr || stmt.containsInvokeExpr()) { +// isAllocation = stmt.getRightOp() instanceof NewExpr; +// if (stmt.getLeftOp() instanceof Local) { +// allocatedLocal = (Local) stmt.getLeftOp(); +// } +// } +// } else if (point.getStmt() instanceof Stmt && ((Stmt)point.getStmt()).containsInvokeExpr()) { +// InvokeExpr expr = ((Stmt)point.getStmt()).getInvokeExpr(); +// if (expr instanceof InstanceInvokeExpr && expr.getMethod().getName().equals("")) { +// isAllocation = true; +// Value base = ((InstanceInvokeExpr) expr).getBase(); +// if (base instanceof Local) { +// allocatedLocal = (Local) base; +// } +// } +// } +// +// // 2. =========================== Lambda/Wrapper 过滤 (保持不变) =========================== +// if (isLambdaContext && !isAllocation) { +// continue; +// } +// +// // 3. =========================== [修改] 增强型逃逸分析 =========================== +// // 针对 ThreadPoolBuilder (返回创建对象) 和 ExecutorWrapper (被包装后流出) +// if (allocatedLocal != null) { +// // 使用新的 checkEscape 方法替代单纯的 isReturned +// if (checkEscape(method, allocatedLocal)) { +// continue; // 如果对象逃逸(被返回、被赋值给字段、被当作参数传递),则不在此处检查 NT +// } +// } +// +// // 4. =========================== Shutdown 匹配增强 (保持你的修改) =========================== +// Set keyPoints = new HashSet(); +// keyPoints.addAll(pointCollector.getShutDownNowPoints()); +// keyPoints.addAll(pointCollector.getShutDownPoints()); +// +// boolean terminate = false; +// for (KeyPoint terminatePoint : keyPoints) { +// // A. 原有的别名分析检查 +// if (point.isAliasCaller(terminatePoint)) { +// terminate = true; +// break; +// } +// +// // B. 同方法内的局部变量一致性检查 +// if (allocatedLocal != null) { +// if (isSameMethodAndVariable(point, allocatedLocal, terminatePoint)) { +// terminate = true; +// break; +// } +// } +// } +// +// if (!terminate) { +// ThreadErrorRecord.recordNT(component, point); +// } +// } Log.i(component, " # 3.1. Start NT.. "); for (InitPoint point : pointCollector.getInitialPoints()) { + + // 1. =========================== 语义过滤逻辑 (基础) =========================== + SootMethod method = point.getMethod(); + String methodName = method.getName(); + SootClass declaringClass = method.getDeclaringClass(); + String className = declaringClass.getName(); + + boolean isLambdaContext = + className.contains("lambda") || + className.contains("Lambda") || + methodName.contains("lambda$") || + (className.contains("$") && (methodName.equals("apply") || methodName.equals("run"))); + + boolean isAllocation = false; + Local allocatedLocal = null; + InvokeExpr originInvokeExpr = null; // [新增] 用于后续判断方法名 + + // 提取创建点对应的变量 + if (point.getStmt() instanceof DefinitionStmt) { + DefinitionStmt stmt = (DefinitionStmt) point.getStmt(); + if (stmt.getRightOp() instanceof NewExpr || stmt.containsInvokeExpr()) { + isAllocation = stmt.getRightOp() instanceof NewExpr; + if (stmt.getLeftOp() instanceof Local) { + allocatedLocal = (Local) stmt.getLeftOp(); + } + // [新增] 记录调用的表达式,用于后续分析来源 + if (stmt.containsInvokeExpr()) { + originInvokeExpr = stmt.getInvokeExpr(); + } + } + } else if (point.getStmt() instanceof Stmt && ((Stmt)point.getStmt()).containsInvokeExpr()) { + InvokeExpr expr = ((Stmt)point.getStmt()).getInvokeExpr(); + if (expr instanceof InstanceInvokeExpr && expr.getMethod().getName().equals("")) { + isAllocation = true; + Value base = ((InstanceInvokeExpr) expr).getBase(); + if (base instanceof Local) { + allocatedLocal = (Local) base; + } + } + } + + // 2. =========================== Lambda/Wrapper 过滤 =========================== + if (isLambdaContext && !isAllocation) { + continue; + } + + // 2.5. =========================== [新增/核心修复] 借用模式过滤 (Borrowing Logic) =========================== + // 目的:过滤掉 access$000, getExecutor, getCurrentContext 等非创建型获取 + // 逻辑:如果变量不是通过 new 创建的,且来源方法的命名暗示它是“借用”或“访问”,则跳过检测 + if (!isAllocation && originInvokeExpr != null) { + String invokedMethodName = originInvokeExpr.getMethod().getName(); + + // 1. 过滤合成方法 (Synthetic Accessor) - 解决 access$000 误报 + // 编译器生成的用于访问外部类私有字段的方法通常以 access$ 开头 + if (invokedMethodName.startsWith("access$")) { + continue; + } + + // 2. 过滤 Getter 类方法 - 解决 getExecutor, getCurrentContext 误报 + // 通常以 get, current, find, lookup 开头的方法返回的是已有对象的引用 + if (invokedMethodName.startsWith("get") || + invokedMethodName.startsWith("current") || + invokedMethodName.startsWith("find") || + invokedMethodName.startsWith("lookup") || + invokedMethodName.equals("executor")) { // 某些流畅风格直接用名词 + + // 特例排除:如果方法名包含 factory, new, create, build,即使以 get 开头也要小心(如 getNewInstance) + // 但对于 NT 检测,保守起见,getter 通常不需要在当前上下文关闭 + continue; + } + + // 3. 过滤单例模式 + if (invokedMethodName.equals("getInstance")) { + continue; + } + } + + // 3. =========================== 增强型逃逸分析 =========================== + // 针对 ThreadPoolBuilder (返回创建对象) 和 ExecutorWrapper (被包装后流出) + if (allocatedLocal != null) { + // 使用 checkEscape 方法替代单纯的 isReturned + if (checkEscape(method, allocatedLocal)) { + continue; // 如果对象逃逸(被返回、被赋值给字段、被当作参数传递),则不在此处检查 NT + } + } + + // 4. =========================== Shutdown 匹配增强 =========================== Set keyPoints = new HashSet(); keyPoints.addAll(pointCollector.getShutDownNowPoints()); keyPoints.addAll(pointCollector.getShutDownPoints()); + boolean terminate = false; for (KeyPoint terminatePoint : keyPoints) { + // A. 原有的别名分析检查 if (point.isAliasCaller(terminatePoint)) { terminate = true; break; } + + // B. 同方法内的局部变量一致性检查 + if (allocatedLocal != null) { + if (isSameMethodAndVariable(point, allocatedLocal, terminatePoint)) { + terminate = true; + break; + } + } } + if (!terminate) { ThreadErrorRecord.recordNT(component, point); } } + + + + + + + + + + Log.i(component, " # 4. Start IL.. "); for (KeyPoint point : pointCollector.getIsTerminatedPoints()) { @@ -129,52 +628,225 @@ public void run() { } } +// Log.i(component, " # 4. Start IL (Scope + Heuristic Name Check).. "); +// +// for (KeyPoint point : pointCollector.getIsTerminatedPoints()) { +// // 使用新的 ScopeILChecker,传入所有收集到的 shutdown 点 +// if (ScopeILChecker.isShutDownMisuse(pointCollector.getShutDownPoints(), point)) { +// ThreadErrorRecord.recordIL(component, point); +// } +// } + + Log.i(component, " # 5. Start CallerRunsChecker.. "); for (InitPoint point : pointCollector.getInitialPoints()) { +// 有setRejectedExecutionHandlerPoint if (CallerRunsChecker.hasMisuse(point, pointCollector.getSetRejectedExecutionHandlerPoints())) { ThreadErrorRecord.recordCallerRunsChecker(component, point); } } - Log.i(component, " # 6. Start ExceptionHandlerChecker.. "); + + + + + + + + + + + + + + +// Log.i(component, " # 6. Start ExceptionHandlerChecker.. "); +// for (InitPoint point : pointCollector.getInitialPoints()) { +//// 有设置线程工厂的point、有submitPoint、有setUncaughtExceptionHandlerPoint +// if (ExceptionHandlerChecker.hasMisuse(point, pointCollector.getSetThreadFactoryPoints(), +// pointCollector.getSubmitPoints(), pointCollector.getSetUncaughtExceptionHandlerPoints())) { +// ThreadErrorRecord.recordNonExceptionHandlerChecker(component, point); +// } +// } + Log.i(component, " # 6. Start ExceptionHandlerChecker (Scope Based).. "); for (InitPoint point : pointCollector.getInitialPoints()) { - if (ExceptionHandlerChecker.hasMisuse(point, pointCollector.getSetThreadFactoryPoints(), - pointCollector.getSubmitPoints(), pointCollector.getSetUncaughtExceptionHandlerPoints())) { + // 使用新的 ScopeChecker + // 逻辑:只要在当前类作用域内发现了“裸奔”的任务逻辑(无try-catch),且没有明显的Factory Handler,就报错 + boolean hasMisuse = ScopeExceptionHandlerChecker.hasMisuse( + point, + pointCollector.getSetThreadFactoryPoints(), + pointCollector.getSetUncaughtExceptionHandlerPoints() + ); + + if (hasMisuse) { ThreadErrorRecord.recordNonExceptionHandlerChecker(component, point); } } - Log.i(component, " # 7. Start RepeatedlyCreateThreadPool.. "); + + + + + + + + + + + + + + + Log.i(component, " # 7. Start RepeatedlyCreateThreadPool (Loop & Path Reachability).. "); + +// 用于策略1:统计每个方法内创建线程池的次数 + Map> methodCreationCounts = new HashMap<>(); + +// 遍历所有收集到的创建点 for (InitPoint point : pointCollector.getInitialPoints()) { - CallGraph callGraph = Scene.v().getCallGraph(); - Iterator edgesInto = callGraph.edgesInto(point.getMethod()); - int count = 0; - while (edgesInto.hasNext()) { - edgesInto.next(); - count++; - if (count > 1) { + SootMethod method = point.getMethod(); + + // --------------------------------------------------------- + // 策略 1 数据准备:将创建点按方法分组 + // --------------------------------------------------------- + if (!methodCreationCounts.containsKey(method)) { + methodCreationCounts.put(method, new ArrayList()); + } + methodCreationCounts.get(method).add(point); + + // --------------------------------------------------------- + // 策略 2:循环体检测 (Loop Detection) - 保持不变,非常重要 + // --------------------------------------------------------- + if (isStmtInLoop(method, point.getStmt())) { + Log.i(component, "RCTP found in Loop: " + method.getSignature()); + ThreadErrorRecord.recordRCTP(component, point); + } + } + +// --------------------------------------------------------- +// 策略 1 执行:同方法内【顺序执行】的多实例检测 +// 修改逻辑:只有当两个创建点之间存在“可达路径”时,才判定为 RCTP +// --------------------------------------------------------- + for (Map.Entry> entry : methodCreationCounts.entrySet()) { + List points = entry.getValue(); + + // 如果数量少于 2,肯定构不成重复创建,直接跳过 + if (points.size() < 2) continue; + + SootMethod method = entry.getKey(); + if (!method.hasActiveBody()) continue; + + // 构建控制流图 (CFG),用于判断语句之间的连通性 + BriefUnitGraph cfg = new BriefUnitGraph(method.getActiveBody()); + + // 用于记录真正确认误用的点,避免重复记录 + Set truePositivePoints = new HashSet<>(); + + // 两两比较所有创建点,检查是否存在 A -> B 的路径 + for (int i = 0; i < points.size(); i++) { + for (int j = 0; j < points.size(); j++) { + if (i == j) continue; // 不和自己比 + + InitPoint pointA = points.get(i); + InitPoint pointB = points.get(j); + + // 核心检测:如果在 CFG 中,从 A 可以走到 B + // 说明在一次运行中,可能会先创建 A,然后又创建 B -> 这才是 RCTP + if (isReachable(cfg, pointA.getStmt(), pointB.getStmt())) { + truePositivePoints.add(pointA); + truePositivePoints.add(pointB); + } + } + } + + // 记录确认的误用点 + if (!truePositivePoints.isEmpty()) { + Log.i(component, "RCTP found (Sequential creations): " + method.getSignature()); + for (InitPoint point : truePositivePoints) { ThreadErrorRecord.recordRCTP(component, point); - break; } + } else { + // 如果虽然有多个点,但彼此不可达(互斥分支),则认为是 Factory 模式,忽略 + Log.i(component, "Ignored Mutually Exclusive Creations (Factory Pattern): " + method.getSignature()); } } +// Log.i(component, " # 7. Start RepeatedlyCreateThreadPool.. "); +//// 简单的基于方法数量的检测 +// if (pointCollector.getInitialPoints().size() > 1) { +// Log.i(component, "Multiple thread pool creation methods found: " + +// pointCollector.getInitialPoints().size()); +// for (InitPoint point : pointCollector.getInitialPoints()) { +// ThreadErrorRecord.recordRCTP(component, point); +// } +// } + +//// 重复创建线程池 +// Log.i(component, " # 7. Start RepeatedlyCreateThreadPool.. "); +// for (InitPoint point : pointCollector.getInitialPoints()) { +// CallGraph callGraph = Scene.v().getCallGraph(); +// Iterator edgesInto = callGraph.edgesInto(point.getMethod()); +// int count = 0; +// while (edgesInto.hasNext()) { +// edgesInto.next(); +// count++; +// if (count > 1) { +// ThreadErrorRecord.recordRCTP(component, point); +// break; +// } +// } +// } + + + + + + + + + + + + + + + + + + + + + + +// Log.i(component, " # 8. Start UnrefactoredThreadLocal (UTL).. "); +// if (pointCollector instanceof PointCollectorExecutor) { +// for (InitPoint point : pointCollector.getInitialPoints()) { +// for (OneParaKeyPoint submitPoint : pointCollector.getSubmitPoints()) { +// if (!point.isAliasCaller(submitPoint)) { +// continue; +// } +// if (ThreadLocalChecker.hasThreadLocalMisuse(point, submitPoint)) { +// ThreadErrorRecord.recordUTL(component, point); +// break; +// } +// } +// } +// } Log.i(component, " # 8. Start UnrefactoredThreadLocal (UTL).. "); - if (pointCollector instanceof PointCollectorExecutor) { - for (InitPoint point : pointCollector.getInitialPoints()) { - for (OneParaKeyPoint submitPoint : pointCollector.getSubmitPoints()) { - if (!point.isAliasCaller(submitPoint)) { - continue; - } - if (ThreadLocalChecker.hasThreadLocalMisuse(point, submitPoint)) { - ThreadErrorRecord.recordUTL(component, point); - break; - } - } + // 注意:这里不再需要依赖 pointCollector instanceof PointCollectorExecutor + // 也不需要遍历 submitPoints,因为我们是基于 Scope 扫描 + for (InitPoint point : pointCollector.getInitialPoints()) { + // 直接传入 InitPoint,让 Checker 去扫描该 Point 所在的整个类环境 + if (ScopeThreadLocalChecker.hasThreadLocalMisuse(point)) { + ThreadErrorRecord.recordUTL(component, point); + // 如果同一个类里只需报一次错,可以在这里 break,否则去掉 break + // break; } } + +// Log.i(component, " # 9. Start UnboundedNumberOfThread (UBNT Core).. "); if (pointCollector instanceof PointCollectorExecutor) { for (InitPoint point : pointCollector.getInitialPoints()) { @@ -183,13 +855,12 @@ public void run() { continue; } if (IntMaxChecker.hasMaxIntegerSizeMisuse(setSizePoint)) { - ThreadErrorRecord.recordUBNT(component + "-coreSize-", point); + ThreadErrorRecord.recordUBNT(component , point); break; } } } } - Log.i(component, " # 10. Start UnboundedNumberOfThread (UBNT Max).. ", pointCollector.getSetMaxThreadSizePoints()); if (pointCollector instanceof PointCollectorExecutor) { for (InitPoint point : pointCollector.getInitialPoints()) { @@ -198,22 +869,35 @@ public void run() { continue; } if (IntMaxChecker.hasMaxIntegerSizeMisuse(setSizePoint)) { - ThreadErrorRecord.recordUBNT(component + "-MaxSize-", point); + ThreadErrorRecord.recordUBNT(component , point); break; } } } } - Log.i(component, " # 11. Start UnnamedThread (UNT) .. "); +// Log.i(component, " # 11. Start UnnamedThread (UNT) .. "); +// for (InitPoint point : pointCollector.getInitialPoints()) { +//// setThreadFactory传进去;和setName的地方传进去; +// if (SetThreadNameChecker.hasMisuse(point, pointCollector.getSetThreadFactoryPoints(), +// pointCollector.getSetNamePoints())) { +// ThreadErrorRecord.recordUNT(component, point); +// break; +// } +// } + + Log.i(component, " # 11. Start UnnamedThread (UNT) (Scope Based).. "); for (InitPoint point : pointCollector.getInitialPoints()) { - if (SetThreadNameChecker.hasMisuse(point, pointCollector.getSetThreadFactoryPoints(), - pointCollector.getSetNamePoints())) { +// 注意:这里不再传入 setFactoryPoints 和 setNamePoints,只传入 InitPoint + if (ScopeThreadNameChecker.hasMisuse(point)) { ThreadErrorRecord.recordUNT(component, point); break; } } + + + } } diff --git a/src/ac/pool/checker/RunMethod.java b/src/ac/pool/checker/RunMethod.java index bbf80f0..d089496 100644 --- a/src/ac/pool/checker/RunMethod.java +++ b/src/ac/pool/checker/RunMethod.java @@ -35,38 +35,55 @@ * @author Baoquan Cui * @version 1.0 */ -public class RunMethod extends MethodLoopAnalyzer{ +// 循环分析(loop analysis),专门用于分析多线程环境中的方法循环结构,特别是检查循环是否可以被正确取消。 +// 确保循环能够响应线程中断请求 +public class RunMethod extends MethodLoopAnalyzer{ +// 方法摘要缓存,避免重复分析相同的方法,相同的方法缓存一个就行。 +// key——方法的完整签名;value——对应的RunMethod分析结果。 protected static final Map doInBackgroundMethods = new HashMap<>(); - +// 存储所有可能导致循环取消的语句单元,包含 GotoStmt 和其他可能改变循环控制流的语句 +// 这些最后用于遍历判断这些语句单元unit,是否有取消语句 protected Set mCancelUnitList = new HashSet<>(); public RunMethod(SootMethod methodUnderAnalysis) { +// 传递要分析的SootMethod,初始化基础循环分析 super(methodUnderAnalysis); +// 启动完整的分析方法,生成循环摘要 generation(); } +//返回分析过程中发现的所有可能取消循环的语句单元 public Set getCancelledUnits() { return mCancelUnitList; } + +// 返回一个布尔值,判断是否所有的loop循环都有这个取消的机制。 public boolean isAllLoopCancelled() { +// 第一部分:处理取消单元(mCancelUnitList) +// 标记循环取消状态,检查方法中的所有循环是否都有适当的取消机制。 for (Unit unit : mCancelUnitList) { UnitInfo unitInfo = getUnitInfo(unit); - if (unit instanceof GotoStmt) { + if (unit instanceof GotoStmt) {//如果是go to的语句,也是取消机制的表现之一 GotoStmt gotoStmt = (GotoStmt) unit; - Unit targetUnit = gotoStmt.getTarget(); + Unit targetUnit = gotoStmt.getTarget(); // 得到跳转目标的那个unit UnitInfo targetInfo = getUnitInfo(targetUnit); - if (unitInfo.mLoopHeaderUnit != null) { + if (unitInfo.mLoopHeaderUnit != null) {//循环头不为空 UnitInfo headerUnitInfo = getUnitInfo(unitInfo.mLoopHeaderUnit); - headerUnitInfo.isCancelled = targetInfo.mLoopHeaderUnit != unitInfo.mLoopHeaderUnit; +// 判断本语句unitInfo和目标跳转语句targetInfo的循环头是否是一样的?、 +// 如果跳转目标属于不同的循环,说明当前循环可以被退出 + boolean isDifferent= (targetInfo.mLoopHeaderUnit != unitInfo.mLoopHeaderUnit); + headerUnitInfo.isCancelled =isDifferent; } } +// 如果取消单元本身就是循环头,直接标记为已取消 if (mLoopHeaderList.contains(unit)) { UnitInfo headerUnitInfo = getUnitInfo(unit); headerUnitInfo.isCancelled = true; } } +// 遍历所有循环头,检查是否都被标记为可取消,只要有一个循环不可取消,就返回false for (Unit unit : mLoopHeaderList) { UnitInfo unitInfo = getUnitInfo(unit); if (!unitInfo.isCancelled) { @@ -75,22 +92,48 @@ public boolean isAllLoopCancelled() { } return true; } - + + + + +// 整合被调用方法的循环分析结果; +// 收集Goto语句 - 识别潜在的循环退出点 protected void afterLoopAnalysis() { - for (Unit unit : mUnitGraph) { + for (Unit unit : mUnitGraph) {//遍历控制流图中的每一个语句单元unit. +// InvokeExpr theExpr = ((Stmt) unit).containsInvokeExpr() ? ((Stmt) unit).getInvokeExpr() : null; + InvokeExpr theExpr = null; +// if (unit instanceof Stmt) { +//// 获取unit的调用表达式 +// theExpr = ((Stmt) unit).getInvokeExpr(); +// } + // 修复:添加调用表达式检查 + if (unit instanceof Stmt) { + Stmt stmt = (Stmt) unit; + if (stmt.containsInvokeExpr()) { + theExpr = stmt.getInvokeExpr(); + } + } - InvokeExpr theExpr = ((Stmt) unit).containsInvokeExpr() ? ((Stmt) unit).getInvokeExpr() : null; // Process the summary of invoked method +// 处理当前方法中调用的其他方法,整合它们的循环分析结果 +// SootMethod method = theExpr.getMethod();是一个SootMethod类的实例对象。 if (theExpr != null && theExpr.getMethod().hasActiveBody()) { + //获得分析方法的方法签名 String key = theExpr.getMethod().getSignature(); + //通过该方法签名去map中获得对应的value RunMethod currentMethodSummary = doInBackgroundMethods.get(key); - if (currentMethodSummary != null) { - this.mLoopHeaderList.addAll(currentMethodSummary.mLoopHeaderList); - this.mCancelUnitList.addAll(currentMethodSummary.mCancelUnitList); + if (currentMethodSummary != null) {//找到了被调用方法的分析摘要,将其结果合并到当前方法中 + this.mLoopHeaderList.addAll(currentMethodSummary.mLoopHeaderList);//合并循环头信息 + this.mCancelUnitList.addAll(currentMethodSummary.mCancelUnitList);//合并取消单元信息 } } + // goto语句包括: + //break 语句 + //continue 语句 + //显式的 goto 标签跳转 + //任何改变控制流的无条件跳转 if (unit instanceof GotoStmt) { - mCancelUnitList.add(unit); + mCancelUnitList.add(unit);//将所有Goto语句添加到取消单元列表中 } } } diff --git a/src/ac/pool/checker/ScopeExceptionHandlerChecker.java b/src/ac/pool/checker/ScopeExceptionHandlerChecker.java new file mode 100644 index 0000000..7ccdd57 --- /dev/null +++ b/src/ac/pool/checker/ScopeExceptionHandlerChecker.java @@ -0,0 +1,126 @@ +package ac.pool.checker; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import ac.pool.point.InitPoint; +import ac.pool.point.KeyPoint; +import ac.pool.point.OneParaKeyPoint; +import soot.Body; +import soot.Scene; +import soot.SootClass; +import soot.SootMethod; +import soot.Trap; +import soot.Unit; +import soot.Value; +import soot.jimple.AssignStmt; +import soot.jimple.DivExpr; +import soot.jimple.RemExpr; +import soot.jimple.ThrowStmt; + +// 修改后的检查器:只检测未被捕获的“危险指令”(如除法、显式throw) +public class ScopeExceptionHandlerChecker { + + public static boolean hasMisuse(InitPoint initPoint, + Set setFactoryPoints, + Set setUncaughtExceptionHandlerPoints) { + + // 1. 如果全局设置了 UncaughtExceptionHandler,通常认为已兜底(根据你的需求,可保留或注释掉此行) + // if (hasFactoryHandler(...) || hasGlobalHandler(...)) return false; + // 这里为了专注检测你的除0用例,我们先假设没有全局handler,直接检查局部。 + + SootClass hostClass = initPoint.getMethod().getDeclaringClass(); + List classesToScan = getRelatedClasses(hostClass); + + for (SootClass sc : classesToScan) { + if (sc.isLibraryClass() || sc.isJavaLibraryClass()) continue; + + for (SootMethod method : new ArrayList<>(sc.getMethods())) { + // 仅扫描有方法体且像任务的方法 + if (method.hasActiveBody() && isTaskMethod(method)) { + // 2. 深入检查方法内部指令 + if (hasUnhandledRiskyInstruction(method)) { + return true; + } + } + } + } + return false; + } + + // 核心逻辑:检查是否存在“裸奔”的危险指令 + private static boolean hasUnhandledRiskyInstruction(SootMethod method) { + Body body = method.getActiveBody(); + + for (Unit unit : body.getUnits()) { + // 1. 判断当前指令是否是危险的(可能抛出异常) + if (isRiskyUnit(unit)) { + // 2. 如果是危险指令,检查它是否被 try-catch 保护 + if (!isProtectedByTrap(body, unit)) { + // 发现未捕获的危险指令(例如 10/0) + return true; + } + } + } + return false; + } + + // 判断指令是否有风险 + private static boolean isRiskyUnit(Unit unit) { + // 情况A: 赋值语句中包含除法或取模 (对应 10/0) + if (unit instanceof AssignStmt) { + Value rightOp = ((AssignStmt) unit).getRightOp(); + // DivExpr 对应除法 (/), RemExpr 对应取模 (%) + // 只要有除法,就有除0风险 (ArithmeticException) + if (rightOp instanceof DivExpr || rightOp instanceof RemExpr) { + return true; + } + } + +// // 情况B: 显式的 throw 语句 +// if (unit instanceof ThrowStmt) { +// return true; +// } + + // 情况C (可选): 调用了可能抛出异常的方法 + // if (unit.containsInvokeExpr()) { ... } + // 为了精准定位你的测试用例,这里先暂时不加,避免误报太多 + + return false; + } + + // 检查某个 Unit 是否在任何一个 Trap (try-catch) 的保护范围内 + private static boolean isProtectedByTrap(Body body, Unit unit) { + for (Trap trap : body.getTraps()) { + // Trap 的范围是 [beginUnit, endUnit) + // 我们需要判断 unit 是否在这个链条区间内 + Unit current = trap.getBeginUnit(); + while (current != null && current != trap.getEndUnit()) { + if (current == unit) { + return true; // 找到了,该指令被这个 trap 保护 + } + current = body.getUnits().getSuccOf(current); + } + } + return false; + } + + private static boolean isTaskMethod(SootMethod method) { + String name = method.getName(); + // 匹配 run, call, 或者 lambda 生成的方法 + return name.equals("run") || name.equals("call") || name.startsWith("lambda$"); + } + + private static List getRelatedClasses(SootClass hostClass) { + List list = new ArrayList<>(); + list.add(hostClass); + String hostName = hostClass.getName(); + for (SootClass sc : Scene.v().getApplicationClasses()) { + if (sc.getName().startsWith(hostName + "$")) { + list.add(sc); + } + } + return list; + } +} \ No newline at end of file diff --git a/src/ac/pool/checker/ScopeILChecker.java b/src/ac/pool/checker/ScopeILChecker.java new file mode 100644 index 0000000..bace0b6 --- /dev/null +++ b/src/ac/pool/checker/ScopeILChecker.java @@ -0,0 +1,150 @@ +package ac.pool.checker; + +import ac.pool.point.KeyPoint; +import soot.*; +import soot.jimple.*; +// 修正 Import:Loop 类位于 annotation.logic 包中 +import soot.jimple.toolkits.annotation.logic.Loop; +import soot.toolkits.graph.LoopNestTree; + +import java.util.HashSet; +import java.util.Set; + +/** + * ScopeILChecker (Final Corrected) + * 修正了 Loop 类的引用错误。 + */ +public class ScopeILChecker { + + /** + * 检测是否存在 IL (Infinite Loop) 误用 + * @return true = 存在误用 (Bug); false = 安全 (Safe) + */ + public static boolean isShutDownMisuse(Set globalShutdowns, KeyPoint isShutDownPoint) { + + // Step 1: 必须先确定 isShutdown 真的在循环里 + if (!isInLoop(isShutDownPoint)) { + return false; // 不在循环里,直接跳过 + } + + // Step 2: 准备匹配信息 (Name & Class) + // 回溯 Lambda 中的变量来源 ($r1 -> val$tpe) + String checkVarName = getTraceableVariableName(isShutDownPoint); + if (checkVarName == null) return true; // 无法追踪,保守报错 + + SootClass checkClass = isShutDownPoint.getMethod().getDeclaringClass(); + + // Step 3: 在全局 shutdown 集合中寻找“解药” + for (KeyPoint shutDownPoint : globalShutdowns) { + SootClass shutdownClass = shutDownPoint.getMethod().getDeclaringClass(); + + // A. 作用域亲和性检查 (Inner <-> Outer) + if (!isScopeRelated(checkClass, shutdownClass)) { + continue; + } + + // B. 变量名模糊匹配 + String closeVarName = getTraceableVariableName(shutDownPoint); + + // 只要名字存在包含关系 (val$tpe vs tpe),就认为是同一个对象 + if (isNameFuzzyMatch(checkVarName, closeVarName)) { + return false; // 找到了匹配的关闭操作 -> 安全 + } + } + + // Step 4: 循环结束仍未找到匹配 -> 确认为误用 + return true; + } + + // ========================================================================= + // 关键修复:使用正确的 Loop 类路径进行循环检测 + // ========================================================================= + + private static boolean isInLoop(KeyPoint point) { + SootMethod method = point.getMethod(); + if (!method.hasActiveBody()) return false; + + Body body = method.getActiveBody(); + Stmt targetStmt = point.getStmt(); + + // 直接通过 Body 构建 LoopNestTree,Soot 会自动处理控制流图 + LoopNestTree loopNestTree = new LoopNestTree(body); + + // 检查 targetStmt 是否属于任何一个循环 + for (Loop loop : loopNestTree) { + // getLoopStatements 返回循环内的所有语句 + if (loop.getLoopStatements().contains(targetStmt)) { + return true; + } + } + return false; + } + + // ========================================================================= + // 变量回溯与匹配逻辑 + // ========================================================================= + + private static boolean isScopeRelated(SootClass c1, SootClass c2) { + if (c1.equals(c2)) return true; + if (c1.hasOuterClass() && c1.getOuterClass().equals(c2)) return true; + if (c2.hasOuterClass() && c2.getOuterClass().equals(c1)) return true; + return false; + } + + private static boolean isNameFuzzyMatch(String name1, String name2) { + if (name1 == null || name2 == null) return false; + String n1 = simplify(name1); + String n2 = simplify(name2); + return n1.contains(n2) || n2.contains(n1); + } + + private static String simplify(String name) { + return name.replace("val$", "") + .replace("access$", "") + .replace("this.", "") + .toLowerCase().trim(); + } + + private static String getTraceableVariableName(KeyPoint point) { + Unit unit = point.getStmt(); + SootMethod method = point.getMethod(); + Value baseValue = null; + + if (unit instanceof Stmt) { + Stmt stmt = (Stmt) unit; + if (stmt.containsInvokeExpr()) { + if (stmt.getInvokeExpr() instanceof InstanceInvokeExpr) { + baseValue = ((InstanceInvokeExpr) stmt.getInvokeExpr()).getBase(); + } + } + } + + if (baseValue == null) return null; + + if (baseValue instanceof Local) { + return traceOrigin(method, (Local) baseValue, new HashSet<>()); + } + return baseValue.toString(); + } + + private static String traceOrigin(SootMethod method, Local local, Set visited) { + if (!method.hasActiveBody() || visited.contains(local)) return local.toString(); + visited.add(local); + + for (Unit u : method.getActiveBody().getUnits()) { + if (u instanceof DefinitionStmt) { + DefinitionStmt def = (DefinitionStmt) u; + if (def.getLeftOp().equals(local)) { + Value right = def.getRightOp(); + if (right instanceof FieldRef) { + return ((FieldRef) right).getField().getName(); + } + if (right instanceof Local) { + return traceOrigin(method, (Local) right, visited); + } + } + } + } + return local.toString(); + } +} \ No newline at end of file diff --git a/src/ac/pool/checker/ScopeThreadLocalChecker.java b/src/ac/pool/checker/ScopeThreadLocalChecker.java new file mode 100644 index 0000000..5b69a54 --- /dev/null +++ b/src/ac/pool/checker/ScopeThreadLocalChecker.java @@ -0,0 +1,144 @@ +package ac.pool.checker; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import ac.pool.point.InitPoint; +import soot.Body; +import soot.Scene; +import soot.SootClass; +import soot.SootMethod; +import soot.Unit; +import soot.Value; +import soot.jimple.InstanceInvokeExpr; +import soot.jimple.NullConstant; +import soot.jimple.Stmt; + +// 新思路:基于作用域的ThreadLocal检查器 +// 逻辑:检查定义了线程池的类(及其内部类/Lambda)中,是否存在只set不remove的情况 +public class ScopeThreadLocalChecker { + + // [新增] 白名单包/类前缀 + // 这些框架通常使用 try-finally 或拦截器模式跨方法管理 ThreadLocal,不适用单方法检查 + private static final List WHITELIST_PREFIXES = Arrays.asList( + "org.eclipse.jetty.", // Jetty 服务器核心 + "org.springframework.", // Spring 框架 + "org.apache.tomcat.", // Tomcat 容器 + "io.netty.", // Netty 网络框架 + "java.util.logging." // Java 日志 + ); + + public static boolean hasThreadLocalMisuse(InitPoint point) { + // 1. 获取初始化线程池的方法所属的类 + SootClass hostClass = point.getMethod().getDeclaringClass(); + + // [新增] 0. 白名单检查:如果宿主类属于已知框架,直接跳过 + if (isWhitelisted(hostClass)) { + return false; + } + + // 2. 收集需要扫描的所有相关类(宿主类 + 内部类/Lambda类) + List classesToScan = getRelatedClasses(hostClass); + + // 3. 遍历这些类中的所有方法 + for (SootClass sc : classesToScan) { + // 避免扫描库类,只扫应用类 + if (sc.isLibraryClass() || sc.isJavaLibraryClass()) continue; + + // [新增] 再次确保扫描的内部类也不在白名单中 + if (isWhitelisted(sc)) continue; + + for (SootMethod method : new ArrayList<>(sc.getMethods())) { + // 如果方法有方法体,进行检查 + if (method.hasActiveBody()) { + // [新增] 跳过构造函数和静态初始化块 + // ThreadLocal 通常在业务方法中 set/remove,在构造中 set 通常是初始化行为 + if (method.isConstructor() || method.isStaticInitializer()) { + continue; + } + + if (hasSetButNoRemove(method)) { + // 只要发现一个方法有问题,就报告 + return true; + } + } + } + } + return false; + } + + // [新增] 检查类是否在白名单中 + private static boolean isWhitelisted(SootClass sc) { + String name = sc.getName(); + for (String prefix : WHITELIST_PREFIXES) { + if (name.startsWith(prefix)) { + return true; + } + } + return false; + } + + // 收集宿主类及其所有的内部类(包括匿名内部类和Lambda生成的类) + private static List getRelatedClasses(SootClass hostClass) { + List list = new ArrayList<>(); + list.add(hostClass); + + String hostName = hostClass.getName(); + + // 遍历所有应用类,寻找名字包含 hostName$ 的类 + for (SootClass sc : Scene.v().getApplicationClasses()) { + if (sc.getName().startsWith(hostName + "$")) { + list.add(sc); + } + } + return list; + } + + // 检查一个方法体内是否 set 了 ThreadLocal 却没 remove + private static boolean hasSetButNoRemove(SootMethod method) { + boolean hasSet = false; + boolean hasRemove = false; + + // [新增] 强制获取 Body,增加健壮性 + if (!method.hasActiveBody()) { + try { + method.retrieveActiveBody(); + } catch (Exception e) { + return false; + } + } + + Body body = method.getActiveBody(); + for (Unit unit : body.getUnits()) { + if (unit instanceof Stmt) { + Stmt stmt = (Stmt) unit; + if (stmt.containsInvokeExpr()) { + SootMethod invokedMethod = stmt.getInvokeExpr().getMethod(); + String methodSig = invokedMethod.getSignature(); + + // 1. 检查 ThreadLocal.set + if (methodSig.contains("java.lang.ThreadLocal: void set(java.lang.Object)")) { + hasSet = true; + + // [新增] 检查 set(null) 的情况 + // set(null) 在语义上等同于 remove,很多代码会用 set(null) 来清理 + if (stmt.getInvokeExpr() instanceof InstanceInvokeExpr) { + Value arg = stmt.getInvokeExpr().getArg(0); + if (arg instanceof NullConstant) { + hasRemove = true; // 视为已清理 + } + } + } + + // 2. 检查 ThreadLocal.remove + if (methodSig.contains("java.lang.ThreadLocal: void remove()")) { + hasRemove = true; + } + } + } + } + + // 只有 set 没有 remove,判定为误用 + return hasSet && !hasRemove; + } +} \ No newline at end of file diff --git a/src/ac/pool/checker/ScopeThreadNameChecker.java b/src/ac/pool/checker/ScopeThreadNameChecker.java new file mode 100644 index 0000000..d768f42 --- /dev/null +++ b/src/ac/pool/checker/ScopeThreadNameChecker.java @@ -0,0 +1,108 @@ +package ac.pool.checker; + +import java.util.ArrayList; +import java.util.List; +import ac.pool.point.InitPoint; +import soot.Body; +import soot.Scene; +import soot.SootClass; +import soot.SootMethod; +import soot.Unit; +import soot.jimple.Stmt; + +// 线程命名检查器 (Scope-Based) +public class ScopeThreadNameChecker { + + // 线程.setName() 方法的签名 + private static final String THREAD_SET_NAME_SIG = ""; + // ThreadFactory.newThread() 方法的子签名 + private static final String THREAD_FACTORY_NEW_THREAD_SUBSIG = "java.lang.Thread newThread(java.lang.Runnable)"; + + public static boolean hasMisuse(InitPoint initPoint) { + SootClass hostClass = initPoint.getMethod().getDeclaringClass(); + List classesToScan = getRelatedClasses(hostClass); + + // 1. 检查是否存在命名线程工厂(ThreadFactory) + if (isNamingThreadFactoryDefined(classesToScan)) { + return false; // 找到了命名工厂,认为命名机制存在 + } + + // 2. 检查是否存在任务内部的命名调用 (Thread.currentThread().setName()) + if (isNamingDoneInTask(classesToScan)) { + return false; // 找到了任务内的命名调用,认为命名机制存在 + } + + // 如果两种命名机制都不存在,则判定为 UNT 误用 + return true; + } + + // 检查作用域内是否有实现 ThreadFactory 且在 newThread 中调用了 setName 的类 + private static boolean isNamingThreadFactoryDefined(List classesToScan) { + SootClass threadFactoryClass = Scene.v().getSootClassUnsafe("java.util.concurrent.ThreadFactory"); + if (threadFactoryClass == null) return false; + + for (SootClass sc : classesToScan) { + // 检查当前类是否实现了 ThreadFactory 接口 + if (sc.implementsInterface(String.valueOf(threadFactoryClass))) { + SootMethod newThreadMethod = sc.getMethodUnsafe(THREAD_FACTORY_NEW_THREAD_SUBSIG); + + if (newThreadMethod != null && newThreadMethod.hasActiveBody()) { + // 检查 newThread 方法体中是否有 setName 调用 + if (methodContainsSetName(newThreadMethod)) { + return true; + } + } + } + } + return false; + } + + // 检查作用域内的任务方法(run/call/lambda)中是否有 setName 调用 + private static boolean isNamingDoneInTask(List classesToScan) { + for (SootClass sc : classesToScan) { + for (SootMethod method : new ArrayList<>(sc.getMethods())) { + String name = method.getName(); + // 检查任务型方法 (run/call/lambda$) + if ((name.equals("run") || name.equals("call") || name.startsWith("lambda$")) + && method.hasActiveBody()) { + + if (methodContainsSetName(method)) { + return true; + } + } + } + } + return false; + } + + // 检查方法体内是否包含 Thread.setName() 的调用 + private static boolean methodContainsSetName(SootMethod method) { + if (!method.hasActiveBody()) return false; + Body body = method.getActiveBody(); + for (Unit unit : body.getUnits()) { + if (unit instanceof Stmt) { + Stmt stmt = (Stmt) unit; + if (stmt.containsInvokeExpr()) { + if (stmt.getInvokeExpr().getMethod().getSignature().equals(THREAD_SET_NAME_SIG)) { + return true; + } + } + } + } + return false; + } + + // 收集宿主类及其所有的内部类(包括匿名内部类和Lambda生成的类) + private static List getRelatedClasses(SootClass hostClass) { + List list = new ArrayList<>(); + list.add(hostClass); + String hostName = hostClass.getName(); + + for (SootClass sc : Scene.v().getApplicationClasses()) { + if (sc.getName().startsWith(hostName + "$")) { + list.add(sc); + } + } + return list; + } +} \ No newline at end of file diff --git a/src/ac/pool/checker/SetThreadNameChecker.java b/src/ac/pool/checker/SetThreadNameChecker.java index bba6f5c..181b7e7 100644 --- a/src/ac/pool/checker/SetThreadNameChecker.java +++ b/src/ac/pool/checker/SetThreadNameChecker.java @@ -13,19 +13,28 @@ import soot.Unit; import soot.jimple.ReturnStmt; +//线程命名检查器,专门用于检测线程池中是否正确地设置了线程名称。 public class SetThreadNameChecker { +// initPoint: 线程池的初始化点 +// setFactoryPoints: 设置ThreadFactory的调用点集合 +// setNameKeyPoints: 设置线程名称的调用点集合(如thread.setName()) public static boolean hasMisuse(InitPoint initPoint, Set setFactoryPoints, Set setNameKeyPoints) { +// 先对setName方法调用点的判断; for (KeyPoint setNameKeyPoint : setNameKeyPoints) { +// 说明,setName的对象和initPoint的对象是同一个对象; if (setNameKeyPoint.isAliasCaller(initPoint)) { return false; } } +// ThreadFactory线程命名检查, 遍历所有的ThreadFactory for (OneParaKeyPoint setFactoryPoint : setFactoryPoints) { if (setFactoryPoint.isAliasCaller(initPoint)) { +// 对于相关的ThreadFactory,获取其创建的核心线程 Set coreThreads = getCoreThreadsFromThreadFactory(setFactoryPoint.getParaLocal()); for (Local coreThread : coreThreads) { +// 检查这些线程是否被设置了名称,如果找到任何命名调用,返回false for (KeyPoint setNameKeyPoint : setNameKeyPoints) { if (setNameKeyPoint.isAliasCaller(coreThread)) { return false; @@ -34,20 +43,27 @@ public static boolean hasMisuse(InitPoint initPoint, Set setFac } } } +// 如果以上两种均为找到别名调用,则说明存在 线程名称误用。 return true; } + +// 获取ThreadFactory创建的线程 private static Set getCoreThreadsFromThreadFactory(Local threadFactoryLocal) { Set locals = new HashSet<>(); +// 使用指针分析(PTA)获取ThreadFactory局部变量可能指向的对象类型 for (Type taskPossiableType : KeyPoint.pta.reachingObjects(threadFactoryLocal).possibleTypes()) { - if(taskPossiableType instanceof RefType) { + if(taskPossiableType instanceof RefType) {//如果是引用类型 RefType refType = (RefType) taskPossiableType; +// 查找ThreadFactory的newThread方法,就是创建Thread的方法。 SootMethod newThreadMethod = refType.getSootClass().getMethodUnsafe(ThreadSig.METHOD_SUBSIG_NEWTHREAD); +// 遍历newThread方法体中的所有语句,查找返回语句(ReturnStmt,提取返回的线程局部变量 if (newThreadMethod != null && newThreadMethod.hasActiveBody()) { for (Unit unit : newThreadMethod.getActiveBody().getUnits()) { - if (unit instanceof ReturnStmt) { + if (unit instanceof ReturnStmt) {//查找返回语句,ReturnStmt ReturnStmt returnStmt = (ReturnStmt) unit; if (returnStmt.getOp() instanceof Local) { +// 提取返回的线程局部变量 locals.add((Local) returnStmt.getOp()); } } @@ -57,5 +73,4 @@ private static Set getCoreThreadsFromThreadFactory(Local threadFactoryLoc } return locals; } - } diff --git a/src/ac/pool/checker/ThreadLocalChecker.java b/src/ac/pool/checker/ThreadLocalChecker.java index 4e9abae..7166100 100644 --- a/src/ac/pool/checker/ThreadLocalChecker.java +++ b/src/ac/pool/checker/ThreadLocalChecker.java @@ -8,12 +8,16 @@ import soot.Unit; import soot.jimple.Stmt; +//ThreadLocal误用检查器,专门用于检测在线程池环境中不当使用ThreadLocal的问题。 public class ThreadLocalChecker { - + // 检查在特定的线程池初始化点和任务提交点之间,是否存在ThreadLocal的误用。 public static boolean hasThreadLocalMisuse(InitPoint point, OneParaKeyPoint submitPoint) { +// 获取提交点参数(任务对象)的所有可能类型 for (Type type : submitPoint.getParaLocalPossiableTypes()) { - RefType refType = (RefType) type; + RefType refType = (RefType) type;//转为引用类型 +// 根据引用类型,获取后台执行方法 SootMethod backgroundMethod = ExceptionHandlerChecker.getBackgroundMethod(refType); + //对每个后台方法进行分析,是否有对ThreadPool类的操作。 if (hasThreadLocalInThreadPool(backgroundMethod)) { return true; } @@ -22,11 +26,12 @@ public static boolean hasThreadLocalMisuse(InitPoint point, OneParaKeyPoint sub } private static boolean hasThreadLocalInThreadPool(SootMethod sootMethod) { - if (sootMethod != null && sootMethod.hasActiveBody()) { - for (Unit unit : sootMethod.getActiveBody().getUnits()) { + if (sootMethod != null && sootMethod.hasActiveBody()) {//方法不为null并且方法有活跃体 + for (Unit unit : sootMethod.getActiveBody().getUnits()) {//对于每一条语句进行分析 if (unit instanceof Stmt) { Stmt stmt = (Stmt) unit; - if (stmt.containsInvokeExpr()) { + if (stmt.containsInvokeExpr()) {//stmt.containsInvokeExpr()表示包含方法调用语句。 +// 检查指定的方法中是否包含对ThreadLocal类的任何方法调用。 if ("java.lang.ThreadLocal" .equals(stmt.getInvokeExpr().getMethod().getDeclaringClass().toString())) { return true; diff --git a/src/ac/pool/point/DestroyPoint.java b/src/ac/pool/point/DestroyPoint.java index fbddf4b..2be6724 100644 --- a/src/ac/pool/point/DestroyPoint.java +++ b/src/ac/pool/point/DestroyPoint.java @@ -1,24 +1,28 @@ package ac.pool.point; - import soot.SootField; import soot.SootMethod; import soot.jimple.Stmt; +//销毁关键点扩展类 +//表示和处理与对象销毁相关的关键操作,特别是与HTR(Hard to Release)字段相关的销毁点。 +//表示异步组件的销毁操作点,并关联到特定的 HTR 字段 + public class DestroyPoint extends KeyPoint { - SootField htrField = null; +// Soot字段类 + SootField htrField = null;// 关联的可销毁字段 public SootField getHTRField() { return htrField; } public static DestroyPoint newDestroyPoint(SootMethod method, Stmt stmt, SootField htrField) { - DestroyPoint point = new DestroyPoint(); point.method = method; point.stmt = stmt; +// 核心:设置关联字段,调用这个方法的时候,要将相关联的字段赋值给调用实例的域。 point.htrField = htrField; +// 设置——调用该销毁方法的调用者。 point.setCaller(getBaseCaller(stmt)); return point; - } } \ No newline at end of file diff --git a/src/ac/pool/point/InitPoint.java b/src/ac/pool/point/InitPoint.java index f88a545..d395c36 100644 --- a/src/ac/pool/point/InitPoint.java +++ b/src/ac/pool/point/InitPoint.java @@ -8,16 +8,23 @@ import soot.jimple.DefinitionStmt; import soot.jimple.Stmt; +//初始化init关键点扩展类 +// public class InitPoint extends KeyPoint{ - - Set taskLocals = new HashSet<>(); - - Set coreThreadLocals = new HashSet<>(); - +// 追踪在初始化过程中创建或关联的 任务对象 + Set taskLocals = new HashSet<>();// 与初始化相关的任务对象集合 +// 追踪初始化过程中涉及的 核心线程对象 + Set coreThreadLocals = new HashSet<>();// 与初始化相关的核心线程对象集合 + + +// 向初始化点添加一个任务局部变量。 +// 建立初始化对象与任务对象的关联关系 public void addTask(Local taskLocal) { taskLocals.add(taskLocal); } - + +// 向初始化点Init——point添加一个核心线程局部变量。 +// 追踪线程池的核心线程配置 public void addCoreThread(Local coreThreadLocal) { coreThreadLocals.add(coreThreadLocal); } @@ -29,12 +36,18 @@ public Set getCoreThreadLocals() { public Set getTaskLocals() { return taskLocals; } - + + public static InitPoint newInitPoint(SootMethod method, Stmt stmt) { +// 创建基础的关键点对象,初始化KeyPoint的三个基本字段信息,method、stmt、caller InitPoint point = new InitPoint(); point.method = method; point.stmt = stmt; +// 实例方法调用的话,有明确的调用者,直接getBaseCaller(stmt)即可。 +// 这里也可能返回null,因为不是可能不是实例调用。 point.setCaller(getBaseCaller(stmt)); +// 当 getBaseCaller() 返回 null(静态方法调用)且语句是赋值语句 DefinitionStm +// 将赋值语句的左值(接收返回值的变量)作为调用者 if(point.getCaller() == null && stmt instanceof DefinitionStmt) { // r0 = Executors.newxxx() DefinitionStmt definitionStmt = (DefinitionStmt) stmt; point.setCaller(definitionStmt.getLeftOp()); diff --git a/src/ac/pool/point/KeyPoint.java b/src/ac/pool/point/KeyPoint.java index d9e2982..8188eeb 100644 --- a/src/ac/pool/point/KeyPoint.java +++ b/src/ac/pool/point/KeyPoint.java @@ -15,32 +15,38 @@ import soot.jimple.InvokeExpr; import soot.jimple.Stmt; import soot.jimple.spark.sets.EmptyPointsToSet; +import soot.jimple.toolkits.pointer.FullObjectSet; -public class KeyPoint { +//作为所有关键点类型的基类,提供了统一的方法调用点caller分析框架 +//特别是对调用对象(caller)的指针分析和类型推断。 +//为所有类型的关键点提供共同的分析接口 +public class KeyPoint { + // 指针分析器 public static final PointsToAnalysis pta = Scene.v().getPointsToAnalysis(); + SootMethod method = null;// 关键点所在的方法 + Stmt stmt = null; // 关键点语句 + private Value caller = null;// 方法调用的对象(如 thread.start() 中的 thread) - SootMethod method = null; - Stmt stmt = null; - private Value caller = null; +// 获取调用表达式中的第 index 个参数值。 +// 返回的是lambda创建的匿名类实例的Local引用 protected Value getParameter(int index) { - return stmt.getInvokeExpr().getArg(index); } - +// 对局部变量进行指针分析,获取该变量可能指向的对象集合。 protected PointsToSet reachingObjects(Value value) { if(value instanceof Local) { return pta.reachingObjects((Local) value); } return EmptyPointsToSet.v(); - } - + +// 获取调用者(caller)可能指向的对象集合。 public PointsToSet getCallerPointsToSet() { return reachingObjects(getCaller()); } - +// 分析调用者(caller)可能的具体类型(RefType是class或者interface),返回 RefType 集合。 public Set getCallerPossibleType() { PointsToSet pts = getCallerPointsToSet(); Set set = new HashSet(); @@ -51,35 +57,42 @@ public Set getCallerPossibleType() { } return set ; } - +// 检查两个关键点的调用者是否存在别名关系(是否可能指向同一个对象)。 +// 一个参数是局部变量类型;一个参数是KeyPoint类型; +// 这个别名分析,精度太低了。 + +// 由于指针分析失效,r0 和 r2 都被认为是同一个 FullObjectSet +// 所以 hasNonEmptyIntersection 总是返回 true,导致检测器认为它们操作的是同一个对象。 public boolean isAliasCaller(KeyPoint otherKeyPoint) { - return getCallerPointsToSet().hasNonEmptyIntersection(otherKeyPoint.getCallerPointsToSet()); - } - - public boolean isAliasCaller(Local local) { - return getCallerPointsToSet().hasNonEmptyIntersection(pta.reachingObjects(local)); - } +// 如果指针分析失效,回退到基于变量名的简单分析 + PointsToSet thisPTS = getCallerPointsToSet(); + PointsToSet otherPTS = otherKeyPoint.getCallerPointsToSet(); - public SootMethod getMethod() { - return method; - } - - public void setMethod(SootMethod method) { - this.method = method; +// 检查是否是 FullObjectSet(指针分析失效的情况) + if (thisPTS instanceof FullObjectSet || otherPTS instanceof FullObjectSet) { + // 回退到基于变量名的分析 + Value thisCaller = this.getCaller(); + Value otherCaller = otherKeyPoint.getCaller(); + if (thisCaller instanceof Local && otherCaller instanceof Local) { + Local thisLocal = (Local) thisCaller; + Local otherLocal = (Local) otherCaller; + // 如果变量名相同,认为是同一个对象 + return thisLocal.getName().equals(otherLocal.getName()); + } + return false; // 保守策略:不同变量名认为不是别名 + } + //检查两个指针是否可能指向相同的对象 + boolean result = thisPTS.hasNonEmptyIntersection(otherPTS); + return result; } + - public void setStmt(Stmt stmt) { - this.stmt = stmt; + public boolean isAliasCaller(Local local) {//这个方法也许是导致误报的情况之一的原因,要后面结合Check详细分析; + return getCallerPointsToSet().hasNonEmptyIntersection(pta.reachingObjects(local)); } - public Value getCaller() { - return caller; - } - public Stmt getStmt() { - return stmt; - } - +// 创建关键点对象、提取调用者基础对象。 public static KeyPoint newPoint(SootMethod method, Stmt stmt) { KeyPoint keyPoint = new KeyPoint(); keyPoint.method = method; @@ -100,6 +113,30 @@ public static Value getBaseCaller(InvokeExpr invokeExpr) { return null; } + + + + + + + public SootMethod getMethod() { + return method; + } + + public void setMethod(SootMethod method) { + this.method = method; + } + + public void setStmt(Stmt stmt) { + this.stmt = stmt; + } + public Stmt getStmt() { + return stmt; + } + public Value getCaller() { + return caller; + } + public void setCaller(Value caller) { this.caller = caller; } diff --git a/src/ac/pool/point/OneParaKeyPoint.java b/src/ac/pool/point/OneParaKeyPoint.java index fed2dd8..9ef9679 100644 --- a/src/ac/pool/point/OneParaKeyPoint.java +++ b/src/ac/pool/point/OneParaKeyPoint.java @@ -10,9 +10,11 @@ import soot.Type; import soot.jimple.Stmt; +//KeyPoint的单参数关键点扩展类,专门用于表示和处理带有一个重要参数的关键调用点。 + public class OneParaKeyPoint extends KeyPoint{ - Local paraLocal = null; + Local paraLocal = null; // 关键参数对应的局部变量 public void setParaLocal(Local paraLocal) { this.paraLocal = paraLocal; @@ -21,10 +23,16 @@ public void setParaLocal(Local paraLocal) { public Local getParaLocal() { return paraLocal; } - + + +// 分析关键参数可能指向的对象类型集合 public Set getParaLocalPossiableTypes() { +// 返回 paraLocal 变量可能指向的所有对象集合 PointsToSet pts = pta.reachingObjects(paraLocal); Set set = new HashSet(); +// 类型提取和过滤: +// possibleTypes() 获取对象集合中所有可能的类型 +// 只保留 RefType(引用类型),过滤掉基本类型和数组类型 for(Type type: pts.possibleTypes()) { if(type instanceof RefType) { set.add((RefType) type); @@ -32,19 +40,25 @@ public Set getParaLocalPossiableTypes() { } return set; } - + +// 创建单参数关键点对象,并提取指定索引的参数——>关键参数。 +// 为什么这个方法,必须将指定索引的参数转为Local类型的关键参数? +// Local 变量:可以在方法体内进行数据流分析,追踪其赋值来源 +// 非Local变量:难以进行精确的指针分析和数据流追踪 + +// index一般是0 public static OneParaKeyPoint newOneParaKeyPoint(SootMethod method, Stmt stmt, int index) { +// 基础对象创建和设置 OneParaKeyPoint point = new OneParaKeyPoint(); - point.method = method; - point.stmt = stmt; - point.setCaller(getBaseCaller(stmt)); - if (point.getParameter(index) instanceof Local) { + point.method = method;// 设置所在方法 + point.stmt = stmt;// 设置语句 + point.setCaller(getBaseCaller(stmt));// 提取调用者对象 +// 参数提取和验证 + if (point.getParameter(index) instanceof Local) {//检查这个value是不是Local局部变量类型 point.paraLocal = (Local) point.getParameter(index); } else { return null; } - return point; } - } diff --git a/src/ac/pool/point/OneParaValueKeyPoint.java b/src/ac/pool/point/OneParaValueKeyPoint.java index 2205c57..ad32aa4 100644 --- a/src/ac/pool/point/OneParaValueKeyPoint.java +++ b/src/ac/pool/point/OneParaValueKeyPoint.java @@ -3,9 +3,13 @@ import soot.SootMethod; import soot.Value; import soot.jimple.Stmt; +//单参数值关键点扩展类 +//扩展基础关键点功能,支持对任意类型参数值(包括数值、字符串等)的分析,特别适用于线程池配置参数的追踪。 +//OneParaKeyPoint:精度优先,只处理可追踪的对象引用 +//OneParaValueKeyPoint:覆盖优先,处理所有类型的参数值 public class OneParaValueKeyPoint extends KeyPoint{ - + // 参数值(可以是任意类型的 Value) Value paraValue = null; public void setParaValue(Value paraValue) { @@ -18,10 +22,11 @@ public Value getParaValue() { public static OneParaValueKeyPoint newOneParaValueKeyPoint(SootMethod method, Stmt stmt, int index) { OneParaValueKeyPoint point = new OneParaValueKeyPoint(); +// 初始化实例的信息。 point.method = method; point.stmt = stmt; point.setCaller(getBaseCaller(stmt)); - point.paraValue = point.getParameter(index); + point.paraValue = point.getParameter(index);// 接受任意 Value 类型 return point; } diff --git a/src/ac/pool/point/PointCollector.java b/src/ac/pool/point/PointCollector.java index fa14fa0..781ce68 100644 --- a/src/ac/pool/point/PointCollector.java +++ b/src/ac/pool/point/PointCollector.java @@ -22,58 +22,114 @@ import soot.Unit; import soot.Value; import soot.jimple.Stmt; +//PointCollector, +//PointCollector (抽象基类) +//├── PointCollectorAsyncTask (AsyncTask收集器) +//├── PointCollectorExecutor (线程池收集器) +//└── PointCollectorThread (Thread收集器) +//PointCollector是一个异步关键点收集框架 + +//——提供了完整的收集算法框架 +//——定义了14种关键点类型的存储结构 +//——实现了通用的代码遍历和关键点识别流程 + +//通过遍历所有类的方法体,识别和收集各种异步操作的关键调用点(key caller),为后续的 misuse 检测提供数据基础。 public abstract class PointCollector { - protected Set initialPoints = new HashSet<>(); + protected Set initialPoints = new HashSet<>();// 初始化点 - protected Set submitPoints = new HashSet<>(); + protected Set submitPoints = new HashSet<>();// 任务提交点 - protected Set shutDownPoints = new HashSet<>(); + protected Set shutDownPoints = new HashSet<>();//关闭点 - protected Set shutDownNowPoints = new HashSet<>(); + protected Set shutDownNowPoints = new HashSet<>();// 立即关闭点 - protected Set startPoints = new HashSet<>(); + protected Set startPoints = new HashSet<>();// 启动点 - static protected Set destroyPoints = new HashSet<>(); + static protected Set destroyPoints = new HashSet<>(); //销毁点 - protected Set setThreadFactoryPoints = new HashSet<>(); + protected Set setThreadFactoryPoints = new HashSet<>();// 线程工厂设置 - protected Set isTerminatedPoints = new HashSet<>(); + protected Set isTerminatedPoints = new HashSet<>();// 终止状态检查 - protected Set setRejectedExecutionHandlerPoints = new HashSet<>(); + protected Set setRejectedExecutionHandlerPoints = new HashSet<>();// 拒绝策略设置 - protected Set setUncaughtExceptionHandlerPoints = new HashSet<>(); + protected Set setUncaughtExceptionHandlerPoints = new HashSet<>();// 异常处理器设置 - protected Set setCoreThreadSizePoints = new HashSet<>(); + protected Set setCoreThreadSizePoints = new HashSet<>();// 核心线程数设置 - protected Set setMaxThreadSizePoints = new HashSet<>(); + protected Set setMaxThreadSizePoints = new HashSet<>();// 最大线程数设置 - protected Set setNamePoints = new HashSet<>(); + protected Set setNamePoints = new HashSet<>();// 线程名设置 - public void start(Collection classes) { + // 固定算法骨架 +// public void start(Collection classes) { +// // 遍历所有类 → 所有方法 → 所有语句 +// // 调用 findKeyPoint() 进行关键点识别 +// Log.i("## start PointCollector: classes.size() = ", classes.size()); +// for (SootClass sootClass : classes) {//遍历所有类。 +// List methods = new ArrayList<>(sootClass.getMethods()); +// for (SootMethod method : methods) {//遍历所有方法。 +//// if (!Scene.v().getReachableMethods().contains(method)) { +//// continue; +//// } +// if (method.hasActiveBody()) {// 只分析有avtivebody的方法 +// Body body = method.getActiveBody(); +// for (Unit unit : body.getUnits()) {// 分析每个方法体,遍历每个unit》 +// Stmt stmt = (Stmt) unit; +// if (stmt.containsInvokeExpr()) { +// //对于每个方法和该方法中的每个statement,调用findKeyPoint.去进行仔细分析。 +// findKeyPoint(stmt, method); +// } +// } +// } +// } +// } +// } + public void start(Collection classes) { Log.i("## start PointCollector: classes.size() = ", classes.size()); - for (SootClass sootClass : classes) { List methods = new ArrayList<>(sootClass.getMethods()); for (SootMethod method : methods) { -// if (!Scene.v().getReachableMethods().contains(method)) { -// continue; -// } + + // [修复 1] 如果没有 Body,强制加载! + if (!method.hasActiveBody()) { + try { + method.retrieveActiveBody(); + } catch (Exception e) { + // 某些系统方法无法加载是正常的,忽略 + } + } + + // [修复 2] 再次检查 hasActiveBody() + // 此时 Lambda 方法应该已经有 Body 了 if (method.hasActiveBody()) { Body body = method.getActiveBody(); for (Unit unit : body.getUnits()) { Stmt stmt = (Stmt) unit; if (stmt.containsInvokeExpr()) { + + // [临时 Debug] 这一步是用来验证是否成功进入了 Lambda 内部 + // 看到这个输出,说明第一关过了 + if (stmt.getInvokeExpr().getMethod().getName().equals("isShutdown")) { + System.out.println("DEBUG: 成功进入 Lambda 并发现 isShutdown! " + method.getName()); + } + findKeyPoint(stmt, method); } } + } else { + // 如果强制加载后还是没有,打印日志看看是哪个方法顽固不化 + // System.out.println("DEBUG: 依然没有 Body: " + method.getName()); } } } } + + // 根据参数类型查找参数索引 protected int getParaIndexByType(String type, Stmt stmt) { for (int i = 0; i < stmt.getInvokeExpr().getMethod().getParameterCount(); i++) { if (type.equals(stmt.getInvokeExpr().getMethod().getParameterType(i).toString())) { @@ -83,14 +139,19 @@ protected int getParaIndexByType(String type, Stmt stmt) { return -1; } + +// 这是关键点识别的核心方法,采用责任链模式 protected void findKeyPoint(Stmt stmt, SootMethod method) { +// 快速过滤,只处理包含方法调用的语句,排除赋值、跳转等其他语句 if (!stmt.containsInvokeExpr()) { return; } + +// 接下来是关键点识别链(14个独立检查) +// 对于符合判断的调用点类型,就加入其相应的集合。 if (isInitPoint(stmt)) { InitPoint point = newInitPoint(method, stmt); if (point != null) { - initialPoints.add(point); } } @@ -182,6 +243,8 @@ protected KeyPoint newSetNamePoint(SootMethod method, Stmt stmt) { protected abstract OneParaValueKeyPoint newSetCoreThreadSizePoint(SootMethod method, Stmt stmt); +// protected abstract OneParaValueKeyPoint newSetQueueSizePoint(SootMethod method, Stmt stmt); + protected abstract KeyPoint newStartPoint(SootMethod method, Stmt stmt); protected abstract KeyPoint newSetUncaughtExceptionHandlerPoint(SootMethod method, Stmt stmt); @@ -196,13 +259,22 @@ protected KeyPoint newSetNamePoint(SootMethod method, Stmt stmt) { protected abstract KeyPoint newShutdownNowPoint(SootMethod method, Stmt stmt); + +// // DestroyPoint 的特殊处理——通过指针分析识别与可销毁字段相关的调用 protected DestroyPoint newDestroyPoint(SootMethod method, Stmt stmt) { +// 得到调用者caller,也许是局部变量类型的; Value caller = KeyPoint.getBaseCaller(stmt); if (caller instanceof Local) { +// 得到指针分析器,pta PointsToAnalysis pointsToAnalysis = Scene.v().getPointsToAnalysis(); +// 遍历可销毁字段; for (SootField sootField : HTRChecker.destructibleSootFields) { +// 这个if条件,说明要判断sootField和caller,是否是继承关系? +// 如果是继承关系,说明caller这个Local局部变量是可销毁的,因为这个sootField是可销毁的; if (InheritanceProcess.isInheritedFromGivenClass(sootField.getType(), caller.getType()) - || InheritanceProcess.isInheritedFromGivenClass(caller.getType(), sootField.getType())) { + || InheritanceProcess.isInheritedFromGivenClass(caller.getType(), sootField.getType())) + { +// 指针分析,求得caller可能指向对象的集合 PointsToSet aliaSet = pointsToAnalysis.reachingObjects((Local) caller, sootField); if (!aliaSet.isEmpty()) { DestroyPoint point = DestroyPoint.newDestroyPoint(method, stmt, sootField); @@ -214,16 +286,20 @@ protected DestroyPoint newDestroyPoint(SootMethod method, Stmt stmt) { return null; } + protected abstract boolean isSetMaxThreadSizePoint(Stmt stmt); protected abstract boolean isSetCoreThreadSizePoint(Stmt stmt); + protected abstract boolean isStartPoint(Stmt stmt); + +// 判断当前语句是否是异步组件的销毁操作,比如线程中断、线程池关闭、资源释放等。 protected boolean isDestroyPoint(Stmt stmt) { return DestructibleIdentify.isDestroyMethod(stmt.getInvokeExpr().getMethod()); } - +// 用于识别设置线程名称的方法调用,这对于线程调试和监控很重要。 protected boolean isSetThreadNamePoint(Stmt stmt) { return stmt.containsInvokeExpr() && (ThreadSig.METHOD_SIG_SET_NAME.equals(stmt.getInvokeExpr().getMethod().getSignature())); @@ -297,6 +373,7 @@ public Set getSetMaxThreadSizePoints() { return setMaxThreadSizePoints; } + public Set getSetNamePoints() { return setNamePoints; } diff --git a/src/ac/util/AsyncInherit.java b/src/ac/util/AsyncInherit.java index 3e8c0e7..e6fc80d 100644 --- a/src/ac/util/AsyncInherit.java +++ b/src/ac/util/AsyncInherit.java @@ -8,45 +8,49 @@ import soot.SootClass; import soot.Type; +//一个异步组件继承关系判断工具,专门用于在 Android 异步组件检测工具中判断类是否继承自特定的异步相关类。 + + public class AsyncInherit { - + // 判断是否继承自 Executor public static boolean isInheritedFromExecutor(SootClass theClass) { return InheritanceProcess.isInheritedFromGivenClass(theClass, ExecutorSig.CLASS_EXECUTOR); } - + // 判断类型是否继承自 ExecutorService public static boolean isInheritedFromExecutor(Type type) { if (type instanceof RefType) { return isInheritedFromExecutor(((RefType) type).getSootClass()); } return false; } - + // 判断是否是 CallerRunsPolicy 拒绝策略 public static boolean isInheritedCallerRunsPolicy(Type type) { if (type instanceof RefType) { return InheritanceProcess.isInheritedFromGivenClass(((RefType) type).getSootClass(), ExecutorSig.CLASS_CallerRunsPolicy); } return false; } - + + // 判断是否继承自 Thread public static boolean isInheritedFromThread(SootClass theClass) { return InheritanceProcess.isInheritedFromGivenClass(theClass, ThreadSig.CLASS_THREAD); } - public static boolean isInheritedFromExecutorService(SootClass theClass) { return InheritanceProcess.isInheritedFromGivenClass(theClass, ExecutorSig.CLASS_EXECUTOR_SERVICE); } - public static boolean isInheritedFromExecutorService(Type type) { if (type instanceof RefType) { return isInheritedFromExecutorService(((RefType) type).getSootClass()); } return false; } + // 判断是否实现 Runnable 接口 public static boolean isInheritedFromRunnable(SootClass theClass) { return InheritanceProcess.isInheritedFromGivenClass(theClass, ThreadSig.CLASS_RUNNABLE); } + // 判断是否实现 Callable 接口 public static boolean isInheritedFromRunnable(Type type) { if (type instanceof RefType) { @@ -54,7 +58,7 @@ public static boolean isInheritedFromRunnable(Type type) { } return false; } - + // 判断是否实现 Callable 接口 public static boolean isInheritedFromCallable(SootClass theClass) { return InheritanceProcess.isInheritedFromGivenClass(theClass, ExecutorSig.CLASS_CALLABLE); } @@ -65,24 +69,24 @@ public static boolean isInheritedFromCallable(Type type) { } return false; } - + // 判断是否继承自 Activity public static boolean isInheritedFromActivity(SootClass theClass) { return InheritanceProcess.isInheritedFromGivenClass(theClass, AsyncTaskSig.CLASS_ACTIVITY, MatchType.equal); } - + // 判断是否继承自 View public static boolean isInheritedFromView(SootClass theClass) { return InheritanceProcess.isInheritedFromGivenClass(theClass, AsyncTaskSig.CLASS_VIEW, MatchType.equal); } - + // 判断是否继承自 Fragment(支持多种 Fragment 类型) public static boolean isInheritedFromFragment(SootClass theClass) { return InheritanceProcess.isInheritedFromGivenClass(theClass, AsyncTaskSig.CLASS_FRAGMENT, MatchType.equal) || InheritanceProcess.isInheritedFromGivenClass(theClass, AsyncTaskSig.CLASS_SUPPORT_FRAGMENT, MatchType.equal) || InheritanceProcess.isInheritedFromGivenClass(theClass, AsyncTaskSig.CLASS_SUPPORT_FRAGMENT_V7, MatchType.equal); } - + // 判断是否继承自 AsyncTask public static boolean isInheritedFromAsyncTask(SootClass theClass) { return InheritanceProcess.isInheritedFromGivenClass(theClass, AsyncTaskSig.ASYNC_TASK, MatchType.equal); } diff --git a/src/ac/util/DataFlowProcess.java b/src/ac/util/DataFlowProcess.java index 5097867..edf26cd 100644 --- a/src/ac/util/DataFlowProcess.java +++ b/src/ac/util/DataFlowProcess.java @@ -22,6 +22,11 @@ import soot.toolkits.graph.BriefUnitGraph; import soot.toolkits.graph.UnitGraph; +//数据流分析工具,专门用于在 Android 异步组件检测工具中进行过程内数据流分析,追踪局部变量的类型信息。 + +//processLocalOfIdentityStmt() 方法:分析方法的 this 引用和参数引用;建立局部变量到对应类的映射关系 +//findInstantiateClassOfLocal() 方法:核心功能:通过数据流分析确定局部变量指向的实际对象类型,支持向后数据流分析 + public class DataFlowProcess { /** diff --git a/src/ac/util/InheritanceProcess.java b/src/ac/util/InheritanceProcess.java index c0bb311..86f7718 100644 --- a/src/ac/util/InheritanceProcess.java +++ b/src/ac/util/InheritanceProcess.java @@ -30,6 +30,14 @@ import soot.SootClass; import soot.Type; + +//继承关系处理工具,专门用于在 Android 异步组件检测工具中分析和处理类的继承关系。 + +// isInheritedFromGivenClass() 方法:检查一个类是否继承自指定的父类 +// getDirectSubClasses() 方法:获取指定类的所有直接子类 +// 相等匹配 (MatchType.equal):精确匹配类名 +// 正则匹配 (MatchType.regular):使用正则表达式匹配类名 +// isClassInSystemPackage() 方法:判断类是否属于系统包(如 android.、java. 等) /** * Processing class inheritance relationship * @@ -40,32 +48,40 @@ public class InheritanceProcess { public enum MatchType { equal, regular +// 一个是精确匹配模式,一个是正则表达式匹配模式。 } +// 获取指定类(currentClass)的直接子类 public static List getDirectSubClasses(SootClass currentClass) { List subclasses = new ArrayList(); - for (SootClass theClass : Scene.v().getApplicationClasses()) { + for (SootClass theClass : Scene.v().getApplicationClasses()) {//遍历所有应用类 + //检查当前遍历的类的直接父类是否等于 currentClass if (theClass.getSuperclassUnsafe().equals(currentClass)) { - subclasses.add(theClass); + subclasses.add(theClass);//如果是的话,将当前类加入这个子类列表 } } return subclasses; } - +// 检查两个类型之间的继承关系:subtype(子类型)/ parentType(父类型) public static boolean isInheritedFromGivenClass(Type subType, Type parentType) { +// 基本类型 (PrimType) 如果都是基本类型,则返回false,因为基本类型无继承关系。 if (subType instanceof PrimType || parentType instanceof PrimType) return false; +// 如果一个是数组类型,一个是引用类型,则返回false,因为类型不兼容 else if ((subType instanceof ArrayType && parentType instanceof RefType) || (parentType instanceof ArrayType && subType instanceof RefType)) return false; +// 如果两个都是引用类型 (RefType),转换为 SootClass 并调用三参数版本的方法进行精确匹配 else if (subType instanceof RefType && parentType instanceof RefType) return isInheritedFromGivenClass(((RefType) subType).getSootClass(), parentType.toString(), MatchType.equal); +// 如果两个都是数组类型的,获取两个数组的维度numDimensions,如果维度不一样则返回false else if (subType instanceof ArrayType && parentType instanceof ArrayType) { int subDim = ((ArrayType) subType).numDimensions; int parentDim = ((ArrayType) parentType).numDimensions; if (subDim != parentDim) return false; +// 获取数组的基本类型,并递归检查基本类型的继承关系 Type subBaseType = ((ArrayType) subType).baseType; Type parentBaseType = ((ArrayType) parentType).baseType; return isInheritedFromGivenClass(subBaseType, parentBaseType); @@ -73,14 +89,17 @@ else if (subType instanceof ArrayType && parentType instanceof ArrayType) { return false; } } - + +// 两参数版本的方法 public static boolean isInheritedFromGivenClass(SootClass theClass, String classNameUnderMatch) { - MatchType matchType = MatchType.equal; + MatchType matchType = MatchType.equal;//设置匹配模式为精确匹配 if (theClass == null) return false; +// 检查当前类是否匹配目标类名,如果匹配直接返回 true if (isTypeMatch(theClass, classNameUnderMatch, matchType)) { return true; } +// 如果类的解析级别低于 HIERARCHY 级别,尝试提升解析级别 if(theClass.resolvingLevel() < SootClass.HIERARCHY) { Scene.v().addBasicClass(theClass.getName(), SootClass.HIERARCHY); } @@ -88,6 +107,7 @@ public static boolean isInheritedFromGivenClass(SootClass theClass, String class return false; } for (SootClass interfaceClass : theClass.getInterfaces()) { +// 遍历 theClass.getInterfaces() 获取该类的所有接口,对每一个接口递归调用该方法 if (isInheritedFromGivenClass(interfaceClass, classNameUnderMatch, matchType)) { return true; } @@ -99,9 +119,11 @@ public static boolean isInheritedFromGivenClass(SootClass theClass, String class MatchType matchType) { if (theClass == null) return false; +// 确保当前类的解析级别足够(HIERARCHY 级别包含继承关系信息) if(theClass.resolvingLevel() < SootClass.HIERARCHY) { Scene.v().addBasicClass(theClass.getName(), SootClass.HIERARCHY); } +// 根据类名获取父类的 SootClass 对象 SootClass parentClass = Scene.v().getSootClass(classNameUnderMatch); if( parentClass.resolvingLevel() < SootClass.HIERARCHY) { Scene.v().addBasicClass(parentClass.getName(), SootClass.HIERARCHY); @@ -109,19 +131,25 @@ public static boolean isInheritedFromGivenClass(SootClass theClass, String class if(theClass.resolvingLevel() < SootClass.HIERARCHY || parentClass.resolvingLevel() < SootClass.HIERARCHY) { return false; } +// 检查当前类是否直接匹配目标 if (isTypeMatch(theClass, classNameUnderMatch, matchType)) return true; +// 递归检查所有实现的接口 for (SootClass interfaceClass : theClass.getInterfaces()) { if (isInheritedFromGivenClass(interfaceClass, classNameUnderMatch, matchType)) { return true; } - } + } +// 最后递归检查父类的继承链 return isInheritedFromGivenClass(theClass.getSuperclassUnsafe(), classNameUnderMatch, matchType); } +// 检查类型匹配 private static boolean isTypeMatch(SootClass currentClass, String classNameUnderMatch, MatchType matchType) { +// 如果是精确匹配模式,比较类名字符串是否完全相等 if (matchType == MatchType.equal && currentClass.getType().toString().equals(classNameUnderMatch)) return true; +// 如果是正则匹配模式,调用 isRegularMatch 方法 else if (matchType == MatchType.regular && isRegularMatch(currentClass.getType().toString(), classNameUnderMatch)) return true; @@ -129,8 +157,11 @@ && isRegularMatch(currentClass.getType().toString(), classNameUnderMatch)) return false; } +// 执行正则表达式匹配 private static boolean isRegularMatch(String targetStr, String regularStr) { +// 将正则字符串编译为 Pattern 对象 Pattern p = Pattern.compile(regularStr); +// 创建匹配器,对目标字符串进行匹配 Matcher m = p.matcher(targetStr); return m.matches(); } diff --git a/src/ac/util/LOC.java b/src/ac/util/LOC.java new file mode 100644 index 0000000..16f6dab --- /dev/null +++ b/src/ac/util/LOC.java @@ -0,0 +1,108 @@ +package ac.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.rmi.server.RemoteObject; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class LOC { + + + private static int classCount = 0; + private static int lineCode = 0; + static Set jarList = new HashSet<>(); + + public static void main(String[] args) { + +// args = new String[] { "./apache-hive-4.0.0", "./apache-hive-4.0.0-alpha-1", "./apache-iotdb-0.13.4", +// "./apache-iotdb-1.3.1", "./apache-tez-0.10.3", "./apache-uniffle-0.8.0-incubating", "./cassandra", +// "./elasticsearch-7.17.21", "./elasticsearch-hadoop-7.17.21", "./hadoop-3.4.0", "./hadoop322_backup", +// "./HBase-2.4.5", "./hbase-3.0.0-beta-1", "./ozone-1.4.0", "./phoenix-hbase-2.5.0-5.2.0", +// "./pravega-0.13.0", "./zookeeper" }; + +// args = new String[] {"D:\\cbq\\Research\\RPC20240724\\rmi-jndi-ldap-jrmp-jmx-jms-master","D:\\cbq\\Research\\RPC20240724\\apache-jmeter-5.6.3", "D:\\cbq\\Research\\RPC20240724\\org.ops4j.pax.exam2-exam-reactor-4.13.5", "D:\\cbq\\Research\\RPC20240724\\RPC-Benchmark\\grpc-java-master\\benchmarks", "D:\\cbq\\Research\\RPC20240724\\RPC-Benchmark\\dubbo-samples-master"}; + + args = new String[] {"/Users/qiuyucheng/Downloads/Felidae-ThreadPool-main/TestProject/agrona-1.17.1.jar"}; + for (String str : args) { + String path = str; + if (path.toLowerCase().endsWith("jar")) { + jarList.add(path); + } else { + findAllJarFiles(new File(path)); + } + for (String jar : jarList) { + findAllJarClassfiles(jar); + } + System.out.println("*************************"); + System.out.println(path); + System.out.println("jarList.size() = " + jarList.size()); + System.out.println("classCount = " + classCount); + System.out.println("lineCode = " + lineCode); + jarList.clear(); + lineCode = 0; + classCount = 0; + } + + } + + public static void findAllJarFiles(File dir) { + try { + File[] files = dir.listFiles(); + for (int i = 0; i < files.length; i++) { + String jspPath = files[i].getAbsolutePath().replace("\\", "/"); + if (jspPath.toLowerCase().endsWith(".jar")) { + jarList.add(jspPath); + } + if (files[i].isDirectory()) { + findAllJarFiles(files[i]); + } + + } + } catch (Exception e) { + e.printStackTrace(); + } + + } + + public static Set findAllJarClassfiles(String jarName) { + Set jarFileList = new HashSet(); + try { + JarFile jarFile = new JarFile(jarName); + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) { + JarEntry jarEntry = entries.nextElement(); + String fileName = jarEntry.getName(); + if (fileName.endsWith(".class")) { + // System.out.println(fileName); + fileName = fileName.replace("/", "."); + jarFileList.add(fileName); + classCount++; + try { + InputStream is = jarFile.getInputStream(jarEntry); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + @SuppressWarnings("unused") + String line = ""; + while ((line = br.readLine()) != null) { + lineCode++; + } + } catch (Throwable e) { +// e.printStackTrace(); + } + } + } + jarFile.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return jarFileList; + } + + +} \ No newline at end of file diff --git a/src/ac/util/Log.java b/src/ac/util/Log.java index 2ef97e7..84575c7 100644 --- a/src/ac/util/Log.java +++ b/src/ac/util/Log.java @@ -24,6 +24,11 @@ * @author Baoquan Cui * @version 1.0 */ + +// log类的作用:输出信息日志、错误日志信息 +// 通过 DEBUG 和 ERROR_DEBUG 常量控制日志输出 +// 当前设置为 true,表示启用所有日志 +// 可以通过改为 false 来禁用日志输出 public class Log { private static final boolean DEBUG = true; diff --git a/src/destructible/DestructibleIdentify.java b/src/destructible/DestructibleIdentify.java index 48252fb..b7ae8e0 100644 --- a/src/destructible/DestructibleIdentify.java +++ b/src/destructible/DestructibleIdentify.java @@ -25,25 +25,39 @@ import ac.constant.ThreadSig; import ac.util.Log; +//识别可销毁资源 public class DestructibleIdentify { +// 存储类与其销毁方法的映射 public final static Map destructibleClasses = new HashMap<>(); +// 记录包含destroy方法的类名 public final static Set destroyClasses = new HashSet<>(); +// 记录包含close方法的类名 public final static Set closeClasses = new HashSet<>(); +// 记录使用PreDestroy/PostConstruct注解的类名 public final static Set annotationClasses = new HashSet<>(); +// 记录空实现的销毁方法——NULL方法实现 public final static Map emptyDestructibleClasses = new HashMap<>(); +// 遍历所有类,识别可销毁类并填充数据结构 public static void collect() { Set classes = new HashSet(); - classes.addAll(Scene.v().getClasses()); - classes.addAll(Scene.v().getApplicationClasses()); - for (SootClass sootClass : classes) { - if (isDestructibleClass(sootClass,sootClass)) { + classes.addAll(Scene.v().getClasses());//获取Scene中的所有class(库类、应用类) + classes.addAll(Scene.v().getApplicationClasses());//获取所有应用类,HashSet会去重 + for (SootClass sootClass : classes) { //对于每一个类,进行分析,是不是可销毁的类? + if (isDestructibleClass(sootClass,sootClass)) { //具体逻辑待实现。 + } } } - + +// 空方法检测。 +// 这段代码的核心目的是检测方法是否真正需要实例上下文: +// 返回false:方法使用了this对象(访问字段、调用方法等) +// 返回true:方法没有使用this对象,行为类似静态方法 public static boolean isEmptyMethod(SootMethod sootMethod) { +// 抽象方法、native方法,不视为空 +// 静态方法、没有方法体avtivebody、空方法体的,视为空 if(sootMethod.isAbstract()) { return false; } @@ -56,50 +70,72 @@ public static boolean isEmptyMethod(SootMethod sootMethod) { if(!sootMethod.hasActiveBody()) { return true; } + Body body = sootMethod.getActiveBody(); if (body.getUnits().isEmpty()) { return true; } +// 构建 UnitGraph 语句调用图,生成的控制流图对象,每个节点是一个Unit(单条指令) UnitGraph unitGraph = new BriefUnitGraph(body); +// 返回代表当前对象实例的局部变量 Local thisLocal = body.getThisLocal(); - SimpleLocalDefs localDefs = new SimpleLocalDefs(unitGraph); - SimpleLocalUses localUses = new SimpleLocalUses(unitGraph, localDefs); - for (Unit defsOfThisLocal : localDefs.getDefsOf(thisLocal)) { + SimpleLocalDefs localDefs = new SimpleLocalDefs(unitGraph);//找出每个局部变量在哪些位置被赋值 + SimpleLocalUses localUses = new SimpleLocalUses(unitGraph,localDefs);//找出每个变量的值在哪些位置被使用 +// 这个逻辑判断——————当前方法是否真正使用了this对象 + for (Unit defsOfThisLocal : localDefs.getDefsOf(thisLocal)) {//获取所有对this局部变量的"定义"点 +// 检查每个定义点的使用情况 if(!localUses.getUsesOf(defsOfThisLocal).isEmpty()) { return false; } } +// 如果实例方法没有使用this,可以考虑改为静态方法,因为静态方法本来就没有this return true; } + +// 判断是否为可销毁的类 +// 最初要分析的原始类(在整个递归过程中保持不变) +// 当前正在分析的具体类(在递归过程中会变化) public static boolean isDestructibleClass(SootClass originClass, SootClass sootClass) { - if (destructibleClasses.containsKey(sootClass)) { + if (destructibleClasses.containsKey(sootClass)) {//检查当前分析的类是否已经在缓存中 +// 如果已缓存,从缓存中获取当前类对应的销毁方法,将原始类也添加到缓存中,映射到相同的销毁方法 +// 原始分析MyServiceImpl类(originClass) + // 发现它继承自AbstractService(sootClass) + // 而AbstractService已经在缓存中有destroy()方法 + // 于是将MyServiceImpl也映射到同一个destroy()方法 destructibleClasses.put(originClass, destructibleClasses.get(sootClass)); return true; } +// 检查当前类是否是Thread类,如果是Thread类,直接返回不可销毁 if(ThreadSig.CLASS_THREAD.equals(sootClass.getName())) { return false; } +// 获取当前类的所有方法,用Set去重 Set sootMethods = new HashSet(sootClass.getMethods()); - for (SootMethod sootMethod : sootMethods) { + for (SootMethod sootMethod : sootMethods) {//遍历每一个方法。 +// 方法是否有PreDestroy注解?是否以destroy结尾的方法?方法名是否包含close? if (isPreDestroyAnnotationMethod(sootMethod) || isDestroyMethod(sootMethod) || isCloseMethod(sootMethod)) { +// 将原始类与找到的销毁方法建立映射关系 destructibleClasses.put(originClass, sootMethod); +// 原始类是否是应用类+确保原始类不是抽象类+检查销毁方法是否为空实现 if(Scene.v().getApplicationClasses().contains(originClass) && !originClass.isAbstract() && isEmptyMethod(sootMethod)) { Log.e("#####################"); Log.e(originClass.getName()); Log.e(sootMethod); +// 空实现的销毁方法记录 emptyDestructibleClasses.put(originClass.getName() , sootMethod); } return true; } } - if (sootClass.hasSuperclass()) { + if (sootClass.hasSuperclass()) {//检查是否有父类并且父类是否为可销毁类 if (isDestructibleClass(originClass, sootClass.getSuperclass())) { return true; } } - if (sootClass.getInterfaces() != null) { + if (sootClass.getInterfaces() != null) { //遍历当前类实现的所有接口 for (SootClass interfaceClass : sootClass.getInterfaces()) { +// 检查每个接口是否定义了可销毁方法。 if (isDestructibleClass(originClass, interfaceClass)) { return true; } @@ -107,13 +143,18 @@ public static boolean isDestructibleClass(SootClass originClass, SootClass sootC } return false; } - + + +// 判断是否为PreDestroy注解标注的方法 private static boolean isPreDestroyAnnotationMethod(SootMethod sootMethod) { +// 1.获取该方法的所有注解 List annotationTags = getAnnotationtags(sootMethod); - for (AnnotationTag annotationTag : annotationTags) { + for (AnnotationTag annotationTag : annotationTags) {//遍历注解列表中的所有注解。 //Ljavax/annotation/PreDestroy; //Ljakarta/annotation/PreDestroy; //xxx/PostConstruct; +// 看看是不是PreDestroy或者PostConstruct注解的方法。 +// 如果是,那么将这个类加入annotationClasses if (annotationTag.getType().endsWith("PreDestroy") || annotationTag.getType().endsWith("PostConstruct")) { annotationClasses.add(sootMethod.getDeclaringClass().getName()); return true; @@ -121,29 +162,32 @@ private static boolean isPreDestroyAnnotationMethod(SootMethod sootMethod) { } return false; } - + + +// 检测是否为Destroy方法。 public static boolean isDestroyMethod(SootMethod sootMethod) { - if(sootMethod.getName().toLowerCase().endsWith("destroy")) { - destroyClasses.add(sootMethod.getDeclaringClass().getName()); + if(sootMethod.getName().toLowerCase().endsWith("destroy")) {//是否以destroy结尾 + destroyClasses.add(sootMethod.getDeclaringClass().getName());//记录包含destroy方法的类名 return true; } return false; } - +// 检测是否为Close方法。 private static boolean isCloseMethod(SootMethod sootMethod) { - if(sootMethod.getName().toLowerCase().contains("close")) { - closeClasses.add(sootMethod.getDeclaringClass().getName()); + if(sootMethod.getName().toLowerCase().contains("close")) {// 是否以close结尾 + closeClasses.add(sootMethod.getDeclaringClass().getName());//记录包含close方法的类名 return true; } return false; } +// 提取一个类或者方法的所有注解。返回一个注解列表。 private static List getAnnotationtags(AbstractHost host) { - List tags = new ArrayList<>(); - for (Tag tag : host.getTags()) { - if (tag instanceof VisibilityAnnotationTag) { + List tags = new ArrayList<>();//创建空的注解列表 + for (Tag tag : host.getTags()) {//遍历宿主(方法/类)的所有标签 + if (tag instanceof VisibilityAnnotationTag) {//检查标签是否是可见性注解标签 VisibilityAnnotationTag visibilityAnnotationTag = (VisibilityAnnotationTag) tag; - tags.addAll(visibilityAnnotationTag.getAnnotations()); + tags.addAll(visibilityAnnotationTag.getAnnotations());//提取所有注解并添加到结果列表 } } return tags;