runtime/traceback-builtins.cpp (276 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #include "traceback-builtins.h" #include "builtins.h" #include "globals.h" #include "os.h" #include "str-builtins.h" #include "type-builtins.h" namespace py { static const BuiltinAttribute kTracebackAttributes[] = { {ID(_traceback__next), RawTraceback::kNextOffset, AttributeFlags::kHidden}, {ID(_traceback__function), RawTraceback::kFunctionOffset, AttributeFlags::kHidden}, {ID(tb_lasti), RawTraceback::kLastiOffset, AttributeFlags::kReadOnly}, {ID(_traceback__lineno), RawTraceback::kLinenoOffset, AttributeFlags::kHidden}, }; static const word kTracebackLimit = 1000; // PyTraceback_LIMIT static const word kTracebackRecursiveCutoff = 3; // TB_RECURSIVE_CUTOFF void initializeTracebackType(Thread* thread) { addBuiltinType(thread, ID(traceback), LayoutId::kTraceback, /*superclass_id=*/LayoutId::kObject, kTracebackAttributes, Traceback::kSize, /*basetype=*/false); } static RawObject lineRepeated(Thread* thread, word count) { count -= kTracebackRecursiveCutoff; if (count == 1) { return thread->runtime()->newStrFromCStr( " [Previous line repeated 1 more time]\n"); } return thread->runtime()->newStrFromFmt( " [Previous line repeated %w more times]\n", count); } static RawObject sourceLine(Thread* thread, const Object& filename, const Object& lineno_obj) { if (!filename.isStr() || !lineno_obj.isSmallInt()) { return NoneType::object(); } HandleScope scope(thread); Runtime* runtime = thread->runtime(); Object linecache(&scope, runtime->symbols()->at(ID(linecache))); if (runtime->findModule(linecache).isErrorNotFound()) { linecache = thread->invokeFunction1(ID(builtins), ID(__import__), linecache); if (linecache.isErrorException()) { thread->clearPendingException(); return NoneType::object(); } } Object line_obj(&scope, thread->invokeFunction2(ID(linecache), ID(getline), filename, lineno_obj)); if (line_obj.isErrorException()) { return *line_obj; } CHECK(line_obj.isStr(), "got a non-str line"); Str line(&scope, *line_obj); line = strStripSpace(thread, line); word length = line.length(); if (length == 0) { return NoneType::object(); } MutableBytes result(&scope, runtime->newMutableBytesUninitialized(length + 5)); result.replaceFromWithByte(0, ' ', 4); result.replaceFromWithStr(4, *line, length); result.byteAtPut(length + 4, '\n'); return result.becomeStr(); } static RawObject tracebackFilename(Thread* thread, const Traceback& traceback) { HandleScope scope(thread); Object code(&scope, Function::cast(traceback.function()).code()); if (!code.isCode()) { return NoneType::object(); } Object name(&scope, Code::cast(*code).filename()); if (thread->runtime()->isInstanceOfStr(*name)) { return strUnderlying(*name); } return NoneType::object(); } static RawObject tracebackFunctionName(Thread* thread, const Traceback& traceback) { HandleScope scope(thread); Function function(&scope, traceback.function()); Object name(&scope, function.name()); Runtime* runtime = thread->runtime(); if (runtime->isInstanceOfStr(*name)) { return strUnderlying(*name); } Object code_obj(&scope, function.code()); if (!code_obj.isCode()) { return NoneType::object(); } Code code(&scope, *code_obj); if (!code.isNative()) { return NoneType::object(); } void* addr = Int::cast(code.code()).asCPtr(); char name_buf[128]; word name_size = std::strlen(name_buf); word name_len = OS::sharedObjectSymbolName(addr, name_buf, name_size); if (name_len < 0) { return runtime->newStrFromFmt("<native function at %p (no symbol found)>", addr); } if (name_len < name_size) { return runtime->newStrFromFmt("<native function at %p (%s)>", addr, name_buf); } unique_c_ptr<char> heap_buf( reinterpret_cast<char*>(std::malloc(name_len + 1))); word new_len = OS::sharedObjectSymbolName(addr, heap_buf.get(), name_len + 1); DCHECK(name_len == new_len, "unexpected number of bytes written"); return runtime->newStrFromFmt("<native function at %p (%s)>", addr, heap_buf.get()); } static RawObject tracebackLineno(Thread* thread, const Traceback& traceback) { HandleScope scope(thread); Object lineno(&scope, traceback.lineno()); if (lineno.isSmallInt()) { return *lineno; } Object code_obj(&scope, Function::cast(traceback.function()).code()); if (!code_obj.isCode()) { return NoneType::object(); } Code code(&scope, *code_obj); if (code.isNative() || !code.lnotab().isBytes()) { return NoneType::object(); } word lasti = SmallInt::cast(traceback.lasti()).value(); Object result(&scope, SmallInt::fromWord(code.offsetToLineNum(lasti))); traceback.setLineno(*result); return *result; } static void writeCStr(const MutableBytes& dst, word* index, const char* src) { word length = std::strlen(src); dst.replaceFromWithAll(*index, {reinterpret_cast<const byte*>(src), length}); *index += length; } static void writeStr(const MutableBytes& dst, word* index, RawStr src) { word length = src.length(); dst.replaceFromWithStr(*index, src, length); *index += length; } static word tracebackWriteLine(const Object& filename, const Object& lineno, const Object& function_name, const MutableBytes& dst, bool determine_size) { word index = 0; if (filename.isStr()) { if (determine_size) { index += std::strlen(" File \"") + Str::cast(*filename).length() + 1; } else { writeCStr(dst, &index, " File \""); writeStr(dst, &index, Str::cast(*filename)); writeCStr(dst, &index, "\""); } } else if (determine_size) { index += std::strlen(" File \"<unknown>\""); } else { writeCStr(dst, &index, " File \"<unknown>\""); } if (lineno.isSmallInt()) { char buf[kUwordDigits10 + 9]; word size = std::snprintf(buf, sizeof(buf), ", line %ld", SmallInt::cast(*lineno).value()); DCHECK_BOUND(size, kUwordDigits10 + 8); if (determine_size) { index += size; } else { writeCStr(dst, &index, buf); } } if (function_name.isStr()) { if (determine_size) { index += std::strlen(", in ") + Str::cast(*function_name).length() + 1; } else { writeCStr(dst, &index, ", in "); writeStr(dst, &index, Str::cast(*function_name)); writeCStr(dst, &index, "\n"); } } else if (determine_size) { index += std::strlen(", in <invalid name>\n"); } else { writeCStr(dst, &index, ", in <invalid name>\n"); } return index; } RawObject tracebackWrite(Thread* thread, const Traceback& traceback, const Object& file) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); Object limit_obj( &scope, runtime->lookupNameInModule(thread, ID(sys), ID(tracebacklimit))); word limit = kTracebackLimit; if (!limit_obj.isErrorNotFound() && runtime->isInstanceOfInt(*limit_obj)) { limit = intUnderlying(*limit_obj).asWordSaturated(); if (limit <= 0) { return NoneType::object(); } } Str line(&scope, runtime->newStrFromCStr("Traceback (most recent call last):\n")); Object result(&scope, thread->invokeMethod2(file, ID(write), line)); if (result.isErrorException()) return *result; word depth = 0; Object tb(&scope, *traceback); while (tb.isTraceback()) { depth++; tb = Traceback::cast(*tb).next(); } Traceback current(&scope, *traceback); while (depth > limit) { depth--; current = current.next(); } MutableBytes buffer(&scope, runtime->emptyMutableBytes()); Object filename(&scope, NoneType::object()); Object function_name(&scope, NoneType::object()); Object lineno(&scope, NoneType::object()); Object last_filename(&scope, NoneType::object()); Object last_function_name(&scope, NoneType::object()); Object last_lineno(&scope, NoneType::object()); Object next(&scope, NoneType::object()); for (word count = 0;;) { filename = tracebackFilename(thread, current); lineno = tracebackLineno(thread, current); function_name = tracebackFunctionName(thread, current); bool filename_changed = last_filename.isNoneType() || filename.isNoneType() || !Str::cast(*last_filename).equals(Str::cast(*filename)); bool lineno_changed = last_lineno.isNoneType() || lineno != last_lineno; bool function_name_changed = last_function_name.isNoneType() || function_name.isNoneType() || !Str::cast(*last_function_name).equals(Str::cast(*function_name)); if (filename_changed || lineno_changed || function_name_changed) { if (count > kTracebackRecursiveCutoff) { line = lineRepeated(thread, count); result = thread->invokeMethod2(file, ID(write), line); if (result.isErrorException()) return *result; } last_filename = *filename; last_lineno = *lineno; last_function_name = *function_name; count = 0; } count++; if (count <= kTracebackRecursiveCutoff) { word size = tracebackWriteLine(filename, lineno, function_name, buffer, true); buffer = runtime->newMutableBytesUninitialized(size); tracebackWriteLine(filename, lineno, function_name, buffer, false); line = buffer.becomeStr(); result = thread->invokeMethod2(file, ID(write), line); if (result.isErrorException()) return *result; result = sourceLine(thread, filename, lineno); if (result.isErrorException()) return *result; if (result.isStr()) { result = thread->invokeMethod2(file, ID(write), result); if (result.isErrorException()) return *result; } result = runtime->handlePendingSignals(thread); if (result.isErrorException()) return *result; } next = current.next(); if (next.isNoneType()) { if (count > kTracebackRecursiveCutoff) { line = lineRepeated(thread, count); result = thread->invokeMethod2(file, ID(write), line); if (result.isErrorException()) return *result; } return NoneType::object(); } current = *next; buffer = runtime->emptyMutableBytes(); } } } // namespace py