Rules - overview and description

Check strict IEC rules

The checks under the node "Check strict IEC rules" determine functionalities and data types that are allowed in TwinCAT, in extension of IEC61131-3.

Checking concurrent/competing access

The following rules exist on this topic:

SA0006: Write access from several tasks
Determines variables with write access from more than one task.

SA0103: Concurrent access on not atomic data
Determines non-atomic variables (for example with data types STRING, WSTRING, ARRAY, STRUCT, FB instances, 64-bit data types) that are used in more than one task.

 

Please note that only direct access can be recognized. Indirect access operations, for example via pointer/reference, are not listed.

Please also refer to the documentation on the subject "Multi-task data access synchronization in the PLC", which contains several notes on the necessity and options for data access synchronization.

 

Overview

- SA0001: Unreachable code

- SA0002: Empty objects

- SA0003: Empty statements

- SA0004: Multiple writes access on output

- SA0006: Write access from several tasks

- SA0007: Address operators on constants

- SA0008: Check subrange types

- SA0009: Unused return values

- SA0010: Arrays with only one component

- SA0011: Useless declarations

- SA0012: Variables which could be declared as constants

- SA0013: Declarations with the same variable name

- SA0014: Assignments of instances

- SA0015: Access to global data via FB_init

- SA0016: Gaps in structures

- SA0017: Non-regular assignments

- SA0018: Unusual bit access

- SA0020: Possibly assignment of truncated value to REAL variable

- SA0021: Transporting the address of a temporary variable

- SA0022: (Possibly) non-rejected return values

- SA0023: Complex return values

- SA0024: Untyped literals/constants

- SA0025: Unqualified enumeration constants

- SA0026: Possible truncated strings

- SA0027: Multiple usage of name

- SA0028: Overlapping memory areas

- SA0029: Notation in implementation different to declaration

- List unused objects

          - SA0031: Unused signatures

          - SA0032: Unused enumeration constants

          - SA0033: Unused variables

          - SA0035: Unused input variables

          - SA0036: Unused output variables

- SA0034: Enumeration variables with incorrect assignment

- SA0037: Write access to input variable

- SA0038: Read access to output variable

- SA0040: Possible division by zero

- SA0041: Possibly loop-invariant code

- SA0042: Usage of different access paths

- SA0043: Use of a global variable in only one POU

- SA0044: Declarations with reference to interface

- Conversions

          - SA0019: Implicit pointer conversions

          - SA0130: Implicit expanding conversions

          - SA0131: Implicit narrowing conversions

          - SA0132: Implicit signed/unsigned conversions

          - SA0133: Explicit narrowing conversions

          - SA0134: Explicit signed/unsigned conversions

- Usage of direct addresses

          - SA0005: Invalid addresses and data types

          - SA0047: Access to direct addresses

          - SA0048: AT declarations on direct addresses

- Rules for operators

          - SA0051: Comparison operators on BOOL variables

          - SA0052: Unusual shift operation

          - SA0053: Too big bitwise shift

          - SA0054: Comparisons of REAL/LREAL for equality/inequality

          - SA0055: Unnecessary comparison operations of unsigned operands

          - SA0056: Constant out of valid range

          - SA0057: Possible loss of decimal points

          - SA0058: Operations of enumeration variables

          - SA0059: Comparison operations always returning TRUE or FALSE

          - SA0060: Zero used as invalid operand

          - SA0061: Unusual operation on pointer

          - SA0062: Using TRUE and FALSE in expressions

          - SA0063: Possibly not 16-bit-compatible operations

          - SA0064: Addition of pointer

          - SA0065: Incorrect pointer addition to base size

          - SA0066: Use of temporary results

- Rules for statements

          - FOR statements

                    - SA0072: Invalid uses of counter variable

                    - SA0073: Use of non-temporary counter variable

                    - SA0080: Loop index variable for array index exceeds array range

                    - SA0081: Upper border is not a constant

          - CASE statements

                    - SA0075: Missing ELSE

                    - SA0076: Missing enumeration constant

                    - SA0077: Type mismatches with CASE expression

                    - SA0078: Missing CASE branches

          - SA0090: Return statement before end of function

- SA0095: Assignments in conditions

- SA0100: Variables greater than <n> bytes

- SA0101: Names with invalid length

- SA0102: Access to program/fb variables from the outside

- SA0103: Concurrent access on not atomic data

- SA0105: Multiple instance calls

- SA0106: Virtual method calls in FB_init

- SA0107: Missing formal parameters

- Check strict IEC rules

          - SA0111: Pointer variables

          - SA0112: Reference variables

          - SA0113: Variables with data type WSTRING

          - SA0114: Variables with data type LTIME

          - SA0115: Declarations with data type UNION

          - SA0117: Variables with data type BIT

          - SA0119: Object-oriented features

          - SA0120: Program calls

          - SA0121: Missing VAR_EXTERNAL declarations

          - SA0122: Array index defined as expression

          - SA0123: Usages of INI, ADR or BITADR

          - SA0147: Unusual shift operation - strict

          - SA0148: Unusual bit access - strict

- Rules for initializations

          - SA0118: Initializations not using constants

          - SA0124: Dereference access in initializations

          - SA0125: References in initializations

- SA0140: Statements commented out

- Possible use of uninitialized variables

          - SA0039: Possible null pointer dereferences

          - SA0046: Possible use of not initialized interface

          - SA0145: Possible use of not initialized reference

- SA0150: Violations of lower or upper limits of the metrics

- SA0160: Recursive calls

- SA0161: Unpacked structure in packed structure

- SA0162: Missing comments

- SA0163: Nested comments

- SA0164: Multi-line comments

- SA0166: Maximum number of input/output/VAR_IN_OUT variables

- SA0167: Report temporary FunctionBlock instances

 

Detailed description

SA0001: Unreachable code

Function

Determines code that is not executed, for example due to a RETURN or CONTINUE statement.

Reason

Unreachable code should be avoided in any case. The check often indicates the presence of test code, which should be removed.

Importance

high

PLCopen rule

CP2

 

Sample 1 – RETURN:

PROGRAM MAIN
VAR
    bReturnBeforeEnd : BOOL;
END_VAR
bReturnBeforeEnd := FALSE;
RETURN;
bReturnBeforeEnd := TRUE;        // => SA0001

Sample 2 – CONTINUE:

FUNCTION F_ContinueInLoop : BOOL
VAR
    nCounter  : INT;
END_VAR
F_ContinueInLoop := FALSE;
 
FOR nCounter := INT#0 TO INT#5 BY INT#1 DO
    CONTINUE;
    F_ContinueInLoop := FALSE;   // => SA0001
END_FOR

 

SA0002: Empty objects

Function

Determines POUs, GVLs or data type declarations that do not contain code.

Reason

Empty objects should be avoided. They are often a sign that an object is not fully implemented.

Exception: In some cases, the body of a function block will not assigned code if it is only to be used via interfaces. In other cases, a method is only created because it is required by an interface, without scope for meaningful implementation of the method. In any case, a comment should be included in such a situation.

Importance

medium

 

SA0003: Empty statements

Function

Determines lines of code containing a semicolon (;) but no statement.

Reason

An empty statement can be an indication of missing code.

Exception

Although there are meaningful uses for empty statements. For example, it may be useful to explicitly program all cases in a CASE statement, including cases in which no action is required. If such an empty CASE instruction is commented, the statistical code analysis does not generate an error message.

Importance

low

 

Samples:

;                                // => SA0003
(* comment *);                   // => SA0003
nVar;                            // => SA0003

The following sample generates the error "SA0003: Empty statement" for State 2.

CASE nVar OF
    1: DoSomething();
    2: ;
    3: DoSomethingElse();
END_CASE

The following sample does not generate an SA0003 error.

CASE nVar OF
    1: DoSomething();
    2: ;                         // nothing to do
    3: DoSomethingElse();
END_CASE

SA0004: Multiple write access on output

Function

Determines outputs that are written at more than one position.

Reason

The maintainability suffers if an output is written in various places in the code. It is then unclear which write access is actually affecting the process. It is good practice to perform the calculation of the output variables in auxiliary variables and to assign the calculated value to a point at the end of the cycle.

Exception

No error is issued if an output variable is written in different branches of IF or CASE statements.

Importance

high

PLCopen rule

CP12

 

This rule cannot be deactivated by a pragma or an attribute!
Further information on attributes can be found under Pragmas and attributes.

 

Sample:

Global variable list:

VAR_GLOBAL
    bVar     AT%QX0.0 : BOOL;
    nSample  AT%QW5   : INT;
END_VAR

Program MAIN:

PROGRAM MAIN
VAR
    nCondition        : INT;
END_VAR
IF nCondition < INT#0 THEN
    bVar    := TRUE;             // => SA0004
    nSample := INT#12;           // => SA0004
END_IF
 
CASE nCondition OF
    INT#1:
        bVar := FALSE;           // => SA0004
 
    INT#2:
        nSample := INT#11;       // => SA0004
 
ELSE
     bVar    := TRUE;            // => SA0004
     nSample := INT#9;           // => SA0004
END_CASE

 

SA0006: Write access from several tasks

Function

Determines variables with write access from more than one task.

Reason

A variable that is written in several tasks may change its value unexpectedly under certain circumstances. This can lead to confusing situations. String variables and, on some 32-bit systems, 64-bit integer variables also may even assume an inconsistent state if the variable is written in two tasks at the same time.

Exception

In certain cases it may be necessary for several tasks to write a variable. Make sure, for example through the use of semaphores, that the access does not lead to an inconsistent state.

Importance

high

PLCopen rule

CP10

 

See also rule SA0103.

Call corresponds to write access

Please note that calls are interpreted as write access. For example, calling a method for a function block instance is regarded as a write access to the function block instance. A more detailed analysis of accesses and calls is not possible, e.g. due to virtual calls (pointers, interface).

To deactivate rule SA0006 for a variable (e.g. for a function block instance), the following attribute can be inserted above the variable declaration: {attribute 'analysis' := '-6'}

 

Sample:

The two global variables nVar and bVar are written by two tasks.

Global variable list:

VAR_GLOBAL
    nVar  : INT;
    bVar  : BOOL;
END_VAR

Program MAIN_Fast, called from the task PlcTaskFast:

nVar := nVar + 1;                // => SA0006 
bVar := (nVar > 10);             // => SA0006

Program MAIN_Slow, called from the task PlcTaskSlow:

nVar := nVar + 2;                // => SA0006 
bVar := (nVar < -50);            // => SA0006

 

SA0007: Address operators on constants

Function

Determines locations at which the ADR operator is used for a constant.

Reason

A pointer to a constant variable cancels the CONSTANT property of the variable. The variable can be changed via the pointer without the compiler reporting this.

Exception

In rare cases, it may make sense for pointer to a constant to be passed to a function. If this option is used, measures must be implemented to ensure that the function does not change the value that was passed to it. In this case, use VAR_IN_OUT CONSTANT if possible.

Importance

high

 

If the option Replace constants is enabled in the compiler options of the PLC project properties, the address operator for scalar constants (Integer, BOOL, REAL) is not allowed and a compilation error is issued. (Constant strings, structures and arrays always have an address.)

 

Sample:

PROGRAM MAIN
VAR CONSTANT
    cValue  : INT := INT#15;
END_VAR
VAR
    pValue  : POINTER TO INT;
END_VAR
pValue := ADR(cValue);           // => SA0007

 

SA0008: Check subrange types

Function

Determines range exceedances of subrange types. Assigned literals are checked at an early stage by the compiler. If constants are assigned, the values must be within the defined range. If variables are assigned, the data types must be identical.

Reason

If subrange types are used, make sure that the function remains within the respective subrange. The compiler checks such subrange violations only for assignments of constants.

Importance

low

 

The check is not performed for CFC objects, because the code structure does not allow this.

 

Sample:

PROGRAM MAIN
VAR
    nSub1  : INT (INT#1..INT#10);
    nSub2  : INT (INT#1..INT#1000);
    nVar   : INT;
END_VAR
nSub1 := nSub2;                  // => SA0008 
nSub1 := nVar;                   // => SA0008

 

SA0009: Unused return values

Function

Determines function, method and property calls for which the return value is not used.

Reason

If a function or method returns a return value, the value should be evaluated. In many cases the return value contains information to indicate whether the function was executed successfully. If no evaluation is performed, it is subsequently not possible to determine whether the return value was overlooked or whether it is in fact not required.

Exception

If a return value is of no interest during a call, this can be documented and the assignment can be omitted. Error returns should never be ignored!

Importance

medium

PLCopen rule

CP7/CP17

 

Sample:

Function F_ReturnBOOL:

FUNCTION F_ReturnBOOL : BOOL
F_ReturnBOOL := TRUE;

Program MAIN:

PROGRAM MAIN
VAR
    bVar  : BOOL;
END_VAR
F_ReturnBOOL();                  // => SA0009 
bVar := F_ReturnBOOL();

 

SA0010: Arrays with only one component

Function

Determines arrays containing only a single component.

Reason

An array with a component can be replaced by a Base Type variable. Access to such a variable is much faster than access to a variable via an index.

Exception

The length of an array is often determined by a constant and used as a parameter for a program. The program can then work with arrays of different lengths and does not have to be changed if the length is only 1. Such a situation should be documented accordingly.

Importance

low

 

Samples:

PROGRAM MAIN 
VAR
    aEmpty1  : ARRAY [0..0] OF INT;                 // => SA0010
    aEmpty2  : ARRAY [15..15] OF REAL;              // => SA0010
END_VAR

 

SA0011: Useless declarations

Function

Determines structures, unions or enumerations with at most one component.

Reason

Such a declaration can be confusing for a reader. A structure with only one element can be replaced by an alias type. An enumeration with an element can be replaced by a constant.

Importance

low

PLCopen rule

CP22/CP24

 

Sample 1 – Structure:

TYPE ST_SingleStruct :           // => SA0011 
STRUCT
    nPart  : INT;
END_STRUCT
END_TYPE

Sample 2 – Union:

TYPE U_SingleUnion :             // => SA0011 
UNION
    fVar  : LREAL;
END_UNION
END_TYPE

Sample 3 – Enumeration:

TYPE E_SingleEnum :              // => SA0011 
(
    eOnlyOne := 1
);
END_TYPE

 

SA0012: Variables which could be declared as constants

Function

Determines variables that are not subject to write access and therefore could not be declared as constants.

Reason

If a variable is only written at the declaration point and is otherwise only used in read mode, the static analysis assumes that the variable is to remain unchanged. Declaration as a constant means that the variable is checked for changes in the event of program modifications. Plus, declaration as a constant may lead to faster code.

Importance

low

 

Sample:

PROGRAM MAIN 
VAR
    nSample  : INT := INT#17;
    nVar     : INT; 
END_VAR
nVar := nVar + nSample;          // => SA0012

 

SA0013: Declarations with the same variable name

Function

Determines variables with the same name as other variables (example: global and local variables with the same name), or the same name as functions, actions, methods or properties within the same access range.

Reason

Identical names can be confusing when the code is read and can lead to errors if the wrong object is accessed accidentally. We therefore recommend using naming conventions that avoid such situations.

Importance

medium

PLCopen rule

N5/N9

 

Samples:

Global variable list GVL_App:

VAR_GLOBAL
    nVar  : INT;
END_VAR

MAIN program, containing a method with the name Sample:

PROGRAM MAIN
VAR
    bVar    : BOOL;
    nVar    : INT;               // => SA0013
    Sample  : DWORD;             // => SA0013
END_VAR
.nVar := 100;                    // Writing global variable "nVar"
nVar  := 500;                    // Writing local variable "nVar"
METHOD Sample
VAR_INPUT
… 

 

SA0014: Assignments of instances

Function

Determines assignments to function block instances. For instances with pointer or reference variables such assignments may be risky.

Reason

This is a performance warning. When an instance is assigned to another instance, all elements and subelements are copied from one instance to the other. Pointers to data are also copied, but not their referenced data, so that the target instance and the source instance contain the same data after the assignment. Depending on the size of the instances, such an assignment may take a long time. If, for example, an instance is to be passed to a function for processing, it is much better to pass a pointer to the instance.

A copy method can be useful for selectively copying values from one instance to another:

inst_First.Copy_From(inst_Second)

Importance

medium

 

Sample:

PROGRAM MAIN 
VAR
    fb1  : FB_Sample;
    fb2  : FB_Sample;
END_VAR
fb1();
fb2 := fb1;                      // => SA0014

 

SA0015: Access to global data via FB_init

Function

Determines access of a function block to global data via the FB_init method. The value of this variables depends on the order of the initializations!

Reason

Depending on the declaration location of the instance of a function block, a non-initialized variable may be accessed if the rule is violated.

Importance

high

 

Sample:

Global variable list GVL_App:

VAR_GLOBAL
    nVar      : INT;
END_VAR

Function block FB_Sample:

FUNCTION_BLOCK FB_Sample
VAR
    nLocal    : INT;
END_VAR

Method FB_Sample.FB_init:

METHOD FB_init : BOOL
VAR_INPUT
    bInitRetains  : BOOL;        // if TRUE, the retain variables are initialized (warm start / cold start)
    bInCopyCode   : BOOL;        // if TRUE, the instance afterwards gets moved into the copy code (online change)
END_VAR
nLocal := 2*nVar;                // => SA0015

Program MAIN:

PROGRAM MAIN
VAR
    fbSample  : FB_Sample;
END_VAR

 

SA0016: Gaps in structures

Function

Determines gaps in structures or function blocks, caused by the alignment requirements of the currently selected target system. If possible, you should remove gaps by rearranging the structure elements or by filling them with dummy elements. If this is not possible, you can disable the rule for the affected structures using the attribute {attribute 'analysis' := '...'}.

Reason

Due to different alignment requirements on different platforms, such structures may have a different layout in the memory. The code may behave differently, depending on the platform.

Importance

low

 

Samples:

TYPE ST_UnpaddedStructure1 :
STRUCT
    bBOOL  : BOOL;
    nINT   : INT;                // => SA0016
    nBYTE  : BYTE;
    nWORD  : WORD;
END_STRUCT
END_TYPE
TYPE ST_UnpaddedStructure2 :
STRUCT
    bBOOL  : WORD;
    nINT   : INT;
    nBYTE  : BYTE;
    nWORD  : WORD;               // => SA0016
END_STRUCT
END_TYPE

 

SA0017: Non-regular assignments

Function

Determines assignments to pointers, which are not an address (ADR operator, pointer variables) or constant 0.

Reason

If a pointer contains a value that is not a valid address, an access violation exception occurs when dereferencing the pointer.

Importance

high

 

Sample:

PROGRAM MAIN 
VAR
    nVar      : INT;
    pInt      : POINTER TO INT;
    nAddress  : XWORD;
END_VAR
nAddress := nAddress + 1;
 
pInt     := ADR(nVar);           // no error
pInt     := 0;                   // no error
pInt     := nAddress;            // => SA0017

 

SA0018: Unusual bit access

Function

Determines bit access to signed variables. However, the IEC 61131-3 standard only permits bit access to bit fields. See also strict rule SA0148.

Reason

Signed data types should not be used as bit fields and vice versa. The IEC 61131-3 standard does not provide for such access. This rule must be observed if the code is to be portable.

Exception

Exception for flag enumerations: If an enumeration is declared as flag via the pragma attribute {attribute 'flags'}, the error SA0018 is not issued for bit access with OR, AND or NOT operations.

Importance

medium

 

Samples:

PROGRAM MAIN 
VAR
    nINT    : INT;
    nDINT   : DINT;
    nULINT  : ULINT;
    nSINT   : SINT;
    nUSINT  : USINT;
    nBYTE   : BYTE;
END_VAR
nINT.3    := TRUE;               // => SA0018
nDINT.4   := TRUE;               // => SA0018
nULINT.18 := FALSE;              // no error because this is an unsigned data type
nSINT.2   := FALSE;              // => SA0018
nUSINT.3  := TRUE;               // no error because this is an unsigned data type
nBYTE.5   := FALSE;              // no error because BYTE is a bit field

 

SA0020: Possibly assignment of truncated value to REAL variable

Function

Determines operations on integer variables, during which a truncated value may be assigned to a variable of data type REAL.

Reason

The static code analysis returns an error when the result of an integer calculation is assigned to a REAL or LREAL variable. The programmer should be made aware of a possibly incorrect interpretation of such an assignment:

lrealvar := dintvar1 * dintvar2.

Since the value range of LREAL is greater than that of DINT, it could be assumed that the result of the calculation is always displayed in LREAL. But this is not the case. The processor calculates the result of the multiplication as an integer and then casts the result to LREAL. An overflow in the integer calculation would be lost. To avoid this problem, the calculation should be performed as a REAL operation:

lreal_var := TO_LREAL(dintvar1) * TO_LREAL(dintvar2)

Importance

high

 

Sample:

PROGRAM MAIN 
VAR
    nVar1  : DWORD;
    nVar2  : DWORD;
    fVar   : REAL;
END_VAR
nVar1 := nVar1 + DWORD#1;
nVar2 := nVar2 + DWORD#2;
fVar  := nVar1 * nVar2;          // => SA0020

 

SA0021: Transporting the address of a temporary variable

Function

Determines assignments of addresses of temporary variables (variables on the stack) to non-temporary variables.

Reason

Local variables of a function or method are created on the stack and exist only while the function or method is processed. If a pointer points to such a variable after processing the method or function, then this pointer can be used to access undefined memory or an incorrect variable in another function. This situation must be avoided in any case.

Importance

high

 

Sample:

Method FB_Sample.SampleMethod:

METHOD SampleMethod : XWORD
VAR
    fVar  : LREAL;
END_VAR
SampleMethod := ADR(fVar);

Program  MAIN:

PROGRAM MAIN
VAR
    nReturn   : XWORD;
    fbSample  : FB_Sample;
END_VAR
nReturn := fbSample.SampleMethod();                 // => SA0021

 

SA0022: (Possibly) unassigned return value

Function

Determines all functions and methods containing an execution thread without assignment to the return value.

Reason

An unassigned return value in a function or method indicates missing code. Even if the return value always has a default value, it is useful to explicitly assign it again in any case, in order to avoid ambiguities.

Importance

medium

 

Sample:

FUNCTION F_Sample : DWORD
VAR_INPUT
    nIn    : UINT;
END_VAR
VAR
    nTemp  : INT;
END_VAR
nIn := nIn + UINT#1;
 
IF (nIn > UINT#10) THEN
    nTemp    := 1;               // => SA0022
ELSE
    F_Sample := DWORD#100;
END_IF

 

SA0023: Complex return values

Function

Determines complex return values that cannot be returned with a simple register copy of the processor. These include structures and arrays as well as return values of the type STRING (irrespective of the size of memory space occupied).

Reason

This is a performance warning. If large values are returned as a result of a function, method, or property, the processor copies them repeatedly when the code is executed. This can lead to runtime problems and should be avoided if possible. Better performance is achieved if a structured value is passed to a function or method as VAR_IN_OUT and filled in the function or method.

Importance

medium

 

Sample:

Structure ST_sample:

TYPE ST_Sample :
STRUCT
    n1  : INT;
    n2  : BYTE;
END_STRUCT
END_TYPE

Example of functions with return value:

FUNCTION F_MyFunction1 : I_MyInterface              // no error
FUNCTION F_MyFunction2 : ST_Sample                  // => SA0023
FUNCTION F_MyFunction3 : ARRAY[0..1] OF BOOL        // => SA0023

 

SA0024: Untyped literals/constants

Function

Determines untyped literals/constants (e.g. nCount : INT := 10;).

Reason

TwinCAT assigns the types for literals according to their use. In some cases, this can lead to unexpected situations that are better clarified by a typed literal. Sample:

dw := ROL(DWORD#1, i)

Importance

low

 

Sample:

PROGRAM MAIN
VAR
    nVar  : INT;
    fVar  : LREAL;
END_VAR
nVar := 100;                     // => SA0024
nVar := INT#100;                 // no error
 
fVar := 12.5;                    // => SA0024
fVar := LREAL#12.5;              // no error

 

SA0025: Unqualified enumeration constants

Function

Determines enumeration constants that are not used with a qualified name, i.e. without preceding enumeration name.

Reason

Qualified access makes the code more readable and easier to maintain. Without forcing qualified variable names, an additional enumeration could be inserted when the program is extended. This enumeration contains a constant with the same name as an existing enumeration (see the sample below: "eRed"). In this case there would be an ambiguous access in this piece of code. We recommend using only enumerations that have the {attribute 'qualified-only’}.

Importance

medium

 

Sample:

Enumeration E_Color:

TYPE E_Color :
(
    eRed,
    eGreen,
    eBlue
);
END_TYPE

Program MAIN:

PROGRAM MAIN
VAR
    eColor  : E_Color;
END_VAR
eColor := E_Color.eGreen;        // no error
eColor := eGreen;                // => SA0025

 

SA0026: Possible truncated strings

Function

Determines string assignments and initializations that do not use an adequate string length.

Reason

If strings of different lengths are assigned, a string may be truncated. The result is then not the expected one.

Importance

medium

 

Samples:

PROGRAM MAIN
VAR
    sVar1  : STRING[10];
    sVar2  : STRING[6];
    sVar3  : STRING[6] := 'abcdefghi';              // => SA0026
END_VAR
sVar2 := sVar1;                                     // => SA0026

 

SA0027: Multiple usage of name

Function

Determines multiple use of a variable name/identifier or object name (POU) within the scope of a project. The following cases are covered:

  • The name of an enumeration constant is the same as the name in another enumeration within the application or a referenced library.
  • The name of a variable that is the same as the name of an object within the application or a referenced library.
  • The name of a variable is the same as the name of an enumeration constant within the application or a referenced library.
  • The name of an object is the same as the name of another object within the application or a referenced library.

Reason

Identical names can be confusing when reading the code. They can lead to errors if the wrong object is accessed inadvertently. Therefore, define and follow naming conventions in order to avoid such situations.

Importance

medium

 

Sample:

The following example generates error/warning SA0027, since the library Tc2_Standard is referenced in the project, which provides the function block TON.

PROGRAM MAIN
VAR
    ton  : INT;                  // => SA0027
END_VAR

 

SA0028: Overlapping memory areas

Function

Determines the points due to which two or more variables occupy the same memory space.

Reason

If two variables occupy the same memory space, the code may behave very unexpectedly. This must be avoided in all cases. If the use of a value in different interpretations is unavoidable, for example once as DINT and once as REAL, you should define a UNION. Also, via a pointer you can access a value typed otherwise without converting the value.

Importance

high

 

Sample:

In the following sample both variables use byte 21, i.e. the memory areas of the variables overlap.

PROGRAM MAIN
VAR
    nVar1 AT%QB21  : INT;        // => SA0028
    nVar2 AT%QD5   : DWORD;      // => SA0028
END_VAR

 

SA0029: Notation in implementation different to declaration

Function

Determines the code positions (in the implementation) at which the notation of an identifier differs from the notation in its declaration.

Reason

The IEC 61131-3 standard defines identifiers as not case-sensitive. This means that a variable declared as "varx" can also be used as "VaRx" in the code. However, this can be confusing and misleading and should therefore be avoided.

Importance

medium

 

Samples:

Function F_TEST:

FUNCTION F_TEST : BOOL

Program MAIN:

PROGRAM MAIN
VAR
    nVar     : INT;
    bReturn  : BOOL;
END_VAR
nvar    := nVar + 1;             // => SA0029
bReturn := F_Test();             // => SA0029

 

SA0031: Unused signatures

Function

Determines programs, function blocks, functions, data types, interfaces, methods, properties, actions etc., which are not called within the compiled program code.

Reason

Unused objects result in unnecessary project bloat and confusion when the code is read.

Importance

low

PLCopen rule

CP2

 

SA0032: Unused enumeration constants

Function

Determines enumeration constants that are not used in the compiled program code.

Reason

Unused enumeration constants result in unnecessary enumeration definition bloat and confusion when the program is read.

Importance

low

PLCopen rule

CP24

 

Sample:

Enumeration E_Sample:

TYPE E_Sample :
(
    eNull,
    eOne,                        // => SA0032
    eTwo
);
END_TYPE

Program MAIN:

PROGRAM MAIN
VAR
    eSample  : E_Sample;
END_VAR
eSample := E_Sample.eNull;
eSample := E_Sample.eTwo;

 

SA0033: Unused variables

Function

Determines variables that are declared but not used within the compiled program code.

Reason

Unused variables make a program less easy to read and maintain. Unused variables occupy unnecessary memory space and take up unnecessary runtime during the initialization.

Importance

medium

PLCopen rule

CP22/CP24

 

SA0035: Unused input variables

Function

Determines input variables that are not used externally by any function block instance.

Reason

Unused variables make a program less easy to read and maintain. Unused variables occupy unnecessary memory space and take up unnecessary runtime during the initialization.

Importance

medium

PLCopen rule

CP24

 

Sample:

Function block FB_Sample:

FUNCTION_BLOCK FB_Sample
VAR_INPUT
    nIn  : INT;
    bIn  : BOOL;                 // => SA0035
END_VAR
VAR_OUTPUT
    nOut : INT;                  // => SA0036
END_VAR

Program MAIN:

PROGRAM MAIN
VAR
    fbSample : FB_Sample;
END_VAR
fbSample(nIn := 99);

 

SA0036: Unused output variables

Function

Determines output variables that are not used externally by any function block instance.

Reason

Unused variables make a program less easy to read and maintain. Unused variables occupy unnecessary memory space and take up unnecessary runtime during the initialization.

Importance

medium

PLCopen rule

CP24

 

Sample:

Function block FB_Sample:

FUNCTION_BLOCK FB_Sample
VAR_INPUT
    nIn  : INT;
    bIn  : BOOL;                 // => SA0035
END_VAR
VAR_OUTPUT
    nOut : INT;                  // => SA0036
END_VAR

Program MAIN:

PROGRAM MAIN
VAR
    fbSample : FB_Sample;
END_VAR
fbSample(nIn := 99);

 

SA0034: Enumeration variables with incorrect assignment

Function

Determines values that are assigned to an enumeration variable. Only defined enumeration constants may be assigned to an enumeration variable.

Reason

An enumeration type variable should only have the intended values, otherwise code that uses that variable may not work correctly. We recommend using only enumerations that have the {attribute 'strict'}. In this case the compiler checks the correct use of the enumeration components.

Importance

high

 

Sample:

Enumeration E_Color:

TYPE E_Color :
(
    eRed   := 1,
    eBlue  := 2,
    eGreen := 3
);
END_TYPE

Program MAIN:

PROGRAM MAIN
VAR
    eColor : E_Color;
END_VAR
eColor := E_Color.eRed;
eColor := eBlue;
eColor := 1;                     // => SA0034

 

SA0037: Write access to input variable

Function

Determines input variables (VAR_INPUT) that are subject to write access within the POU.

Reason

According to the IEC 61131-3 standard, an input variable may not be changed within a function block. Such access is also an error source and makes the code more difficult to maintain. It indicates that a variable is used as an input and simultaneously as an auxiliary variable. Such dual use should be avoided.

Importance

medium

 

Sample:

Function block FB_Sample:

FUNCTION_BLOCK FB_Sample
VAR_INPUT
    bIn   : BOOL := TRUE;
    nIn   : INT := 100;
END_VAR
VAR_OUTPUT
    bOut  : BOOL;
END_VAR

Method FB_Sample.SampleMethod:

IF bIn THEN
    nIn  := 500;                 // => SA0037
    bOut := TRUE;
END_IF

 

SA0038: Read access to output variable

Function

Determines output variables (VAR_OUTPUT) that are subject to read access within the POU.

Reason

The IEC-61131-3 standard prohibits reading an output within a function block. It indicates that the output is not only used as an output but also as a temporary variable for intermediate results. Such dual use should be avoided.

Importance

low

 

Sample:

Function block FB_Sample:

FUNCTION_BLOCK FB_Sample
VAR_OUTPUT
    bOut    : BOOL;
    nOut    : INT;
END_VAR
VAR
    bLocal  : BOOL;
    nLocal  : INT;
END_VAR

Method FB_Sample.SampleMethod:

IF bOut THEN                     // => SA0038
    bLocal := (nOut > 100);      // => SA0038
    nLocal := nOut;              // => SA0038
    nLocal := 2*nOut;            // => SA0038
END_IF

 

SA0040: Possible division by zero

Function

Determines code positions at which division by zero may occur.

Reason

Division by 0 is not allowed. A variable that is used as a divisor should always be checked for 0 first. Otherwise, a "Divide by Zero" exception may occur at runtime.

Importance

high

 

Sample:

PROGRAM MAIN
VAR CONSTANT
    cSample     : INT := 100;
END_VAR
VAR
    nQuotient1  : INT;
    nDividend1  : INT;
    nDivisor1   : INT;
 
    nQuotient2  : INT;
    nDividend2  : INT;
    nDivisor2   : INT;
END_VAR
nDivisor1  := cSample;
nQuotient1 := nDividend1/nDivisor1;                 // no error
 
nQuotient2 := nDividend2/nDivisor2;                 // => SA0040

 

SA0041: Possibly loop-invariant code

Function

Determines code that may be loop-invariant, i.e. code within a FOR, WHILE or REPEAT loop that returns the same result in each loop, in which case repeated execution would be unnecessary. Only calculations are taken into account, no simple assignments.

Reason

This is a performance warning. Code that is executed in a loop but performs a repetitive action in each loop can be executed outside the loop.

Importance

medium

 

Sample:

In the following sample SA0041 is output as error/warning, since the variables nTest1 and nTest2 are not used in the loop.

PROGRAM MAIN
VAR
    nTest1    : INT := 5;
    nTest2    : INT := nTest1;
    nTest3    : INT;
    nTest4    : INT;
    nTest5    : INT;
    nTest6    : INT;
    nCounter  : INT;
END_VAR
FOR nCounter := 1 TO 100 BY 1 DO
    nTest3 := nTest1 + nTest2;   // => SA0041
    nTest4 := nTest3 + nCounter; // no loop-invariant code, because nTest3 and nCounter are used within loop
    nTest6 := nTest5;            // simple assignments are not regarded
END_FOR

 

SA0042: Usage of different access paths

Function

Determines the usage of different access paths for the same variable.

Reason

Different access to the same element reduces the readability and maintainability of a program. We recommend consistent use of {attribute 'qualified-only'} for libraries, global variable lists and enumerations. This forces fully qualified access.

Importance

low

 

Samples:

In the following sample SA0042 is output as error/warning, because the global variable nGlobal is accessed directly and via the GVL namespace, and because the function CONCAT is accessed directly and via the library namespace.

Global variables:

VAR_GLOBAL
    nGlobal   : INT;
END_VAR

Program MAIN:

PROGRAM MAIN
VAR
    sVar      : STRING;
END_VAR
nGlobal      := INT#2;                              // => SA0042
GVL.nGlobal  := INT#3;                              // => SA0042
 
sVar := CONCAT('ab', 'cd');                         // => SA0042
sVar := Tc2_Standard.CONCAT('ab', 'cd');            // => SA0042

 

SA0043: Use of a global variable in only one POU

Function

Determines global variables that are only used in a single POU.

Reason

A global variable that is only used at one point should also be declared at this point.

Importance

medium

PLCopen rule

CP26

 

Sample:

The global variable nGlobal1 is only used in the MAIN program.

Global variables:

VAR_GLOBAL
    nGlobal1  : INT;             // => SA0043
    nGlobal2  : INT;
END_VAR

Program SubProgram:

nGlobal2 := 123;

Program MAIN:

SubProgram();
 
nGlobal1 := nGlobal2; 

 

SA0044: Declarations with reference to interface

Function

Determines declarations with REFERENCE TO <interface> and declarations of VAR_IN_OUT variables with the type of an interface (realized implicitly via REFERENCE TO).

Reason

An interface type is always implicitly a reference to an instance of a function block that implements this interface. A reference to an interface is therefore a reference to a reference and can lead to unwanted behavior.

Importance

high

 

Samples:

I_Sample is an interface defined in the project.

Function block FB_Sample:

FUNCTION_BLOCK FB_Sample
VAR_INPUT
    iInput      : I_Sample;
END_VAR
VAR_OUTPUT
    iOutput     : I_Sample;
END_VAR
VAR_IN_OUT
    iInOut1     : I_Sample;              // => SA0044

    {attribute 'analysis' := '-44'}
    iInOut2     : I_Sample;              // no error SA0044 because rule is deactivated via attribute
END_VAR

Program MAIN:

PROGRAM MAIN 
VAR
    fbSample    : FB_Sample;
    iSample     : I_Sample;
    refItf      : REFERENCE TO I_Sample; // => SA0044
END_VAR

 

SA0019: Implicit pointer conversions

Function

Determines implicitly generated pointer data type conversions.

Reason

Pointers are not strictly typed in TwinCAT and can be assigned to each other as required. This is a commonly used option and therefore not reported by the compiler. However, it can also unintentionally lead to unexpected access. If a POINTER TO BYTE is assigned to a POINTER TO DWORD, it is possible that the last pointer will unintentionally overwrite memory. Therefore, always check this rule and suppress the message only in cases where you deliberately want to access a value with a different type.

Implicit data type conversions are reported with a different message.

Exception

BOOL ↔ BIT

Importance

high

PLCopen rule

CP25

 

Samples:

PROGRAM MAIN 
VAR
    nInt   : INT;
    nByte  : BYTE;
 
    pInt   : POINTER TO INT;
    pByte  : POINTER TO BYTE;
END_VAR
pInt  := ADR(nInt);
pByte := ADR(nByte);
 
pInt  := ADR(nByte);             // => SA0019
pByte := ADR(nInt);              // => SA0019
 
pInt  := pByte;                  // => SA0019
pByte := pInt;                   // => SA0019

 

SA0130: Implicit expanding conversions

Function

Determines implicitly performed conversions from smaller to larger data types.

Reason

The compiler allows any assignment of different types if the range of the source type is fully within the range of the target type. However, the compiler will build a conversion into the code as late as possible. For an assignment of the following type:

lint := dint * dint;

the compiler performs the implicit conversion only after the multiplication:

lint := TO_LINT(dint * dint);

An overflow is therefore truncated. If you want to prevent this, you can have the conversion performed earlier for the elements:

lint := TO_LINT(dint) * TO_LINT(dint);

Therefore, it may be useful to report points where the compiler implements implicit conversions in order to check whether these are exactly what is intended. In addition, explicit conversions can serve to improve portability to other systems if they have more restrictive type checks.

Exception

BOOL ↔ BIT

Importance

low

 

Samples:

PROGRAM MAIN 
VAR
    nBYTE    : BYTE;
    nUSINT   : USINT;
    nUINT    : UINT;
    nINT     : INT;
    nUDINT   : UDINT;
    nDINT    : DINT;
    nULINT   : ULINT;
    nLINT    : LINT;
    nLWORD   : LWORD;
    fLREAL   : LREAL;
END_VAR
nLINT   := nINT;                 // => SA0130
nULINT  := nBYTE;                // => SA0130
nLWORD  := nUDINT;               // => SA0130
fLREAL  := nBYTE;                // => SA0130
nDINT   := nUINT;                // => SA0130
 
nBYTE.5 := FALSE;                // no error (BIT-BOOL-conversion)

 

SA0131: Implicit narrowing conversions

Function

Determines implicitly performed conversions from larger to smaller data types.

Exception

BOOL ↔ BIT

Importance

low

 

This message is now obsolete because it is already reported as a warning by the compiler.

 

Sample:

PROGRAM MAIN
VAR
    fREAL    : REAL;
    fLREAL   : LREAL;
END_VAR
fREAL   := fLREAL;               // => SA0131
 
nBYTE.5 := FALSE;                // no error (BIT-BOOL-conversion)

 

SA0132: Implicit signed/unsigned conversions

Function

Determines implicitly performed conversions from signed to unsigned data types or vice versa.

Importance

low

 

This message is now obsolete because it is already reported as a warning by the compiler.

 

Samples:

PROGRAM MAIN
VAR
    nBYTE    : BYTE;
    nUDINT   : UDINT;
    nULINT   : ULINT;
    nWORD    : WORD;
    nLWORD   : LWORD;
    nSINT    : SINT;
    nINT     : INT;
    nDINT    : DINT;
    nLINT    : LINT;
END_VAR
nLINT   := nULINT;               // => SA0132
nUDINT  := nDINT;                // => SA0132
nSINT   := nBYTE;                // => SA0132
nWORD   := nINT;                 // => SA0132
nLWORD  := nSINT;                // => SA0132

 

SA0133: Explicit narrowing conversions

Function

Determines explicitly performed conversions from a larger to a smaller data type.

Reason

A large number of type conversions can mean that incorrect data types have been selected for variables. There are therefore programming guidelines that require an explicit justification for data type conversions.

Importance

low

 

Samples:

PROGRAM MAIN
VAR
    nSINT    : SINT;
    nDINT    : DINT;
    nLINT    : LINT;
    nBYTE    : BYTE;
    nUINT    : UINT;
    nDWORD   : DWORD;
    nLWORD   : LWORD;
    fREAL    : REAL;
    fLREAL   : LREAL;
END_VAR
nSINT := LINT_TO_SINT(nLINT);    // => SA0133
nBYTE := DINT_TO_BYTE(nDINT);    // => SA0133
nSINT := DWORD_TO_SINT(nDWORD);  // => SA0133
nUINT := LREAL_TO_UINT(fLREAL);  // => SA0133
fREAL := LWORD_TO_REAL(nLWORD);  // => SA0133

 

SA0134: Explicit signed/unsigned conversions

Function

Determines explicitly performed conversions from signed to unsigned data types or vice versa.

Reason

Excessive use of type conversions may mean that incorrect data types have been selected for variables. There are therefore programming guidelines that require an explicit justification for data type conversions.

Importance

low

 

Samples:

PROGRAM MAIN
VAR
    nBYTE    : BYTE;
    nUDINT   : UDINT;
    nULINT   : ULINT;
    nWORD    : WORD;
    nLWORD   : LWORD;
    nSINT    : SINT;
    nINT     : INT;
    nDINT    : DINT;
    nLINT    : LINT;
END_VAR
nLINT  := ULINT_TO_LINT(nULINT); // => SA0134
nUDINT := DINT_TO_UDINT(nDINT);  // => SA0134
nSINT  := BYTE_TO_SINT(nBYTE);   // => SA0134
nWORD  := INT_TO_WORD(nINT);     // => SA0134
nLWORD := SINT_TO_LWORD(nSINT);  // => SA0134

 

SA0005: Invalid addresses and data types

Function

Determines invalid address and data type specifications.

The following size prefixes are valid for addresses. Deviations from this lead to an invalid address specification.

  • X for BOOL
  • B for 1-byte data types
  • W for 2-byte data types
  • D for 4-byte data types

Reason

Variables that lie on direct addresses should, if possible, be associated with an address that corresponds to their data type width. It can be confusing for the reader of the code if, for example, a DWORD is placed on a BYTE address.

Importance

low

 

If the recommended placeholders %I* or %Q* are used, TwinCAT automatically performs flexible and optimized addressing.

 

Samples:

PROGRAM MAIN 
VAR
    nOK   AT%QW0   : INT;
    bOK   AT%QX5.0 : BOOL;
 
    nNOK  AT%QD10  : INT;        // => SA0005
    bNOK  AT%QB15  : BOOL;       // => SA0005
END_VAR

 

SA0047: Access to direct addresses

Function

Determines direct address access operations in the implementation code.

Reason

Symbolic programming is always preferred: A variable has a name that can also have a meaning. An address does not provide an indication of what it is used for.

Importance

high

PLCopen rule

N1/CP1

 

Samples:

PROGRAM MAIN
VAR
    bBOOL  : BOOL;
    nBYTE  : BYTE;
    nWORD  : WORD;
    nDWORD : DWORD;
END_VAR
bBOOL  := %IX0.0;                // => SA0047
%QX0.0 := bBOOL;                 // => SA0047
%QW2   := nWORD;                 // => SA0047
%QD4   := nDWORD;                // => SA0047
%MX0.1 := bBOOL;                 // => SA0047
%MB1   := nBYTE;                 // => SA0047
%MD4   := nDWORD;                // => SA0047

 

SA0048: AT declarations on direct addresses

Function

Determines AT declarations on direct addresses.

Reason

The use of direct addresses in the code is an error source and leads to poorer readability and maintainability of the code.

We therefore recommend using the placeholders %I* or %Q*, for which TwinCAT automatically carries out flexible and optimized addressing.

Importance

high

PLCopen rule

N1/CP1

 

Samples:

PROGRAMM MAIN
VAR
    b1    AT%IX0.0 : BOOL;       // => SA0048
    b2    AT%I*    : BOOL;       // no error
END_VAR

 

SA0051: Comparison operations on BOOL variables

Function

Determines comparison operations on variables of type BOOL.

Reason

TwinCAT allows such comparisons, but they are rather unusual and can be confusing. The IEC-61131-3 standard does not provide for these comparisons, so you should avoid them.

Importance

medium

 

Sample:

PROGRAM MAIN
VAR
    b1       : BOOL;
    b2       : BOOL;
    bResult  : BOOL;
END_VAR
bResult := (b1 > b2);            // => SA0051 
bResult := NOT b1 AND b2;
bResult := b1 XOR b2;

 

SA0052: Unusual shift operation

Function

Determines shift operation (bit shift) on signed variables. However, the IEC 61131-3 standard only permits shift operations to bit fields. See also strict rule SA0147.

Reason

TwinCAT allows shift operations on signed data types. However, such operations are unusual and can be confusing. The IEC-61131-3 standard does not provide for such operations, so you should avoid them.

Exception

Shift operation on bit array data types (byte, DWORD, LWORD, WORD) do not result in a SA0052 error.

Importance

medium

 

Samples:

PROGRAM MAIN
VAR
    nINT   : INT;
    nDINT  : DINT;
    nULINT : ULINT;
    nSINT  : SINT;
    nUSINT : USINT;
    nLINT  : LINT;
 
    nDWORD : DWORD;
    nBYTE  : BYTE;
END_VAR
nINT   := SHL(nINT, BYTE#2);     // => SA0052 
nDINT  := SHR(nDINT, BYTE#4);    // => SA0052
nULINT := ROL(nULINT, BYTE#1);   // no error because this is an unsigned data type
nSINT  := ROL(nSINT, BYTE#2);    // => SA0052
nUSINT := ROR(nUSINT, BYTE#3);   // no error because this is an unsigned data type
nLINT  := ROR(nLINT, BYTE#2);    // => SA0052
 
nDWORD := SHL(nDWORD, BYTE#3);   // no error because DWORD is a bit field data type
nBYTE  := SHR(nBYTE, BYTE#1);    // no error because BYTE is a bit field data type 

 

SA0053: Too big bitwise shift

Function

Determines whether the data type width was exceeded in bitwise shift of operands.

Reason

If a shift operation exceeds the data type width, a constant 0 is generated. If a rotation shift exceeds the data type width, it is difficult to read and the rotation value should therefore be shortened.

Importance

high

 

Samples:

PROGRAM MAIN
VAR
    nBYTE  : BYTE;
    nWORD  : WORD;
    nDWORD : DWORD;
    nLWORD : LWORD;
END_VAR
nBYTE  := SHR(nBYTE, BYTE#8);    // => SA0053 
nWORD  := SHL(nWORD, BYTE#45);   // => SA0053
nDWORD := ROR(nDWORD, BYTE#78);  // => SA0053
nLWORD := ROL(nLWORD, BYTE#111); // => SA0053

nBYTE  := SHR(nBYTE, BYTE#7);    // no error
nWORD  := SHL(nWORD, BYTE#15);   // no error

 

SA0054: Comparisons of REAL/LREAL for equality/inequality

Function

Determines where the comparison operators = (equality) and <> (inequality) compare operands of type REAL or LREAL.

Reason

REAL/LREAL values are implemented as floating-point numbers according to the IEEE 754 standard. This standard implies that certain seemingly simple decimal numbers cannot be represented exactly. As a result, the same decimal number may have different LREAL representations.

Sample:

lr11 := 1.1;
lr33 := 3.3;
lrVar1 := lr11 + lr11;
lrVar2 := lr33 - lr11;
bTest := lrVar1 = lrVar2;

bTest will return FALSE in this case, even if the variables lrVar1 and lrVar2 both return the monitoring value 2.2. This is not a compiler error, but a property of the floating-point units of all common processors. You can avoid this by specifying a minimum value by which the values may differ:

bTest := ABS(lrVar1 - lrVar2) < 0.1;

Exception

A comparison with 0.0 is not reported by this analysis. For 0 there is an exact representation in the IEEE 754 standard and therefore the comparison normally works as expected. For better performance, it therefore makes sense to allow a direct comparison here.

Importance

high

PLCopen rule

CP54

 

Samples:

PROGRAM MAIN
VAR
    fREAL1  : REAL;
    fREAL2  : REAL;
    fLREAL1 : LREAL;
    fLREAL2 : LREAL;
    bResult : BOOL;
END_VAR
bResult := (fREAL1 = fREAL1);    // => SA0054 
bResult := (fREAL1 = fREAL2);    // => SA0054
bResult := (fREAL1 <> fREAL2);   // => SA0054
bResult := (fLREAL1 = fLREAL1);  // => SA0054
bResult := (fLREAL1 = fLREAL2);  // => SA0054
bResult := (fLREAL2 <> fLREAL2); // => SA0054
 
bResult := (fREAL1 > fREAL2);    // no error
bResult := (fLREAL1 < fLREAL2);  // no error

 

SA0055: Unnecessary comparison operations of unsigned operands

Function

Determines unnecessary comparisons with unsigned operands. An unsigned data type is never less than zero.

Reason

A comparison revealed by this check provides a constant result and indicates an error in the code.

Importance

high

 

Samples:

PROGRAM MAIN
VAR
    nBYTE   : BYTE;
    nWORD   : WORD;
    nDWORD  : DWORD;
    nLWORD  : LWORD;
    nUSINT  : USINT;
    nUINT   : UINT;
    nUDINT  : UDINT;
    nULINT  : ULINT;
 
    nSINT   : SINT;
    nINT    : INT;
    nDINT   : DINT;
    nLINT   : LINT;
 
    bResult : BOOL;
END_VAR
bResult := (nBYTE >= BYTE#0);    // => SA0055 
bResult := (nWORD < WORD#0);     // => SA0055
bResult := (nDWORD >= DWORD#0);  // => SA0055
bResult := (nLWORD < LWORD#0);   // => SA0055
bResult := (nUSINT >= USINT#0);  // => SA0055
bResult := (nUINT < UINT#0);     // => SA0055
bResult := (nUDINT >= UDINT#0);  // => SA0055
bResult := (nULINT < ULINT#0);   // => SA0055
 
bResult := (nSINT < SINT#0);     // no error
bResult := (nINT < INT#0);       // no error
bResult := (nDINT < DINT#0);     // no error
bResult := (nLINT < LINT#0);     // no error 

 

SA0056: Constant out of valid range

Function

Determines literals (constants) outside the valid operator range.

Reason

The message is output in cases where a variable is compared with a constant that lies outside the value range of this variable. The comparison then returns a constant TRUE or FALSE. This indicates a programming error.

Importance

high

 

Samples:

PROGRAM MAIN
VAR
    nBYTE   : BYTE;
    nWORD   : WORD;
    nDWORD  : DWORD;
    nUSINT  : USINT;
    nUINT   : UINT;
    nUDINT  : UDINT;
 
    bResult : BOOL;
END_VAR
bResult := nBYTE >= 355;                            // => SA0056
bResult := nWORD > UDINT#70000;                     // => SA0056
bResult := nDWORD >= ULINT#4294967300;              // => SA0056
bResult := nUSINT > UINT#355;                       // => SA0056
bResult := nUINT >= UDINT#70000;                    // => SA0056
bResult := nUDINT > ULINT#4294967300;               // => SA0056

 

SA0057: Possible loss of decimal points

Function

Determines statements with possible loss of decimal points.

Reason

A piece of code of the following type:

diTemp2 := 1 rTemp1 := TO_REAL(diTemp2 / DINT#2)

can lead to misinterpretation. This line of code can lead to the assumption that the division would be performed as a REAL operation and the result in this case would be REAL#0.5. However, this is not the case, i.e. the operation is performed as an integer operation, the result is cast to REAL, and rTemp1 is assigned the value REAL#0. To avoid this, you should use a cast to ensure that the operation is performed as a REAL operation:

rTemp1 := TO_REAL(diTemp2) / REAL#2.

Importance

medium

 

Samples:

PROGRAM MAIN
VAR
    fREAL : REAL;
    nDINT : DINT;
    nLINT : LINT;
END_VAR
nDINT := nDINT + DINT#11;
fREAL := DINT_TO_REAL(nDINT / DINT#3);              // => SA0057
fREAL := DINT_TO_REAL(nDINT) / 3.0;                 // no error
fREAL := DINT_TO_REAL(nDINT) / REAL#3.0;            // no error
 
nLINT := nLINT + LINT#13;
fREAL := LINT_TO_REAL(nLINT / LINT#7);              // => SA0057
fREAL := LINT_TO_REAL(nLINT) / 7.0;                 // no error
fREAL := LINT_TO_REAL(nLINT) / REAL#7.0;            // no error

 

SA0058: Operations of enumeration variables

Function

Determines operations on variables of type enumeration. Assignments are permitted.

Reason

Enumerations should not be used as normal integer values. Alternatively, an alias data type can be defined or a subrange type can be used.

Exception

If an enumeration is marked with the attribute {attribute 'strict'}, the compiler reports such an operation.

If an enumeration is declared as a flag via the pragma attribute {attribute 'flags'}, no SA0058 error is issued for operations with AND, OR, NOT, XOR.

Importance

medium

 

Sample 1:

Enumeration E_Color:

TYPE E_Color :
(
    eRed   := 1,
    eBlue  := 2,
    eGreen := 3
);
END_TYPE

Program MAIN:

PROGRAM MAIN
VAR
    nVar   : INT;
    eColor : E_Color;
END_VAR
eColor := E_Color.Green;                            // no error
eColor := E_Color.Green + 1;                        // => SA0058
nVar   := E_Color.Blue / 2;                         // => SA0058
nVar   := E_Color.Green + E_Color.Red;              // => SA0058

 Sample 2:

Enumeration E_State with attribute 'flags':

{attribute 'flags'}
TYPE E_State :
(
    eUnknown := 16#00000001,
    eStopped := 16#00000002,
    eRunning := 16#00000004
) DWORD;
END_TYPE

Program MAIN:

PROGRAM MAIN
VAR
    nFlags : DWORD;
    nState : DWORD;
END_VAR
IF (nFlags AND E_State.eUnknown) <> DWORD#0 THEN    // no error
    nState := nState AND E_State.eUnknown;          // no error
 
ELSIF (nFlags OR E_State.eStopped) <> DWORD#0 THEN  // no error
    nState := nState OR E_State.eRunning;           // no error
END_IF

 

SA0059: Comparison operations always returning TRUE or FALSE

Function

Determines comparisons with literals that always have the result TRUE or FALSE and can already be evaluated during compilation.

Reason

An operation that consistently returns TRUE or FALSE is an indication of a programming error.

Importance

high

 

Samples:

PROGRAM MAIN
VAR
    nBYTE   : BYTE;
    nWORD   : WORD;
    nDWORD  : DWORD;
    nLWORD  : LWORD;
    nUSINT  : USINT;
    nUINT   : UINT;
    nUDINT  : UDINT;
    nULINT  : ULINT;
    nSINT   : SINT;
    nINT    : INT;
    nDINT   : DINT;
    nLINT   : LINT;
    bResult : BOOL;
END_VAR
bResult := nBYTE  <= 255;                           // => SA0059
bResult := nBYTE  <= BYTE#255;                      // => SA0059
bResult := nWORD  <= WORD#65535;                    // => SA0059
bResult := nDWORD <= DWORD#4294967295;              // => SA0059
bResult := nLWORD <= LWORD#18446744073709551615;    // => SA0059
bResult := nUSINT <= USINT#255;                     // => SA0059
bResult := nUINT  <= UINT#65535;                    // => SA0059
bResult := nUDINT <= UDINT#4294967295;              // => SA0059
bResult := nULINT <= ULINT#18446744073709551615;    // => SA0059
bResult := nSINT  >= -128;                          // => SA0059
bResult := nSINT  >= SINT#-128;                     // => SA0059
bResult := nINT   >= INT#-32768;                    // => SA0059
bResult := nDINT  >= DINT#-2147483648;              // => SA0059
bResult := nLINT  >= LINT#-9223372036854775808;     // => SA0059

 

SA0060: Zero used as invalid operand

Function

Determines operations in which an operand with value 0 results in an invalid or meaningless operation.

Reason

Such an expression may indicate a programming error. In any case, it causes unnecessary runtime.

Importance

medium

 

Samples:

PROGRAM MAIN
VAR
    nBYTE   : BYTE;
    nWORD   : WORD;
    nDWORD  : DWORD;
    nLWORD  : LWORD;
END_VAR
nBYTE  := nBYTE  + 0;            // => SA0060
nWORD  := nWORD  - WORD#0;       // => SA0060
nDWORD := nDWORD * DWORD#0;      // => SA0060
nLWORD := nLWORD / 0;            // Compile error: Division by zero

 

SA0061: Unusual operation on pointer

Function

Determines operations on variables of type POINTER TO, which are not = (equality), <> (inequality), + (addition) or ADR.

Reason

Pointer arithmetic is generally permitted in TwinCAT and can be used in a meaningful way. The addition of a pointer with an integer value is therefore classified as a common operation on pointers. This makes it possible to process an array of variable length using a pointer. All other (unusual) operations with pointers are reported with SA0061.

Importance

high

PLCopen rule

E2/E3

 

Samples:

PROGRAM MAIN
VAR
    pINT  : POINTER TO INT;
    nVar  : INT;
END_VAR
pINT := ADR(nVar);               // no error
pINT := pINT * DWORD#5;          // => SA0061
pINT := pINT / DWORD#2;          // => SA0061
pINT := pINT MOD DWORD#3;        // => SA0061
pINT := pINT + DWORD#1;          // no error
pINT := pINT - DWORD#1;          // => SA0061

 

SA0062: Using TRUE and FALSE in expressions

Function

Determines the use of the literal TRUE or FALSE in expressions (e.g. b1 AND NOT TRUE).

Reason

Such an expression is obviously unnecessary and may indicate an error. In any case, the expression causes an unnecessary load on the runtime.

Importance

medium

 

Samples:

PROGRAM MAIN
VAR
    bVar1  : BOOL;
    bVar2  : BOOL;
END_VAR
bVar1 := bVar1 AND NOT TRUE;     // => SA0062
bVar2 := bVar1 OR TRUE;          // => SA0062
bVar2 := bVar1 OR NOT FALSE;     // => SA0062
bVar2 := bVar1 AND FALSE;        // => SA0062

 

SA0063: Possibly not 16-bit-compatible operations

Function

Determines 16-bit operations with intermediate results. Background: 32-bit intermediate results may be truncated on 16-bit systems.

Reason

This message is intended to protect against problems in the very rare case when code is written that is intended to run on both a 16-bit processor and a 32-bit processor.

Importance

low

 

Sample:

(nVar+10) may exceed 16 bits.

PROGRAM MAIN
VAR
    nVar  : INT;
END_VAR
nVar := (nVar + 10) / 2;         // => SA0063

 

SA0064: Addition of pointer

Function

Determines all pointer additions.

Reason

Pointer arithmetic is generally permitted in TwinCAT and can be used in a meaningful way. However, it is also a source of error. Therefore, there are programming rules that prohibit pointer arithmetic. Such a requirement can be verified with this test.

Importance

medium

 

Samples:

PROGRAM MAIN
VAR
    aTest : ARRAY[0..10] OF INT;
    pINT  : POINTER TO INT;
    nIdx  : INT;
END_VAR
pINT  := ADR(aTest[0]);
pINT^ := 0;
pINT  := ADR(aTest) + SIZEOF(INT);                  // => SA0064
pINT^ := 1;
pINT  := ADR(aTest) + 6;                            // => SA0064
pINT  := ADR(aTest[10]);
 
FOR nIdx := 0 TO 10 DO
    pINT^ := nIdx;
    pINT  := pINT + 2;                              // => SA0064
END_FOR

 

SA0065: Incorrect pointer addition to base size

Function

Determines pointer additions in which the value to be added does not match the basic data size of the pointer. Only literals with the basic size may be added. No multiples of the basic size may be added.

Reason

In TwinCAT (in contrast to C and C++), when a pointer with an integer value is added, only this integer value is added as the number of bytes, not the integer value multiplied by the base size. Sample in ST:

pINT := ADR(array_of_int[0]);
pINT := pINT + 2 ; // in TwinCAT zeigt pINT anschließend auf array_of_int[1]

This code would work differently in C:

short* pShort
pShort = &(array_of_short[0])
pShort = pShort + 2; // in C zeigt pShort anschließend auf array_of_short[2]

In TwinCAT, a multiple of the basic size of the pointer should therefore always be added to a pointer. Otherwise the pointer may point to a non-aligned memory, which (depending on the processor) can lead to an alignment exception during access.

Importance

high

 

Samples:

PROGRAM MAIN
VAR
    pUDINT : POINTER TO UDINT;
    nVar   : UDINT;
    pREAL  : POINTER TO REAL;
    fVar   : REAL;
END_VAR
pUDINT := ADR(nVar) + 4;
pUDINT := ADR(nVar) + (2 + 2);
pUDINT := ADR(nVar) + SIZEOF(UDINT);
pUDINT := ADR(nVar) + 3;                            // => SA0065
pUDINT := ADR(nVar) + 2*SIZEOF(UDINT);              // => SA0065
pUDINT := ADR(nVar) + (3 + 2);                      // => SA0065
 
pREAL  := ADR(fVar);
pREAL  := pREAL + 4;
pREAL  := pREAL + (2 + 2);
pREAL  := pREAL + SIZEOF(REAL);
pREAL  := pREAL + 1;                                // => SA0065
pREAL  := pREAL + 2;                                // => SA0065
pREAL  := pREAL + 3;                                // => SA0065
pREAL  := pREAL + (SIZEOF(REAL) - 1);               // => SA0065
pREAL  := pREAL + (1 + 4);                          // => SA0065

 

SA0066: Use of temporary results

Function

Determines applications of intermediate results in statements with a data type that is smaller than the register size. In this case the implicit cast may lead to undesirable results.

Reason

For performance reasons, TwinCAT carries out operations across the register width of the processor. Intermediate results are not truncated. This can lead to misinterpretations, as in the following case:

usintTest := 0;
bError := usintTest - 1 <> 255;

In TwinCAT, bError is TRUE in this case, because the operation usintTest - 1 is typically executed as a 32-bit operation and the result is not cast to the size of bytes. In the register the value 16#ffffffff is then displayed and this is not equal to 255. To avoid this, you have to explicitly cast the intermediate result:

bError := TO_USINT(usintTest - 1) <> 255;

Importance

low

 

If this message is enabled, a large number of rather unproblematic situations in the code will be reported. Although a problem can only arise if the operation produces an overflow or underflow in the data type, the static analysis cannot differentiate between the individual situations.

If you include an explicit typecast in all reported situations, the code will be much slower and less readable!

 

Sample:

PROGRAM MAIN
VAR
    nBYTE   : BYTE;
    nDINT   : DINT;
    nLINT   : LINT;
    bResult : BOOL;
END_VAR
//=====================================================================================================
// type size smaller than register size
// use of temporary result + implicit casting => SA0066
bResult := ((nBYTE - 1) <> 255);                           // => SA0066
 
// correcting this code by explicit cast so that the type size is equal to or bigger than register size
bResult := ((BYTE_TO_LINT(nBYTE) - 1) <> 255);             // no error
bResult := ((BYTE_TO_LINT(nBYTE) - LINT#1) <> LINT#255);   // no error
 
//=====================================================================================================
// result depends on solution platform
bResult := ((nDINT - 1) <> 255);                           // no error on x86 solution platform
                                                           // => SA0066 on x64 solution platform
 
// correcting this code by explicit cast so that the type size is equal to or bigger than register size
bResult := ((DINT_TO_LINT(nDINT) - LINT#1) <> LINT#255);   // no error
 
//=====================================================================================================
// type size equal to or bigger than register size
// use of temporary result and no implicit casting => no error
bResult := ((nLINT - 1) <> 255);                           // no error
 
//====================================================================================================

SA0072: Invalid uses of counter variable

Function

Determines write access operations to a counter variable within a FOR loop.

Reason

Manipulating the counter variable in a FOR loop can easily lead to an infinite loop. To prevent the execution of the loop for certain values of the counter variables, use CONTINUE or simply IF.

Importance

high

PLCopen rule

L12

 

Sample:

PROGRAM MAIN
VAR_TEMP
    nIndex  : INT;
END_VAR
VAR
    aSample : ARRAY[1..10] OF INT;
    nLocal  : INT;
END_VAR
FOR nIndex := 1 TO 10 BY 1 DO
    aSample[nIndex] := nIndex;                      // no error
    nLocal          := nIndex;                      // no error
 
    nIndex          := nIndex - 1;                  // => SA0072
    nIndex          := nIndex + 1;                  // => SA0072
    nIndex          := nLocal;                      // => SA0072
END_FOR

 

SA0073: Use of non-temporary counter variable

Function

Determines the use of non-temporary variables in FOR loops.

Reason

This is a performance warning. A counter variable is always initialized each time a programming block is called. You can create such a variable as a temporary variable (VAR_TEMP). This may result in faster access, and the variable does not occupy permanent storage space.

Importance

medium

PLCopen rule

CP21/L13

 

Sample:

PROGRAM MAIN
VAR
    nIndex  : INT;
    nSum    : INT;
END_VAR
FOR nIndex := 1 TO 10 BY 1 DO    // => SA0073 
    nSum := nSum + nIndex;
END_FOR

 

SA0080: Loop index variable for array index exceeds array range

Function

Determines FOR statements in which the index variable is used for access to an array index and exceeds the array index range.

Reason

Arrays are typically processed in FOR loops. The start and end value of the counter variable should typically match or at least not exceed the lower and upper limits of the array. Here a typical cause of error is detected if array boundaries are changed and constants are not handled carefully, or if a different value is used by mistake in the FOR loop than in the array declaration.

Importance

high

 

Samples:

PROGRAM MAIN
VAR CONSTANT
    c1      : INT := 0;
END_VAR
VAR
    nIndex1 : INT;
    nIndex2 : INT;
    nIndex3 : INT;
    a1      : ARRAY[1..100] OF INT;
    a2      : ARRAY[1..9,1..9,1..9] OF INT;
    a3      : ARRAY[0..99] OF INT;
END_VAR
// 1 violation of the rule (lower range is exeeded) => 1 error SA0080
FOR nIndex1 := c1 TO INT#100 BY INT#1 DO
    a1[nIndex1] := nIndex1;                         // => SA0080
END_FOR
 
// 6 violations (lower and upper range is exeeded for each array dimension) => 3 errors SA0080
FOR nIndex2 := INT#0 TO INT#10 BY INT#1 DO
    a2[nIndex2, nIndex2, nIndex2] := nIndex2;       // => SA0080
END_FOR
 
// 1 violation (upper range is exeeded by the end result of the index), expressions on index are not evaluated => no error
FOR nIndex3 := INT#0 TO INT#50 BY INT#1 DO
    a3[nIndex3 * INT#2] := nIndex3;                 // no error
END_FOR

 

SA0081: Upper border is not a constant

Function

Determines FOR statements in which the upper limit is not defined with a constant value.

Reason

If the upper limit of a loop is a variable value, it is no longer possible to see how often a loop is executed. This can lead to serious problems at runtime, in the worst case to an infinite loop.

Importance

high

 

Samples:

PROGRAM MAIN
VAR CONSTANT
    cMax   : INT := 10;
END_VAR
VAR
    nIndex : INT;
    nVar   : INT;
    nMax1  : INT := 10;
    nMax2  : INT := 10;
END_VAR
FOR nIndex := 0 TO 10 DO         // no error
    nVar := nIndex;
END_FOR
 
FOR nIndex := 0 TO cMax DO       // no error
    nVar := nIndex;
END_FOR

FOR nIndex := 0 TO nMax1 DO      // => SA0081
    nVar := nIndex;
END_FOR

FOR nIndex := 0 TO nMax2 DO      // => SA0081
    nVar := nIndex;
 
    IF nVar = 10 THEN
        nMax2 := 50;
    END_IF
END_FOR

 

SA0075: Missing ELSE

Function

Determines CASE statements without ELSE branch.

Reason

Defensive programming requires the presence of an ELSE in every CASE statement. If no action is required in the ELSE case, you should indicate this with a comment. The reader of the code is then aware that the case was not simply overlooked.

Importance

low

PLCopen rule

L17

 

Sample:

PROGRAM MAIN
VAR
    nVar : INT;
    bVar : BOOL;
END_VAR
nVar := nVar + INT#1;
 
CASE nVar OF                     // => SA0075
    INT#1:
        bVar := FALSE;
 
    INT#2:
        bVar := TRUE;
END_CASE

 

SA0076: Missing enumeration constant

Function

Determines code positions where an enumeration variable is used as condition and not all enumeration values are treated as CASE branches.

Reason

Defensive programming requires the processing of all possible values of an enumeration. If no action is required for a particular enumeration value, you should indicate this explicitly with a comment. This makes it clear that the value was not simply overlooked.

Importance

low

 

Sample:

In the following sample the enumeration value eYellow is not treated as a CASE branch.

Enumeration E_Color:

TYPE E_Color :
(
    eRed,
    eGreen,
    eBlue,
    eYellow
);
END_TYPE

Program MAIN:

PROGRAM MAIN
VAR
    eColor : E_Color;
    bVar   : BOOL;
END_VAR
eColor := E_Color.eYellow;
 
CASE eColor OF                   // => SA0076
    E_Color.eRed:
         bVar := FALSE;
 
    E_Color.eGreen,
    E_Color.eBlue:
         bVar := TRUE;
 
ELSE
    bVar := NOT bVar;
END_CASE 

 

SA0077: Type mismatches with CASE expression

Function

Determines code positions where the data type of a condition does not match that of the CASE branch.

Reason

If the data types between the CASE variable and the CASE case do not match, this could indicate an error.

Importance

low

 

Sample:

Enumeration E_Sample:

TYPE E_Sample :
(
    eNull,
    eOne,
    eTwo
) DWORD;
END_TYPE

Program MAIN:

PROGRAM MAIN
VAR
    nDINT  : DINT;
    bVar   : BOOL;
END_VAR
nDINT := nDINT + DINT#1;
 
CASE nDINT OF
    DINT#1:
         bVar := FALSE;
 
    E_Sample.eTwo,               // => SA0077
    DINT#3:
         bVar := TRUE;
 
ELSE
    bVar := NOT bVar;
END_CASE

 

SA0078: Missing CASE branches

Function

Determines CASE statements without cases, i.e. with only a single ELSE statement.

Reason

A CASE statement without cases wastes execution time and is difficult to read.

Importance

medium

 

Sample:

PROGRAM MAIN
VAR
    nVar   : DINT;
    bVar   : BOOL;
END_VAR
nVar := nVar + INT#1;
 
CASE nVar OF                     // => SA0078
ELSE
    bVar := NOT bVar;
END_CASE

 

SA0090: Return statement before end of function

Function

Determines code positions where the RETURN statement is not the last statement in a function, method, property or program.

Reason

A RETURN in the code leads to poorer maintainability, testability and readability of the code. A RETURN in the code is easily overlooked. You must insert code, which should be executed in any case when a function exits, before each RETURN. This is often overlooked.

Importance

medium

PLCopen rule

CP14

 

Sample:

FUNCTION F_TestFunction : BOOL
F_TestFunction := FALSE;
RETURN;                          // => SA0090
F_TestFunction := TRUE;

 

SA0095: Assignments in conditions

Function

Determines assignments in conditions of IF, CASE, WHILE or REPEAT constructs.

Reason

An assignment (:=) and a comparison (=) can easily be confused. An assignment in a condition can therefore easily be unintentional and is therefore reported. This can also confuse readers of the code.

Importance

high

 

Samples:

PROGRAM MAIN
VAR
    bTest   : BOOL;
    bResult : BOOL;
    bValue  : BOOL;
 
    b1      : BOOL;
    n1      : INT;
    n2      : INT;
 
    nCond1  : INT  := INT#1;
    nCond2  : INT  := INT#2;
    bCond   : BOOL := FALSE;
    nVar    : INT;
    eSample : E_Sample;
END_VAR
// IF constructs
IF (bTest := TRUE) THEN                                    // => SA0095
    DoSomething();
END_IF
 
IF (bResult := F_Sample(bInput := bValue)) THEN            // => SA0095
    DoSomething();
END_IF
 
b1 := ((n1 := n2) = 99);                                   // => SA0095
 
IF INT_TO_BOOL(nCond1 := nCond2) THEN                      // => SA0095
    DoSomething();
ELSIF (nCond1 := 11) = 11 THEN                             // => SA0095
    DoSomething();
END_IF
 
IF bCond := TRUE THEN                                      // => SA0095
    DoSomething();
END_IF
 
IF (bCond := FALSE) OR (nCond1 := nCond2) = 12 THEN        // => SA0095
    DoSomething();
END_IF
 
IF (nVar := nVar + 1) = 120 THEN                           // => SA0095
    DoSomething();
END_IF
 
// CASE construct
CASE (eSample := E_Sample.eMember0) OF                     // => SA0095
    E_Sample.eMember0:
            DoSomething();
 
    E_Sample.eMember1:
            DoSomething();
END_CASE
 
// WHILE construct
WHILE (bCond = TRUE) OR (nCond1 := nCond2) = 12 DO         // => SA0095
    DoSomething();
END_WHILE
 
// REPEAT construct
REPEAT
    DoSomething();
UNTIL
     (bCond = TRUE) OR ((nCond1 := nCond2) = 12)           // => SA0095
END_REPEAT

 

SA0100: Variables greater than <n> bytes

Function

Determines variables that use more than n bytes; n is defined by the current configuration.

You can configure the parameter that is taken into account in the check by double-clicking on the row for rule 100 in the rule configuration (PLC Project Properties > category "Static Analysis" > "Rules" tab > Rule 100). You can make the following settings in the dialog that appears:

  • Upper limit in bytes (default value: 1024)

Reason

Some programming guidelines specify a maximum size for a single variable. This function facilitates a corresponding check.

Importance

low

 

Sample:

In the following sample the variable aSample is greater than 1024 bytes.

PROGRAM MAIN
VAR
    aSample : ARRAY [0..1024] OF BYTE;              // => SA0100
END_VAR

 

SA0101: Names with invalid length

Function

Determines names with invalid length. The object names must have a defined length.

You can configure the parameters that are taken into account in the check by double-clicking on the row for rule 101 in the rule configuration (PLC Project Properties > category "Static Analysis" > "Rules" tab > Rule 101). You can make the following settings in the dialog that appears:

  • Minimum number of characters (default value: 5)
  • Maximum number of characters (default value: 30)
  • Exceptions

Reason

Some programming guidelines specify a minimum length for variable names. Compliance can be verified with this analysis.

Importance

low

PLCopen rule

N6

 

Samples:

Rule 101 is configured with the following parameters:

  • Minimum number of characters: 5
  • Maximum number of characters: 30
  • Exceptions: MAIN, i

Program PRG1:

PROGRAM PRG1                     // => SA0101
VAR
END_VAR

Program MAIN:

PROGRAM MAIN                     // no error due to configured exceptions
VAR
    i     : INT;                 // no error due to configured exceptions
    b     : BOOL;                // => SA0101
    nVar1 : INT;
END_VAR
PRG1();

 

SA0102: Access to program/fb variables from the outside

Function

Determines external access to local variables of programs or function blocks.

Reason

TwinCAT determines external write access operations to local variables of programs or function blocks as compilation errors. Since read access operations to local variables are not intercepted by the compiler and this violates the basic principle of data encapsulation (concealing of data) and contravenes the IEC 61131-3 standard, this rule can be used to determine read access to local variables.

Importance

medium

 

Samples:

Function block FB_Base:

FUNCTION_BLOCK FB_Base
VAR
    nLocal : INT;
END_VAR

Method FB_Base.SampleMethod:

METHOD SampleMethod : INT
VAR_INPUT
END_VAR
nLocal := nLocal + 1;

Function block FB_Sub:

FUNCTION_BLOCK FB_Sub EXTENDS FB_Base

Method FB_Sub.SampleMethod:

METHOD SampleMethod : INT
VAR_INPUT
END_VAR
nLocal := nLocal + 5;

Program PRG_1:

PROGRAM PRG_1
VAR
    bLocal : BOOL;
END_VAR
bLocal := NOT bLocal;

Program MAIN:

PROGRAM MAIN
VAR
    bRead     : BOOL;
    nReadBase : INT;
    nReadSub  : INT;
    fbBase    : FB_Base;
    fbSub     : FB_Sub;
END_VAR
bRead     := PRG_1.bLocal;       // => SA0102 
nReadBase := fbBase.nLocal;      // => SA0102
nReadSub  := fbSub.nLocal;       // => SA0102

 

SA0103: Concurrent access on not atomic data

Function

Determines non-atomic variables (for example with data types STRING, WSTRING, ARRAY, STRUCT, FB instances, 64-bit data types) that are used in more than one task.

Reason

If no synchronization occurs during access, inconsistent values may be read when reading in one task and writing in another task at the same time.

Exception

This rule does not apply in the following cases:

  • If the target system has an FPU (floating point unit), the access of several tasks to LREAL variables is not determined and reported.
  • If the target system is a 64-bit processor or "TwinCAT RT (x64)" is selected as the solution platform, the rule does not apply for 64-bit data types.

Importance

medium

 

See also rule SA0006.

 

Samples:

Structure ST_sample:

TYPE ST_Sample :
STRUCT
    bMember : BOOL;
    nTest   : INT;
END_STRUCT
END_TYPE

Function block FB_Sample:

FUNCTION_BLOCK FB_Sample
VAR_INPUT
    fInput  : LREAL;
END_VAR

GVL:

    {attribute 'qualified_only'}
VAR_GLOBAL    
    fTest   : LREAL;              // => no error SA0103: Since the target system has a FPU, SA0103 does not apply.
    nTest   : LINT;               // => error reporting depends on the solution platform:
                                     // - SA0103 error if solution platform is set to "TwinCAT RT(x86)"
                                     // - no error SA0103 if solution platform is set to "TwinCAT (x64)"
    sTest   : STRING;             // => SA0103
    wsTest  : WSTRING;            // => SA0103
    aTest   : ARRAY[0..2] OF INT; // => SA0103
    aTest2  : ARRAY[0..2] OF INT; // => SA0103
    fbTest  : FB_Sample;          // => SA0103
    stTest  : ST_Sample;          // => SA0103
END_VAR

Program MAIN1, called by task PlcTask1:

PROGRAM MAIN1
VAR
END_VAR
GVL.fTest         := 5.0;
GVL.nTest         := 123;
GVL.sTest         := 'sample text';
GVL.wsTest        := "sample text";
GVL.aTest         := GVL.aTest2;
GVL.fbTest.fInput := 3;
GVL.stTest.nTest  := GVL.stTest.nTest + 1;

Program MAIN2, called by task PlcTask2:

PROGRAM MAIN2
VAR
    fLocal  : LREAL;
    nLocal  : LINT;
    sLocal  : STRING;
    wsLocal : WSTRING;
    aLocal  : ARRAY[0..2] OF INT;
    aLocal2 : ARRAY[0..2] OF INT;
    fLocal2 : LREAL;
    nLocal2  : INT;
END_VAR
fLocal  := GVL.fTest + 1.5;
nLocal  := GVL.nTest + 10;
sLocal  := GVL.sTest;
wsLocal := GVL.wsTest;
aLocal  := GVL.aTest;
aLocal2 := GVL.aTest2;
fLocal2 := GVL.fbTest.fInput;
nLocal2 := GVL.stTest.nTest;

 

SA0105: Multiple instance calls

Function

Determines and reports instances of function blocks that are called more than once. To ensure that an error message for a repeatedly called function block instance is generated, the attribute {attribute 'analysis:report-multiple-instance-call'} must be added in the declaration part of the function block.

Reason

Some function blocks are designed such that they can only be called once in a cycle. This test checks whether a call is made at several points.

Importance

low

PLCopen rule

CP16/CP20

 

Sample:

In the following sample the static analysis will issue an error for fb2, since the instance is called more than once, and the function block is declared with the required attribute.

Function block FB_Test1 without attribute:

FUNCTION_BLOCK FB_Test1

Function block FB_Test2 with attribute:

{attribute 'analysis:report-multiple-instance-calls'}
FUNCTION_BLOCK FB_Test2

Program MAIN:

PROGRAM MAIN 
VAR
    fb1  : FB_Test1;
    fb2  : FB_Test2;
END_VAR
fb1();
fb1();
fb2();                           // => SA0105
fb2();                           // => SA0105

 

SA0106: Virtual method calls in FB_init

Function

Determines method calls in the method FB_init of a basic function block, which are overwritten by a function block derived from the basic FB.

Reason

In such cases it may happen that the variables in overwritten methods are not initialized in the base FB.

Importance

high

 

Sample:

  • Function block FB_Base has the methods FB_init and MyInit. FB_init calls MyInit for initialization.
  • Function block FB_Sub is derived from FB_Base.
  • FB_Sub.MyInit overwrites or extends FB_Base.MyInit.
  • MAIN instantiates FB_Sub. During this process it uses the instance variable nSub before it was initialized, due to the call sequence during the initialization.

 

Function block FB_Base:

FUNCTION_BLOCK FB_Base
VAR
    nBase        : DINT;
END_VAR

Method FB_Base.FB_init:

METHOD FB_init : BOOL
VAR_INPUT
    bInitRetains : BOOL;
    bInCopyCode  : BOOL;
END_VAR
VAR
    nLocal       : DINT;
END_VAR
nLocal := MyInit();              // => SA0106

Method FB_Base.MyInit:

METHOD MyInit : DINT
nBase  := 123;                   // access to member of FB_Base
MyInit := nBase;

Function block FB_Sub:

FUNCTION_BLOCK FB_Sub EXTENDS FB_Base
VAR
    nSub         : DINT;
END_VAR

Method FB_Sub.MyInit:

METHOD MyInit : DINT
nSub   := 456;                   // access to member of FB_Sub
SUPER^.MyInit();                 // call of base implementation
MyInit := nSub;

Program MAIN:

PROGRAM MAIN
VAR
    fbBase       : FB_Base;
    fbSub        : FB_Sub;
END_VAR

 

The instance MAIN.fbBase has the following variable values after the initialization:

  • nBase is 123

The instance MAIN.fbSub has the following variable values after the initialization:

  • nBase is 123
  • nSub is 0

The variable MAIN.fbSub.nSub is 0 after the initialization, because the following call sequence is used during the initialization of fbSub:

  • Initialization of the basic function block:
    • implicit initialization
    • explicit initialization: FB_Base.FB_init
    • FB_Base.FB_init calls FB_Sub.MyInit → SA0106
    • FB_Sub.MyInit calls FB_Base.MyInit (via SUPER pointer)
  • Initialization of the derived function block:
    • implicit initialization

 

SA0107: Missing formal parameters

Function

Determines where formal parameters are missing.

Reason

Code becomes more readable if the formal parameters are specified when the code is called.

Importance

low

 

Sample:

Function F_Sample:

FUNCTION F_Sample : BOOL
VAR_INPUT
    bIn1 : BOOL;
    bIn2 : BOOL;
END_VAR
F_Sample := bIn1 AND bIn2;

Program MAIN:

PROGRAM MAIN
VAR
    bReturn : BOOL;
END_VAR
bReturn := F_Sample(TRUE, FALSE);                   // => SA0107 
bReturn := F_Sample(TRUE, bIn2 := FALSE);           // => SA0107
bReturn := F_Sample(bIn1 := TRUE, bIn2 := FALSE);   // no error

 

SA0111: Pointer variables

Function

Determines variables of type POINTER TO.

Reason

The IEC 61131-3 standard does not allow pointers.

Importance

low

 

Sample:

PROGRAM MAIN
VAR
    pINT   : POINTER TO INT;     // => SA0111
END_VAR

 

SA0112: Reference variables

Function

Determines variables of type REFERENCE TO.

Reason

The IEC 61131-3 standard does not allow references.

Importance

low

 

Sample:

PROGRAM MAIN
VAR
    refInt : REFERENCE TO INT;   // => SA0112
END_VAR

 

SA0113: Variables with data type WSTRING

Function

Determines variables of type WSTRING.

Reason

Not all systems support WSTRING. The code becomes easier to port if WSTRING is not used.

Importance

low

 

Sample:

PROGRAM MAIN
VAR
    wsVar  : WSTRING;            // => SA0113
END_VAR

 

SA0114: Variables with data type LTIME

Function

Determines variables of type LTIME.

Reason

Not all systems support LTIME. The code becomes more portable if LTIME is not used.

Importance

low

 

Sample:

PROGRAM MAIN
VAR
    tVar   : LTIME;              // => SA0114
END_VAR
// no error SA0114 for the following code line:
tVar := tVar + LTIME#1000D15H23M12S34MS2US44NS;

 

SA0115: Declarations with data type UNION

Function

Determines declarations of a UNION data type and declarations of variables of the type of a UNION.

Reason

The IEC-61131-3 standard has no provision for unions. The code becomes easier to port if there are no unions.

Importance

low

 

Samples:

Union U_Sample:

TYPE U_Sample :                  // => SA0115 
UNION
    fVar    : LREAL;
    nVar    : LINT;
END_UNION
END_TYPE

Program MAIN:

PROGRAM MAIN
VAR
    uSample : U_Sample;          // => SA0115
END_VAR

 

SA0117: Variables with data type BIT

Function

Determines declarations of variables of type BIT (possible within structure and function block definitions).

Reason

The IEC-61131-3 has no provision for data type BIT. The code becomes easier to port if BIT is not used.

Importance

low

 

Samples:

Structure ST_sample:

TYPE ST_Sample :
STRUCT
    bBIT  : BIT;                 // => SA0117
    bBOOL : BOOL;
END_STRUCT
END_TYPE

Function block FB_Sample:

FUNCTION_BLOCK FB_Sample
VAR
    bBIT  : BIT;                 // => SA0117
    bBOOL : BOOL;
END_VAR

 

SA0119: Object-oriented features

Function

Determines the use of object-oriented features such as:

  • Function block declarations with EXTENDS or IMPLEMENTS
  • Property and interface declarations
  • Use of the THIS or SUPER pointer

Reason

Not all systems support object-oriented programming. The code becomes easier to port if object orientation is not used.

Importance

low

 

Samples:

Interface I_Sample:

INTERFACE I_Sample                                  // => SA0119

Function block FB_Base:

FUNCTION_BLOCK FB_Base IMPLEMENTS I_Sample          // => SA0119

Function block FB_Sub:

FUNCTION_BLOCK FB_Sub EXTENDS FB_Base               // => SA0119

Method FB_Sub.SampleMethod:

METHOD SampleMethod : BOOL                          // no error

Get function of the property FB_Sub.SampleProperty:

VAR                                                 // => SA0119 
END_VAR

Set function of the property FB_Sub.SampleProperty:

VAR                                                 // => SA0119 
END_VAR

 

SA0120: Program calls

Function

Determines program calls.

Reason

According to the IEC 61131-3 standard, programs can only be called in the task configuration. The code becomes easier to port if program calls elsewhere are avoided.

Importance

low

 

Sample:

Program SubProgram:

PROGRAM SubProgram

Program MAIN:

PROGRAM MAIN
SubProgram();                    // => SA0120

 

SA0121: Missing VAR_EXTERNAL declarations

Function

Determines the use of a global variable in the function block, without it being declared as VAR_EXTERNAL (required according to the standard).

Reason

According to the IEC 61131-3 standard, access to global variables is only permitted via an explicit import through a VAR_EXTERNAL declaration.

Importance

low

PLCopen rule

CP18

 

In TwinCAT 3 PLC it is not necessary for variables to be declared as external. The keyword exists in order to maintain compatibility with IEC 61131-3.

 

Sample:

Global variables:

VAR_GLOBAL
    nGlobal : INT;
END_VAR

Program Prog1:

PROGRAM Prog1
VAR
    nVar    : INT;
END_VAR
nVar := nGlobal;                 // => SA0121

Program Prog2:

PROGRAM Prog2
VAR
    nVar    : INT;
END_VAR
VAR_EXTERNAL
    nGlobal : INT;
END_VAR
nVar := nGlobal;                 // no error

 

SA0122: Array index defined as expression

Function

Determines the use of expressions in the declaration of array boundaries.

Reason

Not all systems allow expressions as array boundaries.

Importance

low

 

Sample:

PROGRAM MAIN
VAR CONSTANT
    cSample  : INT := INT#15;
END_VAR
VAR
    aSample1 : ARRAY[0..10] OF INT;
    aSample2 : ARRAY[0..10+5] OF INT;               // => SA0122
    aSample3 : ARRAY[0..cSample] OF INT;
    aSample4 : ARRAY[0..cSample + 1] OF INT;        // => SA0122
END_VAR

 

SA0123: Usages of INI, ADR or BITADR

Function

Determines the use of the (TwinCAT-specific) operators INI, ADR, BITADR.

Reason

TwinCAT-specific operators prevent portability of the code.

Importance

low

 

Sample:

PROGRAM MAIN
VAR
    nVar : INT;
    pINT : POINTER TO INT;
END_VAR
pINT := ADR(nVar);               // => SA0123

 

SA0147: Unusual shift operation - strict

Function

Determines bit shift operations that are not performed on bit field data types (BYTE, WORD, DWORD, LWORD).

Reason

The IEC 61131-3 standard only allows bit access to bit field data types. However, the TwinCAT 3 compiler also allows bit shift operations with unsigned data types.

Importance

low

 

See also non-strict rule SA0052.

 

Samples:

PROGRAM MAIN
VAR
    nBYTE     : BYTE := 16#45;
    nWORD     : WORD := 16#0045;
    nUINT     : UINT;
    nDINT     : DINT;
    nResBYTE  : BYTE;
    nResWORD  : WORD;
    nResUINT  : UINT;
    nResDINT  : DINT;
    nShift    : BYTE := 2;
END_VAR
nResBYTE := SHL(nByte,nShift);   // no error because BYTE is a bit field
nResWORD := SHL(nWORD,nShift);   // no error because WORD is a bit field
nResUINT := SHL(nUINT,nShift);   // => SA0147
nResDINT := SHL(nDINT,nShift);   // => SA0147

 

SA0148: Unusual bit access - strict

Function

Determines bit access operations that are not performed on bit field data types (BYTE, WORD, DWORD, LWORD).

Reason

The IEC 61131-3 standard only allows bit access to bit field data types. However, the TwinCAT 3 compiler also allows bit access to unsigned data types.

Importance

low

 

See also non-strict rule SA0018.

 

Samples:

PROGRAM MAIN
VAR
    nINT      : INT;
    nDINT     : DINT;
    nULINT    : ULINT;
    nSINT     : SINT;
    nUSINT    : USINT;
    nBYTE     : BYTE;
END_VAR
nINT.3    := TRUE;               // => SA0148
nDINT.4   := TRUE;               // => SA0148
nULINT.18 := FALSE;              // => SA0148
nSINT.2   := FALSE;              // => SA0148
nUSINT.3  := TRUE;               // => SA0148
nBYTE.5   := FALSE;              // no error because BYTE is a bitfield

 

SA0118: Initializations not using constants

Function

Determines initializations that do not assign constants.

Reason

Initializations should be as consistent as possible and should not refer to other variables. In particular, you should avoid function calls during initialization, since this can lead to access to uninitialized data.

Importance

medium

 

Samples:

Function F_ReturnDWORD:

FUNCTION F_ReturnDWORD : DWORD

Program MAIN:

PROGRAM MAIN
VAR CONSTANT
    c1 : DWORD := 100;
END_VAR
VAR
    n1 : DWORD := c1;
    n2 : DWORD := F_ReturnDWORD();                  // => SA0118
    n3 : DWORD := 150;
    n4 : DWORD := n3;                               // => SA0118
END_VAR

 

SA0124: Dereference access in initializations

Function

Determines all code locations where dereferenced pointers are used in the declaration part of POUs.

Reason

Pointers and references should not be used for initializations, because this can lead to access violations at runtime if the pointer has not been initialized.

Importance

medium

 

Samples:

FUNCTION_BLOCK FB_Test
VAR_INPUT
    pStruct    : POINTER TO ST_Test;
    refStruct  : REFERENCE TO ST_Test;
END_VAR
VAR
    bPointer   : BOOL := pStruct^.bTest;  // => SA0124: Dereference access in initialization
    bRef       : BOOL := refStruct.bTest; // => SA0125: Reference used in initialization
END_VAR
bPointer := pStruct^.bTest;               // => SA0039: Possible null pointer dereference 'pStruct^'
bRef     := refStruct.bTest;              // => SA0145: Possible use of not initialized reference 'refStruct'
 
IF pStruct <> 0 THEN
    bPointer := pStruct^.bTest;           // no error SA0039 as the pointer is checked for unequal 0
END_IF

IF __ISVALIDREF(refStruct) THEN
    bRef     := refStruct.bTest;          // no error SA0145 as the reference is checked via __ISVALIDREF
END_IF

Overview of the rules on "dereferencing".

Pointers:

  • Dereferencing of pointers in the declaration part => SA0124
  • Possible null pointer dereferences in the implementation part => SA0039

References:

  • Use of references in the declaration part => SA0125
  • Possible use of not initialized reference in the implementation part => SA0145

Interfaces:

  • Possible use of not initialized interface in the implementation part => SA0046

 

SA0125: References in initializations

Function

Determines all reference variables used for initialization in the declaration part of POUs.

Reason

Pointers and references should not be used for initializations, because this can lead to access violations at runtime if the pointer has not been initialized.

Importance

medium

 

Samples:

FUNCTION_BLOCK FB_Test
VAR_INPUT
    pStruct    : POINTER TO ST_Test;
    refStruct  : REFERENCE TO ST_Test;
END_VAR
VAR
    bPointer   : BOOL := pStruct^.bTest;  // => SA0124: Dereference access in initialization
    bRef       : BOOL := refStruct.bTest; // => SA0125: Reference used in initialization
END_VAR
bPointer := pStruct^.bTest;               // => SA0039: Possible null pointer dereference 'pStruct^'
bRef     := refStruct.bTest;              // => SA0145: Possible use of not initialized reference 'refStruct'
 
IF pStruct <> 0 THEN
    bPointer := pStruct^.bTest;           // no error SA0039 as the pointer is checked for unequal 0
END_IF

IF __ISVALIDREF(refStruct) THEN
    bRef     := refStruct.bTest;          // no error SA0145 as the reference is checked via __ISVALIDREF
END_IF

Overview of the rules on "dereferencing".

Pointers:

  • Dereferencing of pointers in the declaration part => SA0124
  • Possible null pointer dereferences in the implementation part => SA0039

References:

  • Use of references in the declaration part => SA0125
  • Possible use of not initialized reference in the implementation part => SA0145

Interfaces:

  • Possible use of not initialized interface in the implementation part => SA0046

 

SA0039: Possible null pointer dereferences

Function

Determines code positions at which a NULL-pointer may be dereferenced.

Reason

A pointer should be checked before each dereferencing to see if it is not equal to 0. Otherwise an access violation may occur at runtime.

Importance

high

 

Sample 1:

PROGRAM MAIN 
VAR
    pInt1     : POINTER TO INT;
    pInt2     : POINTER TO INT;
    pInt3     : POINTER TO INT;
    nVar1     : INT;
    nCounter  : INT;
END_VAR
nCounter := nCounter + INT#1;
 
pInt1    := ADR(nVar1);
pInt1^   := nCounter;            // no error
 
pInt2^   := nCounter;            // => SA0039
nVar1    := pInt3^;              // => SA0039

Sample 2:

FUNCTION_BLOCK FB_Test
VAR_INPUT
    pStruct    : POINTER TO ST_Test;
    refStruct  : REFERENCE TO ST_Test;
END_VAR
VAR
    bPointer   : BOOL := pStruct^.bTest;  // => SA0124: Dereference access in initialization
    bRef       : BOOL := refStruct.bTest; // => SA0125: Reference used in initialization
END_VAR
bPointer := pStruct^.bTest;               // => SA0039: Possible null pointer dereference 'pStruct^'
bRef     := refStruct.bTest;              // => SA0145: Possible use of not initialized reference 'refStruct'
 
IF pStruct <> 0 THEN
    bPointer := pStruct^.bTest;           // no error SA0039 as the pointer is checked for unequal 0
END_IF

IF __ISVALIDREF(refStruct) THEN
    bRef     := refStruct.bTest;          // no error SA0145 as the reference is checked via __ISVALIDREF
END_IF

Overview of the rules on "dereferencing".

Pointers:

  • Dereferencing of pointers in the declaration part => SA0124
  • Possible null pointer dereferences in the implementation part => SA0039

References:

  • Use of references in the declaration part => SA0125
  • Possible use of not initialized reference in the implementation part => SA0145

Interfaces:

  • Possible use of not initialized interface in the implementation part => SA0046

 

SA0046: Possible use of not initialized interface

Function

Determines the use of interfaces that may not have been initialized before the use.

Reason

An interface reference should be checked for <> 0 before it is used, otherwise an access violation may occur at runtime.

Importance

high

 

Samples:

Interface I_Sample:

INTERFACE I_Sample
METHOD SampleMethod : BOOL
VAR_INPUT
    nInput  : INT;
END_VAR

Function block FB_Sample:

FUNCTION_BLOCK FB_Sample IMPLEMENTS I_Sample
METHOD SampleMethod : BOOL
VAR_INPUT
    nInput  : INT;
END_VAR

Program MAIN:

PROGRAM MAIN
VAR
    fbSample      : FB_Sample;
    iSample       : I_Sample;
    iSampleNotSet : I_Sample;
    nParam        : INT;
    bReturn       : BOOL;
END_VAR
iSample := fbSample; 
bReturn := iSample.SampleMethod(nInput := nParam);         // no error
 
bReturn := iSampleNotSet.SampleMethod(nInput := nParam);   // => SA0046

Overview of the rules on "dereferencing".

Pointers:

  • Dereferencing of pointers in the declaration part => SA0124
  • Possible null pointer dereferences in the implementation part => SA0039

References:

  • Use of references in the declaration part => SA0125
  • Possible use of not initialized reference in the implementation part => SA0145

Interfaces:

  • Possible use of not initialized interface in the implementation part => SA0046

 

SA0145: Possible use of not initialized reference

Function

Determines all reference variables that may not be initialized before they are used and were not checked by the __ISVALIDREF operator. This rule is applied in the implementation part of POUs.

Reason

A reference should be checked for validity before it is accessed, otherwise an access violation may occur at runtime.

Importance

high

 

Samples:

FUNCTION_BLOCK FB_Test
VAR_INPUT
    pStruct    : POINTER TO ST_Test;
    refStruct  : REFERENCE TO ST_Test;
END_VAR
VAR
    bPointer   : BOOL := pStruct^.bTest;  // => SA0124: Dereference access in initialization
    bRef       : BOOL := refStruct.bTest; // => SA0125: Reference used in initialization
END_VAR
bPointer := pStruct^.bTest;               // => SA0039: Possible null pointer dereference 'pStruct^'
bRef     := refStruct.bTest;              // => SA0145: Possible use of not initialized reference 'refStruct'
 
IF pStruct <> 0 THEN
    bPointer := pStruct^.bTest;           // no error SA0039 as the pointer is checked for unequal 0
END_IF

IF __ISVALIDREF(refStruct) THEN
    bRef     := refStruct.bTest;          // no error SA0145 as the reference is checked via __ISVALIDREF
END_IF

Overview of the rules on "dereferencing".

Pointers:

  • Dereferencing of pointers in the declaration part => SA0124
  • Possible null pointer dereferences in the implementation part => SA0039

References:

  • Use of references in the declaration part => SA0125
  • Possible use of not initialized reference in the implementation part => SA0145

Interfaces:

  • Possible use of not initialized interface in the implementation part => SA0046

 

SA0140: Statements commented out

Function

Determines statements that are commented out.

Reason

Code is often commented out for debugging purposes. When such a comment is enabled, it is not clear at a later point in time whether the code should be deleted or whether it was only commented out for debugging purposes and was inadvertently not commented in again.

Importance

high

PLCopen rule

C4

 

Sample:

//bStart := TRUE;                // => SA0140

 

SA0150: Violations of lower or upper limits of the metrics

Function

Determines function blocks that violate the enabled metrics at the lower or upper limit.

Reason

Code that adheres to certain metrics is easier to read, easier to maintain and easier to test.

Importance

high

PLCopen rule

CP9

 

Sample:

The metric "Number of calls" is enabled and configured in the metrics configuration enabled (PLC Project Properties > category "Static Analysis" > "Metrics" tab).

  • Lower limit: 0
  • Upper limit: 3
  • Function block Prog1 is called 5 times

During the execution of the static analysis the violation of SA0150 is issued as an error or warning in the message window.

// => SA0150: Metric violation for 'Prog1'. Result for metric 'Calls' (5) > 3"

 

SA0160: Recursive calls

Function

Determines recursive calls of programs, actions, methods and properties. Determines possible recursions through virtual function calls and interface calls.

Reason

Recursions lead to non-deterministic behavior and are therefore a source of errors.

Importance

medium

PLCopen rule

CP13

 

Sample 1:

Method FB_Sample.SampleMethod1:

METHOD SampleMethod1
VAR_INPUT
END_VAR
SampleMethod1(); (* => SA0160: Recursive call:
                              'MAIN -> FB_Sample.SampleMethod1 -> FB_Sample.SampleMethod1' *)

Method FB_Sample.SampleMethod2:

METHOD SampleMethod2 : BOOL
VAR_INPUT
END_VAR
SampleMethod2 := THIS^.SampleMethod2();(* => SA0160: Recursive call:
                                                    'MAIN -> FB_Sample.SampleMethod2 -> FB_Sample.SampleMethod2' *)

Program MAIN:

PROGRAM MAIN
VAR
    fbSample : FB_Sample;
    bReturn  : BOOL;
END_VAR
fbSample.SampleMethod1();
bReturn := fbSample.SampleMethod2();

Sample 2:

Please note regarding properties:

For a property, a local input variable is implicitly created with the name of the property. The following Set function of a property thus assigns the value of the implicit local input variables to the property of an FB variable.

Function block FB_Sample:

FUNCTION_BLOCK FB_Sample
VAR
    nParameter : INT;
END_VAR

Set function of the property SampleProperty:

nParameter := SampleProperty;

 

In the following Set function, the implicit input variable of the property is assigned to itself. The assignment of a variable to itself does not constitute a recursion, so that this Set function does not generate an SA0160 error.

Set function of the property SampleProperty:

SampleProperty := SampleProperty;              // no error SA0160

 

However, access to a property using the THIS pointer is qualified. By using the THIS pointer, the instance and thus the property is accessed, rather than the implicit local input variable. This means that the shading of implicit local input variables and the property itself is lifted. In the following Set function, a new call to the property is generated, which leads to a recursion and thus to error SA0160.

Set function of the property SampleProperty:

THIS^.SampleProperty := SampleProperty;        // => SA0160

 

SA0161: Unpacked structure in packed structure

Function

Determines unpacked structures that are used in packed structures.

Reason

An unpacked structure is usually placed by the compiler on an address that allows aligned access to all elements within the structure. If you create this structure in a packed structure, aligned access is no longer possible, and access to an element in the unpacked structure can lead to a misalignment exception at runtime.

Importance

high

 

Sample:

The structure ST_SingleDataRecord is packed but contains instances of the unpacked structures ST_4Byte and ST_9Byte. This results in a SA0161 error message.

{attribute 'pack_mode' := '1'}
TYPE ST_SingleDataRecord :
STRUCT
    st9Byte          : ST_9Byte; // => SA0161
    st4Byte          : ST_4Byte; // => SA0161
    n1               : UDINT;
    n2               : UDINT;
    n3               : UDINT;
    n4               : UDINT;
END_STRUCT
END_TYPE

Structure ST_9Byte:

TYPE ST_9Byte :
STRUCT
    nRotorSlots      : USINT;
    nMaxCurrent      : UINT;
    nVelocity        : USINT;
    nAcceleration    : UINT;
    nDeceleration    : UINT;
    nDirectionChange : USINT;
END_STRUCT
END_TYPE

Structure ST_4Byte:

TYPE ST_4Byte :
STRUCT
    fDummy           : REAL;
END_STRUCT
END_TYPE

 

SA0162: Missing comments

Function

Determines points in the program that are not commented. Comments are required for:

  • the declaration of variables. The comments are shown above or to the right.
  • the declaration of POUs, DUTs, GVLs or interfaces. The comments are shown above the declaration (in the first row).

Reason

Full commentary is required by many programming guidelines. It increases the readability and maintainability of the code.

Importance

low

PLCopen rule

C2

 

Samples:

The following sample generates the error for the variable b1: "SA0162: Missing comment for 'b1'".

// Comment for MAIN program
PROGRAM MAIN
VAR
    b1  : BOOL;
    // Comment for variable b2
    b2  : BOOL;
    b3  : BOOL;                  // Comment for variable b3
END_VAR

 

SA0163: Nested comments

Function

Determines code positions with nested comments.

Reason

Nested comments are difficult to read and should be avoided.

Importance

low

PLCopen rule

C3

 

Samples:

The four nested comments identified accordingly in the following sample each result in the error: "SA0163: Nested comment '<…>'".

    (* That is
(* nested comment number 1 *)
*)
PROGRAM MAIN
VAR
    (* That is
    // nested comment
    number 2 *)
    a        : DINT;
    b        : DINT;
     
    (* That is
    (* nested comment number 3 *) *)
    c        : BOOL;
    nCounter : INT;
END_VAR
(* That is // nested comment number 4 *) 
 
nCounter := nCounter + 1;
 
(* This is not a nested comment *)

 

SA0164: Multi-line comments

Function

Determines code positions at which the multi-line comment operator (* *) is used. Only the two single-line comment operators are allowed: // for standard comments, /// for documentation comments.

Reason

Some programming guidelines prohibit multi-line comments in the code, because the beginning and end of a comment could get out of sight and the closing comment bracket could be deleted by mistake.

Importance

low

PLCopen rule

C5

 

You can deactivate this check with the pragma {analysis ...}, including for comments in the declaration part.

 

Samples:

(*
This comment leads to error:
"SA0164 …"
*)
PROGRAM MAIN
VAR
    /// Documentation comment not reported by SA0164
    nCounter1: DINT;
    nCounter2: DINT;             // Standard single-line comment not reported by SA0164
END_VAR
(* This comment leads to error: "SA0164 …" *)
nCounter1 := nCounter1 + 1;
nCounter2 := nCounter2 + 1;

 

SA0166: Maximum number of input/output/VAR_IN_OUT variables

Function

The check determines whether a defined number of input variables (VAR_INPUT), output variables (VAR_OUTPUT) or VAR_IN_OUT variables is exceeded in a function block.

You can configure the parameters that are taken into account in the check by double-clicking on the row for rule 166 in the rule configuration (PLC Project Properties > category "Static Analysis" > "Rules" tab > Rule 166). You can make the following settings in the dialog that appears:

  • Maximum number of inputs (default value: 10)
  • Maximum number of outputs (default value: 10)
  • Maximum number of inputs/outputs (default value: 10)

Reason

This is about checking individual programming guidelines. Many programming guidelines stipulate a maximum number of parameters for function blocks. Too many parameters make the code unreadable and the function blocks difficult to test.

Importance

medium

PLCopen rule

CP23

 

Sample:

Rule 166 is configured with the following parameters:

  • Maximum number of inputs: 0
  • Maximum number of outputs: 10
  • Maximum number of inputs/outputs: 1

The following function block therefore reports two SA0166 errors, since too many inputs (> 0) and too many inputs/outputs (> 1) are declared.

Function block FB_Sample:

FUNCTION_BLOCK FB_Sample         // => SA0166
VAR_INPUT
    bIn     : BOOL;
END_VAR
VAR_OUTPUT
    bOut    : BOOL;
END_VAR
VAR_IN_OUT
    bInOut1 : BOOL;
    bInOut2 : BOOL;
END_VAR

 

SA0167: Report temporary FunctionBlock instances

Function

Determines function block instances that are declared as temporary variables. This applies to instances that are declared in a method, in a function or as VAR_TEMP, and which are reinitialized in each processing cycle or each function block call.

Reason

Function blocks have a state that is usually retained over several PLC cycles. An instance on the stack exists only for the duration of the function call. It is therefore only rarely useful to create an instance as a temporary variable. Secondly, function block instances are frequently large and require a great deal of space on the stack (which is usually limited on controllers). Thirdly, the initialization and often also the scheduling of the function block can take up quite a lot of time.

Importance

medium

 

Samples:

Method FB_Sample.SampleMethod:

METHOD SampleMethod : INT
VAR_INPUT
END_VAR
VAR
    fbTrigger : R_TRIG;          // => SA0167
END_VAR

Function F_Sample:

FUNCTION F_Sample : INT
VAR_INPUT
END_VAR
VAR
    fbSample  : FB_Sample;       // => SA0167
END_VAR

Program MAIN:

PROGRAM MAIN
VAR_TEMP
    fbSample  : FB_Sample;       // => SA0167
    nReturn   : INT;
END_VAR
nReturn := F_Sample();