代码潜在故障的动态分析

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(使用前将#替换为@)

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

  • 创建链表而不将节点声明为指针

    我已经在谷歌和一些教科书上搜索了很长一段时间 我似乎无法理解为什么在构建链表时 节点需要是指针 例如 如果我有一个节点定义为 typedef struct Node int value struct Node next Node 为什么为了
  • 显示UnityWebRequest的进度

    我正在尝试使用下载 assetbundle统一网络请求 https docs unity3d com ScriptReference Networking UnityWebRequest GetAssetBundle html并显示进度 根
  • 控件的命名约定[重复]

    这个问题在这里已经有答案了 Microsoft 在其网站上提供了命名指南 here http msdn microsoft com en us library xzf533w0 VS 71 aspx 我还有 框架设计指南 一书 我找不到有关
  • Android 中麦克风的后台访问

    是否可以通过 Android 手机上的后台应用程序 服务 持续监控麦克风 我想做的一些想法 不断聆听背景中的声音信号 收到 有趣的 音频信号后 执行一些网络操作 如果前台应用程序需要的话 后台应用程序必须能够智能地放弃对麦克风的访问 除非可
  • Java列表的线程安全

    我有一个列表 它将在线程安全上下文或非线程安全上下文中使用 究竟会是哪一个 无法提前确定 在这种特殊情况下 每当列表进入非线程安全上下文时 我都会使用它来包装它 Collections synchronizedList 但如果不进入非线程安
  • 如何从泛型类调用静态方法?

    我有一个包含静态创建方法的类 public class TestClass public static
  • 链接器错误:已定义

    我尝试在 Microsoft Visual Studio 2012 中编译我的 Visual C 项目 使用 MFC 但出现以下错误 error LNK2005 void cdecl operator new unsigned int 2
  • 如何使用 C# / .Net 将文件列表从 AWS S3 下载到我的设备?

    我希望下载存储在 S3 中的多个图像 但目前如果我只能下载一个就足够了 我有对象路径的信息 当我运行以下代码时 出现此错误 遇到错误 消息 读取对象时 访问被拒绝 我首先做一个亚马逊S3客户端基于我的密钥和访问配置的对象连接到服务器 然后创
  • 对现有视频添加水印

    我正在寻找一种用 C 在视频上加水印的方法 就像在上面写文字一样 图片或文字标签 我该怎么做 谢谢 您可以使用 Nreco 视频转换器 代码看起来像 NReco VideoConverter FFMpegConverter wrap new
  • 玩!框架:运行“h2-browser”可以运行,但网页不可用

    当我运行命令时activator h2 browser它会使用以下 url 打开浏览器 192 168 1 17 8082 但我得到 使用 Chrome 此网页无法使用 奇怪的是它以前确实有效 从那时起我唯一改变的是JAVA OPTS以启用
  • 声明的包“”与预期的包不匹配

    我可以编译并运行我的代码 但 VSCode 中始终显示错误 早些时候有一个弹出窗口 我不记得是什么了 我点击了 全局应用 从那以后一直是这样 Output is there but so is the error The declared
  • 如何从两个不同的项目中获取文件夹的相对路径

    我有两个项目和一个共享库 用于从此文件夹加载图像 C MainProject Project1 Images 项目1的文件夹 C MainProject Project1 Files Bin x86 Debug 其中有project1 ex
  • Firebase 添加新节点

    如何将这些节点放入用户节点中 并创建另一个节点来存储帖子 我的数据库参考 databaseReference child user getUid setValue userInformations 您需要使用以下代码 databaseRef
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • 捕获的图像分辨率太大

    我在做什么 我允许用户捕获图像 将其存储到 SD 卡中并上传到服务器 但捕获图像的分辨率为宽度 4608 像素和高度 2592 像素 现在我想要什么 如何在不影响质量的情况下获得小分辨率图像 例如我可以获取或设置捕获的图像分辨率为原始图像分
  • 有没有办法为Java的字符集名称添加别名

    我收到一个异常 埋藏在第 3 方库中 消息如下 java io UnsupportedEncodingException BIG 5 我认为发生这种情况是因为 Java 没有定义这个名称java nio charset Charset Ch
  • 如何在文本框中插入图像

    有没有办法在文本框中插入图像 我正在开发一个聊天应用程序 我想用图标图像更改值 等 但我找不到如何在文本框中插入图像 Thanks 如果您使用 RichTextBox 进行聊天 请查看Paste http msdn microsoft co
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • C++ 标准是否指定了编译器的 STL 实现细节?

    在写答案时this https stackoverflow com questions 30909296 can you put a pimpl class inside a vector我遇到了一个有趣的情况 这个问题演示了这样一种情况
  • 使用.NET技术录制屏幕视频[关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 有没有一种方法可以使用 NET 技术来录制屏幕 无论是桌面还是窗口 我的目标是免费的 我喜欢小型 低

随机推荐

  • 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只能用