3

我正在从 Oracle http://docs.oracle.com/javase/tutorial/extra/generics/legacy.html阅读本教程

但我无法弄清楚这条线是什么意思

因此,即使存在未经检查的警告,Java 虚拟机的类型安全性和完整性也不会受到威胁。

有人可以更清楚地为我解释吗?补充:JVM的“完整性”究竟是什么,“处于风险中”的真正含义是什么?

4

4 回答 4

5

这意味着 JVM 永远不会被愚弄,认为一个对象是一种它不是的类型。诱使运行时认为一条数据具有不同的类型是一种强大的攻击向量,特别是如果您可以诱使运行时允许您将值分配给它认为是longint但实际上是指针的值。

JVM 的基本安全模型依赖于对象是运行时认为的类型。

我阅读了一篇引人入胜的论文,其中详细介绍了对运行 Java 的机器的攻击,其中涉及使用热灯来大幅增加内存错误。然后,他们使用了一个程序,该程序在内存中策略性地排列了数十亿个对象,并等待其中一个对象发生零星的位翻转。这样做会诱使 JVM 认为它正在处理不同类型的对象,并最终允许 JVM 运行任意代码(有关完整阅读,请参阅使用内存错误攻击虚拟机)。

他们使用位翻转的事实与泛型无关。但是那篇文章详细介绍了欺骗运行时认为对象属于不同类型的能力。总而言之,假设您有课程A并且B

class A {
    public long data = 0;
}

class B {
}

如果你能以某种方式欺骗 JVM 以允许这样做:

A aButActuallyB = someMagicAssignment(new B());

WheresomeMagicAssignment是一个可以引用B并以某种方式将所指对象作为A. 然后想想当你这样做时实际会发生什么:

aButActuallyB.data = 0x05124202;

您可能正在写入 JVM 原始内存中的任意数据!例如,该数据可能是方法的位置。将其更改为指向某个字节数组的内容可以让您随后运行任意代码。

所以当甲骨文说

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 绝不会允许您提供一个方法 a List<String>,然后让该方法String对该列表的元素执行操作,而不在运行时检查这些元素是否实际上是Strings。它绝不会允许您在不手动检查B的情况下将 a 视为a 。A

于 2013-05-09T14:39:07.177 回答
1

它只是意味着,当您拥有List<String> stringListList stringList在您的代码中,一旦编译并运行,JVM 就完全没有风险。JVM 只看到List stringList.

这是因为类型擦除,其中泛型参数化类型在运行时被擦除(删除)。

同一篇文章明确指出:

基本上,擦除消除(或擦除)所有通用类型信息。尖括号之间的所有类型信息都被丢弃,因此,例如,参数化类型 likeList<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从不存储/使用泛型信息(即使元数据驻留在类文件中)。

我希望这有帮助。

于 2013-05-09T10:56:03.113 回答
1

我认为他们得到的是泛型是在编译器中实现的,而不是在虚拟机中实现的,因此无论您是否抑制未经检查的警告,编译器发出的字节码都是相同的;抑制未经检查的警告不是以绕过虚拟机级别的类型检查的方式实现的,仅在编译器级别。

于 2013-05-09T10:56:15.933 回答
0

答案就在那句话之前的句子中:

这样做的原因是,Java 编译器将泛型实现为称为擦除的前端转换。您可以(几乎)将其视为源到源的翻译,从而将 loophole() 的通用版本转换为非通用版本。

这意味着,由于擦除,具有泛型的源代码将编译为与非泛型源代码相同(或至少相似)的字节码。

于 2013-05-09T10:56:19.967 回答