Skip to content
Find file
Fetching contributors…
Cannot retrieve contributors at this time
6440 lines (5446 sloc) 202 KB
/*
* verify.c:
*
* Author:
* Mono Project (http://www.mono-project.com)
*
* Copyright 2001-2003 Ximian, Inc (http://www.ximian.com)
* Copyright 2004-2009 Novell, Inc (http://www.novell.com)
* Copyright 2011 Rodrigo Kumpera
* Licensed under the MIT license. See LICENSE file in the project root for full license information.
*/
#include <config.h>
#include <mono/metadata/object-internals.h>
#include <mono/metadata/verify.h>
#include <mono/metadata/verify-internals.h>
#include <mono/metadata/opcodes.h>
#include <mono/metadata/tabledefs.h>
#include <mono/metadata/reflection.h>
#include <mono/metadata/debug-helpers.h>
#include <mono/metadata/mono-endian.h>
#include <mono/metadata/metadata.h>
#include <mono/metadata/metadata-internals.h>
#include <mono/metadata/class-internals.h>
#include <mono/metadata/security-manager.h>
#include <mono/metadata/security-core-clr.h>
#include <mono/metadata/tokentype.h>
#include <mono/metadata/mono-basic-block.h>
#include <mono/metadata/attrdefs.h>
#include <mono/metadata/class-internals.h>
#include <mono/utils/mono-counters.h>
#include <mono/utils/monobitset.h>
#include <string.h>
#include <ctype.h>
static MiniVerifierMode verifier_mode = MONO_VERIFIER_MODE_OFF;
static gboolean verify_all = FALSE;
/*
* Set the desired level of checks for the verfier.
*
*/
void
mono_verifier_set_mode (MiniVerifierMode mode)
{
verifier_mode = mode;
}
void
mono_verifier_enable_verify_all ()
{
verify_all = TRUE;
}
#ifndef DISABLE_VERIFIER
/*
* Pull the list of opcodes
*/
#define OPDEF(a,b,c,d,e,f,g,h,i,j) \
a = i,
enum {
#include "mono/cil/opcode.def"
LAST = 0xff
};
#undef OPDEF
#ifdef MONO_VERIFIER_DEBUG
#define VERIFIER_DEBUG(code) do { code } while (0)
#else
#define VERIFIER_DEBUG(code)
#endif
//////////////////////////////////////////////////////////////////
#define IS_STRICT_MODE(ctx) (((ctx)->level & MONO_VERIFY_NON_STRICT) == 0)
#define IS_FAIL_FAST_MODE(ctx) (((ctx)->level & MONO_VERIFY_FAIL_FAST) == MONO_VERIFY_FAIL_FAST)
#define IS_SKIP_VISIBILITY(ctx) (((ctx)->level & MONO_VERIFY_SKIP_VISIBILITY) == MONO_VERIFY_SKIP_VISIBILITY)
#define IS_REPORT_ALL_ERRORS(ctx) (((ctx)->level & MONO_VERIFY_REPORT_ALL_ERRORS) == MONO_VERIFY_REPORT_ALL_ERRORS)
#define CLEAR_PREFIX(ctx, prefix) do { (ctx)->prefix_set &= ~(prefix); } while (0)
#define ADD_VERIFY_INFO(__ctx, __msg, __status, __exception) \
do { \
MonoVerifyInfoExtended *vinfo = g_new (MonoVerifyInfoExtended, 1); \
vinfo->info.status = __status; \
vinfo->info.message = ( __msg ); \
vinfo->exception_type = (__exception); \
(__ctx)->list = g_slist_prepend ((__ctx)->list, vinfo); \
} while (0)
//TODO support MONO_VERIFY_REPORT_ALL_ERRORS
#define ADD_VERIFY_ERROR(__ctx, __msg) \
do { \
ADD_VERIFY_INFO(__ctx, __msg, MONO_VERIFY_ERROR, MONO_EXCEPTION_INVALID_PROGRAM); \
(__ctx)->valid = 0; \
} while (0)
#define CODE_NOT_VERIFIABLE(__ctx, __msg) \
do { \
if ((__ctx)->verifiable || IS_REPORT_ALL_ERRORS (__ctx)) { \
ADD_VERIFY_INFO(__ctx, __msg, MONO_VERIFY_NOT_VERIFIABLE, MONO_EXCEPTION_UNVERIFIABLE_IL); \
(__ctx)->verifiable = 0; \
if (IS_FAIL_FAST_MODE (__ctx)) \
(__ctx)->valid = 0; \
} \
} while (0)
#define ADD_VERIFY_ERROR2(__ctx, __msg, __exception) \
do { \
ADD_VERIFY_INFO(__ctx, __msg, MONO_VERIFY_ERROR, __exception); \
(__ctx)->valid = 0; \
} while (0)
#define CODE_NOT_VERIFIABLE2(__ctx, __msg, __exception) \
do { \
if ((__ctx)->verifiable || IS_REPORT_ALL_ERRORS (__ctx)) { \
ADD_VERIFY_INFO(__ctx, __msg, MONO_VERIFY_NOT_VERIFIABLE, __exception); \
(__ctx)->verifiable = 0; \
if (IS_FAIL_FAST_MODE (__ctx)) \
(__ctx)->valid = 0; \
} \
} while (0)
#define CHECK_ADD4_OVERFLOW_UN(a, b) ((guint32)(0xFFFFFFFFU) - (guint32)(b) < (guint32)(a))
#define CHECK_ADD8_OVERFLOW_UN(a, b) ((guint64)(0xFFFFFFFFFFFFFFFFUL) - (guint64)(b) < (guint64)(a))
#if SIZEOF_VOID_P == 4
#define CHECK_ADDP_OVERFLOW_UN(a,b) CHECK_ADD4_OVERFLOW_UN(a, b)
#else
#define CHECK_ADDP_OVERFLOW_UN(a,b) CHECK_ADD8_OVERFLOW_UN(a, b)
#endif
#define ADDP_IS_GREATER_OR_OVF(a, b, c) (((a) + (b) > (c)) || CHECK_ADDP_OVERFLOW_UN (a, b))
#define ADD_IS_GREATER_OR_OVF(a, b, c) (((a) + (b) > (c)) || CHECK_ADD4_OVERFLOW_UN (a, b))
/*Flags to be used with ILCodeDesc::flags */
enum {
/*Instruction has not been processed.*/
IL_CODE_FLAG_NOT_PROCESSED = 0,
/*Instruction was decoded by mono_method_verify loop.*/
IL_CODE_FLAG_SEEN = 1,
/*Instruction was target of a branch or is at a protected block boundary.*/
IL_CODE_FLAG_WAS_TARGET = 2,
/*Used by stack_init to avoid double initialize each entry.*/
IL_CODE_FLAG_STACK_INITED = 4,
/*Used by merge_stacks to decide if it should just copy the eval stack.*/
IL_CODE_STACK_MERGED = 8,
/*This instruction is part of the delegate construction sequence, it cannot be target of a branch.*/
IL_CODE_DELEGATE_SEQUENCE = 0x10,
/*This is a delegate created from a ldftn to a non final virtual method*/
IL_CODE_LDFTN_DELEGATE_NONFINAL_VIRTUAL = 0x20,
/*This is a call to a non final virtual method*/
IL_CODE_CALL_NONFINAL_VIRTUAL = 0x40,
};
typedef enum {
RESULT_VALID,
RESULT_UNVERIFIABLE,
RESULT_INVALID
} verify_result_t;
typedef struct {
MonoType *type;
int stype;
MonoMethod *method;
} ILStackDesc;
typedef struct {
ILStackDesc *stack;
guint16 size, max_size;
guint16 flags;
} ILCodeDesc;
typedef struct {
int max_args;
int max_stack;
int verifiable;
int valid;
int level;
int code_size;
ILCodeDesc *code;
ILCodeDesc eval;
MonoType **params;
GSList *list;
/*Allocated fnptr MonoType that should be freed by us.*/
GSList *funptrs;
/*Type dup'ed exception types from catch blocks.*/
GSList *exception_types;
int num_locals;
MonoType **locals;
/*TODO get rid of target here, need_merge in mono_method_verify and hoist the merging code in the branching code*/
int target;
guint32 ip_offset;
MonoMethodSignature *signature;
MonoMethodHeader *header;
MonoGenericContext *generic_context;
MonoImage *image;
MonoMethod *method;
/*This flag helps solving a corner case of delegate verification in that you cannot have a "starg 0"
*on a method that creates a delegate for a non-final virtual method using ldftn*/
gboolean has_this_store;
/*This flag is used to control if the contructor of the parent class has been called.
*If the this pointer is pushed on the eval stack and it's a reference type constructor and
* super_ctor_called is false, the uninitialized flag is set on the pushed value.
*
* Poping an uninitialized this ptr from the eval stack is an unverifiable operation unless
* the safe variant is used. Only a few opcodes can use it : dup, pop, ldfld, stfld and call to a constructor.
*/
gboolean super_ctor_called;
guint32 prefix_set;
gboolean has_flags;
MonoType *constrained_type;
} VerifyContext;
static void
merge_stacks (VerifyContext *ctx, ILCodeDesc *from, ILCodeDesc *to, gboolean start, gboolean external);
static int
get_stack_type (MonoType *type);
static gboolean
mono_delegate_signature_equal (MonoMethodSignature *delegate_sig, MonoMethodSignature *method_sig, gboolean is_static_ldftn);
static gboolean
mono_class_is_valid_generic_instantiation (VerifyContext *ctx, MonoClass *klass);
static gboolean
mono_method_is_valid_generic_instantiation (VerifyContext *ctx, MonoMethod *method);
static MonoGenericParam*
verifier_get_generic_param_from_type (VerifyContext *ctx, MonoType *type);
static gboolean
verifier_class_is_assignable_from (MonoClass *target, MonoClass *candidate);
//////////////////////////////////////////////////////////////////
enum {
TYPE_INV = 0, /* leave at 0. */
TYPE_I4 = 1,
TYPE_I8 = 2,
TYPE_NATIVE_INT = 3,
TYPE_R8 = 4,
/* Used by operator tables to resolve pointer types (managed & unmanaged) and by unmanaged pointer types*/
TYPE_PTR = 5,
/* value types and classes */
TYPE_COMPLEX = 6,
/* Number of types, used to define the size of the tables*/
TYPE_MAX = 6,
/* Used by tables to signal that a result is not verifiable*/
NON_VERIFIABLE_RESULT = 0x80,
/*Mask used to extract just the type, excluding flags */
TYPE_MASK = 0x0F,
/* The stack type is a managed pointer, unmask the value to res */
POINTER_MASK = 0x100,
/*Stack type with the pointer mask*/
RAW_TYPE_MASK = 0x10F,
/* Controlled Mutability Manager Pointer */
CMMP_MASK = 0x200,
/* The stack type is a null literal*/
NULL_LITERAL_MASK = 0x400,
/**Used by ldarg.0 and family to let delegate verification happens.*/
THIS_POINTER_MASK = 0x800,
/**Signals that this is a boxed value type*/
BOXED_MASK = 0x1000,
/*This is an unitialized this ref*/
UNINIT_THIS_MASK = 0x2000,
};
static const char* const
type_names [TYPE_MAX + 1] = {
"Invalid",
"Int32",
"Int64",
"Native Int",
"Float64",
"Native Pointer",
"Complex"
};
enum {
PREFIX_UNALIGNED = 1,
PREFIX_VOLATILE = 2,
PREFIX_TAIL = 4,
PREFIX_CONSTRAINED = 8,
PREFIX_READONLY = 16
};
//////////////////////////////////////////////////////////////////
#ifdef ENABLE_VERIFIER_STATS
#define _MEM_ALLOC(amt) do { allocated_memory += (amt); working_set += (amt); } while (0)
#define _MEM_FREE(amt) do { working_set -= (amt); } while (0)
static int allocated_memory;
static int working_set;
static int max_allocated_memory;
static int max_working_set;
static int total_allocated_memory;
static void
finish_collect_stats (void)
{
max_allocated_memory = MAX (max_allocated_memory, allocated_memory);
max_working_set = MAX (max_working_set, working_set);
total_allocated_memory += allocated_memory;
allocated_memory = working_set = 0;
}
static void
init_verifier_stats (void)
{
static gboolean inited;
if (!inited) {
inited = TRUE;
mono_counters_register ("Maximum memory allocated during verification", MONO_COUNTER_METADATA | MONO_COUNTER_INT, &max_allocated_memory);
mono_counters_register ("Maximum memory used during verification", MONO_COUNTER_METADATA | MONO_COUNTER_INT, &max_working_set);
mono_counters_register ("Total memory allocated for verification", MONO_COUNTER_METADATA | MONO_COUNTER_INT, &total_allocated_memory);
}
}
#else
#define _MEM_ALLOC(amt) do {} while (0)
#define _MEM_FREE(amt) do { } while (0)
#define finish_collect_stats()
#define init_verifier_stats()
#endif
//////////////////////////////////////////////////////////////////
/*Token validation macros and functions */
#define IS_MEMBER_REF(token) (mono_metadata_token_table (token) == MONO_TABLE_MEMBERREF)
#define IS_METHOD_DEF(token) (mono_metadata_token_table (token) == MONO_TABLE_METHOD)
#define IS_METHOD_SPEC(token) (mono_metadata_token_table (token) == MONO_TABLE_METHODSPEC)
#define IS_FIELD_DEF(token) (mono_metadata_token_table (token) == MONO_TABLE_FIELD)
#define IS_TYPE_REF(token) (mono_metadata_token_table (token) == MONO_TABLE_TYPEREF)
#define IS_TYPE_DEF(token) (mono_metadata_token_table (token) == MONO_TABLE_TYPEDEF)
#define IS_TYPE_SPEC(token) (mono_metadata_token_table (token) == MONO_TABLE_TYPESPEC)
#define IS_METHOD_DEF_OR_REF_OR_SPEC(token) (IS_METHOD_DEF (token) || IS_MEMBER_REF (token) || IS_METHOD_SPEC (token))
#define IS_TYPE_DEF_OR_REF_OR_SPEC(token) (IS_TYPE_DEF (token) || IS_TYPE_REF (token) || IS_TYPE_SPEC (token))
#define IS_FIELD_DEF_OR_REF(token) (IS_FIELD_DEF (token) || IS_MEMBER_REF (token))
/*
* Verify if @token refers to a valid row on int's table.
*/
static gboolean
token_bounds_check (MonoImage *image, guint32 token)
{
if (image_is_dynamic (image))
return mono_reflection_is_valid_dynamic_token ((MonoDynamicImage*)image, token);
return image->tables [mono_metadata_token_table (token)].rows >= mono_metadata_token_index (token) && mono_metadata_token_index (token) > 0;
}
static MonoType *
mono_type_create_fnptr_from_mono_method (VerifyContext *ctx, MonoMethod *method)
{
MonoType *res = g_new0 (MonoType, 1);
_MEM_ALLOC (sizeof (MonoType));
//FIXME use mono_method_get_signature_full
res->data.method = mono_method_signature (method);
res->type = MONO_TYPE_FNPTR;
ctx->funptrs = g_slist_prepend (ctx->funptrs, res);
return res;
}
/*
* mono_type_is_enum_type:
*
* Returns: TRUE if @type is an enum type.
*/
static gboolean
mono_type_is_enum_type (MonoType *type)
{
if (type->type == MONO_TYPE_VALUETYPE && type->data.klass->enumtype)
return TRUE;
if (type->type == MONO_TYPE_GENERICINST && type->data.generic_class->container_class->enumtype)
return TRUE;
return FALSE;
}
/*
* mono_type_is_value_type:
*
* Returns: TRUE if @type is named after @namespace.@name.
*
*/
static gboolean
mono_type_is_value_type (MonoType *type, const char *namespace_, const char *name)
{
return type->type == MONO_TYPE_VALUETYPE &&
!strcmp (namespace_, type->data.klass->name_space) &&
!strcmp (name, type->data.klass->name);
}
/*
* Returns TURE if @type is VAR or MVAR
*/
static gboolean
mono_type_is_generic_argument (MonoType *type)
{
return type->type == MONO_TYPE_VAR || type->type == MONO_TYPE_MVAR;
}
/*
* mono_type_get_underlying_type_any:
*
* This functions is just like mono_type_get_underlying_type but it doesn't care if the type is byref.
*
* Returns the underlying type of @type regardless if it is byref or not.
*/
static MonoType*
mono_type_get_underlying_type_any (MonoType *type)
{
if (type->type == MONO_TYPE_VALUETYPE && type->data.klass->enumtype)
return mono_class_enum_basetype (type->data.klass);
if (type->type == MONO_TYPE_GENERICINST && type->data.generic_class->container_class->enumtype)
return mono_class_enum_basetype (type->data.generic_class->container_class);
return type;
}
static G_GNUC_UNUSED const char*
mono_type_get_stack_name (MonoType *type)
{
return type_names [get_stack_type (type) & TYPE_MASK];
}
#define CTOR_REQUIRED_FLAGS (METHOD_ATTRIBUTE_SPECIAL_NAME | METHOD_ATTRIBUTE_RT_SPECIAL_NAME)
#define CTOR_INVALID_FLAGS (METHOD_ATTRIBUTE_STATIC)
static gboolean
mono_method_is_constructor (MonoMethod *method)
{
return ((method->flags & CTOR_REQUIRED_FLAGS) == CTOR_REQUIRED_FLAGS &&
!(method->flags & CTOR_INVALID_FLAGS) &&
!strcmp (".ctor", method->name));
}
static gboolean
mono_class_has_default_constructor (MonoClass *klass)
{
MonoMethod *method;
int i;
mono_class_setup_methods (klass);
if (mono_class_has_failure (klass))
return FALSE;
for (i = 0; i < klass->method.count; ++i) {
method = klass->methods [i];
if (mono_method_is_constructor (method) &&
mono_method_signature (method) &&
mono_method_signature (method)->param_count == 0 &&
(method->flags & METHOD_ATTRIBUTE_MEMBER_ACCESS_MASK) == METHOD_ATTRIBUTE_PUBLIC)
return TRUE;
}
return FALSE;
}
/*
* Verify if @type is valid for the given @ctx verification context.
* this function checks for VAR and MVAR types that are invalid under the current verifier,
*/
static gboolean
mono_type_is_valid_type_in_context_full (MonoType *type, MonoGenericContext *context, gboolean check_gtd)
{
int i;
MonoGenericInst *inst;
switch (type->type) {
case MONO_TYPE_VAR:
case MONO_TYPE_MVAR:
if (!context)
return FALSE;
inst = type->type == MONO_TYPE_VAR ? context->class_inst : context->method_inst;
if (!inst || mono_type_get_generic_param_num (type) >= inst->type_argc)
return FALSE;
break;
case MONO_TYPE_SZARRAY:
return mono_type_is_valid_type_in_context_full (&type->data.klass->byval_arg, context, check_gtd);
case MONO_TYPE_ARRAY:
return mono_type_is_valid_type_in_context_full (&type->data.array->eklass->byval_arg, context, check_gtd);
case MONO_TYPE_PTR:
return mono_type_is_valid_type_in_context_full (type->data.type, context, check_gtd);
case MONO_TYPE_GENERICINST:
inst = type->data.generic_class->context.class_inst;
if (!inst->is_open)
break;
for (i = 0; i < inst->type_argc; ++i)
if (!mono_type_is_valid_type_in_context_full (inst->type_argv [i], context, check_gtd))
return FALSE;
break;
case MONO_TYPE_CLASS:
case MONO_TYPE_VALUETYPE: {
MonoClass *klass = type->data.klass;
/*
* It's possible to encode generic'sh types in such a way that they disguise themselves as class or valuetype.
* Fixing the type decoding is really tricky since under some cases this behavior is needed, for example, to
* have a 'class' type pointing to a 'genericinst' class.
*
* For the runtime these non canonical (weird) encodings work fine, the worst they can cause is some
* reflection oddities which are harmless - to security at least.
*/
if (klass->byval_arg.type != type->type)
return mono_type_is_valid_type_in_context_full (&klass->byval_arg, context, check_gtd);
if (check_gtd && klass->generic_container)
return FALSE;
break;
}
default:
break;
}
return TRUE;
}
static gboolean
mono_type_is_valid_type_in_context (MonoType *type, MonoGenericContext *context)
{
return mono_type_is_valid_type_in_context_full (type, context, FALSE);
}
/*This function returns NULL if the type is not instantiatable*/
static MonoType*
verifier_inflate_type (VerifyContext *ctx, MonoType *type, MonoGenericContext *context)
{
MonoError error;
MonoType *result;
result = mono_class_inflate_generic_type_checked (type, context, &error);
if (!mono_error_ok (&error)) {
mono_error_cleanup (&error);
return NULL;
}
return result;
}
/*A side note here. We don't need to check if arguments are broken since this
is only need to be done by the runtime before realizing the type.
*/
static gboolean
is_valid_generic_instantiation (MonoGenericContainer *gc, MonoGenericContext *context, MonoGenericInst *ginst)
{
MonoError error;
int i;
if (ginst->type_argc != gc->type_argc)
return FALSE;
for (i = 0; i < gc->type_argc; ++i) {
MonoGenericParamInfo *param_info = mono_generic_container_get_param_info (gc, i);
MonoClass *paramClass;
MonoClass **constraints;
MonoType *param_type = ginst->type_argv [i];
/*it's not our job to validate type variables*/
if (mono_type_is_generic_argument (param_type))
continue;
paramClass = mono_class_from_mono_type (param_type);
/* A GTD can't be a generic argument.
*
* Due to how types are encoded we must check for the case of a genericinst MonoType and GTD MonoClass.
* This happens in cases such as: class Foo<T> { void X() { new Bar<T> (); } }
*
* Open instantiations can have GTDs as this happens when one type is instantiated with others params
* and the former has an expansion into the later. For example:
* class B<K> {}
* class A<T>: B<K> {}
* The type A <K> has a parent B<K>, that is inflated into the GTD B<>.
* Since A<K> is open, thus not instantiatable, this is valid.
*/
if (paramClass->generic_container && param_type->type != MONO_TYPE_GENERICINST && !ginst->is_open)
return FALSE;
/*it's not safe to call mono_class_init from here*/
if (paramClass->generic_class && !paramClass->inited) {
if (!mono_class_is_valid_generic_instantiation (NULL, paramClass))
return FALSE;
}
if (!param_info->constraints && !(param_info->flags & GENERIC_PARAMETER_ATTRIBUTE_SPECIAL_CONSTRAINTS_MASK))
continue;
if ((param_info->flags & GENERIC_PARAMETER_ATTRIBUTE_VALUE_TYPE_CONSTRAINT) && (!paramClass->valuetype || mono_class_is_nullable (paramClass)))
return FALSE;
if ((param_info->flags & GENERIC_PARAMETER_ATTRIBUTE_REFERENCE_TYPE_CONSTRAINT) && paramClass->valuetype)
return FALSE;
if ((param_info->flags & GENERIC_PARAMETER_ATTRIBUTE_CONSTRUCTOR_CONSTRAINT) && !paramClass->valuetype && !mono_class_has_default_constructor (paramClass))
return FALSE;
if (!param_info->constraints)
continue;
for (constraints = param_info->constraints; *constraints; ++constraints) {
MonoClass *ctr = *constraints;
MonoType *inflated;
inflated = mono_class_inflate_generic_type_checked (&ctr->byval_arg, context, &error);
if (!mono_error_ok (&error)) {
mono_error_cleanup (&error);
return FALSE;
}
ctr = mono_class_from_mono_type (inflated);
mono_metadata_free_type (inflated);
/*FIXME maybe we need the same this as verifier_class_is_assignable_from*/
if (!mono_class_is_assignable_from_slow (ctr, paramClass))
return FALSE;
}
}
return TRUE;
}
/**
* mono_generic_param_is_constraint_compatible:
*
* Returns: TRUE if @candidate is constraint compatible with @target.
*
* This means that @candidate constraints are a super set of @target constaints
*/
static gboolean
mono_generic_param_is_constraint_compatible (VerifyContext *ctx, MonoGenericParam *target, MonoGenericParam *candidate, MonoClass *candidate_param_class, MonoGenericContext *context)
{
MonoGenericParamInfo *tinfo = mono_generic_param_info (target);
MonoGenericParamInfo *cinfo = mono_generic_param_info (candidate);
MonoClass **candidate_class;
gboolean class_constraint_satisfied = FALSE;
gboolean valuetype_constraint_satisfied = FALSE;
int tmask = tinfo->flags & GENERIC_PARAMETER_ATTRIBUTE_SPECIAL_CONSTRAINTS_MASK;
int cmask = cinfo->flags & GENERIC_PARAMETER_ATTRIBUTE_SPECIAL_CONSTRAINTS_MASK;
if (cinfo->constraints) {
for (candidate_class = cinfo->constraints; *candidate_class; ++candidate_class) {
MonoClass *cc;
MonoType *inflated = verifier_inflate_type (ctx, &(*candidate_class)->byval_arg, ctx->generic_context);
if (!inflated)
return FALSE;
cc = mono_class_from_mono_type (inflated);
mono_metadata_free_type (inflated);
if (mono_type_is_reference (&cc->byval_arg) && !MONO_CLASS_IS_INTERFACE (cc))
class_constraint_satisfied = TRUE;
else if (!mono_type_is_reference (&cc->byval_arg) && !MONO_CLASS_IS_INTERFACE (cc))
valuetype_constraint_satisfied = TRUE;
}
}
class_constraint_satisfied |= (cmask & GENERIC_PARAMETER_ATTRIBUTE_REFERENCE_TYPE_CONSTRAINT) != 0;
valuetype_constraint_satisfied |= (cmask & GENERIC_PARAMETER_ATTRIBUTE_VALUE_TYPE_CONSTRAINT) != 0;
if ((tmask & GENERIC_PARAMETER_ATTRIBUTE_REFERENCE_TYPE_CONSTRAINT) && !class_constraint_satisfied)
return FALSE;
if ((tmask & GENERIC_PARAMETER_ATTRIBUTE_VALUE_TYPE_CONSTRAINT) && !valuetype_constraint_satisfied)
return FALSE;
if ((tmask & GENERIC_PARAMETER_ATTRIBUTE_CONSTRUCTOR_CONSTRAINT) && !((cmask & GENERIC_PARAMETER_ATTRIBUTE_CONSTRUCTOR_CONSTRAINT) ||
valuetype_constraint_satisfied)) {
return FALSE;
}
if (tinfo->constraints) {
MonoClass **target_class;
for (target_class = tinfo->constraints; *target_class; ++target_class) {
MonoClass *tc;
MonoType *inflated = verifier_inflate_type (ctx, &(*target_class)->byval_arg, context);
if (!inflated)
return FALSE;
tc = mono_class_from_mono_type (inflated);
mono_metadata_free_type (inflated);
/*
* A constraint from @target might inflate into @candidate itself and in that case we don't need
* check it's constraints since it satisfy the constraint by itself.
*/
if (mono_metadata_type_equal (&tc->byval_arg, &candidate_param_class->byval_arg))
continue;
if (!cinfo->constraints)
return FALSE;
for (candidate_class = cinfo->constraints; *candidate_class; ++candidate_class) {
MonoClass *cc;
inflated = verifier_inflate_type (ctx, &(*candidate_class)->byval_arg, ctx->generic_context);
if (!inflated)
return FALSE;
cc = mono_class_from_mono_type (inflated);
mono_metadata_free_type (inflated);
if (verifier_class_is_assignable_from (tc, cc))
break;
/*
* This happens when we have the following:
*
* Bar<K> where K : IFace
* Foo<T, U> where T : U where U : IFace
* ...
* Bar<T> <- T here satisfy K constraint transitively through to U's constraint
*
*/
if (mono_type_is_generic_argument (&cc->byval_arg)) {
MonoGenericParam *other_candidate = verifier_get_generic_param_from_type (ctx, &cc->byval_arg);
if (mono_generic_param_is_constraint_compatible (ctx, target, other_candidate, cc, context)) {
break;
}
}
}
if (!*candidate_class)
return FALSE;
}
}
return TRUE;
}
static MonoGenericParam*
verifier_get_generic_param_from_type (VerifyContext *ctx, MonoType *type)
{
MonoGenericContainer *gc;
MonoMethod *method = ctx->method;
int num;
num = mono_type_get_generic_param_num (type);
if (type->type == MONO_TYPE_VAR) {
MonoClass *gtd = method->klass;
if (gtd->generic_class)
gtd = gtd->generic_class->container_class;
gc = gtd->generic_container;
} else { //MVAR
MonoMethod *gmd = method;
if (method->is_inflated)
gmd = ((MonoMethodInflated*)method)->declaring;
gc = mono_method_get_generic_container (gmd);
}
if (!gc)
return NULL;
return mono_generic_container_get_param (gc, num);
}
/*
* Verify if @type is valid for the given @ctx verification context.
* this function checks for VAR and MVAR types that are invalid under the current verifier,
* This means that it either
*/
static gboolean
is_valid_type_in_context (VerifyContext *ctx, MonoType *type)
{
return mono_type_is_valid_type_in_context (type, ctx->generic_context);
}
static gboolean
is_valid_generic_instantiation_in_context (VerifyContext *ctx, MonoGenericInst *ginst, gboolean check_gtd)
{
int i;
for (i = 0; i < ginst->type_argc; ++i) {
MonoType *type = ginst->type_argv [i];
if (!mono_type_is_valid_type_in_context_full (type, ctx->generic_context, TRUE))
return FALSE;
}
return TRUE;
}
static gboolean
generic_arguments_respect_constraints (VerifyContext *ctx, MonoGenericContainer *gc, MonoGenericContext *context, MonoGenericInst *ginst)
{
int i;
for (i = 0; i < ginst->type_argc; ++i) {
MonoType *type = ginst->type_argv [i];
MonoGenericParam *target = mono_generic_container_get_param (gc, i);
MonoGenericParam *candidate;
MonoClass *candidate_class;
if (!mono_type_is_generic_argument (type))
continue;
if (!is_valid_type_in_context (ctx, type))
return FALSE;
candidate = verifier_get_generic_param_from_type (ctx, type);
candidate_class = mono_class_from_mono_type (type);
if (!mono_generic_param_is_constraint_compatible (ctx, target, candidate, candidate_class, context))
return FALSE;
}
return TRUE;
}
static gboolean
mono_method_repect_method_constraints (VerifyContext *ctx, MonoMethod *method)
{
MonoMethodInflated *gmethod = (MonoMethodInflated *)method;
MonoGenericInst *ginst = gmethod->context.method_inst;
MonoGenericContainer *gc = mono_method_get_generic_container (gmethod->declaring);
return !gc || generic_arguments_respect_constraints (ctx, gc, &gmethod->context, ginst);
}
static gboolean
mono_class_repect_method_constraints (VerifyContext *ctx, MonoClass *klass)
{
MonoGenericClass *gklass = klass->generic_class;
MonoGenericInst *ginst = gklass->context.class_inst;
MonoGenericContainer *gc = gklass->container_class->generic_container;
return !gc || generic_arguments_respect_constraints (ctx, gc, &gklass->context, ginst);
}
static gboolean
mono_method_is_valid_generic_instantiation (VerifyContext *ctx, MonoMethod *method)
{
MonoMethodInflated *gmethod = (MonoMethodInflated *)method;
MonoGenericInst *ginst = gmethod->context.method_inst;
MonoGenericContainer *gc = mono_method_get_generic_container (gmethod->declaring);
if (!gc) /*non-generic inflated method - it's part of a generic type */
return TRUE;
if (ctx && !is_valid_generic_instantiation_in_context (ctx, ginst, TRUE))
return FALSE;
return is_valid_generic_instantiation (gc, &gmethod->context, ginst);
}
static gboolean
mono_class_is_valid_generic_instantiation (VerifyContext *ctx, MonoClass *klass)
{
MonoGenericClass *gklass = klass->generic_class;
MonoGenericInst *ginst = gklass->context.class_inst;
MonoGenericContainer *gc = gklass->container_class->generic_container;
if (ctx && !is_valid_generic_instantiation_in_context (ctx, ginst, TRUE))
return FALSE;
return is_valid_generic_instantiation (gc, &gklass->context, ginst);
}
static gboolean
mono_type_is_valid_in_context (VerifyContext *ctx, MonoType *type)
{
MonoClass *klass;
if (type == NULL) {
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Invalid null type at 0x%04x", ctx->ip_offset), MONO_EXCEPTION_BAD_IMAGE);
return FALSE;
}
if (!is_valid_type_in_context (ctx, type)) {
char *str = mono_type_full_name (type);
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Invalid generic type (%s%s) (argument out of range or %s is not generic) at 0x%04x",
str [0] == '!' ? "" : type->type == MONO_TYPE_VAR ? "!" : "!!",
str,
type->type == MONO_TYPE_VAR ? "class" : "method",
ctx->ip_offset),
MONO_EXCEPTION_BAD_IMAGE);
g_free (str);
return FALSE;
}
klass = mono_class_from_mono_type (type);
mono_class_init (klass);
if (mono_class_has_failure (klass)) {
if (klass->generic_class && !mono_class_is_valid_generic_instantiation (NULL, klass))
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Invalid generic instantiation of type %s.%s at 0x%04x", klass->name_space, klass->name, ctx->ip_offset), MONO_EXCEPTION_TYPE_LOAD);
else
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Could not load type %s.%s at 0x%04x", klass->name_space, klass->name, ctx->ip_offset), MONO_EXCEPTION_TYPE_LOAD);
return FALSE;
}
if (klass->generic_class && mono_class_has_failure (klass->generic_class->container_class)) {
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Could not load type %s.%s at 0x%04x", klass->name_space, klass->name, ctx->ip_offset), MONO_EXCEPTION_TYPE_LOAD);
return FALSE;
}
if (!klass->generic_class)
return TRUE;
if (!mono_class_is_valid_generic_instantiation (ctx, klass)) {
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Invalid generic type instantiation of type %s.%s at 0x%04x", klass->name_space, klass->name, ctx->ip_offset), MONO_EXCEPTION_TYPE_LOAD);
return FALSE;
}
if (!mono_class_repect_method_constraints (ctx, klass)) {
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Invalid generic type instantiation of type %s.%s (generic args don't respect target's constraints) at 0x%04x", klass->name_space, klass->name, ctx->ip_offset), MONO_EXCEPTION_TYPE_LOAD);
return FALSE;
}
return TRUE;
}
static verify_result_t
mono_method_is_valid_in_context (VerifyContext *ctx, MonoMethod *method)
{
if (!mono_type_is_valid_in_context (ctx, &method->klass->byval_arg))
return RESULT_INVALID;
if (!method->is_inflated)
return RESULT_VALID;
if (!mono_method_is_valid_generic_instantiation (ctx, method)) {
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Invalid generic method instantiation of method %s.%s::%s at 0x%04x", method->klass->name_space, method->klass->name, method->name, ctx->ip_offset), MONO_EXCEPTION_UNVERIFIABLE_IL);
return RESULT_INVALID;
}
if (!mono_method_repect_method_constraints (ctx, method)) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Invalid generic method instantiation of method %s.%s::%s (generic args don't respect target's constraints) at 0x%04x", method->klass->name_space, method->klass->name, method->name, ctx->ip_offset));
return RESULT_UNVERIFIABLE;
}
return RESULT_VALID;
}
static MonoClassField*
verifier_load_field (VerifyContext *ctx, int token, MonoClass **out_klass, const char *opcode) {
MonoError error;
MonoClassField *field;
MonoClass *klass = NULL;
if (ctx->method->wrapper_type != MONO_WRAPPER_NONE) {
field = (MonoClassField *)mono_method_get_wrapper_data (ctx->method, (guint32)token);
klass = field ? field->parent : NULL;
} else {
if (!IS_FIELD_DEF_OR_REF (token) || !token_bounds_check (ctx->image, token)) {
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Invalid field token 0x%08x for %s at 0x%04x", token, opcode, ctx->ip_offset), MONO_EXCEPTION_BAD_IMAGE);
return NULL;
}
field = mono_field_from_token_checked (ctx->image, token, &klass, ctx->generic_context, &error);
mono_error_cleanup (&error); /*FIXME don't swallow the error */
}
if (!field || !field->parent || !klass) {
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Cannot load field from token 0x%08x for %s at 0x%04x", token, opcode, ctx->ip_offset), MONO_EXCEPTION_BAD_IMAGE);
return NULL;
}
if (!mono_type_is_valid_in_context (ctx, &klass->byval_arg))
return NULL;
if (mono_field_get_flags (field) & FIELD_ATTRIBUTE_LITERAL) {
char *type_name = mono_type_get_full_name (field->parent);
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Cannot reference literal field %s::%s at 0x%04x", type_name, field->name, ctx->ip_offset));
g_free (type_name);
return NULL;
}
*out_klass = klass;
return field;
}
static MonoMethod*
verifier_load_method (VerifyContext *ctx, int token, const char *opcode) {
MonoMethod* method;
if (ctx->method->wrapper_type != MONO_WRAPPER_NONE) {
method = (MonoMethod *)mono_method_get_wrapper_data (ctx->method, (guint32)token);
} else {
MonoError error;
if (!IS_METHOD_DEF_OR_REF_OR_SPEC (token) || !token_bounds_check (ctx->image, token)) {
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Invalid method token 0x%08x for %s at 0x%04x", token, opcode, ctx->ip_offset), MONO_EXCEPTION_BAD_IMAGE);
return NULL;
}
method = mono_get_method_checked (ctx->image, token, NULL, ctx->generic_context, &error);
mono_error_cleanup (&error); /* FIXME don't swallow this error */
}
if (!method) {
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Cannot load method from token 0x%08x for %s at 0x%04x", token, opcode, ctx->ip_offset), MONO_EXCEPTION_BAD_IMAGE);
return NULL;
}
if (mono_method_is_valid_in_context (ctx, method) == RESULT_INVALID)
return NULL;
return method;
}
static MonoType*
verifier_load_type (VerifyContext *ctx, int token, const char *opcode) {
MonoType* type;
if (ctx->method->wrapper_type != MONO_WRAPPER_NONE) {
MonoClass *klass = (MonoClass *)mono_method_get_wrapper_data (ctx->method, (guint32)token);
type = klass ? &klass->byval_arg : NULL;
} else {
MonoError error;
if (!IS_TYPE_DEF_OR_REF_OR_SPEC (token) || !token_bounds_check (ctx->image, token)) {
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Invalid type token 0x%08x at 0x%04x", token, ctx->ip_offset), MONO_EXCEPTION_BAD_IMAGE);
return NULL;
}
type = mono_type_get_checked (ctx->image, token, ctx->generic_context, &error);
mono_error_cleanup (&error); /*FIXME don't swallow the error */
}
if (!type) {
ADD_VERIFY_ERROR2 (ctx, g_strdup_printf ("Cannot load type from token 0x%08x for %s at 0x%04x", token, opcode, ctx->ip_offset), MONO_EXCEPTION_BAD_IMAGE);
return NULL;
}
if (!mono_type_is_valid_in_context (ctx, type))
return NULL;
return type;
}
/* stack_slot_get_type:
*
* Returns the stack type of @value. This value includes POINTER_MASK.
*
* Use this function to checks that account for a managed pointer.
*/
static gint32
stack_slot_get_type (ILStackDesc *value)
{
return value->stype & RAW_TYPE_MASK;
}
/* stack_slot_get_underlying_type:
*
* Returns the stack type of @value. This value does not include POINTER_MASK.
*
* Use this function is cases where the fact that the value could be a managed pointer is
* irrelevant. For example, field load doesn't care about this fact of type on stack.
*/
static gint32
stack_slot_get_underlying_type (ILStackDesc *value)
{
return value->stype & TYPE_MASK;
}
/* stack_slot_is_managed_pointer:
*
* Returns TRUE is @value is a managed pointer.
*/
static gboolean
stack_slot_is_managed_pointer (ILStackDesc *value)
{
return (value->stype & POINTER_MASK) == POINTER_MASK;
}
/* stack_slot_is_managed_mutability_pointer:
*
* Returns TRUE is @value is a managed mutability pointer.
*/
static G_GNUC_UNUSED gboolean
stack_slot_is_managed_mutability_pointer (ILStackDesc *value)
{
return (value->stype & CMMP_MASK) == CMMP_MASK;
}
/* stack_slot_is_null_literal:
*
* Returns TRUE is @value is the null literal.
*/
static gboolean
stack_slot_is_null_literal (ILStackDesc *value)
{
return (value->stype & NULL_LITERAL_MASK) == NULL_LITERAL_MASK;
}
/* stack_slot_is_this_pointer:
*
* Returns TRUE is @value is the this literal
*/
static gboolean
stack_slot_is_this_pointer (ILStackDesc *value)
{
return (value->stype & THIS_POINTER_MASK) == THIS_POINTER_MASK;
}
/* stack_slot_is_boxed_value:
*
* Returns TRUE is @value is a boxed value
*/
static gboolean
stack_slot_is_boxed_value (ILStackDesc *value)
{
return (value->stype & BOXED_MASK) == BOXED_MASK;
}
static const char *
stack_slot_get_name (ILStackDesc *value)
{
return type_names [value->stype & TYPE_MASK];
}
#define APPEND_WITH_PREDICATE(PRED,NAME) do {\
if (PRED (value)) { \
if (!first) \
g_string_append (str, ", "); \
g_string_append (str, NAME); \
first = FALSE; \
} } while (0)
static char*
stack_slot_stack_type_full_name (ILStackDesc *value)
{
GString *str = g_string_new ("");
char *result;
gboolean has_pred = FALSE, first = TRUE;
if ((value->stype & TYPE_MASK) != value->stype) {
g_string_append(str, "[");
APPEND_WITH_PREDICATE (stack_slot_is_this_pointer, "this");
APPEND_WITH_PREDICATE (stack_slot_is_boxed_value, "boxed");
APPEND_WITH_PREDICATE (stack_slot_is_null_literal, "null");
APPEND_WITH_PREDICATE (stack_slot_is_managed_mutability_pointer, "cmmp");
APPEND_WITH_PREDICATE (stack_slot_is_managed_pointer, "mp");
has_pred = TRUE;
}
if (mono_type_is_generic_argument (value->type) && !stack_slot_is_boxed_value (value)) {
if (!has_pred)
g_string_append(str, "[");
if (!first)
g_string_append (str, ", ");
g_string_append (str, "unboxed");
has_pred = TRUE;
}
if (has_pred)
g_string_append(str, "] ");
g_string_append (str, stack_slot_get_name (value));
result = str->str;
g_string_free (str, FALSE);
return result;
}
static char*
stack_slot_full_name (ILStackDesc *value)
{
char *type_name = mono_type_full_name (value->type);
char *stack_name = stack_slot_stack_type_full_name (value);
char *res = g_strdup_printf ("%s (%s)", type_name, stack_name);
g_free (type_name);
g_free (stack_name);
return res;
}
//////////////////////////////////////////////////////////////////
void
mono_free_verify_list (GSList *list)
{
MonoVerifyInfoExtended *info;
GSList *tmp;
for (tmp = list; tmp; tmp = tmp->next) {
info = (MonoVerifyInfoExtended *)tmp->data;
g_free (info->info.message);
g_free (info);
}
g_slist_free (list);
}
#define ADD_ERROR(list,msg) \
do { \
MonoVerifyInfoExtended *vinfo = g_new (MonoVerifyInfoExtended, 1); \
vinfo->info.status = MONO_VERIFY_ERROR; \
vinfo->info.message = (msg); \
(list) = g_slist_prepend ((list), vinfo); \
} while (0)
#define ADD_WARN(list,code,msg) \
do { \
MonoVerifyInfoExtended *vinfo = g_new (MonoVerifyInfoExtended, 1); \
vinfo->info.status = (code); \
vinfo->info.message = (msg); \
(list) = g_slist_prepend ((list), vinfo); \
} while (0)
#define ADD_INVALID(list,msg) \
do { \
MonoVerifyInfoExtended *vinfo = g_new (MonoVerifyInfoExtended, 1); \
vinfo->status = MONO_VERIFY_ERROR; \
vinfo->message = (msg); \
(list) = g_slist_prepend ((list), vinfo); \
/*G_BREAKPOINT ();*/ \
goto invalid_cil; \
} while (0)
#define CHECK_STACK_UNDERFLOW(num) \
do { \
if (cur_stack < (num)) \
ADD_INVALID (list, g_strdup_printf ("Stack underflow at 0x%04x (%d items instead of %d)", ip_offset, cur_stack, (num))); \
} while (0)
#define CHECK_STACK_OVERFLOW() \
do { \
if (cur_stack >= max_stack) \
ADD_INVALID (list, g_strdup_printf ("Maxstack exceeded at 0x%04x", ip_offset)); \
} while (0)
static int
in_any_block (MonoMethodHeader *header, guint offset)
{
int i;
MonoExceptionClause *clause;
for (i = 0; i < header->num_clauses; ++i) {
clause = &header->clauses [i];
if (MONO_OFFSET_IN_CLAUSE (clause, offset))
return 1;
if (MONO_OFFSET_IN_HANDLER (clause, offset))
return 1;
if (MONO_OFFSET_IN_FILTER (clause, offset))
return 1;
}
return 0;
}
/*
* in_any_exception_block:
*
* Returns TRUE is @offset is part of any exception clause (filter, handler, catch, finally or fault).
*/
static gboolean
in_any_exception_block (MonoMethodHeader *header, guint offset)
{
int i;
MonoExceptionClause *clause;
for (i = 0; i < header->num_clauses; ++i) {
clause = &header->clauses [i];
if (MONO_OFFSET_IN_HANDLER (clause, offset))
return TRUE;
if (MONO_OFFSET_IN_FILTER (clause, offset))
return TRUE;
}
return FALSE;
}
/*
* is_valid_branch_instruction:
*
* Verify if it's valid to perform a branch from @offset to @target.
* This should be used with br and brtrue/false.
* It returns 0 if valid, 1 for unverifiable and 2 for invalid.
* The major difference from other similiar functions is that branching into a
* finally/fault block is invalid instead of just unverifiable.
*/
static int
is_valid_branch_instruction (MonoMethodHeader *header, guint offset, guint target)
{
int i;
MonoExceptionClause *clause;
for (i = 0; i < header->num_clauses; ++i) {
clause = &header->clauses [i];
/*branching into a finally block is invalid*/
if ((clause->flags == MONO_EXCEPTION_CLAUSE_FINALLY || clause->flags == MONO_EXCEPTION_CLAUSE_FAULT) &&
!MONO_OFFSET_IN_HANDLER (clause, offset) &&
MONO_OFFSET_IN_HANDLER (clause, target))
return 2;
if (clause->try_offset != target && (MONO_OFFSET_IN_CLAUSE (clause, offset) ^ MONO_OFFSET_IN_CLAUSE (clause, target)))
return 1;
if (MONO_OFFSET_IN_HANDLER (clause, offset) ^ MONO_OFFSET_IN_HANDLER (clause, target))
return 1;
if (MONO_OFFSET_IN_FILTER (clause, offset) ^ MONO_OFFSET_IN_FILTER (clause, target))
return 1;
}
return 0;
}
/*
* is_valid_cmp_branch_instruction:
*
* Verify if it's valid to perform a branch from @offset to @target.
* This should be used with binary comparison branching instruction, like beq, bge and similars.
* It returns 0 if valid, 1 for unverifiable and 2 for invalid.
*
* The major differences from other similar functions are that most errors lead to invalid
* code and only branching out of finally, filter or fault clauses is unverifiable.
*/
static int
is_valid_cmp_branch_instruction (MonoMethodHeader *header, guint offset, guint target)
{
int i;
MonoExceptionClause *clause;
for (i = 0; i < header->num_clauses; ++i) {
clause = &header->clauses [i];
/*branching out of a handler or finally*/
if (clause->flags != MONO_EXCEPTION_CLAUSE_NONE &&
MONO_OFFSET_IN_HANDLER (clause, offset) &&
!MONO_OFFSET_IN_HANDLER (clause, target))
return 1;
if (clause->try_offset != target && (MONO_OFFSET_IN_CLAUSE (clause, offset) ^ MONO_OFFSET_IN_CLAUSE (clause, target)))
return 2;
if (MONO_OFFSET_IN_HANDLER (clause, offset) ^ MONO_OFFSET_IN_HANDLER (clause, target))
return 2;
if (MONO_OFFSET_IN_FILTER (clause, offset) ^ MONO_OFFSET_IN_FILTER (clause, target))
return 2;
}
return 0;
}
/*
* A leave can't escape a finally block
*/
static int
is_correct_leave (MonoMethodHeader *header, guint offset, guint target)
{
int i;
MonoExceptionClause *clause;
for (i = 0; i < header->num_clauses; ++i) {
clause = &header->clauses [i];
if (clause->flags == MONO_EXCEPTION_CLAUSE_FINALLY && MONO_OFFSET_IN_HANDLER (clause, offset) && !MONO_OFFSET_IN_HANDLER (clause, target))
return 0;
if (MONO_OFFSET_IN_FILTER (clause, offset))
return 0;
}
return 1;
}
/*
* A rethrow can't happen outside of a catch handler.
*/
static int
is_correct_rethrow (MonoMethodHeader *header, guint offset)
{
int i;
MonoExceptionClause *clause;
for (i = 0; i < header->num_clauses; ++i) {
clause = &header->clauses [i];
if (MONO_OFFSET_IN_HANDLER (clause, offset))
return 1;
}
return 0;
}
/*
* An endfinally can't happen outside of a finally/fault handler.
*/
static int
is_correct_endfinally (MonoMethodHeader *header, guint offset)
{
int i;
MonoExceptionClause *clause;
for (i = 0; i < header->num_clauses; ++i) {
clause = &header->clauses [i];
if (MONO_OFFSET_IN_HANDLER (clause, offset) && (clause->flags == MONO_EXCEPTION_CLAUSE_FAULT || clause->flags == MONO_EXCEPTION_CLAUSE_FINALLY))
return 1;
}
return 0;
}
/*
* An endfilter can only happens inside a filter clause.
* In non-strict mode filter is allowed inside the handler clause too
*/
static MonoExceptionClause *
is_correct_endfilter (VerifyContext *ctx, guint offset)
{
int i;
MonoExceptionClause *clause;
for (i = 0; i < ctx->header->num_clauses; ++i) {
clause = &ctx->header->clauses [i];
if (clause->flags != MONO_EXCEPTION_CLAUSE_FILTER)
continue;
if (MONO_OFFSET_IN_FILTER (clause, offset))
return clause;
if (!IS_STRICT_MODE (ctx) && MONO_OFFSET_IN_HANDLER (clause, offset))
return clause;
}
return NULL;
}
/*
* Non-strict endfilter can happens inside a try block or any handler block
*/
static int
is_unverifiable_endfilter (VerifyContext *ctx, guint offset)
{
int i;
MonoExceptionClause *clause;
for (i = 0; i < ctx->header->num_clauses; ++i) {
clause = &ctx->header->clauses [i];
if (MONO_OFFSET_IN_CLAUSE (clause, offset))
return 1;
}
return 0;
}
static gboolean
is_valid_bool_arg (ILStackDesc *arg)
{
if (stack_slot_is_managed_pointer (arg) || stack_slot_is_boxed_value (arg) || stack_slot_is_null_literal (arg))
return TRUE;
switch (stack_slot_get_underlying_type (arg)) {
case TYPE_I4:
case TYPE_I8:
case TYPE_NATIVE_INT:
case TYPE_PTR:
return TRUE;
case TYPE_COMPLEX:
g_assert (arg->type);
switch (arg->type->type) {
case MONO_TYPE_CLASS:
case MONO_TYPE_STRING:
case MONO_TYPE_OBJECT:
case MONO_TYPE_SZARRAY:
case MONO_TYPE_ARRAY:
case MONO_TYPE_FNPTR:
case MONO_TYPE_PTR:
return TRUE;
case MONO_TYPE_GENERICINST:
/*We need to check if the container class
* of the generic type is a valuetype, iow:
* is it a "class Foo<T>" or a "struct Foo<T>"?
*/
return !arg->type->data.generic_class->container_class->valuetype;
default:
return FALSE;
}
default:
return FALSE;
}
}
/*Type manipulation helper*/
/*Returns the byref version of the supplied MonoType*/
static MonoType*
mono_type_get_type_byref (MonoType *type)
{
if (type->byref)
return type;
return &mono_class_from_mono_type (type)->this_arg;
}
/*Returns the byval version of the supplied MonoType*/
static MonoType*
mono_type_get_type_byval (MonoType *type)
{
if (!type->byref)
return type;
return &mono_class_from_mono_type (type)->byval_arg;
}
static MonoType*
mono_type_from_stack_slot (ILStackDesc *slot)
{
if (stack_slot_is_managed_pointer (slot))
return mono_type_get_type_byref (slot->type);
return slot->type;
}
/*Stack manipulation code*/
static void
ensure_stack_size (ILCodeDesc *stack, int required)
{
int new_size = 8;
ILStackDesc *tmp;
if (required < stack->max_size)
return;
/* We don't have to worry about the exponential growth since stack_copy prune unused space */
new_size = MAX (8, MAX (required, stack->max_size * 2));
g_assert (new_size >= stack->size);
g_assert (new_size >= required);
tmp = g_new0 (ILStackDesc, new_size);
_MEM_ALLOC (sizeof (ILStackDesc) * new_size);
if (stack->stack) {
if (stack->size)
memcpy (tmp, stack->stack, stack->size * sizeof (ILStackDesc));
g_free (stack->stack);
_MEM_FREE (sizeof (ILStackDesc) * stack->max_size);
}
stack->stack = tmp;
stack->max_size = new_size;
}
static void
stack_init (VerifyContext *ctx, ILCodeDesc *state)
{
if (state->flags & IL_CODE_FLAG_STACK_INITED)
return;
state->size = state->max_size = 0;
state->flags |= IL_CODE_FLAG_STACK_INITED;
}
static void
stack_copy (ILCodeDesc *to, ILCodeDesc *from)
{
ensure_stack_size (to, from->size);
to->size = from->size;
/*stack copy happens at merge points, which have small stacks*/
if (from->size)
memcpy (to->stack, from->stack, sizeof (ILStackDesc) * from->size);
}
static void
copy_stack_value (ILStackDesc *to, ILStackDesc *from)
{
to->stype = from->stype;
to->type = from->type;
to->method = from->method;
}
static int
check_underflow (VerifyContext *ctx, int size)
{
if (ctx->eval.size < size) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Stack underflow, required %d, but have %d at 0x%04x", size, ctx->eval.size, ctx->ip_offset));
return 0;
}
return 1;
}
static int
check_overflow (VerifyContext *ctx)
{
if (ctx->eval.size >= ctx->max_stack) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Method doesn't have stack-depth %d at 0x%04x", ctx->eval.size + 1, ctx->ip_offset));
return 0;
}
return 1;
}
/*This reject out PTR, FNPTR and TYPEDBYREF*/
static gboolean
check_unmanaged_pointer (VerifyContext *ctx, ILStackDesc *value)
{
if (stack_slot_get_type (value) == TYPE_PTR) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Unmanaged pointer is not a verifiable type at 0x%04x", ctx->ip_offset));
return 0;
}
return 1;
}
/*TODO verify if MONO_TYPE_TYPEDBYREF is not allowed here as well.*/
static gboolean
check_unverifiable_type (VerifyContext *ctx, MonoType *type)
{
if (type->type == MONO_TYPE_PTR || type->type == MONO_TYPE_FNPTR) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Unmanaged pointer is not a verifiable type at 0x%04x", ctx->ip_offset));
return 0;
}
return 1;
}
static ILStackDesc *
stack_push (VerifyContext *ctx)
{
g_assert (ctx->eval.size < ctx->max_stack);
g_assert (ctx->eval.size <= ctx->eval.max_size);
ensure_stack_size (&ctx->eval, ctx->eval.size + 1);
return & ctx->eval.stack [ctx->eval.size++];
}
static ILStackDesc *
stack_push_val (VerifyContext *ctx, int stype, MonoType *type)
{
ILStackDesc *top = stack_push (ctx);
top->stype = stype;
top->type = type;
return top;
}
static ILStackDesc *
stack_pop (VerifyContext *ctx)
{
ILStackDesc *ret;
g_assert (ctx->eval.size > 0);
ret = ctx->eval.stack + --ctx->eval.size;
if ((ret->stype & UNINIT_THIS_MASK) == UNINIT_THIS_MASK)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Found use of uninitialized 'this ptr' ref at 0x%04x", ctx->ip_offset));
return ret;
}
/* This function allows to safely pop an unititialized this ptr from
* the eval stack without marking the method as unverifiable.
*/
static ILStackDesc *
stack_pop_safe (VerifyContext *ctx)
{
g_assert (ctx->eval.size > 0);
return ctx->eval.stack + --ctx->eval.size;
}
/*Positive number distance from stack top. [0] is stack top, [1] is the one below*/
static ILStackDesc*
stack_peek (VerifyContext *ctx, int distance)
{
g_assert (ctx->eval.size - distance > 0);
return ctx->eval.stack + (ctx->eval.size - 1 - distance);
}
static ILStackDesc *
stack_push_stack_val (VerifyContext *ctx, ILStackDesc *value)
{
ILStackDesc *top = stack_push (ctx);
copy_stack_value (top, value);
return top;
}
/* Returns the MonoType associated with the token, or NULL if it is invalid.
*
* A boxable type can be either a reference or value type, but cannot be a byref type or an unmanaged pointer
* */
static MonoType*
get_boxable_mono_type (VerifyContext* ctx, int token, const char *opcode)
{
MonoType *type;
MonoClass *klass;
if (!(type = verifier_load_type (ctx, token, opcode)))
return NULL;
if (type->byref && type->type != MONO_TYPE_TYPEDBYREF) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Invalid use of byref type for %s at 0x%04x", opcode, ctx->ip_offset));
return NULL;
}
if (type->type == MONO_TYPE_VOID) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Invalid use of void type for %s at 0x%04x", opcode, ctx->ip_offset));
return NULL;
}
if (type->type == MONO_TYPE_TYPEDBYREF)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Invalid use of typedbyref for %s at 0x%04x", opcode, ctx->ip_offset));
if (!(klass = mono_class_from_mono_type (type)))
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Could not retrieve type token for %s at 0x%04x", opcode, ctx->ip_offset));
if (klass->generic_container && type->type != MONO_TYPE_GENERICINST)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Cannot use the generic type definition in a boxable type position for %s at 0x%04x", opcode, ctx->ip_offset));
check_unverifiable_type (ctx, type);
return type;
}
/*operation result tables */
static const unsigned char bin_op_table [TYPE_MAX][TYPE_MAX] = {
{TYPE_I4, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_I8, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_NATIVE_INT, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_R8, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
};
static const unsigned char add_table [TYPE_MAX][TYPE_MAX] = {
{TYPE_I4, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV},
{TYPE_INV, TYPE_I8, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_NATIVE_INT, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_R8, TYPE_INV, TYPE_INV},
{TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV, TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
};
static const unsigned char sub_table [TYPE_MAX][TYPE_MAX] = {
{TYPE_I4, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_I8, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_NATIVE_INT, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_R8, TYPE_INV, TYPE_INV},
{TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV, TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV, TYPE_NATIVE_INT | NON_VERIFIABLE_RESULT, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
};
static const unsigned char int_bin_op_table [TYPE_MAX][TYPE_MAX] = {
{TYPE_I4, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_I8, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_NATIVE_INT, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
};
static const unsigned char shift_op_table [TYPE_MAX][TYPE_MAX] = {
{TYPE_I4, TYPE_INV, TYPE_I4, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_I8, TYPE_INV, TYPE_I8, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_NATIVE_INT, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
};
static const unsigned char cmp_br_op [TYPE_MAX][TYPE_MAX] = {
{TYPE_I4, TYPE_INV, TYPE_I4, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_I4, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_I4, TYPE_INV, TYPE_I4, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_I4, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_I4, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
};
static const unsigned char cmp_br_eq_op [TYPE_MAX][TYPE_MAX] = {
{TYPE_I4, TYPE_INV, TYPE_I4, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_I4, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_I4, TYPE_INV, TYPE_I4, TYPE_INV, TYPE_I4 | NON_VERIFIABLE_RESULT, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_I4, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_I4 | NON_VERIFIABLE_RESULT, TYPE_INV, TYPE_I4, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_I4},
};
static const unsigned char add_ovf_un_table [TYPE_MAX][TYPE_MAX] = {
{TYPE_I4, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV},
{TYPE_INV, TYPE_I8, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_NATIVE_INT, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV, TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
};
static const unsigned char sub_ovf_un_table [TYPE_MAX][TYPE_MAX] = {
{TYPE_I4, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_I8, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_NATIVE_INT, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV, TYPE_PTR | NON_VERIFIABLE_RESULT, TYPE_INV, TYPE_NATIVE_INT | NON_VERIFIABLE_RESULT, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
};
static const unsigned char bin_ovf_table [TYPE_MAX][TYPE_MAX] = {
{TYPE_I4, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_I8, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_NATIVE_INT, TYPE_INV, TYPE_NATIVE_INT, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
{TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV, TYPE_INV},
};
#ifdef MONO_VERIFIER_DEBUG
/*debug helpers */
static void
dump_stack_value (ILStackDesc *value)
{
printf ("[(%x)(%x)", value->type->type, value->stype);
if (stack_slot_is_this_pointer (value))
printf ("[this] ");
if (stack_slot_is_boxed_value (value))
printf ("[boxed] ");
if (stack_slot_is_null_literal (value))
printf ("[null] ");
if (stack_slot_is_managed_mutability_pointer (value))
printf ("Controled Mutability MP: ");
if (stack_slot_is_managed_pointer (value))
printf ("Managed Pointer to: ");
switch (stack_slot_get_underlying_type (value)) {
case TYPE_INV:
printf ("invalid type]");
return;
case TYPE_I4:
printf ("int32]");
return;
case TYPE_I8:
printf ("int64]");
return;
case TYPE_NATIVE_INT:
printf ("native int]");
return;
case TYPE_R8:
printf ("float64]");
return;
case TYPE_PTR:
printf ("unmanaged pointer]");
return;
case TYPE_COMPLEX:
switch (value->type->type) {
case MONO_TYPE_CLASS:
case MONO_TYPE_VALUETYPE:
printf ("complex] (%s)", value->type->data.klass->name);
return;
case MONO_TYPE_STRING:
printf ("complex] (string)");
return;
case MONO_TYPE_OBJECT:
printf ("complex] (object)");
return;
case MONO_TYPE_SZARRAY:
printf ("complex] (%s [])", value->type->data.klass->name);
return;
case MONO_TYPE_ARRAY:
printf ("complex] (%s [%d %d %d])",
value->type->data.array->eklass->name,
value->type->data.array->rank,
value->type->data.array->numsizes,
value->type->data.array->numlobounds);
return;
case MONO_TYPE_GENERICINST:
printf ("complex] (inst of %s )", value->type->data.generic_class->container_class->name);
return;
case MONO_TYPE_VAR:
printf ("complex] (type generic param !%d - %s) ", value->type->data.generic_param->num, mono_generic_param_info (value->type->data.generic_param)->name);
return;
case MONO_TYPE_MVAR:
printf ("complex] (method generic param !!%d - %s) ", value->type->data.generic_param->num, mono_generic_param_info (value->type->data.generic_param)->name);
return;
default: {
//should be a boxed value
char * name = mono_type_full_name (value->type);
printf ("complex] %s", name);
g_free (name);
return;
}
}
default:
printf ("unknown stack %x type]\n", value->stype);
g_assert_not_reached ();
}
}
static void
dump_stack_state (ILCodeDesc *state)
{
int i;
printf ("(%d) ", state->size);
for (i = 0; i < state->size; ++i)
dump_stack_value (state->stack + i);
printf ("\n");
}
#endif
/**
* is_array_type_compatible:
*
* Returns TRUE if candidate array type can be assigned to target.
*
* Both parameters MUST be of type MONO_TYPE_ARRAY (target->type == MONO_TYPE_ARRAY)
*/
static gboolean
is_array_type_compatible (MonoType *target, MonoType *candidate)
{
MonoArrayType *left = target->data.array;
MonoArrayType *right = candidate->data.array;
g_assert (target->type == MONO_TYPE_ARRAY);
g_assert (candidate->type == MONO_TYPE_ARRAY);
if (left->rank != right->rank)
return FALSE;
return verifier_class_is_assignable_from (left->eklass, right->eklass);
}
static int
get_stack_type (MonoType *type)
{
int mask = 0;
int type_kind = type->type;
if (type->byref)
mask = POINTER_MASK;
/*TODO handle CMMP_MASK */
handle_enum:
switch (type_kind) {
case MONO_TYPE_I1:
case MONO_TYPE_U1:
case MONO_TYPE_BOOLEAN:
case MONO_TYPE_I2:
case MONO_TYPE_U2:
case MONO_TYPE_CHAR:
case MONO_TYPE_I4:
case MONO_TYPE_U4:
return TYPE_I4 | mask;
case MONO_TYPE_I:
case MONO_TYPE_U:
return TYPE_NATIVE_INT | mask;
/* FIXME: the spec says that you cannot have a pointer to method pointer, do we need to check this here? */
case MONO_TYPE_FNPTR:
case MONO_TYPE_PTR:
case MONO_TYPE_TYPEDBYREF:
return TYPE_PTR | mask;
case MONO_TYPE_VAR:
case MONO_TYPE_MVAR:
case MONO_TYPE_CLASS:
case MONO_TYPE_STRING:
case MONO_TYPE_OBJECT:
case MONO_TYPE_SZARRAY:
case MONO_TYPE_ARRAY:
return TYPE_COMPLEX | mask;
case MONO_TYPE_I8:
case MONO_TYPE_U8:
return TYPE_I8 | mask;
case MONO_TYPE_R4:
case MONO_TYPE_R8:
return TYPE_R8 | mask;
case MONO_TYPE_GENERICINST:
case MONO_TYPE_VALUETYPE:
if (mono_type_is_enum_type (type)) {
type = mono_type_get_underlying_type_any (type);
if (!type)
return FALSE;
type_kind = type->type;
goto handle_enum;
} else {
return TYPE_COMPLEX | mask;
}
default:
return TYPE_INV;
}
}
/* convert MonoType to ILStackDesc format (stype) */
static gboolean
set_stack_value (VerifyContext *ctx, ILStackDesc *stack, MonoType *type, int take_addr)
{
int mask = 0;
int type_kind = type->type;
if (type->byref || take_addr)
mask = POINTER_MASK;
/* TODO handle CMMP_MASK */
handle_enum:
stack->type = type;
switch (type_kind) {
case MONO_TYPE_I1:
case MONO_TYPE_U1:
case MONO_TYPE_BOOLEAN:
case MONO_TYPE_I2:
case MONO_TYPE_U2:
case MONO_TYPE_CHAR:
case MONO_TYPE_I4:
case MONO_TYPE_U4:
stack->stype = TYPE_I4 | mask;
break;
case MONO_TYPE_I:
case MONO_TYPE_U:
stack->stype = TYPE_NATIVE_INT | mask;
break;
/*FIXME: Do we need to check if it's a pointer to the method pointer? The spec says it' illegal to have that.*/
case MONO_TYPE_FNPTR:
case MONO_TYPE_PTR:
case MONO_TYPE_TYPEDBYREF:
stack->stype = TYPE_PTR | mask;
break;
case MONO_TYPE_CLASS:
case MONO_TYPE_STRING:
case MONO_TYPE_OBJECT:
case MONO_TYPE_SZARRAY:
case MONO_TYPE_ARRAY:
case MONO_TYPE_VAR:
case MONO_TYPE_MVAR:
stack->stype = TYPE_COMPLEX | mask;
break;
case MONO_TYPE_I8:
case MONO_TYPE_U8:
stack->stype = TYPE_I8 | mask;
break;
case MONO_TYPE_R4:
case MONO_TYPE_R8:
stack->stype = TYPE_R8 | mask;
break;
case MONO_TYPE_GENERICINST:
case MONO_TYPE_VALUETYPE:
if (mono_type_is_enum_type (type)) {
MonoType *utype = mono_type_get_underlying_type_any (type);
if (!utype) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Could not resolve underlying type of %x at %d", type->type, ctx->ip_offset));
return FALSE;
}
type = utype;
type_kind = type->type;
goto handle_enum;
} else {
stack->stype = TYPE_COMPLEX | mask;
break;
}
default:
VERIFIER_DEBUG ( printf ("unknown type 0x%02x in eval stack type\n", type->type); );
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Illegal value set on stack 0x%02x at %d", type->type, ctx->ip_offset));
return FALSE;
}
return TRUE;
}
/*
* init_stack_with_value_at_exception_boundary:
*
* Initialize the stack and push a given type.
* The instruction is marked as been on the exception boundary.
*/
static void
init_stack_with_value_at_exception_boundary (VerifyContext *ctx, ILCodeDesc *code, MonoClass *klass)
{
MonoError error;
MonoType *type = mono_class_inflate_generic_type_checked (&klass->byval_arg, ctx->generic_context, &error);
if (!mono_error_ok (&error)) {
char *name = mono_type_get_full_name (klass);
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Invalid class %s used for exception", name));
g_free (name);
mono_error_cleanup (&error);
return;
}
if (!ctx->max_stack) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Stack overflow at 0x%04x", ctx->ip_offset));
return;
}
stack_init (ctx, code);
ensure_stack_size (code, 1);
set_stack_value (ctx, code->stack, type, FALSE);
ctx->exception_types = g_slist_prepend (ctx->exception_types, type);
code->size = 1;
code->flags |= IL_CODE_FLAG_WAS_TARGET;
if (mono_type_is_generic_argument (type))
code->stack->stype |= BOXED_MASK;
}
/* Class lazy loading functions */
static GENERATE_GET_CLASS_WITH_CACHE (ienumerable, System.Collections.Generic, IEnumerable`1)
static GENERATE_GET_CLASS_WITH_CACHE (icollection, System.Collections.Generic, ICollection`1)
static GENERATE_GET_CLASS_WITH_CACHE (ireadonly_list, System.Collections.Generic, IReadOnlyList`1)
static GENERATE_GET_CLASS_WITH_CACHE (ireadonly_collection, System.Collections.Generic, IReadOnlyCollection`1)
static MonoClass*
get_ienumerable_class (void)
{
return mono_class_get_ienumerable_class ();
}
static MonoClass*
get_icollection_class (void)
{
return mono_class_get_icollection_class ();
}
static MonoClass*
get_ireadonlylist_class (void)
{
return mono_class_get_ireadonly_list_class ();
}
static MonoClass*
get_ireadonlycollection_class (void)
{
return mono_class_get_ireadonly_collection_class ();
}
static MonoClass*
inflate_class_one_arg (MonoClass *gtype, MonoClass *arg0)
{
MonoType *args [1];
args [0] = &arg0->byval_arg;
return mono_class_bind_generic_parameters (gtype, 1, args, FALSE);
}
static gboolean
verifier_inflate_and_check_compat (MonoClass *target, MonoClass *gtd, MonoClass *arg)
{
MonoClass *tmp;
if (!(tmp = inflate_class_one_arg (gtd, arg)))
return FALSE;
if (mono_class_is_variant_compatible (target, tmp, TRUE))
return TRUE;
return FALSE;
}
static gboolean
verifier_class_is_assignable_from (MonoClass *target, MonoClass *candidate)
{
MonoClass *iface_gtd;
if (target == candidate)
return TRUE;
if (mono_class_has_variant_generic_params (target)) {
if (MONO_CLASS_IS_INTERFACE (target)) {
if (MONO_CLASS_IS_INTERFACE (candidate) && mono_class_is_variant_compatible (target, candidate, TRUE))
return TRUE;
if (candidate->rank == 1) {
if (verifier_inflate_and_check_compat (target, mono_defaults.generic_ilist_class, candidate->element_class))
return TRUE;
if (verifier_inflate_and_check_compat (target, get_icollection_class (), candidate->element_class))
return TRUE;
if (verifier_inflate_and_check_compat (target, get_ienumerable_class (), candidate->element_class))
return TRUE;
if (verifier_inflate_and_check_compat (target, get_ireadonlylist_class (), candidate->element_class))
return TRUE;
if (verifier_inflate_and_check_compat (target, get_ireadonlycollection_class (), candidate->element_class))
return TRUE;
} else {
MonoError error;
int i;
while (candidate && candidate != mono_defaults.object_class) {
mono_class_setup_interfaces (candidate, &error);
if (!mono_error_ok (&error)) {
mono_error_cleanup (&error);
return FALSE;
}
/*klass is a generic variant interface, We need to extract from oklass a list of ifaces which are viable candidates.*/
for (i = 0; i < candidate->interface_offsets_count; ++i) {
MonoClass *iface = candidate->interfaces_packed [i];
if (mono_class_is_variant_compatible (target, iface, TRUE))
return TRUE;
}
for (i = 0; i < candidate->interface_count; ++i) {
MonoClass *iface = candidate->interfaces [i];
if (mono_class_is_variant_compatible (target, iface, TRUE))
return TRUE;
}
candidate = candidate->parent;
}
}
} else if (target->delegate) {
if (mono_class_is_variant_compatible (target, candidate, TRUE))
return TRUE;
}
return FALSE;
}
if (mono_class_is_assignable_from (target, candidate))
return TRUE;
if (!MONO_CLASS_IS_INTERFACE (target) || !target->generic_class || candidate->rank != 1)
return FALSE;
iface_gtd = target->generic_class->container_class;
if (iface_gtd != mono_defaults.generic_ilist_class && iface_gtd != get_icollection_class () && iface_gtd != get_ienumerable_class ())
return FALSE;
target = mono_class_from_mono_type (target->generic_class->context.class_inst->type_argv [0]);
candidate = candidate->element_class;
return TRUE;
}
/*Verify if type 'candidate' can be stored in type 'target'.
*
* If strict, check for the underlying type and not the verification stack types
*/
static gboolean
verify_type_compatibility_full (VerifyContext *ctx, MonoType *target, MonoType *candidate, gboolean strict)
{
#define IS_ONE_OF3(T, A, B, C) (T == A || T == B || T == C)
#define IS_ONE_OF2(T, A, B) (T == A || T == B)
MonoType *original_candidate = candidate;
VERIFIER_DEBUG ( printf ("checking type compatibility %s x %s strict %d\n", mono_type_full_name (target), mono_type_full_name (candidate), strict); );
/*only one is byref */
if (candidate->byref ^ target->byref) {
/* converting from native int to byref*/
if (get_stack_type (candidate) == TYPE_NATIVE_INT && target->byref) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("using byref native int at 0x%04x", ctx->ip_offset));
return TRUE;
}
return FALSE;
}
strict |= target->byref;
/*From now on we don't care about byref anymore, so it's ok to discard it here*/
candidate = mono_type_get_underlying_type_any (candidate);
handle_enum:
switch (target->type) {
case MONO_TYPE_VOID:
return candidate->type == MONO_TYPE_VOID;
case MONO_TYPE_I1:
case MONO_TYPE_U1:
case MONO_TYPE_BOOLEAN:
if (strict)
return IS_ONE_OF3 (candidate->type, MONO_TYPE_I1, MONO_TYPE_U1, MONO_TYPE_BOOLEAN);
case MONO_TYPE_I2:
case MONO_TYPE_U2:
case MONO_TYPE_CHAR:
if (strict)
return IS_ONE_OF3 (candidate->type, MONO_TYPE_I2, MONO_TYPE_U2, MONO_TYPE_CHAR);
case MONO_TYPE_I4:
case MONO_TYPE_U4: {
gboolean is_native_int = IS_ONE_OF2 (candidate->type, MONO_TYPE_I, MONO_TYPE_U);
gboolean is_int4 = IS_ONE_OF2 (candidate->type, MONO_TYPE_I4, MONO_TYPE_U4);
if (strict)
return is_native_int || is_int4;
return is_native_int || get_stack_type (candidate) == TYPE_I4;
}
case MONO_TYPE_I8:
case MONO_TYPE_U8:
return IS_ONE_OF2 (candidate->type, MONO_TYPE_I8, MONO_TYPE_U8);
case MONO_TYPE_R4:
case MONO_TYPE_R8:
if (strict)
return candidate->type == target->type;
return IS_ONE_OF2 (candidate->type, MONO_TYPE_R4, MONO_TYPE_R8);
case MONO_TYPE_I:
case MONO_TYPE_U: {
gboolean is_native_int = IS_ONE_OF2 (candidate->type, MONO_TYPE_I, MONO_TYPE_U);
gboolean is_int4 = IS_ONE_OF2 (candidate->type, MONO_TYPE_I4, MONO_TYPE_U4);
if (strict)
return is_native_int || is_int4;
return is_native_int || get_stack_type (candidate) == TYPE_I4;
}
case MONO_TYPE_PTR:
if (candidate->type != MONO_TYPE_PTR)
return FALSE;
/* check the underlying type */
return verify_type_compatibility_full (ctx, target->data.type, candidate->data.type, TRUE);
case MONO_TYPE_FNPTR: {
MonoMethodSignature *left, *right;
if (candidate->type != MONO_TYPE_FNPTR)
return FALSE;
left = mono_type_get_signature (target);
right = mono_type_get_signature (candidate);
return mono_metadata_signature_equal (left, right) && left->call_convention == right->call_convention;
}
case MONO_TYPE_GENERICINST: {
MonoClass *target_klass;
MonoClass *candidate_klass;
if (mono_type_is_enum_type (target)) {
target = mono_type_get_underlying_type_any (target);
if (!target)
return FALSE;
goto handle_enum;
}
/*
* VAR / MVAR compatibility must be checked by verify_stack_type_compatibility
* to take boxing status into account.
*/
if (mono_type_is_generic_argument (original_candidate))
return FALSE;
target_klass = mono_class_from_mono_type (target);
candidate_klass = mono_class_from_mono_type (candidate);
if (mono_class_is_nullable (target_klass)) {
if (!mono_class_is_nullable (candidate_klass))
return FALSE;
return target_klass == candidate_klass;
}
return verifier_class_is_assignable_from (target_klass, candidate_klass);
}
case MONO_TYPE_STRING:
return candidate->type == MONO_TYPE_STRING;
case MONO_TYPE_CLASS:
/*
* VAR / MVAR compatibility must be checked by verify_stack_type_compatibility
* to take boxing status into account.
*/
if (mono_type_is_generic_argument (original_candidate))
return FALSE;
if (candidate->type == MONO_TYPE_VALUETYPE)
return FALSE;
/* If candidate is an enum it should return true for System.Enum and supertypes.
* That's why here we use the original type and not the underlying type.
*/
return verifier_class_is_assignable_from (target->data.klass, mono_class_from_mono_type (original_candidate));
case MONO_TYPE_OBJECT:
return MONO_TYPE_IS_REFERENCE (candidate);
case MONO_TYPE_SZARRAY: {
MonoClass *left;
MonoClass *right;
if (candidate->type != MONO_TYPE_SZARRAY)
return FALSE;
left = mono_class_from_mono_type (target);
right = mono_class_from_mono_type (candidate);
return verifier_class_is_assignable_from (left, right);
}
case MONO_TYPE_ARRAY:
if (candidate->type != MONO_TYPE_ARRAY)
return FALSE;
return is_array_type_compatible (target, candidate);
case MONO_TYPE_TYPEDBYREF:
return candidate->type == MONO_TYPE_TYPEDBYREF;
case MONO_TYPE_VALUETYPE: {
MonoClass *target_klass;
MonoClass *candidate_klass;
if (candidate->type == MONO_TYPE_CLASS)
return FALSE;
target_klass = mono_class_from_mono_type (target);
candidate_klass = mono_class_from_mono_type (candidate);
if (target_klass == candidate_klass)
return TRUE;
if (mono_type_is_enum_type (target)) {
target = mono_type_get_underlying_type_any (target);
if (!target)
return FALSE;
goto handle_enum;
}
return FALSE;
}
case MONO_TYPE_VAR:
if (candidate->type != MONO_TYPE_VAR)
return FALSE;
return mono_type_get_generic_param_num (candidate) == mono_type_get_generic_param_num (target);
case MONO_TYPE_MVAR:
if (candidate->type != MONO_TYPE_MVAR)
return FALSE;
return mono_type_get_generic_param_num (candidate) == mono_type_get_generic_param_num (target);
default:
VERIFIER_DEBUG ( printf ("unknown store type %d\n", target->type); );
g_assert_not_reached ();
return FALSE;
}
return 1;
#undef IS_ONE_OF3
#undef IS_ONE_OF2
}
static gboolean
verify_type_compatibility (VerifyContext *ctx, MonoType *target, MonoType *candidate)
{
return verify_type_compatibility_full (ctx, target, candidate, FALSE);
}
/*
* Returns the generic param bound to the context been verified.
*
*/
static MonoGenericParam*
get_generic_param (VerifyContext *ctx, MonoType *param)
{
guint16 param_num = mono_type_get_generic_param_num (param);
if (param->type == MONO_TYPE_VAR) {
if (!ctx->generic_context->class_inst || ctx->generic_context->class_inst->type_argc <= param_num) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Invalid generic type argument %d", param_num));
return NULL;
}
return ctx->generic_context->class_inst->type_argv [param_num]->data.generic_param;
}
/*param must be a MVAR */
if (!ctx->generic_context->method_inst || ctx->generic_context->method_inst->type_argc <= param_num) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Invalid generic method argument %d", param_num));
return NULL;
}
return ctx->generic_context->method_inst->type_argv [param_num]->data.generic_param;
}
static gboolean
recursive_boxed_constraint_type_check (VerifyContext *ctx, MonoType *type, MonoClass *constraint_class, int recursion_level)
{
MonoType *constraint_type = &constraint_class->byval_arg;
if (recursion_level <= 0)
return FALSE;
if (verify_type_compatibility_full (ctx, type, mono_type_get_type_byval (constraint_type), FALSE))
return TRUE;
if (mono_type_is_generic_argument (constraint_type)) {
MonoGenericParam *param = get_generic_param (ctx, constraint_type);
MonoClass **klass;
if (!param)
return FALSE;
for (klass = mono_generic_param_info (param)->constraints; klass && *klass; ++klass) {
if (recursive_boxed_constraint_type_check (ctx, type, *klass, recursion_level - 1))
return TRUE;
}
}
return FALSE;
}
/**
* is_compatible_boxed_valuetype:
*
* Returns: TRUE if @candidate / @stack is a valid boxed valuetype.
*
* @type The source type. It it tested to be of the proper type.
* @candidate type of the boxed valuetype.
* @stack stack slot of the boxed valuetype, separate from @candidade since one could be changed before calling this function
* @strict if TRUE candidate must be boxed compatible to the target type
*
*/
static gboolean
is_compatible_boxed_valuetype (VerifyContext *ctx, MonoType *type, MonoType *candidate, ILStackDesc *stack, gboolean strict)
{
if (!stack_slot_is_boxed_value (stack))
return FALSE;
if (type->byref || candidate->byref)
return FALSE;
if (mono_type_is_generic_argument (candidate)) {
MonoGenericParam *param = get_generic_param (ctx, candidate);
MonoClass **klass;
if (!param)
return FALSE;
for (klass = mono_generic_param_info (param)->constraints; klass && *klass; ++klass) {
/*256 should be enough since there can't be more than 255 generic arguments.*/
if (recursive_boxed_constraint_type_check (ctx, type, *klass, 256))
return TRUE;
}
}
if (mono_type_is_generic_argument (type))
return FALSE;
if (!strict)
return TRUE;
return MONO_TYPE_IS_REFERENCE (type) && verifier_class_is_assignable_from (mono_class_from_mono_type (type), mono_class_from_mono_type (candidate));
}
static int
verify_stack_type_compatibility_full (VerifyContext *ctx, MonoType *type, ILStackDesc *stack, gboolean drop_byref, gboolean valuetype_must_be_boxed)
{
MonoType *candidate = mono_type_from_stack_slot (stack);
if (MONO_TYPE_IS_REFERENCE (type) && !type->byref && stack_slot_is_null_literal (stack))
return TRUE;
if (is_compatible_boxed_valuetype (ctx, type, candidate, stack, TRUE))
return TRUE;
if (valuetype_must_be_boxed && !stack_slot_is_boxed_value (stack) && !MONO_TYPE_IS_REFERENCE (candidate))
return FALSE;
if (!valuetype_must_be_boxed && stack_slot_is_boxed_value (stack))
return FALSE;
if (drop_byref)
return verify_type_compatibility_full (ctx, type, mono_type_get_type_byval (candidate), FALSE);
/* Handle how Roslyn emit fixed statements by encoding it as byref */
if (type->byref && candidate->byref && (type->type == MONO_TYPE_I) && !mono_type_is_reference (candidate)) {
if (!IS_STRICT_MODE (ctx))
return TRUE;
}
return verify_type_compatibility_full (ctx, type, candidate, FALSE);
}
static int
verify_stack_type_compatibility (VerifyContext *ctx, MonoType *type, ILStackDesc *stack)
{
return verify_stack_type_compatibility_full (ctx, type, stack, FALSE, FALSE);
}
static gboolean
mono_delegate_type_equal (MonoType *target, MonoType *candidate)
{
if (candidate->byref ^ target->byref)
return FALSE;
switch (target->type) {
case MONO_TYPE_VOID:
case MONO_TYPE_I1:
case MONO_TYPE_U1:
case MONO_TYPE_BOOLEAN:
case MONO_TYPE_I2:
case MONO_TYPE_U2:
case MONO_TYPE_CHAR:
case MONO_TYPE_I4:
case MONO_TYPE_U4:
case MONO_TYPE_I8:
case MONO_TYPE_U8:
case MONO_TYPE_R4:
case MONO_TYPE_R8:
case MONO_TYPE_I:
case MONO_TYPE_U:
case MONO_TYPE_STRING:
case MONO_TYPE_TYPEDBYREF:
return candidate->type == target->type;
case MONO_TYPE_PTR:
if (candidate->type != MONO_TYPE_PTR)
return FALSE;
return mono_delegate_type_equal (target->data.type, candidate->data.type);
case MONO_TYPE_FNPTR:
if (candidate->type != MONO_TYPE_FNPTR)
return FALSE;
return mono_delegate_signature_equal (mono_type_get_signature (target), mono_type_get_signature (candidate), FALSE);
case MONO_TYPE_GENERICINST: {
MonoClass *target_klass;
MonoClass *candidate_klass;
target_klass = mono_class_from_mono_type (target);
candidate_klass = mono_class_from_mono_type (candidate);
/*FIXME handle nullables and enum*/
return verifier_class_is_assignable_from (target_klass, candidate_klass);
}
case MONO_TYPE_OBJECT:
return MONO_TYPE_IS_REFERENCE (candidate);
case MONO_TYPE_CLASS:
return verifier_class_is_assignable_from(target->data.klass, mono_class_from_mono_type (candidate));
case MONO_TYPE_SZARRAY:
if (candidate->type != MONO_TYPE_SZARRAY)
return FALSE;
return verifier_class_is_assignable_from (mono_class_from_mono_type (target)->element_class, mono_class_from_mono_type (candidate)->element_class);
case MONO_TYPE_ARRAY:
if (candidate->type != MONO_TYPE_ARRAY)
return FALSE;
return is_array_type_compatible (target, candidate);
case MONO_TYPE_VALUETYPE:
/*FIXME handle nullables and enum*/
return mono_class_from_mono_type (candidate) == mono_class_from_mono_type (target);
case MONO_TYPE_VAR:
return candidate->type == MONO_TYPE_VAR && mono_type_get_generic_param_num (target) == mono_type_get_generic_param_num (candidate);
return FALSE;
case MONO_TYPE_MVAR:
return candidate->type == MONO_TYPE_MVAR && mono_type_get_generic_param_num (target) == mono_type_get_generic_param_num (candidate);
return FALSE;
default:
VERIFIER_DEBUG ( printf ("Unknown type %d. Implement me!\n", target->type); );
g_assert_not_reached ();
return FALSE;
}
}
static gboolean
mono_delegate_param_equal (MonoType *delegate, MonoType *method)
{
if (mono_metadata_type_equal_full (delegate, method, TRUE))
return TRUE;
return mono_delegate_type_equal (method, delegate);
}
static gboolean
mono_delegate_ret_equal (MonoType *delegate, MonoType *method)
{
if (mono_metadata_type_equal_full (delegate, method, TRUE))
return TRUE;
return mono_delegate_type_equal (delegate, method);
}
/*
* mono_delegate_signature_equal:
*
* Compare two signatures in the way expected by delegates.
*
* This function only exists due to the fact that it should ignore the 'has_this' part of the signature.
*
* FIXME can this function be eliminated and proper metadata functionality be used?
*/
static gboolean
mono_delegate_signature_equal (MonoMethodSignature *delegate_sig, MonoMethodSignature *method_sig, gboolean is_static_ldftn)
{
int i;
int method_offset = is_static_ldftn ? 1 : 0;
if (delegate_sig->param_count + method_offset != method_sig->param_count)
return FALSE;
if (delegate_sig->call_convention != method_sig->call_convention)
return FALSE;
for (i = 0; i < delegate_sig->param_count; i++) {
MonoType *p1 = delegate_sig->params [i];
MonoType *p2 = method_sig->params [i + method_offset];
if (!mono_delegate_param_equal (p1, p2))
return FALSE;
}
if (!mono_delegate_ret_equal (delegate_sig->ret, method_sig->ret))
return FALSE;
return TRUE;
}
gboolean
mono_verifier_is_signature_compatible (MonoMethodSignature *target, MonoMethodSignature *candidate)
{
return mono_delegate_signature_equal (target, candidate, FALSE);
}
/*
* verify_ldftn_delegate:
*
* Verify properties of ldftn based delegates.
*/
static void
verify_ldftn_delegate (VerifyContext *ctx, MonoClass *delegate, ILStackDesc *value, ILStackDesc *funptr)
{
MonoMethod *method = funptr->method;
/*ldftn non-final virtuals only allowed if method is not static,
* the object is a this arg (comes from a ldarg.0), and there is no starg.0.
* This rules doesn't apply if the object on stack is a boxed valuetype.
*/
if ((method->flags & METHOD_ATTRIBUTE_VIRTUAL) && !(method->flags & METHOD_ATTRIBUTE_FINAL) && !(method->klass->flags & TYPE_ATTRIBUTE_SEALED) && !stack_slot_is_boxed_value (value)) {
/*A stdarg 0 must not happen, we fail here only in fail fast mode to avoid double error reports*/
if (IS_FAIL_FAST_MODE (ctx) && ctx->has_this_store)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Invalid ldftn with virtual function in method with stdarg 0 at 0x%04x", ctx->ip_offset));
/*current method must not be static*/
if (ctx->method->flags & METHOD_ATTRIBUTE_STATIC)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Invalid ldftn with virtual function at 0x%04x", ctx->ip_offset));
/*value is the this pointer, loaded using ldarg.0 */
if (!stack_slot_is_this_pointer (value))
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Invalid object argument, it is not the this pointer, to ldftn with virtual method at 0x%04x", ctx->ip_offset));
ctx->code [ctx->ip_offset].flags |= IL_CODE_LDFTN_DELEGATE_NONFINAL_VIRTUAL;
}
}
/*
* verify_delegate_compatibility:
*
* Verify delegate creation sequence.
*
*/
static void
verify_delegate_compatibility (VerifyContext *ctx, MonoClass *delegate, ILStackDesc *value, ILStackDesc *funptr)
{
#define IS_VALID_OPCODE(offset, opcode) (ip [ip_offset - offset] == opcode && (ctx->code [ip_offset - offset].flags & IL_CODE_FLAG_SEEN))
#define IS_LOAD_FUN_PTR(kind) (IS_VALID_OPCODE (6, CEE_PREFIX1) && ip [ip_offset - 5] == kind)
MonoMethod *invoke, *method;
const guint8 *ip = ctx->header->code;
guint32 ip_offset = ctx->ip_offset;
gboolean is_static_ldftn = FALSE, is_first_arg_bound = FALSE;
if (stack_slot_get_type (funptr) != TYPE_PTR || !funptr->method) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Invalid function pointer parameter for delegate constructor at 0x%04x", ctx->ip_offset));
return;
}
invoke = mono_get_delegate_invoke (delegate);
method = funptr->method;
if (!method || !mono_method_signature (method)) {
char *name = mono_type_get_full_name (delegate);
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Invalid method on stack to create delegate %s construction at 0x%04x", name, ctx->ip_offset));
g_free (name);
return;
}
if (!invoke || !mono_method_signature (invoke)) {
char *name = mono_type_get_full_name (delegate);
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Delegate type %s with bad Invoke method at 0x%04x", name, ctx->ip_offset));
g_free (name);
return;
}
is_static_ldftn = (ip_offset > 5 && IS_LOAD_FUN_PTR (CEE_LDFTN)) && method->flags & METHOD_ATTRIBUTE_STATIC;
if (is_static_ldftn)
is_first_arg_bound = mono_method_signature (invoke)->param_count + 1 == mono_method_signature (method)->param_count;
if (!mono_delegate_signature_equal (mono_method_signature (invoke), mono_method_signature (method), is_first_arg_bound)) {
char *fun_sig = mono_signature_get_desc (mono_method_signature (method), FALSE);
char *invoke_sig = mono_signature_get_desc (mono_method_signature (invoke), FALSE);
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Function pointer signature '%s' doesn't match delegate's signature '%s' at 0x%04x", fun_sig, invoke_sig, ctx->ip_offset));
g_free (fun_sig);
g_free (invoke_sig);
}
/*
* Delegate code sequences:
* [-6] ldftn token
* newobj ...
*
*
* [-7] dup
* [-6] ldvirtftn token
* newobj ...
*
* ldftn sequence:*/
if (ip_offset > 5 && IS_LOAD_FUN_PTR (CEE_LDFTN)) {
verify_ldftn_delegate (ctx, delegate, value, funptr);
} else if (ip_offset > 6 && IS_VALID_OPCODE (7, CEE_DUP) && IS_LOAD_FUN_PTR (CEE_LDVIRTFTN)) {
ctx->code [ip_offset - 6].flags |= IL_CODE_DELEGATE_SEQUENCE;
}else {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Invalid code sequence for delegate creation at 0x%04x", ctx->ip_offset));
}
ctx->code [ip_offset].flags |= IL_CODE_DELEGATE_SEQUENCE;
//general tests
if (is_first_arg_bound) {
if (mono_method_signature (method)->param_count == 0 || !verify_stack_type_compatibility_full (ctx, mono_method_signature (method)->params [0], value, FALSE, TRUE))
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("This object not compatible with function pointer for delegate creation at 0x%04x", ctx->ip_offset));
} else {
if (method->flags & METHOD_ATTRIBUTE_STATIC) {
if (!stack_slot_is_null_literal (value) && !is_first_arg_bound)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Non-null this args used with static function for delegate creation at 0x%04x", ctx->ip_offset));
} else {
if (!verify_stack_type_compatibility_full (ctx, &method->klass->byval_arg, value, FALSE, TRUE) && !stack_slot_is_null_literal (value))
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("This object not compatible with function pointer for delegate creation at 0x%04x", ctx->ip_offset));
}
}
if (stack_slot_get_type (value) != TYPE_COMPLEX)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Invalid first parameter for delegate creation at 0x%04x", ctx->ip_offset));
#undef IS_VALID_OPCODE
#undef IS_LOAD_FUN_PTR
}
/* implement the opcode checks*/
static void
push_arg (VerifyContext *ctx, unsigned int arg, int take_addr)
{
ILStackDesc *top;
if (arg >= ctx->max_args) {
if (take_addr)
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Method doesn't have argument %d", arg + 1));
else {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Method doesn't have argument %d", arg + 1));
if (check_overflow (ctx)) //FIXME: what sane value could we ever push?
stack_push_val (ctx, TYPE_I4, &mono_defaults.int32_class->byval_arg);
}
} else if (check_overflow (ctx)) {
/*We must let the value be pushed, otherwise we would get an underflow error*/
check_unverifiable_type (ctx, ctx->params [arg]);
if (ctx->params [arg]->byref && take_addr)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("ByRef of ByRef at 0x%04x", ctx->ip_offset));
top = stack_push (ctx);
if (!set_stack_value (ctx, top, ctx->params [arg], take_addr))
return;
if (arg == 0 && !(ctx->method->flags & METHOD_ATTRIBUTE_STATIC)) {
if (take_addr)
ctx->has_this_store = TRUE;
else
top->stype |= THIS_POINTER_MASK;
if (mono_method_is_constructor (ctx->method) && !ctx->super_ctor_called && !ctx->method->klass->valuetype)
top->stype |= UNINIT_THIS_MASK;
}
}
}
static void
push_local (VerifyContext *ctx, guint32 arg, int take_addr)
{
if (arg >= ctx->num_locals) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Method doesn't have local %d", arg + 1));
} else if (check_overflow (ctx)) {
/*We must let the value be pushed, otherwise we would get an underflow error*/
check_unverifiable_type (ctx, ctx->locals [arg]);
if (ctx->locals [arg]->byref && take_addr)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("ByRef of ByRef at 0x%04x", ctx->ip_offset));
set_stack_value (ctx, stack_push (ctx), ctx->locals [arg], take_addr);
}
}
static void
store_arg (VerifyContext *ctx, guint32 arg)
{
ILStackDesc *value;
if (arg >= ctx->max_args) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Method doesn't have argument %d at 0x%04x", arg + 1, ctx->ip_offset));
if (check_underflow (ctx, 1))
stack_pop (ctx);
return;
}
if (check_underflow (ctx, 1)) {
value = stack_pop (ctx);
if (!verify_stack_type_compatibility (ctx, ctx->params [arg], value)) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Incompatible type %s in argument store at 0x%04x", stack_slot_get_name (value), ctx->ip_offset));
}
}
if (arg == 0 && !(ctx->method->flags & METHOD_ATTRIBUTE_STATIC))
ctx->has_this_store = 1;
}
static void
store_local (VerifyContext *ctx, guint32 arg)
{
ILStackDesc *value;
if (arg >= ctx->num_locals) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Method doesn't have local var %d at 0x%04x", arg + 1, ctx->ip_offset));
return;
}
/*TODO verify definite assigment */
if (!check_underflow (ctx, 1))
return;
value = stack_pop (ctx);
if (ctx->locals [arg]->byref && stack_slot_is_managed_mutability_pointer (value))
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Cannot use a readonly managed reference when storing on a local variable at 0x%04x", ctx->ip_offset));
if (!verify_stack_type_compatibility (ctx, ctx->locals [arg], value)) {
char *expected = mono_type_full_name (ctx->locals [arg]);
char *found = stack_slot_full_name (value);
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Incompatible type '%s' on stack cannot be stored to local %d with type '%s' at 0x%04x",
found,
arg,
expected,
ctx->ip_offset));
g_free (expected);
g_free (found);
}
}
/*FIXME add and sub needs special care here*/
static void
do_binop (VerifyContext *ctx, unsigned int opcode, const unsigned char table [TYPE_MAX][TYPE_MAX])
{
ILStackDesc *a, *b, *top;
int idxa, idxb, complexMerge = 0;
unsigned char res;
if (!check_underflow (ctx, 2))
return;
b = stack_pop (ctx);
a = stack_pop (ctx);
idxa = stack_slot_get_underlying_type (a);
if (stack_slot_is_managed_pointer (a)) {
idxa = TYPE_PTR;
complexMerge = 1;
}
idxb = stack_slot_get_underlying_type (b);
if (stack_slot_is_managed_pointer (b)) {
idxb = TYPE_PTR;
complexMerge = 2;
}
--idxa;
--idxb;
res = table [idxa][idxb];
VERIFIER_DEBUG ( printf ("binop res %d\n", res); );
VERIFIER_DEBUG ( printf ("idxa %d idxb %d\n", idxa, idxb); );
top = stack_push (ctx);
if (res == TYPE_INV) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Binary instruction applyed to ill formed stack (%s x %s)", stack_slot_get_name (a), stack_slot_get_name (b)));
copy_stack_value (top, a);
return;
}
if (res & NON_VERIFIABLE_RESULT) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Binary instruction is not verifiable (%s x %s)", stack_slot_get_name (a), stack_slot_get_name (b)));
res = res & ~NON_VERIFIABLE_RESULT;
}
if (complexMerge && res == TYPE_PTR) {
if (complexMerge == 1)
copy_stack_value (top, a);
else if (complexMerge == 2)
copy_stack_value (top, b);
/*
* There is no need to merge the type of two pointers.
* The only valid operation is subtraction, that returns a native
* int as result and can be used with any 2 pointer kinds.
* This is valid acording to Patition III 1.1.4
*/
} else
top->stype = res;
}
static void
do_boolean_branch_op (VerifyContext *ctx, int delta)
{
int target = ctx->ip_offset + delta;
ILStackDesc *top;
VERIFIER_DEBUG ( printf ("boolean branch offset %d delta %d target %d\n", ctx->ip_offset, delta, target); );
if (target < 0 || target >= ctx->code_size) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Boolean branch target out of code at 0x%04x", ctx->ip_offset));
return;
}
switch (is_valid_branch_instruction (ctx->header, ctx->ip_offset, target)) {
case 1:
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Branch target escapes out of exception block at 0x%04x", ctx->ip_offset));
break;
case 2:
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Branch target escapes out of exception block at 0x%04x", ctx->ip_offset));
return;
}
ctx->target = target;
if (!check_underflow (ctx, 1))
return;
top = stack_pop (ctx);
if (!is_valid_bool_arg (top))
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Argument type %s not valid for brtrue/brfalse at 0x%04x", stack_slot_get_name (top), ctx->ip_offset));
check_unmanaged_pointer (ctx, top);
}
static gboolean
stack_slot_is_complex_type_not_reference_type (ILStackDesc *slot)
{
return stack_slot_get_type (slot) == TYPE_COMPLEX && !MONO_TYPE_IS_REFERENCE (slot->type) && !stack_slot_is_boxed_value (slot);
}
static gboolean
stack_slot_is_reference_value (ILStackDesc *slot)
{
return stack_slot_get_type (slot) == TYPE_COMPLEX && (MONO_TYPE_IS_REFERENCE (slot->type) || stack_slot_is_boxed_value (slot));
}
static void
do_branch_op (VerifyContext *ctx, signed int delta, const unsigned char table [TYPE_MAX][TYPE_MAX])
{
ILStackDesc *a, *b;
int idxa, idxb;
unsigned char res;
int target = ctx->ip_offset + delta;
VERIFIER_DEBUG ( printf ("branch offset %d delta %d target %d\n", ctx->ip_offset, delta, target); );
if (target < 0 || target >= ctx->code_size) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Branch target out of code at 0x%04x", ctx->ip_offset));
return;
}
switch (is_valid_cmp_branch_instruction (ctx->header, ctx->ip_offset, target)) {
case 1: /*FIXME use constants and not magic numbers.*/
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Branch target escapes out of exception block at 0x%04x", ctx->ip_offset));
break;
case 2:
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Branch target escapes out of exception block at 0x%04x", ctx->ip_offset));
return;
}
ctx->target = target;
if (!check_underflow (ctx, 2))
return;
b = stack_pop (ctx);
a = stack_pop (ctx);
idxa = stack_slot_get_underlying_type (a);
if (stack_slot_is_managed_pointer (a))
idxa = TYPE_PTR;
idxb = stack_slot_get_underlying_type (b);
if (stack_slot_is_managed_pointer (b))
idxb = TYPE_PTR;
if (stack_slot_is_complex_type_not_reference_type (a) || stack_slot_is_complex_type_not_reference_type (b)) {
res = TYPE_INV;
} else {
--idxa;
--idxb;
res = table [idxa][idxb];
}
VERIFIER_DEBUG ( printf ("branch res %d\n", res); );
VERIFIER_DEBUG ( printf ("idxa %d idxb %d\n", idxa, idxb); );
if (res == TYPE_INV) {
CODE_NOT_VERIFIABLE (ctx,
g_strdup_printf ("Compare and Branch instruction applyed to ill formed stack (%s x %s) at 0x%04x", stack_slot_get_name (a), stack_slot_get_name (b), ctx->ip_offset));
} else if (res & NON_VERIFIABLE_RESULT) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Compare and Branch instruction is not verifiable (%s x %s) at 0x%04x", stack_slot_get_name (a), stack_slot_get_name (b), ctx->ip_offset));
res = res & ~NON_VERIFIABLE_RESULT;
}
}
static void
do_cmp_op (VerifyContext *ctx, const unsigned char table [TYPE_MAX][TYPE_MAX], guint32 opcode)
{
ILStackDesc *a, *b;
int idxa, idxb;
unsigned char res;
if (!check_underflow (ctx, 2))
return;
b = stack_pop (ctx);
a = stack_pop (ctx);
if (opcode == CEE_CGT_UN) {
if ((stack_slot_is_reference_value (a) && stack_slot_is_null_literal (b)) ||
(stack_slot_is_reference_value (b) && stack_slot_is_null_literal (a))) {
stack_push_val (ctx, TYPE_I4, &mono_defaults.int32_class->byval_arg);
return;
}
}
idxa = stack_slot_get_underlying_type (a);
if (stack_slot_is_managed_pointer (a))
idxa = TYPE_PTR;
idxb = stack_slot_get_underlying_type (b);
if (stack_slot_is_managed_pointer (b))
idxb = TYPE_PTR;
if (stack_slot_is_complex_type_not_reference_type (a) || stack_slot_is_complex_type_not_reference_type (b)) {
res = TYPE_INV;
} else {
--idxa;
--idxb;
res = table [idxa][idxb];
}
if(res == TYPE_INV) {
char *left_type = stack_slot_full_name (a);
char *right_type = stack_slot_full_name (b);
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf("Compare instruction applyed to ill formed stack (%s x %s) at 0x%04x", left_type, right_type, ctx->ip_offset));
g_free (left_type);
g_free (right_type);
} else if (res & NON_VERIFIABLE_RESULT) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Compare instruction is not verifiable (%s x %s) at 0x%04x", stack_slot_get_name (a), stack_slot_get_name (b), ctx->ip_offset));
res = res & ~NON_VERIFIABLE_RESULT;
}
stack_push_val (ctx, TYPE_I4, &mono_defaults.int32_class->byval_arg);
}
static void
do_ret (VerifyContext *ctx)
{
MonoType *ret = ctx->signature->ret;
VERIFIER_DEBUG ( printf ("checking ret\n"); );
if (ret->type != MONO_TYPE_VOID) {
ILStackDesc *top;
if (!check_underflow (ctx, 1))
return;
top = stack_pop(ctx);
if (!verify_stack_type_compatibility (ctx, ctx->signature->ret, top)) {
char *ret_type = mono_type_full_name (ctx->signature->ret);
char *stack_type = stack_slot_full_name (top);
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Incompatible return value on stack with method signature, expected '%s' but got '%s' at 0x%04x", ret_type, stack_type, ctx->ip_offset));
g_free (stack_type);
g_free (ret_type);
return;
}
if (ret->byref || ret->type == MONO_TYPE_TYPEDBYREF || mono_type_is_value_type (ret, "System", "ArgIterator") || mono_type_is_value_type (ret, "System", "RuntimeArgumentHandle"))
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Method returns byref, TypedReference, ArgIterator or RuntimeArgumentHandle at 0x%04x", ctx->ip_offset));
}
if (ctx->eval.size > 0) {
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Stack not empty (%d) after ret at 0x%04x", ctx->eval.size, ctx->ip_offset));
}
if (in_any_block (ctx->header, ctx->ip_offset))
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("ret cannot escape exception blocks at 0x%04x", ctx->ip_offset));
}
/*
* FIXME we need to fix the case of a non-virtual instance method defined in the parent but call using a token pointing to a subclass.
* This is illegal but mono_get_method_full decoded it.
* TODO handle calling .ctor outside one or calling the .ctor for other class but super
*/
static void
do_invoke_method (VerifyContext *ctx, int method_token, gboolean virtual_)
{
MonoError error;
int param_count, i;
MonoMethodSignature *sig;
ILStackDesc *value;
MonoMethod *method;
gboolean virt_check_this = FALSE;
gboolean constrained = ctx->prefix_set & PREFIX_CONSTRAINED;
if (!(method = verifier_load_method (ctx, method_token, virtual_ ? "callvirt" : "call")))
return;
if (virtual_) {
CLEAR_PREFIX (ctx, PREFIX_CONSTRAINED);
if (method->klass->valuetype) // && !constrained ???
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Cannot use callvirtual with valuetype method at 0x%04x", ctx->ip_offset));
if ((method->flags & METHOD_ATTRIBUTE_STATIC))
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Cannot use callvirtual with static method at 0x%04x", ctx->ip_offset));
} else {
if (method->flags & METHOD_ATTRIBUTE_ABSTRACT)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Cannot use call with an abstract method at 0x%04x", ctx->ip_offset));
if ((method->flags & METHOD_ATTRIBUTE_VIRTUAL) && !(method->flags & METHOD_ATTRIBUTE_FINAL) && !(method->klass->flags & TYPE_ATTRIBUTE_SEALED)) {
virt_check_this = TRUE;
ctx->code [ctx->ip_offset].flags |= IL_CODE_CALL_NONFINAL_VIRTUAL;
}
}
if (!(sig = mono_method_get_signature_checked (method, ctx->image, method_token, ctx->generic_context, &error))) {
mono_error_cleanup (&error);
sig = mono_method_get_signature_checked (method, ctx->image, method_token, NULL, &error);
}
if (!sig) {
char *name = mono_type_get_full_name (method->klass);
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Could not resolve signature of %s:%s at 0x%04x due to: %s", name, method->name, ctx->ip_offset, mono_error_get_message (&error)));
mono_error_cleanup (&error);
g_free (name);
return;
}
param_count = sig->param_count + sig->hasthis;
if (!check_underflow (ctx, param_count))
return;
for (i = sig->param_count - 1; i >= 0; --i) {
VERIFIER_DEBUG ( printf ("verifying argument %d\n", i); );
value = stack_pop (ctx);
if (!verify_stack_type_compatibility (ctx, sig->params[i], value)) {
char *stack_name = stack_slot_full_name (value);
char *sig_name = mono_type_full_name (sig->params [i]);
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Incompatible parameter with function signature: Calling method with signature (%s) but for argument %d there is a (%s) on stack at 0x%04x", sig_name, i, stack_name, ctx->ip_offset));
g_free (stack_name);
g_free (sig_name);
}
if (stack_slot_is_managed_mutability_pointer (value))
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Cannot use a readonly pointer as argument of %s at 0x%04x", virtual_ ? "callvirt" : "call", ctx->ip_offset));
if ((ctx->prefix_set & PREFIX_TAIL) && stack_slot_is_managed_pointer (value)) {
ADD_VERIFY_ERROR (ctx, g_strdup_printf ("Cannot pass a byref argument to a tail %s at 0x%04x", virtual_ ? "callvirt" : "call", ctx->ip_offset));
return;
}
}
if (sig->hasthis) {
MonoType *type = &method->klass->byval_arg;
ILStackDesc copy;
if (mono_method_is_constructor (method) && !method->klass->valuetype) {
if (IS_STRICT_MODE (ctx) && !mono_method_is_constructor (ctx->method))
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Cannot call a constructor outside one at 0x%04x", ctx->ip_offset));
if (IS_STRICT_MODE (ctx) && method->klass != ctx->method->klass->parent && method->klass != ctx->method->klass)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Cannot call a constructor of a type different from this or super at 0x%04x", ctx->ip_offset));
ctx->super_ctor_called = TRUE;
value = stack_pop_safe (ctx);
if (IS_STRICT_MODE (ctx) && (value->stype & THIS_POINTER_MASK) != THIS_POINTER_MASK)
CODE_NOT_VERIFIABLE (ctx, g_strdup_printf ("Invalid 'this ptr' argument for constructor at 0x%04x", ctx->ip_offset));
} else {
value = stack_pop (ctx);
}
copy_stack_value (&copy, value);
//TODO we should extract this to a 'drop_byref_argument' and use everywhere
//Other parts of the code suffer from the same issue of
copy.type = mono_type_get_type_byval (copy.type);
copy.stype &= ~POINTER_MASK;