作者:RednaxelaFX
主页:[url]http://rednaxelafx.iteye.com[/url]
日期:2009-06-02
系列笔记:
[url=http://rednaxelafx.iteye.com/blog/379607]一个通不过Java字节码校验的例子[/url]
[url=http://rednaxelafx.iteye.com/blog/379703]数组协变带来的静态类型漏洞[/url]
[url=http://rednaxelafx.iteye.com/blog/382429]以Python为例讨论高级编程语言程序的wire format与校验[/url]
[url=http://rednaxelafx.iteye.com/blog/400452]CLR上的接口调用也是在运行时检查的[/url]
[url=http://rednaxelafx.iteye.com/blog/400597]为什么JVM与CLR都不对接口方法调用做静态校验?[/url]
继续看到底要运行一个Java程序需要做的各种检查是在什么时候发生的。这次我们来看看接口调用的问题。
当前的JVM规范中,与方法调用相关的指令有4个:invokevirtual、invokeinterface、invokestatic与invokespecial。其中调用接口方法时使用的JVM指令是invokeinterface。这个指令与另外3个方法调用指令有一个显著的差异:它不要求JVM的校验器(verifier)检查被调用对象(receiver)的类型;另外3个方法调用指令都要求校验被调用对象。也就是说,使用invokeinterface时如果被调用对象没有实现指定的接口,则应该在运行时而不是链接时抛出异常;而另外3个方法调用指令都要求在链接时抛出异常。
看看[url=http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc6.html#invokeinterface]JVM规范[/url]是怎么说的:
[quote="Java Virtual Machine Specification, 2nd Edition"][size=medium][b]invokeinterface[/b][/size]
...
[size=small][b]Runtime Exceptions[/b][/size]
...
if the class of [i]objectref[/i] does not implement the resolved interface, [i]invokeinterface[/i] throws an IncompatibleClassChangeError.[/quote]
可以留意一下另外3个方法调用指令中“IncompatibleClassChangeError”都是Linking Exception而不是Runtime Exception。
这种规定对Java程序来说可见的行为就是:如果一个方法通不过校验,则整个方法都不会被执行;如果能通过校验而抛出运行时异常,则方法当中抛出异常之前的部分都会被执行。
当然,我们直接用Java语言写出来的程序很难引发这样的错误,因为Java编译器会做检查来保证一定程度的类型安全。但是Java的class文件,或者说Java字节码可以由Java编译器以外的别的方式生成,此时就得不到Java编译器对类型安全的保证,而要依赖于JVM对字节码的校验以及运行时的检查了。
================================================================
我是之前在读[url=http://blogs.sun.com/jrose/]John Rose[/url]对JSR 292的invokedynamic的讲解时留意到invokeinterface的这个特点的。John特别提到invokedynamic就像invokeinterface一样,都不在校验时对被调用对象的类型做检查。不过之前一直没见过调用对一个没实现接口的对象调用接口方法实际是个什么样子。
好吧,这次就来看个例子。首先创建一个接口IFoo,一个实现了该接口的类FooImpl,和一个未实现该接口的类Bar:
IFoo.java:
FooImpl.java:
Bar.java:
接下来构造出一个能引发运行时异常的程序。大致的意思是这样的:
注意第7行代码。如果就这么写然后编译的话,生成的字节码里会有一个checkcast指令将Bar类型的引用转换为IFoo类型的引用。如果有checkcast的话,运行时就会在该指令上报错,因为Bar没有实现IFoo。但这次我想引发的错误不是强制转换相关,而是接口调用相关:[color=green]想达到的效果是以b为被调用对象,但调用IFoo.method()而不是Bar上已有的方法[/color]。所以要靠自己来生成字节码,避免checkcast指令。
上个月的两个相关帖里我使用了ObjectWeb的[url=http://forge.ow2.org/project/showfiles.php?group_id=23]ASM[/url]库来生成Java字节码。这个库很实用,但写起来还是繁琐了些。这次我决定用Charles Nutter写的bitescript。使用该库需要JRuby 1.2.0或更高的版本,我这次用的是JRuby 1.3.0RC2。
安装bitescript只要用JRuby的gem就行:
然后编写生成字节码用的脚本:
test.rb:
可以对比一下直接用ASM时的代码,显然用bitescript要简洁易懂得多。Good job, Charles!
把前面的IFoo.class、FooImpl.class和Bar.class放在“当前目录”下,然后执行上述脚本,生成TestInterfaceCall.class。(注意,执行该脚本时,JRuby要能够找到前面几个.class文件,不然生成出来的代码有错误。留意底下的回复。)
接着,运行java TestInterfaceCall,
可以看到程序打印出了"FooImpl.method()"这句话,也就是说异常是在运行时而不是链接时抛出的。
如今用到Java的字节码改写/动态生成的工具已经很普遍了,如果在使用它们的时候不够小心,相信这里所提到的运行时异常也会有机会见到的 =v=
P.S. 我这次运行的环境是:
[code=""]D:\sdk\jruby-1.3.0RC2\test_bitescript>java -version
java version "1.6.0_11"
Java(TM) SE Runtime Environment (build 1.6.0_11-b03)
Java HotSpot(TM) Client VM (build 11.0-b16, mixed mode, sharing)[/code]
=========================
更新:发现这么一篇论文:[url=http://portal.acm.org/citation.cfm?id=1773015.1773292]Using abstract interpretation to add type checking for interfaces in Java bytecode verification[/url]
[quote="abstract"]Java interface types support multiple inheritance. Because of this, the standard bytecode verifier ignores them, since it is not able to model the class hierarchy as a lattice. Thus, type checks on interfaces are performed at run time. We propose a verification methodology that removes the need for run-time checks. The methodology consists of: (1) an augmented verifier that is very similar to the standard one, but is also able to check for interface types in most cases; (2) for all other cases, a set of additional simpler verifiers, each one specialized for a single interface type. We obtain these verifiers in a systematic way by using abstract interpretation techniques. Finally, we describe an implementation of the methodology and evaluate it on a large set of benchmarks.[/quote]
主页:[url]http://rednaxelafx.iteye.com[/url]
日期:2009-06-02
系列笔记:
[url=http://rednaxelafx.iteye.com/blog/379607]一个通不过Java字节码校验的例子[/url]
[url=http://rednaxelafx.iteye.com/blog/379703]数组协变带来的静态类型漏洞[/url]
[url=http://rednaxelafx.iteye.com/blog/382429]以Python为例讨论高级编程语言程序的wire format与校验[/url]
[url=http://rednaxelafx.iteye.com/blog/400452]CLR上的接口调用也是在运行时检查的[/url]
[url=http://rednaxelafx.iteye.com/blog/400597]为什么JVM与CLR都不对接口方法调用做静态校验?[/url]
继续看到底要运行一个Java程序需要做的各种检查是在什么时候发生的。这次我们来看看接口调用的问题。
当前的JVM规范中,与方法调用相关的指令有4个:invokevirtual、invokeinterface、invokestatic与invokespecial。其中调用接口方法时使用的JVM指令是invokeinterface。这个指令与另外3个方法调用指令有一个显著的差异:它不要求JVM的校验器(verifier)检查被调用对象(receiver)的类型;另外3个方法调用指令都要求校验被调用对象。也就是说,使用invokeinterface时如果被调用对象没有实现指定的接口,则应该在运行时而不是链接时抛出异常;而另外3个方法调用指令都要求在链接时抛出异常。
看看[url=http://java.sun.com/docs/books/jvms/second_edition/html/Instructions2.doc6.html#invokeinterface]JVM规范[/url]是怎么说的:
[quote="Java Virtual Machine Specification, 2nd Edition"][size=medium][b]invokeinterface[/b][/size]
...
[size=small][b]Runtime Exceptions[/b][/size]
...
if the class of [i]objectref[/i] does not implement the resolved interface, [i]invokeinterface[/i] throws an IncompatibleClassChangeError.[/quote]
可以留意一下另外3个方法调用指令中“IncompatibleClassChangeError”都是Linking Exception而不是Runtime Exception。
这种规定对Java程序来说可见的行为就是:如果一个方法通不过校验,则整个方法都不会被执行;如果能通过校验而抛出运行时异常,则方法当中抛出异常之前的部分都会被执行。
当然,我们直接用Java语言写出来的程序很难引发这样的错误,因为Java编译器会做检查来保证一定程度的类型安全。但是Java的class文件,或者说Java字节码可以由Java编译器以外的别的方式生成,此时就得不到Java编译器对类型安全的保证,而要依赖于JVM对字节码的校验以及运行时的检查了。
================================================================
我是之前在读[url=http://blogs.sun.com/jrose/]John Rose[/url]对JSR 292的invokedynamic的讲解时留意到invokeinterface的这个特点的。John特别提到invokedynamic就像invokeinterface一样,都不在校验时对被调用对象的类型做检查。不过之前一直没见过调用对一个没实现接口的对象调用接口方法实际是个什么样子。
好吧,这次就来看个例子。首先创建一个接口IFoo,一个实现了该接口的类FooImpl,和一个未实现该接口的类Bar:
IFoo.java:
public interface IFoo {
void method();
}
FooImpl.java:
public class FooImpl implements IFoo {
public void method() {
System.out.println("FooImpl.method()");
}
}
Bar.java:
public class Bar {
public void anotherMethod() {
System.out.println("Bar.anotherMethod()");
}
}
接下来构造出一个能引发运行时异常的程序。大致的意思是这样的:
public class TestInterfaceCall {
public static void main(String[] args) {
IFoo f = new FooImpl();
f.method();
Bar b = new Bar();
((IFoo)b).method(); // << watch this
}
}
注意第7行代码。如果就这么写然后编译的话,生成的字节码里会有一个checkcast指令将Bar类型的引用转换为IFoo类型的引用。如果有checkcast的话,运行时就会在该指令上报错,因为Bar没有实现IFoo。但这次我想引发的错误不是强制转换相关,而是接口调用相关:[color=green]想达到的效果是以b为被调用对象,但调用IFoo.method()而不是Bar上已有的方法[/color]。所以要靠自己来生成字节码,避免checkcast指令。
上个月的两个相关帖里我使用了ObjectWeb的[url=http://forge.ow2.org/project/showfiles.php?group_id=23]ASM[/url]库来生成Java字节码。这个库很实用,但写起来还是繁琐了些。这次我决定用Charles Nutter写的bitescript。使用该库需要JRuby 1.2.0或更高的版本,我这次用的是JRuby 1.3.0RC2。
安装bitescript只要用JRuby的gem就行:
gem install bitescript
然后编写生成字节码用的脚本:
test.rb:
require 'rubygems'
require 'bitescript'
include BiteScript
IFoo = Java::IFoo
FooImpl = Java::FooImpl
Bar = Java::Bar
fb = FileBuilder.build(__FILE__) do
public_class 'TestInterfaceCall' do
public_static_method 'main', void, string[] do
# IFoo f = new FooImpl();
new FooImpl
dup
invokespecial FooImpl, '<init>', [void]
astore 1
# f.method();
aload 1
invokeinterface IFoo, 'method', [void]
# Bar b = new Bar();
new Bar
dup
invokespecial Bar, '<init>', [void]
astore 2
# ((IFoo)b).method();
aload 2
## checkcast IFoo # skip the cast to trigger IncompatibleClassChangeError
invokeinterface IFoo, 'method', [void]
returnvoid
end
end
end
fb.generate do |filename, class_builder|
File.open(filename, 'w') do |file|
file.write(class_builder.generate)
end
end
可以对比一下直接用ASM时的代码,显然用bitescript要简洁易懂得多。Good job, Charles!
把前面的IFoo.class、FooImpl.class和Bar.class放在“当前目录”下,然后执行上述脚本,生成TestInterfaceCall.class。(注意,执行该脚本时,JRuby要能够找到前面几个.class文件,不然生成出来的代码有错误。留意底下的回复。)
接着,运行java TestInterfaceCall,
D:\sdk\jruby-1.3.0RC2\test_bitescript>java TestInterfaceCall
FooImpl.method()
Exception in thread "main" java.lang.IncompatibleClassChangeError
at TestInterfaceCall.main(test.rb)
可以看到程序打印出了"FooImpl.method()"这句话,也就是说异常是在运行时而不是链接时抛出的。
如今用到Java的字节码改写/动态生成的工具已经很普遍了,如果在使用它们的时候不够小心,相信这里所提到的运行时异常也会有机会见到的 =v=
P.S. 我这次运行的环境是:
[code=""]D:\sdk\jruby-1.3.0RC2\test_bitescript>java -version
java version "1.6.0_11"
Java(TM) SE Runtime Environment (build 1.6.0_11-b03)
Java HotSpot(TM) Client VM (build 11.0-b16, mixed mode, sharing)[/code]
=========================
更新:发现这么一篇论文:[url=http://portal.acm.org/citation.cfm?id=1773015.1773292]Using abstract interpretation to add type checking for interfaces in Java bytecode verification[/url]
[quote="abstract"]Java interface types support multiple inheritance. Because of this, the standard bytecode verifier ignores them, since it is not able to model the class hierarchy as a lattice. Thus, type checks on interfaces are performed at run time. We propose a verification methodology that removes the need for run-time checks. The methodology consists of: (1) an augmented verifier that is very similar to the standard one, but is also able to check for interface types in most cases; (2) for all other cases, a set of additional simpler verifiers, each one specialized for a single interface type. We obtain these verifiers in a systematic way by using abstract interpretation techniques. Finally, we describe an implementation of the methodology and evaluate it on a large set of benchmarks.[/quote]