代码潜在故障的动态分析

2023-10-30

[b][size=large]引子[/size][/b]
大家都听说过FindBugs的大名。这是一款静态代码分析的工具。能够直接对字节码文件加以分析,并发现潜在的反模式(anti-pattern),从而有效地促进代码质量的改善。

但FindBugs只能用于[b][color=blue]静态[/color][/b]代码分析。这也就意味着对于一些运行时的问题,例如,对于指定对象所属类型的校验、对于文件的打开和关闭是否相互对应,对于HashMap中的对象是否被修改过导致永远无法再次获得等情况,FindBugs根本无从下手。为此,本文提出了动态分析的思想并给出演示实现。

[b][size=large]动态代码分析[/size][/b]
所谓动态代码分析,就是相对于静态代码的分析。这是一句废话,就当立论了吧。

OK,所谓动态代码分析,就是指在程序运行期间能够主动检查代码运行的机制、模式、问题,收集代码的各种运行信息,并分阶段执行汇总分析,根据指定的一些标准,获得代码质量相关判断结果。

这样说比较枯燥乏味,我们举一些比较有趣的例子来说明问题。

例如以下的代码,看看我们能够发现什么问题:

// Hello.java
public class Hello implements Serializable
{
public void sayTo(String name)
{
System.out.println("Hello, " + name + "! Nice to meet U!");
}
}

// Runner.java
public class Runner implements Runnable, Serializable
{
public void run()
{
Hello hello = new Hello(){};
OutputStream baos = new ByteArrayOutputStream();
ObjectOutput oo = null;
try {
oo = new ObjectOutputStream(baos);
oo.writeObject(hello);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (oo != null) {
try {
oo.flush();
oo.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
hello.sayTo("Regular");
}
}

看出问题的请举手。

不过我可以保证,这段代码可以安全通过FindBugs检查。因为这段代码从静态类文件来看,基本上没有什么毛病。而且运行一万次,一万次结果正确。这分明就是正确的代码嘛!

……

但是,如果我们现在有收端和发端,从一方把Hello类对象发到另一方接收,那么……

还是不会错!这分明就是完全正确的代码嘛!

……

但是我们还不知足,把收端和发端分别编译,然后再重新尝试刚才的操作,那么……

竟然还是不会错!

……

最后,我们把以上代码修改如下:

public class Runner implements Runnable, Serializable
{
private static final long serialVersionUID = 1L;

public void run()
{
Hello hello1 = new Hello(){
private static final long serialVersionUID = 2L;};
Hello hello2 = new Hello(){
private static final long serialVersionUID = 3L;};
// ...

在这种情况下,Hello类将同时拥有两个匿名类,两个类的名称并非顺序排列,在不同的编译环境中可能产生不同的类名,因此序列化和反序列化[b][color=blue]可能[/color][/b]会导致失败。

而ObjectOutputStream的writeObject方法根本不会检查对象是否为匿名类实例,甚至连是否实现了Serializable接口都不会检查。所以这段代码会通过检查并隐含可能发生的错误,直到某一天突然无声无息的爆发,打你个措手不及。

因此,动态代码分析应运而生了。

[b][size=large]目标[/size][/b]
[list]
[*]能够监管代码的运行
[*]能够记录代码的某些操作
[*]能够发现代码的某些反模式
[*]不能对代码文件造成任何改变
[*]不能让代码的运行依赖于检查
[*]不能过多干涉代码的运行,乃至重建JVM实现(过于厚重)
[/list]
[b][size=large]实现方案[/size][/b]
经过以上分析,我们可以想见,这个方案是涉及到AOP的。AOP的概念不用多解释了,大多数同学都风闻已久。我们这里为了实现最轻量级的方案原型,采用了ASM库并自行实现了ClassLoader。

具体原理如下:

FileClassLoader加载入口类的对象,然后由入口类对象启动一根线程,然后所有的操作过程中需要的类就都会经由FileClassLoader获得。对于我们要监控的操作,会通过RegularClassAdapter动态插入一些检查代码。若发现问题则收集或者直接显示在界面上。

以下是一些主要类的代码:

// 检查模块入口类 Main.java
// ...
public class Main
{
@SuppressWarnings("unchecked")
public static void main(String[] args) throws Exception
{
ClassLoader loader = new FileClassLoader(".\\classes\\");
// bug包是可能存在问题的代码包,以后会用这个包名确定需要插入代码的类文件
Class<Runnable> cls = (Class<Runnable>) loader.loadClass("bug.Runner");
System.out.println("ClassLoader: " + cls.getClassLoader());
Constructor<Runnable> ctor = cls.getConstructor(new Class[0]);
ctor.setAccessible(true);
Runnable runner = ctor.newInstance(new Object[0]);
Thread thread = new Thread(runner);
thread.start();
}
}


// FileClassLoader.java
public class FileClassLoader extends ClassLoader
{
private String root;

public FileClassLoader(String rootDir)
{
if (rootDir == null) {
throw new IllegalArgumentException("Null root directory");
}
root = rootDir;
}

@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
// Since all support classes of loaded class use same class loader
// must check subclass cache of classes for things like Object
// Class loaded yet?
Class<?> c = findLoadedClass(name);
if (c != null) {
System.out.println("O: " + name);
} else {
try {
c = findSystemClass(name);
System.out.println("@: " + name);
} catch (Exception e) {
// Ignore these
}
}
if (c == null) {
System.out.println("X: " + name);
// Convert class name argument to filename
// Convert package names into subdirectories
String filename = name.replace('.', File.separatorChar) + ".class";

try {
// Load class data from file and save in byte array
// Convert byte array to Class
// If failed, throw exception
byte data[] = loadClassData(filename);
if (name.startsWith("bug.")) {
System.out.println("#: " + name);
ClassReader cr = new ClassReader(data);
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassAdapter ca = new RegularClassAdapter(cw);
cr.accept(ca, 0); // ClassReader.SKIP_DEBUG
data = cw.toByteArray();
c = defineClass(name, data, 0, data.length);
} else {
c = defineClass(name, data, 0, data.length);
if (c == null) {
throw new ClassNotFoundException(name);
}
}
} catch (IOException ex) {
throw new ClassNotFoundException(filename, ex);
}
}
// Resolve class definition if approrpriate
if (resolve) {
resolveClass(c);
}
// Return class just created
return c;
}

private byte[] loadClassData(String filename) throws IOException
{
// Create a file object relative to directory provided
File f = new File(root, filename);

// Get size of class file
int size = (int) f.length();

// Reserve space to read
byte buff[] = new byte[size];

// Get stream to read from
FileInputStream fis = new FileInputStream(f);
DataInputStream dis = new DataInputStream(fis);

// Read in data
dis.readFully(buff);

// close stream
dis.close();

// return data
return buff;
}
}


// RegularClassAdapter.java
public class RegularClassAdapter extends ClassAdapter
{
public RegularClassAdapter(ClassVisitor cv)
{
super(cv);
}

@Override
public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature,
final String[] exceptions)
{
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
if (mv != null) {
mv = new CheckAnonySerMethodAdapter(mv);
}
return mv;
}
}

class CheckAnonySerMethodAdapter extends MethodAdapter
{
private static final String OWNER = "java/io/ObjectOutputStream",
NAME = "<init>",
DESC = "(Ljava/io/OutputStream;)V";
private static final String MOCK = "mock/ObjectOutputStream";

public CheckAnonySerMethodAdapter(MethodVisitor mv)
{
super(mv);
}

@Override
public void visitTypeInsn(int opcode, String type)
{
if (type.equals(OWNER)) {
type = MOCK;
}
super.visitTypeInsn(opcode, type);
}

public void visitMethodInsn(int opcode, String owner, String name, String desc)
{
if (opcode == Opcodes.INVOKESPECIAL
&& OWNER.equals(owner) && NAME.equals(name) && DESC.equals(desc)) {
owner = MOCK;
}
super.visitMethodInsn(opcode, owner, name, desc);
}
}


package mock;

import java.io.IOException;
import java.io.OutputStream;

public class ObjectOutputStream extends java.io.ObjectOutputStream
{
private final java.io.ObjectOutputStream oos;

public ObjectOutputStream(OutputStream os) throws IOException
{
super();
oos = new java.io.ObjectOutputStream(os);
}

@Override
protected void writeObjectOverride(Object obj) throws IOException
{
Class cls = obj.getClass();
if (cls.isAnonymousClass()) {
System.err.println("ANONYMOUS CLASS SERIALIZATION PATTERN: " + cls);
Thread.dumpStack();
}
oos.writeObject(obj);
}

// 所有java.io.ObjectOutputStream的方法都需要采用如下的方式代理实现
public void writeUnshared(Object obj) throws IOException
{
oos.writeUnshared(obj);
}

//...

[b][size=large]效果[/size][/b]
[code]
X: bug.Runner
#: bug.Runner
@: java.lang.Runnable
@: java.io.Serializable
@: java.lang.Object
ClassLoader: regular.FileClassLoader@19821f
@: java.lang.Throwable
@: java.io.IOException
@: java.io.OutputStream
@: java.io.ByteArrayOutputStream
@: java.io.ObjectOutput
X: bug.Hello
#: bug.Hello
X: bug.Runner$1
#: bug.Runner$1
@: mock.ObjectOutputStream
ANONYMOUS CLASS SERIALIZATION PATTERN: class bug.Runner$1
java.lang.Exception: Stack trace
at java.lang.Thread.dumpStack(Thread.java:1158)
at mock.ObjectOutputStream.writeObjectOverride(ObjectOutputStream.java:21)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:298)
at bug.Runner.run(Runner.java:44)
at java.lang.Thread.run(Thread.java:595)
@: sun.reflect.SerializationConstructorAccessorImpl
@: java.lang.String
@: java.lang.System
@: java.lang.StringBuilder
@: java.io.PrintStream
Hello, Regular! Nice to meet U!
[/code]

参考网页:
[url=http://java.sun.com/developer/onlineTraining/Security/Fundamentals/magercises/ClassLoader/help.html]Writing Your Own ClassLoader[/url]

[url=http://www.ibm.com/developerworks/cn/java/j-lo-asm30/index.html]AOP 的利器:ASM 3.0 介绍[/url]
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

代码潜在故障的动态分析 的相关文章

  • 在Linux中,找不到框架“.NETFramework,Version=v4.5”的参考程序集

    我已经设置了 Visual studio 来在我的 Ubuntu 机器上编译 C 代码 我将工作区 我的代码加载到 VS 我可以看到以下错误 The reference assemblies for framework NETFramewo
  • 类型约束

    我有以下类层次结构 class Header IEnumerable
  • 将多模块 Maven 项目导入 Eclipse 时出现问题 (STS 2.5.2)

    我刚刚花了最后一个小时查看 Stackoverflow com 上的线程 尝试将 Maven 项目导入到 Spring ToolSuite 2 5 2 中 Maven 项目有多个模块 当我使用 STS 中的 Import 向导导入项目时 所
  • 启动时的 Excel 加载项

    我正在使用 Visual C 创建 Microsoft Excel 的加载项 当我第一次创建解决方案时 它包含一个名为 ThisAddIn Startup 的函数 我在这个函数中添加了以下代码 private void ThisAddIn
  • 使用valgrind进行GDB远程调试

    如果我使用远程调试gdb我连接到gdbserver using target remote host 2345 如果我使用 valgrind 和 gdb 调试内存错误 以中断无效内存访问 我会使用 target remote vgdb 启动
  • Java中未绑定通配符泛型的用途和要点是什么?

    我不明白未绑定通配符泛型有什么用 具有上限的绑定通配符泛型 stuff for Object item stuff System out println item Since PrintStream println 可以处理所有引用类型 通
  • 如何在 Qt 应用程序中通过终端命令运行分离的应用程序?

    我想使用命令 cd opencv opencv 3 0 0 alpha samples cpp cpp example facedetect lena jpg 在 Qt 应用程序中按钮的 clicked 方法上运行 OpenCV 示例代码
  • 在视口中查找 WPF 控件

    Updated 这可能是一个简单或复杂的问题 但在 wpf 中 我有一个列表框 我用一个填充数据模板从列表中 有没有办法找出特定的数据模板项位于视口中 即我已滚动到其位置并且可以查看 目前我连接到了 listbox ScrollChange
  • 使用 SAX 进行 XML 解析 |如何处理特殊字符?

    我们有一个 JAVA 应用程序 可以从 SAP 系统中提取数据 解析数据并呈现给用户 使用 SAP JCo 连接器提取数据 最近我们抛出了一个异常 org xml sax SAXParseException 字符引用 是无效的 XML 字符
  • 保护 APK 中的字符串

    我正在使用 Xamarin 的 Mono for Android 开发一个 Android 应用程序 我目前正在努力使用 Google Play API 添加应用内购买功能 为此 我需要从我的应用程序内向 Google 发送公共许可证密钥
  • 高效列出目录中的所有子目录

    请参阅迄今为止所采取的建议的编辑 我正在尝试使用 WinAPI 和 C 列出给定目录中的所有目录 文件夹 现在我的算法又慢又低效 使用 FindFirstFileEx 打开我正在搜索的文件夹 然后我查看目录中的每个文件 使用 FindNex
  • 查看Jasper报告执行的SQL

    运行 Jasper 报表 其中 SQL 嵌入到报表文件 jrxml 中 时 是否可以看到执行的 SQL 理想情况下 我还想查看替换每个 P 占位符的值 Cheers Don JasperReports 使用 Jakarta Commons
  • 打印大型 WPF 用户控件

    我有一个巨大的数据 我想使用 WPF 打印 我发现WPF提供了一个PrintDialog PrintVisual用于打印派生的任何 WPF 控件的方法Visual class PrintVisual只会打印一页 因此我需要缩放控件以适合页面
  • 将 JTextArea 内容写入文件

    我在 Java Swing 中有一个 JTextArea 和一个 提交 按钮 需要将textarea的内容写入一个带有换行符的文件中 我得到的输出是这样的 它被写为文件中的一个字符串 try BufferedWriter fileOut n
  • Unity:通过拦截将两个接口注册为一个单例

    我有一个实现两个接口的类 我想对该类的方法应用拦截 我正在遵循中的建议Unity 将两个接口注册为一个单例 https stackoverflow com questions 1394650 unity register two inter
  • OpenGL:仅获取模板缓冲区而没有深度缓冲区?

    我想获取一个模板缓冲区 但如果可能的话 不要承受附加深度缓冲区的开销 因为我不会使用它 我发现的大多数资源表明 虽然模板缓冲区是可选的 例如 排除它以利于获得更高的深度缓冲区精度 但我还没有看到任何请求并成功获取仅 8 位模板缓冲区的代码
  • 实体框架中的“it”是什么

    如果以前有人问过这个问题 请原谅我 但我的任何搜索中都没有出现 它 我有两个数据库表 Person 和 Employee 对每个类型的表进行建模 例如 Employee is a Person 在我的 edmx 设计器中 我定义了一个实体
  • com.jcraft.jsch.JSchException:身份验证失败

    当我从本地磁盘上传文件到远程服务器时 出现这样的异常 com jcraft jsch JSchException Auth fail at org apache tools ant taskdefs optional ssh Scp exe
  • 可访问性不一致:参数类型的可访问性低于方法

    我试图在两个表单之间传递一个对象 基本上是对当前登录用户的引用 目前 我在登录表单中有一些类似的内容 private ACTInterface oActInterface public void button1 Click object s
  • 堆栈是向上增长还是向下增长?

    我在 C 中有这段代码 int q 10 int s 5 int a 3 printf Address of a d n int a printf Address of a 1 d n int a 1 printf Address of a

随机推荐

  • va_list 详解

    VA LIST 是在C语言中解决变参问题的一组宏 VA LIST的成员 1 va list型变量 ifdef M ALPHA typedef struct char a0 pointer to first homed integer arg
  • 【Spring 核心

    IoC IoC 简介 定义 IoC 和 DI Bean IoC 容器 Ioc IoC容器 IoC 简介 定义 IoC即控制反转 Inversion of Control 缩写为 IoC IoC又称为依赖倒置原则 设计模式六大原则之一 IoC
  • 技术文档工程师笔试_如何帮助工程师制作技术文档

    技术文档工程师笔试 As discussed in my previous post technical writers are a vital part of any team They focus on creating documen
  • FPGA计数器边界问题解析

    FPGA计数器边界问题解析 一次作者在处理AMBE2000数据接收过程中 遇到一个问题 对该计数器边界总是模糊不清 现在予以说明 以警示以后工作时书写错误代码 AMBE2000数据一旦准备好后 一次会输出24个字 其中第1个字0x13ec是
  • 数智人力时代,如何通过人才精细化管理发挥员工最大效能

    人才作为企业竞争中最活跃 也最有创造力的资源要素 管理他们同样也不得马虎 一刀切和单一维度地进行人才分类 不利于员工充分发挥主观能动性 进而提升组织能力 而要让员工在工作中有成就感 获得感和主动性 就需要进行人才精细化管理 对症下药 才能实
  • thinter打开新窗口隐藏主窗口并实现窗口切换

    from tkinter import windows Tk windows geometry 500x300 windows title 主窗口 def b windows withdraw 隐藏主窗口 global root root
  • 百度AI(一)

    前言 第一步 在百度AI上注册账号 在控制台内创建属于你的相应的应用 以下是创建完成后的 API Key SecretKey 是俩个要用到的参数 根据文档 选择相应的API 人脸对比请求地址 发送请求获取 access token 注意 a
  • keepalived + lvs (DR)

    目录 一 概念 二 实验流程命令 三 实验的目的 四 实验步骤 一 概念 Keepalived和LVS Linux Virtual Server 可以结合使用来实现双机热备和负载均衡 Keepalived负责监控主备服务器的可用性 并在主服
  • MySQL —— 复合查询

    目录 MySQL复合查询 一 基本查询回顾 二 多表查询 三 自连接 四 子查询 1 单行子查询 2 多行子查询 3 多列子查询 4 在from子句中使用子查询 五 合并查询 MySQL复合查询 一 基本查询回顾 前面我们讲解的mysql表
  • 2023年——个人每日分享汇总

    摘要 今年是每日分享的第四个年头 在这几年分享中 虽然回头看不知道当时分享内容由何而感 分享内容现在也遗忘记不住 但是这个过程中能够感觉到自己的一个改变 现在的内容错别字少了 也会检查一下语句是否通顺 修行 修心 成长 价值 学到一个四阶段
  • vue过滤器和修饰符

    过滤器的作用 在我们页面显示值之前加一层过滤 展示我们过滤后的值 注意事项 过滤器可以用在两个地方 双花括号插值和 v bind 表达式 使用语法 变量 过滤器名 1 全局定义 Vue filter gettime function dat
  • 百度墨卡托坐标转百度经纬度坐标Python

    本文参考 https blog csdn net qq 16664325 article details 67639684 原文用的是java语言 我只是把它转成Python语言 xu 6370996 81 Sp 1 289059486E7
  • shell脚本----if(数字条件,字符串条件,字符串为空)

    二元比较操作符 比较变量或者比较数字 注意数字与字符串的区别 1 整数比较 cpp view plain copy print eq 等于 如 if a eq b ne 不等于 如 if a ne b gt 大于 如 if a gt b g
  • plt.rcParams参数详解

    plt rcParams参数详解 0 plt rcParams参数 1 Font 1 1 字体 1 2 样式 新增 0 plt rcParams参数 plt rcParams keys font 1 Font 1 1 字体 plt rcPa
  • 数字化时代-20:一张图看清中国金融市场的轮廓

    关键词 资本 金钱 金融 银行 证券 保险 财政 中国制度优势 前言 本文试图通过图解的方式 从宏观上对中国的金融市场有一个初步的认识 在金融市场上流动的鲜血是金钱 金钱是金融市场 甚至整个经济的血液 金钱的充沛的流动给整个经济注入活力 金
  • 蓝桥杯 ADV-319 VIP试题 哈密尔顿回路(试题解析)

    试题 算法提高 哈密尔顿回路 提交此题 评测记录 资源限制 时间限制 2 0s 内存限制 256 0MB 问题描述 给出一个有向图 输出这个图的一个哈密尔顿回路 输入格式 输入的第一行包含两个整数n m 分别表示图的点数和边数 接下来m行
  • Java基础-实现zip解压缩

    可实现 文件 文件夹的解压缩操作 import java io File import java io FileInputStream import java io FileOutputStream import java io IOExc
  • Flutter控件——常用控件:Text

    Text text dart 源码 const Text String this data 要显示的字符串 Key key this style 样式TextStyle this strutStyle this textAlign 文本应如
  • 【C++】IO流

    文章目录 1 自定义类型与内置类型的相互转化 1 1operator 类型 2 C 文件IO 3 C 文件IO的二进制读取和文本读取 4 stringstream 类 1 自定义类型与内置类型的相互转化 在做IO类型的OJ的时候 有多个接收
  • 代码潜在故障的动态分析

    b size large 引子 size b 大家都听说过FindBugs的大名 这是一款静态代码分析的工具 能够直接对字节码文件加以分析 并发现潜在的反模式 anti pattern 从而有效地促进代码质量的改善 但FindBugs只能用