blob: e79319545d97a15cc1bbe9e1c07315beddaad960 [file] [log] [blame]
#include "jit/bc-offset-mapping.h"
#include "jit/bytecode-to-ir.h"
#include "jit/expression.h"
#include "jit/exception.h"
#include "jit/subroutine.h"
#include "jit/statement.h"
#include "jit/tree-node.h"
#include "jit/compiler.h"
#include "cafebabe/constant_pool.h"
#include "vm/bytecode.h"
#include "vm/method.h"
#include "vm/die.h"
#include "vm/trace.h"
#include "vm/preload.h"
#include "vm/verifier.h"
#include "lib/stack.h"
#include "lib/bitset.h"
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#define verify_goto_w verify_goto
#define verify_jsr_w verify_jsr
#define BYTECODE(opc, name, size, type) [opc] = verify_ ## name,
static verify_fn_t verifiers[] = {
# include <vm/bytecode-def.h>
};
#undef BYTECODE
bool opt_trace_verifier;
static const char *vm_type_to_str(enum vm_type vm_type)
{
switch(vm_type) {
case J_VOID: return "J_VOID";
case J_REFERENCE: return "J_REFERENCE";
case J_BYTE: return "J_BYTE";
case J_SHORT: return "J_SHORT";
case J_INT: return "J_INT";
case J_LONG: return "J_LONG";
case J_CHAR: return "J_CHAR";
case J_FLOAT: return "J_FLOAT";
case J_DOUBLE: return "J_DOUBLE";
case J_BOOLEAN: return "J_BOOLEAN";
case J_RETURN_ADDRESS: return "J_RETURN_ADDRESS";
case VM_TYPE_MAX: return "VM_TYPE_MAX";
default: return "Unknown type";
}
}
struct verifier_local_var *alloc_verifier_local_var(int nb_vars)
{
int i;
struct verifier_local_var *vars;
vars = malloc(nb_vars * sizeof(struct verifier_local_var));
if (!vars)
return NULL;
for (i=0;i<nb_vars;i++) {
vars[i].state = UNKNOWN;
vars[i].op.is_fragment = false;
}
return vars;
}
void free_verifier_local_var(struct verifier_local_var *vars)
{
free(vars);
}
struct verifier_stack *alloc_verifier_stack(enum vm_type vm_type)
{
struct verifier_stack *newst;
newst = malloc(sizeof(struct verifier_stack));
if (!newst)
return NULL;
newst->op.vm_type = vm_type;
newst->op.is_fragment = false;
return newst;
}
void free_verifier_stack(struct verifier_stack *stack)
{
free(stack);
}
struct verifier_state *alloc_verifier_state(int nb_vars)
{
struct verifier_state *news;
news = malloc(sizeof(struct verifier_state));
if (!news)
return NULL;
news->stack = alloc_verifier_stack(VM_TYPE_MAX);
if (!news->stack)
return NULL;
INIT_LIST_HEAD(&news->stack->slots);
news->vars = alloc_verifier_local_var(nb_vars);
news->nb_vars = nb_vars;
return news;
}
void free_verifier_state(struct verifier_state *state)
{
struct verifier_stack *ptr, *tmp;
free_verifier_local_var(state->vars);
list_for_each_entry_safe(ptr, tmp, &state->stack->slots, slots) {
list_del(&ptr->slots);
free_verifier_stack(ptr);
}
free_verifier_stack(state->stack);
free(state);
}
struct verifier_block *alloc_verifier_block(struct verifier_context *vrf, uint32_t begin_offset)
{
struct verifier_block *newb;
newb = malloc(sizeof(struct verifier_block));
if (!newb)
return NULL;
newb->begin_offset = begin_offset;
newb->code = vrf->code + begin_offset;
newb->pc = 0;
newb->initial_state = alloc_verifier_state(vrf->max_locals);
if (!newb->initial_state)
return NULL;
newb->final_state = alloc_verifier_state(vrf->max_locals);
if (!newb->final_state)
return NULL;
newb->following_offsets = malloc(sizeof(uint32_t) * INITIAL_FOLLOWERS_SIZE);
newb->nb_followers = 0;
newb->parent_ctx = vrf;
return newb;
}
void free_verifier_block(struct verifier_block *block)
{
free_verifier_state(block->initial_state);
free_verifier_state(block->final_state);
free(block->following_offsets);
free(block);
}
struct verifier_jump_destinations *alloc_verifier_jump_destinations(unsigned long dest)
{
struct verifier_jump_destinations *newjd;
newjd = malloc(sizeof(struct verifier_jump_destinations));
if (!newjd)
return NULL;
newjd->dest = dest;
return newjd;
}
void free_verifier_jump_destinations(struct verifier_jump_destinations *jmp_dests)
{
free(jmp_dests);
}
struct verifier_context *alloc_verifier_context(struct vm_method *vmm)
{
struct verifier_context *vrf;
vrf = malloc(sizeof(struct verifier_context));
if (!vrf)
return NULL;
vrf->method = vmm;
vrf->code = vmm->code_attribute.code;
vrf->code_size = vmm->code_attribute.code_length;
vrf->max_locals = vmm->code_attribute.max_locals;
vrf->max_stack = vmm->code_attribute.max_stack;
vrf->jmp_dests = alloc_verifier_jump_destinations(0);
if (!vrf->jmp_dests)
return NULL;
INIT_LIST_HEAD(&vrf->jmp_dests->list);
vrf->vb_list = alloc_verifier_block(vrf, 0);
if (!vrf->vb_list)
return NULL;
INIT_LIST_HEAD(&vrf->vb_list->blocks);
return vrf;
}
void free_verifier_context(struct verifier_context *ctx)
{
struct verifier_block *vbtmp, *vbptr;
struct verifier_jump_destinations *vjdtmp, *vjdptr;
list_for_each_entry_safe(vbptr, vbtmp, &ctx->vb_list->blocks, blocks) {
list_del(&vbptr->blocks);
free_verifier_block(vbptr);
}
free_verifier_block(ctx->vb_list);
list_for_each_entry_safe(vjdptr, vjdtmp, &ctx->jmp_dests->list, list) {
list_del(&vjdptr->list);
free_verifier_jump_destinations(vjdptr);
}
free_verifier_jump_destinations(ctx->jmp_dests);
free(ctx);
}
#if 0
static enum cafebabe_constant_tag vm_type_to_constant_pool_tag(enum vm_type vm_type) {
switch (vm_type) {
case J_INT:
return CAFEBABE_CONSTANT_TAG_INTEGER;
case J_LONG:
return CAFEBABE_CONSTANT_TAG_LONG;
case J_FLOAT:
return CAFEBABE_CONSTANT_TAG_FLOAT;
case J_DOUBLE:
return CAFEBABE_CONSTANT_TAG_DOUBLE;
case J_REFERENCE:
return CAFEBABE_CONSTANT_TAG_STRING;
default:
return -1;
}
}
#endif
static int verify_constant_tag(struct verifier_context *vrf, enum cafebabe_constant_tag tag, unsigned int index)
{
const struct cafebabe_class *class = vrf->method->class->class;
if (index >= class->constant_pool_count)
return vrf_err("Invalid reference to constant %u.\n", index), E_WRONG_CONSTANT_POOL_INDEX;
if (class->constant_pool[index].tag != tag)
return E_TYPE_CHECKING;
return 0;
}
static inline void undef_vrf_lvar(struct verifier_state *s, unsigned int idx)
{
s->vars[idx].state = UNDEFINED;
}
static inline void def_vrf_lvar(struct verifier_state *s, enum vm_type vm_type, unsigned int idx)
{
s->vars[idx].state = DEFINED;
s->vars[idx].op.vm_type = vm_type;
if (!vm_type_is_pair(vm_type))
return;
s->vars[idx].op.is_fragment = false;
s->vars[idx+1].state = DEFINED;
s->vars[idx+1].op.vm_type = vm_type;
s->vars[idx+1].op.is_fragment = true;
}
int peek_vrf_lvar(struct verifier_block *b, enum vm_type vm_type, unsigned int idx)
{
struct verifier_state *final = b->final_state, *init = b->initial_state;
/* If the state is unknown we cannot tell if the transition is valid.
* But we can infer the type of the variable for the rest of the block.
*/
if (final->vars[idx].state == UNKNOWN) {
if (vm_type_is_pair(vm_type) && final->vars[idx+1].state != UNKNOWN)
return E_TYPE_CHECKING;
def_vrf_lvar(init, vm_type, idx);
return store_vrf_lvar(b, vm_type, idx);
}
if (final->vars[idx].op.vm_type != vm_type)
return vrf_err("Local variable %i (out of %d) of wrong type: expected %s, got %s.\n", idx, final->nb_vars, vm_type_to_str(vm_type), vm_type_to_str(final->vars[idx].op.vm_type)), E_TYPE_CHECKING;
if (vm_type_is_pair(vm_type)) {
if (final->vars[idx+1].state != DEFINED)
return E_TYPE_CHECKING;
if (final->vars[idx].op.is_fragment || final->vars[idx+1].op.vm_type != vm_type || !final->vars[idx+1].op.is_fragment)
return E_TYPE_CHECKING;
}
return 0;
}
int store_vrf_lvar(struct verifier_block *s, enum vm_type vm_type, unsigned int idx)
{
def_vrf_lvar(s->final_state, vm_type, idx);
return 0;
}
static inline int tail_def_vrf_op(struct verifier_state *s, enum vm_type vm_type)
{
struct verifier_stack *new;
new = alloc_verifier_stack(vm_type);
if (!new)
return ENOMEM;
if (vm_type_is_pair(vm_type)) {
new->op.is_fragment = true;
list_add_tail(&new->slots, &s->stack->slots);
new = alloc_verifier_stack(vm_type);
if (!new)
return ENOMEM;
}
list_add_tail(&new->slots, &s->stack->slots);
return 0;
}
int vrf_stack_size(struct verifier_stack *st)
{
return list_size(&st->slots);
}
int push_vrf_op(struct verifier_block *b, enum vm_type vm_type)
{
struct verifier_stack *new;
new = alloc_verifier_stack(vm_type);
if (!new)
return ENOMEM;
if (vm_type_is_pair(vm_type)) {
list_add(&new->slots, &b->final_state->stack->slots);
new = alloc_verifier_stack(vm_type);
if (!new)
return ENOMEM;
new->op.is_fragment = true;
}
list_add(&new->slots, &b->final_state->stack->slots);
return 0;
}
int pop_vrf_op(struct verifier_block *b, enum vm_type vm_type)
{
struct verifier_state *final = b->final_state, *init = b->initial_state;
struct verifier_stack *el;
el = list_first_entry(&final->stack->slots, struct verifier_stack, slots);
/* VM_TYPE_MAX marks the bottom of the stack. If we see it we can
* infer what would have been needed in the the initial stack.
*/
if (el->op.vm_type == VM_TYPE_MAX)
return tail_def_vrf_op(init, vm_type);
if (vm_type_is_pair(vm_type)) {
if (el->op.vm_type != vm_type || !el->op.is_fragment)
return vrf_err("Error popping stack element of type %s instead of %s.\n", vm_type_to_str(el->op.vm_type), vm_type_to_str(vm_type)), E_TYPE_CHECKING;
list_del(&el->slots);
free(el);
el = list_first_entry(&final->stack->slots, struct verifier_stack, slots);
}
if (el->op.vm_type != vm_type || el->op.is_fragment)
return vrf_err("Error popping stack element of type %s instead of %s.\n", vm_type_to_str(el->op.vm_type), vm_type_to_str(vm_type)), E_TYPE_CHECKING;
list_del(&el->slots);
free(el);
return 0;
}
int peek_vrf_op(struct verifier_block *b, enum vm_type vm_type)
{
struct verifier_state *final = b->final_state, *init = b->initial_state;
struct verifier_stack *el;
el = list_first_entry(&final->stack->slots, struct verifier_stack, slots);
/* VM_TYPE_MAX marks the bottom of the stack. If we see it we can
* then infer what would have been needed in the the initial stack.
*/
if (el->op.vm_type == VM_TYPE_MAX)
return tail_def_vrf_op(init, vm_type);
if (vm_type_is_pair(vm_type)) {
if (el->op.vm_type != vm_type || !el->op.is_fragment)
return vrf_err("Error peeking stack element of type %s instead of %s.\n", vm_type_to_str(el->op.vm_type), vm_type_to_str(vm_type)), E_TYPE_CHECKING;
el = list_entry(el->slots.next, struct verifier_stack, slots);
}
if (el->op.vm_type != vm_type || el->op.is_fragment)
return vrf_err("Error peeking stack element of type %s instead of %s.\n", vm_type_to_str(el->op.vm_type), vm_type_to_str(vm_type)), E_TYPE_CHECKING;
return 0;
}
static inline int valid_dest(int offset, unsigned long pc, unsigned long code_size)
{
if ((long) pc + offset < 0)
return vrf_err("Invalid negative jump destination %ld.\n", (long) pc + offset), E_INVALID_BRANCH;
if (pc + offset >= code_size)
return vrf_err("Invalid jump destination %lu.\n", (unsigned long) pc + offset), E_INVALID_BRANCH;
return 0;
}
int add_jump_destination(struct verifier_jump_destinations *jd, unsigned long dest)
{
struct verifier_jump_destinations *cur, *new;
list_for_each_entry(cur, &jd->list, list) {
if (dest > cur->dest)
continue;
else if (dest == cur->dest)
return 0;
new = alloc_verifier_jump_destinations(dest);
if (!new)
return ENOMEM;
list_add_tail(&new->list, &cur->list);
return 0;
}
/* If we're here the new destination is greater than the current
* max of the list, so we just add it at the end.
*/
new = alloc_verifier_jump_destinations(dest);
if (!new)
return ENOMEM;
list_add_tail(&new->list, &jd->list);
return 0;
}
int add_tableswitch_destinations(struct verifier_jump_destinations *jd, const unsigned char *code, unsigned long pc, unsigned long code_size)
{
int high, low, def, err, offset;
unsigned long base_pc, end_pc;
base_pc = pc;
pc += 1 + get_tableswitch_padding(pc);
def = read_s32(code + pc);
low = read_s32(code + pc + 4);
high = read_s32(code + pc + 8);
pc += 12;
err = valid_dest(def, base_pc, code_size);
if (err)
return err;
err = add_jump_destination(jd, (unsigned long) base_pc + def);
if (err)
return err;
end_pc = (high - low) * 4 + pc;
while (pc < end_pc) {
offset = read_s32(code + pc);
err = valid_dest(offset, base_pc, code_size);
if (err)
return err;
err = add_jump_destination(jd, (unsigned long) base_pc + offset);
if (err)
return err;
pc += 4;
}
return 0;
}
int add_lookupswitch_destinations(struct verifier_jump_destinations *jd, const unsigned char *code, unsigned long pc, unsigned long code_size)
{
int npairs, def, err, offset;
unsigned long base_pc, end_pc;
base_pc = pc;
pc += 1 + get_tableswitch_padding(pc);
def = read_s32(code + pc);
npairs = read_s32(code + pc + 4);
pc += 8;
err = valid_dest(def, base_pc, code_size);
if (err)
return err;
err = add_jump_destination(jd, (unsigned long) base_pc + def);
if (err)
return err;
end_pc = npairs * 4 * 2 + pc;
pc += 4; /* We position PC on the offsets. */
while (pc < end_pc) {
offset = read_s32(code + pc);
err = valid_dest(offset, base_pc, code_size);
if (err)
return err;
err = add_jump_destination(jd, (unsigned long) base_pc + offset);
if (err)
return err;
pc += 8;
}
return 0;
}
static int cmp_verifier_op(struct verifier_operand *op1, struct verifier_operand *op2)
{
if (op1->vm_type != op2->vm_type)
return vrf_err("Operand comparison: different types: %s and %s.\n", vm_type_to_str(op1->vm_type), vm_type_to_str(op2->vm_type)), E_TYPE_CHECKING;
if (op1->is_fragment != op2->is_fragment)
return vrf_err("Operand comparison: one operand is fragment and the other is not.\n"), E_TYPE_CHECKING;
return 0;
}
int transition_verifier_stack(struct verifier_stack *stc, struct verifier_stack *stn)
{
int err;
struct verifier_stack *cp, *np;
/* If the stack of the current state is smaller than the stack
* expected in the next state we know the states are
* imcompatible.
*/
if (list_size(&stc->slots) < list_size(&stn->slots))
return E_TYPE_CHECKING;
/* The current stack may contain more elements than the one from
* the next stack, but we are not interested in the additional
* elements here (we will adjust the next stack later). What we
* need is to compare the top elements until the next stack is
* empty.
*/
np = list_first_entry(&stn->slots, struct verifier_stack, slots);
list_for_each_entry(cp, &stc->slots, slots) {
/* The type VM_TYPE_MAX is used to signal the stack bottom.
*/
if (np->op.vm_type == VM_TYPE_MAX)
break;
err = cmp_verifier_op(&cp->op, &np->op);
if (err)
return err;
np = list_entry(np->slots.next, struct verifier_stack, slots);
}
return 0;
}
int transition_verifier_local_var(struct verifier_local_var *varsc, struct verifier_local_var *varsn, int nb_vars)
{
int i, err;
for (i=0;i<nb_vars;i++) {
/* if the variable is unknown in the next state the
* transition is valid automatically.
*/
if (varsn->state == UNKNOWN)
goto next_iteration;
/* if the variable is defined in the next state and not
* in the current one the transition is invalid.
*/
if (varsn->state == DEFINED && varsc->state == UNDEFINED)
return E_TYPE_CHECKING;
err = cmp_verifier_op(&varsc->op, &varsn->op);
if (err)
return err;
next_iteration:
varsc++;
varsn++;
}
return 0;
}
int transition_verifier_state(struct verifier_state *sc, struct verifier_state *sn)
{
int err;
err = transition_verifier_stack(sc->stack, sn->stack);
if (err)
return err;
/* We assume the number of variables is set for the method and
* will not differ between sc and sn.
*/
err = transition_verifier_local_var(sc->vars, sn->vars, sc->nb_vars);
if (err)
return err;
return 0;
}
static int verify_exception_table(struct cafebabe_code_attribute *ca, struct verifier_jump_destinations *jd)
{
int err, i = 1;
struct cafebabe_code_attribute_exception *ex = ca->exception_table;
if (ca->exception_table_length == 0)
return 0;
do {
if (ex->start_pc > ex->end_pc)
return vrf_err("Exception table: protected code section borns are inversed.\n"), E_INVALID_EXCEPTION_HANDLER;
if (ex->end_pc > ca->code_length)
return vrf_err("Exception table: protected code section ends after method code end.\n"), E_INVALID_EXCEPTION_HANDLER;
err = add_jump_destination(jd, ex->handler_pc);
if (err)
return err;
ex++;
i++;
} while (i < ca->exception_table_length);
return 0;
}
static const char *verify_error_string = "No error";
static void verify_error(int err, unsigned long pos, struct verifier_context *vrf)
{
switch (err) {
case E_MALFORMED_BC:
verify_error_string = "Malformed bytecode";
break;
case E_TYPE_CHECKING:
verify_error_string = "Type checking";
break;
case E_INVALID_BRANCH:
verify_error_string = "Invalid jump";
break;
case E_WRONG_LOCAL_INDEX:
verify_error_string = "Reference to a too high local variable";
break;
case E_WRONG_CONSTANT_POOL_INDEX:
verify_error_string = "Reference to a too high constant";
break;
case E_FALLING_OFF:
verify_error_string = "Falling off method code";
break;
case E_INVALID_EXCEPTION_HANDLER:
verify_error_string = "Erroneous exception table";
vrf_err("Error in exception table in method %s of class %s.", vrf->method->name, vrf->method->class->name);
return;
case E_OVERRIDES_FINAL:
verify_error_string = "Overriding final method";
vrf_err("Method %s of class %s is overriding method %s of class %s which is declared final.", vrf->method->name, vrf->method->class->name, vrf->method->name, vrf->method->class->super->name);
return;
case E_NOT_IMPLEMENTED:
return;
default:
verify_error_string = "Unknown error";
}
vrf_err("%s at PC=%lu (code size: %lu) in method %s of class %s.", verify_error_string, pos, vrf->code_size, vrf->method->name, vrf->method->class->name);
}
static int verifier_first_pass(struct verifier_context *vrf)
{
int offset, err = 0;
long next_insn_offset;
unsigned int index;
unsigned long pc;
unsigned char *code, *last_insn_ptr = vrf->code;
struct bitset *insn_map;
struct verifier_jump_destinations *jd;
pc = 0;
code = vrf->code;
insn_map = alloc_bitset(vrf->code_size);
while (pc < vrf->code_size) {
/* Ensure the code doesn't end in the middle of an
* instruction and that the opcode is valid.
*/
next_insn_offset = bc_insn_size_safe(code, pc, vrf->code_size);
if (next_insn_offset < 0)
err = next_insn_offset;
if (err)
goto out;
/* Adding the position of the current instruction to the
* table for later comparison with possible jump
* destinations.
*/
set_bit(insn_map->bits, pc);
if (bc_is_branch(code[pc])) {
/* In case of the RET instruction, the jump
* destination is stored in a local variable, so we
* cannot check anything about it as we do not
* manipulate values here. Later type checking will
* ensure no shenanigans are intented involving the
* return address...
*/
if (bc_is_ret(code))
goto next_iteration;
else if (code[pc] == OPC_TABLESWITCH) {
err = add_tableswitch_destinations(vrf->jmp_dests, code, pc, vrf->code_size);
if (!err)
goto next_iteration;
}
else if (code[pc] == OPC_LOOKUPSWITCH) {
err = add_lookupswitch_destinations(vrf->jmp_dests, code, pc, vrf->code_size);
if (!err)
goto next_iteration;
}
if (err)
goto out;
offset = bc_target_off(code + pc);
/* Check if the jump destination is possible. */
err = valid_dest(offset, pc, vrf->code_size);
if (err)
goto out;
/* And register the possible jump destination. */
err = add_jump_destination(vrf->jmp_dests, pc + offset);
if (err)
goto out;
}
else if (bc_uses_local_var(code[pc])) {
index = get_local_var_index(code, pc);
if (index < vrf->max_locals)
goto next_iteration;
vrf_err("Invalid local variable %u referenced.\n", index);
err = E_WRONG_LOCAL_INDEX;
goto out;
}
else if (bc_is_ldc(code[pc])) {
if (code[pc] == OPC_LDC)
index = read_u8(code + pc + 1);
else
index = read_u16(code + pc + 1);
if (code[pc] == OPC_LDC2_W) {
err = verify_constant_tag(vrf, CAFEBABE_CONSTANT_TAG_DOUBLE, index);
if (!err)
goto next_iteration;
err = verify_constant_tag(vrf, CAFEBABE_CONSTANT_TAG_LONG, index);
}
else {
err = verify_constant_tag(vrf, CAFEBABE_CONSTANT_TAG_INTEGER, index);
if (!err)
goto next_iteration;
err = verify_constant_tag(vrf, CAFEBABE_CONSTANT_TAG_FLOAT, index);
if (!err)
goto next_iteration;
err = verify_constant_tag(vrf, CAFEBABE_CONSTANT_TAG_STRING, index);
if (!err)
goto next_iteration;
/* XXX This seems illegal but Class does load a class reference with ldc ... */
err = verify_constant_tag(vrf, CAFEBABE_CONSTANT_TAG_CLASS, index);
}
if (err) {
vrf_err("Invalid constant type for constant index %u.\n", index);
goto out;
}
}
next_iteration:
last_insn_ptr = code + pc;
pc += next_insn_offset;
}
/* Check the exception table of the method (that the borns of the
* interval are in order, the end born doesn't go past the end of
* the actual code), and adds the handlers as possible jump
* destinations.
*/
err = verify_exception_table(&vrf->method->code_attribute, vrf->jmp_dests);
if (err)
goto out;
/* Check the jump destinations against the instruction map to
* ensure no jump is trying the redirect the control flow to an
* operand instead of an instruction start.
*/
list_for_each_entry(jd, &vrf->jmp_dests->list, list) {
if (test_bit(insn_map->bits, jd->dest))
continue;
vrf_err("Invalid jump destination %lu.\n", jd->dest);
err = E_INVALID_BRANCH;
/* Here we do something that may look strange but
* that won't hurt performance anyway because it
* only happens if there is actually an error : we
* go through the method code another time to find
* the first instruction trying to jump at the
* invalid offset, so that we can print the right
* PC with verify_error at the exit.
*/
pc = 0;
while (pc < vrf->code_size) {
next_insn_offset = bc_insn_size_safe(code, pc, vrf->code_size);
if (bc_is_branch(code[pc])) {
if (bc_is_ret(code))
goto _next_iteration;
offset = bc_target_off(code + pc);
if (pc + offset == jd->dest)
goto out;
}
_next_iteration:
pc += next_insn_offset;
}
}
/* If the PC is not right on the end we're missing some part of the
* last instruction.
*/
if (pc != vrf->code_size)
err = E_MALFORMED_BC;
/* If the last instruction was not a return of some kind or an
* inconditional jump there is a chance of 'falling off' the code
* of the method, which is illegal.
*/
if (!(bc_is_unconditionnal_branch(last_insn_ptr)))
err = E_FALLING_OFF;
out:
free(insn_map);
if (err) {
verify_error(err, pc, vrf);
return err;
}
return 0;
}
int verify_instruction(struct verifier_block *bb)
{
verify_fn_t verify;
bb->is_wide = false;
bb->opc = read_u8(&bb->code[bb->pc]);
if (bb->opc == OPC_WIDE) {
bb->is_wide = true;
bb->pc++;
bb->opc = read_u8(&bb->code[bb->pc]);
}
if (bb->opc >= ARRAY_SIZE(verifiers))
return warn("%d out of bounds", bb->opc), -EINVAL;
verify = verifiers[bb->opc];
if (!verify)
return warn("no verify function for %d found", bb->opc), -EINVAL;
int err = verify(bb);
if (err)
verify_error(err, bb->pc, bb->parent_ctx);
return err;
}
static int vm_method_overrides_final(struct verifier_context *vrf)
{
struct vm_method *vmm = vrf->method;
struct vm_method *overridden;
/* XXX Special care taken for some classpath base classes which do
* override final methods of their superclass... Anyway adding a
* user-defined class to the java. package is prohibited so no need to
* worry about malicious users.
*/
if (!strncmp("java/", vmm->class->name, strlen("java/")))
return 0;
/* If the method does not override anything we're safe. */
overridden = vm_method_get_overridden(vmm);
if (!overridden)
return 0;
if (vm_method_is_final(overridden)) {
verify_error(E_OVERRIDES_FINAL, 0, vrf);
return E_OVERRIDES_FINAL;
}
return 0;
}
int vm_method_verify(struct vm_method *vmm)
{
int err;
struct verifier_context *vrf;
if (vm_method_is_abstract(vmm) || vm_method_is_native(vmm) || !vmm->code_attribute.code_length)
return 0; /* Nothing to check ! */
vrf = alloc_verifier_context(vmm);
if (!vrf)
return ENOMEM;
err = vm_method_overrides_final(vrf);
if (err)
goto out;
err = verifier_first_pass(vrf);
if (err)
goto out;
out:
free_verifier_context(vrf);
if (err == E_NOT_IMPLEMENTED)
return 0;
if (err) {
if (opt_trace_verifier) {
printf("Method code :\n");
print_method(vmm);
trace_flush();
printf("\n");
}
if (!vm_java_lang_VerifyError)
return warn("Would have raised VerifyError exception."), err;
signal_new_exception(vm_java_lang_VerifyError, verify_error_string);
}
return err;
}