|  | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ | 
|  |  | 
|  | /* This Source Code Form is subject to the terms of the Mozilla Public | 
|  | * License, v. 2.0. If a copy of the MPL was not distributed with this | 
|  | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | 
|  |  | 
|  | #include "primpl.h" | 
|  | #include "prenv.h" | 
|  | #include "prprf.h" | 
|  | #include <string.h> | 
|  | #ifdef ANDROID | 
|  | #include <android/log.h> | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * Lock used to lock the log. | 
|  | * | 
|  | * We can't define _PR_LOCK_LOG simply as PR_Lock because PR_Lock may | 
|  | * contain assertions.  We have to avoid assertions in _PR_LOCK_LOG | 
|  | * because PR_ASSERT calls PR_LogPrint, which in turn calls _PR_LOCK_LOG. | 
|  | * This can lead to infinite recursion. | 
|  | */ | 
|  | static PRLock *_pr_logLock; | 
|  | #if defined(_PR_PTHREADS) || defined(_PR_BTHREADS) | 
|  | #define _PR_LOCK_LOG() PR_Lock(_pr_logLock); | 
|  | #define _PR_UNLOCK_LOG() PR_Unlock(_pr_logLock); | 
|  | #elif defined(_PR_GLOBAL_THREADS_ONLY) | 
|  | #define _PR_LOCK_LOG() { _PR_LOCK_LOCK(_pr_logLock) | 
|  | #define _PR_UNLOCK_LOG() _PR_LOCK_UNLOCK(_pr_logLock); } | 
|  | #else | 
|  |  | 
|  | #define _PR_LOCK_LOG() \ | 
|  | { \ | 
|  | PRIntn _is; \ | 
|  | PRThread *_me = _PR_MD_CURRENT_THREAD(); \ | 
|  | if (!_PR_IS_NATIVE_THREAD(_me)) \ | 
|  | _PR_INTSOFF(_is); \ | 
|  | _PR_LOCK_LOCK(_pr_logLock) | 
|  |  | 
|  | #define _PR_UNLOCK_LOG() \ | 
|  | _PR_LOCK_UNLOCK(_pr_logLock); \ | 
|  | PR_ASSERT(_me == _PR_MD_CURRENT_THREAD()); \ | 
|  | if (!_PR_IS_NATIVE_THREAD(_me)) \ | 
|  | _PR_INTSON(_is); \ | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | #if defined(XP_PC) | 
|  | #define strcasecmp stricmp | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * On NT, we can't define _PUT_LOG as PR_Write or _PR_MD_WRITE, | 
|  | * because every asynchronous file io operation leads to a fiber context | 
|  | * switch.  So we define _PUT_LOG as fputs (from stdio.h).  A side | 
|  | * benefit is that fputs handles the LF->CRLF translation.  This | 
|  | * code can also be used on other platforms with file stream io. | 
|  | */ | 
|  | #if defined(WIN32) || defined(XP_OS2) | 
|  | #define _PR_USE_STDIO_FOR_LOGGING | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | ** Coerce Win32 log output to use OutputDebugString() when | 
|  | ** NSPR_LOG_FILE is set to "WinDebug". | 
|  | */ | 
|  | #if defined(XP_PC) | 
|  | #define WIN32_DEBUG_FILE (FILE*)-2 | 
|  | #endif | 
|  |  | 
|  | #ifdef WINCE | 
|  | static void OutputDebugStringA(const char* msg) { | 
|  | int len = MultiByteToWideChar(CP_ACP, 0, msg, -1, 0, 0); | 
|  | WCHAR *wMsg = (WCHAR *)PR_Malloc(len * sizeof(WCHAR)); | 
|  | MultiByteToWideChar(CP_ACP, 0, msg, -1, wMsg, len); | 
|  | OutputDebugStringW(wMsg); | 
|  | PR_Free(wMsg); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /* Macros used to reduce #ifdef pollution */ | 
|  |  | 
|  | #if defined(_PR_USE_STDIO_FOR_LOGGING) && defined(XP_PC) | 
|  | #define _PUT_LOG(fd, buf, nb) \ | 
|  | PR_BEGIN_MACRO \ | 
|  | if (logFile == WIN32_DEBUG_FILE) { \ | 
|  | char savebyte = buf[nb]; \ | 
|  | buf[nb] = '\0'; \ | 
|  | OutputDebugStringA(buf); \ | 
|  | buf[nb] = savebyte; \ | 
|  | } else { \ | 
|  | fwrite(buf, 1, nb, fd); \ | 
|  | fflush(fd); \ | 
|  | } \ | 
|  | PR_END_MACRO | 
|  | #elif defined(_PR_USE_STDIO_FOR_LOGGING) | 
|  | #define _PUT_LOG(fd, buf, nb) {fwrite(buf, 1, nb, fd); fflush(fd);} | 
|  | #elif defined(ANDROID) | 
|  | #define _PUT_LOG(fd, buf, nb)                                \ | 
|  | PR_BEGIN_MACRO                                           \ | 
|  | if (fd == _pr_stderr) {                                  \ | 
|  | char savebyte = buf[nb];                             \ | 
|  | buf[nb] = '\0';                                      \ | 
|  | __android_log_write(ANDROID_LOG_INFO, "PRLog", buf); \ | 
|  | buf[nb] = savebyte;                                  \ | 
|  | } else {                                                 \ | 
|  | PR_Write(fd, buf, nb);                               \ | 
|  | }                                                        \ | 
|  | PR_END_MACRO | 
|  | #elif defined(_PR_PTHREADS) | 
|  | #define _PUT_LOG(fd, buf, nb) PR_Write(fd, buf, nb) | 
|  | #else | 
|  | #define _PUT_LOG(fd, buf, nb) _PR_MD_WRITE(fd, buf, nb) | 
|  | #endif | 
|  |  | 
|  | /************************************************************************/ | 
|  |  | 
|  | static PRLogModuleInfo *logModules; | 
|  |  | 
|  | static char *logBuf = NULL; | 
|  | static char *logp; | 
|  | static char *logEndp; | 
|  | #ifdef _PR_USE_STDIO_FOR_LOGGING | 
|  | static FILE *logFile = NULL; | 
|  | #else | 
|  | static PRFileDesc *logFile = 0; | 
|  | #endif | 
|  | static PRBool outputTimeStamp = PR_FALSE; | 
|  | static PRBool appendToLog = PR_FALSE; | 
|  |  | 
|  | #define LINE_BUF_SIZE           512 | 
|  | #define DEFAULT_BUF_SIZE        16384 | 
|  |  | 
|  | #ifdef _PR_NEED_STRCASECMP | 
|  |  | 
|  | /* | 
|  | * strcasecmp is defined in /usr/ucblib/libucb.a on some platforms | 
|  | * such as NCR and Unixware.  Linking with both libc and libucb | 
|  | * may cause some problem, so I just provide our own implementation | 
|  | * of strcasecmp here. | 
|  | */ | 
|  |  | 
|  | static const unsigned char uc[] = | 
|  | { | 
|  | '\000', '\001', '\002', '\003', '\004', '\005', '\006', '\007', | 
|  | '\010', '\011', '\012', '\013', '\014', '\015', '\016', '\017', | 
|  | '\020', '\021', '\022', '\023', '\024', '\025', '\026', '\027', | 
|  | '\030', '\031', '\032', '\033', '\034', '\035', '\036', '\037', | 
|  | ' ',    '!',    '"',    '#',    '$',    '%',    '&',    '\'', | 
|  | '(',    ')',    '*',    '+',    ',',    '-',    '.',    '/', | 
|  | '0',    '1',    '2',    '3',    '4',    '5',    '6',    '7', | 
|  | '8',    '9',    ':',    ';',    '<',    '=',    '>',    '?', | 
|  | '@',    'A',    'B',    'C',    'D',    'E',    'F',    'G', | 
|  | 'H',    'I',    'J',    'K',    'L',    'M',    'N',    'O', | 
|  | 'P',    'Q',    'R',    'S',    'T',    'U',    'V',    'W', | 
|  | 'X',    'Y',    'Z',    '[',    '\\',   ']',    '^',    '_', | 
|  | '`',    'A',    'B',    'C',    'D',    'E',    'F',    'G', | 
|  | 'H',    'I',    'J',    'K',    'L',    'M',    'N',    'O', | 
|  | 'P',    'Q',    'R',    'S',    'T',    'U',    'V',    'W', | 
|  | 'X',    'Y',    'Z',    '{',    '|',    '}',    '~',    '\177' | 
|  | }; | 
|  |  | 
|  | PRIntn strcasecmp(const char *a, const char *b) | 
|  | { | 
|  | const unsigned char *ua = (const unsigned char *)a; | 
|  | const unsigned char *ub = (const unsigned char *)b; | 
|  |  | 
|  | if( ((const char *)0 == a) || (const char *)0 == b ) | 
|  | return (PRIntn)(a-b); | 
|  |  | 
|  | while( (uc[*ua] == uc[*ub]) && ('\0' != *a) ) | 
|  | { | 
|  | a++; | 
|  | ua++; | 
|  | ub++; | 
|  | } | 
|  |  | 
|  | return (PRIntn)(uc[*ua] - uc[*ub]); | 
|  | } | 
|  |  | 
|  | #endif /* _PR_NEED_STRCASECMP */ | 
|  |  | 
|  | void _PR_InitLog(void) | 
|  | { | 
|  | char *ev; | 
|  |  | 
|  | _pr_logLock = PR_NewLock(); | 
|  |  | 
|  | ev = PR_GetEnv("NSPR_LOG_MODULES"); | 
|  | if (ev && ev[0]) { | 
|  | char module[64];  /* Security-Critical: If you change this | 
|  | * size, you must also change the sscanf | 
|  | * format string to be size-1. | 
|  | */ | 
|  | PRBool isSync = PR_FALSE; | 
|  | PRIntn evlen = strlen(ev), pos = 0; | 
|  | PRInt32 bufSize = DEFAULT_BUF_SIZE; | 
|  | while (pos < evlen) { | 
|  | PRIntn level = 1, count = 0, delta = 0; | 
|  | count = sscanf(&ev[pos], "%63[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-]%n:%d%n", | 
|  | module, &delta, &level, &delta); | 
|  | pos += delta; | 
|  | if (count == 0) break; | 
|  |  | 
|  | /* | 
|  | ** If count == 2, then we got module and level. If count | 
|  | ** == 1, then level defaults to 1 (module enabled). | 
|  | */ | 
|  | if (strcasecmp(module, "sync") == 0) { | 
|  | isSync = PR_TRUE; | 
|  | } else if (strcasecmp(module, "bufsize") == 0) { | 
|  | if (level >= LINE_BUF_SIZE) { | 
|  | bufSize = level; | 
|  | } | 
|  | } else if (strcasecmp(module, "timestamp") == 0) { | 
|  | outputTimeStamp = PR_TRUE; | 
|  | } else if (strcasecmp(module, "append") == 0) { | 
|  | appendToLog = PR_TRUE; | 
|  | } else { | 
|  | PRLogModuleInfo *lm = logModules; | 
|  | PRBool skip_modcheck = | 
|  | (0 == strcasecmp (module, "all")) ? PR_TRUE : PR_FALSE; | 
|  |  | 
|  | while (lm != NULL) { | 
|  | if (skip_modcheck) lm -> level = (PRLogModuleLevel)level; | 
|  | else if (strcasecmp(module, lm->name) == 0) { | 
|  | lm->level = (PRLogModuleLevel)level; | 
|  | break; | 
|  | } | 
|  | lm = lm->next; | 
|  | } | 
|  | } | 
|  | /*found:*/ | 
|  | count = sscanf(&ev[pos], " , %n", &delta); | 
|  | pos += delta; | 
|  | if (count == EOF) break; | 
|  | } | 
|  | PR_SetLogBuffering(isSync ? 0 : bufSize); | 
|  |  | 
|  | ev = PR_GetEnvSecure("NSPR_LOG_FILE"); | 
|  | if (ev && ev[0]) { | 
|  | if (!PR_SetLogFile(ev)) { | 
|  | #ifdef XP_PC | 
|  | char* str = PR_smprintf("Unable to create nspr log file '%s'\n", ev); | 
|  | if (str) { | 
|  | OutputDebugStringA(str); | 
|  | PR_smprintf_free(str); | 
|  | } | 
|  | #else | 
|  | fprintf(stderr, "Unable to create nspr log file '%s'\n", ev); | 
|  | #endif | 
|  | } | 
|  | } else { | 
|  | #ifdef _PR_USE_STDIO_FOR_LOGGING | 
|  | logFile = stderr; | 
|  | #else | 
|  | logFile = _pr_stderr; | 
|  | #endif | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void _PR_LogCleanup(void) | 
|  | { | 
|  | PRLogModuleInfo *lm = logModules; | 
|  |  | 
|  | PR_LogFlush(); | 
|  |  | 
|  | #ifdef _PR_USE_STDIO_FOR_LOGGING | 
|  | if (logFile | 
|  | && logFile != stdout | 
|  | && logFile != stderr | 
|  | #ifdef XP_PC | 
|  | && logFile != WIN32_DEBUG_FILE | 
|  | #endif | 
|  | ) { | 
|  | fclose(logFile); | 
|  | } | 
|  | #else | 
|  | if (logFile && logFile != _pr_stdout && logFile != _pr_stderr) { | 
|  | PR_Close(logFile); | 
|  | } | 
|  | #endif | 
|  | logFile = NULL; | 
|  |  | 
|  | if (logBuf) | 
|  | PR_DELETE(logBuf); | 
|  |  | 
|  | while (lm != NULL) { | 
|  | PRLogModuleInfo *next = lm->next; | 
|  | free((/*const*/ char *)lm->name); | 
|  | PR_Free(lm); | 
|  | lm = next; | 
|  | } | 
|  | logModules = NULL; | 
|  |  | 
|  | if (_pr_logLock) { | 
|  | PR_DestroyLock(_pr_logLock); | 
|  | _pr_logLock = NULL; | 
|  | } | 
|  | } | 
|  |  | 
|  | static void _PR_SetLogModuleLevel( PRLogModuleInfo *lm ) | 
|  | { | 
|  | char *ev; | 
|  |  | 
|  | ev = PR_GetEnv("NSPR_LOG_MODULES"); | 
|  | if (ev && ev[0]) { | 
|  | char module[64];  /* Security-Critical: If you change this | 
|  | * size, you must also change the sscanf | 
|  | * format string to be size-1. | 
|  | */ | 
|  | PRIntn evlen = strlen(ev), pos = 0; | 
|  | while (pos < evlen) { | 
|  | PRIntn level = 1, count = 0, delta = 0; | 
|  |  | 
|  | count = sscanf(&ev[pos], "%63[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-]%n:%d%n", | 
|  | module, &delta, &level, &delta); | 
|  | pos += delta; | 
|  | if (count == 0) break; | 
|  |  | 
|  | /* | 
|  | ** If count == 2, then we got module and level. If count | 
|  | ** == 1, then level defaults to 1 (module enabled). | 
|  | */ | 
|  | if (lm != NULL) | 
|  | { | 
|  | if ((strcasecmp(module, "all") == 0) | 
|  | || (strcasecmp(module, lm->name) == 0)) | 
|  | { | 
|  | lm->level = (PRLogModuleLevel)level; | 
|  | } | 
|  | } | 
|  | count = sscanf(&ev[pos], " , %n", &delta); | 
|  | pos += delta; | 
|  | if (count == EOF) break; | 
|  | } | 
|  | } | 
|  | } /* end _PR_SetLogModuleLevel() */ | 
|  |  | 
|  | PR_IMPLEMENT(PRLogModuleInfo*) PR_NewLogModule(const char *name) | 
|  | { | 
|  | PRLogModuleInfo *lm; | 
|  |  | 
|  | if (!_pr_initialized) _PR_ImplicitInitialization(); | 
|  |  | 
|  | lm = PR_NEWZAP(PRLogModuleInfo); | 
|  | if (lm) { | 
|  | lm->name = strdup(name); | 
|  | lm->level = PR_LOG_NONE; | 
|  | lm->next = logModules; | 
|  | logModules = lm; | 
|  | _PR_SetLogModuleLevel(lm); | 
|  | } | 
|  | return lm; | 
|  | } | 
|  |  | 
|  | PR_IMPLEMENT(PRBool) PR_SetLogFile(const char *file) | 
|  | { | 
|  | #ifdef _PR_USE_STDIO_FOR_LOGGING | 
|  | FILE *newLogFile; | 
|  |  | 
|  | #ifdef XP_PC | 
|  | if ( strcmp( file, "WinDebug") == 0) | 
|  | { | 
|  | newLogFile = WIN32_DEBUG_FILE; | 
|  | } | 
|  | else | 
|  | #endif | 
|  | { | 
|  | const char *mode = appendToLog ? "a" : "w"; | 
|  | newLogFile = fopen(file, mode); | 
|  | if (!newLogFile) | 
|  | return PR_FALSE; | 
|  |  | 
|  | #ifndef WINCE  /* _IONBF does not exist in the Windows Mobile 6 SDK. */ | 
|  | /* We do buffering ourselves. */ | 
|  | setvbuf(newLogFile, NULL, _IONBF, 0); | 
|  | #endif | 
|  | } | 
|  | if (logFile | 
|  | && logFile != stdout | 
|  | && logFile != stderr | 
|  | #ifdef XP_PC | 
|  | && logFile != WIN32_DEBUG_FILE | 
|  | #endif | 
|  | ) { | 
|  | fclose(logFile); | 
|  | } | 
|  | logFile = newLogFile; | 
|  | return PR_TRUE; | 
|  | #else | 
|  | PRFileDesc *newLogFile; | 
|  | PRIntn flags = PR_WRONLY|PR_CREATE_FILE; | 
|  | if (appendToLog) { | 
|  | flags |= PR_APPEND; | 
|  | } else { | 
|  | flags |= PR_TRUNCATE; | 
|  | } | 
|  |  | 
|  | newLogFile = PR_Open(file, flags, 0666); | 
|  | if (newLogFile) { | 
|  | if (logFile && logFile != _pr_stdout && logFile != _pr_stderr) { | 
|  | PR_Close(logFile); | 
|  | } | 
|  | logFile = newLogFile; | 
|  | } | 
|  | return (PRBool) (newLogFile != 0); | 
|  | #endif /* _PR_USE_STDIO_FOR_LOGGING */ | 
|  | } | 
|  |  | 
|  | PR_IMPLEMENT(void) PR_SetLogBuffering(PRIntn buffer_size) | 
|  | { | 
|  | PR_LogFlush(); | 
|  |  | 
|  | if (logBuf) | 
|  | PR_DELETE(logBuf); | 
|  |  | 
|  | if (buffer_size >= LINE_BUF_SIZE) { | 
|  | logp = logBuf = (char*) PR_MALLOC(buffer_size); | 
|  | logEndp = logp + buffer_size; | 
|  | } | 
|  | } | 
|  |  | 
|  | PR_IMPLEMENT(void) PR_LogPrint(const char *fmt, ...) | 
|  | { | 
|  | va_list ap; | 
|  | char line[LINE_BUF_SIZE]; | 
|  | char *line_long = NULL; | 
|  | PRUint32 nb_tid = 0, nb; | 
|  | PRThread *me; | 
|  | PRExplodedTime now; | 
|  |  | 
|  | if (!_pr_initialized) _PR_ImplicitInitialization(); | 
|  |  | 
|  | if (!logFile) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (outputTimeStamp) { | 
|  | PR_ExplodeTime(PR_Now(), PR_GMTParameters, &now); | 
|  | nb_tid = PR_snprintf(line, sizeof(line)-1, | 
|  | "%04d-%02d-%02d %02d:%02d:%02d.%06d UTC - ", | 
|  | now.tm_year, now.tm_month + 1, now.tm_mday, | 
|  | now.tm_hour, now.tm_min, now.tm_sec, | 
|  | now.tm_usec); | 
|  | } | 
|  |  | 
|  | me = PR_GetCurrentThread(); | 
|  | nb_tid += PR_snprintf(line+nb_tid, sizeof(line)-nb_tid-1, "%ld[%p]: ", | 
|  | #if defined(_PR_BTHREADS) | 
|  | me, me); | 
|  | #else | 
|  | me ? me->id : 0L, me); | 
|  | #endif | 
|  |  | 
|  | va_start(ap, fmt); | 
|  | nb = nb_tid + PR_vsnprintf(line+nb_tid, sizeof(line)-nb_tid-1, fmt, ap); | 
|  | va_end(ap); | 
|  |  | 
|  | /* | 
|  | * Check if we might have run out of buffer space (in case we have a | 
|  | * long line), and malloc a buffer just this once. | 
|  | */ | 
|  | if (nb == sizeof(line)-2) { | 
|  | va_start(ap, fmt); | 
|  | line_long = PR_vsmprintf(fmt, ap); | 
|  | va_end(ap); | 
|  | /* If this failed, we'll fall back to writing the truncated line. */ | 
|  | } | 
|  |  | 
|  | if (line_long) { | 
|  | nb = strlen(line_long); | 
|  | _PR_LOCK_LOG(); | 
|  | if (logBuf != 0) { | 
|  | _PUT_LOG(logFile, logBuf, logp - logBuf); | 
|  | logp = logBuf; | 
|  | } | 
|  | /* | 
|  | * Write out the thread id (with an optional timestamp) and the | 
|  | * malloc'ed buffer. | 
|  | */ | 
|  | _PUT_LOG(logFile, line, nb_tid); | 
|  | _PUT_LOG(logFile, line_long, nb); | 
|  | /* Ensure there is a trailing newline. */ | 
|  | if (!nb || (line_long[nb-1] != '\n')) { | 
|  | char eol[2]; | 
|  | eol[0] = '\n'; | 
|  | eol[1] = '\0'; | 
|  | _PUT_LOG(logFile, eol, 1); | 
|  | } | 
|  | _PR_UNLOCK_LOG(); | 
|  | PR_smprintf_free(line_long); | 
|  | } else { | 
|  | /* Ensure there is a trailing newline. */ | 
|  | if (nb && (line[nb-1] != '\n')) { | 
|  | line[nb++] = '\n'; | 
|  | line[nb] = '\0'; | 
|  | } | 
|  | _PR_LOCK_LOG(); | 
|  | if (logBuf == 0) { | 
|  | _PUT_LOG(logFile, line, nb); | 
|  | } else { | 
|  | /* If nb can't fit into logBuf, write out logBuf first. */ | 
|  | if (logp + nb > logEndp) { | 
|  | _PUT_LOG(logFile, logBuf, logp - logBuf); | 
|  | logp = logBuf; | 
|  | } | 
|  | /* nb is guaranteed to fit into logBuf. */ | 
|  | memcpy(logp, line, nb); | 
|  | logp += nb; | 
|  | } | 
|  | _PR_UNLOCK_LOG(); | 
|  | } | 
|  | PR_LogFlush(); | 
|  | } | 
|  |  | 
|  | PR_IMPLEMENT(void) PR_LogFlush(void) | 
|  | { | 
|  | if (logBuf && logFile) { | 
|  | _PR_LOCK_LOG(); | 
|  | if (logp > logBuf) { | 
|  | _PUT_LOG(logFile, logBuf, logp - logBuf); | 
|  | logp = logBuf; | 
|  | } | 
|  | _PR_UNLOCK_LOG(); | 
|  | } | 
|  | } | 
|  |  | 
|  | PR_IMPLEMENT(void) PR_Abort(void) | 
|  | { | 
|  | PR_LogPrint("Aborting"); | 
|  | #ifdef ANDROID | 
|  | __android_log_write(ANDROID_LOG_ERROR, "PRLog", "Aborting"); | 
|  | #endif | 
|  | abort(); | 
|  | } | 
|  |  | 
|  | PR_IMPLEMENT(void) PR_Assert(const char *s, const char *file, PRIntn ln) | 
|  | { | 
|  | PR_LogPrint("Assertion failure: %s, at %s:%d\n", s, file, ln); | 
|  | fprintf(stderr, "Assertion failure: %s, at %s:%d\n", s, file, ln); | 
|  | fflush(stderr); | 
|  | #ifdef WIN32 | 
|  | DebugBreak(); | 
|  | #elif defined(XP_OS2) | 
|  | asm("int $3"); | 
|  | #elif defined(ANDROID) | 
|  | __android_log_assert(NULL, "PRLog", "Assertion failure: %s, at %s:%d\n", | 
|  | s, file, ln); | 
|  | #endif | 
|  | abort(); | 
|  | } |