util = require './util'
ConstantPool = require './ConstantPool'
attributes = require './attributes'
opcodes = require './opcodes'
pull in external modules
util = require './util'
ConstantPool = require './ConstantPool'
attributes = require './attributes'
opcodes = require './opcodes'
Define methods
later to avoid circular dependency; methods references
natives, natives references ClassData
methods = null
{JavaObject,JavaClassObject} = require './java_object'
{trace} = require './logging'
"use strict"
root = exports ? this.ClassData = {}
Represents a single Class in the JVM.
class ClassData
Responsible for setting up all of the fields that are guaranteed to be present on any ClassData object.
constructor: (@loader=null) ->
@access_flags = {}
@initialized = false
@resolved = false
@jco = null
Flip to 1 to trigger reset() call on load.
@reset_bit = 0
Resets any ClassData state that may have been built up We do not reset 'initialized' here; only reference types need to reset that.
reset: ->
@jco = null
@reset_bit = 0
Reset any referenced classes if they are up for reset.
sc = @get_super_class()
if sc?.reset_bit is 1
sc.reset()
for iface in @get_interfaces
if iface.reset_bit is 1 then iface.reset()
return
toExternalString: () -> util.ext_classname @this_class
getLoadState: ->
if @initialized then 'initialized'
else if @resolved then 'resolved'
else 'loaded'
Returns the ClassLoader object of the classloader that initialized this class. Returns null for the default classloader.
get_class_loader: -> @loader
get_type: -> @this_class
get_super_class_type: -> @super_class
get_super_class: -> @super_class_cdata
get_interface_types: -> []
get_interfaces: -> []
get_class_object: (rs) ->
@jco = new JavaClassObject(rs, @) unless @jco?
@jco
get_method: -> null
get_methods: -> {}
get_fields: -> []
method_lookup: (rs, spec, null_handled) ->
return null if null_handled
rs.java_throw rs.get_bs_class('Ljava/lang/NoSuchMethodError;'),
"No such method found in #{util.ext_classname(method_spec.class)}::#{method_spec.sig}"
field_lookup: (rs, spec, null_handled) ->
return null if null_handled
rs.java_throw rs.get_bs_class('Ljava/lang/NoSuchFieldError;'),
"No such field found in #{util.ext_classname(field_spec.class)}::#{field_spec.name}"
Checks if the class file is initialized. It will set @initialized to 'true' if this class has no static initialization method and its parent classes are initialized, too.
is_initialized: ->
return true if @initialized
return false unless @is_resolved()
return false if @get_method('<clinit>()V')?
@initialized = if @get_super_class()?.is_initialized() else false
return @initialized
is_resolved: -> @resolved
is_subinterface: -> false
is_subclass: (target) ->
return true if @ is target
return false unless @get_super_class()? # I'm java/lang/Object, can't go further
return @get_super_class().is_subclass target
class root.PrimitiveClassData extends ClassData
constructor: (@this_class, loader) ->
super loader
@initialized = true
@resolved = true
Returns a boolean indicating if this class is an instance of the target class. "target" is a ClassData object. The ClassData objects do not need to be initialized; just loaded.
is_castable: (target) -> @this_class == target.this_class
create_wrapper_object: (rs, value) ->
type_desc = switch @this_class
when 'B' then 'Ljava/lang/Byte;'
when 'C' then 'Ljava/lang/Character;'
when 'D' then 'Ljava/lang/Double;'
when 'F' then 'Ljava/lang/Float;'
when 'I' then 'Ljava/lang/Integer;'
when 'J' then 'Ljava/lang/Long;'
when 'S' then 'Ljava/lang/Short;'
when 'Z' then 'Ljava/lang/Boolean;'
else
throw new Error("Tried to create_wrapper_object for type #{@this_class}")
these are all initialized in preinit (for the BSCL, at least)
wrapped = new JavaObject rs, rs.get_bs_class(type_desc)
HACK: all primitive wrappers store their value in a private static final field named 'value'
wrapped.fields[type_desc+'value'] = value
return wrapped
class root.ArrayClassData extends ClassData
constructor: (@component_type, loader) ->
super loader
@this_class = "[#{@component_type}"
@super_class = 'Ljava/lang/Object;'
reset: ->
super()
ccls = @get_component_class()
if ccls?.reset_bit
ccls.reset()
return
get_component_type: -> return @component_type
get_component_class: -> return @component_class_cdata
This class itself has no fields/methods, but java/lang/Object does.
field_lookup: (rs, field_spec) -> @super_class_cdata.field_lookup rs, field_spec
method_lookup: (rs, field_spec) -> @super_class_cdata.method_lookup rs, field_spec
Resolved and initialized are the same for array types.
set_resolved: (@super_class_cdata, @component_class_cdata) ->
@resolved = true
@initialized = true
Returns a boolean indicating if this class is an instance of the target class. "target" is a ClassData object. The ClassData objects do not need to be initialized; just loaded. See §2.6.7 for casting rules.
is_castable: (target) -> # target is c2
unless target instanceof root.ArrayClassData
return false if target instanceof root.PrimitiveClassData
Must be a reference type.
if target.access_flags.interface
Interface reference type
return target.get_type() in ['Ljava/lang/Cloneable;','Ljava/io/Serializable;']
Non-interface reference type
return target.get_type() is 'Ljava/lang/Object;'
We are both array types, so it only matters if my component type can be cast to its component type.
return @get_component_class().is_castable(target.get_component_class())
Represents a "reference" Class -- that is, a class that neither represents a primitive nor an array.
class root.ReferenceClassData extends ClassData
constructor: (bytes_array, loader) ->
XXX: Circular dependency hack.
unless methods?
methods = require './methods'
super loader
bytes_array = new util.BytesArray bytes_array
throw "Magic number invalid" if (bytes_array.get_uint 4) != 0xCAFEBABE
@minor_version = bytes_array.get_uint 2
@major_version = bytes_array.get_uint 2
throw "Major version invalid" unless 45 <= @major_version <= 51
@constant_pool = new ConstantPool
@constant_pool.parse(bytes_array)
bitmask for {public,final,super,interface,abstract} class modifier
@access_byte = bytes_array.get_uint 2
@access_flags = util.parse_flags @access_byte
@this_class = @constant_pool.get(bytes_array.get_uint 2).deref()
super reference is 0 when there's no super (basically just java.lang.Object)
super_ref = bytes_array.get_uint 2
@super_class = @constant_pool.get(super_ref).deref() unless super_ref is 0
direct interfaces of this class
isize = bytes_array.get_uint 2
@interfaces = (@constant_pool.get(bytes_array.get_uint 2).deref() for i in [0...isize] by 1)
fields of this class
num_fields = bytes_array.get_uint 2
@fields = (new methods.Field(@) for i in [0...num_fields] by 1)
@fl_cache = {}
for f,i in @fields
f.parse(bytes_array,@constant_pool,i)
@fl_cache[f.name] = f
class methods
num_methods = bytes_array.get_uint 2
@methods = {}
It would probably be safe to make @methods the @ml_cache, but it would make debugging harder as you would lose track of who owns what method.
@ml_cache = {}
for i in [0...num_methods] by 1
m = new methods.Method(@)
m.parse(bytes_array,@constant_pool,i)
mkey = m.name + m.raw_descriptor
@methods[mkey] = m
@ml_cache[mkey] = m
class attributes
@attrs = attributes.make_attributes(bytes_array,@constant_pool)
throw "Leftover bytes in classfile: #{bytes_array}" if bytes_array.has_bytes()
Contains the value of all static fields. Will be reset when reset() is run.
@static_fields = Object.create null
Resets the ClassData for subsequent JVM invocations. Resets all
of the built up state / caches present in the opcode instructions.
Eventually, this will also handle clinit
duties.
reset: ->
super()
@initialized = false
@static_fields = Object.create null
for method in @methods
method.initialize()
get_interfaces: -> @interface_cdatas
get_interface_types: -> @interfaces
get_fields: -> @fields
get_method: (sig) -> @methods[sig]
get_methods: -> @methods
get_attribute: (name) ->
for attr in @attrs then if attr.name is name then return attr
return null
get_attributes: (name) -> attr for attr in @attrs when attr.name is name
get_default_fields: ->
return @default_fields unless @default_fields is undefined
@construct_default_fields()
return @default_fields
Handles static fields. We lazily create them, since we cannot initialize static default String values before Ljava/lang/String; is initialized.
_initialize_static_field: (rs, name) ->
f = @fl_cache[name]
if f?.access_flags.static
cva = f.get_attribute 'ConstantValue'
if cva?
cv = if f.type is 'Ljava/lang/String;' then rs.init_string cva.value else cva.value
@static_fields[name] = if cv? then cv else util.initial_value f.raw_descriptor
else
rs.java_throw @loader.get_initialized_class('Ljava/lang/NoSuchFieldError;'), name
return
static_get: (rs, name) ->
return @static_fields[name] unless @static_fields[name] is undefined
@_initialize_static_field rs, name
return @static_get rs, name
static_put: (rs, name, val) ->
unless @static_fields[name] is undefined
@static_fields[name] = val
else
@_initialize_static_field rs, name
@static_put rs, name, val
set_resolved: (@super_class_cdata, interface_cdatas) ->
trace "Class #{@get_type()} is now resolved."
@interface_cdatas = if interface_cdatas? then interface_cdatas else []
@resolved = true
construct_default_fields: () ->
init fields from this and inherited ClassDatas
cls = @
Object.create(null) avoids interference with Object.prototype's properties
@default_fields = Object.create null
while cls?
for f in cls.fields when not f.access_flags.static
val = util.initial_value f.raw_descriptor
@default_fields[cls.get_type() + f.name] = val
cls = cls.get_super_class()
return
field_lookup: (rs, field_spec, null_handled) ->
field = @fl_cache[field_spec.name]
return field if field?
field = @_field_lookup(rs, field_spec)
if field? or null_handled is true
@fl_cache[field_spec.name] = field
return field
Throw exception
rs.java_throw rs.get_bs_class('Ljava/lang/NoSuchFieldError;'),
"No such field found in #{util.ext_classname(field_spec.class)}::#{field_spec.name}"
_field_lookup: (rs, field_spec) ->
for field in @fields
if field.name is field_spec.name
return field
These may not be initialized! But we have them loaded.
for ifc_cls in @get_interfaces()
field = ifc_cls.field_lookup(rs, field_spec, true)
return field if field?
sc = @get_super_class()
if sc?
field = sc.field_lookup(rs, field_spec, true)
return field if field?
return null
method_lookup: (rs, method_spec, null_handled) ->
method = @ml_cache[method_spec.sig]
return method if method?
method = @_method_lookup(rs, method_spec)
if method? or null_handled is true
@ml_cache[method_spec.sig] = method
return method
Throw exception
rs.java_throw rs.get_bs_class('Ljava/lang/NoSuchMethodError;'),
"No such method found in #{util.ext_classname(method_spec.class)}::#{method_spec.sig}"
_method_lookup: (rs, method_spec) ->
method = @methods[method_spec.sig]
return method if method?
parent = @get_super_class()
if parent?
method = parent.method_lookup(rs, method_spec, true)
return method if method?
for ifc in @get_interfaces()
method = ifc.method_lookup(rs, method_spec, true)
return method if method?
return null
Returns a boolean indicating if this class is an instance of the target class. "target" is a ClassData object. The ClassData objects do not need to be initialized; just loaded. See §2.6.7 for casting rules.
is_castable: (target) ->
return false unless target instanceof root.ReferenceClassData
if @access_flags.interface
We are both interfaces
if target.access_flags.interface then return @is_subinterface(target)
Only I am an interface
return target.get_type() is 'Ljava/lang/Object;' unless target.access_flags.interface
else
I am a regular class, target is an interface
if target.access_flags.interface then return @is_subinterface(target)
We are both regular classes
return @is_subclass(target)
Returns 'true' if I implement the target interface.
is_subinterface: (target) ->
return true if @this_class is target.this_class
for super_iface in @get_interfaces()
return true if super_iface.is_subinterface target
return false unless @get_super_class()? # I'm java/lang/Object, can't go further
return @get_super_class().is_subinterface target