/* * Copyright (C) 2012 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "VMInspector.h" #if ENABLE(VMINSPECTOR) #include #include #include namespace JSC { const char* VMInspector::getTypeName(JSValue value) { if (value.isInt32()) return ""; if (value.isBoolean()) return ""; if (value.isNull()) return ""; if (value.isUndefined()) return ""; if (value.isCell()) return ""; if (value.isEmpty()) return ""; return ""; } void VMInspector::dumpFrame0(CallFrame* frame) { dumpFrame(frame, 0, 0, 0, 0); } void VMInspector::dumpFrame(CallFrame* frame, const char* prefix, const char* funcName, const char* file, int line) { int frameCount = VMInspector::countFrames(frame); if (frameCount < 0) return; Instruction* vPC = 0; if (frame->codeBlock()) vPC = frame->currentVPC(); #define CAST reinterpret_cast if (prefix) printf("%s ", prefix); printf("frame [%d] %p { cb %p:%s, retPC %p:%s, scope %p:%s, callee %p:%s, callerFrame %p:%s, argc %d, vPC %p }", frameCount, frame, CAST(frame[JSStack::CodeBlock].payload()), getTypeName(frame[JSStack::CodeBlock].jsValue()), CAST(frame[JSStack::ReturnPC].payload()), getTypeName(frame[JSStack::ReturnPC].jsValue()), CAST(frame[JSStack::ScopeChain].payload()), getTypeName(frame[JSStack::ScopeChain].jsValue()), CAST(frame[JSStack::Callee].payload()), getTypeName(frame[JSStack::Callee].jsValue()), CAST(frame[JSStack::CallerFrame].callFrame()), getTypeName(frame[JSStack::CallerFrame].jsValue()), frame[JSStack::ArgumentCount].payload(), vPC); if (funcName || file || (line >= 0)) { printf(" @"); if (funcName) printf(" %s", funcName); if (file) printf(" %s", file); if (line >= 0) printf(":%d", line); } printf("\n"); } int VMInspector::countFrames(CallFrame* frame) { int count = -1; while (frame && !frame->hasHostCallFrameFlag()) { count++; frame = frame->callerFrame(); } return count; } //============================================================================ // class FormatPrinter // - implements functionality to support fprintf. // // The FormatPrinter classes do the real formatting and printing. // By default, the superclass FormatPrinter will print to stdout (printf). // Each of the subclass will implement the other ...printf() options. // The subclasses are: // // FileFormatPrinter - fprintf // StringFormatPrinter - sprintf // StringNFormatPrinter - snprintf class FormatPrinter { public: virtual ~FormatPrinter() { } void print(const char* format, va_list args); protected: // Low level printers: bool printArg(const char* format, ...); virtual bool printArg(const char* format, va_list args); // JS type specific printers: void printWTFString(va_list args, bool verbose); }; // The public print() function is the real workhorse behind the printf // family of functions. print() deciphers the % formatting, translate them // to primitive formats, and dispatches to underlying printArg() functions // to do the printing. // // The non-public internal printArg() function is virtual and is responsible // for handling the variations between printf, fprintf, sprintf, and snprintf. void FormatPrinter::print(const char* format, va_list args) { const char* p = format; const char* errorStr; // buffer is only used for 2 purposes: // 1. To temporarily hold a copy of normal chars (not needing formatting) // to be passed to printArg() and printed. // // The incoming format string may contain a string of normal chars much // longer than 128, but we handle this by breaking them out to 128 chars // fragments and printing each fragment before re-using the buffer to // load up the next fragment. // // 2. To hold a single "%..." format to be passed to printArg() to process // a single va_arg. char buffer[129]; // 128 chars + null terminator. char* end = &buffer[sizeof(buffer) - 1]; const char* startOfFormatSpecifier = 0; while (true) { char c = *p++; char* curr = buffer; // Print leading normal chars: while (c != '\0' && c != '%') { *curr++ = c; if (curr == end) { // Out of buffer space. Flush the fragment, and start over. *curr = '\0'; bool success = printArg("%s", buffer); if (!success) { errorStr = buffer; goto handleError; } curr = buffer; } c = *p++; } // If we have stuff in the buffer, flush the fragment: if (curr != buffer) { ASSERT(curr < end + 1); *curr = '\0'; bool success = printArg("%s", buffer); if (!success) { errorStr = buffer; goto handleError; } } // End if there are not more chars to print: if (c == '\0') break; // If we get here, we've must have seen a '%': startOfFormatSpecifier = p - 1; ASSERT(*startOfFormatSpecifier == '%'); c = *p++; // Check for "%%" case: if (c == '%') { bool success = printArg("%c", '%'); if (!success) { errorStr = p - 2; goto handleError; } continue; } // Check for JS (%J) formatting extensions: if (c == 'J') { bool verbose = false; c = *p++; if (UNLIKELY(c == '\0')) { errorStr = p - 2; // Rewind to % in "%J\0" goto handleError; } if (c == '+') { verbose = true; c= *p++; if (UNLIKELY(c == '\0')) { errorStr = p - 3; // Rewind to % in "%J+\0" goto handleError; } } switch (c) { // %Js - WTF::String* case 's': { printWTFString(args, verbose); continue; } } // END switch. // Check for non-JS extensions: } else if (c == 'b') { int value = va_arg(args, int); printArg("%s", value ? "TRUE" : "FALSE"); continue; } // If we didn't handle the format in one of the above cases, // rewind p and let the standard formatting check handle it // if possible: p = startOfFormatSpecifier; ASSERT(*p == '%'); // Check for standard formatting: // A format specifier always starts with a % and ends with some // alphabet. We'll do the simple thing and scan until the next // alphabet, or the end of string. // In the following, we're going to use buffer as storage for a copy // of a single format specifier. Hence, conceptually, we can think of // 'buffer' as synonymous with 'argFormat' here: #define ABORT_IF_FORMAT_TOO_LONG(curr) \ do { \ if (UNLIKELY(curr >= end)) \ goto formatTooLong; \ } while (false) curr = buffer; *curr++ = *p++; // Output the first % in the format specifier. c = *p++; // Grab the next char in the format specifier. // Checks for leading modifiers e.g. "%-d": // 0, -, ' ', +, '\'' if (c == '0' || c == '-' || c == ' ' || c == '+' || c == '\'' || c == '#') { ABORT_IF_FORMAT_TOO_LONG(curr); *curr++ = c; c = *p++; } // Checks for decimal digit field width modifiers e.g. "%2f": while (c >= '0' && c <= '9') { ABORT_IF_FORMAT_TOO_LONG(curr); *curr++ = c; c = *p++; } // Checks for '.' e.g. "%2.f": if (c == '.') { ABORT_IF_FORMAT_TOO_LONG(curr); *curr++ = c; c = *p++; // Checks for decimal digit precision modifiers e.g. "%.2f": while (c >= '0' && c <= '9') { ABORT_IF_FORMAT_TOO_LONG(curr); *curr++ = c; c = *p++; } } // Checks for the modifier where can be: // l, h, j, t, z // e.g. "%ld" if (c == 'l' || c == 'h' || c == 'j' || c == 't' || c == 'z' || c == 'L') { ABORT_IF_FORMAT_TOO_LONG(curr); *curr++ = c; char prevChar = c; c = *p++; // Checks for the modifier ll or hh in %: if ((prevChar == 'l' || prevChar == 'h') && c == prevChar) { ABORT_IF_FORMAT_TOO_LONG(curr); *curr++ = c; c = *p++; } } // Checks for % where can be: // d, i, n, o, u, x, X // But hey, we're just going to do the simple thing and allow any // alphabet. The user is expected to pass correct format specifiers. // We won't do any format checking here. We'll just pass it on, and the // underlying ...printf() implementation may do the needed checking // at its discretion. while (c != '\0' && !isASCIIAlpha(c)) { ABORT_IF_FORMAT_TOO_LONG(curr); *curr++ = c; c = *p++; } ABORT_IF_FORMAT_TOO_LONG(curr); *curr++ = c; if (c == '\0') { // Uh oh. Bad format. We should have gotten an alphabet instead. // Print the supposed format as a string instead: errorStr = buffer; goto handleError; } // Otherwise, we have the alpha that terminates the format. // Terminate the buffer (i.e. argFormat) string: ASSERT(isASCIIAlpha(c)); ABORT_IF_FORMAT_TOO_LONG(curr); *curr = '\0'; bool success = printArg(buffer, args); if (!success) { errorStr = buffer; goto handleError; } } #undef ABORT_IF_FORMAT_TOO_LONG return; formatTooLong: // Print the error string: ASSERT(!!startOfFormatSpecifier); p = startOfFormatSpecifier; ASSERT(p >= format); printArg("ERROR @ Format too long at \"%s\"\n", p); return; handleError: // We've got an error. Can't do any more work. Print an error message if // possible and then just return. // The errorStr may be pointing into the middle of buffer, or the original // format string. Move the string to buffer for consistency, and also so // that we can strip it of newlines below. if (errorStr != buffer) { size_t length = strlen(errorStr); if (length > sizeof(buffer) - 1) length = sizeof(buffer) - 1; memmove(buffer, errorStr, length); buffer[length] = '\0'; // Terminate the moved error string. } // Strip the newlines: char* cp = buffer; while (*cp) { if (*cp == '\n' || *cp == '\r') *cp = ' '; cp++; } // Print the error string: printArg("ERROR @ \"%s\"\n", buffer); } bool FormatPrinter::printArg(const char* format, ...) { va_list args; va_start(args, format); bool success = printArg(format, args); va_end(args); return success; } bool FormatPrinter::printArg(const char* format, va_list args) { int count = ::vprintf(format, args); return (count >= 0); // Fail if less than 0 chars printed. } // %Js - WTF::String* // verbose mode prints: WTF::String "" void FormatPrinter::printWTFString(va_list args, bool verbose) { const String* str = va_arg(args, const String*); // Print verbose header if appropriate: if (verbose) printArg("WTF::String \""); // Print the string itself: if (!str->isEmpty()) { if (str->is8Bit()) { const LChar* chars = str->characters8(); printArg("%s", reinterpret_cast(chars)); } else { const UChar* chars = str->characters16(); printArg("%S", reinterpret_cast(chars)); } } // Print verbose footer if appropriate: if (verbose) printArg("\""); } //============================================================================ // class FileFormatPrinter // - implements functionality to support fprintf. class FileFormatPrinter: public FormatPrinter { public: FileFormatPrinter(FILE*); private: virtual bool printArg(const char* format, va_list args); FILE* m_file; }; FileFormatPrinter::FileFormatPrinter(FILE* file) : m_file(file) { } bool FileFormatPrinter::printArg(const char* format, va_list args) { int count = ::vfprintf(m_file, format, args); return (count >= 0); // Fail if less than 0 chars printed. } //============================================================================ // class StringFormatPrinter // - implements functionality to support sprintf. class StringFormatPrinter: public FormatPrinter { public: StringFormatPrinter(char* buffer); private: virtual bool printArg(const char* format, va_list args); char* m_buffer; }; StringFormatPrinter::StringFormatPrinter(char* buffer) : m_buffer(buffer) { } bool StringFormatPrinter::printArg(const char* format, va_list args) { int count = ::vsprintf(m_buffer, format, args); m_buffer += count; return (count >= 0); // Fail if less than 0 chars printed. } //============================================================================ // class StringNFormatPrinter // - implements functionality to support snprintf. class StringNFormatPrinter: public FormatPrinter { public: StringNFormatPrinter(char* buffer, size_t); private: virtual bool printArg(const char* format, va_list args); char* m_buffer; size_t m_size; }; StringNFormatPrinter::StringNFormatPrinter(char* buffer, size_t size) : m_buffer(buffer) , m_size(size) { } bool StringNFormatPrinter::printArg(const char* format, va_list args) { if (m_size > 0) { int count = ::vsnprintf(m_buffer, m_size, format, args); // According to vsnprintf specs, ... bool success = (count >= 0); if (static_cast(count) >= m_size) { // If count > size, then we didn't have enough buffer space. count = m_size; } // Adjust the buffer to what's left if appropriate: if (success) { m_buffer += count; m_size -= count; } return success; } // No more room to print. Declare it a fail: return false; } //============================================================================ // VMInspector printf family of methods: void VMInspector::fprintf(FILE* file, const char* format, ...) { va_list args; va_start(args, format); FileFormatPrinter(file).print(format, args); va_end(args); } void VMInspector::printf(const char* format, ...) { va_list args; va_start(args, format); FormatPrinter().print(format, args); va_end(args); } void VMInspector::sprintf(char* buffer, const char* format, ...) { va_list args; va_start(args, format); StringFormatPrinter(buffer).print(format, args); va_end(args); } void VMInspector::snprintf(char* buffer, size_t size, const char* format, ...) { va_list args; va_start(args, format); StringNFormatPrinter(buffer, size).print(format, args); va_end(args); } } // namespace JSC #endif // ENABLE(VMINSPECTOR)