`
regular
  • 浏览: 77991 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

代码潜在故障的动态分析

    博客分类:
  • Java
阅读更多
引子
大家都听说过FindBugs的大名。这是一款静态代码分析的工具。能够直接对字节码文件加以分析,并发现潜在的反模式(anti-pattern),从而有效地促进代码质量的改善。

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

动态代码分析
所谓动态代码分析,就是相对于静态代码的分析。这是一句废话,就当立论了吧。

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类将同时拥有两个匿名类,两个类的名称并非顺序排列,在不同的编译环境中可能产生不同的类名,因此序列化和反序列化可能会导致失败。

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

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

目标
  • 能够监管代码的运行
  • 能够记录代码的某些操作
  • 能够发现代码的某些反模式
  • 不能对代码文件造成任何改变
  • 不能让代码的运行依赖于检查
  • 不能过多干涉代码的运行,乃至重建JVM实现(过于厚重)

实现方案
经过以上分析,我们可以想见,这个方案是涉及到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);
    }

    //...

效果
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!


参考网页:
Writing Your Own ClassLoader

AOP 的利器:ASM 3.0 介绍
分享到:
评论

相关推荐

    动态故障树最小割序集求解java代码

    动态故障树(Dynamic Fault Tree,DFT)是一种系统安全工程中的分析方法,用于评估系统在各种潜在故障条件下的行为和可靠性。它通过图形化的方式表示系统中各组件的故障模式及其相互关系,帮助分析人员识别可能导致...

    【故障分析】轴承故障分析(时频图+功率谱)含Matlab源码.zip

    在轴承故障分析中,异常的功率谱特征可能预示着轴承存在潜在问题。通过分析功率谱,我们可以识别出异常频率,这些频率往往与故障模式有关,比如滚动体缺陷可能会产生特定的谐波频率。 Matlab中的Signal Processing ...

    【故障诊断分析】基于傅里叶变换FFT提取承故障特征附代码.zip

    在故障诊断领域,傅里叶变换(Fast Fourier Transform, FFT)是一种非常重要的工具,它能够帮助我们从时域信号中提取出频域特征,从而识别设备的潜在故障。本资料包"【故障诊断分析】基于傅里叶变换FFT提取承故障...

    【故障诊断分析】基于 FFT轴承故障诊断matlab代码.zip

    文件"【故障诊断分析】基于 FFT轴承故障诊断matlab代码.pdf"应该是详细讲解如何使用MATLAB进行FFT处理和轴承故障诊断的教程,包括理论背景、代码实现步骤以及可能的结果分析。通过阅读和理解这份文档,工程师和研究...

    MATLAB代码 信号分析 VMD分解代码 包络谱分析

    总结来说,MATLAB代码“信号分析 VMD分解代码 包络谱分析”是电机健康监测的有效工具,它利用VMD进行信号分解,结合时域、频域和包络谱分析,帮助工程师诊断电机轴承的潜在故障。通过理解并应用这些技术,可以在早期...

    Fast- Kurtogram_齿轮故障诊断_快速谱峭度源代码_包络滤波_齿轮故障_包络峭度_源码.rar

    这些源代码可能是用Python、MATLAB或其他编程语言编写的,用户可基于这些代码对实际采集的齿轮振动数据进行分析,生成相应的谱峭度图和包络滤波结果,以判断齿轮是否存在潜在的故障问题。 对于工程师和研究人员来说...

    IBM Storwize V系列存储错误代码

    这要求他们不仅要有扎实的技术功底,还需保持对最新技术动态的敏感,及时获取和学习相关的技术资料和更新,以便更高效地应对各种潜在的问题。此外,合理地维护存储环境,定期进行硬件检测和软件更新,也能有效预防...

    基于主成分分析(PCA)的故障诊断

    PCA(主成分分析)是一种广泛应用于数据分析和机器学习领域的统计方法,主要...总的来说,PCA在故障诊断中的应用充分体现了数据分析在工业领域的重要性,它能帮助我们提前发现并解决潜在问题,避免设备停机造成的损失。

    weblogic定期故障分析

    ### WebLogic定期故障分析 #### 关键知识点梳理与解析 **一、背景介绍** WebLogic作为一款广泛应用的企业级中间件,常被部署于关键业务场景之中。然而,在实际运行过程中,偶尔会出现性能瓶颈甚至宕机的情况。...

    PF_Code.rar_fault diagnosis_故障_故障诊断_故障诊断 matlab_粒子滤波 matlab

    标题 "PF_Code.rar_fault 诊断_故障_故障诊断_故障诊断 matlab_粒子滤波 matlab" 指涉的是一...通过分析和运行提供的MATLAB代码,学习者不仅可以掌握粒子滤波的基本原理,还能了解如何将其应用于实际的故障诊断问题中。

    dianlixitong.zip_故障_故障 仿真_故障仿真_电力系统分析_电力系统故障

    4. 分析结果:通过分析仿真结果,评估系统对故障的响应,识别潜在的稳定性问题,并为保护策略的优化提供依据。 在提供的文件"21840252untitled1"中,可能包含了电力系统故障仿真的具体实现代码或模型。这些代码通常...

    DGA-master_变压器故障_DGA变压器_matlabsimulink_dga_DGAmaster_

    3. **matlabsimulink**:MATLAB Simulink是一款强大的可视化建模工具,用于系统仿真和多域动态分析。在本项目中,它可能被用来构建故障诊断的模型和算法。 4. **dga**:直接对应离散光谱分析,是诊断变压器故障的...

    book-CD.rar_fault _matlab轴心轨迹_故障诊断_轴心轨迹代码_轴心轨迹图

    通过MATLAB编写的程序代码能够实现对旋转机械状态的深入分析,这对于及时发现和诊断设备潜在故障具有显著意义。 轴心轨迹分析技术是故障诊断中的一项关键技术,它通过分析旋转轴的动态振动信号,绘制出轴心在设备...

    xiaobo_故障小波分析_xiaobo_小波分析_故障信号_振动信号分析_

    小波分析是一种强大的数学...通过小波变换,我们可以揭示信号的局部特征和动态变化,从而有效地识别潜在的设备故障。"xiaobo.m"文件中的代码可能是实现这一过程的关键,包括信号读取、小波变换、特征提取以及结果解释。

    故障分析】轴承故障分析(时频图+功率谱)含Matlab源码.zip.zip

    在轴承故障分析中,异常的功率谱特征可能预示着轴承存在潜在问题。通过分析功率谱,我们可以识别出异常频率,这些频率往往与故障模式有关,比如滚动体缺陷可能会产生特定的谐波频率。 Matlab中的Signal Processing ...

    轴承动力学建模matlab_ODE45_故障诊断_matlab轴承_轴承_loseifk

    轴承的动力学建模对于理解和预测其性能、寿命以及潜在故障至关重要。在本项目中,我们利用MATLAB进行轴承动力学的建模与故障诊断。MATLAB中的ODE45函数是求解常微分方程(ODE)的工具,特别适用于模拟动态系统。 一...

    故障分析】轴承故障分析(时频图+功率谱)含Matlab源码.zip1.zip

    在轴承故障分析中,异常的功率谱特征可能预示着轴承存在潜在问题。通过分析功率谱,我们可以识别出异常频率,这些频率往往与故障模式有关,比如滚动体缺陷可能会产生特定的谐波频率。 Matlab中的Signal Processing ...

    松下伺服发生故障报警代码一览与对策.doc

    【松下伺服驱动器故障分析与对策】 松下伺服驱动器在运行过程中可能出现各种故障,这些故障通常由报警错误代码来表示。以下是针对部分报警代码的详细解释和对应的解决策略: 1. 故障代码110:控制电源电压不足保护...

Global site tag (gtag.js) - Google Analytics