DIAGOPT(PedanticErrors, 1, 0) /// -pedantic-errors
DIAGOPT(ShowColumn, 1, 1) /// Show column number on diagnostics.
DIAGOPT(ShowLocation, 1, 1) /// Show source location information.
+DIAGOPT(ShowLevel, 1, 1) /// Show diagnostic level.
DIAGOPT(AbsolutePath, 1, 0) /// Use absolute paths.
DIAGOPT(ShowCarets, 1, 1) /// Show carets in diagnostics.
DIAGOPT(ShowFixits, 1, 1) /// Show fixit information.
if (DiagOpts->ShowColors)
OS.resetColor();
- printDiagnosticLevel(OS, Level, DiagOpts->ShowColors,
- DiagOpts->CLFallbackMode);
+ if (DiagOpts->ShowLevel)
+ printDiagnosticLevel(OS, Level, DiagOpts->ShowColors,
+ DiagOpts->CLFallbackMode);
printDiagnosticMessage(OS,
/*IsSupplemental*/ Level == DiagnosticsEngine::Note,
Message, OS.tell() - StartOfLocationInfo,
--- /dev/null
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
--- /dev/null
+"""
+Test the diagnostics emitted by our embeded Clang instance that parses expressions.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+from lldbsuite.test.decorators import *
+
+class ExprDiagnosticsTestCase(TestBase):
+
+ mydir = TestBase.compute_mydir(__file__)
+
+ def setUp(self):
+ # Call super's setUp().
+ TestBase.setUp(self)
+
+ self.main_source = "main.cpp"
+ self.main_source_spec = lldb.SBFileSpec(self.main_source)
+
+ def test_source_and_caret_printing(self):
+ """Test that the source and caret positions LLDB prints are correct"""
+ self.build()
+
+ (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
+ '// Break here', self.main_source_spec)
+ frame = thread.GetFrameAtIndex(0)
+
+ # Test that source/caret are at the right position.
+ value = frame.EvaluateExpression("unknown_identifier")
+ self.assertFalse(value.GetError().Success())
+ # We should get a nice diagnostic with a caret pointing at the start of
+ # the identifier.
+ self.assertIn("\nunknown_identifier\n^\n", value.GetError().GetCString())
+ self.assertIn("<user expression 0>:1:1", value.GetError().GetCString())
+
+ # Same as above but with the identifier in the middle.
+ value = frame.EvaluateExpression("1 + unknown_identifier ")
+ self.assertFalse(value.GetError().Success())
+ self.assertIn("\n1 + unknown_identifier", value.GetError().GetCString())
+ self.assertIn("\n ^\n", value.GetError().GetCString())
+
+ # Multiline expressions.
+ value = frame.EvaluateExpression("int a = 0;\nfoobar +=1;\na")
+ self.assertFalse(value.GetError().Success())
+ # We should still get the right line information and caret position.
+ self.assertIn("\nfoobar +=1;\n^\n", value.GetError().GetCString())
+ # It's the second line of the user expression.
+ self.assertIn("<user expression 2>:2:1", value.GetError().GetCString())
+
+ # Top-level expressions.
+ top_level_opts = lldb.SBExpressionOptions();
+ top_level_opts.SetTopLevel(True)
+
+ value = frame.EvaluateExpression("void foo(unknown_type x) {}", top_level_opts)
+ self.assertFalse(value.GetError().Success())
+ self.assertIn("\nvoid foo(unknown_type x) {}\n ^\n", value.GetError().GetCString())
+ # Top-level expressions might use a different wrapper code, but the file name should still
+ # be the same.
+ self.assertIn("<user expression 3>:1:10", value.GetError().GetCString())
+
+ # Multiline top-level expressions.
+ value = frame.EvaluateExpression("void x() {}\nvoid foo(unknown_type x) {}", top_level_opts)
+ self.assertFalse(value.GetError().Success())
+ self.assertIn("\nvoid foo(unknown_type x) {}\n ^\n", value.GetError().GetCString())
+ self.assertIn("<user expression 4>:2:10", value.GetError().GetCString())
+
+ # Test that we render Clang's 'notes' correctly.
+ value = frame.EvaluateExpression("struct SFoo{}; struct SFoo { int x; };", top_level_opts)
+ self.assertFalse(value.GetError().Success())
+ self.assertIn("<user expression 5>:1:8: previous definition is here\nstruct SFoo{}; struct SFoo { int x; };\n ^\n", value.GetError().GetCString())
+
+ # Declarations from the debug information currently have no debug information. It's not clear what
+ # we should do in this case, but we should at least not print anything that's wrong.
+ # In the future our declarations should have valid source locations.
+ value = frame.EvaluateExpression("struct FooBar { double x };", top_level_opts)
+ self.assertFalse(value.GetError().Success())
+ self.assertEqual("error: <user expression 6>:1:8: redefinition of 'FooBar'\nstruct FooBar { double x };\n ^\n", value.GetError().GetCString())
+
+ value = frame.EvaluateExpression("foo(1, 2)")
+ self.assertFalse(value.GetError().Success())
+ self.assertEqual("error: <user expression 7>:1:1: no matching function for call to 'foo'\nfoo(1, 2)\n^~~\nnote: candidate function not viable: requires single argument 'x', but 2 arguments were provided\n\n", value.GetError().GetCString())
+
+ # Redefine something that we defined in a user-expression. We should use the previous expression file name
+ # for the original decl.
+ value = frame.EvaluateExpression("struct Redef { double x; };", top_level_opts)
+ value = frame.EvaluateExpression("struct Redef { float y; };", top_level_opts)
+ self.assertFalse(value.GetError().Success())
+ self.assertIn("error: <user expression 9>:1:8: redefinition of 'Redef'\nstruct Redef { float y; };\n ^\n<user expression 8>:1:8: previous definition is here\nstruct Redef { double x; };\n ^", value.GetError().GetCString())
+
+ @skipUnlessDarwin
+ def test_source_locations_from_objc_modules(self):
+ self.build()
+
+ (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
+ '// Break here', self.main_source_spec)
+ frame = thread.GetFrameAtIndex(0)
+
+ # Import foundation so that the Obj-C module is loaded (which contains source locations
+ # that can be used by LLDB).
+ self.runCmd("expr @import Foundation")
+ value = frame.EvaluateExpression("NSLog(1);")
+ self.assertFalse(value.GetError().Success())
+ print(value.GetError().GetCString())
+ # LLDB should print the source line that defines NSLog. To not rely on any
+ # header paths/line numbers or the actual formatting of the Foundation headers, only look
+ # for a few tokens in the output.
+ # File path should come from Foundation framework.
+ self.assertIn("/Foundation.framework/", value.GetError().GetCString())
+ # The NSLog definition source line should be printed. Return value and
+ # the first argument are probably stable enough that this test can check for them.
+ self.assertIn("void NSLog(NSString *format", value.GetError().GetCString())
+
--- /dev/null
+void foo(int x) {}
+
+struct FooBar {
+ int i;
+};
+
+int main() {
+ FooBar f;
+ foo(1);
+ return 0; // Break here
+}
"expression self->non_existent_member",
COMMAND_FAILED_AS_EXPECTED,
error=True,
- startstr="error: 'MyString' does not have a member named 'non_existent_member'")
+ substrs=["error:", "'MyString' does not have a member named 'non_existent_member'"])
# Use expression parser.
self.runCmd("expression self->str")
return diag->getKind() == eDiagnosticOriginClang;
}
- ClangDiagnostic(const char *message, DiagnosticSeverity severity,
+ ClangDiagnostic(llvm::StringRef message, DiagnosticSeverity severity,
uint32_t compiler_id)
: Diagnostic(message, severity, eDiagnosticOriginClang, compiler_id) {}
class ClangDiagnosticManagerAdapter : public clang::DiagnosticConsumer {
public:
- ClangDiagnosticManagerAdapter()
- : m_passthrough(new clang::TextDiagnosticBuffer) {}
-
- ClangDiagnosticManagerAdapter(
- const std::shared_ptr<clang::TextDiagnosticBuffer> &passthrough)
- : m_passthrough(passthrough) {}
+ ClangDiagnosticManagerAdapter(DiagnosticOptions &opts) {
+ DiagnosticOptions *m_options = new DiagnosticOptions(opts);
+ m_options->ShowPresumedLoc = true;
+ m_options->ShowLevel = false;
+ m_os.reset(new llvm::raw_string_ostream(m_output));
+ m_passthrough.reset(
+ new clang::TextDiagnosticPrinter(*m_os, m_options, false));
+ }
void ResetManager(DiagnosticManager *manager = nullptr) {
m_manager = manager;
void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel,
const clang::Diagnostic &Info) override {
- if (m_manager) {
- llvm::SmallVector<char, 32> diag_str;
- Info.FormatDiagnostic(diag_str);
- diag_str.push_back('\0');
- const char *data = diag_str.data();
+ // Render diagnostic message to m_output.
+ m_output.clear();
+ m_passthrough->HandleDiagnostic(DiagLevel, Info);
+ m_os->flush();
+ if (m_manager) {
lldb_private::DiagnosticSeverity severity;
bool make_new_diagnostic = true;
severity = eDiagnosticSeverityRemark;
break;
case DiagnosticsEngine::Level::Note:
- m_manager->AppendMessageToDiagnostic(data);
+ m_manager->AppendMessageToDiagnostic(m_output);
make_new_diagnostic = false;
}
if (make_new_diagnostic) {
+ // ClangDiagnostic messages are expected to have no whitespace/newlines
+ // around them.
+ std::string stripped_output = llvm::StringRef(m_output).trim();
+
ClangDiagnostic *new_diagnostic =
- new ClangDiagnostic(data, severity, Info.getID());
+ new ClangDiagnostic(stripped_output, severity, Info.getID());
m_manager->AddDiagnostic(new_diagnostic);
// Don't store away warning fixits, since the compiler doesn't have
}
}
}
-
- m_passthrough->HandleDiagnostic(DiagLevel, Info);
- }
-
- void FlushDiagnostics(DiagnosticsEngine &Diags) {
- m_passthrough->FlushDiagnostics(Diags);
- }
-
- DiagnosticConsumer *clone(DiagnosticsEngine &Diags) const {
- return new ClangDiagnosticManagerAdapter(m_passthrough);
}
- clang::TextDiagnosticBuffer *GetPassthrough() { return m_passthrough.get(); }
+ clang::TextDiagnosticPrinter *GetPassthrough() { return m_passthrough.get(); }
private:
DiagnosticManager *m_manager = nullptr;
- std::shared_ptr<clang::TextDiagnosticBuffer> m_passthrough;
+ std::shared_ptr<clang::TextDiagnosticPrinter> m_passthrough;
+ /// Output stream of m_passthrough.
+ std::shared_ptr<llvm::raw_string_ostream> m_os;
+ /// Output string filled by m_os.
+ std::string m_output;
};
static void SetupModuleHeaderPaths(CompilerInstance *compiler,
// Implementation of ClangExpressionParser
//===----------------------------------------------------------------------===//
-ClangExpressionParser::ClangExpressionParser(
- ExecutionContextScope *exe_scope, Expression &expr,
- bool generate_debug_info, std::vector<std::string> include_directories)
+ClangExpressionParser::ClangExpressionParser(ExecutionContextScope *exe_scope, Expression &expr,
+ bool generate_debug_info, std::vector<std::string> include_directories, std::string filename)
: ExpressionParser(exe_scope, expr, generate_debug_info), m_compiler(),
m_pp_callbacks(nullptr),
- m_include_directories(std::move(include_directories)) {
+ m_include_directories(std::move(include_directories)), m_filename(std::move(filename)) {
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));
// We can't compile expressions without a target. So if the exe_scope is
// 6. Set up the diagnostic buffer for reporting errors
- m_compiler->getDiagnostics().setClient(new ClangDiagnosticManagerAdapter);
+ auto diag_mgr = new ClangDiagnosticManagerAdapter(
+ m_compiler->getDiagnostics().getDiagnosticOptions());
+ m_compiler->getDiagnostics().setClient(diag_mgr);
// 7. Set up the source management objects inside the compiler
m_compiler->createFileManager();
ClangDiagnosticManagerAdapter *adapter =
static_cast<ClangDiagnosticManagerAdapter *>(
m_compiler->getDiagnostics().getClient());
- clang::TextDiagnosticBuffer *diag_buf = adapter->GetPassthrough();
- diag_buf->FlushDiagnostics(m_compiler->getDiagnostics());
+ auto diag_buf = adapter->GetPassthrough();
adapter->ResetManager(&diagnostic_manager);
if (!created_main_file) {
std::unique_ptr<MemoryBuffer> memory_buffer =
- MemoryBuffer::getMemBufferCopy(expr_text, "<lldb-expr>");
+ MemoryBuffer::getMemBufferCopy(expr_text, m_filename);
source_mgr.setMainFileID(source_mgr.createFileID(std::move(memory_buffer)));
}
/// @param[in] include_directories
/// List of include directories that should be used when parsing the
/// expression.
+ ///
+ /// @param[in] filename
+ /// Name of the source file that should be used when rendering
+ /// diagnostics (i.e. errors, warnings or notes from Clang).
ClangExpressionParser(ExecutionContextScope *exe_scope, Expression &expr,
bool generate_debug_info,
- std::vector<std::string> include_directories = {});
+ std::vector<std::string> include_directories = {},
+ std::string filename = "<clang expression>");
/// Destructor
~ClangExpressionParser() override;
std::unique_ptr<ClangASTContext> m_ast_context;
std::vector<std::string> m_include_directories;
+ /// File name used for the user expression.
+ std::string m_filename;
};
}
using namespace lldb_private;
-const char *ClangExpressionSourceCode::g_expression_prefix = R"(
+const char *ClangExpressionSourceCode::g_expression_prefix =
+ R"(
+#line 1 "<lldb wrapper prefix>"
#ifndef offsetof
#define offsetof(t, d) __builtin_offsetof(t, d)
#endif
}
)";
-static const char *c_start_marker = " /*LLDB_BODY_START*/\n ";
-static const char *c_end_marker = ";\n /*LLDB_BODY_END*/\n";
-
namespace {
class AddMacroState {
}
}
+lldb_private::ClangExpressionSourceCode::ClangExpressionSourceCode(
+ llvm::StringRef filename, llvm::StringRef name, llvm::StringRef prefix,
+ llvm::StringRef body, Wrapping wrap)
+ : ExpressionSourceCode(name, prefix, body, wrap) {
+ // Use #line markers to pretend that we have a single-line source file
+ // containing only the user expression. This will hide our wrapper code
+ // from the user when we render diagnostics with Clang.
+ m_start_marker = "#line 1 \"" + filename.str() + "\"\n";
+ m_end_marker = "\n;\n#line 1 \"<lldb wrapper suffix>\"\n";
+}
+
namespace {
/// Allows checking if a token is contained in a given expression.
class TokenVerifier {
case lldb::eLanguageTypeC:
case lldb::eLanguageTypeC_plus_plus:
case lldb::eLanguageTypeObjC:
- tagged_body.append(c_start_marker);
+ tagged_body.append(m_start_marker);
tagged_body.append(m_body);
- tagged_body.append(c_end_marker);
+ tagged_body.append(m_end_marker);
break;
}
switch (wrapping_language) {
bool ClangExpressionSourceCode::GetOriginalBodyBounds(
std::string transformed_text, lldb::LanguageType wrapping_language,
size_t &start_loc, size_t &end_loc) {
- const char *start_marker;
- const char *end_marker;
-
switch (wrapping_language) {
default:
return false;
case lldb::eLanguageTypeC:
case lldb::eLanguageTypeC_plus_plus:
case lldb::eLanguageTypeObjC:
- start_marker = c_start_marker;
- end_marker = c_end_marker;
break;
}
- start_loc = transformed_text.find(start_marker);
+ start_loc = transformed_text.find(m_start_marker);
if (start_loc == std::string::npos)
return false;
- start_loc += strlen(start_marker);
- end_loc = transformed_text.find(end_marker);
+ start_loc += m_start_marker.size();
+ end_loc = transformed_text.find(m_end_marker);
return end_loc != std::string::npos;
}
public:
static const char *g_expression_prefix;
- static ClangExpressionSourceCode *CreateWrapped(llvm::StringRef prefix,
+ static ClangExpressionSourceCode *CreateWrapped(llvm::StringRef filename,
+ llvm::StringRef prefix,
llvm::StringRef body) {
- return new ClangExpressionSourceCode("$__lldb_expr", prefix, body, Wrap);
+ return new ClangExpressionSourceCode(filename, "$__lldb_expr", prefix, body,
+ Wrap);
}
/// Generates the source code that will evaluate the expression.
// Given a string returned by GetText, find the beginning and end of the body
// passed to CreateWrapped. Return true if the bounds could be found. This
// will also work on text with FixItHints applied.
- static bool GetOriginalBodyBounds(std::string transformed_text,
- lldb::LanguageType wrapping_language,
- size_t &start_loc, size_t &end_loc);
+ bool GetOriginalBodyBounds(std::string transformed_text,
+ lldb::LanguageType wrapping_language,
+ size_t &start_loc, size_t &end_loc);
protected:
- ClangExpressionSourceCode(llvm::StringRef name, llvm::StringRef prefix,
- llvm::StringRef body, Wrapping wrap)
- : ExpressionSourceCode(name, prefix, body, wrap) {}
+ ClangExpressionSourceCode(llvm::StringRef filename, llvm::StringRef name,
+ llvm::StringRef prefix, llvm::StringRef body,
+ Wrapping wrap);
+
+private:
+ /// String marking the start of the user expression.
+ std::string m_start_marker;
+ /// String marking the end of the user expression.
+ std::string m_end_marker;
};
} // namespace lldb_private
return "$";
}
+ /// Returns the next file name that should be used for user expressions.
+ std::string GetNextExprFileName() {
+ std::string name;
+ name.append("<user expression ");
+ name.append(std::to_string(m_next_user_file_id++));
+ name.append(">");
+ return name;
+ }
+
llvm::Optional<CompilerType>
GetCompilerTypeFromPersistentDecl(ConstString type_name) override;
}
private:
+ /// The counter used by GetNextExprFileName.
+ uint32_t m_next_user_file_id = 0;
// The counter used by GetNextPersistentVariableName
uint32_t m_next_persistent_variable_id = 0;
#include "ClangDiagnostic.h"
#include "ClangExpressionDeclMap.h"
#include "ClangExpressionParser.h"
-#include "ClangExpressionSourceCode.h"
#include "ClangModulesDeclVendor.h"
#include "ClangPersistentVariables.h"
if (PersistentExpressionState *persistent_state =
target->GetPersistentExpressionStateForLanguage(
lldb::eLanguageTypeC)) {
+ m_clang_state = llvm::cast<ClangPersistentVariables>(persistent_state);
m_result_delegate.RegisterPersistentState(persistent_state);
} else {
diagnostic_manager.PutString(
DiagnosticManager &diagnostic_manager, ExecutionContext &exe_ctx,
std::vector<std::string> modules_to_import, bool for_completion) {
+ m_filename = m_clang_state->GetNextExprFileName();
std::string prefix = m_expr_prefix;
if (m_options.GetExecutionPolicy() == eExecutionPolicyTopLevel) {
m_transformed_text = m_expr_text;
} else {
- std::unique_ptr<ClangExpressionSourceCode> source_code(
- ClangExpressionSourceCode::CreateWrapped(prefix.c_str(),
- m_expr_text.c_str()));
+ m_source_code.reset(ClangExpressionSourceCode::CreateWrapped(
+ m_filename, prefix.c_str(), m_expr_text.c_str()));
- if (!source_code->GetText(m_transformed_text, m_expr_lang,
- m_in_static_method, exe_ctx, !m_ctx_obj,
- for_completion, modules_to_import)) {
+ if (!m_source_code->GetText(m_transformed_text, m_expr_lang,
+ m_in_static_method, exe_ctx, !m_ctx_obj,
+ for_completion, modules_to_import)) {
diagnostic_manager.PutString(eDiagnosticSeverityError,
"couldn't construct expression body");
return;
// transformed code. We need this later for the code completion.
std::size_t original_start;
std::size_t original_end;
- bool found_bounds = source_code->GetOriginalBodyBounds(
+ bool found_bounds = m_source_code->GetOriginalBodyBounds(
m_transformed_text, m_expr_lang, original_start, original_end);
if (found_bounds)
m_user_expression_start_pos = original_start;
// parser_sp will never be empty.
ClangExpressionParser parser(exe_scope, *this, generate_debug_info,
- m_include_directories);
+ m_include_directories, m_filename);
unsigned num_errors = parser.Parse(diagnostic_manager);
size_t fixed_end;
const std::string &fixed_expression =
diagnostic_manager.GetFixedExpression();
- if (ClangExpressionSourceCode::GetOriginalBodyBounds(
- fixed_expression, m_expr_lang, fixed_start, fixed_end))
+ if (m_source_code->GetOriginalBodyBounds(fixed_expression, m_expr_lang,
+ fixed_start, fixed_end))
m_fixed_text =
fixed_expression.substr(fixed_start, fixed_end - fixed_start);
}
#include "ASTStructExtractor.h"
#include "ClangExpressionDeclMap.h"
#include "ClangExpressionHelper.h"
+#include "ClangExpressionSourceCode.h"
#include "ClangExpressionVariable.h"
#include "IRForTarget.h"
/// were not able to calculate this position.
llvm::Optional<size_t> m_user_expression_start_pos;
ResultDelegate m_result_delegate;
+ ClangPersistentVariables *m_clang_state;
+ std::unique_ptr<ClangExpressionSourceCode> m_source_code;
+ /// File name used for the expression.
+ std::string m_filename;
/// The object (if any) in which context the expression is evaluated.
/// See the comment to `UserExpression::Evaluate` for details.