1 /*
2     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
3     SPDX-FileCopyrightText: 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
4 
5     SPDX-License-Identifier: LGPL-2.0-or-later
6 */
7 
8 #include "duchainutils.h"
9 
10 #include <algorithm>
11 
12 #include <interfaces/icore.h>
13 #include <interfaces/ilanguagecontroller.h>
14 
15 #include "../interfaces/ilanguagesupport.h"
16 #include "../assistant/staticassistantsmanager.h"
17 #include <debug.h>
18 
19 #include "declaration.h"
20 #include "classfunctiondeclaration.h"
21 #include "ducontext.h"
22 #include "duchain.h"
23 #include "use.h"
24 #include "duchainlock.h"
25 #include "classmemberdeclaration.h"
26 #include "functiondefinition.h"
27 #include "specializationstore.h"
28 #include "persistentsymboltable.h"
29 #include "classdeclaration.h"
30 #include "parsingenvironment.h"
31 
32 #include <QStandardPaths>
33 
34 using namespace KDevelop;
35 using namespace KTextEditor;
36 
completionProperties(const Declaration * dec)37 CodeCompletionModel::CompletionProperties DUChainUtils::completionProperties(const Declaration* dec)
38 {
39   CodeCompletionModel::CompletionProperties p;
40 
41   if(dec->context()->type() == DUContext::Class) {
42     if (const auto* member = dynamic_cast<const ClassMemberDeclaration*>(dec)) {
43       switch (member->accessPolicy()) {
44         case Declaration::Public:
45           p |= CodeCompletionModel::Public;
46           break;
47         case Declaration::Protected:
48           p |= CodeCompletionModel::Protected;
49           break;
50         case Declaration::Private:
51           p |= CodeCompletionModel::Private;
52           break;
53         default:
54           break;
55       }
56 
57       if (member->isStatic())
58         p |= CodeCompletionModel::Static;
59       if (member->isAuto())
60         {}//TODO
61       if (member->isFriend())
62         p |= CodeCompletionModel::Friend;
63       if (member->isRegister())
64         {}//TODO
65       if (member->isExtern())
66         {}//TODO
67       if (member->isMutable())
68         {}//TODO
69     }
70   }
71 
72   if (const auto* function = dynamic_cast<const AbstractFunctionDeclaration*>(dec)) {
73     p |= CodeCompletionModel::Function;
74     if (function->isVirtual())
75       p |= CodeCompletionModel::Virtual;
76     if (function->isInline())
77       p |= CodeCompletionModel::Inline;
78     if (function->isExplicit())
79       {}//TODO
80   }
81 
82   if( dec->isTypeAlias() )
83     p |= CodeCompletionModel::TypeAlias;
84 
85   if (dec->abstractType()) {
86     switch (dec->abstractType()->whichType()) {
87       case AbstractType::TypeIntegral:
88         p |= CodeCompletionModel::Variable;
89         break;
90       case AbstractType::TypePointer:
91         p |= CodeCompletionModel::Variable;
92         break;
93       case AbstractType::TypeReference:
94         p |= CodeCompletionModel::Variable;
95         break;
96       case AbstractType::TypeFunction:
97         p |= CodeCompletionModel::Function;
98         break;
99       case AbstractType::TypeStructure:
100         p |= CodeCompletionModel::Class;
101         break;
102       case AbstractType::TypeArray:
103         p |= CodeCompletionModel::Variable;
104         break;
105       case AbstractType::TypeEnumeration:
106         p |= CodeCompletionModel::Enum;
107         break;
108       case AbstractType::TypeEnumerator:
109         p |= CodeCompletionModel::Variable;
110         break;
111       case AbstractType::TypeAbstract:
112       case AbstractType::TypeDelayed:
113       case AbstractType::TypeUnsure:
114       case AbstractType::TypeAlias:
115         // TODO
116         break;
117     }
118 
119     if( dec->abstractType()->modifiers() & AbstractType::ConstModifier )
120       p |= CodeCompletionModel::Const;
121 
122     if( dec->kind() == Declaration::Instance && !dec->isFunctionDeclaration() )
123       p |= CodeCompletionModel::Variable;
124   }
125 
126   if (dec->context()) {
127     if( dec->context()->type() == DUContext::Global )
128       p |= CodeCompletionModel::GlobalScope;
129     else if( dec->context()->type() == DUContext::Namespace )
130       p |= CodeCompletionModel::NamespaceScope;
131     else if( dec->context()->type() != DUContext::Class && dec->context()->type() != DUContext::Enum )
132       p |= CodeCompletionModel::LocalScope;
133   }
134 
135   return p;
136 }
137 /**We have to construct the item from the pixmap, else the icon will be marked as "load on demand",
138  * and for some reason will be loaded every time it's used(this function returns a QIcon marked "load on demand"
139  * each time this is called). And the loading is very slow. Seems like a bug somewhere, it cannot be ment to be that slow.
140  */
141 #define RETURN_CACHED_ICON(name) {static QIcon icon(QIcon( \
142       QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("kdevelop/pics/" name ".png"))\
143     ).pixmap(QSize(16, 16)));\
144     return icon;}
145 
iconForProperties(KTextEditor::CodeCompletionModel::CompletionProperties p)146 QIcon DUChainUtils::iconForProperties(KTextEditor::CodeCompletionModel::CompletionProperties p)
147 {
148 
149   if( (p & CodeCompletionModel::Variable) )
150     if( (p & CodeCompletionModel::Protected) )
151       RETURN_CACHED_ICON("CVprotected_var")
152     else if( p & CodeCompletionModel::Private )
153       RETURN_CACHED_ICON("CVprivate_var")
154     else
155       RETURN_CACHED_ICON("CVpublic_var")
156   else
157   if( (p & CodeCompletionModel::Union) && (p & CodeCompletionModel::Protected) )
158     RETURN_CACHED_ICON("protected_union")
159 
160   else if( p & CodeCompletionModel::Enum )
161     if( p & CodeCompletionModel::Protected )
162       RETURN_CACHED_ICON("protected_enum")
163     else if( p & CodeCompletionModel::Private )
164       RETURN_CACHED_ICON("private_enum")
165     else
166       RETURN_CACHED_ICON("enum")
167 
168   else if( p & CodeCompletionModel::Struct )
169     if( p & CodeCompletionModel::Private )
170       RETURN_CACHED_ICON("private_struct")
171     else
172       RETURN_CACHED_ICON("struct")
173 
174   else if( p & CodeCompletionModel::Slot )
175     if( p & CodeCompletionModel::Protected )
176       RETURN_CACHED_ICON("CVprotected_slot")
177     else if( p & CodeCompletionModel::Private )
178       RETURN_CACHED_ICON("CVprivate_slot")
179     else if(p & CodeCompletionModel::Public )
180       RETURN_CACHED_ICON("CVpublic_slot")
181     else RETURN_CACHED_ICON("slot")
182   else if( p & CodeCompletionModel::Signal )
183     if( p & CodeCompletionModel::Protected )
184       RETURN_CACHED_ICON("CVprotected_signal")
185     else
186       RETURN_CACHED_ICON("signal")
187 
188   else if( p & CodeCompletionModel::Class )
189     if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Protected) )
190       RETURN_CACHED_ICON("protected_class")
191     else if( (p & CodeCompletionModel::Class) && (p & CodeCompletionModel::Private) )
192       RETURN_CACHED_ICON("private_class")
193     else
194       RETURN_CACHED_ICON("code-class")
195 
196   else if( p & CodeCompletionModel::Union )
197     if( p & CodeCompletionModel::Private )
198       RETURN_CACHED_ICON("private_union")
199     else
200       RETURN_CACHED_ICON("union")
201 
202   else if( p & CodeCompletionModel::TypeAlias )
203     if ((p & CodeCompletionModel::Const) /*||  (p & CodeCompletionModel::Volatile)*/)
204       RETURN_CACHED_ICON("CVtypedef")
205     else
206       RETURN_CACHED_ICON("typedef")
207 
208   else if( p & CodeCompletionModel::Function ) {
209     if( p & CodeCompletionModel::Protected )
210       RETURN_CACHED_ICON("protected_function")
211     else if( p & CodeCompletionModel::Private )
212       RETURN_CACHED_ICON("private_function")
213     else
214       RETURN_CACHED_ICON("code-function")
215   }
216 
217   if( p & CodeCompletionModel::Protected )
218     RETURN_CACHED_ICON("protected_field")
219   else if( p & CodeCompletionModel::Private )
220     RETURN_CACHED_ICON("private_field")
221   else
222     RETURN_CACHED_ICON("field")
223 
224   return QIcon();
225 }
226 
iconForDeclaration(const Declaration * dec)227 QIcon DUChainUtils::iconForDeclaration(const Declaration* dec)
228 {
229   return iconForProperties(completionProperties(dec));
230 }
231 
contentContextFromProxyContext(TopDUContext * top)232 TopDUContext* DUChainUtils::contentContextFromProxyContext(TopDUContext* top)
233 {
234   if(!top)
235     return nullptr;
236   if(top->parsingEnvironmentFile() && top->parsingEnvironmentFile()->isProxyContext()) {
237     if(!top->importedParentContexts().isEmpty())
238     {
239       DUContext* ctx = top->importedParentContexts().at(0).context(nullptr);
240       if(!ctx)
241         return nullptr;
242       TopDUContext* ret = ctx->topContext();
243       if(!ret)
244         return nullptr;
245       if(ret->url() != top->url())
246         qCDebug(LANGUAGE) << "url-mismatch between content and proxy:" << top->url().toUrl() << ret->url().toUrl();
247       if(ret->url() == top->url() && !ret->parsingEnvironmentFile()->isProxyContext())
248         return ret;
249     }
250     else {
251       qCDebug(LANGUAGE) << "Proxy-context imports no content-context";
252     }
253   } else
254     return top;
255   return nullptr;
256 }
257 
standardContextForUrl(const QUrl & url,bool preferProxyContext)258 TopDUContext* DUChainUtils::standardContextForUrl(const QUrl& url, bool preferProxyContext) {
259   KDevelop::TopDUContext* chosen = nullptr;
260 
261   const auto languages = ICore::self()->languageController()->languagesForUrl(url);
262 
263   for (const auto language : languages) {
264     if(!chosen)
265     {
266       chosen = language->standardContext(url, preferProxyContext);
267     }
268   }
269 
270   if(!chosen)
271     chosen = DUChain::self()->chainForDocument(IndexedString(url), preferProxyContext);
272 
273   if(!chosen && preferProxyContext)
274     return standardContextForUrl(url, false); // Fall back to a normal context
275 
276   return chosen;
277 }
278 
279 struct ItemUnderCursorInternal
280 {
281   Declaration* declaration;
282   DUContext* context;
283   RangeInRevision range;
284 };
285 
itemUnderCursorInternal(const CursorInRevision & c,DUContext * ctx,RangeInRevision::ContainsBehavior behavior)286 ItemUnderCursorInternal itemUnderCursorInternal(const CursorInRevision& c, DUContext* ctx, RangeInRevision::ContainsBehavior behavior)
287 {
288   //Search all collapsed sub-contexts. In C++, those can contain declarations that have ranges out of the context
289   const auto childContexts = ctx->childContexts();
290   for (DUContext* subCtx : childContexts) {
291     //This is a little hacky, but we need it in case of foreach macros and similar stuff
292     if(subCtx->range().contains(c, behavior) || subCtx->range().isEmpty() || subCtx->range().start.line == c.line || subCtx->range().end.line == c.line) {
293       ItemUnderCursorInternal sub = itemUnderCursorInternal(c, subCtx, behavior);
294       if(sub.declaration) {
295         return sub;
296       }
297     }
298   }
299 
300   const auto localDeclarations = ctx->localDeclarations();
301   for (Declaration* decl : localDeclarations) {
302     if(decl->range().contains(c, behavior)) {
303       return {decl, ctx, decl->range()};
304     }
305   }
306 
307   //Try finding a use under the cursor
308   for(int a = 0; a < ctx->usesCount(); ++a) {
309     if(ctx->uses()[a].m_range.contains(c, behavior)) {
310       return {ctx->topContext()->usedDeclarationForIndex(ctx->uses()[a].m_declarationIndex), ctx, ctx->uses()[a].m_range};
311     }
312   }
313 
314   return {nullptr, nullptr, RangeInRevision()};
315 }
316 
itemUnderCursor(const QUrl & url,const KTextEditor::Cursor & cursor)317 DUChainUtils::ItemUnderCursor DUChainUtils::itemUnderCursor(const QUrl& url, const KTextEditor::Cursor& cursor)
318 {
319   KDevelop::TopDUContext* top = standardContextForUrl(url.adjusted(QUrl::NormalizePathSegments));
320 
321   if(!top) {
322     return {nullptr, nullptr, KTextEditor::Range()};
323   }
324 
325   ItemUnderCursorInternal decl = itemUnderCursorInternal(top->transformToLocalRevision(cursor), top, RangeInRevision::Default);
326   if (decl.declaration == nullptr)
327   {
328     decl = itemUnderCursorInternal(top->transformToLocalRevision(cursor), top, RangeInRevision::IncludeBackEdge);
329   }
330   return {decl.declaration, decl.context, top->transformFromLocalRevision(decl.range)};
331 }
332 
declarationForDefinition(Declaration * definition,TopDUContext * topContext)333 Declaration* DUChainUtils::declarationForDefinition(Declaration* definition, TopDUContext* topContext)
334 {
335   if(!definition)
336     return nullptr;
337 
338   if(!topContext)
339     topContext = definition->topContext();
340 
341   if(dynamic_cast<FunctionDefinition*>(definition)) {
342     Declaration* ret = static_cast<FunctionDefinition*>(definition)->declaration();
343     if(ret)
344       return ret;
345   }
346 
347   return definition;
348 }
349 
declarationInLine(const KTextEditor::Cursor & _cursor,DUContext * ctx)350 Declaration* DUChainUtils::declarationInLine(const KTextEditor::Cursor& _cursor, DUContext* ctx) {
351   if(!ctx)
352     return nullptr;
353 
354   CursorInRevision cursor = ctx->transformToLocalRevision(_cursor);
355 
356   const auto localDeclarations = ctx->localDeclarations();
357   for (Declaration* decl : localDeclarations) {
358     if(decl->range().start.line == cursor.line)
359       return decl;
360     DUContext* funCtx = functionContext(decl);
361     if(funCtx && funCtx->range().contains(cursor))
362       return decl;
363   }
364 
365   const auto childContexts = ctx->childContexts();
366   for (DUContext* child : childContexts){
367     Declaration* decl = declarationInLine(_cursor, child);
368     if(decl)
369       return decl;
370   }
371 
372   return nullptr;
373 }
374 
~DUChainItemFilter()375 DUChainUtils::DUChainItemFilter::~DUChainItemFilter() {
376 }
377 
collectItems(DUContext * context,DUChainItemFilter & filter)378 void DUChainUtils::collectItems( DUContext* context, DUChainItemFilter& filter ) {
379 
380   QVector<DUContext*> children = context->childContexts();
381   QVector<Declaration*> localDeclarations = context->localDeclarations();
382 
383   QVector<DUContext*>::const_iterator childIt = children.constBegin();
384   QVector<Declaration*>::const_iterator declIt = localDeclarations.constBegin();
385 
386   while(childIt != children.constEnd() || declIt != localDeclarations.constEnd()) {
387 
388     DUContext* child = nullptr;
389     if(childIt != children.constEnd())
390       child = *childIt;
391 
392     Declaration* decl = nullptr;
393     if(declIt != localDeclarations.constEnd())
394       decl = *declIt;
395 
396     if(decl) {
397       if(child && child->range().start.line >= decl->range().start.line)
398         child = nullptr;
399     }
400 
401     if(child) {
402       if(decl && decl->range().start >= child->range().start)
403         decl = nullptr;
404     }
405 
406     if(decl) {
407       if( filter.accept(decl) ) {
408         //Action is done in the filter
409       }
410 
411       ++declIt;
412       continue;
413     }
414 
415     if(child) {
416       if( filter.accept(child) )
417         collectItems(child, filter);
418       ++childIt;
419       continue;
420     }
421   }
422 }
423 
argumentContext(KDevelop::Declaration * decl)424 KDevelop::DUContext* DUChainUtils::argumentContext(KDevelop::Declaration* decl) {
425   DUContext* internal = decl->internalContext();
426   if( !internal )
427     return nullptr;
428   if( internal->type() == DUContext::Function )
429     return internal;
430   const auto importedParentContexts = internal->importedParentContexts();
431   for (const DUContext::Import& ctx : importedParentContexts) {
432     if( ctx.context(decl->topContext()) )
433       if( ctx.context(decl->topContext())->type() == DUContext::Function )
434         return ctx.context(decl->topContext());
435   }
436   return nullptr;
437 }
438 
collectAllVersions(Declaration * decl)439 QList<IndexedDeclaration> DUChainUtils::collectAllVersions(Declaration* decl) {
440   QList<IndexedDeclaration> ret;
441   ret << IndexedDeclaration(decl);
442 
443   if(decl->inSymbolTable())
444   {
445     uint count;
446     const IndexedDeclaration* allDeclarations;
447     PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations);
448     for(uint a = 0; a < count; ++a)
449       if(!(allDeclarations[a] == IndexedDeclaration(decl)))
450         ret << allDeclarations[a];
451   }
452 
453   return ret;
454 }
455 
inheritersInternal(const Declaration * decl,uint & maxAllowedSteps,bool collectVersions)456 static QList<Declaration*> inheritersInternal(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions)
457 {
458   QList<Declaration*> ret;
459 
460   if(!dynamic_cast<const ClassDeclaration*>(decl))
461     return ret;
462 
463   if(maxAllowedSteps == 0)
464     return ret;
465 
466   if(decl->internalContext() && decl->internalContext()->type() == DUContext::Class) {
467     const auto indexedImporters = decl->internalContext()->indexedImporters();
468     for (const IndexedDUContext importer : indexedImporters) {
469 
470       DUContext* imp = importer.data();
471 
472       if(!imp)
473         continue;
474 
475       if(imp->type() == DUContext::Class && imp->owner())
476         ret << imp->owner();
477 
478       --maxAllowedSteps;
479 
480       if(maxAllowedSteps == 0)
481         return ret;
482     }
483   }
484 
485   if(collectVersions && decl->inSymbolTable()) {
486     uint count;
487     const IndexedDeclaration* allDeclarations;
488     PersistentSymbolTable::self().declarations(decl->qualifiedIdentifier(), count, allDeclarations);
489     for(uint a = 0; a < count; ++a) {
490       ++maxAllowedSteps;
491 
492       if(allDeclarations[a].data() && allDeclarations[a].data() != decl) {
493         ret += inheritersInternal(allDeclarations[a].data(), maxAllowedSteps, false);
494       }
495 
496       if(maxAllowedSteps == 0)
497         return ret;
498     }
499   }
500 
501   return ret;
502 }
503 
inheriters(const Declaration * decl,uint & maxAllowedSteps,bool collectVersions)504 QList<Declaration*> DUChainUtils::inheriters(const Declaration* decl, uint& maxAllowedSteps, bool collectVersions)
505 {
506   auto inheriters = inheritersInternal(decl, maxAllowedSteps, collectVersions);
507 
508   // remove duplicates
509   std::sort(inheriters.begin(), inheriters.end());
510   inheriters.erase(std::unique(inheriters.begin(), inheriters.end()), inheriters.end());
511 
512   return inheriters;
513 }
514 
overriders(const Declaration * currentClass,const Declaration * overriddenDeclaration,uint & maxAllowedSteps)515 QList<Declaration*> DUChainUtils::overriders(const Declaration* currentClass, const Declaration* overriddenDeclaration, uint& maxAllowedSteps) {
516   QList<Declaration*> ret;
517 
518   if(maxAllowedSteps == 0)
519     return ret;
520 
521   if(currentClass != overriddenDeclaration->context()->owner() && currentClass->internalContext())
522     ret += currentClass->internalContext()->findLocalDeclarations(overriddenDeclaration->identifier(), CursorInRevision::invalid(), currentClass->topContext(), overriddenDeclaration->abstractType());
523 
524   const auto inheriters = DUChainUtils::inheriters(currentClass, maxAllowedSteps);
525   for (Declaration* inheriter : inheriters) {
526     ret += overriders(inheriter, overriddenDeclaration, maxAllowedSteps);
527   }
528 
529   return ret;
530 }
531 
hasUse(DUContext * context,int usedDeclarationIndex)532 static bool hasUse(DUContext* context, int usedDeclarationIndex) {
533   if(usedDeclarationIndex == std::numeric_limits<int>::max())
534     return false;
535 
536   for(int a = 0; a < context->usesCount(); ++a)
537     if(context->uses()[a].m_declarationIndex == usedDeclarationIndex)
538       return true;
539 
540   const auto childContexts = context->childContexts();
541   return std::any_of(childContexts.begin(), childContexts.end(), [&](DUContext* child) {
542     return hasUse(child, usedDeclarationIndex);
543   });
544 }
545 
contextHasUse(DUContext * context,Declaration * declaration)546 bool DUChainUtils::contextHasUse(DUContext* context, Declaration* declaration) {
547   return hasUse(context, context->topContext()->indexForUsedDeclaration(declaration, false));
548 }
549 
countUses(DUContext * context,int usedDeclarationIndex)550 static uint countUses(DUContext* context, int usedDeclarationIndex) {
551   if(usedDeclarationIndex == std::numeric_limits<int>::max())
552     return 0;
553 
554   uint ret = 0;
555 
556   for(int a = 0; a < context->usesCount(); ++a)
557     if(context->uses()[a].m_declarationIndex == usedDeclarationIndex)
558       ++ret;
559 
560   const auto childContexts = context->childContexts();
561   for (DUContext* child : childContexts) {
562     ret += countUses(child, usedDeclarationIndex);
563   }
564 
565   return ret;
566 }
567 
contextCountUses(DUContext * context,Declaration * declaration)568 uint DUChainUtils::contextCountUses(DUContext* context, Declaration* declaration) {
569   return countUses(context, context->topContext()->indexForUsedDeclaration(declaration, false));
570 }
571 
overridden(const Declaration * decl)572 Declaration* DUChainUtils::overridden(const Declaration* decl) {
573   const auto* classFunDecl = dynamic_cast<const ClassFunctionDeclaration*>(decl);
574   if(!classFunDecl || !classFunDecl->isVirtual())
575     return nullptr;
576 
577   QList<Declaration*> decls;
578 
579   const auto importedParentContexts = decl->context()->importedParentContexts();
580   for (const DUContext::Import &import : importedParentContexts) {
581     DUContext* ctx = import.context(decl->topContext());
582     if(ctx)
583       decls += ctx->findDeclarations(QualifiedIdentifier(decl->identifier()),
584                                             CursorInRevision::invalid(), decl->abstractType(), decl->topContext(), DUContext::DontSearchInParent);
585   }
586 
587   auto it = std::find_if(decls.constBegin(), decls.constEnd(), [&](Declaration* found) {
588     const auto* foundClassFunDecl = dynamic_cast<const ClassFunctionDeclaration*>(found);
589     return (foundClassFunDecl && foundClassFunDecl->isVirtual());
590   });
591 
592   return (it != decls.constEnd()) ? *it : nullptr;
593 }
594 
functionContext(Declaration * decl)595 DUContext* DUChainUtils::functionContext(Declaration* decl) {
596   DUContext* functionContext = decl->internalContext();
597   if(functionContext && functionContext->type() != DUContext::Function) {
598     const auto importedParentContexts = functionContext->importedParentContexts();
599     for (const DUContext::Import& import : importedParentContexts) {
600       DUContext* ctx = import.context(decl->topContext());
601       if(ctx && ctx->type() == DUContext::Function)
602         functionContext = ctx;
603     }
604   }
605 
606   if(functionContext && functionContext->type() == DUContext::Function)
607     return functionContext;
608   return nullptr;
609 }
610 
allProblemsForContext(const KDevelop::ReferencedTopDUContext & top)611 QVector<KDevelop::Problem::Ptr> KDevelop::DUChainUtils::allProblemsForContext(const KDevelop::ReferencedTopDUContext& top)
612 {
613     QVector<KDevelop::Problem::Ptr> ret;
614 
615     const auto problems = top->problems();
616     const auto contextProblems = ICore::self()->languageController()->staticAssistantsManager()->problemsForContext(top);
617     ret.reserve(problems.size() + contextProblems.size());
618 
619     for (const auto& p : problems) {
620         ret << p;
621     }
622     for (const auto& p : contextProblems) {
623         ret << p;
624     }
625 
626     return ret;
627 }
628 
629