我正在阅读Oracle http://docs.oracle.com/javase/tutorial/extra/generics/legacy.html
中的本教程但我无法弄清楚这条线的含义
因此, Java虚拟机的类型安全性和完整性 即使存在未经检查的警告,也不会有风险。
有人可以更清楚地为我解释一下吗? 补充:JVM的“完整性”究竟是什么?“风险”究竟是什么意思?
答案 0 :(得分:5)
这意味着JVM永远不会被认为对象是一种它不是的类型。欺骗运行时将一段数据视为具有不同类型是一种强大的攻击向量,特别是如果您可以欺骗运行时允许您为其认为是long
或int
的值分配值,但实际上是一个指针。
JVM的基本安全模型依赖于对象是运行时认为的类型。
我读了一篇引人入胜的论文,详细介绍了对运行Java的机器的攻击,该机器涉及使用加热灯来大幅增加内存错误。然后,他们使用了一个程序,数十亿个对象在内存中策略性地对齐,并等待一个有零星的位翻转。这样做会欺骗JVM认为它正在处理不同类型的对象,并最终允许JVM运行任意代码(对于完整的读取,请参阅Using Memory Errors to Attack a Virtual Machine)。
他们使用位翻转这一事实与Generics无关。但是,该文章详细介绍了欺骗运行时思考对象的不同类型的能力。总而言之,假设您有课程A
和B
:
class A {
public long data = 0;
}
class B {
}
如果你能以某种方式欺骗JVM来允许这个:
A aButActuallyB = someMagicAssignment(new B());
其中someMagicAssignment
是一个方法,可以引用B
并以某种方式将引用对象作为A
返回。然后考虑一下当你做了实际时会发生什么:
aButActuallyB.data = 0x05124202;
您可能正在写入JVM原始内存中的任意数据!例如,该数据可以是方法的位置。将其更改为指向某个字节数组的内容可以让您随后运行任意代码。
所以当Oracle说
时即使存在未经检查的警告,Java虚拟机的类型安全性和完整性也不会存在风险。
它的含义是即使你能做到这一点:
public static <T> T someMagicAssignment(B b){
return (T) b; //unchecked cast warning
}
然后用:
来调用它A a = MyClass.<A>someMagicAssignment(new B());
这仍然会在分配到a
进行运行时检查。
因此,编写该方法someMagicAssignment
并不比以前容易。泛型不会以任何方式增加此攻击向量的表面区域,因为JVM在其内部类型系统中忽略了泛型。从来没有JVM允许您给方法List<String>
,然后让该方法对该列表的元素执行String
操作,而不是在运行时检查元素实际上是{{ 1}}秒。在没有手动检查的情况下,决不允许您将String
视为B
。
答案 1 :(得分:1)
这只是意味着,当您的代码中有List<String> stringList
或List stringList
时,一旦编译并运行,JVM就没有风险了。 JVM只看到List stringList
。
这是因为类型擦除,其中Generics参数化类型在运行时被删除(删除)。
同一篇文章明确指出:
基本上,擦除会删除(或删除)所有泛型类型 信息。尖括号之间的所有类型信息都是 抛出,例如,像
List<String>
这样的参数化类型 转换为List
。
文档中的风险只是意味着,如果你有一个
的例子List<String> stringList = new Arrays.asList("one", "two", "three");
String number = stringList.get(0);
JVM将其理解为:
List stringList = new Arrays.asList("one", "two", "three");
String number = (String)stringList.get(0);
虽然,第二个版本会抱怨Generics raw-types ,但JVM会删除参数化类型并隐式检索元素为Object
,并且JVM的类型转换规则仍然适用。
JVM 从不存储/使用泛型信息(即使元数据位于类文件中)。
我希望这会有所帮助。
答案 2 :(得分:1)
我认为他们所得到的是泛型是在编译器中实现的,而不是在虚拟机中实现的,因此无论是否抑制未经检查的警告,编译器发出的字节码都是相同的;抑制未经检查的警告未以绕过虚拟机级别的类型检查的方式实现,仅在编译器级别实现。
答案 3 :(得分:0)
答案在紧接在那一句之前的句子中:
原因是,泛型是由Java实现的 编译器作为前端转换称为擦除。你可以(差不多) 将其视为源到源的翻译,即通用的 版本的漏洞()被转换为非通用版本。
这意味着,由于擦除,带有泛型的源代码将编译为与非通用源代码相同(或至少相似)的字节码。