1435933ddSDimitry Andric //===-- CPPLanguageRuntime.cpp
2435933ddSDimitry Andric //-------------------------------------------------*- C++ -*-===//
3ac7ddfbfSEd Maste //
4ac7ddfbfSEd Maste //                     The LLVM Compiler Infrastructure
5ac7ddfbfSEd Maste //
6ac7ddfbfSEd Maste // This file is distributed under the University of Illinois Open Source
7ac7ddfbfSEd Maste // License. See LICENSE.TXT for details.
8ac7ddfbfSEd Maste //
9ac7ddfbfSEd Maste //===----------------------------------------------------------------------===//
10ac7ddfbfSEd Maste 
11ac7ddfbfSEd Maste #include "lldb/Target/CPPLanguageRuntime.h"
12ac7ddfbfSEd Maste 
13ac7ddfbfSEd Maste #include <string.h>
14ac7ddfbfSEd Maste 
157aa51b79SEd Maste #include "llvm/ADT/StringRef.h"
167aa51b79SEd Maste 
17*b5893f02SDimitry Andric #include "lldb/Symbol/Block.h"
18*b5893f02SDimitry Andric #include "lldb/Symbol/VariableList.h"
19*b5893f02SDimitry Andric 
20ac7ddfbfSEd Maste #include "lldb/Core/PluginManager.h"
21ac7ddfbfSEd Maste #include "lldb/Core/UniqueCStringMap.h"
22*b5893f02SDimitry Andric #include "lldb/Symbol/ClangASTContext.h"
23*b5893f02SDimitry Andric #include "lldb/Target/ABI.h"
24ac7ddfbfSEd Maste #include "lldb/Target/ExecutionContext.h"
25*b5893f02SDimitry Andric #include "lldb/Target/RegisterContext.h"
26*b5893f02SDimitry Andric #include "lldb/Target/SectionLoadList.h"
27*b5893f02SDimitry Andric #include "lldb/Target/StackFrame.h"
28*b5893f02SDimitry Andric #include "lldb/Target/ThreadPlanRunToAddress.h"
29*b5893f02SDimitry Andric #include "lldb/Target/ThreadPlanStepInRange.h"
30ac7ddfbfSEd Maste 
31ac7ddfbfSEd Maste using namespace lldb;
32ac7ddfbfSEd Maste using namespace lldb_private;
33ac7ddfbfSEd Maste 
34ac7ddfbfSEd Maste //----------------------------------------------------------------------
35ac7ddfbfSEd Maste // Destructor
36ac7ddfbfSEd Maste //----------------------------------------------------------------------
~CPPLanguageRuntime()37435933ddSDimitry Andric CPPLanguageRuntime::~CPPLanguageRuntime() {}
38ac7ddfbfSEd Maste 
CPPLanguageRuntime(Process * process)39435933ddSDimitry Andric CPPLanguageRuntime::CPPLanguageRuntime(Process *process)
40435933ddSDimitry Andric     : LanguageRuntime(process) {}
41ac7ddfbfSEd Maste 
GetObjectDescription(Stream & str,ValueObject & object)42435933ddSDimitry Andric bool CPPLanguageRuntime::GetObjectDescription(Stream &str,
43435933ddSDimitry Andric                                               ValueObject &object) {
44ac7ddfbfSEd Maste   // C++ has no generic way to do this.
45ac7ddfbfSEd Maste   return false;
46ac7ddfbfSEd Maste }
47ac7ddfbfSEd Maste 
GetObjectDescription(Stream & str,Value & value,ExecutionContextScope * exe_scope)48435933ddSDimitry Andric bool CPPLanguageRuntime::GetObjectDescription(
49435933ddSDimitry Andric     Stream &str, Value &value, ExecutionContextScope *exe_scope) {
50ac7ddfbfSEd Maste   // C++ has no generic way to do this.
51ac7ddfbfSEd Maste   return false;
52ac7ddfbfSEd Maste }
53*b5893f02SDimitry Andric 
54*b5893f02SDimitry Andric CPPLanguageRuntime::LibCppStdFunctionCallableInfo
FindLibCppStdFunctionCallableInfo(lldb::ValueObjectSP & valobj_sp)55*b5893f02SDimitry Andric CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
56*b5893f02SDimitry Andric     lldb::ValueObjectSP &valobj_sp) {
57*b5893f02SDimitry Andric   LibCppStdFunctionCallableInfo optional_info;
58*b5893f02SDimitry Andric 
59*b5893f02SDimitry Andric   if (!valobj_sp)
60*b5893f02SDimitry Andric     return optional_info;
61*b5893f02SDimitry Andric 
62*b5893f02SDimitry Andric   // Member __f_ has type __base*, the contents of which will hold:
63*b5893f02SDimitry Andric   // 1) a vtable entry which may hold type information needed to discover the
64*b5893f02SDimitry Andric   //    lambda being called
65*b5893f02SDimitry Andric   // 2) possibly hold a pointer to the callable object
66*b5893f02SDimitry Andric   // e.g.
67*b5893f02SDimitry Andric   //
68*b5893f02SDimitry Andric   // (lldb) frame var -R  f_display
69*b5893f02SDimitry Andric   // (std::__1::function<void (int)>) f_display = {
70*b5893f02SDimitry Andric   //  __buf_ = {
71*b5893f02SDimitry Andric   //  …
72*b5893f02SDimitry Andric   // }
73*b5893f02SDimitry Andric   //  __f_ = 0x00007ffeefbffa00
74*b5893f02SDimitry Andric   // }
75*b5893f02SDimitry Andric   // (lldb) memory read -fA 0x00007ffeefbffa00
76*b5893f02SDimitry Andric   // 0x7ffeefbffa00: ... `vtable for std::__1::__function::__func<void (*) ...
77*b5893f02SDimitry Andric   // 0x7ffeefbffa08: ... `print_num(int) at std_function_cppreference_exam ...
78*b5893f02SDimitry Andric   //
79*b5893f02SDimitry Andric   // We will be handling five cases below, std::function is wrapping:
80*b5893f02SDimitry Andric   //
81*b5893f02SDimitry Andric   // 1) a lambda we know at compile time. We will obtain the name of the lambda
82*b5893f02SDimitry Andric   //    from the first template pameter from __func's vtable. We will look up
83*b5893f02SDimitry Andric   //    the lambda's operator()() and obtain the line table entry.
84*b5893f02SDimitry Andric   // 2) a lambda we know at runtime. A pointer to the lambdas __invoke method
85*b5893f02SDimitry Andric   //    will be stored after the vtable. We will obtain the lambdas name from
86*b5893f02SDimitry Andric   //    this entry and lookup operator()() and obtain the line table entry.
87*b5893f02SDimitry Andric   // 3) a callable object via operator()(). We will obtain the name of the
88*b5893f02SDimitry Andric   //    object from the first template parameter from __func's vtable. We will
89*b5893f02SDimitry Andric   //    look up the objectc operator()() and obtain the line table entry.
90*b5893f02SDimitry Andric   // 4) a member function. A pointer to the function will stored after the
91*b5893f02SDimitry Andric   //    we will obtain the name from this pointer.
92*b5893f02SDimitry Andric   // 5) a free function. A pointer to the function will stored after the vtable
93*b5893f02SDimitry Andric   //    we will obtain the name from this pointer.
94*b5893f02SDimitry Andric   ValueObjectSP member__f_(
95*b5893f02SDimitry Andric       valobj_sp->GetChildMemberWithName(ConstString("__f_"), true));
96*b5893f02SDimitry Andric 
97*b5893f02SDimitry Andric   if (member__f_) {
98*b5893f02SDimitry Andric     ValueObjectSP sub_member__f_(
99*b5893f02SDimitry Andric        member__f_->GetChildMemberWithName(ConstString("__f_"), true));
100*b5893f02SDimitry Andric 
101*b5893f02SDimitry Andric     if (sub_member__f_)
102*b5893f02SDimitry Andric         member__f_ = sub_member__f_;
103*b5893f02SDimitry Andric   }
104*b5893f02SDimitry Andric 
105*b5893f02SDimitry Andric   lldb::addr_t member__f_pointer_value = member__f_->GetValueAsUnsigned(0);
106*b5893f02SDimitry Andric 
107*b5893f02SDimitry Andric   optional_info.member__f_pointer_value = member__f_pointer_value;
108*b5893f02SDimitry Andric 
109*b5893f02SDimitry Andric   ExecutionContext exe_ctx(valobj_sp->GetExecutionContextRef());
110*b5893f02SDimitry Andric   Process *process = exe_ctx.GetProcessPtr();
111*b5893f02SDimitry Andric 
112*b5893f02SDimitry Andric   if (process == nullptr)
113*b5893f02SDimitry Andric     return optional_info;
114*b5893f02SDimitry Andric 
115*b5893f02SDimitry Andric   uint32_t address_size = process->GetAddressByteSize();
116*b5893f02SDimitry Andric   Status status;
117*b5893f02SDimitry Andric 
118*b5893f02SDimitry Andric   // First item pointed to by __f_ should be the pointer to the vtable for
119*b5893f02SDimitry Andric   // a __base object.
120*b5893f02SDimitry Andric   lldb::addr_t vtable_address =
121*b5893f02SDimitry Andric       process->ReadPointerFromMemory(member__f_pointer_value, status);
122*b5893f02SDimitry Andric 
123*b5893f02SDimitry Andric   if (status.Fail())
124*b5893f02SDimitry Andric     return optional_info;
125*b5893f02SDimitry Andric 
126*b5893f02SDimitry Andric   lldb::addr_t address_after_vtable = member__f_pointer_value + address_size;
127*b5893f02SDimitry Andric   // As commened above we may not have a function pointer but if we do we will
128*b5893f02SDimitry Andric   // need it.
129*b5893f02SDimitry Andric   lldb::addr_t possible_function_address =
130*b5893f02SDimitry Andric       process->ReadPointerFromMemory(address_after_vtable, status);
131*b5893f02SDimitry Andric 
132*b5893f02SDimitry Andric   if (status.Fail())
133*b5893f02SDimitry Andric     return optional_info;
134*b5893f02SDimitry Andric 
135*b5893f02SDimitry Andric   Target &target = process->GetTarget();
136*b5893f02SDimitry Andric 
137*b5893f02SDimitry Andric   if (target.GetSectionLoadList().IsEmpty())
138*b5893f02SDimitry Andric     return optional_info;
139*b5893f02SDimitry Andric 
140*b5893f02SDimitry Andric   Address vtable_addr_resolved;
141*b5893f02SDimitry Andric   SymbolContext sc;
142*b5893f02SDimitry Andric   Symbol *symbol;
143*b5893f02SDimitry Andric 
144*b5893f02SDimitry Andric   if (!target.GetSectionLoadList().ResolveLoadAddress(vtable_address,
145*b5893f02SDimitry Andric                                                       vtable_addr_resolved))
146*b5893f02SDimitry Andric     return optional_info;
147*b5893f02SDimitry Andric 
148*b5893f02SDimitry Andric   target.GetImages().ResolveSymbolContextForAddress(
149*b5893f02SDimitry Andric       vtable_addr_resolved, eSymbolContextEverything, sc);
150*b5893f02SDimitry Andric   symbol = sc.symbol;
151*b5893f02SDimitry Andric 
152*b5893f02SDimitry Andric   if (symbol == nullptr)
153*b5893f02SDimitry Andric     return optional_info;
154*b5893f02SDimitry Andric 
155*b5893f02SDimitry Andric   llvm::StringRef vtable_name(symbol->GetName().GetCString());
156*b5893f02SDimitry Andric   bool found_expected_start_string =
157*b5893f02SDimitry Andric       vtable_name.startswith("vtable for std::__1::__function::__func<");
158*b5893f02SDimitry Andric 
159*b5893f02SDimitry Andric   if (!found_expected_start_string)
160*b5893f02SDimitry Andric     return optional_info;
161*b5893f02SDimitry Andric 
162*b5893f02SDimitry Andric   // Given case 1 or 3 we have a vtable name, we are want to extract the first
163*b5893f02SDimitry Andric   // template parameter
164*b5893f02SDimitry Andric   //
165*b5893f02SDimitry Andric   //  ... __func<main::$_0, std::__1::allocator<main::$_0> ...
166*b5893f02SDimitry Andric   //             ^^^^^^^^^
167*b5893f02SDimitry Andric   //
168*b5893f02SDimitry Andric   // We do this by find the first < and , and extracting in between.
169*b5893f02SDimitry Andric   //
170*b5893f02SDimitry Andric   // This covers the case of the lambda known at compile time.
171*b5893f02SDimitry Andric   size_t first_open_angle_bracket = vtable_name.find('<') + 1;
172*b5893f02SDimitry Andric   size_t first_comma = vtable_name.find_first_of(',');
173*b5893f02SDimitry Andric 
174*b5893f02SDimitry Andric   llvm::StringRef first_template_parameter =
175*b5893f02SDimitry Andric       vtable_name.slice(first_open_angle_bracket, first_comma);
176*b5893f02SDimitry Andric 
177*b5893f02SDimitry Andric   Address function_address_resolved;
178*b5893f02SDimitry Andric 
179*b5893f02SDimitry Andric   // Setup for cases 2, 4 and 5 we have a pointer to a function after the
180*b5893f02SDimitry Andric   // vtable. We will use a process of elimination to drop through each case
181*b5893f02SDimitry Andric   // and obtain the data we need.
182*b5893f02SDimitry Andric   if (target.GetSectionLoadList().ResolveLoadAddress(
183*b5893f02SDimitry Andric           possible_function_address, function_address_resolved)) {
184*b5893f02SDimitry Andric     target.GetImages().ResolveSymbolContextForAddress(
185*b5893f02SDimitry Andric         function_address_resolved, eSymbolContextEverything, sc);
186*b5893f02SDimitry Andric     symbol = sc.symbol;
187*b5893f02SDimitry Andric   }
188*b5893f02SDimitry Andric 
189*b5893f02SDimitry Andric   auto get_name = [&first_template_parameter, &symbol]() {
190*b5893f02SDimitry Andric     // Given case 1:
191*b5893f02SDimitry Andric     //
192*b5893f02SDimitry Andric     //    main::$_0
193*b5893f02SDimitry Andric     //
194*b5893f02SDimitry Andric     // we want to append ::operator()()
195*b5893f02SDimitry Andric     if (first_template_parameter.contains("$_"))
196*b5893f02SDimitry Andric       return llvm::Regex::escape(first_template_parameter.str()) +
197*b5893f02SDimitry Andric              R"(::operator\(\)\(.*\))";
198*b5893f02SDimitry Andric 
199*b5893f02SDimitry Andric     if (symbol != NULL &&
200*b5893f02SDimitry Andric         symbol->GetName().GetStringRef().contains("__invoke")) {
201*b5893f02SDimitry Andric 
202*b5893f02SDimitry Andric       llvm::StringRef symbol_name = symbol->GetName().GetStringRef();
203*b5893f02SDimitry Andric       size_t pos2 = symbol_name.find_last_of(':');
204*b5893f02SDimitry Andric 
205*b5893f02SDimitry Andric       // Given case 2:
206*b5893f02SDimitry Andric       //
207*b5893f02SDimitry Andric       //    main::$_1::__invoke(...)
208*b5893f02SDimitry Andric       //
209*b5893f02SDimitry Andric       // We want to slice off __invoke(...) and append operator()()
210*b5893f02SDimitry Andric       std::string lambda_operator =
211*b5893f02SDimitry Andric           llvm::Regex::escape(symbol_name.slice(0, pos2 + 1).str()) +
212*b5893f02SDimitry Andric           R"(operator\(\)\(.*\))";
213*b5893f02SDimitry Andric 
214*b5893f02SDimitry Andric       return lambda_operator;
215*b5893f02SDimitry Andric     }
216*b5893f02SDimitry Andric 
217*b5893f02SDimitry Andric     // Case 3
218*b5893f02SDimitry Andric     return first_template_parameter.str() + R"(::operator\(\)\(.*\))";
219*b5893f02SDimitry Andric     ;
220*b5893f02SDimitry Andric   };
221*b5893f02SDimitry Andric 
222*b5893f02SDimitry Andric   std::string func_to_match = get_name();
223*b5893f02SDimitry Andric 
224*b5893f02SDimitry Andric   SymbolContextList scl;
225*b5893f02SDimitry Andric 
226*b5893f02SDimitry Andric   target.GetImages().FindFunctions(RegularExpression{func_to_match}, true, true,
227*b5893f02SDimitry Andric                                    true, scl);
228*b5893f02SDimitry Andric 
229*b5893f02SDimitry Andric   // Case 1,2 or 3
230*b5893f02SDimitry Andric   if (scl.GetSize() >= 1) {
231*b5893f02SDimitry Andric     SymbolContext sc2 = scl[0];
232*b5893f02SDimitry Andric 
233*b5893f02SDimitry Andric     AddressRange range;
234*b5893f02SDimitry Andric     sc2.GetAddressRange(eSymbolContextEverything, 0, false, range);
235*b5893f02SDimitry Andric 
236*b5893f02SDimitry Andric     Address address = range.GetBaseAddress();
237*b5893f02SDimitry Andric 
238*b5893f02SDimitry Andric     Address addr;
239*b5893f02SDimitry Andric     if (target.ResolveLoadAddress(address.GetCallableLoadAddress(&target),
240*b5893f02SDimitry Andric                                   addr)) {
241*b5893f02SDimitry Andric       LineEntry line_entry;
242*b5893f02SDimitry Andric       addr.CalculateSymbolContextLineEntry(line_entry);
243*b5893f02SDimitry Andric 
244*b5893f02SDimitry Andric       if (first_template_parameter.contains("$_") ||
245*b5893f02SDimitry Andric           (symbol != nullptr &&
246*b5893f02SDimitry Andric            symbol->GetName().GetStringRef().contains("__invoke"))) {
247*b5893f02SDimitry Andric         // Case 1 and 2
248*b5893f02SDimitry Andric         optional_info.callable_case = LibCppStdFunctionCallableCase::Lambda;
249*b5893f02SDimitry Andric       } else {
250*b5893f02SDimitry Andric         // Case 3
251*b5893f02SDimitry Andric         optional_info.callable_case =
252*b5893f02SDimitry Andric             LibCppStdFunctionCallableCase::CallableObject;
253*b5893f02SDimitry Andric       }
254*b5893f02SDimitry Andric 
255*b5893f02SDimitry Andric       optional_info.callable_symbol = *symbol;
256*b5893f02SDimitry Andric       optional_info.callable_line_entry = line_entry;
257*b5893f02SDimitry Andric       optional_info.callable_address = addr;
258*b5893f02SDimitry Andric       return optional_info;
259*b5893f02SDimitry Andric     }
260*b5893f02SDimitry Andric   }
261*b5893f02SDimitry Andric 
262*b5893f02SDimitry Andric   // Case 4 or 5
263*b5893f02SDimitry Andric   if (!symbol->GetName().GetStringRef().startswith("vtable for")) {
264*b5893f02SDimitry Andric     optional_info.callable_case =
265*b5893f02SDimitry Andric         LibCppStdFunctionCallableCase::FreeOrMemberFunction;
266*b5893f02SDimitry Andric     optional_info.callable_address = function_address_resolved;
267*b5893f02SDimitry Andric     optional_info.callable_symbol = *symbol;
268*b5893f02SDimitry Andric 
269*b5893f02SDimitry Andric     return optional_info;
270*b5893f02SDimitry Andric   }
271*b5893f02SDimitry Andric 
272*b5893f02SDimitry Andric   return optional_info;
273*b5893f02SDimitry Andric }
274*b5893f02SDimitry Andric 
275*b5893f02SDimitry Andric lldb::ThreadPlanSP
GetStepThroughTrampolinePlan(Thread & thread,bool stop_others)276*b5893f02SDimitry Andric CPPLanguageRuntime::GetStepThroughTrampolinePlan(Thread &thread,
277*b5893f02SDimitry Andric                                                  bool stop_others) {
278*b5893f02SDimitry Andric   ThreadPlanSP ret_plan_sp;
279*b5893f02SDimitry Andric 
280*b5893f02SDimitry Andric   lldb::addr_t curr_pc = thread.GetRegisterContext()->GetPC();
281*b5893f02SDimitry Andric 
282*b5893f02SDimitry Andric   TargetSP target_sp(thread.CalculateTarget());
283*b5893f02SDimitry Andric 
284*b5893f02SDimitry Andric   if (target_sp->GetSectionLoadList().IsEmpty())
285*b5893f02SDimitry Andric     return ret_plan_sp;
286*b5893f02SDimitry Andric 
287*b5893f02SDimitry Andric   Address pc_addr_resolved;
288*b5893f02SDimitry Andric   SymbolContext sc;
289*b5893f02SDimitry Andric   Symbol *symbol;
290*b5893f02SDimitry Andric 
291*b5893f02SDimitry Andric   if (!target_sp->GetSectionLoadList().ResolveLoadAddress(curr_pc,
292*b5893f02SDimitry Andric                                                           pc_addr_resolved))
293*b5893f02SDimitry Andric     return ret_plan_sp;
294*b5893f02SDimitry Andric 
295*b5893f02SDimitry Andric   target_sp->GetImages().ResolveSymbolContextForAddress(
296*b5893f02SDimitry Andric       pc_addr_resolved, eSymbolContextEverything, sc);
297*b5893f02SDimitry Andric   symbol = sc.symbol;
298*b5893f02SDimitry Andric 
299*b5893f02SDimitry Andric   if (symbol == nullptr)
300*b5893f02SDimitry Andric     return ret_plan_sp;
301*b5893f02SDimitry Andric 
302*b5893f02SDimitry Andric   llvm::StringRef function_name(symbol->GetName().GetCString());
303*b5893f02SDimitry Andric 
304*b5893f02SDimitry Andric   // Handling the case where we are attempting to step into std::function.
305*b5893f02SDimitry Andric   // The behavior will be that we will attempt to obtain the wrapped
306*b5893f02SDimitry Andric   // callable via FindLibCppStdFunctionCallableInfo() and if we find it we
307*b5893f02SDimitry Andric   // will return a ThreadPlanRunToAddress to the callable. Therefore we will
308*b5893f02SDimitry Andric   // step into the wrapped callable.
309*b5893f02SDimitry Andric   //
310*b5893f02SDimitry Andric   bool found_expected_start_string =
311*b5893f02SDimitry Andric       function_name.startswith("std::__1::function<");
312*b5893f02SDimitry Andric 
313*b5893f02SDimitry Andric   if (!found_expected_start_string)
314*b5893f02SDimitry Andric     return ret_plan_sp;
315*b5893f02SDimitry Andric 
316*b5893f02SDimitry Andric   AddressRange range_of_curr_func;
317*b5893f02SDimitry Andric   sc.GetAddressRange(eSymbolContextEverything, 0, false, range_of_curr_func);
318*b5893f02SDimitry Andric 
319*b5893f02SDimitry Andric   StackFrameSP frame = thread.GetStackFrameAtIndex(0);
320*b5893f02SDimitry Andric 
321*b5893f02SDimitry Andric   if (frame) {
322*b5893f02SDimitry Andric     ValueObjectSP value_sp = frame->FindVariable(ConstString("this"));
323*b5893f02SDimitry Andric 
324*b5893f02SDimitry Andric     CPPLanguageRuntime::LibCppStdFunctionCallableInfo callable_info =
325*b5893f02SDimitry Andric         FindLibCppStdFunctionCallableInfo(value_sp);
326*b5893f02SDimitry Andric 
327*b5893f02SDimitry Andric     if (callable_info.callable_case != LibCppStdFunctionCallableCase::Invalid &&
328*b5893f02SDimitry Andric         value_sp->GetValueIsValid()) {
329*b5893f02SDimitry Andric       // We found the std::function wrapped callable and we have its address.
330*b5893f02SDimitry Andric       // We now create a ThreadPlan to run to the callable.
331*b5893f02SDimitry Andric       ret_plan_sp.reset(new ThreadPlanRunToAddress(
332*b5893f02SDimitry Andric           thread, callable_info.callable_address, stop_others));
333*b5893f02SDimitry Andric       return ret_plan_sp;
334*b5893f02SDimitry Andric     } else {
335*b5893f02SDimitry Andric       // We are in std::function but we could not obtain the callable.
336*b5893f02SDimitry Andric       // We create a ThreadPlan to keep stepping through using the address range
337*b5893f02SDimitry Andric       // of the current function.
338*b5893f02SDimitry Andric       ret_plan_sp.reset(new ThreadPlanStepInRange(thread, range_of_curr_func,
339*b5893f02SDimitry Andric                                                   sc, eOnlyThisThread,
340*b5893f02SDimitry Andric                                                   eLazyBoolYes, eLazyBoolYes));
341*b5893f02SDimitry Andric       return ret_plan_sp;
342*b5893f02SDimitry Andric     }
343*b5893f02SDimitry Andric   }
344*b5893f02SDimitry Andric 
345*b5893f02SDimitry Andric   return ret_plan_sp;
346*b5893f02SDimitry Andric }
347