diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index eb4cc616897b4..8a14fd0a818d2 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -213,6 +213,12 @@ Attribute Changes in Clang and requires all listed capabilities to be held when accessing the guarded variable. +- The :doc:`ThreadSafetyAnalysis` introduces two new attributes, + ``guarded_by_any`` and ``pt_guarded_by_any``. Unlike ``guarded_by``, these + follow a weaker ownership model: *writing* still requires all listed + capabilities to be held exclusively, but *reading* only requires at least one + of them to be held. + Improvements to Clang's diagnostics ----------------------------------- - Added ``-Wlifetime-safety`` to enable lifetime safety analysis, diff --git a/clang/docs/ThreadSafetyAnalysis.rst b/clang/docs/ThreadSafetyAnalysis.rst index 0628206918f7f..3b45151e648c3 100644 --- a/clang/docs/ThreadSafetyAnalysis.rst +++ b/clang/docs/ThreadSafetyAnalysis.rst @@ -206,6 +206,41 @@ When multiple capabilities are listed, all of them must be held: } +GUARDED_BY_ANY(...) and PT_GUARDED_BY_ANY(...) +---------------------------------------------- + +``GUARDED_BY_ANY`` is an attribute on data members that declares the data +member is protected by a set of capabilities, exploiting the following +invariant: a writer must hold *all* listed capabilities exclusively, so +holding *any one* of them is sufficient to guarantee at least shared (read) +access. Concretely: + +* **Write** access requires *all* listed capabilities to be held exclusively. +* **Read** access requires *at least one* of the listed capabilities to be held + (shared or exclusive). + +``PT_GUARDED_BY_ANY`` is the pointer counterpart: the data member itself is +unconstrained, but the *data it points to* follows the same rules. + +.. code-block:: c++ + + Mutex mu1, mu2; + int a GUARDED_BY_ANY(mu1, mu2); + + void reader() REQUIRES_SHARED(mu1) { + int x = a; // OK: mu1 is held (shared read access). + a = 1; // Warning! Writing requires both mu1 and mu2. + } + + void writer() REQUIRES(mu1, mu2) { + a = 1; // OK: both mu1 and mu2 are held exclusively. + } + + void unprotected() { + int x = a; // Warning! No capability held at all. + } + + REQUIRES(...), REQUIRES_SHARED(...) ----------------------------------- @@ -890,6 +925,12 @@ implementation. #define PT_GUARDED_BY(...) \ THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(__VA_ARGS__)) + #define GUARDED_BY_ANY(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(guarded_by_any(__VA_ARGS__)) + + #define PT_GUARDED_BY_ANY(...) \ + THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by_any(__VA_ARGS__)) + #define ACQUIRED_BEFORE(...) \ THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) diff --git a/clang/include/clang/Analysis/Analyses/ThreadSafety.h b/clang/include/clang/Analysis/Analyses/ThreadSafety.h index 9fb23212aaf97..2b7c20553c578 100644 --- a/clang/include/clang/Analysis/Analyses/ThreadSafety.h +++ b/clang/include/clang/Analysis/Analyses/ThreadSafety.h @@ -193,6 +193,17 @@ class ThreadSafetyHandler { virtual void handleNoMutexHeld(const NamedDecl *D, ProtectedOperationKind POK, AccessKind AK, SourceLocation Loc) {} + /// Warn when a read of a guarded_by_any variable occurs while none of the + /// listed capabilities are held. + /// \param D -- The decl for the protected variable + /// \param POK -- The kind of protected operation (e.g. variable access) + /// \param LockNames -- Comma-separated list of capability names, quoted + /// \param Loc -- The location of the read + virtual void handleGuardedByAnyReadNotHeld(const NamedDecl *D, + ProtectedOperationKind POK, + StringRef LockNames, + SourceLocation Loc) {} + /// Warn when a protected operation occurs while the specific mutex protecting /// the operation is not locked. /// \param Kind -- the capability's name parameter (role, mutex, etc). diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index c3068ce9e69d9..ee9887ebe268b 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -4175,6 +4175,28 @@ def PtGuardedBy : InheritableAttr { let Documentation = [Undocumented]; } +def GuardedByAny : InheritableAttr { + let Spellings = [GNU<"guarded_by_any">]; + let Args = [VariadicExprArgument<"Args">]; + let LateParsed = LateAttrParseExperimentalExt; + let TemplateDependent = 1; + let ParseArgumentsAsUnevaluated = 1; + let InheritEvenIfAlreadyPresent = 1; + let Subjects = SubjectList<[Field, SharedVar]>; + let Documentation = [Undocumented]; +} + +def PtGuardedByAny : InheritableAttr { + let Spellings = [GNU<"pt_guarded_by_any">]; + let Args = [VariadicExprArgument<"Args">]; + let LateParsed = LateAttrParseExperimentalExt; + let TemplateDependent = 1; + let ParseArgumentsAsUnevaluated = 1; + let InheritEvenIfAlreadyPresent = 1; + let Subjects = SubjectList<[Field, SharedVar]>; + let Documentation = [Undocumented]; +} + def AcquiredAfter : InheritableAttr { let Spellings = [GNU<"acquired_after">]; let Args = [VariadicExprArgument<"Args">]; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 0e6b3f51a5231..c6d2f27994fd3 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4328,6 +4328,12 @@ def warn_var_deref_requires_any_lock : Warning< "%select{reading|writing}1 the value pointed to by %0 requires holding " "%select{any mutex|any mutex exclusively}1">, InGroup, DefaultIgnore; +def warn_variable_requires_any_of_locks : Warning< + "reading variable %0 requires holding at least one of %1">, + InGroup, DefaultIgnore; +def warn_var_deref_requires_any_of_locks : Warning< + "reading the value pointed to by %0 requires holding at least one of %1">, + InGroup, DefaultIgnore; def warn_fun_excludes_mutex : Warning< "cannot call function '%1' while %0 '%2' is held">, InGroup, DefaultIgnore; diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index da2bda1b43526..690bf714cec60 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -9761,6 +9761,20 @@ Expected ASTImporter::Import(const Attr *FromAttr) { From->args_size()); break; } + case attr::GuardedByAny: { + const auto *From = cast(FromAttr); + AI.importAttr(From, + AI.importArrayArg(From->args(), From->args_size()).value(), + From->args_size()); + break; + } + case attr::PtGuardedByAny: { + const auto *From = cast(FromAttr); + AI.importAttr(From, + AI.importArrayArg(From->args(), From->args_size()).value(), + From->args_size()); + break; + } case attr::AcquiredAfter: { const auto *From = cast(FromAttr); AI.importAttr(From, diff --git a/clang/lib/Analysis/ThreadSafety.cpp b/clang/lib/Analysis/ThreadSafety.cpp index 91017178fea25..b5c28ce955d36 100644 --- a/clang/lib/Analysis/ThreadSafety.cpp +++ b/clang/lib/Analysis/ThreadSafety.cpp @@ -41,6 +41,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/ScopeExit.h" #include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Allocator.h" #include "llvm/Support/Casting.h" @@ -1282,6 +1283,12 @@ class ThreadSafetyAnalyzer { void warnIfMutexHeld(const FactSet &FSet, const NamedDecl *D, const Expr *Exp, Expr *MutexExp, til::SExpr *Self, SourceLocation Loc); + void warnIfAnyMutexNotHeldForRead(const FactSet &FSet, const NamedDecl *D, + const Expr *Exp, + llvm::ArrayRef Args, + ProtectedOperationKind POK, + SourceLocation Loc); + void checkAccess(const FactSet &FSet, const Expr *Exp, AccessKind AK, ProtectedOperationKind POK); void checkPtAccess(const FactSet &FSet, const Expr *Exp, AccessKind AK, @@ -1868,6 +1875,24 @@ void ThreadSafetyAnalyzer::warnIfMutexHeld(const FactSet &FSet, } } +void ThreadSafetyAnalyzer::warnIfAnyMutexNotHeldForRead( + const FactSet &FSet, const NamedDecl *D, const Expr *Exp, + llvm::ArrayRef Args, ProtectedOperationKind POK, + SourceLocation Loc) { + SmallVector Names; + for (auto *Arg : Args) { + CapabilityExpr Cp = SxBuilder.translateAttrExpr(Arg, D, Exp, nullptr); + if (Cp.isInvalid() || Cp.shouldIgnore()) + continue; + const FactEntry *LDat = FSet.findLockUniv(FactMan, Cp); + if (LDat && LDat->isAtLeast(LK_Shared)) + return; // At least one held — read access is safe. + Names.push_back("'" + Cp.toString() + "'"); + } + if (!Names.empty()) + Handler.handleGuardedByAnyReadNotHeld(D, POK, llvm::join(Names, ", "), Loc); +} + /// Checks guarded_by and pt_guarded_by attributes. /// Whenever we identify an access (read or write) to a DeclRefExpr that is /// marked with guarded_by, we must ensure the appropriate mutexes are held. @@ -1937,6 +1962,17 @@ void ThreadSafetyAnalyzer::checkAccess(const FactSet &FSet, const Expr *Exp, for (const auto *I : D->specific_attrs()) for (auto *Arg : I->args()) warnIfMutexNotHeld(FSet, D, Exp, AK, Arg, POK, nullptr, Loc); + + for (const auto *I : D->specific_attrs()) { + if (AK == AK_Written) { + // Write requires all capabilities. + for (auto *Arg : I->args()) + warnIfMutexNotHeld(FSet, D, Exp, AK, Arg, POK, nullptr, Loc); + } else { + // Read requires at least one capability. + warnIfAnyMutexNotHeldForRead(FSet, D, Exp, I->args(), POK, Loc); + } + } } /// Checks pt_guarded_by and pt_guarded_var attributes. @@ -2003,6 +2039,19 @@ void ThreadSafetyAnalyzer::checkPtAccess(const FactSet &FSet, const Expr *Exp, for (auto *Arg : I->args()) warnIfMutexNotHeld(FSet, D, Exp, AK, Arg, PtPOK, nullptr, Exp->getExprLoc()); + + for (const auto *I : D->specific_attrs()) { + if (AK == AK_Written) { + // Write requires all capabilities. + for (auto *Arg : I->args()) + warnIfMutexNotHeld(FSet, D, Exp, AK, Arg, PtPOK, nullptr, + Exp->getExprLoc()); + } else { + // Read requires at least one capability. + warnIfAnyMutexNotHeldForRead(FSet, D, Exp, I->args(), PtPOK, + Exp->getExprLoc()); + } + } } /// Process a function call, method call, constructor call, diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index ea5ac639ffd5e..5ade30ea56528 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2141,6 +2141,33 @@ class ThreadSafetyReporter : public clang::threadSafety::ThreadSafetyHandler { Warnings.emplace_back(std::move(Warning), getNotes()); } + void handleGuardedByAnyReadNotHeld(const NamedDecl *D, + ProtectedOperationKind POK, + StringRef LockNames, + SourceLocation Loc) override { + unsigned DiagID = 0; + switch (POK) { + case POK_VarAccess: + case POK_PassByRef: + case POK_ReturnByRef: + case POK_PassPointer: + case POK_ReturnPointer: + DiagID = diag::warn_variable_requires_any_of_locks; + break; + case POK_VarDereference: + case POK_PtPassByRef: + case POK_PtReturnByRef: + case POK_PtPassPointer: + case POK_PtReturnPointer: + DiagID = diag::warn_var_deref_requires_any_of_locks; + break; + case POK_FunctionCall: + llvm_unreachable("POK_FunctionCall not applicable here"); + } + PartialDiagnosticAt Warning(Loc, S.PDiag(DiagID) << D << LockNames); + Warnings.emplace_back(std::move(Warning), getNotes()); + } + void handleMutexNotHeld(StringRef Kind, const NamedDecl *D, ProtectedOperationKind POK, Name LockName, LockKind LK, SourceLocation Loc, diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index 7380f0057f2fa..e95638be52df6 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -488,6 +488,27 @@ static void handlePtGuardedByAttr(Sema &S, Decl *D, const ParsedAttr &AL) { PtGuardedByAttr(S.Context, AL, Args.data(), Args.size())); } +static void handleGuardedByAnyAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + SmallVector Args; + if (!checkGuardedByAttrCommon(S, D, AL, Args)) + return; + + D->addAttr(::new (S.Context) + GuardedByAnyAttr(S.Context, AL, Args.data(), Args.size())); +} + +static void handlePtGuardedByAnyAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + SmallVector Args; + if (!checkGuardedByAttrCommon(S, D, AL, Args)) + return; + + if (!threadSafetyCheckIsPointer(S, D, AL)) + return; + + D->addAttr(::new (S.Context) + PtGuardedByAnyAttr(S.Context, AL, Args.data(), Args.size())); +} + static bool checkAcquireOrderAttrCommon(Sema &S, Decl *D, const ParsedAttr &AL, SmallVectorImpl &Args) { if (!AL.checkAtLeastNumArgs(S, 1)) @@ -7988,6 +8009,12 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_PtGuardedBy: handlePtGuardedByAttr(S, D, AL); break; + case ParsedAttr::AT_GuardedByAny: + handleGuardedByAnyAttr(S, D, AL); + break; + case ParsedAttr::AT_PtGuardedByAny: + handlePtGuardedByAnyAttr(S, D, AL); + break; case ParsedAttr::AT_LockReturned: handleLockReturnedAttr(S, D, AL); break; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index cfb78ef5f5897..76949e7fb7a34 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -19453,6 +19453,10 @@ bool Sema::checkThisInStaticMemberFunctionAttributes(CXXMethodDecl *Method) { Args = llvm::ArrayRef(G->args_begin(), G->args_size()); else if (const auto *G = dyn_cast(A)) Args = llvm::ArrayRef(G->args_begin(), G->args_size()); + else if (const auto *G = dyn_cast(A)) + Args = llvm::ArrayRef(G->args_begin(), G->args_size()); + else if (const auto *G = dyn_cast(A)) + Args = llvm::ArrayRef(G->args_begin(), G->args_size()); else if (const auto *AA = dyn_cast(A)) Args = llvm::ArrayRef(AA->args_begin(), AA->args_size()); else if (const auto *AB = dyn_cast(A)) diff --git a/clang/test/SemaCXX/thread-safety-annotations.h b/clang/test/SemaCXX/thread-safety-annotations.h index bcda81cc69049..1678d9f1c58e7 100644 --- a/clang/test/SemaCXX/thread-safety-annotations.h +++ b/clang/test/SemaCXX/thread-safety-annotations.h @@ -32,7 +32,9 @@ #define EXCLUSIVE_UNLOCK_FUNCTION(...) __attribute__((release_capability(__VA_ARGS__))) #define SHARED_UNLOCK_FUNCTION(...) __attribute__((release_shared_capability(__VA_ARGS__))) #define GUARDED_BY(...) __attribute__((guarded_by(__VA_ARGS__))) +#define GUARDED_BY_ANY(...) __attribute__((guarded_by_any(__VA_ARGS__))) #define PT_GUARDED_BY(...) __attribute__((pt_guarded_by(__VA_ARGS__))) +#define PT_GUARDED_BY_ANY(...) __attribute__((pt_guarded_by_any(__VA_ARGS__))) // Common #define REENTRANT_CAPABILITY __attribute__((reentrant_capability)) diff --git a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp index 9ef6d7d3349d2..57f1955f10bfd 100644 --- a/clang/test/SemaCXX/warn-thread-safety-analysis.cpp +++ b/clang/test/SemaCXX/warn-thread-safety-analysis.cpp @@ -1570,6 +1570,50 @@ void func() } } // end namespace thread_annot_lock_42 +namespace guarded_by_any { +// Test GUARDED_BY_ANY: any one capability suffices for read; all are required +// for write. +class Foo { + Mutex mu1, mu2; + int x GUARDED_BY_ANY(mu1, mu2); + int *p PT_GUARDED_BY_ANY(mu1, mu2); + + // All locks held exclusively: read and write both OK. + void f1() EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) { + x = 1; + *p = 1; + } + + // Only mu1 held exclusively: read OK, write warns about mu2. + void f2() EXCLUSIVE_LOCKS_REQUIRED(mu1) { + int y = x; + int z = *p; + x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu2' exclusively}} + *p = 1; // expected-warning {{writing the value pointed to by 'p' requires holding mutex 'mu2' exclusively}} + } + + // One lock held shared: read OK, write warns about both. + void f3() SHARED_LOCKS_REQUIRED(mu1) { + int y = x; + int z = *p; + x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu1' exclusively}} \ + // expected-warning {{writing variable 'x' requires holding mutex 'mu2' exclusively}} + *p = 1; // expected-warning {{writing the value pointed to by 'p' requires holding mutex 'mu1' exclusively}} \ + // expected-warning {{writing the value pointed to by 'p' requires holding mutex 'mu2' exclusively}} + } + + // No locks held: read and write both warn. + void f4() { + int y = x; // expected-warning {{reading variable 'x' requires holding at least one of 'mu1', 'mu2'}} + int z = *p; // expected-warning {{reading the value pointed to by 'p' requires holding at least one of 'mu1', 'mu2'}} + x = 1; // expected-warning {{writing variable 'x' requires holding mutex 'mu1' exclusively}} \ + // expected-warning {{writing variable 'x' requires holding mutex 'mu2' exclusively}} + *p = 1; // expected-warning {{writing the value pointed to by 'p' requires holding mutex 'mu1' exclusively}} \ + // expected-warning {{writing the value pointed to by 'p' requires holding mutex 'mu2' exclusively}} + } +}; +} // end namespace guarded_by_any + namespace thread_annot_lock_46 { // Test the support for annotations on virtual functions. class Base { diff --git a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp index 86d998711a1a0..3fe94958b0443 100644 --- a/clang/test/SemaCXX/warn-thread-safety-parsing.cpp +++ b/clang/test/SemaCXX/warn-thread-safety-parsing.cpp @@ -5,9 +5,11 @@ #define LOCKABLE __attribute__ ((lockable)) #define REENTRANT_CAPABILITY __attribute__ ((reentrant_capability)) #define SCOPED_LOCKABLE __attribute__ ((scoped_lockable)) -#define GUARDED_BY(...) __attribute__ ((guarded_by(__VA_ARGS__))) -#define GUARDED_VAR __attribute__ ((guarded_var)) -#define PT_GUARDED_BY(...) __attribute__ ((pt_guarded_by(__VA_ARGS__))) +#define GUARDED_BY(...) __attribute__ ((guarded_by(__VA_ARGS__))) +#define GUARDED_BY_ANY(...) __attribute__ ((guarded_by_any(__VA_ARGS__))) +#define GUARDED_VAR __attribute__ ((guarded_var)) +#define PT_GUARDED_BY(...) __attribute__ ((pt_guarded_by(__VA_ARGS__))) +#define PT_GUARDED_BY_ANY(...) __attribute__ ((pt_guarded_by_any(__VA_ARGS__))) #define PT_GUARDED_VAR __attribute__ ((pt_guarded_var)) #define ACQUIRED_AFTER(...) __attribute__ ((acquired_after(__VA_ARGS__))) #define ACQUIRED_BEFORE(...) __attribute__ ((acquired_before(__VA_ARGS__))) @@ -454,6 +456,63 @@ int * pgb_var_arg_bad_4 PT_GUARDED_BY(umu); // \ // expected-warning {{'pt_guarded_by' attribute requires arguments whose type is annotated with 'capability' attribute}} +//-----------------------------------------// +// Guarded By Any Attribute (gba) +//-----------------------------------------// + +#if !__has_attribute(guarded_by_any) +#error "Should support guarded_by_any attribute" +#endif + +int gba_var_arg GUARDED_BY_ANY(mu1); +int gba_var_args GUARDED_BY_ANY(mu1, mu2); + +int gba_var_noargs __attribute__((guarded_by_any)); // \ + // expected-error {{'guarded_by_any' attribute takes at least 1 argument}} + +class GBAFoo { + private: + int gba_field_noargs __attribute__((guarded_by_any)); // \ + // expected-error {{'guarded_by_any' attribute takes at least 1 argument}} + int gba_field_args GUARDED_BY_ANY(mu1); + int gba_field_multi GUARDED_BY_ANY(mu1, mu2); +}; + +class GUARDED_BY_ANY(mu1) GBA { // \ + // expected-warning {{'guarded_by_any' attribute only applies to non-static data members and global variables}} +}; + +int gba_var_arg_bad GUARDED_BY_ANY(1); // \ + // expected-warning {{'guarded_by_any' attribute requires arguments whose type is annotated with 'capability' attribute; type here is 'int'}} + +//-----------------------------------------// +// Pt Guarded By Any Attribute (pgba) +//-----------------------------------------// + +#if !__has_attribute(pt_guarded_by_any) +#error "Should support pt_guarded_by_any attribute" +#endif + +int *pgba_ptr_var_arg PT_GUARDED_BY_ANY(mu1); +int *pgba_ptr_var_args PT_GUARDED_BY_ANY(mu1, mu2); + +int *pgba_var_noargs __attribute__((pt_guarded_by_any)); // \ + // expected-error {{'pt_guarded_by_any' attribute takes at least 1 argument}} + +int pgba_var_nonptr PT_GUARDED_BY_ANY(mu1); // \ + // expected-warning {{'pt_guarded_by_any' only applies to pointer types; type here is 'int'}} + +class PGBAFoo { + private: + int *pgba_field_noargs __attribute__((pt_guarded_by_any)); // \ + // expected-error {{'pt_guarded_by_any' attribute takes at least 1 argument}} + int *pgba_field_args PT_GUARDED_BY_ANY(mu1); + int *pgba_field_multi PT_GUARDED_BY_ANY(mu1, mu2); +}; + +int *pgba_var_arg_bad PT_GUARDED_BY_ANY(1); // \ + // expected-warning {{'pt_guarded_by_any' attribute requires arguments whose type is annotated with 'capability' attribute; type here is 'int'}} + //-----------------------------------------// // Acquired After (aa) //-----------------------------------------//