发现 JDK 的 3 个 bug

2018-12-04 10:22:34 +08:00
 TommyLemon

1.Annotation 引用非空 enum 数组返回空数组

首次发现时的环境:JDK 1.8

首次发现所在项目:APIJSON

测试用例:

public enum RequestRole {

	/**未登录,不明身份的用户
	 */
	UNKNOWN,

	/**已登录的用户
	 */
	LOGIN,

	/**联系人,必须已登录
	 */
	CONTACT,

	/**圈子成员(CONTACT + OWNER),必须已登录
	 */
	CIRCLE,

	/**拥有者,必须已登录
	 */
	OWNER,

	/**管理员,必须已登录
	 */
	ADMIN;

	//似乎不管怎么做,外部引用后都是空值。并且如果在注解内的位置不是最前的,还会导致被注解的类在其它类中 import 报错。
	//虽然直接打印显示正常,但被 @MethodAccess 内 RequestRole[] GET()等方法引用后获取的是空值
	public static final RequestRole[] ALL = {RequestRole.UNKNOWN};//values();//所有
	public static final RequestRole[] HIGHS;//高级
	static {
		HIGHS = new RequestRole[] {OWNER, ADMIN};
	}

	public static final String[] NAMES = {
			UNKNOWN.name(), LOGIN.name(), CONTACT.name(), CIRCLE.name(), OWNER.name(), ADMIN.name()
	};


}


@MethodAccess(
		GETS = RequestRole.ALL,
		HEADS = RequestRole.HIGHS
		)
public class Verify {

}


public class DemoVerifier {
	// <TableName, <METHOD, allowRoles>>
	// <User, <GET, [OWNER, ADMIN]>>
    public static final Map<String, Map<RequestMethod, RequestRole[]>> ACCESS_MAP;
	static { //注册权限
        ACCESS_MAP = new HashMap<String, Map<RequestMethod, RequestRole[]>>();
		ACCESS_MAP.put(Verify.class.getSimpleName(), getAccessMap(Verify.class.getAnnotation(MethodAccess.class)));
	}

	public static HashMap<RequestMethod, RequestRole[]> getAccessMap(MethodAccess access) {
		if (access == null) {
			return null;
		}

		HashMap<RequestMethod, RequestRole[]> map = new HashMap<>();
		map.put(GET, access.GET());
		map.put(HEAD, access.HEAD());
		map.put(GETS, access.GETS());
		map.put(HEADS, access.HEADS());
		map.put(POST, access.POST());
		map.put(PUT, access.PUT());
		map.put(DELETE, access.DELETE());

		return map;
	}

}

解决方案:

不抽象数组常量 ALL,HIGHTS 等,而是在每个用到的地方硬编码写死具体的值。

2.ArrayList 可通过构造函数传入非指定泛型的 List 并在 get 时出错

首次发现时的环境:JDK 1.7

首次发现所在项目:APIJSON

测试用例:

JSONArray arr = new JSONArray(); //com.alibaba.fastjson.JSONArray
arr.add("s");

List<Long> list = new ArrayList<>(arr); 
list.get(0); //throw new IllegalArgumentException

解决方案:

1.改用 Open JDK8

2.升级 JDK

注:后面多次测试,已无法复现。

3.基本类型在三元表达式内可赋值为 null,编译通过但运行出错

首次发现时的环境:JDK 1.7

测试用例:

int i = true ? null : 0; //Exception in thread "main" java.lang.NullPointerException

首次发现所在项目:ZBLibrary

解决方案:

在给基础类型用 3 元表达式赋值时,null 先转为基础类型的默认值。

最后再提 2 个不是 bug,但容易引发编程 bug 的问题:

1.局部变量和同名的全局变量能在一个方法内,编译通过,运行也正常。

    public class Test {
        
        int val;
        @Override
        public String toString() {
            val = 1;
            String val = "";
            return super.toString();
        }
    }

如果两个变量中间隔了比较长的其它代码,很可能会导致开发人员将两者混淆,导致逻辑认知错误,从而写出或改出有问题的代码。

解决方案:

命名局部变量前先搜素,确保没有已声明的同名全局变量。

2. (非 JDK bug ) Gson 通过 TypeToken 转换 List<t> 能写入不属于 T 类型的数据,get 出来赋值给 T 类型的变量 /常量报错。</t>


        String json = "[1, '2', 'a']";
        Type type = new TypeToken<Integer>(){}.getType();
        Gson gson = new Gson();
        List<Integer> list = gson.fromJson(json, type);
        
        Integer i = list == null || list.isEmpty() ? null : list.get(0); //Exception cannot cast String to Integer

解决方案:

1.手动检查列表内数据都符合泛型 T

2.改用 fastjson 等其它能静态检查类型的库。

6488 次点击
所在节点    Java
22 条回复
Kaiv2
2018-12-04 10:55:18 +08:00
```java
JSONArray arr = new JSONArray(); //com.alibaba.fastjson.JSONArray
arr.add("s");

List<Long> list = new ArrayList<>(arr);
list.get(0); //throw new IllegalArgumentException
```

这个你确定能编译过?你看看 JSONArray 的源码

```java
public class JSONArray extends JSON implements List<Object>, Cloneable, RandomAccess, Serializable {
private static final long serialVersionUID = 1L;
private final List<Object> list;
protected transient Object relatedArray;
protected transient Type componentType;

public JSONArray() {
this.list = new ArrayList();
}
```
里面明明定义的是 `Object` 的泛型
TommyLemon
2018-12-04 11:19:03 +08:00
@Kaiv2
我当时的环境下,就是能正常编译通过,并且只在运行到 get 时崩溃。
后面写博客时,自己再试已经无法复现,所以就在文中加了备注:
“注:后面多次测试,已无法复现。”
但是可能部分其它开发者的环境下也会有这种问题,所以还是发出来更好。

源码是 Object 类型,泛型本来就是用 Object 存,编译时静态检查,通过后擦除泛型,然后取出时强转,你看看原理以及 ArrayList 的代码就知道了。
当时导致这个 bug 的原因是 ArrayList 构造函数是
```java
public ArrayList(Collection<?> c) { //还是 Collection<? extends Object> 来着,记不清了,但肯定是两者中的一个
...
}
```
后面我再次测试,以及在 Android SDK 中看到的都是
```java
public ArrayList(Collection<? extends E> c) {
...
}
```
然后就标记错误,无法编译通过了。
sorra
2018-12-04 12:57:18 +08:00
哪款编译器,啥版本?
1. 编译器没报错?
还有一种可能,这些类属于不同的 jar 分开编译,都放一起编译试试

2. 编译器没警告?

3. 确实是问题,编译器没警告,需要靠 IDE 警告。
27
2018-12-04 14:03:09 +08:00
第三个可以写成
Integer i = true ? null : 0;
TommyLemon
2018-12-04 15:07:17 +08:00
@sorra
1.代码注释里有提到:
“如果在注解内的位置不是最前的,会导致被注解的类在其它类中 import 报错。”
其它情况就不报错,运行时在注解里拿到的值始终是空数组,但是非注解的地方拿到的值又是正常的(例如打印)。
都是同一个 1.8 版本的 jar 编译的

2.发博客前测试时会有报错的,具体看 #2 楼我的回复。

3.都没任何提示,运行直接抛异常。

以上 bug 都是在我个人的设备上发现的,具体的设备信息、环境信息等,只能等回去再看看。
TommyLemon
2018-12-04 15:17:07 +08:00
@27 以上所有用例都是简化版本,实际业务代码里都不是这么直接的写出 null 值的,而是 变量 /方法返回值 等,
例如
```java
int quantity= response == null ? 0 : response.getInteger("quantity");
```
getInteger 返回 null 就会导致问题 2,fastjson 可以用 getIntValue 来避免.

另外改用 Integer 只能保证在这里是不会出错,但很多时候我们需要把变量传到其它方法里,例如
```java
public static String formatNumber(int num) {
//把 12345678 转换为 12,345,678
}


Integer quantity= response == null ? 0 : response.getInteger("quantity"); // getInteger return null
String text = formatNumber(quantity); //抛异常 NullPointerException
```

Java 的隐式类型转换是一定要谨慎使用的,调用方法也要注意看参数类型和返回值类型。
TommyLemon
2018-12-04 15:23:04 +08:00
最后一个用例写错了
```java
Integer i = list == null || list.isEmpty() ? null : list.get(0); //Exception cannot cast String to Integer
```
应该改为
```java
Integer i = list == null || list.isEmpty() ? null : list.get(1); //Exception cannot cast String to Integer
```
zjp
2018-12-04 15:46:47 +08:00
花式贴链接……
三目运算符会隐式转型,int i = (Integer) null; 合法,int i = true ? null : 0; 也就合法
nutting
2018-12-04 15:49:39 +08:00
我想起一个,反射得到一个类的所有方法数组,jdk1.6 和 1.7 有差别,里面排序不一样,所以最好自己排一下
Kaiv2
2018-12-04 16:06:25 +08:00
@TommyLemon

这个 bug 无法确认是否真的存在过
```java
public ArrayList(Collection<?> c) { //还是 Collection<? extends Object> 来着,记不清了,但肯定是两者中的一个
```
如果之前这个方法真的存在,至少我觉得 get(0)会抛出 类型转换异常 `java.lang.ClassCastException`。

//throw new IllegalArgumentException 是从哪里抛出的呢?
TommyLemon
2018-12-04 16:44:03 +08:00
@Kaiv2 因为写博客时已经无法复现了,所以 Exception 也记错了,确实应该是 ClassCastException,不过也不排除 get 里面抛的是其它的,毕竟代码也找不到了,唉。
TommyLemon
2018-12-04 16:44:59 +08:00
@zjp 是这样,所以才会导致 bug
TommyLemon
2018-12-04 16:51:11 +08:00
@nutting 毕竟内部实现有改动很正常,还有 JDK 8 及以下是不能通过反射拿到成员变量的名称的,
@ApiModelProperty(value="id" ,required=true)
private Integer id;
所以 Swagger 等注解里面还得手写名称等,其实本身就已经声明了变量名为 id。JDK 9 还是 10 已经加入了。
JDK 7 比 6 在多了一个 getAnnotation(Class<T> c) , 之前只能 getAnnotations() 拿到所有注解再过滤。
TommyLemon
2018-12-04 17:40:01 +08:00
@TommyLemon 不过这些不能算 bug 了,只能算功能缺失,而且都在后续更新中加入了
chocotan
2018-12-04 18:04:36 +08:00
第一题 谷歌一下 https://stackoverflow.com/questions/13253624/how-to-supply-enum-value-to-an-annotation-from-a-constant-in-java
第二题 java 泛型是类型擦除的,当然是什么类型都能进 list,哪里是 bug,至于要不要抛 ClassCastException,看有没有把 get 的返回值赋给具体的类型,get 方法里会强转;主贴里说的“ IllegalArgumentException ”,围观了一下 ArrayList 源码,都不会在 get 的时候抛
mritd
2018-12-04 19:22:04 +08:00
看了这个贴子外加上次推广以后,我承认我已经对这个项目彻底失去信心了,一开始进来我以为要上 jdk 官方 bug 列表,我还在想 v 站大佬真是多 ...现在么...
cqy2016
2018-12-04 19:35:44 +08:00
...这应该叫你代码的 bug
blindpirate
2018-12-04 19:36:26 +08:00
sagaxu
2018-12-05 01:27:11 +08:00
发现 3 个广告
james2013
2018-12-05 09:17:04 +08:00
强行发广告...

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/514093

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX