bboyjing's blog

自己动手写JVM二十【类和对象(三)】

类和字段符号引用解析

类符号引用解析

先修改class.go文件,添加判断是否能访问其他类的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// self class是否能被other class访问
func (self *Class) isAccessibleTo(other *Class) bool {
/*
若self class访问标识为public或者两个类在同一个包下,则可以访问
暂时简单地按照包名来检查类是否属于同一个包
*/
return self.IsPublic() ||
self.getPackageName() == other.getPackageName()
}
// 获取包名
func (self *Class) getPackageName() string {
if i := strings.LastIndex(self.name, "/"); i >= 0 {
return self.name[:i]
}
return ""
}

修改cp_symref.go文件,添加类符号解析方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 解析类符号引用
func (self *SymRef) ResolvedClass() *Class {
// 如果类符号引用已经解析,直接返回类指针
if self.class == nil {
// 解析类符号
self.resolveClassRef()
}
return self.class
}
func (self *SymRef) resolveClassRef() {
// 获取当前Class指针
d := self.cp.class
// 通过需要引用的类的完全限定名加载类
c := d.loader.LoadClass(self.className)
// 判断d是否能有权限调用引用类c
if !c.isAccessibleTo(d) {
panic("java.lang.IllegalAccessError")
}
self.class = c
}

字段符号引用解析

先修改class_member.go文件,添加判断是否能访问其他字段的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 判断self字段是否能被d class访问
func (self *ClassMember) isAccessibleTo(d *Class) bool {
// 如果self字段是public,则任何类都可以访问
if self.IsPublic() {
return true
}
// 获取self字段所属的class
c := self.class
// 如果self字段是protected,只有子类和同一包下的类可以访问(isSubClassOf()函数后面给出)
if self.IsProtected() {
return d == c || d.isSubClassOf(c) ||
c.getPackageName() == d.getPackageName()
}
// 如果是默认访问权限,则只有同一包下的类可以访问
if !self.IsPrivate() {
return c.getPackageName() == d.getPackageName()
}
// self字段是private,则只有本类才可以访问
return d == c
}

修改cp_fieldref.go文件,添加解析方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 初始化字段符号引用
func newFieldRef(cp *ConstantPool, refInfo *classfile.ConstantFieldrefInfo) *FieldRef {
ref := &FieldRef{}
ref.cp = cp
ref.copyMemberRefInfo(&refInfo.ConstantMemberrefInfo)
return ref
}
// 解析字段符号引用
func (self *FieldRef) ResolvedField() *Field {
if self.field == nil {
self.resolveFieldRef()
}
return self.field
}
func (self *FieldRef) resolveFieldRef() {
// 获取当前Class指针
d := self.cp.class
// 解析字段符号引用之前需要先解析字段所属的类
c := self.ResolvedClass()
//根据字段名和描述符查找字段
field := lookupField(c, self.name, self.descriptor)
if field == nil {
// 若没有找到字段,报出NoSuchFieldError异常
panic("java.lang.NoSuchFieldError")
}
if !field.isAccessibleTo(d) {
// 若当前类没有该字段的访问权限,则报出IllegalAccessError异常
panic("java.lang.IllegalAccessError")
}
self.field = field
}
// 根据字段名和描述符查找字段
func lookupField(c *Class, name, descriptor string) *Field {
// 从Class结构体中遍历fields
for _, field := range c.fields {
if field.name == name && field.descriptor == descriptor {
return field
}
}
// 遍历接口
for _, iface := range c.interfaces {
if field := lookupField(iface, name, descriptor); field != nil {
return field
}
}
// 遍历超类的fields
if c.superClass != nil {
return lookupField(c.superClass, name, descriptor)
}
return nil
}

类和对象相关指令

下面我们来实现10条类和对象相关指令。new指令用来创建类实例;putstatic和getstatic指令用于存取静态变量;putfield和getfield用于存取实例变量;instanceof和checkcast指令用于判断对象是否属于某种类型;ldc系列指令把运行时常量池中的常量推到操作数栈顶。本节部分实现代码位于/instructions/references包下,ldc系列的指令实现代码位于/instructions/constants包下。下面先建一个用于测试的java文件,在java项目中新建MyObject.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyObject {
public static int staticVar;
public int instanceVar;
public static void main(String[] args) {
int x = 32768; // ldc
MyObject myObj = new MyObject(); // new
MyObject.staticVar = x; // putstatic
x = MyObject.staticVar; // getstatic
myObj.instanceVar = x; // putfield
x = myObj.instanceVar; // getfield
Object obj = myObj;
if (obj instanceof MyObject) { // instanceof
myObj = (MyObject) obj; // checkcast
System.out.println(myObj.instanceVar);
}
}
}

这里稍微补充下之前的iconst指令和ldc系列指令的区别:当int取值-1 ~ 5采用iconst指令,取值-128 ~ 127采用bipush指令,取值-32768 ~ 32767采用sipush指令,取值-2147483648 ~ 2147483647采用 ldc 指令。

new指令

new指令专门用来创建类实例,在实现new指令之前有两个地方需要完善:

  1. 修改/rtdata/frame.go文件,给Frame结构体添加方法区的方法信息指针属性,并且修改newFrame()函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    type Frame struct {
    ...
    // 方法区的方法信息指针
    method *heap.Method
    }
    // 实例化栈帧(此时有两个newFrame函数,并且编译有错,暂时先不管)
    func newFrame(thread *Thread, method *heap.Method) *Frame {
    return &Frame{
    thread: thread,
    method: method,
    localVars: newLocalVars(method.MaxLocals()),
    operandStack: newOperandStack(method.MaxStack()),
    }
    }
    // 省略Getter方法
    ...
  2. 修改thread.go文件,修改NewFrame()函数:

    1
    2
    3
    4
    // 为线程创建新的栈帧
    func (self *Thread) NewFrame(method *heap.Method) *Frame {
    return newFrame(self, method)
    }
  3. 修改class.go文件,添加初始化类的方法:

    1
    2
    3
    4
    5
    // 创建对象引用
    func (self *Class) NewObject() *Object {
    // 调用Object结构体
    return newObject(self)
    }

new指令实现代码位于new.go文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// new指令结构体(2个字节操作数)
type NEW struct{ base.Index16Instruction }
func (self *NEW) Execute(frame *rtdata.Frame) {
// 获取运行时常量池
cp := frame.Method().Class().ConstantPool()
// 通过索引从常量池中获取类符号引用
classRef := cp.GetConstant(self.Index).(*heap.ClassRef)
// 解析类
class := classRef.ResolvedClass()
// 接口和抽象类不能实例化,抛出InstantiationError异常
if class.IsInterface() || class.IsAbstract() {
panic("java.lang.InstantiationError")
}
// 创建对象引用
ref := class.NewObject()
// 将对象引用入栈
frame.OperandStack().PushRef(ref)
}