Skip to content

Implement a warning that detects Swift compile units that were compiled with #3361

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

Merged
merged 5 commits into from
Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions lldb/include/lldb/Symbol/SymbolFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ class SymbolFile : public PluginInterface {

Symtab *GetSymtab();

virtual llvm::VersionTuple GetProducerVersion(CompileUnit &comp_unit) {
return {};
}
virtual lldb::LanguageType ParseLanguage(CompileUnit &comp_unit) = 0;
/// Return the Xcode SDK comp_unit was compiled against.
virtual XcodeSDK ParseXcodeSDK(CompileUnit &comp_unit) { return {}; }
Expand Down
12 changes: 11 additions & 1 deletion lldb/include/lldb/Target/Process.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ class ProcessProperties : public Properties {
void SetDetachKeepsStopped(bool keep_stopped);
bool GetWarningsOptimization() const;
bool GetWarningsUnsupportedLanguage() const;
#ifdef LLDB_ENABLE_SWIFT
bool GetWarningsToolchainMismatch() const;

Choose a reason for hiding this comment

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

As a follow up patch, can we get rid of this function and the PrintWarning.* ones below by moving them into the language plugin?

#endif
bool GetStopOnExec() const;
std::chrono::seconds GetUtilityExpressionTimeout() const;
std::chrono::seconds GetInterruptTimeout() const;
Expand Down Expand Up @@ -372,7 +375,8 @@ class Process : public std::enable_shared_from_this<Process>,
enum Warnings {
eWarningsOptimization = 1,
eWarningsUnsupportedLanguage = 2,
eWarningsSwiftImport
eWarningsSwiftImport,
eWarningsToolchainMismatch
};

typedef Range<lldb::addr_t, lldb::addr_t> LoadRange;
Expand Down Expand Up @@ -1311,6 +1315,7 @@ class Process : public std::enable_shared_from_this<Process>,
/// pre-computed.
void PrintWarningOptimization(const SymbolContext &sc);

#ifdef LLDB_ENABLE_SWIFT
/// Prints a async warning message to the user one time per Process
/// for a Module whose Swift AST sections couldn't be loaded because
/// they aren't buildable on the current machine.
Expand All @@ -1320,6 +1325,11 @@ class Process : public std::enable_shared_from_this<Process>,
void PrintWarningCantLoadSwiftModule(const Module &module,
std::string details);

/// Print a user-visible warning about Swift CUs compiled with a
/// different Swift compiler than the one embedded in LLDB.
void PrintWarningToolchainMismatch(const SymbolContext &sc);
#endif

/// Print a user-visible warning about a function written in a
/// language that this version of LLDB doesn't support.
///
Expand Down
93 changes: 37 additions & 56 deletions lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -609,52 +609,45 @@ bool DWARFUnit::DW_AT_decl_file_attributes_are_invalid() {
}

bool DWARFUnit::Supports_unnamed_objc_bitfields() {
if (GetProducer() == eProducerClang) {
const uint32_t major_version = GetProducerVersionMajor();
return major_version > 425 ||
(major_version == 425 && GetProducerVersionUpdate() >= 13);
}
return true; // Assume all other compilers didn't have incorrect ObjC bitfield
// info
if (GetProducer() == eProducerClang)
return GetProducerVersion() >= llvm::VersionTuple(425, 0, 13);
// Assume all other compilers didn't have incorrect ObjC bitfield info.
return true;
Comment on lines +612 to +615

Choose a reason for hiding this comment

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

Why can't this live upstream?

Copy link
Author

Choose a reason for hiding this comment

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

This is a cherry-pick.

}

void DWARFUnit::ParseProducerInfo() {
m_producer_version_major = UINT32_MAX;
m_producer_version_minor = UINT32_MAX;
m_producer_version_update = UINT32_MAX;

m_producer = eProducerOther;
const DWARFDebugInfoEntry *die = GetUnitDIEPtrOnly();
if (die) {

const char *producer_cstr =
die->GetAttributeValueAsString(this, DW_AT_producer, nullptr);
if (producer_cstr) {
RegularExpression llvm_gcc_regex(
llvm::StringRef("^4\\.[012]\\.[01] \\(Based on Apple "
"Inc\\. build [0-9]+\\) \\(LLVM build "
"[\\.0-9]+\\)$"));
if (llvm_gcc_regex.Execute(llvm::StringRef(producer_cstr))) {
m_producer = eProducerLLVMGCC;
} else if (strstr(producer_cstr, "clang")) {
static RegularExpression g_clang_version_regex(
llvm::StringRef("clang-([0-9]+)\\.([0-9]+)\\.([0-9]+)"));
llvm::SmallVector<llvm::StringRef, 4> matches;
if (g_clang_version_regex.Execute(llvm::StringRef(producer_cstr),
&matches)) {
m_producer_version_major =
StringConvert::ToUInt32(matches[1].str().c_str(), UINT32_MAX, 10);
m_producer_version_minor =
StringConvert::ToUInt32(matches[2].str().c_str(), UINT32_MAX, 10);
m_producer_version_update =
StringConvert::ToUInt32(matches[3].str().c_str(), UINT32_MAX, 10);
}
m_producer = eProducerClang;
} else if (strstr(producer_cstr, "GNU"))
m_producer = eProducerGCC;
}
if (!die)
return;

llvm::StringRef producer(
die->GetAttributeValueAsString(this, DW_AT_producer, nullptr));
if (producer.empty())
return;

static RegularExpression g_swiftlang_version_regex(
llvm::StringRef(R"(swiftlang-([0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?))"));
static RegularExpression g_clang_version_regex(
llvm::StringRef(R"(clang-([0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?))"));
static RegularExpression g_llvm_gcc_regex(
llvm::StringRef(R"(4\.[012]\.[01] )"
R"(\(Based on Apple Inc\. build [0-9]+\) )"
R"(\(LLVM build [\.0-9]+\)$)"));

llvm::SmallVector<llvm::StringRef, 3> matches;
if (g_swiftlang_version_regex.Execute(producer, &matches)) {
m_producer_version.tryParse(matches[1]);
m_producer = eProducerSwift;
} else if (producer.contains("clang")) {
if (g_clang_version_regex.Execute(producer, &matches))
m_producer_version.tryParse(matches[1]);
m_producer = eProducerClang;
} else if (producer.contains("GNU")) {
m_producer = eProducerGCC;
} else if (g_llvm_gcc_regex.Execute(producer)) {
m_producer = eProducerLLVMGCC;
}
if (m_producer == eProducerInvalid)
m_producer = eProcucerOther;
}

DWARFProducer DWARFUnit::GetProducer() {
Expand All @@ -663,22 +656,10 @@ DWARFProducer DWARFUnit::GetProducer() {
return m_producer;
}

uint32_t DWARFUnit::GetProducerVersionMajor() {
if (m_producer_version_major == 0)
ParseProducerInfo();
return m_producer_version_major;
}

uint32_t DWARFUnit::GetProducerVersionMinor() {
if (m_producer_version_minor == 0)
ParseProducerInfo();
return m_producer_version_minor;
}

uint32_t DWARFUnit::GetProducerVersionUpdate() {
if (m_producer_version_update == 0)
llvm::VersionTuple DWARFUnit::GetProducerVersion() {
if (m_producer_version.empty())
ParseProducerInfo();
return m_producer_version_update;
return m_producer_version;
}

uint64_t DWARFUnit::GetDWARFLanguageType() {
Expand Down
13 changes: 4 additions & 9 deletions lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ enum DWARFProducer {
eProducerClang,
eProducerGCC,
eProducerLLVMGCC,
eProcucerOther
eProducerSwift,
eProducerOther
};

/// Base class describing the header of any kind of "unit." Some information
Expand Down Expand Up @@ -194,11 +195,7 @@ class DWARFUnit : public lldb_private::UserID {

DWARFProducer GetProducer();

uint32_t GetProducerVersionMajor();

uint32_t GetProducerVersionMinor();

uint32_t GetProducerVersionUpdate();
llvm::VersionTuple GetProducerVersion();

uint64_t GetDWARFLanguageType();

Expand Down Expand Up @@ -308,9 +305,7 @@ class DWARFUnit : public lldb_private::UserID {
std::unique_ptr<DWARFDebugAranges> m_func_aranges_up;
dw_addr_t m_base_addr = 0;
DWARFProducer m_producer = eProducerInvalid;
uint32_t m_producer_version_major = 0;
uint32_t m_producer_version_minor = 0;
uint32_t m_producer_version_update = 0;
llvm::VersionTuple m_producer_version;
llvm::Optional<uint64_t> m_language_type;
lldb_private::LazyBool m_is_optimized = lldb_private::eLazyBoolCalculate;
llvm::Optional<lldb_private::FileSpec> m_comp_dir;
Expand Down
10 changes: 10 additions & 0 deletions lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -804,6 +804,16 @@ bool SymbolFileDWARF::FixupAddress(Address &addr) {
// This is a normal DWARF file, no address fixups need to happen
return true;
}

llvm::VersionTuple SymbolFileDWARF::GetProducerVersion(CompileUnit &comp_unit) {
std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
DWARFUnit *dwarf_cu = GetDWARFCompileUnit(&comp_unit);
if (dwarf_cu)
return dwarf_cu->GetProducerVersion();
else
return {};
}

lldb::LanguageType SymbolFileDWARF::ParseLanguage(CompileUnit &comp_unit) {
std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
DWARFUnit *dwarf_cu = GetDWARFCompileUnit(&comp_unit);
Expand Down
2 changes: 2 additions & 0 deletions lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ class SymbolFileDWARF : public lldb_private::SymbolFile,
void InitializeObject() override;

// Compile Unit function calls
llvm::VersionTuple
GetProducerVersion(lldb_private::CompileUnit &comp_unit) override;

lldb::LanguageType
ParseLanguage(lldb_private::CompileUnit &comp_unit) override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,15 @@ size_t SymbolFileDWARFDebugMap::GetCompUnitInfosForModule(
return cu_infos.size();
}

llvm::VersionTuple
SymbolFileDWARFDebugMap::GetProducerVersion(CompileUnit &comp_unit) {
std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
SymbolFileDWARF *oso_dwarf = GetSymbolFile(comp_unit);
if (oso_dwarf)
return oso_dwarf->GetProducerVersion(comp_unit);
return {};
}

lldb::LanguageType
SymbolFileDWARFDebugMap::ParseLanguage(CompileUnit &comp_unit) {
std::lock_guard<std::recursive_mutex> guard(GetModuleMutex());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ class SymbolFileDWARFDebugMap : public lldb_private::SymbolFile {
void InitializeObject() override;

// Compile Unit function calls
llvm::VersionTuple
GetProducerVersion(lldb_private::CompileUnit &comp_unit) override;
lldb::LanguageType
ParseLanguage(lldb_private::CompileUnit &comp_unit) override;
lldb_private::XcodeSDK
Expand Down
32 changes: 32 additions & 0 deletions lldb/source/Target/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "lldb/Interpreter/OptionValueProperties.h"
#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Symbol/SymbolFile.h"
#include "lldb/Target/ABI.h"
#include "lldb/Target/AssertFrameRecognizer.h"
#include "lldb/Target/DynamicLoader.h"
Expand Down Expand Up @@ -71,6 +72,10 @@
#include "lldb/Utility/State.h"
#include "lldb/Utility/Timer.h"

#ifdef LLDB_ENABLE_SWIFT
#include "swift/Basic/Version.h"
#endif

using namespace lldb;
using namespace lldb_private;
using namespace std::chrono;
Expand Down Expand Up @@ -285,6 +290,14 @@ bool ProcessProperties::GetWarningsUnsupportedLanguage() const {
nullptr, idx, g_process_properties[idx].default_uint_value != 0);
}

#ifdef LLDB_ENABLE_SWIFT
bool ProcessProperties::GetWarningsToolchainMismatch() const {
const uint32_t idx = ePropertyWarningToolchainMismatch;
return m_collection_sp->GetPropertyAtIndexAsBoolean(
nullptr, idx, g_process_properties[idx].default_uint_value != 0);
}
#endif

bool ProcessProperties::GetStopOnExec() const {
const uint32_t idx = ePropertyStopOnExec;
return m_collection_sp->GetPropertyAtIndexAsBoolean(
Expand Down Expand Up @@ -5869,13 +5882,32 @@ void Process::PrintWarningOptimization(const SymbolContext &sc) {
}
}

#ifdef LLDB_ENABLE_SWIFT
void Process::PrintWarningCantLoadSwiftModule(const Module &module,
std::string details) {
PrintWarning(Process::Warnings::eWarningsSwiftImport, (void *)&module,
"%s: Cannot load Swift type information; %s\n",
module.GetFileSpec().GetCString(), details.c_str());
}

void Process::PrintWarningToolchainMismatch(const SymbolContext &sc) {
if (!GetWarningsToolchainMismatch())
return;
if (!sc.module_sp || !sc.comp_unit)
return;
if (sc.GetLanguage() != eLanguageTypeSwift)
return;
if (SymbolFile *sym_file = sc.module_sp->GetSymbolFile())
if (sym_file->GetProducerVersion(*sc.comp_unit) !=
swift::version::Version::getCurrentCompilerVersion())
PrintWarning(Process::Warnings::eWarningsToolchainMismatch,
sc.module_sp.get(),
"%s was compiled with a Swift compiler from a different "
"toolchain. Swift expression evaluation may not work.\n",
sc.module_sp->GetFileSpec().GetFilename().GetCString());
}
#endif

void Process::PrintWarningUnsupportedLanguage(const SymbolContext &sc) {
if (!GetWarningsUnsupportedLanguage())
return;
Expand Down
3 changes: 3 additions & 0 deletions lldb/source/Target/TargetProperties.td
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,9 @@ let Definition = "process" in {
def WarningUnsupportedLanguage: Property<"unsupported-language-warnings", "Boolean">,
DefaultTrue,
Desc<"If true, warn when stopped in code that is written in a source language that LLDB does not support.">;
def WarningToolchainMismatch: Property<"toolchain-mismatch-warnings", "Boolean">,

Choose a reason for hiding this comment

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

Which scenarios would people want this off?

Copy link
Author

Choose a reason for hiding this comment

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

Any warning that potentially prints on every frame should be switchable. More specifically, I can see how this warning gets annoying when you use LLDB to debug or symbolicate crashes of previously built production software where you just don't have access to a historical matching LLDB.

DefaultTrue,
Desc<"If true, warn when stopped in code that was compiled by a Swift compiler different from the one embedded in LLDB.">;
def StopOnExec: Property<"stop-on-exec", "Boolean">,
Global,
DefaultTrue,
Expand Down
9 changes: 8 additions & 1 deletion lldb/source/Target/Thread.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,11 +322,18 @@ void Thread::FrameSelectedCallback(StackFrame *frame) {

if (frame->HasDebugInformation() &&
(GetProcess()->GetWarningsOptimization() ||
GetProcess()->GetWarningsUnsupportedLanguage())) {
GetProcess()->GetWarningsUnsupportedLanguage()
#ifdef LLDB_ENABLE_SWIFT
|| GetProcess()->GetWarningsToolchainMismatch())
#endif
) {
SymbolContext sc =
frame->GetSymbolContext(eSymbolContextFunction | eSymbolContextModule);
GetProcess()->PrintWarningOptimization(sc);
GetProcess()->PrintWarningUnsupportedLanguage(sc);
#ifdef LLDB_ENABLE_SWIFT
GetProcess()->PrintWarningToolchainMismatch(sc);
#endif
}
SymbolContext msc = frame->GetSymbolContext(eSymbolContextModule);
if (msc.module_sp)
Expand Down
38 changes: 38 additions & 0 deletions lldb/test/Shell/Swift/ToolchainMismatch.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# REQUIRES: swift

# Tests that a warning is printed when stopped in a Swift frame
# compiled by a different Swift compiler than the one embedded in LLDB.

# RUN: rm -rf %t && mkdir %t && cd %t
# RUN: %target-swiftc -g \
# RUN: -module-cache-path %t/cache %S/Inputs/main.swift \
# RUN: -module-name main -o %t/a.ll -emit-ir
# RUN: sed -i -e 's/producer: "[^"]*Swift [^"]*"/producer: "Future Swift (swiftlang-9999.8.7.6)"/g' %t/a.ll
# RUN: %clang_host -c %t/a.ll -o %t/a.o
# RUN: llvm-dwarfdump -r 0 %t/a.o | grep -q swiftlang-9999
# RUN: %target-swiftc \
# RUN: -module-cache-path %t/cache \
# RUN: %t/a.o -o %t/a.out
# RUN: %lldb %t/a.out -s %s 2>&1 | FileCheck %s

# Sanity check: Swift
# RUN: %target-swiftc -g \
# RUN: -module-cache-path %t/cache %S/Inputs/main.swift \
# RUN: -module-name main -o %t/good.out
# RUN: %lldb %t/good.out -s %s 2>&1 | FileCheck %s --check-prefix=SANITY

# Sanity check: Clang
# RUN: %clang_host -g \
# RUN: %S/../Driver/Inputs/hello.cpp \
# RUN: -o %t/clang.out
# RUN: %lldb %t/clang.out -s %s 2>&1 | FileCheck %s --check-prefix=SANITY

b main
run
quit

# The {{ }} avoids accidentally matching the input script!
# CHECK: {{a\.out}} was compiled with a Swift compiler from a different toolchain.
# CHECK: stop reason{{ = }}breakpoint
# SANITY-NOT: {{a\.out}} was compiled with a Swift compiler from a different toolchain.
# SANITY: stop reason{{ = }}breakpoint
Loading