diff --git a/src/mono/mono/metadata/jit-icall-reg.h b/src/mono/mono/metadata/jit-icall-reg.h index 02e30426578fb..2ac1bc9fb05d7 100644 --- a/src/mono/mono/metadata/jit-icall-reg.h +++ b/src/mono/mono/metadata/jit-icall-reg.h @@ -312,6 +312,7 @@ MONO_JIT_ICALL (mono_throw_bad_image) \ MONO_JIT_ICALL (mono_throw_not_supported) \ MONO_JIT_ICALL (mono_throw_platform_not_supported) \ MONO_JIT_ICALL (mono_throw_invalid_program) \ +MONO_JIT_ICALL (mono_throw_type_load) \ MONO_JIT_ICALL (mono_trace_enter_method) \ MONO_JIT_ICALL (mono_trace_leave_method) \ MONO_JIT_ICALL (mono_trace_tail_method) \ diff --git a/src/mono/mono/mini/jit-icalls.c b/src/mono/mono/mini/jit-icalls.c index 628d18abe35cf..15d78d56a271e 100644 --- a/src/mono/mono/mini/jit-icalls.c +++ b/src/mono/mono/mini/jit-icalls.c @@ -1688,6 +1688,22 @@ mono_throw_invalid_program (const char *msg) mono_error_set_pending_exception (error); } +void +mono_throw_type_load (MonoClass* klass) +{ + ERROR_DECL (error); + + if (G_UNLIKELY(!klass)) { + mono_error_set_generic_error (error, "System", "TypeLoadException", ""); + } else { + char* klass_name = mono_type_get_full_name (klass); + mono_error_set_type_load_class (error, klass, "Attempting to load invalid type '%s'.", klass_name); + g_free (klass_name); + } + + mono_error_set_pending_exception (error); +} + void mono_dummy_jit_icall (void) { diff --git a/src/mono/mono/mini/jit-icalls.h b/src/mono/mono/mini/jit-icalls.h index db59a7608c8da..6e9ea58124e1e 100644 --- a/src/mono/mono/mini/jit-icalls.h +++ b/src/mono/mono/mini/jit-icalls.h @@ -234,6 +234,8 @@ ICALL_EXPORT void mono_throw_platform_not_supported (void); ICALL_EXPORT void mono_throw_invalid_program (const char *msg); +ICALL_EXPORT void mono_throw_type_load (MonoClass* klass); + ICALL_EXPORT void mono_dummy_jit_icall (void); ICALL_EXPORT void mono_dummy_jit_icall_val (gpointer ptr); diff --git a/src/mono/mono/mini/method-to-ir.c b/src/mono/mono/mini/method-to-ir.c index e3605981bafcd..346cea073533f 100644 --- a/src/mono/mono/mini/method-to-ir.c +++ b/src/mono/mono/mini/method-to-ir.c @@ -167,7 +167,7 @@ mono_tailcall_print (const char *format, ...) #define TYPE_LOAD_ERROR(klass) do { \ cfg->exception_ptr = klass; \ - LOAD_ERROR; \ + LOAD_ERROR; \ } while (0) #define CHECK_CFG_ERROR do {\ @@ -2303,6 +2303,18 @@ emit_not_supported_failure (MonoCompile *cfg) mono_emit_jit_icall (cfg, mono_throw_not_supported, NULL); } +static void +emit_type_load_failure (MonoCompile* cfg, MonoClass* klass) +{ + MonoInst* iargs[1]; + if (G_LIKELY (klass)) { + EMIT_NEW_CLASSCONST (cfg, iargs [0], klass); + } else { + EMIT_NEW_PCONST (cfg, iargs [0], NULL); + } + mono_emit_jit_icall (cfg, mono_throw_type_load, iargs); +} + static void emit_invalid_program_with_msg (MonoCompile *cfg, MonoError *error_msg, MonoMethod *caller, MonoMethod *callee) { @@ -4963,6 +4975,15 @@ inline_method (MonoCompile *cfg, MonoMethod *cmethod, MonoMethodSignature *fsig, #define CHECK_UNVERIFIABLE(cfg) if (cfg->unverifiable) UNVERIFIED #define CHECK_TYPELOAD(klass) if (!(klass) || mono_class_has_failure (klass)) TYPE_LOAD_ERROR ((klass)) +#define CLEAR_TYPELOAD_EXCEPTION(cfg) if (cfg->exception_type == MONO_EXCEPTION_TYPE_LOAD) { clear_cfg_error (cfg); cfg->exception_type = MONO_EXCEPTION_NONE; } +#define CLASS_HAS_FAILURE(klass) (!(klass) || mono_class_has_failure (klass)) +#define HANDLE_TYPELOAD_ERROR(cfg,klass) do { \ + if (!cfg->compile_aot) \ + TYPE_LOAD_ERROR ((klass)); \ + emit_type_load_failure (cfg, klass); \ + CLEAR_TYPELOAD_EXCEPTION (cfg); \ + } while (0) + /* offset from br.s -> br like opcodes */ #define BIG_BRANCH_OFFSET 13 @@ -5438,7 +5459,9 @@ emit_optimized_ldloca_ir (MonoCompile *cfg, guchar *ip, guchar *end, int local) if ((ip = il_read_initobj (ip, end, &token)) && ip_in_bb (cfg, cfg->cbb, start + 1)) { /* From the INITOBJ case */ klass = mini_get_class (cfg->current_method, token, cfg->generic_context); - CHECK_TYPELOAD (klass); + if (CLASS_HAS_FAILURE (klass)) { + HANDLE_TYPELOAD_ERROR (cfg, klass); + } type = mini_get_underlying_type (m_class_get_byval_arg (klass)); emit_init_local (cfg, local, type, TRUE); return ip; @@ -6170,6 +6193,49 @@ emit_llvmonly_interp_entry (MonoCompile *cfg, MonoMethodHeader *header) link_bblock (cfg, cfg->cbb, cfg->bb_exit); } +static void +method_make_alwaysthrow_typeloadfailure (MonoCompile* cfg, MonoClass* klass) +{ + // Get rid of all out-BBs from the entry BB. (all but init BB) + for (gint16 i = cfg->bb_entry->out_count - 1; i >= 0; i--) { + if (cfg->bb_entry->out_bb [i] != cfg->bb_init) { + mono_unlink_bblock (cfg, cfg->bb_entry, cfg->bb_entry->out_bb [i]); + mono_remove_bblock (cfg, cfg->bb_entry->out_bb [i]); + } + } + + // Discard all out-BBs from the init BB. + for (gint16 i = cfg->bb_init->out_count - 1; i >= 0; i--) { + if (cfg->bb_init->out_bb [i] != cfg->bb_exit) { + mono_unlink_bblock (cfg, cfg->bb_init, cfg->bb_init->out_bb [i]); + mono_remove_bblock (cfg, cfg->bb_init->out_bb [i]); + } + } + + // Maintain linked list consistency. This BB should have been added as the last, + // ignoring the ones that held actual method code. + cfg->cbb = cfg->bb_init; + + // Create a new BB that only throws, link it after the entry. + MonoBasicBlock* bb; + NEW_BBLOCK (cfg, bb); + bb->cil_code = NULL; + bb->cil_length = 0; + cfg->cbb->next_bb = bb; + cfg->cbb = bb; + + emit_type_load_failure (cfg, klass); + MonoInst* ins; + MONO_INST_NEW (cfg, ins, OP_NOT_REACHED); + MONO_ADD_INS (cfg->cbb, ins); + + ADD_BBLOCK (cfg, bb); + mono_link_bblock (cfg, cfg->bb_init, bb); + mono_link_bblock (cfg, bb, cfg->bb_exit); + + cfg->disable_inline = TRUE; +} + typedef union _MonoOpcodeParameter { gint32 i32; gint64 i64; @@ -9871,6 +9937,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b MonoType *ftype; MonoInst *store_val = NULL; MonoInst *thread_ins; + ins = NULL; is_instance = (il_op == MONO_CEE_LDFLD || il_op == MONO_CEE_LDFLDA || il_op == MONO_CEE_STFLD); if (is_instance) { @@ -9898,8 +9965,28 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b else { klass = NULL; field = mono_field_from_token_checked (image, token, &klass, generic_context, cfg->error); - if (!field) - CHECK_TYPELOAD (klass); + if (!field || CLASS_HAS_FAILURE (klass)) { + HANDLE_TYPELOAD_ERROR (cfg, klass); + + // Reached only in AOT. Cannot turn a token into a class. We silence the compilation error + // and generate a runtime exception. + if (cfg->error->error_code == MONO_ERROR_BAD_IMAGE) + clear_cfg_error (cfg); + + // We need to push a dummy value onto the stack, respecting the intended type. + if (il_op == MONO_CEE_LDFLDA || il_op == MONO_CEE_LDSFLDA) { + // Address is expected, push a null pointer. + EMIT_NEW_PCONST (cfg, *sp, NULL); + sp++; + } else if (il_op == MONO_CEE_LDFLD || il_op == MONO_CEE_LDSFLD) { + // An object is expected here. It may be impossible to correctly infer its type, + // we turn this entire method into a throw. + method_make_alwaysthrow_typeloadfailure (cfg, klass); + goto all_bbs_done; + } + + break; + } CHECK_CFG_ERROR; } if (!dont_verify && !cfg->skip_visibility && !mono_method_can_access_field (method, field)) @@ -10365,8 +10452,11 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b } if (!is_const) { - MonoInst *load; + // This can happen in case of type load error. + if (!ins) + EMIT_NEW_PCONST (cfg, ins, 0); + MonoInst *load; EMIT_NEW_LOAD_MEMBASE_TYPE (cfg, load, field->type, ins->dreg, 0); load->flags |= ins_flag; *sp++ = load; @@ -11853,13 +11943,20 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b inline_costs += 100000; break; case MONO_CEE_INITOBJ: - --sp; klass = mini_get_class (method, token, generic_context); - CHECK_TYPELOAD (klass); + if (CLASS_HAS_FAILURE (klass)) { + HANDLE_TYPELOAD_ERROR (cfg, klass); + inline_costs += 10; + break; // reached only in AOT + } + + --sp; + if (mini_class_is_reference (klass)) MONO_EMIT_NEW_STORE_MEMBASE_IMM (cfg, OP_STORE_MEMBASE_IMM, sp [0]->dreg, 0, 0); else mini_emit_initobj (cfg, *sp, NULL, klass); + inline_costs += 1; break; case MONO_CEE_CONSTRAINED_: @@ -11949,7 +12046,12 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b EMIT_NEW_ICONST (cfg, ins, val); } else { klass = mini_get_class (method, token, generic_context); - CHECK_TYPELOAD (klass); + if (CLASS_HAS_FAILURE (klass)) { + HANDLE_TYPELOAD_ERROR (cfg, klass); + EMIT_NEW_ICONST(cfg, ins, 0); + *sp++ = ins; + break; + } if (mini_is_gsharedvt_klass (klass)) { ins = mini_emit_get_gsharedvt_info_klass (cfg, klass, MONO_RGCTX_INFO_CLASS_SIZEOF); @@ -12002,6 +12104,7 @@ mono_method_to_ir (MonoCompile *cfg, MonoMethod *method, MonoBasicBlock *start_b } if (start_new_bblock != 1) UNVERIFIED; +all_bbs_done: cfg->cbb->cil_length = GPTRDIFF_TO_INT32 (ip - cfg->cbb->cil_code); if (cfg->cbb->next_bb) { diff --git a/src/mono/mono/mini/mini-runtime.c b/src/mono/mono/mini/mini-runtime.c index d075623de8cf7..5298e1b492336 100644 --- a/src/mono/mono/mini/mini-runtime.c +++ b/src/mono/mono/mini/mini-runtime.c @@ -5139,6 +5139,7 @@ register_icalls (void) register_icall (mono_throw_not_supported, mono_icall_sig_void, FALSE); register_icall (mono_throw_platform_not_supported, mono_icall_sig_void, FALSE); register_icall (mono_throw_invalid_program, mono_icall_sig_void_ptr, FALSE); + register_icall (mono_throw_type_load, mono_icall_sig_void_ptr, FALSE); register_icall_no_wrapper (mono_dummy_jit_icall, mono_icall_sig_void); //register_icall_no_wrapper (mono_dummy_jit_icall_val, mono_icall_sig_void_ptr); register_icall_no_wrapper (mini_init_method_rgctx, mono_icall_sig_void_ptr_ptr); diff --git a/src/mono/mono/mini/mini.c b/src/mono/mono/mini/mini.c index f96f13e32cc7c..4674a669b1478 100644 --- a/src/mono/mono/mini/mini.c +++ b/src/mono/mono/mini/mini.c @@ -660,7 +660,9 @@ mono_compile_create_var_for_vreg (MonoCompile *cfg, MonoType *type, int opcode, inst->backend.is_pinvoke = 0; inst->dreg = vreg; - if (mono_class_has_failure (inst->klass)) + // In AOT, we do not set the exception so that the compilation can succeed. To indicate + // the error, an exception is thrown in run-time. + if (!cfg->compile_aot && mono_class_has_failure (inst->klass)) mono_cfg_set_exception (cfg, MONO_EXCEPTION_TYPE_LOAD); if (cfg->compute_gc_maps) { @@ -1351,7 +1353,10 @@ mono_allocate_stack_slots (MonoCompile *cfg, gboolean backward, guint32 *stack_s size = mini_type_stack_size (t, &ialign); align = ialign; - if (mono_class_has_failure (mono_class_from_mono_type_internal (t))) + // In AOT, we do not set the exception but allow the compilation to succeed. The error will be + // indicated in runtime by throwing an exception when an operation with the invalid object is + // attempted. + if (!cfg->compile_aot && mono_class_has_failure (mono_class_from_mono_type_internal (t))) mono_cfg_set_exception (cfg, MONO_EXCEPTION_TYPE_LOAD); if (mini_class_is_simd (cfg, mono_class_from_mono_type_internal (t))) diff --git a/src/tests/issues.targets b/src/tests/issues.targets index e142ff1292411..efcd8d4628e89 100644 --- a/src/tests/issues.targets +++ b/src/tests/issues.targets @@ -2405,15 +2405,6 @@ expected failure: overlapped structs fail at AOT compile time, not runtime - - expected failure: overlapped structs fail at AOT compile time, not runtime - - - expected failure: overlapped structs fail at AOT compile time, not runtime - - - expected failure: overlapped structs fail at AOT compile time, not runtime - expected failure: unsupported type with ref field fails at AOT compile time, not runtime