From 24f03f61f647a9796babd2f8ff18af241bdaa9e5 Mon Sep 17 00:00:00 2001 From: Shautvast Date: Mon, 16 Oct 2023 09:10:55 +0200 Subject: [PATCH] WIP statin initializers --- src/class.rs | 158 ++++++++++++++++++++++++++++++++++++--------- src/classloader.rs | 2 +- src/heap.rs | 60 ++++++++--------- src/main.rs | 3 +- src/native.rs | 11 ++-- src/opcodes.rs | 1 + src/vm.rs | 140 +++++++++++++++++++-------------------- tests/Main.class | Bin 328 -> 436 bytes tests/Main.java | 6 ++ 9 files changed, 240 insertions(+), 141 deletions(-) diff --git a/src/class.rs b/src/class.rs index 48fe119..a8b877a 100644 --- a/src/class.rs +++ b/src/class.rs @@ -1,4 +1,4 @@ -use std::cell::UnsafeCell; +use std::cell::{Ref, RefCell, RefMut, UnsafeCell}; use std::collections::HashMap; use std::fmt; use std::hash::Hash; @@ -6,14 +6,52 @@ use std::rc::Rc; use std::sync::Arc; use anyhow::{anyhow, Error}; +use once_cell::sync::Lazy; -use crate::classloader::CpEntry; +use crate::classloader::{CpEntry, load_class}; use crate::heap::ObjectRef; -use crate::io::read_u16; +use crate::io::{find_class, read_bytecode, read_u16}; +use crate::vm::Vm; + +//trying to be ready for multithreaded as much as possible, using Arc's and all, but it will still require (a lot of) extra work +static mut CLASSDEFS: Lazy>>> = Lazy::new(|| HashMap::new()); //TODO add mutex.. + + +// gets the Class from cache, or reads it from classpath, +// then parses the binary data into a Class struct +// Vm keeps ownership of the class and hands out Arc references to it +pub fn get_class(vm: &mut Vm, class_name: &str) -> Result>, Error> { + println!("get_class {}", class_name); + unsafe { + let new_class = CLASSDEFS.entry(class_name.into()).or_insert_with(|| { + println!("read class {} ", class_name); + let resolved_path = find_class(&vm.classpath, class_name).unwrap(); + // println!("full path {}", resolved_path); + let bytecode = read_bytecode(resolved_path).unwrap(); + let mut class = load_class(bytecode).unwrap(); + let super_class_name = class.super_class_name.as_ref(); + if let Some(super_class_name) = super_class_name { + if let Ok(super_class) = get_class(vm, &super_class_name) { + class.super_class = Some(super_class); + } + } + Arc::new(RefCell::new(class)) + }); + + Class::initialize_fields(new_class.clone()); + + let exec = new_class.borrow().methods.contains_key("()V"); + if exec { + vm.execute_class(new_class.clone(), "()V", vec![]).unwrap(); + } + + Ok(new_class.clone()) + } +} + /// the class definition as read from the class file + derived values // TODO implement call to static initializers -// TODO implement storage for static fields #[derive(Debug)] pub struct Class { pub minor_version: u16, @@ -22,13 +60,17 @@ pub struct Class { pub access_flags: u16, pub name: String, pub super_class_name: Option, - pub super_class: Option>, + pub super_class: Option>>, pub interface_indices: Vec, pub interfaces: Vec, pub fields: HashMap, - pub methods: HashMap, + pub methods: HashMap>, pub attributes: HashMap, - pub(crate) field_mapping: Option>>, // first key: this/super/supersuper-name(etc), second key: fieldname, value (type, index) + pub(crate) object_field_mapping: HashMap>, + // first key: this/super/supersuper-name(etc), second key: fieldname, value (type, index) + pub(crate) static_field_mapping: HashMap>, + // first key: this/super/supersuper-name(etc), second key: fieldname, value (type, index) + pub(crate) static_data: Vec, } impl Class { @@ -41,7 +83,7 @@ impl Class { super_class_index: u16, interface_indices: Vec, fields: HashMap, - methods: HashMap, + methods: HashMap>, attributes: HashMap, ) -> Self { let name = Class::class_name(this_class, constant_pool.clone()).unwrap(); @@ -60,12 +102,18 @@ impl Class { fields, methods, attributes, - field_mapping: None, + object_field_mapping: HashMap::new(), + static_field_mapping: HashMap::new(), + static_data: vec![], } } - pub(crate) fn n_fields(&self) -> usize { - self.field_mapping.as_ref().map_or(0, |m| m.len()) + pub(crate) fn n_object_fields(&self) -> usize { + self.object_field_mapping.len() + } + + pub(crate) fn n_static_fields(&self) -> usize { + self.object_field_mapping.len() } // Create a mapping per field(name) to an index in the storage vector that contains the instance data. @@ -76,42 +124,64 @@ impl Class { // This way `this.a` can be differentiated from `super.a`. // // this method looks up this and super classes and calls map_fields for each. - pub fn initialize(&mut self) { - let mut field_mapping = HashMap::new(); + pub fn initialize_fields(class: Arc>) { + let mut this_field_mapping = HashMap::new(); + let mut static_field_mapping = HashMap::new(); let mut field_map_index: usize = 0; - Class::map_fields(&mut field_mapping, self, &mut field_map_index); - let mut sooper = &self.super_class; - while let Some(super_class) = sooper { - Class::map_fields(&mut field_mapping, super_class, &mut field_map_index); - sooper = &super_class.super_class; + Class::add_field_mappings(&mut this_field_mapping, &mut static_field_mapping, class.clone(), &mut field_map_index); + + class.borrow_mut().object_field_mapping = this_field_mapping; + class.borrow_mut().static_field_mapping = static_field_mapping; + + let static_data = Class::set_field_data(class.clone()); + class.borrow_mut().static_data = static_data; + } + + fn add_field_mappings(this_field_mapping: &mut HashMap>, + static_field_mapping: &mut HashMap>, + class: Arc>, field_map_index: &mut usize) { + let (o, s) = Class::map_fields(class.clone(), field_map_index); + let borrow = class.borrow(); + let name = &borrow.name; + this_field_mapping.insert(name.to_owned(), o); + static_field_mapping.insert(name.to_owned(), s); + + if let Some(super_class) = class.borrow().super_class.as_ref() { + Class::add_field_mappings(this_field_mapping, static_field_mapping, super_class.clone(), field_map_index); } - self.field_mapping = Some(field_mapping); } // part of the initialize procedure fn map_fields( - field_mapping: &mut HashMap>, - class: &Class, + class: Arc>, field_map_index: &mut usize, - ) { + ) -> (HashMap, HashMap) { let mut this_fields = HashMap::new(); //fields in class are stored per class and every superclass. - for field in &class.fields { - this_fields.insert( - field.0.to_owned(), - (field.1.type_of().to_owned(), *field_map_index), - ); //name => (type,index) + let mut static_fields = HashMap::new(); //fields in class are stored per class and every superclass. + + for (name, field) in &class.borrow().fields { + if field.is(Modifier::Static) { + static_fields.insert( + name.to_owned(), + (field.type_of().to_owned(), *field_map_index), + ); + } else { + this_fields.insert( + name.to_owned(), + (field.type_of().to_owned(), *field_map_index), + ); //name => (type,index) + } *field_map_index += 1; } - let this_name = class.name.to_owned(); - field_mapping.insert(this_name, this_fields); + (this_fields, static_fields) } pub fn get_version(&self) -> (u16, u16) { (self.major_version, self.minor_version) } - pub fn get_method(&self, name: &str) -> Result<&Method, Error> { + pub fn get_method(&self, name: &str) -> Result<&Rc, Error> { self.methods .get(name) .ok_or(anyhow!("Method {} not found", name)) @@ -135,6 +205,29 @@ impl Class { } } + pub(crate) fn set_field_data(class: Arc>) -> Vec { + let mut field_data = Vec::with_capacity(class.borrow().n_object_fields()); + + for (_, fields) in &class.borrow().static_field_mapping { + for (_, (fieldtype, _)) in fields { + let value = match fieldtype.as_str() { + "Z" => Value::BOOL(false), + "B" => Value::I32(0), + "S" => Value::I32(0), + "I" => Value::I32(0), + "J" => Value::I64(0), + "F" => Value::F32(0.0), + "D" => Value::F64(0.0), + "L" => Value::Null, + _ => Value::Void, + }; + field_data.push(value.into()); + } + } + + field_data + } + // convienence methods for data from the constantpool pub fn cp_field_ref(&self, index: &u16) -> Option<(&u16, &u16)> { @@ -281,6 +374,11 @@ impl Field { } } + pub fn is(&self, modifier: Modifier) -> bool { + let modifier = modifier as u16; + self.access_flags & modifier == modifier + } + pub fn name(&self) -> &String { if let CpEntry::Utf8(utf8) = &self.constant_pool.get(&self.name_index).unwrap() { return utf8; diff --git a/src/classloader.rs b/src/classloader.rs index 597dbb0..45f3d0a 100644 --- a/src/classloader.rs +++ b/src/classloader.rs @@ -48,7 +48,7 @@ pub fn load_class(bytecode: Vec) -> Result { let mut methods = HashMap::new(); for _ in 0..methods_count { let m = read_method(constant_pool.clone(), pos, &bytecode); - methods.insert(m.name(), m); + methods.insert(m.name(), Rc::new(m)); } let attributes_count = read_u16(&bytecode, pos); diff --git a/src/heap.rs b/src/heap.rs index cbe2637..6bb6735 100644 --- a/src/heap.rs +++ b/src/heap.rs @@ -1,17 +1,9 @@ -use std::cell::UnsafeCell; +use std::cell::{RefCell, UnsafeCell}; use std::fmt; +use std::ops::Deref; use std::sync::Arc; use crate::class::{Class, UnsafeValue, Value}; -use crate::classloader::CpEntry; - -// trying to implement efficient object instance storage -pub struct Object { - // locked: bool, - // hashcode: i32, - pub class: Arc, - pub data: Vec, -} //arrays // can contain object or array #[derive(Debug)] @@ -28,14 +20,22 @@ pub enum ObjectRef { Object(Box), } +// trying to implement efficient object instance storage +pub struct Object { + // locked: bool, + // hashcode: i32, + pub class: Arc>, + pub data: Vec, +} //arrays + unsafe impl Send for Object {} unsafe impl Sync for Object {} // object, not array impl Object { - pub fn new(class: Arc) -> Self { - let instance_data = Object::init_fields(&class); + pub fn new(class: Arc>) -> Self { + let instance_data = Object::init_fields(class.clone()); Self { class, data: instance_data, @@ -43,10 +43,10 @@ impl Object { } // initializes all non-static fields to their default values - pub(crate) fn init_fields(class: &Class) -> Vec { - let mut field_data = Vec::with_capacity(class.n_fields()); + pub(crate) fn init_fields(class: Arc>) -> Vec { + let mut field_data = Vec::with_capacity(class.borrow().n_object_fields()); - for (_, fields) in class.field_mapping.as_ref().unwrap() { + for (_, fields) in &class.borrow().object_field_mapping { for (_, (fieldtype, _)) in fields { let value = match fieldtype.as_str() { "Z" => Value::BOOL(false), @@ -67,11 +67,9 @@ impl Object { } pub fn set(&mut self, class_name: &String, field_name: &String, value: UnsafeValue) { - let (_type, index) = self - .class - .field_mapping - .as_ref() - .unwrap() + let borrow = self.class.borrow(); + let (_type, index) =borrow + .object_field_mapping .get(class_name) .unwrap() .get(field_name) @@ -80,11 +78,9 @@ impl Object { } pub fn get(&mut self, class_name: &String, field_name: &String) -> &UnsafeValue { - let (_type, index) = &self - .class - .field_mapping - .as_ref() - .unwrap() + let borrow = self.class.borrow(); + let (_type, index) =borrow + .object_field_mapping .get(class_name) .unwrap() .get(field_name) @@ -92,12 +88,12 @@ impl Object { &self.data[*index] } - fn get_field_name(&self, cp_index: &u16) -> &str { - if let CpEntry::Utf8(name) = self.class.constant_pool.get(cp_index).unwrap() { - return name; - } - panic!() - } + // fn get_field_name(&self, cp_index: &u16) -> &str { + // if let CpEntry::Utf8(name) = self.class.constant_pool.get(cp_index).unwrap() { + // return name; + // } + // panic!() + // } } impl fmt::Debug for Object { @@ -109,7 +105,7 @@ impl fmt::Debug for Object { // // r // } // ).collect(); - write!(f, "{}", self.class.name) + write!(f, "{}", self.class.borrow().name) } } diff --git a/src/main.rs b/src/main.rs index ab5ebfc..72649c2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,8 @@ fn main() -> Result<(), Error> { // TODO build index for package -> jarfile? let mut vm = Vm::new("tests"); - let main_class = "Inheritance"; + // let main_class = "Inheritance"; + let main_class = "Main"; vm.execute(main_class, "main([Ljava/lang/String;)V", vec![]) .unwrap(); diff --git a/src/native.rs b/src/native.rs index 8691b50..09c6e65 100644 --- a/src/native.rs +++ b/src/native.rs @@ -1,8 +1,9 @@ -use std::sync::Arc; +use std::rc::Rc; +use crate::class::{Method, UnsafeValue, Value}; -use crate::class::{Class, Method, UnsafeValue, Value}; - -pub fn invoke_native(class: Arc, method: &Method) -> UnsafeValue { - println!("invoke native {:?}.{:?}", class.name, method.name()); +pub fn invoke_native(method: Rc, args: Vec) -> UnsafeValue { + println!("native {}", method.name()); Value::void() } + + diff --git a/src/opcodes.rs b/src/opcodes.rs index 940697e..17cc95d 100644 --- a/src/opcodes.rs +++ b/src/opcodes.rs @@ -125,6 +125,7 @@ pub const DRETURN: u8 = 175; // (0xaf) Return double from method pub const areturn: u8 = 176; //(0xb0) return reference pub const RETURN_VOID: u8 = 177; // (0xb1) Return void from method (actually 'return' but that's a keyword) pub const GETSTATIC: u8 = 178; // (0xb2) Get static field from class +pub const PUTSTATIC: u8 = 179; // (0xb3) Set static field in class pub const GETFIELD: u8 = 180; // (0xb4) Fetch field from object3 pub const PUTFIELD: u8 = 181; // (0xb5) Set field in object pub const INVOKEVIRTUAL: u8 = 182; // (0xb6) Invoke instance method; dispatch based on class diff --git a/src/vm.rs b/src/vm.rs index aacdac1..39c2d80 100644 --- a/src/vm.rs +++ b/src/vm.rs @@ -1,14 +1,13 @@ -use std::cell::UnsafeCell; +use std::cell::{RefCell, UnsafeCell}; use std::collections::HashMap; use std::rc::Rc; use std::sync::Arc; use anyhow::{anyhow, Error}; -use once_cell::unsync::Lazy; +use crate::class::{AttributeType, Class, get_class, Modifier, UnsafeValue, Value}; use crate::class::Value::Void; -use crate::class::{AttributeType, Class, Modifier, UnsafeValue, Value}; -use crate::classloader::{load_class, CpEntry}; +use crate::classloader::CpEntry; use crate::heap::{Heap, Object, ObjectRef}; use crate::io::*; use crate::native::invoke_native; @@ -42,11 +41,9 @@ impl StackFrame { } } -//trying to be ready for multithreaded as much as possible, using Arc's and all, but it will still require (a lot of) extra work -static mut CLASSDEFS: Lazy>> = Lazy::new(|| HashMap::new()); //TODO add mutex.. pub struct Vm { - classpath: Vec, + pub classpath: Vec, heap: Heap, stack: Vec, } @@ -77,35 +74,8 @@ impl Vm { } } - // gets the Class from cache, or reads it from classpath, - // then parses the binary data into a Class struct - // Vm keeps ownership of the class and hands out Arc references to it - pub fn get_class(&self, class_name: &str) -> Result, Error> { - println!("get_class {}", class_name); - unsafe { - let entry = CLASSDEFS.entry(class_name.into()); - let entry = entry.or_insert_with(|| { - println!("read class {} ", class_name); - let resolved_path = find_class(&self.classpath, class_name).unwrap(); - // println!("full path {}", resolved_path); - let bytecode = read_bytecode(resolved_path).unwrap(); - let mut class = load_class(bytecode).unwrap(); - let super_class_name = class.super_class_name.as_ref(); - if let Some(super_class_name) = super_class_name { - if let Ok(super_class) = self.get_class(&super_class_name) { - class.super_class = Some(super_class.clone()); - } - } - class.initialize(); - Arc::new(class) - }); - Ok(entry.clone()) - } - } - - pub fn new_instance(class: Arc) -> Object { - let mut class = class; + pub fn new_instance(class: Arc>) -> Object { let mut instance = Object::new(class.clone()); instance } @@ -118,25 +88,27 @@ impl Vm { method_name: &str, args: Vec, ) -> Result { + let class = get_class(self, class_name)?; + self.execute_class(class, method_name, args) + } + + pub fn execute_class( + &mut self, + class: Arc>, + method_name: &str, + args: Vec, + ) -> Result { + let this_class = class; + println!("execute {}.{}", this_class.borrow().name, method_name); + + let method = this_class.clone().borrow().get_method(method_name)?.clone(); let mut local_params: Vec> = args.clone().iter().map(|e| Some(e.clone())).collect(); - println!("execute {}.{}", class_name, method_name); - let class = self.get_class(class_name)?; - let method = class.get_method(method_name)?; if method.is(Modifier::Native) { - let return_value = invoke_native(class.clone(), method); - unsafe { - match *return_value.get() { - Void => {} - _ => { - self.local_stack().push_arc(return_value.clone()); - } - } - } + return Ok(invoke_native(method, args)); } - if let AttributeType::Code(code) = method.attributes.get("Code").unwrap() { - let stackframe = StackFrame::new(class_name, method_name); + let stackframe = StackFrame::new(&this_class.borrow().name, &method.name()); self.stack.push(stackframe); let mut pc = &mut 0; @@ -297,23 +269,45 @@ impl Vm { return Ok(Value::void()); } GETSTATIC => { - let cp_index = read_u16(&code.opcodes, pc); - let (class_index, _field_name_and_type_index) = - class.cp_field_ref(&cp_index).unwrap(); // all these unwraps are safe as long as the class is valid - let class_name_index = class.cp_class_ref(class_index).unwrap(); - let class_name = class.cp_utf8(class_name_index).unwrap(); - let class = self.get_class(class_name.as_str())?; - // println!("{:?}", class); //TODO - } - GETFIELD => unsafe { + let borrow = this_class.borrow(); let cp_index = read_u16(&code.opcodes, pc); let (class_index, field_name_and_type_index) = - class.cp_field_ref(&cp_index).unwrap(); + borrow.cp_field_ref(&cp_index).unwrap(); // all these unwraps are safe as long as the class is valid + let (name_index, _) = borrow.cp_name_and_type(field_name_and_type_index).unwrap(); + let (name) = borrow.cp_utf8(name_index).unwrap(); + let class_name_index = borrow.cp_class_ref(class_index).unwrap(); + let class_name = borrow.cp_utf8(class_name_index).unwrap(); + let that = get_class(self, class_name.as_str())?; + let borrow = that.borrow(); + let (_, val_index) = borrow.static_field_mapping.get(class_name).unwrap().get(name).unwrap(); + self.local_stack().push_arc(this_class.borrow().static_data.get(*val_index).unwrap().clone()); + } + PUTSTATIC => { + println!("putstatic"); + let mut borrow = this_class.borrow_mut(); + let cp_index = read_u16(&code.opcodes, pc); + let (class_index, field_name_and_type_index) = + borrow.cp_field_ref(&cp_index).unwrap(); // all these unwraps are safe as long as the class is valid + let (name_index, _) = borrow.cp_name_and_type(field_name_and_type_index).unwrap(); + let (name) = borrow.cp_utf8(name_index).unwrap(); + let class_name_index = borrow.cp_class_ref(class_index).unwrap(); + let class_name = borrow.cp_utf8(class_name_index).unwrap(); + let that = get_class(self, class_name.as_str())?; + let that_borrow = that.borrow(); + let (_, val_index) = that_borrow.static_field_mapping.get(class_name).unwrap().get(name).unwrap(); + let value = self.local_stack().pop()?; + borrow.static_data[*val_index] = value; + } + GETFIELD => unsafe { + let borrow = this_class.borrow(); + let cp_index = read_u16(&code.opcodes, pc); + let (class_index, field_name_and_type_index) = + borrow.cp_field_ref(&cp_index).unwrap(); let (field_name_index, _) = - class.cp_name_and_type(field_name_and_type_index).unwrap(); - let class_name_index = class.cp_class_ref(class_index).unwrap(); - let class_name = class.cp_utf8(class_name_index).unwrap(); - let field_name = class.cp_utf8(field_name_index).unwrap(); + borrow.cp_name_and_type(field_name_and_type_index).unwrap(); + let class_name_index = borrow.cp_class_ref(class_index).unwrap(); + let class_name = borrow.cp_utf8(class_name_index).unwrap(); + let field_name = borrow.cp_utf8(field_name_index).unwrap(); let mut objectref = self.local_stack().pop()?; if let Value::Ref(instance) = &mut *objectref.get() { @@ -324,14 +318,15 @@ impl Vm { } }, PUTFIELD => unsafe { + let borrow = this_class.borrow(); let cp_index = read_u16(&code.opcodes, pc); let (class_index, field_name_and_type_index) = - class.cp_field_ref(&cp_index).unwrap(); + borrow.cp_field_ref(&cp_index).unwrap(); let (field_name_index, _) = - class.cp_name_and_type(field_name_and_type_index).unwrap(); - let class_name_index = class.cp_class_ref(class_index).unwrap(); - let class_name = class.cp_utf8(class_name_index).unwrap(); - let field_name = class.cp_utf8(field_name_index).unwrap(); + borrow.cp_name_and_type(field_name_and_type_index).unwrap(); + let class_name_index = borrow.cp_class_ref(class_index).unwrap(); + let class_name = borrow.cp_utf8(class_name_index).unwrap(); + let field_name = borrow.cp_utf8(field_name_index).unwrap(); let value = self.local_stack().pop()?; let mut objectref = self.local_stack().pop()?; @@ -388,12 +383,13 @@ impl Vm { }, NEW => { let class_index = &read_u16(&code.opcodes, pc); - let class_name_index = class.cp_class_ref(class_index).unwrap(); - let class_name = class.cp_utf8(class_name_index).unwrap(); - let class = self.get_class(class_name)?; + let borrow = this_class.borrow(); + let class_name_index = borrow.cp_class_ref(class_index).unwrap(); + let class_name = borrow.cp_utf8(class_name_index).unwrap(); + let class_to_instantiate = get_class(self, class_name)?; let object = Arc::new(UnsafeCell::new(ObjectRef::Object(Box::new( - Vm::new_instance(class), + Vm::new_instance(class_to_instantiate), )))); self.local_stack().push(Value::Ref(Arc::clone(&object))); self.heap.new_object(object); diff --git a/tests/Main.class b/tests/Main.class index 91bf6e231a2ea46c3af78c883b38d5cfe0b0f45f..ba1b541760e7e259d3eef73555934742d6836fca 100644 GIT binary patch literal 436 zcmZWm%SyvQ6g{^|n@3|?qt<7Ix=Iwx2Ur9vWEFK$35cs{9ZE`?KurBF5iBV90e+Nt zCltGIGjoPJ=P_r#e?GndT;bS84VnSnK^+Z3XC_`mkcw;?+>K{)QW6@2BumOmg68?L z0h3^bX)em46qyYJnm+UZg&YSPXc5e3S;it=NP_N#er%#cfCU#@2DTmSpv#jRkz@oC z1UFi{j>;m*rWZWF&YxJI9VMB(UChU_xEJG8NzN-`*L(a=^J8`&Owv_idz3GVi3}6P zvy|bvazdY@)cE~`Jr#zL#+VD>b(c>)-Z?Sqr%TZ5>LRMKB3anS!Rl(jLQ1Or3FBc2 vD>`%ScQn_e7Hg||Of=EreoP$x`J7ZPEbO-5u({@_Pf>PtE|qY^xQ5;@4(&s8 literal 328 zcmYLEyH3ME5S;ZhHYPR!LPbI6fP_m5B8ninGEf9rLR9B)iY{`_lCkq&G(-X^AHYW; z_Gog&?CkFBjPB?6YX{&4XCZtfK8`|INSs*;7&h{8?Bj&sO_f&N9l=f~CBeCG-XtL| zl$MYEx{~d)sA`@#YoRn@oW2$-u@PA<^fH@wtzFo*%8FBEy;%lReFhIbA zylzA{lR`&0#hC4kf{^UJo~o5xbcDgX>`GDhCNs?^B`)Cc7tFK3YXpoTcjsJ*t9!Wx xeJ~*$Fna$(jzok*=0=+Fsl_$^hV`UYHUR(t diff --git a/tests/Main.java b/tests/Main.java index c32d15b..38521d5 100644 --- a/tests/Main.java +++ b/tests/Main.java @@ -1,5 +1,11 @@ public class Main { + final static String a; + + static{ + a=""; + } + public static void main(String[] args){ FloatBean f = new FloatBean(); f.setValue(42F);