1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 #ifndef LO_CLANG_SHARED_PLUGINS
17 #include <unordered_set>
19 #include <clang/AST/CXXInheritance.h>
21 #include "config_clang.h"
27 look for unnecessary parentheses
32 // Like clang::Stmt::IgnoreImplicit (lib/AST/Stmt.cpp), but also ignoring CXXConstructExpr and
33 // looking through implicit UserDefinedConversion's member function call:
34 Expr
const * ignoreAllImplicit(Expr
const * expr
) {
38 if (auto const e
= dyn_cast
<ExprWithCleanups
>(expr
)) {
39 expr
= e
->getSubExpr();
41 else if (auto const e
= dyn_cast
<CXXConstructExpr
>(expr
)) {
42 if (e
->getNumArgs() == 1) {
46 else if (auto const e
= dyn_cast
<MaterializeTemporaryExpr
>(expr
)) {
47 expr
= e
->GetTemporaryExpr();
49 else if (auto const e
= dyn_cast
<CXXBindTemporaryExpr
>(expr
)) {
50 expr
= e
->getSubExpr();
52 else if (auto const e
= dyn_cast
<ImplicitCastExpr
>(expr
)) {
53 expr
= e
->getSubExpr();
54 if (e
->getCastKind() == CK_UserDefinedConversion
) {
55 auto const ce
= cast
<CXXMemberCallExpr
>(expr
);
56 assert(ce
->getNumArgs() == 0);
57 expr
= ce
->getImplicitObjectArgument();
60 #if CLANG_VERSION >= 80000
61 else if (auto const e
= dyn_cast
<ConstantExpr
>(expr
)) {
62 expr
= e
->getSubExpr();
71 class UnnecessaryParen
:
72 public loplugin::FilteringRewritePlugin
<UnnecessaryParen
>
75 explicit UnnecessaryParen(loplugin::InstantiationData
const & data
):
76 FilteringRewritePlugin(data
) {}
78 virtual bool preRun() override
80 StringRef
fn(handler
.getMainFileName());
81 // fixing this, makes the source in the .y files look horrible
82 if (loplugin::isSamePathname(fn
, WORKDIR
"/YaccTarget/unoidl/source/sourceprovider-parser.cxx"))
84 if (loplugin::isSamePathname(fn
, WORKDIR
"/YaccTarget/idlc/source/parser.cxx"))
88 virtual void run() override
91 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
94 bool VisitParenExpr(const ParenExpr
*);
95 bool VisitIfStmt(const IfStmt
*);
96 bool VisitDoStmt(const DoStmt
*);
97 bool VisitWhileStmt(const WhileStmt
*);
98 bool VisitForStmt(ForStmt
const * stmt
);
99 bool VisitSwitchStmt(const SwitchStmt
*);
100 bool VisitCaseStmt(const CaseStmt
*);
101 bool VisitReturnStmt(const ReturnStmt
* );
102 bool VisitCallExpr(const CallExpr
*);
103 bool VisitVarDecl(const VarDecl
*);
104 bool VisitCXXOperatorCallExpr(const CXXOperatorCallExpr
*);
105 bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr
const *);
106 bool VisitConditionalOperator(ConditionalOperator
const * expr
);
107 bool VisitBinaryConditionalOperator(BinaryConditionalOperator
const * expr
);
108 bool VisitMemberExpr(const MemberExpr
*f
);
109 bool VisitCXXDeleteExpr(const CXXDeleteExpr
*);
111 void VisitSomeStmt(Stmt
const * stmt
, const Expr
* cond
, StringRef stmtName
);
113 void handleUnreachableCodeConditionParens(Expr
const * expr
);
115 // Hack for libxml2's BAD_CAST object-like macro (expanding to "(xmlChar *)"), which is
116 // typically used as if it were a function-like macro, e.g., as "BAD_CAST(pName)" in
117 // SwNode::dumpAsXml (sw/source/core/docnode/node.cxx):
118 bool isPrecededBy_BAD_CAST(Expr
const * expr
);
120 bool badCombination(SourceLocation loc
, int prevOffset
, int nextOffset
);
122 bool removeParens(ParenExpr
const * expr
);
124 std::unordered_set
<ParenExpr
const *> handled_
;
127 bool UnnecessaryParen::VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr
const * expr
)
129 if (expr
->getKind() == UETT_SizeOf
&& !expr
->isArgumentType()) {
130 if (auto const e
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(expr
->getArgumentExpr()))) {
137 bool UnnecessaryParen::VisitConditionalOperator(ConditionalOperator
const * expr
) {
138 handleUnreachableCodeConditionParens(expr
->getCond());
142 bool UnnecessaryParen::VisitBinaryConditionalOperator(BinaryConditionalOperator
const * expr
) {
143 handleUnreachableCodeConditionParens(expr
->getCond());
147 bool UnnecessaryParen::VisitParenExpr(const ParenExpr
* parenExpr
)
149 if (ignoreLocation(parenExpr
))
151 if (compat::getBeginLoc(parenExpr
).isMacroID())
153 if (handled_
.find(parenExpr
) != handled_
.end())
156 auto subExpr
= ignoreAllImplicit(parenExpr
->getSubExpr());
158 if (auto subParenExpr
= dyn_cast
<ParenExpr
>(subExpr
))
160 if (compat::getBeginLoc(subParenExpr
).isMacroID())
163 DiagnosticsEngine::Warning
, "parentheses around parentheses",
164 compat::getBeginLoc(parenExpr
))
165 << parenExpr
->getSourceRange();
166 handled_
.insert(subParenExpr
);
169 // Somewhat redundantly add parenExpr to handled_, so that issues within InitListExpr don't get
170 // reported twice (without having to change TraverseInitListExpr to only either traverse the
171 // syntactic or semantic form, as other plugins do):
173 if (isa
<DeclRefExpr
>(subExpr
)) {
174 if (!isPrecededBy_BAD_CAST(parenExpr
)) {
176 DiagnosticsEngine::Warning
, "unnecessary parentheses around identifier",
177 compat::getBeginLoc(parenExpr
))
178 << parenExpr
->getSourceRange();
179 handled_
.insert(parenExpr
);
181 } else if (isa
<IntegerLiteral
>(subExpr
) || isa
<CharacterLiteral
>(subExpr
)
182 || isa
<FloatingLiteral
>(subExpr
) || isa
<ImaginaryLiteral
>(subExpr
)
183 || isa
<CXXBoolLiteralExpr
>(subExpr
) || isa
<CXXNullPtrLiteralExpr
>(subExpr
)
184 || isa
<ObjCBoolLiteralExpr
>(subExpr
))
186 auto const loc
= compat::getBeginLoc(subExpr
);
187 if (loc
.isMacroID() && compiler
.getSourceManager().isAtStartOfImmediateMacroExpansion(loc
))
189 // just in case the macro could also expand to something that /would/ require
194 DiagnosticsEngine::Warning
, "unnecessary parentheses around literal",
195 compat::getBeginLoc(parenExpr
))
196 << parenExpr
->getSourceRange();
197 handled_
.insert(parenExpr
);
198 } else if (auto const e
= dyn_cast
<clang::StringLiteral
>(subExpr
)) {
199 if (e
->getNumConcatenated() == 1 && !isPrecededBy_BAD_CAST(parenExpr
)) {
201 DiagnosticsEngine::Warning
,
202 "unnecessary parentheses around single-token string literal",
203 compat::getBeginLoc(parenExpr
))
204 << parenExpr
->getSourceRange();
205 handled_
.insert(parenExpr
);
207 } else if (auto const e
= dyn_cast
<UnaryOperator
>(subExpr
)) {
208 auto const op
= e
->getOpcode();
209 if (op
== UO_Plus
|| op
== UO_Minus
) {
210 auto const e2
= e
->getSubExpr();
211 if (isa
<IntegerLiteral
>(e2
) || isa
<FloatingLiteral
>(e2
) || isa
<ImaginaryLiteral
>(e2
)) {
213 DiagnosticsEngine::Warning
,
214 "unnecessary parentheses around signed numeric literal",
215 compat::getBeginLoc(parenExpr
))
216 << parenExpr
->getSourceRange();
217 handled_
.insert(parenExpr
);
220 } else if (isa
<CXXNamedCastExpr
>(subExpr
)) {
221 if (!removeParens(parenExpr
)) {
223 DiagnosticsEngine::Warning
, "unnecessary parentheses around cast",
224 compat::getBeginLoc(parenExpr
))
225 << parenExpr
->getSourceRange();
227 handled_
.insert(parenExpr
);
228 } else if (auto memberExpr
= dyn_cast
<MemberExpr
>(subExpr
)) {
229 if (isa
<CXXThisExpr
>(ignoreAllImplicit(memberExpr
->getBase()))) {
231 DiagnosticsEngine::Warning
, "unnecessary parentheses around member expr",
232 compat::getBeginLoc(parenExpr
))
233 << parenExpr
->getSourceRange();
234 handled_
.insert(parenExpr
);
241 bool UnnecessaryParen::VisitIfStmt(const IfStmt
* ifStmt
)
243 handleUnreachableCodeConditionParens(ifStmt
->getCond());
244 VisitSomeStmt(ifStmt
, ifStmt
->getCond(), "if");
248 bool UnnecessaryParen::VisitDoStmt(const DoStmt
* doStmt
)
250 VisitSomeStmt(doStmt
, doStmt
->getCond(), "do");
254 bool UnnecessaryParen::VisitWhileStmt(const WhileStmt
* whileStmt
)
256 handleUnreachableCodeConditionParens(whileStmt
->getCond());
257 VisitSomeStmt(whileStmt
, whileStmt
->getCond(), "while");
261 bool UnnecessaryParen::VisitForStmt(ForStmt
const * stmt
) {
262 if (auto const cond
= stmt
->getCond()) {
263 handleUnreachableCodeConditionParens(cond
);
268 bool UnnecessaryParen::VisitSwitchStmt(const SwitchStmt
* switchStmt
)
270 VisitSomeStmt(switchStmt
, switchStmt
->getCond(), "switch");
274 bool UnnecessaryParen::VisitCaseStmt(const CaseStmt
* caseStmt
)
276 VisitSomeStmt(caseStmt
, caseStmt
->getLHS(), "case");
280 bool UnnecessaryParen::VisitReturnStmt(const ReturnStmt
* returnStmt
)
282 if (ignoreLocation(returnStmt
))
285 if (!returnStmt
->getRetValue())
287 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(returnStmt
->getRetValue()));
290 if (compat::getBeginLoc(parenExpr
).isMacroID())
292 // assignments need extra parentheses or they generate a compiler warning
293 auto binaryOp
= dyn_cast
<BinaryOperator
>(parenExpr
->getSubExpr());
294 if (binaryOp
&& binaryOp
->getOpcode() == BO_Assign
)
297 // only non-operator-calls for now
298 auto subExpr
= ignoreAllImplicit(parenExpr
->getSubExpr());
299 if (isa
<CallExpr
>(subExpr
) && !isa
<CXXOperatorCallExpr
>(subExpr
))
302 DiagnosticsEngine::Warning
, "parentheses immediately inside return statement",
303 compat::getBeginLoc(parenExpr
))
304 << parenExpr
->getSourceRange();
305 handled_
.insert(parenExpr
);
310 void UnnecessaryParen::VisitSomeStmt(const Stmt
* stmt
, const Expr
* cond
, StringRef stmtName
)
312 if (ignoreLocation(stmt
))
315 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(cond
));
317 if (handled_
.find(parenExpr
) != handled_
.end()) {
320 if (compat::getBeginLoc(parenExpr
).isMacroID())
322 // assignments need extra parentheses or they generate a compiler warning
323 auto binaryOp
= dyn_cast
<BinaryOperator
>(parenExpr
->getSubExpr());
324 if (binaryOp
&& binaryOp
->getOpcode() == BO_Assign
)
326 if (auto const opCall
= dyn_cast
<CXXOperatorCallExpr
>(parenExpr
->getSubExpr())) {
327 if (opCall
->getOperator() == OO_Equal
) {
332 DiagnosticsEngine::Warning
, "parentheses immediately inside %0 statement",
333 compat::getBeginLoc(parenExpr
))
335 << parenExpr
->getSourceRange();
336 handled_
.insert(parenExpr
);
340 bool UnnecessaryParen::VisitCallExpr(const CallExpr
* callExpr
)
342 if (ignoreLocation(callExpr
))
344 if (callExpr
->getNumArgs() != 1 || isa
<CXXOperatorCallExpr
>(callExpr
))
347 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(callExpr
->getArg(0)));
350 if (compat::getBeginLoc(parenExpr
).isMacroID())
352 // assignments need extra parentheses or they generate a compiler warning
353 auto binaryOp
= dyn_cast
<BinaryOperator
>(parenExpr
->getSubExpr());
354 if (binaryOp
&& binaryOp
->getOpcode() == BO_Assign
)
357 DiagnosticsEngine::Warning
, "parentheses immediately inside single-arg call",
358 compat::getBeginLoc(parenExpr
))
359 << parenExpr
->getSourceRange();
360 handled_
.insert(parenExpr
);
364 bool UnnecessaryParen::VisitCXXDeleteExpr(const CXXDeleteExpr
* deleteExpr
)
366 if (ignoreLocation(deleteExpr
))
369 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(deleteExpr
->getArgument()));
372 if (compat::getBeginLoc(parenExpr
).isMacroID())
374 // assignments need extra parentheses or they generate a compiler warning
375 auto binaryOp
= dyn_cast
<BinaryOperator
>(parenExpr
->getSubExpr());
376 if (binaryOp
&& binaryOp
->getOpcode() == BO_Assign
)
379 DiagnosticsEngine::Warning
, "parentheses immediately inside delete expr",
380 compat::getBeginLoc(parenExpr
))
381 << parenExpr
->getSourceRange();
382 handled_
.insert(parenExpr
);
386 bool UnnecessaryParen::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr
* callExpr
)
388 if (ignoreLocation(callExpr
))
390 if (callExpr
->getNumArgs() != 2)
393 // Same logic as CXXOperatorCallExpr::isAssignmentOp(), which our supported clang
395 auto Opc
= callExpr
->getOperator();
396 if (Opc
!= OO_Equal
&& Opc
!= OO_StarEqual
&&
397 Opc
!= OO_SlashEqual
&& Opc
!= OO_PercentEqual
&&
398 Opc
!= OO_PlusEqual
&& Opc
!= OO_MinusEqual
&&
399 Opc
!= OO_LessLessEqual
&& Opc
!= OO_GreaterGreaterEqual
&&
400 Opc
!= OO_AmpEqual
&& Opc
!= OO_CaretEqual
&&
403 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(callExpr
->getArg(1)));
406 if (compat::getBeginLoc(parenExpr
).isMacroID())
408 // Sometimes parentheses make the RHS of an assignment easier to read by
409 // visually disambiguating the = from a call to ==
410 auto sub
= parenExpr
->getSubExpr();
411 if (auto subBinOp
= dyn_cast
<BinaryOperator
>(sub
))
413 if (!(subBinOp
->isMultiplicativeOp() || subBinOp
->isAdditiveOp() || subBinOp
->isPtrMemOp()))
416 if (auto subOperatorCall
= dyn_cast
<CXXOperatorCallExpr
>(sub
))
418 auto op
= subOperatorCall
->getOperator();
419 if (!((op
>= OO_Plus
&& op
<= OO_Exclaim
) || (op
>= OO_ArrowStar
&& op
<= OO_Subscript
)))
422 if (isa
<ConditionalOperator
>(sub
))
426 DiagnosticsEngine::Warning
, "parentheses immediately inside assignment",
427 compat::getBeginLoc(parenExpr
))
428 << parenExpr
->getSourceRange();
429 handled_
.insert(parenExpr
);
433 bool UnnecessaryParen::VisitVarDecl(const VarDecl
* varDecl
)
435 if (ignoreLocation(varDecl
))
437 if (!varDecl
->getInit())
440 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(varDecl
->getInit()));
443 if (compat::getBeginLoc(parenExpr
).isMacroID())
446 // Sometimes parentheses make the RHS of an assignment easier to read by
447 // visually disambiguating the = from a call to ==
448 auto sub
= parenExpr
->getSubExpr();
449 if (auto subBinOp
= dyn_cast
<BinaryOperator
>(sub
))
451 if (!(subBinOp
->isMultiplicativeOp() || subBinOp
->isAdditiveOp() || subBinOp
->isPtrMemOp()))
454 if (auto subOperatorCall
= dyn_cast
<CXXOperatorCallExpr
>(sub
))
456 auto op
= subOperatorCall
->getOperator();
457 if (!((op
>= OO_Plus
&& op
<= OO_Exclaim
) || (op
>= OO_ArrowStar
&& op
<= OO_Subscript
)))
460 if (isa
<ConditionalOperator
>(sub
))
463 // these two are for "parentheses were disambiguated as a function declaration [-Werror,-Wvexing-parse]"
464 auto const sub2
= sub
->IgnoreImplicit();
465 if (isa
<CXXTemporaryObjectExpr
>(sub2
)
466 || isa
<CXXFunctionalCastExpr
>(sub2
))
470 DiagnosticsEngine::Warning
, "parentheses immediately inside vardecl statement",
471 compat::getBeginLoc(parenExpr
))
472 << parenExpr
->getSourceRange();
473 handled_
.insert(parenExpr
);
477 bool UnnecessaryParen::VisitMemberExpr(const MemberExpr
* memberExpr
)
479 if (ignoreLocation(memberExpr
))
482 auto parenExpr
= dyn_cast
<ParenExpr
>(ignoreAllImplicit(memberExpr
->getBase()));
485 if (handled_
.find(parenExpr
) != handled_
.end())
487 if (compat::getBeginLoc(parenExpr
).isMacroID())
490 auto sub
= parenExpr
->getSubExpr();
491 if (isa
<CallExpr
>(sub
)) {
492 if (isa
<CXXOperatorCallExpr
>(sub
))
494 } else if (isa
<CXXConstructExpr
>(sub
)) {
496 } else if (isa
<MemberExpr
>(sub
)) {
498 } else if (isa
<DeclRefExpr
>(sub
)) {
504 DiagnosticsEngine::Warning
, "unnecessary parentheses around member expr",
505 compat::getBeginLoc(parenExpr
))
506 << parenExpr
->getSourceRange();
507 handled_
.insert(parenExpr
);
511 // Conservatively assume any parenthesised integer or Boolean (incl. Objective-C ones) literal in
512 // certain condition expressions (i.e., those for which handleUnreachableCodeConditionParens is
513 // called) to be parenthesised to silence Clang -Wunreachable-code, if that is either the whole
514 // condition expression or appears as a certain sub-expression (looking at what isConfigurationValue
515 // in Clang's lib/Analysis/ReachableCode.cpp looks for, descending into certain unary and binary
517 void UnnecessaryParen::handleUnreachableCodeConditionParens(Expr
const * expr
) {
519 auto const e
= ignoreAllImplicit(expr
);
520 if (auto const e1
= dyn_cast
<ParenExpr
>(e
)) {
521 auto const sub
= e1
->getSubExpr();
522 if (isa
<IntegerLiteral
>(sub
) || isa
<CXXBoolLiteralExpr
>(sub
)
523 || isa
<ObjCBoolLiteralExpr
>(sub
))
527 } else if (auto const e1
= dyn_cast
<UnaryOperator
>(e
)) {
528 if (e1
->getOpcode() == UO_LNot
) {
529 handleUnreachableCodeConditionParens(e1
->getSubExpr());
531 } else if (auto const e1
= dyn_cast
<BinaryOperator
>(e
)) {
532 if (e1
->isLogicalOp() || e1
->isComparisonOp()) {
533 handleUnreachableCodeConditionParens(e1
->getLHS());
534 handleUnreachableCodeConditionParens(e1
->getRHS());
539 bool UnnecessaryParen::isPrecededBy_BAD_CAST(Expr
const * expr
) {
540 if (compat::getBeginLoc(expr
).isMacroID()) {
543 SourceManager
& SM
= compiler
.getSourceManager();
544 const char *p1
= SM
.getCharacterData( compat::getBeginLoc(expr
).getLocWithOffset(-10) );
545 const char *p2
= SM
.getCharacterData( compat::getBeginLoc(expr
) );
546 return std::string(p1
, p2
- p1
).find("BAD_CAST") != std::string::npos
;
551 bool badCombinationChar(char c
) {
552 return (c
>= 'A' && c
<= 'Z') || (c
>= 'a' && c
<= 'z') || (c
>= '0' && c
<= '9') || c
== '_'
553 || c
== '+' || c
== '-' || c
== '\'' || c
== '"';
558 bool UnnecessaryParen::badCombination(SourceLocation loc
, int prevOffset
, int nextOffset
) {
559 //TODO: check for start/end of file; take backslash-newline line concatenation into account
561 = compiler
.getSourceManager().getCharacterData(loc
.getLocWithOffset(prevOffset
))[0];
563 = compiler
.getSourceManager().getCharacterData(loc
.getLocWithOffset(nextOffset
))[0];
564 // An approximation of avoiding whatever combinations that would cause two adjacent tokens to be
565 // lexed differently, using, for now, letters (TODO: non-ASCII ones) and digits and '_'; '+' and
566 // '-' (to avoid ++, etc.); '\'' and '"' (to avoid u'x' or "foo"bar, etc.):
567 return badCombinationChar(c1
) && badCombinationChar(c2
);
570 bool UnnecessaryParen::removeParens(ParenExpr
const * expr
) {
571 if (rewriter
== nullptr) {
574 auto const firstBegin
= compat::getBeginLoc(expr
);
575 auto secondBegin
= compat::getEndLoc(expr
);
576 if (firstBegin
.isMacroID() || secondBegin
.isMacroID()) {
579 unsigned firstLen
= Lexer::MeasureTokenLength(
580 firstBegin
, compiler
.getSourceManager(), compiler
.getLangOpts());
581 for (auto l
= firstBegin
.getLocWithOffset(std::max
<unsigned>(firstLen
, 1));;
582 l
= l
.getLocWithOffset(1))
584 unsigned n
= Lexer::MeasureTokenLength(
585 l
, compiler
.getSourceManager(), compiler
.getLangOpts());
591 unsigned secondLen
= Lexer::MeasureTokenLength(
592 secondBegin
, compiler
.getSourceManager(), compiler
.getLangOpts());
594 auto l
= secondBegin
.getLocWithOffset(-1);
595 auto const c
= compiler
.getSourceManager().getCharacterData(l
)[0];
597 if (compiler
.getSourceManager().getCharacterData(l
.getLocWithOffset(-1))[0] == '\\') {
600 } else if (!(c
== ' ' || c
== '\t' || c
== '\v' || c
== '\f')) {
606 if (!replaceText(firstBegin
, firstLen
, badCombination(firstBegin
, -1, firstLen
) ? " " : "")) {
609 DiagnosticsEngine::Fatal
,
610 "TODO: cannot rewrite opening parenthesis, needs investigation",
613 DiagnosticsEngine::Note
, "when removing these parentheses", expr
->getExprLoc())
614 << expr
->getSourceRange();
618 if (!replaceText(secondBegin
, secondLen
, badCombination(secondBegin
, -1, secondLen
) ? " " : ""))
620 //TODO: roll back first change
623 DiagnosticsEngine::Fatal
,
624 "TODO: cannot rewrite closing parenthesis, needs investigation",
627 DiagnosticsEngine::Note
, "when removing these parentheses", expr
->getExprLoc())
628 << expr
->getSourceRange();
635 loplugin::Plugin::Registration
< UnnecessaryParen
> unnecessaryparen("unnecessaryparen", true);
639 #endif // LO_CLANG_SHARED_PLUGINS
641 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */