binary-parser
binary-parser copied to clipboard
JVM Class File parser example does not work for classes that contain Longs and Doubles in the Constant Pool
Background
... this is due to them taking up two indices in the Constant Pool:
All 8-byte constants take up two entries in the
constant_pooltable of theclassfile. If aCONSTANT_Long_infoorCONSTANT_Double_infostructure is the item in theconstant_pooltable at indexn, then the next usable item in the pool is located at index n+2. Theconstant_poolindexn+1 must be valid but is considered unusable.In retrospect, making 8-byte constants take two constant pool entries was a poor choice.
I know this isn't a repo focused on JVM class file parsing, but thought it might spark a discussion or feature that allows for better interoperability non-standard binary formats like this.
Reproducing
$ javap -v <path to class file with at least one long or double> can verify my claims if you compare the output from the constant pool to that of the example Parser instance. The only problem with that, is there will be an error thrown before you can grab the contents from the parser.
Here's what I did to fix it and it works across Java 6/7/8:
export const ClassFileParser =
Parser.start()
.endianess('big')
.uint32('magic', { assert: JVM_CLASS_FILE_MAGIC_NUMBER })
.uint16('minor_version')
.uint16('major_version')
.uint16('constant_pool_count')
.array('constant_pool', {
type: ConstantPoolInfo,
length: function () {
let previousIndex = this.constant_pool.length - 1;
let previousEntry = this.constant_pool[previousIndex];
if (previousEntry && (previousEntry.tag === 6 || previousEntry.tag === 5)) {
this.constant_pool[previousIndex - 1] = this.constant_pool[previousIndex];
this.constant_pool_count -= 1;
this.constant_pool.push(false);
}
return this.constant_pool_count - 1;
}
})
.uint16('access_flags')
.uint16('this_class')
.uint16('super_class')
.uint16('interface_count')
.array('interfaces', {
type: InterfaceInfo,
length: function () {
return this.interface_count - 1;
}
})
.uint16('field_count')
.array('fields', {
type: ClassMemberInfo,
length: function () {
return this.field_count;
}
})
.uint16('method_count')
.array('methods', {
type: ClassMemberInfo,
length: function () {
return this.method_count;
}
})
.uint16('attribute_count')
.array('attributes', {
type: AttributeInfo,
length: function () {
return this.attribute_count;
}
});
While this does work, it's not very pretty having to do the following if the previous entry was a Long or Double:
- move the previous entry back one slot in the array
- subtract from the pool count
- push a value that somewhat resembles a sentinel value (if I see an entry
=== falseI know there was an incorrect index used)
But at least it makes it work :wink:
Thanks for releasing this library open source -- really made my life a lot easier and it has great performance :smile:
Output from running the above code.
1102 'NameAndType'
1103 'Fieldref'
1104 'Long'
1106 'Long'
1108 'Long'
1110 'NameAndType'
1111 'Fieldref'
Here's some validation for this by grepping javap's output:
