《Java安全编码标准》一3.1 DCL00-J防止类的循环初始化

简介: 本节书摘来自华章出版社《Java安全编码标准》一书中的第3章,第3.1节,作者 (美)Fred Long,Dhruv Mohindra,Robert C. Seacord,Dean F. Sutherland,David Svoboda,更多章节内容可以访问云栖社区“华章计算机”公众号查看

3.1 DCL00-J防止类的循环初始化

在Java语言规范(Java Language Specification, JLS)第 12.4节“对类和接口的初始化”中提到[JLS 2005]:
对类进行的初始化包括执行该类的static静态初始化方法和初始化该类中的静态数据成员(类变量)。
换句话说,一个静态数据成员的出现会触发类的初始化。然而,一个静态数据成员可能会依赖于其他类的初始化,这样有可能形成一个初始化循环。在JLS的8.3.2.1节“类变量的初始化”中也提到[JLS 2005]:
在运行态中,使用编译期的常量来初始化的final的static变量,是最先初始化的。
这个JLS的描述会让人误解。误解之处在于,对于某些对象实例而言,变量就算是static final的,它们的初始化也可能会安排在后期进行。声明一个字段为static final并不能够保证它在被读之前已经完全初始化。
一般来说,程序,特别是对安全敏感的程序,必须消除所有的类初始化循环。

3.1.1 不符合规则的代码示例(类内循环)

不符合规则的代码示例如下,它存在一个涉及多个类的类初始化循环。

public class Cycle {
??private final int balance;
??private static final Cycle c = new Cycle();?
??// Random deposit
??private static final int deposit = (int) (Math.random() * 100);

??public Cycle() {
????balance = deposit – 10; // Subtract processing fee
??}

??public static void main(String[] args) {
????System.out.println("The account balance is: " + c.balance);
??}
}
AI 代码解读

Cycle类声明了一个private static final类变量,这个变量会在创建Cycle对象的时候进行初始化。静态的初始化方法可以保证只调用一次,而这次调用是在第一次使用静态类变量或者在第一次调用构造函数之前发生。
程序员希望通过在存款账户中减去处理费用来计算账户余额。然而,对类变量c的初始化在deposit变量被初始化之前发生,因为在编码上,它在deposit域的初始化之前出现。因此,当对变量C进行静态初始化时,Cycle类的构造函数会读取deposit的数值,这个deposit数值会是0而并非一个随机值。结果是,账户余额计算下来的值是-10。
JLS允许实现忽略这种可能出现的循环的初始化[Bloch 2005a]。

3.1.2 符合规则的方案(类内循环)

这个方案改变了类Cycle的初始化次序,所以对数据成员的初始化是不会造成任何依赖循环的。特别是对c的初始化,因为在编码上处于对deposit初始化之后发生,因而它会在deposit被完全初始化之后进行初始化。

public class Cycle {
??private final int balance;
??// Random deposit
??private static final int deposit = (int) (Math.random() * 100);?
??// Inserted after initialization of required fields
??private static final Cycle c = new Cycle();??
??public Cycle() {
????balance = deposit - 10; // Subtract processing fee
??}

??public static void main(String[] args) {
????System.out.println("The account balance is: " + c.balance);
??}
}
AI 代码解读

当涉及许多字段的时候,并不容易发现这样的初始化循环。因此,确保控制流不产生这样的循环就非常重要。
尽管这个方案可以防止初始化循环,但它也依赖于声明次序,因而也是脆弱的。程序的维护者可能不知道这种声明的次序必须被保持来保证程序的正确性。因而,这种依赖必须在代码的文档中清楚地说明。

3.1.3 不符合规则的代码示例(类间循环)

这个不符合规则的代码示例声明了两个类,这两个类都有静态变量,这些变量的值是互相依赖的。当把这两个类放在一起的时候,这个循环是很显然的;而当把它们分开的时候,却很容易忽略这点。

class A {
??public static final int a = B.b + 1;
??// ...
}

class B {
??public static final int b = A.a + 1;
??// ...
}
AI 代码解读

因为对这些类的初始化次序是可变的,所以导致的结果是,会计算出不同的A.a?和B.b的值。当先初始化A类时,A.a的值是2,而B.b的值是1。当先初始化B类时,这两个值就要反过来了。

3.1.4 符合规则的方案(类间循环)

与规则一致的方案打破了这个类间循环,这是通过消除其中一个依赖来完成的。

class A {
??public static final int a = 2;
??// ...
}
// class B unchanged: b = A.a + 1
AI 代码解读

当打破这个循环时,初始化的值是不变的,总是A.a=2且B.b=3,并且它与初始化次序无关。

3.1.5 风险评估

初始化循环会导致不可预测的结果。
image

3.1.6 相关规范

image

3.1.7 参考书目

image
image

相关文章
|
12天前
|
重学Java基础篇—Java类加载顺序深度解析
本文全面解析Java类的生命周期与加载顺序,涵盖从加载到卸载的七个阶段,并深入探讨初始化阶段的执行规则。通过单类、继承体系的实例分析,明确静态与实例初始化的顺序。同时,列举六种触发初始化的场景及特殊场景处理(如接口初始化)。提供类加载完整流程图与记忆口诀,助于理解复杂初始化逻辑。此外,针对空指针异常等问题提出排查方案,并给出最佳实践建议,帮助开发者优化程序设计、定位BUG及理解框架机制。最后扩展讲解类加载器层次与双亲委派机制,为深入研究奠定基础。
34 0
|
20天前
|
《从头开始学java,一天一个知识点》之:循环结构:for与while循环的使用场景
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问"`a==b`和`equals()`的区别",大脑突然空白
64 22
|
18天前
|
《从头开始学java,一天一个知识点》之:输入与输出:Scanner与System类
你是否也经历过这些崩溃瞬间?三天教程连`i++`和`++i`都说不清,面试时`a==b`与`equals()`区别大脑空白,代码总是莫名报NPE。这个系列就是为你打造的Java「速效救心丸」!每天1分钟,地铁通勤、午休间隙即可学习。直击高频考点和实际开发中的“坑位”,拒绝冗长概念,每篇都有可运行代码示例。涵盖输入输出基础、猜数字游戏、企业编码规范、性能优化技巧、隐藏技能等。助你快速掌握Java核心知识,提升编程能力。点赞、收藏、转发,助力更多小伙伴一起成长!
43 19
重学Java基础篇—类的生命周期深度解析
本文全面解析了Java类的生命周期,涵盖加载、验证、准备、解析、初始化、使用及卸载七个关键阶段。通过分阶段执行机制详解(如加载阶段的触发条件与技术实现),结合方法调用机制、内存回收保护等使用阶段特性,以及卸载条件和特殊场景处理,帮助开发者深入理解JVM运作原理。同时,文章探讨了性能优化建议、典型异常处理及新一代JVM特性(如元空间与模块化系统)。总结中强调安全优先、延迟加载与动态扩展的设计思想,并提供开发建议与进阶方向,助力解决性能调优、内存泄漏排查及框架设计等问题。
32 5
|
19天前
|
《从头开始学java,一天一个知识点》之:字符串处理:String类的核心API
🌱 **《字符串处理:String类的核心API》一分钟速通!** 本文快速介绍Java中String类的3个高频API:`substring`、`indexOf`和`split`,并通过代码示例展示其用法。重点提示:`substring`的结束索引不包含该位置,`split`支持正则表达式。进一步探讨了String不可变性的高效设计原理及企业级编码规范,如避免使用`new String()`、拼接时使用`StringBuilder`等。最后通过互动解密游戏帮助读者巩固知识。 (上一篇:《多维数组与常见操作》 | 下一篇预告:《输入与输出:Scanner与System类》)
46 11
|
12天前
|
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
21 1
|
25天前
|
课时14:Java数据类型划分(初见String类)
课时14介绍Java数据类型,重点初见String类。通过三个范例讲解:观察String型变量、"+"操作符的使用问题及转义字符的应用。String不是基本数据类型而是引用类型,但使用方式类似基本类型。课程涵盖字符串连接、数学运算与字符串混合使用时的注意事项以及常用转义字符的用法。
课时11:综合实战:简单Java类
本次分享的主题是综合实战:简单 Java 类。主要分为两个部分: 1.简单 Java 类的含义 2.简单 Java 类的开发
课时37:综合实战:数据表与简单Java类映射转换
今天我分享的是数据表与简单 Java 类映射转换,主要分为以下四部分。 1. 映射关系基础 2. 映射步骤方法 3. 项目对象配置 4. 数据获取与调试
java常见的集合类有哪些
Map接口和Collection接口是所有集合框架的父接口: 1. Collection接口的子接口包括:Set接口和List接口 2. Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及 Properties等 3. Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等 4. List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等