公开内部表示¶
ID: java/internal-representation-exposure
Kind: problem
Security severity:
Severity: recommendation
Precision: high
Tags:
- reliability
- maintainability
- modularity
- external/cwe/cwe-485
Query suites:
- java-security-and-quality.qls
当对象意外地向对象外部的代码公开其内部表示,并且内部表示随后(故意或意外地)以对象无法处理的方式修改时,就会导致一种微妙的缺陷类型。最常见的情况是,当 getter 返回对对象中可变字段的直接引用时,或者 setter 只是将可变参数分配给其字段时。
建议¶
有三种方法可以解决此问题
使用不可变对象:字段存储的是不可变的对象,这意味着一旦构建,其值就永远不会改变。标准库中的示例有
String
、Integer
或Float
。尽管此类对象可以是别名,或在多个上下文中共享,但由于无法修改对象,因此不会对对象的内部状态进行意外更改。创建只读视图:
java.util.Collections.unmodifiable*
方法可用于创建集合的只读视图,而无需复制它。这往往比创建对象的副本提供更好的性能。请注意,此技术并不适用于所有情况,因为对基础集合的任何更改都会传播到视图。这可能导致意外结果,并且在编写多线程代码时尤其危险。进行防御性复制:每个 setter(或构造函数)都会对传入参数进行复制或克隆。通过这种方式,它构造了一个仅在内部已知的实例,无论传入的对象发生什么情况,状态都保持一致。相反,字段的每个 getter 也必须构造字段值的副本才能返回。
示例¶
在以下示例中,私有字段 items
由 getter getItems
直接返回。因此,调用者获取对内部对象状态的引用,并可以操作购物车中的项目集合。在示例中,每次调用 countItems
时,每个购物车都会被清空。
public class Cart {
private Set<Item> items;
// ...
// AVOID: Exposes representation
public Set<Item> getItems() {
return items;
}
}
....
int countItems(Set<Cart> carts) {
int result = 0;
for (Cart cart : carts) {
Set<Item> items = cart.getItems();
result += items.size();
items.clear(); // AVOID: Changes internal representation
}
return result;
}
解决方案是让 getItems
返回实际字段的副本,例如 return new HashSet<Item>(items);
。