[lldb] Allow limiting the number of error diagnostics when parsing an expression

While debugging another bug I found out that we currently don't set any limit
for the number of diagnostics Clang emits. If a user does something that
generates a lot of errors (like including some long header file from within the
expression function), then we currently spam the LLDB output with potentially
thousands of Clang error diagnostics.

Clang sets a default limit of 20 errors, but given that LLDB is often used
interactively for small expressions I would say a limit of 5 is enough. The
limit is implemented as a setting, so if a user cares about seeing having a
million errors printed to their terminal then they can just increase the
settings value.

Reviewed By: shafik, mib

Differential Revision: https://reviews.llvm.org/D88889
This commit is contained in:
Raphael Isemann 2020-10-13 17:12:07 +02:00
parent 24e07570cc
commit 02114e15da
7 changed files with 82 additions and 0 deletions

View File

@ -173,6 +173,8 @@ public:
llvm::StringRef GetExpressionPrefixContents(); llvm::StringRef GetExpressionPrefixContents();
uint64_t GetExprErrorLimit() const;
bool GetUseHexImmediates() const; bool GetUseHexImmediates() const;
bool GetUseFastStepping() const; bool GetUseFastStepping() const;

View File

@ -454,6 +454,10 @@ ClangExpressionParser::ClangExpressionParser(
// 4. Create and install the target on the compiler. // 4. Create and install the target on the compiler.
m_compiler->createDiagnostics(); m_compiler->createDiagnostics();
// Limit the number of error diagnostics we emit.
// A value of 0 means no limit for both LLDB and Clang.
m_compiler->getDiagnostics().setErrorLimit(target_sp->GetExprErrorLimit());
auto target_info = TargetInfo::CreateTargetInfo( auto target_info = TargetInfo::CreateTargetInfo(
m_compiler->getDiagnostics(), m_compiler->getInvocation().TargetOpts); m_compiler->getDiagnostics(), m_compiler->getInvocation().TargetOpts);
if (log) { if (log) {

View File

@ -4026,6 +4026,12 @@ llvm::StringRef TargetProperties::GetExpressionPrefixContents() {
return ""; return "";
} }
uint64_t TargetProperties::GetExprErrorLimit() const {
const uint32_t idx = ePropertyExprErrorLimit;
return m_collection_sp->GetPropertyAtIndexAsUInt64(
nullptr, idx, g_target_properties[idx].default_uint_value);
}
bool TargetProperties::GetBreakpointsConsultPlatformAvoidList() { bool TargetProperties::GetBreakpointsConsultPlatformAvoidList() {
const uint32_t idx = ePropertyBreakpointUseAvoidList; const uint32_t idx = ePropertyBreakpointUseAvoidList;
return m_collection_sp->GetPropertyAtIndexAsBoolean( return m_collection_sp->GetPropertyAtIndexAsBoolean(

View File

@ -20,6 +20,10 @@ let Definition = "target" in {
def ExprPrefix: Property<"expr-prefix", "FileSpec">, def ExprPrefix: Property<"expr-prefix", "FileSpec">,
DefaultStringValue<"">, DefaultStringValue<"">,
Desc<"Path to a file containing expressions to be prepended to all expressions.">; Desc<"Path to a file containing expressions to be prepended to all expressions.">;
def ExprErrorLimit: Property<"expr-error-limit", "UInt64">,
DefaultUnsignedValue<5>,
Desc<"The maximum amount of errors to emit while parsing an expression. "
"A value of 0 means to always continue parsing if possible.">;
def PreferDynamic: Property<"prefer-dynamic-value", "Enum">, def PreferDynamic: Property<"prefer-dynamic-value", "Enum">,
DefaultEnumValue<"eDynamicDontRunTarget">, DefaultEnumValue<"eDynamicDontRunTarget">,
EnumValues<"OptionEnumValues(g_dynamic_value_types)">, EnumValues<"OptionEnumValues(g_dynamic_value_types)">,

View File

@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@ -0,0 +1,60 @@
"""
Tests target.expr-error-limit.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
class TestCase(TestBase):
mydir = TestBase.compute_mydir(__file__)
@no_debug_info_test
def test(self):
# FIXME: The only reason this test needs to create a real target is because
# the settings of the dummy target can't be changed with `settings set`.
self.build()
target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
# Our test expression that is just several lines of malformed
# integer literals (with a 'yerror' integer suffix). Every error
# has its own unique string (1, 2, 3, 4) and is on its own line
# that we can later find it when Clang prints the respective source
# code for each error to the error output.
# For example, in the error output below we would look for the
# unique `1yerror` string:
# error: <expr>:1:2: invalid suffix 'yerror' on integer constant
# 1yerror
# ^
expr = "1yerror;\n2yerror;\n3yerror;\n4yerror;"
options = lldb.SBExpressionOptions()
options.SetAutoApplyFixIts(False)
# Evaluate the expression and check that only the first 2 errors are
# emitted.
self.runCmd("settings set target.expr-error-limit 2")
eval_result = target.EvaluateExpression(expr, options)
self.assertIn("1yerror", str(eval_result.GetError()))
self.assertIn("2yerror", str(eval_result.GetError()))
self.assertNotIn("3yerror", str(eval_result.GetError()))
self.assertNotIn("4yerror", str(eval_result.GetError()))
# Change to a 3 errors and check again which errors are emitted.
self.runCmd("settings set target.expr-error-limit 3")
eval_result = target.EvaluateExpression(expr, options)
self.assertIn("1yerror", str(eval_result.GetError()))
self.assertIn("2yerror", str(eval_result.GetError()))
self.assertIn("3yerror", str(eval_result.GetError()))
self.assertNotIn("4yerror", str(eval_result.GetError()))
# Disable the error limit and make sure all errors are emitted.
self.runCmd("settings set target.expr-error-limit 0")
eval_result = target.EvaluateExpression(expr, options)
self.assertIn("1yerror", str(eval_result.GetError()))
self.assertIn("2yerror", str(eval_result.GetError()))
self.assertIn("3yerror", str(eval_result.GetError()))
self.assertIn("4yerror", str(eval_result.GetError()))

View File

@ -0,0 +1,3 @@
int main() {
return 0; // break here
}