Java一些安全编码标准(第二版)

一、不要在断言中使用有副作用的表达式

使用 assert 断言语句是一个方便的机制,可以用来在代码中加人诊断测试。使用标准断言语句的表达式必须避免副作用。通常,这种 assert 断言语句的行为依赖于运行属性的状态。当开启时,assert 语句会用来计算表达式的值,并在表达式的值是 false 的时候抛出 AssertionError 异常当关闭时,assert 被定义为什么也不执行。在关闭断言功能的时候,所有由断言中计算的表达式产生的副作用都会丢失。因而,程序禁止在断言中使用有副作用的表达式。

(一)、不符合的示例代码
这个不符合规则的代码示例想要在断言中将一个列表中的空命名删除。然而,对于 boolean表达式而言,当断言关闭时,它是不会被计算的。

private ArrayList<String> names;
void process(int index){
	assert names.remove(nul1);
}

(二)、符合的示例代码
在断言中,需要避免任何可能的副作用。这可以通过将 boolcan 表达式从断言中去掉来实现。

private ArrayList<String> names;
void process(int index) {
	boolean nullsRemoved = names.remove(null);
	assert nullsRemoved; // no side-effect
}

二、不要对同一数据进行位运算和数学运算

整数变量通常是用来表达数值或是位的集合。对数值只能进行数学运算,而对位的集合只能进行逻辑运算。然而,静态分析工具常常不能判断某一特定的整数变量所表达的意图。对同一数据进行位运算和数学运算的时候,暗示着对变量数据解析的混淆。遗憾的是,位运算常常被作为对计算过程进行优化的一种方式。位运算包括~、<<、>>、>>>、&、和|。虽然这些操作符是可以编译的合法元素,但是它们会降低代码的可读性。
(一)、不符合的示例代码
在以下不符合规则的代码示例中,位运算和数学运算运用在不同的变量上。变量 x 只用在位运算中,而变量y只用于数学运算中。

int x = 50;
int y = x<<2;
y += y+1;

(二)、符合的示例代码
在这个符合规则的方案中,赋值语句被修改为用来反映变量 在算术上的本质,从而更清晰地表明了程序员的意图。

int x  = 50;
x = 5 * x + l;

三、确保除法运算和模运算中的除数不为0

在除法运算和模运算中,通常会发生除数为0的错误。所以,必须在运算前检查除数是否为0。
(一)、不符合的示例代码
除法的结果是第一操作数除以第二操作数所得的商。除法运算中常会出现除数为0的错误当被除数等于有符号整数的最小(负) 值并且除数是 -1时,在二进制补码有符号的整数除法中也会溢出。在进行有符号操作数 num1 和 num2 的除法运算时,这个不符合规则的代码示例也会发生除 0 的错误。

long numl,num2,result;
/* Initialize numl and num2 */
result =numl / num2;

(二)、符合的示例代码
这个符合规则的方案中的代码对除数进行检查,从而杜绝了发生除 0 错误的可能性。

long num1,num2,result;
/* Initialize numl and num2*/
if (num2==0){
	// handle error
}else {
	result=numl/ num2;
}

(三)、不符合的示例代码
当操作数是整数类型时,模操作符会计算除法中的余数。这个不符合规则的代码示例在对有符号操作数 num1和num2 进行模运算时,会发生除0错误。

long num1,num2,result;
/* Initialize numl and num2 */
result = numl % num2;

(四)、符合的示例代码
这个符合规则的方案中的代码对除数进行检查,从而杜绝了发生除 0 错误的可能性。

long num1,num2,result;
/* Initialize numl and num2*/
if (num2==0){
	// handle error
}else {
	result=numl % num2;
}

四、不要使用浮点数进行精细计算

Java 语言提供两种基本浮点数据类型:float 型和double 型。它们是依据IEEE 754]IEEE 754标准确立的,该标准为 32 位单精度类型和 64 位双精度类型定义了数值和相关操作。flat 型和double 型各自有固定的位数来表示尾数。所以,用浮点数来精确地表达无理数是不可能的(例如")。此外,这些类型使用二进制来表示尾数,这意味着这些类型也不能精确地表达许多有限小数,例如 0.1,因为这类数据的二进制表达是无限循环的。当需要进行精确计算时,例如货币兑换,不能使用浮点类型,而应该使用其他能完整表达数
据的方式。浮点类型可以运用在对计算精度要求不高的情况中,但是必须系统地估计最大的累积计算误差并确保其在可接受的范围内。这个问题可以考虑用数值分析来解决。可以参考 Goidberg 的著作中对这个问题的介绍[Goldberg 1991]。
(一)、不符合的示例代码
这段代码进行一些简单货币兑换计算。

double dollar = 1.00;
double dime = 0.10;
int number = 7;
System.out.println("A dollar less "+number +"dimes is $"+(dollar - number * dime));

因为 0.10 不能用 Java 的浮点类型 (或任何使用二进制尾数的浮点类型)来精确表达,所以在大多数的平台上会有以下输出:

A dollar less 7 dimes is $0.29999999999999993

(二)、符合的示例代码
这个符合规则的方案用一个整数类型(例如 long 型)来进行计算,而且数值的单位是美分而不是上例中的美元。

long dollar = 100;
long dime 10;
int number=7;
System.out.println("A dollar less"+number +" dimes is"+(dollar - number * dime) + " cents" );

这段代码正确地输出以下内容:

A dollar less 7 dimes is 30 cents

(三)、符合的示例代码
这个符合规则的方案使用了可以精确表达小数的 BigDecimal 类型。要注意的是,在大多数的平台上,相对于基础数据类型,用 BigDecimal 来进行计算,效率比较低。程序性能上的损失是否重要需要根据实际应用来衡量。

import java.math.BigDecimal;
BigDecimal dollar = BigDecimal.valueOf(1.0);
BigDecimal dime= BigDecimal.valueOf(0.1);
int number = 7;
System.out.println("A dollar less "+number +" dimes is $"+(dollar,subtract(new BigDecimal(number).multiply(dime) )) );

这段代码正确地输出以下内容:

A dollar less 7 dimes is 30 cents

五、不要尝试与 NaN 进行比较

因为 NaN (Not-a-Number,非数值)是无序的,所以当一个或两个操作数是 NaN 时,数值比较符 <、<=、>= 和>= 会返回 false。当任意一个操作数是 NaN,相等运算符 == 会返回false,当任意一个操作数是 NaN,不等运算符!= 会返回 true。
因为无序的特性常常导致意外结果,所以不能和 NaN 进行直接比较。当程序员编写比较浮点数值代码,而没有考虑到 NaN 的语义时,会产生问题。例如,在输入校验中,没有考虑到输人数值是 NaN 的可能性会导致预期外的结果。更多内容可参考规则 NUMO8-J。
(一)、不符合的示例代码
这个不符合规则的代码示例尝试和 NaN 进行直接比较。根据 NaN 的语义,所有和 NaN 的比较会返回 false (除!= 运算符返回 true 之外)。因此,代码中的比较只会返回 false,并且不会输出“result is NaN”

public class NaNComparison {
	public static void main(Stringl] args){
		doublex=0.0;
		double result= Math.cos(1/x); // returns NaN if input is infinity
		if (result == Double.NaN){ // comparison is always false!
		    System.out.println("result is NaN");
		}
	}
}

(二)、符合的示例代码
这个符合规则的方案使用方法 DoubleisNaNO 来检测表达式是否是一个 NaN 数值。

public class NaNComparison {
	public static void main(Stringl] args){
		doublex=0.0;
		double result= Math.cos(1/x); // returns NaN if input is infinity
		if (Double.isNaN(result)){ // comparison is always false!
		    System.out.println("result is NaN");
		}
	}
}

六、不要使用浮点变量作为循环计数器

绝对不能使用浮点变量作为循环计数器。有限精度的 IEEE 754 浮点类型不能表达:

  • 精确的分数。
  • 精确的小数,即使是可以用少量数字就可以表达的小数。
  • 大数值的数字。这意味着在允许的精度内,对大浮点数值的增量可能并不会改变其实际数值。
    (一)、不符合的示例代码
    这个不符合规则的代码示例使用一个浮点变量作为循环计数器。小数 0.1 不能精确地用 flat甚至 double 来表达。
for(float x=0.f;x <= 1.0f;x+= 0.1f){
	System.out.printIn(x);
}

因为 0.1f会被处理成可以用 fat 类型表达的最接近的数值,所以在每次循环中,增加至x的数值是一个略大于 0.1 的值。从而,循环只会执行9次,最终并不能产生预期的结果。
(二)、符合的示例代码
这个符合规则的方案使用整数作为循环计数器,并由此得到期望的浮点数值。

for (int count=1;count <= 10;count += 1){
	float x= count/10.0f;
	System.out.printIn(x);
}

七、确保将数值转换成较小类型时不会产生数据丢失或曲解

当较大类型的数值不在较小类型的合法范围内时,将数值转换成较小类型会造成数据的丢失或曲解。因此,所有向下转换必须在转换前检查数值的范围来确保安全。
Java 中有 22 种可能的基本向下转换。根据JLS 的5.1.3 节“向下基本转换”中描述:

  • short至byte或者char
  • char至byte或者short
  • int至 byte、short 或者 char
  • long至 byte、short、char 或者 int
  • float 至byte、short、char、int 或者long
  • double至 byte、short、char、int、long 或者 float
    在较大类型的数值处于较小类型的合法范围内的情况下,基本数据类型的向下转换是允许的。
    (一)、不符合的示例代码
    这个不符合规则的代码示例将一个int 类型的数值转换成byte类型,但没有对数值进行范围检查。
class CastAway {
	public static void main(String[] args) {
		inti=128;
		workWith(i);
	}
	public static void workwith(int i) {
		byteb=(byte) i; // b has value-128
		// work with b
	}
}

因为最初的数值(128)超出了目标类型的合法范围,所以没能得到预期的结果
(二)、符合的示例代码

class CastAway {
	public static void main(String[] args) {
		inti=128;
		workWith(i);
	}
	public static void workwith(int i) {
		if((i< Byte.MIN VALUE) || i> Byte.MAX VALUE)){
			throw new ArithmeticException("Value is out of range");
		}
		byteb=(byte) i; // b has value-128
		// work with b
	}
}

(三)、不符合的示例代码(浮点类型转换成整数类型)
这个不符合规则的代码示例的基本类型的向下转换造成了数值大小和精度的损失

float i  = Float.MIN_VALUE;
float j = Float.MAX_VALUE;
short b = (short) i;
short c = (short) j;

最小和最大的浮点数值被转换成了最小和最大的整数数值 (分别是 0x80000000 和)x7FFFFFFF)。得到的结果将是这些数值的低 16 位(0x00 和 0xFFFF)。最终的数值 (0 和-1)可能并不是预期的结果。
(四)、符合的示例代码(浮点类型转换成整数类型)

float i  = Float.MIN_VALUE;
float j = Float.MAX_VALUE;
if((i<Short.MIN_VALUE) || (i> Short.MAX_VALUE) || (j < Short.MIN_VALUE) || (j> Short.MAX_VALUE)){
	throw new ArithmeticException ("Value is out of range");
}
short b = (short) i;
short c = (short) j;

(五)、不符合的示例代码(double 类型转换成 float 类型)
这个不符合规则的代码示例的基本类型的向下转换造成了数值大小和精度的损失。因为DoubleMAX VALUE 大于 Float.MAX VALUE,所以c得到的数值是无穷大;因为 Double.MIN VALUE 小于FloatMIN VALUE,所以赋予b的数值将会是0。

double i  = Double.MIN_VALUE;
double j = Double.MAX_VALUE;
float b = (float ) i;
float c = (float ) j;

最小和最大的浮点数值被转换成了最小和最大的整数数值 (分别是 0x80000000 和)x7FFFFFFF)。得到的结果将是这些数值的低 16 位(0x00 和 0xFFFF)。最终的数值 (0 和-1)可能并不是预期的结果。
(六)、符合的示例代码(double 类型转换成 float 类型)

double i  = Double.MIN_VALUE;
double j = Double.MAX_VALUE;
if((i<Float.MIN_VALUE) || (i> Float.MAX_VALUE) || (j < Float.MIN_VALUE) || (j> Float.MAX_VALUE)){
	throw new ArithmeticException ("Value is out of range");
}
float b = (float ) i;
float c = (float ) j;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农辰南

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值