| """ | |
| Test calling an expression with errors that a FixIt can fix. | |
| """ | |
| import lldb | |
| from lldbsuite.test.decorators import * | |
| from lldbsuite.test.lldbtest import * | |
| from lldbsuite.test import lldbutil | |
| class ExprCommandWithFixits(TestBase): | |
| def test_with_dummy_target(self): | |
| """Test calling expressions in the dummy target with errors that can be fixed by the FixIts.""" | |
| # Enable fix-its as they were intentionally disabled by TestBase.setUp. | |
| self.runCmd("settings set target.auto-apply-fixits true") | |
| ret_val = lldb.SBCommandReturnObject() | |
| result = self.dbg.GetCommandInterpreter().HandleCommand( | |
| "expression ((1 << 16) - 1))", ret_val | |
| ) | |
| self.assertEqual( | |
| result, lldb.eReturnStatusSuccessFinishResult, ret_val.GetError() | |
| ) | |
| self.assertIn( | |
| "Evaluated this expression after applying Fix-It(s):", ret_val.GetError() | |
| ) | |
| def test_with_target(self): | |
| """Test calling expressions with errors that can be fixed by the FixIts.""" | |
| self.build() | |
| (target, process, self.thread, bkpt) = lldbutil.run_to_source_breakpoint( | |
| self, "Stop here to evaluate expressions", lldb.SBFileSpec("main.cpp") | |
| ) | |
| options = lldb.SBExpressionOptions() | |
| options.SetAutoApplyFixIts(True) | |
| top_level_options = lldb.SBExpressionOptions() | |
| top_level_options.SetAutoApplyFixIts(True) | |
| top_level_options.SetTopLevel(True) | |
| frame = self.thread.GetFrameAtIndex(0) | |
| # Try with one error: | |
| value = frame.EvaluateExpression("my_pointer.first", options) | |
| self.assertTrue(value.IsValid()) | |
| self.assertSuccess(value.GetError()) | |
| self.assertEqual(value.GetValueAsUnsigned(), 10) | |
| # Try with one error in a top-level expression. | |
| # The Fix-It changes "ptr.m" to "ptr->m". | |
| expr = "struct MyTy { int m; }; MyTy x; MyTy *ptr = &x; int m = ptr.m;" | |
| value = frame.EvaluateExpression(expr, top_level_options) | |
| # A successfully parsed top-level expression will yield an | |
| # unknown error. If a parsing error would have happened we | |
| # would get a different error kind, so let's check the error | |
| # kind here. | |
| self.assertEqual(value.GetError().GetCString(), "unknown error") | |
| # Try with two errors: | |
| two_error_expression = "my_pointer.second->a" | |
| value = frame.EvaluateExpression(two_error_expression, options) | |
| self.assertTrue(value.IsValid()) | |
| self.assertSuccess(value.GetError()) | |
| self.assertEqual(value.GetValueAsUnsigned(), 20) | |
| # Try a Fix-It that is stored in the 'note:' diagnostic of an error. | |
| # The Fix-It here is adding parantheses around the ToStr parameters. | |
| fixit_in_note_expr = "#define ToStr(x) #x\nToStr(0 {, })" | |
| value = frame.EvaluateExpression(fixit_in_note_expr, options) | |
| self.assertTrue(value.IsValid()) | |
| self.assertSuccess(value.GetError()) | |
| self.assertEqual(value.GetSummary(), '"(0 {, })"') | |
| # Now turn off the fixits, and the expression should fail: | |
| options.SetAutoApplyFixIts(False) | |
| value = frame.EvaluateExpression(two_error_expression, options) | |
| self.assertTrue(value.IsValid()) | |
| self.assertTrue(value.GetError().Fail()) | |
| error_string = value.GetError().GetCString() | |
| self.assertNotEqual( | |
| error_string.find("fixed expression suggested:"), -1, "Fix was suggested" | |
| ) | |
| self.assertNotEqual( | |
| error_string.find("my_pointer->second.a"), -1, "Fix was right" | |
| ) | |
| def test_with_target_error_applies_fixit(self): | |
| """Check that applying a Fix-it which fails to execute correctly still | |
| prints that the Fix-it was applied.""" | |
| self.build() | |
| (target, process, self.thread, bkpt) = lldbutil.run_to_source_breakpoint( | |
| self, "Stop here to evaluate expressions", lldb.SBFileSpec("main.cpp") | |
| ) | |
| # Enable fix-its as they were intentionally disabled by TestBase.setUp. | |
| self.runCmd("settings set target.auto-apply-fixits true") | |
| ret_val = lldb.SBCommandReturnObject() | |
| result = self.dbg.GetCommandInterpreter().HandleCommand( | |
| "expression null_pointer.first", ret_val | |
| ) | |
| self.assertEqual(result, lldb.eReturnStatusFailed, ret_val.GetError()) | |
| self.assertIn( | |
| "Evaluated this expression after applying Fix-It(s):", ret_val.GetError() | |
| ) | |
| self.assertIn("null_pointer->first", ret_val.GetError()) | |
| @expectedFailureAll( | |
| archs=["aarch64"], oslist=["freebsd"], bugnumber="llvm.org/pr49407" | |
| ) | |
| def test_with_multiple_retries(self): | |
| """Test calling expressions with errors that can be fixed by the FixIts.""" | |
| self.build() | |
| (target, process, self.thread, bkpt) = lldbutil.run_to_source_breakpoint( | |
| self, "Stop here to evaluate expressions", lldb.SBFileSpec("main.cpp") | |
| ) | |
| # Test repeatedly applying Fix-Its to expressions and reparsing them. | |
| multiple_runs_options = lldb.SBExpressionOptions() | |
| multiple_runs_options.SetAutoApplyFixIts(True) | |
| multiple_runs_options.SetTopLevel(True) | |
| frame = self.thread.GetFrameAtIndex(0) | |
| # An expression that needs two parse attempts with one Fix-It each | |
| # to be successfully parsed. | |
| two_runs_expr = """ | |
| struct Data { int m; }; | |
| template<typename T> | |
| struct S1 : public T { | |
| using T::TypeDef; | |
| int f() { | |
| Data data; | |
| data.m = 123; | |
| // The first error as the using above requires a 'typename '. | |
| // Will trigger a Fix-It that puts 'typename' in the right place. | |
| typename S1<T>::TypeDef i = &data; | |
| // i has the type "Data *", so this should be i.m. | |
| // The second run will change the . to -> via the Fix-It. | |
| return i.m; | |
| } | |
| }; | |
| struct ClassWithTypeDef { | |
| typedef Data *TypeDef; | |
| }; | |
| int test_X(int i) { | |
| S1<ClassWithTypeDef> s1; | |
| return s1.f(); | |
| } | |
| """ | |
| # Disable retries which will fail. | |
| multiple_runs_options.SetRetriesWithFixIts(0) | |
| value = frame.EvaluateExpression(two_runs_expr, multiple_runs_options) | |
| errmsg = value.GetError().GetCString() | |
| self.assertIn("using declaration resolved to type without 'typename'", errmsg) | |
| self.assertIn("fixed expression suggested:", errmsg) | |
| self.assertIn("using typename T::TypeDef", errmsg) | |
| # The second Fix-It shouldn't be suggested here as Clang should have | |
| # aborted the parsing process. | |
| self.assertNotIn("i->m", errmsg) | |
| # Retry once, but the expression needs two retries. | |
| multiple_runs_options.SetRetriesWithFixIts(1) | |
| value = frame.EvaluateExpression(two_runs_expr, multiple_runs_options) | |
| errmsg = value.GetError().GetCString() | |
| self.assertIn("fixed expression suggested:", errmsg) | |
| # Both our fixed expressions should be in the suggested expression. | |
| self.assertIn("using typename T::TypeDef", errmsg) | |
| self.assertIn("i->m", errmsg) | |
| # Retry twice, which will get the expression working. | |
| multiple_runs_options.SetRetriesWithFixIts(2) | |
| value = frame.EvaluateExpression(two_runs_expr, multiple_runs_options) | |
| # This error signals success for top level expressions. | |
| self.assertEqual(value.GetError().GetCString(), "unknown error") | |
| # Test that the code above compiles to the right thing. | |
| self.expect_expr("test_X(1)", result_type="int", result_value="123") |