Introduction

This page describes the static checks provided by the package External/CheckerGccPlugins.

Usage

A set of checks is enabled by default when building with gcc. Individual checks are described below. The checkers are implemented as gcc plugins. This takes the form of a shared library that is loaded into gcc using the -fplugin option. During the release build, the plugin is actually taken from the CheckerGccPlugins package from AthenaExternals; the same thing happens for local builds if the CheckerGccPlugins package is not in the local tree. Only if CheckGccPlugins is in the local tree is the locally-built version of the plugins used. (To use a locally-built verion of the checkers, you'll need to first build the checker package itself, then rerun cmake to have it notice the local version.) The set of checkers that are run can be controlled by comand-line arguments. Use -fplugin-arg-libchecker_gccplugins-checkers=LIST to enable the checkers in the comma-separated list LIST. Prefix the name of a checker with no- to instead disable it. Use all or none to enable or disable all the checkers. Use help to print a list of all available checkers. You can control which checkers are run for a local build using the ATLAS_GCC_CHECKERS cmake variable. For example, to enable the threading checker, add -DATLAS_GCC_CHECKERS=thread to your cmake command line.

Available checkers

gaudi_inheritance_plugin (Enabled by default.)

ATLAS classes implementing Gaudi algorithms, services, and tools should not derive directly from the Gaudi Algorithm, Service, and Tool classes, but rather from the classes in AthenaBaseComps: AthAlgorithm, AthService, and AthTool. (There is a small list of exceptions, but it is generally limited to core packages for which using AthenaBaseComps would lead to a circular dependency.) These warnings look like this:
 ../test/gaudi_inheritance_test.cxx:5:7: warning: 'class Foo1' derives directly from 'Algorithm'; should derive from 'AthAlgorithm' instead and use its methods.
 class Foo1 : public Algorithm {};
It is generally not sufficient to just change the inheritance structure; you should also migrate the class to use the facilities provided by the base classes. For example, use evtStore() and detStore() rather than fetching a StoreGateSvc pointer yourself, and use the ATH_MSG_ macros rather creating a MsgStream instance. For some further information, see ImprovingSoftware and AthenaBaseComps.

divcheck_plugin (Disabled by default.)

This check looks for redundant divisions that could be replaced by multiplication by a reciprocal. Division is much slower than multiplication, so if you are dividing several times by the same quantity, it can be faster to take the reciprocal once and then multiply. Be aware that the result will usually not be exactly the same due to rounding issues; this is why the compiler will not make this transformation by default. (It can do this kind of transformation if -ffast-math is used.) There are three separate checks done here: division by constant, repeated division, and division by a loop invariant.

Division by a constant

A division by a constant like this
return x/5.;
will yield a warning:
 x.cc:3:12: warning: Floating-point constant used in division; consider multiplication by inverse. return x/5.;
To get rid of this warning, you can explicitly write it as multiplication by the inverse:
 return x*(1./5.);
In this case, the division will be performed at compile-time. Sometimes this warning can be addressed by adding parentheses. For example,
 return x*10./3;
can be rewritten as
 return x*(10./3);
to ensure that the division is performed at compile time. A common case is doing unit conversions:
 double e_in_gev = e / CLHEP::GeV; 
You can rewrite this as an explicit multiplication by the recriprocal as above, but that's awkward. A better way is to use the Units library from AthenaKernel:
 #included "AthenaKernel/Units.h" ...
 double e_in_gev = e / Athena::Units::GeV; 
The units in Athena::Units are special objects designed that division will be turned into multiplication by a reciprocal at compile-time. In some cases you may not get a warning for a division by a constant. This can be the case if the reciprocal can be evaluated exactly; for example if the divisor is a power of 2. In this case, the compiler can do this transformation itself by default.

Repeated divisions by the same value

Code like this
const double norm = sqrt(x*x + y*y + z*z); 
x /= norm;
y /= norm; 
z /= norm; 
will get a warning:
 x.cc:7:12: warning: Multiple divisions by the same value; consider calculating the inverse once and multiplying. 
y /= norm; ^ x.cc:6:12: 
note: Previous division is here. x /= norm; ^ 
This could be rewritten like
const double norm = sqrt(x*x + y*y + z*z);
const double inv_norm = 1. / norm; 
x *= inv_norm; 
y *= inv_norm; 
z *= inv_norm; 
As mentioned above, this transformation will often change the results slightly, due to rounding effects. Watch out for cases where the denominator may change:
double den = ...;
foo (x / den); 
foo (y / den); ... 
den = den*den; 
foo (z / den);
(though the checker should not give a warning for the last division here; declaring the variable as const would also help to catch this). Also watch out for potential divisions by zero:
double den = ...;
 ... if (den != 0) { foo (x / den); 
If you calculate the inverse, it has to be after the test.

Division by a loop invariant

The checker will warn if you have a division within a loop by a value that does not change within the loop:
double sum = ...; 
for (int i=0; i < N; i++)
 x[i] /= sum; 
gives the warning
x.cc:5:16: warning: Division by loop-invariant value; consider calculating inverse outside of the loop and multiplying within the loop. 
x[i] /= sum; ^ x.cc:4:19: 
note: Loop starts here. for (int i=0; i < N; i++) ^ 
You can rewrite this to move the division outside of the loop:
const double sum = ...; 
const double inv_sum = 1. / sum; 
for (int i=0; i < N; i++) x[i] *= inv_sum; 
The same cautions as previously apply. In particular, be careful about potential division by zero if the loop is not executed. For example, changing
for (int i=0; i < n; i++)
 x[i] /= n; 
to
const double inv_n = 1. / n;
 for (int i=0; i < n; i++)
 x[i] *= inv_n; 
would introduce a division by zero in the case where n is 0.

usingns_plugin (Disabled by default.)

This checker currently requires compiler patches to function correctly. A using declaration or directive should not appear in the global namespace in a header file. Additionally, in the main source file, using should be used only after all #include directives. The checker currently does not warn about the presence of using in namespaces other than the global one, as there are some good use cases for that, but this should be done with caution. For example, this:
// x.h #include using namespace std;
 struct S { vector v; };
 // x.cc 
#include "x.h"
gets the warning
 x.h:2:17: warning: Do not use `using namespace' in an #included file. using namespace std; ^ 
while this code
 using namespace std;
 #include vector v;
gets the warning
 x.cc:1:17: warning: Do not use `using namespace' before an #include. using namespace std; ^ 

naming_plugin (Enabled by default.)

ATLAS has certain conventions on the naming of identifiers. Most importantly, private or protected class members should start with m_ (or possibly s_ if they are static), and no other identifiers should start with m_. Additionally, no identifiers should start with an underscore. Those are the basic rules that this checker enforces. However, there are a number of exceptions to accommodate various existing usages. These include: * Warnings are disabled for most non-ATLAS code. * Private/protected class members may start with p_ if they have pointer type. * Private/protected static class members may have all-capital names. * For classes that appear to be following ROOT conventions (name starts with T followed by a capital letter), private/protected members may start with f. * No checking is done for persistent classes or for aux store container classes. Further exceptions can be added in the case of code that ATLAS does not control, or where changing names would break backwards compatibility. We will be reluctant to add further exceptions for other reasons. Here are some examples of warnings from this checker.
naming_test.cxx:9:7: warning: ATLAS coding standards require that the name of private non-static member 'c::a' start with 'm_'. int a; ^  
naming_test.cxx:21:8: warning: ATLAS coding standards require that the name of function 'void c::m_foo()' not start with 'm_'. void m_foo(); ^ 
naming_test.cxx:50:7: warning: ATLAS coding standards require that variable name '_s' not start with '_'. int _s = 20; ^ 

thread_plugin (Enabled by default.)

This checker warns about constructions that may not be compatible with multithreading. There are quite a number of separate checks, but most can be classified as either checking for the use of static data, checking for violations of constness, or checking relationships between functions. There are a number of custom attributes and pragmas that affect the operation of this checker. For the sake of portability, these should not be used directly; rather, use the macros defined in CxxUtils/checker_macros.h. These macros will be used in this documentation rather than the attributes directly.

Selecting code for checking

Even when the checker is enabled, it does not check code by default; rather, code to be checked must be explicitly marked. There are several ways of doing this. An entire directory tree may be marked for checking by creating a (empty) file at the root of the tree with the name ATLAS_CHECK_THREAD_SAFETY. For a package, this file may also be placed in the include directory. This is recommended for packages that have an include directory, since in that case the file will be visible in an installed release. An entire file may be marked for checking with the directive ATLAS_CHECK_FILE_THREAD_SAFETY:
#include "CxxUtils/checker_macros.h" ATLAS_CHECK_FILE_THREAD_SAFETY;
and checking may be disabled for a given file using ATLAS_NO_CHECK_FILE_THREAD_SAFETY:
 #include "CxxUtils/checker_macros.h" ATLAS_NO_CHECK_FILE_THREAD_SAFETY; 
Checking may also be enabled for an individual class or function with ATLAS_CHECK_THREAD_SAFETY:
 #include "CxxUtils/checker_macros.h" 
void f1 ATLAS_CHECK_THREAD_SAFETY () { ... } 
class ATLAS_CHECK_THREAD_SAFETY C { ... }

Checks related to the use of static data

Non-const static data may not be used directly:

 int f1(int xx) { 
  static int x;
  x = xx; // warning here
  return x; // warning here 
  }
x.cc: In function ‘int f1(int)’: x.cc:8:5: warning: Use of static expression ‘x’ of type ‘int’ within function ‘int f1(int)’ may not be thread-safe. 
x = xx; // warning here
 ~~^~~~ 
x.cc:7:14: note: Declared here:
 static int x;
 ^ 
x.cc:9:10: warning: Use of static expression ‘x’ of type ‘int’ within function ‘int f1(int)’ may not be thread-safe. 
 return x; // warning here  
 ^ 
x.cc:7:14: note: Declared here: 
static int x;
^ 
or bound to a non-const pointer or reference:
static int y1; 
int* f1() { 
return &y1; // warning here }
void foo1(int, int*);
void f2() { foo1(3, &y1); // warning here }
 x.cc: In function ‘int* f1()’: 
 x.cc:8:11: warning: Pointer or reference bound to static expression ‘y1’ of type ‘int’ within function ‘int* f1()’; may not be thread-safe. 
 return &y1; // warning here
 ^~ x.cc:5:12: 
note: Declared here: 
 static int y1;
 ^~ 
x.cc: In function ‘void f2()’: x.cc:14:7: warning: Static expression ‘y1’ of type ‘int’ passed to pointer or reference function argument of ‘foo1’ within function ‘void f2()’; may not be thread-safe.
 foo1(3, &y1); // warning here
 ~~~~^~~~~~~~ x.cc:5:12: 
note: Declared here: 
static int y1; 
^~ 
Declaring a non-const static member variable will also get a warning:
class C { public: C(); // We don't warn about POD types.
 static int x; // warning here. };
 
x.cc:8:14: warning: Static member ‘C::x’ of type ‘int’ within thread-safe class ‘class C’; may not be thread-safe.
 static int x; // warning here. 
^ 
Warnings are not issued for variables declared as thread_local:
static thread_local int x; 
or for std::atomic specializations for fundamental types. These warnings may be removed by making the variable const. They may also be suppressed by declaring the variable ATLAS_THREAD_SAFE:
static int x ATLAS_THREAD_SAFE;
or by declaring the function processing the static data as ATLAS_NOT_THREAD_SAFE or ATLAS_NOT_REENTRANT:
int f5 ATLAS_NOT_THREAD_SAFE (int xx) { ... } 
int f6 ATLAS_NOT_REENTRANT (int xx) { ... } 
A function passing a const static object to a function marked ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE must be declared ATLAS_NOT_REENTRANT (see below).

Checks related to constness

The checker will warn about discarding const via a cast:
int* f1(const int* y) { return const_cast(y); // warning here }
 x.cc: In function ‘int* f1(const int*)’: x.cc:7:28: warning: Const discarded from expression ‘y’ of type ‘const int*’ within function ‘int* f1(const int*)’; may not be thread-safe. 
return const_cast(y); // warning here
 ^ 
It will also warn about returning a non-const pointer or reference to member data from a const member function:
struct S { int* x; int* f1() const; }; 
int* S::f1() const { return x; // warning here } 
x.cc: In member function ‘int* S::f1() const’: x.cc:13:10: warning: Returning non-const pointer or reference member ‘S::x’ of type ‘int*’ from structure ‘const struct S’ within const member function ‘int* S::f1() const’; may not be thread-safe.
 return x; // warning here 
 ^ x.cc:6:8: 
note: Declared here:
 int* x; 
^
Writing to a mutable class member, or taking a non-const reference to a mutable class member, within a const function will also get a warning:
void foo(int&); 
 struct S { mutable int x; void f1(int y) const; 
 void f2() const; }; 
 void S::f1(int y) const { x = y; // warning here }
 void S::f2() const { foo(x); // warning here }
 
x.cc: In member function ‘void S::f1(int) const’: x.cc:15:5: warning: Setting mutable field ‘S::x’ of type ‘int’ within thread-safe function ‘void S::f1(int) const’; may not be thread-safe. 
x = y; // warning here
 ~~^~~ x.cc: In member function ‘void S::f2() const’: x.cc:20:6: warning: Taking non-const reference to mutable field ‘S::x’ of type ‘int’ within thread-safe function ‘void S::f2() const’; may not be thread-safe.
 foo(x); // warning here
 ~~~^~~ 

(Warnings about mutable are not issued for mutex, atomic, or thread-local types.)

There are several ways in which these warnings may be suppressed:

* Add ATLAS_THREAD_SAFE to the left-hand-side of an assignment that discards const:

 const int* y; int* yy ATLAS_THREAD_SAFE = const_cast (y); 

* Add ATLAS_NOT_THREAD_SAFE to the function:

int* f ATLAS_NOT_THREAD_SAFE (const int* y) { return const_cast(y); } 

* Add ATLAS_NOT_CONST_THREAD_SAFE to the function when it discards const from something that is not an argument. Such a function should not access any static const data (unless also marked ATLAS_NOT_THREAD_SAFE or ATLAS_NOT_REENTRANT). A const member function calling an ATLAS_NOT_CONST_THREAD_SAFE member function on the same object must also be marked ATLAS_NOT_CONST_THREAD_SAFE.

 const int* xx(); int* f ATLAS_NOT_CONST_THREAD_SAFE () { const int* y = xx(); return const_cast(y); } 

* Add ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE to the function when it discards const from something that is an argument. A const static object should not be passed to such a function. A function passing one of its arguments or a const member function passing member data to an ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE function must also be marked ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE.

 int* f ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE (const int* y) { return const_cast(y); } 

* For a warning about access to a mutable, the function may be made const, or the data member may be declared with ATLAS_THREAD_SAFE:

 mutable int m ATLAS_THREAD_SAFE;

Checks related to relations between functions

This checks are performed on relations between functions. (Some of these constraints were already mentioned in previous sections.)

* A function calling another function marked as ATLAS_NOT_REENTRANT must also be marked as ATLAS_NOT_REENTRANT.

 void f2 ATLAS_NOT_REENTRANT ();
 void f3() { f2(); }
 void f4 ATLAS_NOT_REENTRANT () { f2(); // this is ok }
 
x.cc: In function ‘void f3()’: x.cc:8:5: warning: Function ‘void f3()’ calling not_reentrant function ‘void f2()’ must also be not_reentrant. 
f2();
~~^~ 

* A const member function calling an ATLAS_NOT_CONST_THREAD_SAFE member function on the same object must also be marked as ATLAS_NOT_CONST_THREAD_SAFE.

 struct S { void f1 [[gnu::not_const_thread_safe]] () const; void f2() const; }; void S::f2() const { f1(); // warning here } 
x.cc: In member function ‘void S::f2() const’: x.cc:13:5: warning: Const member function ‘void S::f2() const’ calling not_const_thread_safe member function ‘void S::f1() const’ with same object must also be not_const_thread_safe.
 f1(); // warning here
 ~~^~ 
* A function passing one of its arguments to a function marked as ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE must also be marked as ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE.
void f1 ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE (int*); void f10 (int* x) { f1(x); // warning here } 
x.cc: In function ‘void f10(int*)’: x.cc:8:5: warning: Function ‘void f10(int*)’ passing argument ‘x’ of type ‘int*’ to argument_not_const_thread_safe function ‘void f1(int*)’ must also be argument_not_const_thread_safe.
 f1(x); // warning here
 ~~^~~ 

* A const member function passing member data to a function marked as ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE must also be marked as ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE. void f1 ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE (const int*);

struct S { void m1() const; int* x; }; void S::m1() const { f1(x); // warning here } 
 x.cc: In member function ‘void S::m1() const’: x.cc:14:5: warning: Const member function ‘void S::m1() const’ passing member data ‘S::x’ of type ‘int*’ to argument_not_const_thread_safe function ‘void f1(const int*)’ must also be argument_not_const_thread_safe.
 f1(x); // warning here
 ~~^~~ 

* A function passing a const static object to a function marked as ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE must be marked as ATLAS_NOT_REENTRANT.

const static int s = 0; int f1 ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE (const int* x); void f2() { f1 (&s); // warning here } 
 x.cc: In function ‘void f2()’: x.cc:9:6: warning: Function ‘void f2()’ passing const static argument ‘& s’ of type ‘const int*’ to argument_not_const_thread_safe function ‘int f1(const int*)’ must be not_reentrant.
 f1 (&s); // warning here 
~~~^~~~ 

* If a virtual function is overridden, the overriding function must have the same attributes.

 struct B { virtual void f1 ATLAS_NOT_REENTRANT (); }; struct D : public B { virtual void f1() override; // warning here }; 
x.cc:12:16: warning: Virtual function ‘virtual void D::f1()’ within class ‘struct D’ does not have attribute ‘not_reentrant’, but overrides ‘virtual void B::f1()’ which does.
 virtual void f1() override; // warning here
                 ^~ x.cc:7:16: 
note: Overridden function declared here: 
virtual void f1 ATLAS_NOT_REENTRANT ();
 ^~ 

* If a threading attribute is present on a function's definition, it must also be present on the declaration.

void f1();
void f1 ATLAS_NOT_REENTRANT () {} // warning here
 x.cc: In function ‘void f1()’: x.cc:7:6: warning: Inconsistent attributes between declaration and definition of function ‘void f1()’.
void f1 ATLAS_NOT_REENTRANT () {} // warning here 
^~ x.cc:6:6: 
note: Declaration is here: void f1(); ^~ x.cc:7:6: 
note: Definition has ‘not_reentrant’ but declaration does not. 
void f1 ATLAS_NOT_REENTRANT () {} // warning here 
^~ 
* A function checked for thread-safety may not call a function marked as ATLAS_NOT_THREAD_SAFE, or any standard library functions known to be thread-unsafe.
 void f2 ATLAS_NOT_THREAD_SAFE ();
 void f4 () { f2(); // warning here } 
x.cc: In function ‘void f4()’: x.cc:8:5: warning: Non-thread-safe function ‘void f2()’ called from thread-safe function ‘void f4()’; may not be thread-safe.
 f2();
 ~~^~ 
* In the future, there should also be a warning if a function checked got thread-safety calls an unchecked function. This is presently disabled until most of the core and data model code passes thread checking.

Thread checker attribute reference

This is a summary of the macros defined in CxxUtils/checker_macros.h that influence the thread checker. For functions and variables, the proper placement of the macros is just after the name, for example:
void f1 ATLAS_NOT_REENTRANT () { ... } 
static int x ATLAS_THREAD_SAFE; 
Multiple attributes may be given:
void f1 ATLAS_CHECK_THREAD_SAFETY ATLAS_NOT_REENTRANT () { ... } 
For functions, the declaration and definition should have the same set of attributes.

For classes, the macros should be placed before the class name:

class ATLAS_CHECK_THREAD_SAFETY C { ... } 
Please use the macros defined in checker_macros.h rather than writing the attributes directly, for purposes of portability and to be compatible with future evolution of the checker.

ATLAS_CHECK_FILE_THREAD_SAFETY, ATLAS_NO_CHECK_FILE_THREAD_SAFETY

These are not actually attributes, but rather pragmas. Include these at the start of a source file to override the package-wide default for whether or not functions should be checked for thread-safety. Enable thread checking by default with: ATLAS_CHECK_FILE_THREAD_SAFETY; and disable it with: ATLAS_NO_CHECK_FILE_THREAD_SAFETY; These directives affect only the specific file containing the directive, and not including or included by it. So if one has for example:
 // a2.h void a2() {...} 
// a1.h #include "CxxUtils/checker_macros.h"
 #include "a2.h"
 ATLAS_CHECK_FILE_THREAD_SAFETY; 
void a1() { ... } // a.cxx #include "a1.h" void a() { ... } 

Then a1 will be checked, but a and a2 will not be.

ATLAS_THREAD_SAFE

Marks that a static or mutable variable is actually ok, or that discarding const on assignment is ok. To suppress warnings about the use of a const or mutable member:
static int x ATLAS_THREAD_SAFE; 
mutable int x ATLAS_THREAD_SAFE; 
To suppress warnings about discarding const in assignment:
 const int * y; int* yy ATLAS_THREAD_SAFE = const_cast (y);

ATLAS_NOT_THREAD_SAFE

Add to a function to suppress warnings about uses of static variables, mutable variables, or discarding const. A function calling an ATLAS_NOT_THREAD_SAFE function must also be marked ATLAS_NOT_THREAD_SAFE. Example:
int* f ATLAS_NOT_THREAD_SAFE (const* x) { 
// Warning suppressed by ATLAS_NOT_THREAD_SAFE. 
return const_cast (x); }

ATLAS_NOT_REENTRANT

Mark that a function uses static data in a non-thread-safe manner. Add to a function to suppress warnings about uses of static variables. A function calling an ATLAS_NOT_REENTRANT function must also be marked ATLAS_NOT_REENTRANT. A function passing a const static object to a function declared ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE should be declared ATLAS_NOT_REENTRANT. Example:
int f6 ATLAS_NOT_REENTRANT (int xx) { static int x; 
// Warning suppressed by ATLAS_NOT_REENTRANT.
 return xx + x; } 

ATLAS_NOT_CONST_THREAD_SAFE

Mark that a function discards const (from something other than an argument). Add to a function to suppress warnings about discarding const from something that is not an argument. An ATLAS_NOT_CONST_THREAD_SAFE function should not access a const static object (unless also marked ATLAS_NOT_THREAD_SAFE or ATLAS_NOT_REENTRANT). A const member function calling an ATLAS_NOT_CONST_THREAD_SAFE member function on the same object must also be marked ATLAS_NOT_CONST_THREAD_SAFE. Example:
const int* xx(); 
int* f ATLAS_NOT_CONST_THREAD_SAFE () { const int* y = xx(); 
// Warning suppressed by ATLAS_NOT_CONST_THREAD_SAFE. 
return const_cast(y); 

ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE

Mark that a function discards const from an argument. Add to a function to suppress warnings about discarding const from an argument. A const static object should not be passed to be function marked as ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE. A function passing one of its arguments to an ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE function must also be marked ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE. A const member function function passing member data to an ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE function must also be marked ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE. Example:
void f1(int* y); 
void f2 ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE (const int* y) {
 // Warning suppressed by ATLAS_ARGUMENT_NOT_CONST_THREAD_SAFE. 
f1(const_cast(y)); } 

thread_safe_debug_plugin (Disabled by default.) This is used only for unit tests.


-- PatrickGartung - 2018-07-16 -- PatrickGartung - 2021-02-26

Edit | Attach | Watch | Print version | History: r1 | Backlinks | Raw View | WYSIWYG | More topic actions
Topic revision: r1 - 2021-02-26 - PatrickGartung
 
    • Cern Search Icon Cern Search
    • TWiki Search Icon TWiki Search
    • Google Search Icon Google Search

    CMSPublic All webs login

This site is powered by the TWiki collaboration platform Powered by PerlCopyright &© 2008-2021 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
or Ideas, requests, problems regarding TWiki? use Discourse or Send feedback