Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[mono][aot] Type load checks do not fail at compile time but produce a runtime exception #91261

Merged
merged 21 commits into from
Sep 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/mono/mono/metadata/jit-icall-reg.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
16 changes: 16 additions & 0 deletions src/mono/mono/mini/jit-icalls.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
2 changes: 2 additions & 0 deletions src/mono/mono/mini/jit-icalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
119 changes: 111 additions & 8 deletions src/mono/mono/mini/method-to-ir.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 {\
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it necessary to remove dead blocks between the bb_init and bb_exit which may not have a direct link to them?

Copy link
Member Author

@jandupej jandupej Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly not; it seems that unreachable blocks are detected (and removed) without my explicitly doing it. This is the first iteration that seems to work locally, waiting for CI. I may be able to simplify it if CI turns out favorable.

EDIT: Are you aware of any unlinked blocks that should be kept?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change looks good to me. I was just curious whether the unreachable blocks are detected and removed.

Is it possible to skip the bb_init and link the bb_entry with the throw block?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had control flow issues when generating LLVM IR without the init block (which was empty no less). I'll see if it can be done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An alternative is to restart the compilation, there is already a mechanism to do that if llvm fails, etc. and emit the exception without compiling the rest of the IL.

// 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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
jandupej marked this conversation as resolved.
Show resolved Hide resolved
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))
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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_:
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/mono/mono/mini/mini-runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
9 changes: 7 additions & 2 deletions src/mono/mono/mini/mini.c
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)))
Expand Down
9 changes: 0 additions & 9 deletions src/tests/issues.targets
Original file line number Diff line number Diff line change
Expand Up @@ -2405,15 +2405,6 @@
<ExcludeList Include="$(XunitTestBinBase)/Loader/classloader/explicitlayout/objrefandnonobjrefoverlap/case9/**">
<Issue>expected failure: overlapped structs fail at AOT compile time, not runtime</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/Loader/classloader/explicitlayout/NestedStructs/case03/**">
<Issue>expected failure: overlapped structs fail at AOT compile time, not runtime</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/Loader/classloader/explicitlayout/NestedStructs/case04/**">
<Issue>expected failure: overlapped structs fail at AOT compile time, not runtime</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/Loader/classloader/explicitlayout/NestedStructs/case05/**">
<Issue>expected failure: overlapped structs fail at AOT compile time, not runtime</Issue>
</ExcludeList>
<ExcludeList Include = "$(XunitTestBinBase)/Loader/classloader/RefFields/Validate/**">
<Issue>expected failure: unsupported type with ref field fails at AOT compile time, not runtime</Issue>
</ExcludeList>
Expand Down
Loading