blob: 5157bef1d8c3b4ca65416e22d61101436b2a8b7e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <assert.h>
#include <stdio.h>
#include "open/vm_field_access.h"
#include "open/vm_method_access.h"
#include "class_interface.h"
#include "tpool.h"
#include "context_base.h"
vf_TypePool::vf_TypePool(SharedClasswideData *_classwide, Class_Handle _klass, unsigned table_incr)
: classwide(_classwide), tableIncr(table_incr),
validTypesTableMax(0), validTypesTableSz(1), validTypes(0)
{
const_object = const_class = const_string = const_throwable = const_arrayref_of_bb =
const_arrayref_of_char = const_arrayref_of_double = const_arrayref_of_float =
const_arrayref_of_integer = const_arrayref_of_long = const_arrayref_of_short =
const_arrayref_of_object = const_this = SM_NONE;
k_class = _klass;
k_cp_length = class_cp_get_size( k_class );
}
/*
* Returns name length if it is array of boolean or 0
*/
inline int vf_TypePool::is_bool_array_conv_needed(const char *type_name, int length) {
return length > 1 && type_name[length - 1] == 'Z' && type_name[length - 2] == '[';
}
/*
* Get SmConstant by type name.
*/
SmConstant vf_TypePool::get_ref_type(const char *type_name, int length) {
//TODO: this assert raise false alarms when type name starts with 'L'
//assert(type_name[0] != 'L');
// find type in hash
vf_HashEntry_t *entry = hash.NewHashEntry( type_name, length );
unsigned index = entry->data_index;
if( !index ) {
//convert array of booleans to array of bytes
if( is_bool_array_conv_needed(type_name, length) ) {
char *new_name = (char*)classwide->mem.malloc(length+1);
tc_memcpy(new_name, type_name, length);
new_name[length-1] = 'B';
index = get_ref_type(new_name, length).getReferenceIdx();
classwide->mem.dealloc_last(new_name, length+1);
}
// Get next free table entry index
if( !index ) {
index = check_table();
validTypes[index].cls = 0;
validTypes[index].name = entry->key;
}
entry->data_index = index;
}
assert(index < validTypesTableSz);
return SmConstant::getReference(index);
}
SmConstant vf_TypePool::get_primitive_type(const char type_char) {
switch( type_char ) {
case 'I':
case 'B':
case 'Z':
case 'C':
case 'S':
return SM_INTEGER;
case 'J':
return SM_LONG;
case 'F':
return SM_FLOAT;
case 'V':
return SM_BOGUS;
case 'D':
return SM_DOUBLE;
default:
assert(0);
return SM_BOGUS;
}
}
SmConstant vf_TypePool::get_type(const char *type_name, int name_len) {
if( name_len > 1 ) {
if( type_name[0] == '[' ) {
return get_ref_type(type_name, name_len);
}
if( type_name[0] == 'L' ) {
return get_ref_type(type_name + 1, name_len - 2);
}
return SM_BOGUS;
} else {
return get_primitive_type(type_name[0]);
}
}
SmConstant vf_TypePool::get_ref_from_array(SmConstant element) {
if( element == SM_NULL ) return SM_NULL;
assert(element.isReference());
assert(sm_get_refname(element)[0] == '[');
return get_type(sm_get_refname(element) + 1);
}
int vf_TypePool::mustbe_assignable(SmConstant from, SmConstant to) {
if( from == to || to == SM_NONE || to == SM_BOGUS ) return true;
if( from == SM_BOGUS ) return false;
if( to.isReference() ) {
if( from == SM_NULL ) return true;
if( from.isReference() ) {
return ref_mustbe_assignable(from, to);
}
return false;
}
if( !to.isPrimitive() ) {
assert( to != from ); // checked above
return false;
}
//migth have to change switch below if merging is done with constants
assert( from != SM_NONE );
assert( from != SM_LOW_WORD );
assert( from != SM_REF_OR_UNINIT_OR_RETADR );
assert( from != SM_REF_OR_UNINIT );
assert( from != SM_ANYARRAY );
assert( from != SM_BOGUS );
switch ( to.c ) {
case SM_LOW_WORD:
return from != SM_HIGH_WORD;
case SM_REF_OR_UNINIT_OR_RETADR:
return from == SM_NULL || from == SM_THISUNINIT || !from.isPrimitive();
case SM_REF_OR_UNINIT:
return from == SM_NULL || from == SM_THISUNINIT || from.isNewObject() || from.isReference();
case SM_ANYARRAY:
return from == SM_NULL || from.isReference() && sm_get_refname(from)[0] == '[';
case SM_NULL:
assert(0);
return false;
case SM_THISUNINIT:
case SM_HIGH_WORD:
case SM_INTEGER:
case SM_FLOAT:
case SM_LONG:
case SM_DOUBLE:
case SM_BOGUS:
return false;
default:
assert(0);
return false;
}
}
int vf_TypePool::ref_mustbe_assignable(SmConstant from, SmConstant to) {
if( to == sm_get_const_object() ) return true;
vf_ValidType *to_type = getVaildType(to.getReferenceIdx());
vf_ValidType *from_type = getVaildType(from.getReferenceIdx());
const char *to_name = to_type->name;
const char *from_name = from_type->name;
int to_array = to_name[0] == '[';
int from_array = from_name[0] == '[';
if( to_array && !from_array ) {
return false;
} else if( to_array && from_array ) {
int dim = 0;
while( to_name[dim] == '[' && from_name[dim] == '[' ) dim++;
if( from_name[dim] != 'L' && from_name[dim] != '[' ) {
//primitive type
dim--;
}
if( to_name[dim] != 'L' ) return false;
if( from_name[dim] == 'L' ) {
//merge refs
return ref_mustbe_assignable(get_type(from_name + dim), get_type(to_name + dim) );
} else {
//to must be Object or an interface
return ref_mustbe_assignable(sm_get_const_object(), get_type(to_name + dim) );
}
} else if( from_array ) {
//from is an array, to is not an array
//must be checked before
assert( from != to );
return to == sm_get_const_object() || !strcmp(to_name, "java/lang/Cloneable") ||
!strcmp(to_name, "java/io/Serializable");
} else {
//both not arrays
//check whether TO class is loaded
if( !to_type->cls ) {
to_type->cls = vf_resolve_class(k_class, to_type->name, false);
if( !to_type->cls ) to_type->cls = CLASS_NOT_LOADED;
}
if( to_type->cls && to_type->cls != CLASS_NOT_LOADED ) {
//if to is loaded and it is an interface, treat it as an object
if(class_is_interface(to_type->cls)) {
return true;
}
} else {
NewConstraint(from_type->name, to_type->name);
return true;
}
//check whether FROM class is loaded
if( !from_type->cls ) {
from_type->cls = vf_resolve_class(k_class, from_type->name, false);
if( !from_type->cls ) from_type->cls = CLASS_NOT_LOADED;
}
if( from_type->cls && from_type->cls != CLASS_NOT_LOADED ) {
return vf_is_extending(from_type->cls, to_type->cls);
} else {
NewConstraint(from_type->name, to_type->name);
return 1;
}
}
}
//check if expected_ref is a super class of 'this', its package differs
int vf_TypePool::checkSuperAndPackage(SmConstant expected_ref) {
//check that expected ref is a super of 'this'
vf_ValidType *expected_type = getVaildType(expected_ref.getReferenceIdx());
Class_Handle& referred = expected_type->cls;
if( !referred ) {
//try to get class
referred = vf_resolve_class(k_class, expected_type->name, false);
if( !referred ) {
referred = CLASS_NOT_LOADED;
}
}
if( referred == CLASS_NOT_LOADED ) {
//referred class can't be resolved ==> still might be a super class
Class_Handle k = class_get_extended_class(k_class, expected_type->name);
if( k ) {
referred = k;
} else {
return false;
}
}
return !class_is_same_package(k_class, referred) && vf_is_extending(k_class, referred);
}
//check if expected_ref is a super class of 'this', its package differs, and it's protected
int vf_TypePool::checkVirtualAccess(SmConstant expected_ref, unsigned short method_idx) {
//check if expected_ref is a super class of 'this', its package differs
if( !checkSuperAndPackage(expected_ref) ) {
return _FALSE;
}
//check further
//check that they are in different packages and method is protected
Method_Handle method = class_resolve_method(k_class, method_idx);
if (!method || method_is_static(method)) {
//it's not a VerifyError
return _FALSE;
}
// for arrays function clone is public
return !method_is_protected(method) ? _FALSE : !memcmp(method_get_name(method), "clone", 6) ? _CLONE : _TRUE;
}
//check if expected_ref is a super class of 'this', its package differs, and it's protected
int vf_TypePool::checkFieldAccess(SmConstant expected_ref, unsigned short field_idx) {
//check if expected_ref is a super class of 'this', its package differs
if( !checkSuperAndPackage(expected_ref) ) {
return _FALSE;
}
//check further
//check that they are in different packages and method is protected
Field_Handle field = class_resolve_nonstatic_field(k_class, field_idx);
if (!field) {
//it's not a VerifyError
return _FALSE;
}
// for arrays function clone is public
return field_is_protected(field) ? _TRUE : _FALSE;
}
void vf_TypePool::NewConstraint(const char *available,
const char *required)
{
vf_TypeConstraint *constraint;
// lookup constraint
for( constraint = classwide->class_constraints; constraint; constraint = constraint->next) {
if( constraint->target == required && constraint->source == available ) {
// this constraint is already present
return;
}
}
// set constraint
constraint = (vf_TypeConstraint*)classwide->constraintPool.malloc(sizeof(vf_TypeConstraint));
constraint->target = required;
constraint->source = available;
constraint->next = classwide->class_constraints;
classwide->class_constraints = constraint;
return;
}
SmConstant vf_TypePool::cpool_get_ldcarg(unsigned short cp_idx) {
if( cp_idx >= k_cp_length || !cp_idx ) return SM_BOGUS;
switch (class_cp_get_tag( k_class, cp_idx ) ) {
case _CONSTANT_String:
return sm_get_const_string();
case _CONSTANT_Integer:
return SM_INTEGER;
case _CONSTANT_Float:
return SM_FLOAT;
case _CONSTANT_Class:
//check if it's a 1.5 class (major version is 49)
return classwide->k_major < 49 ? SM_BOGUS : sm_get_const_class();
default:
return SM_BOGUS;
}
}
SmConstant vf_TypePool::cpool_get_ldc2arg(unsigned short cp_idx) {
if( cp_idx >= k_cp_length || !cp_idx ) return SM_BOGUS;
switch (class_cp_get_tag( k_class, cp_idx ) ) {
case _CONSTANT_Double:
return SM_DOUBLE;
case _CONSTANT_Long:
return SM_LONG;
default:
return SM_BOGUS;
}
}
int vf_TypePool::cpool_is_reftype(unsigned short cp_idx) {
return cp_idx && cp_idx < k_cp_length && class_cp_get_tag( k_class, cp_idx ) == _CONSTANT_Class;
}
int vf_TypePool::cpool_get_class(unsigned short cp_idx, SmConstant *ref, int expected_dim) {
if( !cpool_is_reftype(cp_idx) ) return false;
unsigned short name_idx = class_cp_get_class_name_index(k_class, cp_idx);
if( name_idx >= k_cp_length ) return false;
const char* name = class_cp_get_utf8_bytes( k_class, name_idx );
//validate dimensions
int ptr = 0;
while (name[ptr] == '[' ) {
ptr++;
}
//'name' already contains final '[', so max dimension of class 'name' is 255
if( ptr < expected_dim || ptr > 255 ) return false;
//array is not allowed here
if( ptr && expected_dim == -1 ) return false;
//TODO: do we need to resolve if we don't need SmConstant?
//e.g. do we need to resolve class of a static variable?
//constantpool validation should be done whereever else
if( ref ) *ref = get_ref_type(name, (int)strlen(name));
return true;
}
int vf_TypePool::cpool_get_array(unsigned short cp_idx, SmConstant *ref) {
assert(ref);
if( !cpool_is_reftype(cp_idx) ) return false;
unsigned short name_idx = class_cp_get_class_name_index(k_class, cp_idx);
if( name_idx >= k_cp_length ) return false;
const char* name = class_cp_get_utf8_bytes( k_class, name_idx );
int len = (int)strlen(name);
//validate dimensions
int ptr = 0;
while (name[ptr] == '[' ) {
ptr++;
}
//'name' does not contain final '[', so max dimension of class 'name' is 254
if( ptr > 254 ) return false;
char* arr_name = (char*)classwide->mem.malloc(len + 4);
arr_name[0] = '[';
if( name[0] == '[' ) {
tc_memcpy(arr_name + 1, name, len);
*ref = get_ref_type(arr_name, len + 1);
} else {
arr_name[1] = 'L';
tc_memcpy(arr_name + 2, name, len);
arr_name[len + 2] = ';';
*ref = get_ref_type(arr_name, len + 3);
}
classwide->mem.dealloc_last(arr_name, len + 4);
return true;
}
int vf_TypePool::cpool_get_field(unsigned short cp_idx, SmConstant *ref, SmConstant *value) {
//check it is a field
if( !cp_idx || cp_idx >= k_cp_length || class_cp_get_tag( k_class, cp_idx ) != _CONSTANT_Fieldref ) {
return false;
}
unsigned short class_idx = class_cp_get_ref_class_index( k_class, cp_idx );
if( !cpool_get_class(class_idx, ref) ) return false;
unsigned short name_and_type_idx = class_cp_get_ref_name_and_type_index( k_class, cp_idx );
if( !name_and_type_idx || name_and_type_idx >= k_cp_length || class_cp_get_tag( k_class, name_and_type_idx ) != _CONSTANT_NameAndType ) return false;
//TODO: do we need this check?
//unsigned short name_idx = class_get_cp_name_index( k_class, name_and_type_idx );
//if( !name_idx || name_idx >= k_cp_length || class_cp_get_tag( k_class, name_idx ) != _CONSTANT_Utf8 ) return false;
//get filed type
unsigned short type_idx = class_cp_get_descriptor_index( k_class, name_and_type_idx );
if( !type_idx || type_idx >= k_cp_length || class_cp_get_tag( k_class, type_idx ) != _CONSTANT_Utf8 ) return false;
const char *type = class_cp_get_utf8_bytes( k_class, type_idx );
*value = get_type(type);
return true;
}
int vf_TypePool::cpool_method_start(unsigned short cp_idx, const char **state, SmConstant *objectref,
unsigned short *name_idx, int opcode) {
ClassConstantPoolTags expected_tag = opcode == OP_INVOKEINTERFACE ? _CONSTANT_InterfaceMethodref : _CONSTANT_Methodref;
//check it is a method
if( !cp_idx || cp_idx >= k_cp_length || class_cp_get_tag( k_class, cp_idx ) != expected_tag ) {
return false;
}
unsigned short class_idx = class_cp_get_ref_class_index( k_class, cp_idx );
if( opcode == OP_INVOKEVIRTUAL || opcode == OP_INVOKESPECIAL ) {
if( !cpool_get_class(class_idx, objectref) ) return false;
} else {
if( !cpool_get_class(class_idx, 0) ) return false;
(*objectref) = sm_get_const_object();
}
unsigned short name_and_type_idx = class_cp_get_ref_name_and_type_index( k_class, cp_idx );
if( !name_and_type_idx || name_and_type_idx >= k_cp_length || class_cp_get_tag( k_class, name_and_type_idx ) != _CONSTANT_NameAndType ) return false;
*name_idx = class_cp_get_name_index( k_class, name_and_type_idx );
//TODO: do we need this check?
if( !(*name_idx) || *name_idx >= k_cp_length || class_cp_get_tag( k_class, *name_idx ) != _CONSTANT_Utf8 ) return false;
//get filed type or function args & rettype
unsigned short type_idx = class_cp_get_descriptor_index( k_class, name_and_type_idx );
if( !type_idx || type_idx >= k_cp_length || class_cp_get_tag( k_class, type_idx ) != _CONSTANT_Utf8 ) return false;
(*state) = class_cp_get_utf8_bytes( k_class, type_idx );
return true;
}
//find return type and count number of arguments
int vf_TypePool::cpool_method_get_rettype(const char **state, SmConstant *rettype, int *args_sz) {
const char *name = (*state);
if( name[0] != '(' ) return 0;
int i = 1;
//count number of args: skip '['s and all between 'L' and ';'
(*args_sz) = 0;
bool on = true;
char current;
while ( (current = name[i]) != ')' ) {
if( !current ) return 0;
if( current == 'L' ) on = false;
if( current == ';' ) on = true;
if( on && current != '[') {
(*args_sz)++;
//it is long or double and not an array of long or double
if ((current == 'J' || current == 'D') && name[i-1] != '[') {
(*args_sz)++;
}
}
i++;
}
(*state) = i == 1 ? 0 : name + 1;
name = name + i + 1;
*rettype = get_type(name);
return 1;
}
int vf_TypePool::cpool_method_next_arg(const char **state, SmConstant *argument) {
const char *name = (*state);
//define name length
int len = 0;
//skip '['s
if ((*state)[len] == '[') {
while( (*state)[++len] == '[' ) ;//len++;
}
//skip up to ';'
if( (*state)[len] == 'L' ) {
while( (*state)[++len] != ';' ) ;
// already checked for '\0' by cpool_method_getrettype
// {
// assert((*state)[len]);
// if( !(*state)[len] ) return false;
// len++;
// }
}
len++;
*argument = get_type((*state), len);
(*state) = (*state)[len] == ')' ? 0 : (*state) + len;
return true;
}
int vf_TypePool::cpool_method_is_constructor_call(unsigned short name_idx) {
return !strcmp(class_cp_get_utf8_bytes( k_class, name_idx ), "<init>");
}
/* FIXME: deserve separate file probably */
void vf_release_memory(void* error)
{
tc_free(error);
}
/**
* * Creates error message to be reported with verify error
* */
void vf_create_error_message(Method_Handle method, vf_Context_Base context, char
** error_msg)
{
char pass[12], instr[12];
sprintf(pass, "%d", context.pass);
sprintf(instr, "%d", context.processed_instruction);
const char* cname = class_get_name(method_get_class(method));
const char* mname = method_get_name(method);
const char* mdesc = method_get_descriptor(method);
const char *format = "%s.%s%s, pass: %s, instr: %s, reason: %s";
unsigned msg_len = strlen(cname) + strlen(mname) + strlen(mdesc)
+ strlen(pass) + strlen(instr) + strlen(context.error_message)
+ strlen(format);
*error_msg = (char*)tc_malloc(msg_len);
if(*error_msg != NULL) {
sprintf(*error_msg, format,
cname, mname, mdesc, pass, instr, context.error_message);
}
}
void vf_create_error_message(Class_Handle klass, vf_TypeConstraint* constraint,
char** error_msg)
{
const char* cname = class_get_name(klass);
const char *format = "constraint check failed, class: %s, source: %s, target: %s";
unsigned msg_len = strlen(cname) +
+ strlen(constraint->source)
+ strlen(constraint->target) + strlen(format);
*error_msg = (char*)tc_malloc(msg_len);
if(*error_msg != NULL) {
sprintf(*error_msg, format,
cname, constraint->source, constraint->target);
}
}