1// Licensed to the .NET Foundation under one or more agreements.
2// The .NET Foundation licenses this file to you under the MIT license.
3// See the LICENSE file in the project root for more information.
4
5/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
6XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
7XX XX
8XX error.cpp XX
9XX XX
10XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
11XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
12*/
13
14#include "jitpch.h"
15#ifdef _MSC_VER
16#pragma hdrstop
17#endif
18#include "compiler.h"
19
20#if MEASURE_FATAL
21unsigned fatal_badCode;
22unsigned fatal_noWay;
23unsigned fatal_NOMEM;
24unsigned fatal_noWayAssertBody;
25#ifdef DEBUG
26unsigned fatal_noWayAssertBodyArgs;
27#endif // DEBUG
28unsigned fatal_NYI;
29#endif // MEASURE_FATAL
30
31/*****************************************************************************/
32void DECLSPEC_NORETURN fatal(int errCode)
33{
34#ifdef DEBUG
35 if (errCode != CORJIT_SKIPPED) // Don't stop on NYI: use COMPlus_AltJitAssertOnNYI for that.
36 {
37 if (JitConfig.DebugBreakOnVerificationFailure())
38 {
39 DebugBreak();
40 }
41 }
42#endif // DEBUG
43
44 ULONG_PTR exceptArg = errCode;
45 RaiseException(FATAL_JIT_EXCEPTION, EXCEPTION_NONCONTINUABLE, 1, &exceptArg);
46 UNREACHABLE();
47}
48
49/*****************************************************************************/
50void DECLSPEC_NORETURN badCode()
51{
52#if MEASURE_FATAL
53 fatal_badCode += 1;
54#endif // MEASURE_FATAL
55
56 fatal(CORJIT_BADCODE);
57}
58
59/*****************************************************************************/
60void DECLSPEC_NORETURN noWay()
61{
62#if MEASURE_FATAL
63 fatal_noWay += 1;
64#endif // MEASURE_FATAL
65
66 fatal(CORJIT_INTERNALERROR);
67}
68
69/*****************************************************************************/
70void DECLSPEC_NORETURN NOMEM()
71{
72#if MEASURE_FATAL
73 fatal_NOMEM += 1;
74#endif // MEASURE_FATAL
75
76 fatal(CORJIT_OUTOFMEM);
77}
78
79/*****************************************************************************/
80void DECLSPEC_NORETURN noWayAssertBody()
81{
82#if MEASURE_FATAL
83 fatal_noWayAssertBody += 1;
84#endif // MEASURE_FATAL
85
86#ifndef DEBUG
87 // Even in retail, if we hit a noway, and we have this variable set, we don't want to fall back
88 // to MinOpts, which might hide a regression. Instead, hit a breakpoint (and crash). We don't
89 // have the assert code to fall back on here.
90 // The debug path goes through this function also, to do the call to 'fatal'.
91 // This kind of noway is hit for unreached().
92 if (JitConfig.JitEnableNoWayAssert())
93 {
94 DebugBreak();
95 }
96#endif // !DEBUG
97
98 fatal(CORJIT_RECOVERABLEERROR);
99}
100
101inline static bool ShouldThrowOnNoway(
102#ifdef FEATURE_TRACELOGGING
103 const char* filename, unsigned line
104#endif
105 )
106{
107 return JitTls::GetCompiler() == nullptr ||
108 JitTls::GetCompiler()->compShouldThrowOnNoway(
109#ifdef FEATURE_TRACELOGGING
110 filename, line
111#endif
112 );
113}
114
115/*****************************************************************************/
116void noWayAssertBodyConditional(
117#ifdef FEATURE_TRACELOGGING
118 const char* filename, unsigned line
119#endif
120 )
121{
122#ifdef FEATURE_TRACELOGGING
123 if (ShouldThrowOnNoway(filename, line))
124#else
125 if (ShouldThrowOnNoway())
126#endif // FEATURE_TRACELOGGING
127 {
128 noWayAssertBody();
129 }
130}
131
132#if defined(ALT_JIT)
133
134/*****************************************************************************/
135void notYetImplemented(const char* msg, const char* filename, unsigned line)
136{
137#if FUNC_INFO_LOGGING
138#ifdef DEBUG
139 LogEnv* env = JitTls::GetLogEnv();
140 if (env != nullptr)
141 {
142 const Compiler* const pCompiler = env->compiler;
143 if (pCompiler->verbose)
144 {
145 printf("\n\n%s - NYI (%s:%d - %s)\n", pCompiler->info.compFullName, filename, line, msg);
146 }
147 }
148 if (Compiler::compJitFuncInfoFile != nullptr)
149 {
150 fprintf(Compiler::compJitFuncInfoFile, "%s - NYI (%s:%d - %s)\n",
151 (env == nullptr) ? "UNKNOWN" : env->compiler->info.compFullName, filename, line, msg);
152 fflush(Compiler::compJitFuncInfoFile);
153 }
154#else // !DEBUG
155 if (Compiler::compJitFuncInfoFile != nullptr)
156 {
157 fprintf(Compiler::compJitFuncInfoFile, "NYI (%s:%d - %s)\n", filename, line, msg);
158 fflush(Compiler::compJitFuncInfoFile);
159 }
160#endif // !DEBUG
161#endif // FUNC_INFO_LOGGING
162
163#ifdef DEBUG
164 Compiler* pCompiler = JitTls::GetCompiler();
165 if (pCompiler != nullptr)
166 {
167 // Assume we're within a compFunctionTrace boundary, which might not be true.
168 pCompiler->compFunctionTraceEnd(nullptr, 0, true);
169 }
170#endif // DEBUG
171
172 DWORD value = JitConfig.AltJitAssertOnNYI();
173
174 // 0 means just silently skip
175 // If we are in retail builds, assume ignore
176 // 1 means popup the assert (abort=abort, retry=debugger, ignore=skip)
177 // 2 means silently don't skip (same as 3 for retail)
178 // 3 means popup the assert (abort=abort, retry=debugger, ignore=don't skip)
179 if (value & 1)
180 {
181#ifdef DEBUG
182 assertAbort(msg, filename, line);
183#endif
184 }
185
186 if ((value & 2) == 0)
187 {
188#if MEASURE_FATAL
189 fatal_NYI += 1;
190#endif // MEASURE_FATAL
191
192 fatal(CORJIT_SKIPPED);
193 }
194}
195
196#endif // #if defined(ALT_JIT)
197
198/*****************************************************************************/
199LONG __JITfilter(PEXCEPTION_POINTERS pExceptionPointers, LPVOID lpvParam)
200{
201 DWORD exceptCode = pExceptionPointers->ExceptionRecord->ExceptionCode;
202
203 if (exceptCode == FATAL_JIT_EXCEPTION)
204 {
205 ErrorTrapParam* pParam = (ErrorTrapParam*)lpvParam;
206
207 assert(pExceptionPointers->ExceptionRecord->NumberParameters == 1);
208 pParam->errc = (int)pExceptionPointers->ExceptionRecord->ExceptionInformation[0];
209
210 ICorJitInfo* jitInfo = pParam->jitInfo;
211
212 if (jitInfo != nullptr)
213 {
214 jitInfo->reportFatalError((CorJitResult)pParam->errc);
215 }
216
217 return EXCEPTION_EXECUTE_HANDLER;
218 }
219
220 return EXCEPTION_CONTINUE_SEARCH;
221}
222
223/*****************************************************************************/
224#ifdef DEBUG
225
226DWORD getBreakOnBadCode()
227{
228 return JitConfig.JitBreakOnBadCode();
229}
230
231/*****************************************************************************/
232void debugError(const char* msg, const char* file, unsigned line)
233{
234 const char* tail = strrchr(file, '\\');
235 if (tail != nullptr)
236 {
237 tail = tail + 1;
238 }
239 else
240 {
241 tail = file;
242 }
243
244 LogEnv* env = JitTls::GetLogEnv();
245
246 logf(LL_ERROR, "COMPILATION FAILED: file: %s:%d compiling method %s reason %s\n", tail, line,
247 env->compiler->info.compFullName, msg);
248
249 // We now only assert when user explicitly set ComPlus_JitRequired=1
250 // If ComPlus_JitRequired is 0 or is not set, we will not assert.
251 if (JitConfig.JitRequired() == 1 || getBreakOnBadCode())
252 {
253 // Don't assert if verification is done.
254 if (!env->compiler->tiVerificationNeeded || getBreakOnBadCode())
255 {
256 assertAbort(msg, file, line);
257 }
258 }
259
260 BreakIfDebuggerPresent();
261}
262
263/*****************************************************************************/
264LogEnv::LogEnv(ICorJitInfo* aCompHnd) : compHnd(aCompHnd), compiler(nullptr)
265{
266}
267
268/*****************************************************************************/
269extern "C" void __cdecl assertAbort(const char* why, const char* file, unsigned line)
270{
271 const char* msg = why;
272 LogEnv* env = JitTls::GetLogEnv();
273 const int BUFF_SIZE = 8192;
274 char* buff = (char*)alloca(BUFF_SIZE);
275 if (env->compiler)
276 {
277 _snprintf_s(buff, BUFF_SIZE, _TRUNCATE, "Assertion failed '%s' in '%s' (IL size %d)\n", why,
278 env->compiler->info.compFullName, env->compiler->info.compILCodeSize);
279 msg = buff;
280 }
281 printf(""); // null string means flush
282
283#if FUNC_INFO_LOGGING
284 if (Compiler::compJitFuncInfoFile != nullptr)
285 {
286 fprintf(Compiler::compJitFuncInfoFile, "%s - Assertion failed (%s:%d - %s)\n",
287 (env == nullptr) ? "UNKNOWN" : env->compiler->info.compFullName, file, line, why);
288 }
289#endif // FUNC_INFO_LOGGING
290
291 if (env->compHnd->doAssert(file, line, msg))
292 {
293 DebugBreak();
294 }
295
296#ifdef ALT_JIT
297 // If we hit an assert, and we got here, it's either because the user hit "ignore" on the
298 // dialog pop-up, or they set COMPlus_ContinueOnAssert=1 to not emit a pop-up, but just continue.
299 // If we're an altjit, we have two options: (1) silently continue, as a normal JIT would, probably
300 // leading to additional asserts, or (2) tell the VM that the AltJit wants to skip this function,
301 // thus falling back to the fallback JIT. Setting COMPlus_AltJitSkipOnAssert=1 chooses this "skip"
302 // to the fallback JIT behavior. This is useful when doing ASM diffs, where we only want to see
303 // the first assert for any function, but we don't want to kill the whole ngen process on the
304 // first assert (which would happen if you used COMPlus_NoGuiOnAssert=1 for example).
305 if (JitConfig.AltJitSkipOnAssert() != 0)
306 {
307 fatal(CORJIT_SKIPPED);
308 }
309#endif
310}
311
312/*********************************************************************/
313BOOL vlogf(unsigned level, const char* fmt, va_list args)
314{
315 return JitTls::GetLogEnv()->compHnd->logMsg(level, fmt, args);
316}
317
318int vflogf(FILE* file, const char* fmt, va_list args)
319{
320 // 0-length string means flush
321 if (fmt[0] == '\0')
322 {
323 fflush(file);
324 return 0;
325 }
326
327 const int BUFF_SIZE = 8192;
328 char buffer[BUFF_SIZE];
329 int written = _vsnprintf_s(&buffer[0], BUFF_SIZE, _TRUNCATE, fmt, args);
330
331 if (JitConfig.JitDumpToDebugger())
332 {
333 OutputDebugStringA(buffer);
334 }
335
336 // We use fputs here so that this executes as fast a possible
337 fputs(&buffer[0], file);
338 return written;
339}
340
341int flogf(FILE* file, const char* fmt, ...)
342{
343 va_list args;
344 va_start(args, fmt);
345 int written = vflogf(file, fmt, args);
346 va_end(args);
347 return written;
348}
349
350/*********************************************************************/
351int logf(const char* fmt, ...)
352{
353 va_list args;
354 static bool logToEEfailed = false;
355 int written = 0;
356 //
357 // We remember when the EE failed to log, because vlogf()
358 // is very slow in a checked build.
359 //
360 // If it fails to log an LL_INFO1000 message once
361 // it will always fail when logging an LL_INFO1000 message.
362 //
363 if (!logToEEfailed)
364 {
365 va_start(args, fmt);
366 if (!vlogf(LL_INFO1000, fmt, args))
367 {
368 logToEEfailed = true;
369 }
370 va_end(args);
371 }
372
373 if (logToEEfailed)
374 {
375 // if the EE refuses to log it, we try to send it to stdout
376 va_start(args, fmt);
377 written = vflogf(jitstdout, fmt, args);
378 va_end(args);
379 }
380#if 0 // Enable this only when you need it
381 else
382 {
383 //
384 // The EE just successfully logged our message
385 //
386 static ConfigDWORD fJitBreakOnDumpToken;
387 DWORD breakOnDumpToken = fJitBreakOnDumpToken.val(CLRConfig::INTERNAL_BreakOnDumpToken);
388 static DWORD forbidEntry = 0;
389
390 if ((breakOnDumpToken != 0xffffffff) && (forbidEntry == 0))
391 {
392 forbidEntry = 1;
393
394 // Use value of 0 to get the dump
395 static DWORD currentLine = 1;
396
397 if (currentLine == breakOnDumpToken)
398 {
399 assert(!"Dump token reached");
400 }
401
402 printf("(Token=0x%x) ", currentLine++);
403 forbidEntry = 0;
404 }
405 }
406#endif // 0
407 va_end(args);
408
409 return written;
410}
411
412/*********************************************************************/
413void gcDump_logf(const char* fmt, ...)
414{
415 va_list args;
416 static bool logToEEfailed = false;
417 //
418 // We remember when the EE failed to log, because vlogf()
419 // is very slow in a checked build.
420 //
421 // If it fails to log an LL_INFO1000 message once
422 // it will always fail when logging an LL_INFO1000 message.
423 //
424 if (!logToEEfailed)
425 {
426 va_start(args, fmt);
427 if (!vlogf(LL_INFO1000, fmt, args))
428 {
429 logToEEfailed = true;
430 }
431 va_end(args);
432 }
433
434 if (logToEEfailed)
435 {
436 // if the EE refuses to log it, we try to send it to stdout
437 va_start(args, fmt);
438 vflogf(jitstdout, fmt, args);
439 va_end(args);
440 }
441#if 0 // Enable this only when you need it
442 else
443 {
444 //
445 // The EE just successfully logged our message
446 //
447 static ConfigDWORD fJitBreakOnDumpToken;
448 DWORD breakOnDumpToken = fJitBreakOnDumpToken.val(CLRConfig::INTERNAL_BreakOnDumpToken);
449 static DWORD forbidEntry = 0;
450
451 if ((breakOnDumpToken != 0xffffffff) && (forbidEntry == 0))
452 {
453 forbidEntry = 1;
454
455 // Use value of 0 to get the dump
456 static DWORD currentLine = 1;
457
458 if (currentLine == breakOnDumpToken)
459 {
460 assert(!"Dump token reached");
461 }
462
463 printf("(Token=0x%x) ", currentLine++);
464 forbidEntry = 0;
465 }
466 }
467#endif // 0
468 va_end(args);
469}
470
471/*********************************************************************/
472void logf(unsigned level, const char* fmt, ...)
473{
474 va_list args;
475 va_start(args, fmt);
476 vlogf(level, fmt, args);
477 va_end(args);
478}
479
480void DECLSPEC_NORETURN badCode3(const char* msg, const char* msg2, int arg, __in_z const char* file, unsigned line)
481{
482 const int BUFF_SIZE = 512;
483 char buf1[BUFF_SIZE];
484 char buf2[BUFF_SIZE];
485 sprintf_s(buf1, BUFF_SIZE, "%s%s", msg, msg2);
486 sprintf_s(buf2, BUFF_SIZE, buf1, arg);
487
488 debugError(buf2, file, line);
489 badCode();
490}
491
492void noWayAssertAbortHelper(const char* cond, const char* file, unsigned line)
493{
494 // Show the assert UI.
495 if (JitConfig.JitEnableNoWayAssert())
496 {
497 assertAbort(cond, file, line);
498 }
499}
500
501void noWayAssertBodyConditional(const char* cond, const char* file, unsigned line)
502{
503#ifdef FEATURE_TRACELOGGING
504 if (ShouldThrowOnNoway(file, line))
505#else
506 if (ShouldThrowOnNoway())
507#endif
508 {
509 noWayAssertBody(cond, file, line);
510 }
511 // In CHK we want the assert UI to show up in min-opts.
512 else
513 {
514 noWayAssertAbortHelper(cond, file, line);
515 }
516}
517
518void DECLSPEC_NORETURN noWayAssertBody(const char* cond, const char* file, unsigned line)
519{
520#if MEASURE_FATAL
521 fatal_noWayAssertBodyArgs += 1;
522#endif // MEASURE_FATAL
523
524 noWayAssertAbortHelper(cond, file, line);
525 noWayAssertBody();
526}
527
528#endif // DEBUG
529