Files
clang-p2996/lldb/test/API/commands/expression/completion/TestExprCompletion.py
Raphael Isemann 74a51753a6 [lldb] Make order of completions for expressions deterministic and sorted by Clang's priority values.
Summary:

It turns out that the order in which we provide completions for expressions is
nondeterministic. This leads to confusing user experience and also breaks the
reproducer tests (as two LLDB tests can go out of sync due to the
non-determinism in the completion lists)

The reason for the non-determinism is that the CompletionConsumer informs us
about decls in the order in which it finds declarations in the lookup store of
the DeclContexts it visits (mainly this snippet in SemaLookup.cpp):

``` lang=c++
    // Enumerate all of the results in this context.
    for (DeclContextLookupResult R :
         Load ? Ctx->lookups()
              : Ctx->noload_lookups(/*PreserveInternalState=*/false)) {
       [...]
```

This storage of the lookup is sorted by pointer values (see the hash of
`DeclarationName`) and can therefore be non-deterministic. The LLDB code
completion consumer that receives these calls originally expected that the order
of declarations is defined by Clang, but it seems the API expects the client to
provide an order to the completions.

This patch fixes the issue as follows:

* We sort the completions we get from Clang alphabetically and also by the
priority value we get from Clang (with priority value sorting having precedence
over the alphabetical sorting)

* We make all the functions/variables that touch a completion before the sorting
const-qualified. The idea is that this should prevent that we never have
observable side-effect from touching these declarations in a non-deterministic
order (e.g., we don't try to complete the type by accident).

This way we behave like the other parts of Clang which also sort the results by
some deterministic value (usually the name or something computed from a name,
e.g., edit distance to a given string).

We most likely also need to fix the Clang code to make the loop I listed above
deterministic to prevent these issues in the future (tracked in rdar://63442513
). This wouldn't replace the functionality provided in this patch though as we
would still need the priority and overall alphabetical sorting.

Note: I had to increase the lldb-vscode completion limit to 100 as the tests
look for strings that aren't in the first 50 results anymore due to variable
names starting with letters like 'v' (which are now always shown much further
down in the list due to the alphabetical sorting).

Fixes rdar://63200995

Reviewers: JDevlieghere, clayborg

Reviewed By: JDevlieghere

Subscribers: mgrang, abidh

Differential Revision: https://reviews.llvm.org/D80292
2020-05-27 19:22:01 +02:00

255 lines
12 KiB
Python

"""
Test the lldb command line completion mechanism for the 'expr' command.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbplatform
from lldbsuite.test import lldbutil
class CommandLineExprCompletionTestCase(TestBase):
mydir = TestBase.compute_mydir(__file__)
NO_DEBUG_INFO_TESTCASE = True
def test_expr_completion(self):
self.build()
self.main_source = "main.cpp"
self.main_source_spec = lldb.SBFileSpec(self.main_source)
self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
# Try the completion before we have a context to complete on.
self.assume_no_completions('expr some_expr')
self.assume_no_completions('expr ')
self.assume_no_completions('expr f')
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
'// Break here', self.main_source_spec)
# Completing member functions
self.complete_exactly('expr some_expr.FooNoArgs',
'expr some_expr.FooNoArgsBar()')
self.complete_exactly('expr some_expr.FooWithArgs',
'expr some_expr.FooWithArgsBar(')
self.complete_exactly('expr some_expr.FooWithMultipleArgs',
'expr some_expr.FooWithMultipleArgsBar(')
self.complete_exactly('expr some_expr.FooUnderscore',
'expr some_expr.FooUnderscoreBar_()')
self.complete_exactly('expr some_expr.FooNumbers',
'expr some_expr.FooNumbersBar1()')
self.complete_exactly('expr some_expr.StaticMemberMethod',
'expr some_expr.StaticMemberMethodBar()')
# Completing static functions
self.complete_exactly('expr Expr::StaticMemberMethod',
'expr Expr::StaticMemberMethodBar()')
# Completing member variables
self.complete_exactly('expr some_expr.MemberVariab',
'expr some_expr.MemberVariableBar')
# Multiple completions
self.completions_contain('expr some_expr.',
['some_expr.FooNumbersBar1()',
'some_expr.FooUnderscoreBar_()',
'some_expr.FooWithArgsBar(',
'some_expr.MemberVariableBar'])
self.completions_contain('expr some_expr.Foo',
['some_expr.FooNumbersBar1()',
'some_expr.FooUnderscoreBar_()',
'some_expr.FooWithArgsBar('])
self.completions_contain('expr ',
['static_cast',
'reinterpret_cast',
'dynamic_cast'])
self.completions_contain('expr 1 + ',
['static_cast',
'reinterpret_cast',
'dynamic_cast'])
# Completion expr without spaces
# This is a bit awkward looking for the user, but that's how
# the completion API works at the moment.
self.completions_contain('expr 1+',
['1+some_expr', "1+static_cast"])
# Test with spaces
self.complete_exactly('expr some_expr .FooNoArgs',
'expr some_expr .FooNoArgsBar()')
self.complete_exactly('expr some_expr .FooNoArgs',
'expr some_expr .FooNoArgsBar()')
self.complete_exactly('expr some_expr .FooNoArgs',
'expr some_expr .FooNoArgsBar()')
self.complete_exactly('expr some_expr. FooNoArgs',
'expr some_expr. FooNoArgsBar()')
self.complete_exactly('expr some_expr . FooNoArgs',
'expr some_expr . FooNoArgsBar()')
self.complete_exactly('expr Expr :: StaticMemberMethod',
'expr Expr :: StaticMemberMethodBar()')
self.complete_exactly('expr Expr ::StaticMemberMethod',
'expr Expr ::StaticMemberMethodBar()')
self.complete_exactly('expr Expr:: StaticMemberMethod',
'expr Expr:: StaticMemberMethodBar()')
# Test that string literals don't break our parsing logic.
self.complete_exactly('expr const char *cstr = "some_e"; char c = *cst',
'expr const char *cstr = "some_e"; char c = *cstr')
self.complete_exactly('expr const char *cstr = "some_e" ; char c = *cst',
'expr const char *cstr = "some_e" ; char c = *cstr')
# Requesting completions inside an incomplete string doesn't provide any
# completions.
self.complete_exactly('expr const char *cstr = "some_e',
'expr const char *cstr = "some_e')
# Completing inside double dash should do nothing
self.assume_no_completions('expr -i0 -- some_expr.', 10)
self.assume_no_completions('expr -i0 -- some_expr.', 11)
# Test with expr arguments
self.complete_exactly('expr -i0 -- some_expr .FooNoArgs',
'expr -i0 -- some_expr .FooNoArgsBar()')
self.complete_exactly('expr -i0 -- some_expr .FooNoArgs',
'expr -i0 -- some_expr .FooNoArgsBar()')
# Addrof and deref
self.complete_exactly('expr (*(&some_expr)).FooNoArgs',
'expr (*(&some_expr)).FooNoArgsBar()')
self.complete_exactly('expr (*(&some_expr)) .FooNoArgs',
'expr (*(&some_expr)) .FooNoArgsBar()')
self.complete_exactly('expr (* (&some_expr)) .FooNoArgs',
'expr (* (&some_expr)) .FooNoArgsBar()')
self.complete_exactly('expr (* (& some_expr)) .FooNoArgs',
'expr (* (& some_expr)) .FooNoArgsBar()')
# Addrof and deref (part 2)
self.complete_exactly('expr (&some_expr)->FooNoArgs',
'expr (&some_expr)->FooNoArgsBar()')
self.complete_exactly('expr (&some_expr) ->FooNoArgs',
'expr (&some_expr) ->FooNoArgsBar()')
self.complete_exactly('expr (&some_expr) -> FooNoArgs',
'expr (&some_expr) -> FooNoArgsBar()')
self.complete_exactly('expr (&some_expr)-> FooNoArgs',
'expr (&some_expr)-> FooNoArgsBar()')
# Builtin arg
self.complete_exactly('expr static_ca',
'expr static_cast')
# From other files
self.complete_exactly('expr fwd_decl_ptr->Hidden',
'expr fwd_decl_ptr->HiddenMember')
# Types
self.complete_exactly('expr LongClassNa',
'expr LongClassName')
self.complete_exactly('expr LongNamespaceName::NestedCla',
'expr LongNamespaceName::NestedClass')
# Namespaces
self.complete_exactly('expr LongNamespaceNa',
'expr LongNamespaceName::')
# Multiple arguments
self.complete_exactly('expr &some_expr + &some_e',
'expr &some_expr + &some_expr')
self.complete_exactly('expr SomeLongVarNameWithCapitals + SomeLongVarName',
'expr SomeLongVarNameWithCapitals + SomeLongVarNameWithCapitals')
self.complete_exactly('expr SomeIntVar + SomeIntV',
'expr SomeIntVar + SomeIntVar')
# Multiple statements
self.complete_exactly('expr long LocalVariable = 0; LocalVaria',
'expr long LocalVariable = 0; LocalVariable')
# Custom Decls
self.complete_exactly('expr auto l = [](int LeftHandSide, int bx){ return LeftHandS',
'expr auto l = [](int LeftHandSide, int bx){ return LeftHandSide')
self.complete_exactly('expr struct LocalStruct { long MemberName; } ; LocalStruct S; S.Mem',
'expr struct LocalStruct { long MemberName; } ; LocalStruct S; S.MemberName')
# Completing function call arguments
self.complete_exactly('expr some_expr.FooWithArgsBar(some_exp',
'expr some_expr.FooWithArgsBar(some_expr')
self.complete_exactly('expr some_expr.FooWithArgsBar(SomeIntV',
'expr some_expr.FooWithArgsBar(SomeIntVar')
self.complete_exactly('expr some_expr.FooWithMultipleArgsBar(SomeIntVar, SomeIntVa',
'expr some_expr.FooWithMultipleArgsBar(SomeIntVar, SomeIntVar')
# Function return values
self.complete_exactly('expr some_expr.Self().FooNoArgs',
'expr some_expr.Self().FooNoArgsBar()')
self.complete_exactly('expr some_expr.Self() .FooNoArgs',
'expr some_expr.Self() .FooNoArgsBar()')
self.complete_exactly('expr some_expr.Self(). FooNoArgs',
'expr some_expr.Self(). FooNoArgsBar()')
def test_expr_completion_with_descriptions(self):
self.build()
self.main_source = "main.cpp"
self.main_source_spec = lldb.SBFileSpec(self.main_source)
self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
(target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self,
'// Break here', self.main_source_spec)
self.check_completion_with_desc("expr ", [
# builtin types have no description.
["int", ""],
["float", ""],
# VarDecls have their type as description.
["some_expr", "Expr &"],
], enforce_order = True)
self.check_completion_with_desc("expr some_expr.", [
# Functions have their signature as description.
["some_expr.~Expr()", "inline ~Expr()"],
["some_expr.operator=(", "inline Expr &operator=(const Expr &)"],
# FieldDecls have their type as description.
["some_expr.MemberVariableBar", "int"],
["some_expr.StaticMemberMethodBar()", "static int StaticMemberMethodBar()"],
["some_expr.Self()", "Expr &Self()"],
["some_expr.FooNoArgsBar()", "int FooNoArgsBar()"],
["some_expr.FooWithArgsBar(", "int FooWithArgsBar(int)"],
["some_expr.FooNumbersBar1()", "int FooNumbersBar1()"],
["some_expr.FooUnderscoreBar_()", "int FooUnderscoreBar_()"],
["some_expr.FooWithMultipleArgsBar(", "int FooWithMultipleArgsBar(int, int)"],
], enforce_order = True)
def assume_no_completions(self, str_input, cursor_pos = None):
interp = self.dbg.GetCommandInterpreter()
match_strings = lldb.SBStringList()
if cursor_pos is None:
cursor_pos = len(str_input)
num_matches = interp.HandleCompletion(str_input, cursor_pos, 0, -1, match_strings)
available_completions = []
for m in match_strings:
available_completions.append(m)
self.assertEquals(num_matches, 0, "Got matches, but didn't expect any: " + str(available_completions))
def completions_contain(self, str_input, items):
interp = self.dbg.GetCommandInterpreter()
match_strings = lldb.SBStringList()
num_matches = interp.HandleCompletion(str_input, len(str_input), 0, -1, match_strings)
common_match = match_strings.GetStringAtIndex(0)
for item in items:
found = False
for m in match_strings:
if m == item:
found = True
if not found:
# Transform match_strings to a python list with strings
available_completions = []
for m in match_strings:
available_completions.append(m)
self.assertTrue(found, "Couldn't find completion " + item + " in completions " + str(available_completions))