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 multiple tasks
SA0103: Concurrent access on not atomic data
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
- 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
- SA0017: Non-regular assignments
- 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 code different to declaration
- List unused objects
- SA0032: Unused enumeration constants
- 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
- 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
- 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
- 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
- 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
- SA0161: Unpacked structure in packed structure
- SA0166: Maximum number of input/output/VAR_IN_OUT variables
- SA0167: Report temporary FunctionBlock instances
- SA0168: Unnecessary assignments
- SA0170: Address of an output variable should not be used
- SA0171: Enumerations should have the 'strict' attribute
- SA0175: Suspicious operation on string
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 statement 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 writes 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 disabled via a pragma or attribute! |
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'} |
Samples:
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 only a single 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:
|
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:
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:
|
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 storage 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. |
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. Example:
|
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:
|
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. |
Exception | Enumerations declared with the 'qualified_only' attribute are exempt from SA0027 checking because their elements can only be accessed in a qualified manner. |
Importance | Medium |
Sample:
The following sample 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 storage space. |
Reason | If two variables occupy the same storage 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 code 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 assigned within the respective function block. |
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
bIn1 : BOOL;
bIn2 : BOOL; // => SA0035
END_VAR
VAR_OUTPUT
bOut1 : BOOL;
bOut2 : BOOL; // => SA0036
END_VAR
bOut1 := bIn1;
SA0036: Unused output variables
Function | Determines output variables that are not assigned within the respective function block. |
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
bIn1 : BOOL;
bIn2 : BOOL; // => SA0035
END_VAR
VAR_OUTPUT
bOut1 : BOOL;
bOut2 : BOOL; // => SA0036
END_VAR
bOut1 := bIn1;
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:
the compiler performs the implicit conversion only after the multiplication:
An overflow is therefore truncated. If you want to prevent this, you can have the conversion performed earlier for the elements:
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.
|
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. Example:
bTest will return FALSE in this case, even if the variables fLREAL_a and fLREAL_b 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:
|
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:
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 fREAL 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:
|
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.eGreen; // no error
eColor := E_Color.eGreen + 1; // => SA0058
nVar := E_Color.eBlue / 2; // => SA0058
nVar := E_Color.eGreen + E_Color.eRed; // => 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. |
Reason | Such an expression is obviously unnecessary and may indicate an error. In any case, the expression unnecessarily affects the readability and possibly also 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
IF (bVar = FALSE) THEN // => SA0062
;
END_IF
IF NOT bVar THEN // => no error
;
END_IF
SA0063: Possibly not 16-bit-compatible operations
Function | Determines 16-bit operations with intermediate results. Background: On 16-bit systems, 32-bit temporary results can be truncated. |
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) can 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:
This code would work differently in C:
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:
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:
|
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. |
Exception | A missing ELSE branch is not reported as missing if an enumeration declared with the 'strict' attribute is used in the CASE statement, and if all enumeration constants are listed in that CASE statement. |
Importance | Low |
PLCopen rule | L17 |
Sample:
{attribute 'qualified_only'}
{attribute 'strict'}
{attribute 'to_string'}
TYPE E_Sample :
(
eNull,
eOne,
eTwo
);
END_TYPE
PROGRAM MAIN
VAR
eSample : E_Sample;
nVar : INT;
END_VAR
CASE eSample OF
E_Sample.eNull: nVar := 0;
E_Sample.eOne: nVar := 1;
E_Sample.eTwo: nVar := 2;
END_CASE
CASE eSample OF // => SA0075
E_Sample.eNull: nVar := 0;
E_Sample.eTwo: nVar := 2;
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:
|
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:
|
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:
|
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:
|
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 interfaces
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 references
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 | Detects uncommented locations in the program. Comments are required for:
|
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 "SA0162: Missing comment for 'b1'" for variable 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 disable 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:
|
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();
SA0168: Unnecessary assignments
Function | Determines assignments to variables that have no effects in the code. |
Reason | If several values are assigned to a variable without the variable being evaluated between the assignments, the first assignments do not have any effect on the program. |
Importance | Low |
Sample:
PROGRAM MAIN
VAR
nVar1 : DWORD;
nVar2 : DWORD;
END_VAR
nVar1 := 1;
IF nVar2 > 100 THEN
nVar2 := 0;
nVar2 := nVar2 + 1;
END_IF
nVar1 := 2; // => SA0168
SA0169: Ignored outputs
Function | Determines the outputs of methods and functions that are not specified when calling the method or function. |
Reason | Ignored outputs can be an indication of unhandled errors or nonsensical function calls, as the results are not used. |
Importance | Medium |
Sample:
Function F_Sample:
FUNCTION F_Sample : BOOL
VAR_INPUT
bIn : BOOL;
END_VAR
VAR_OUTPUT
bOut : BOOL;
END_VAR
Program MAIN:
PROGRAM MAIN
VAR
bReturn : BOOL;
bFunOut : BOOL;
END_VAR
bReturn := F_Sample(bIn := TRUE , bOut => bFunOut);
bReturn := F_Sample(bIn := TRUE); // => SA0169
SA0170: Address of an output variable should not be used
Function | Detects code locations where the address of an output variable (VAR_OUTPUT, VAR_IN_OUT) of a function block is determined. |
Reason | It is not allowed to use the address of a function block output in the following way:
|
Exception | No error is reported if the output is used within the same function block. |
Importance | Medium |
Sample:
Function block FB_Sample:
FUNCTION_BLOCK FB_Sample
VAR_INPUT
nIn : INT;
END_VAR
VAR_OUTPUT
nOut : INT;
END_VAR
VAR
pFB : POINTER TO FB_Sample;
pINT : POINTER TO INT;
END_VAR
IF pFB <> 0 THEN
pINT := ADR(pFB^.nOut); // => SA0170
END_IF
nOut := nIn;
pINT := ADR(THIS^.nOut); // no error due to internal usage
pINT := ADR(nOut); // no error due to internal usage
Accesses within another function block, in this case in the MAIN program:
PROGRAM MAIN
VAR
fbSample : FB_Sample;
pExternal : POINTER TO INT;
refExternal : REFERENCE TO INT;
END_VAR
pExternal := ADR(fbSample.nOut); // => SA0170
refExternal REF= fbSample.nOut; // => SA0170
SA0171: Enumerations should have the 'strict' attribute
Function | Detects declarations of enumerations which are not provided with the {attribute 'strict'} attribute. |
Reason | The {attribute 'strict'} attribute causes compiler errors to be issued if the code violates strict programming rules for enumerations. By default, when a new enumeration is created, the declaration is automatically assigned the 'strict' attribute. |
Importance | High |
For more information see: PLC > Reference programming > Pragmas > Attribute pragmas > Attribute 'strict'
Sample:
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_TrafficLight :
(
eRed := 0,
eYellow,
eGreen
);
END_TYPE
{attribute 'qualified_only'}
TYPE E_MachineStates : // => SA0171
(
eStopped := 0,
eRunning,
eError
);
END_TYPE
SA0175: Suspicious operation on string
Function | Determines code positions that are suspicious for UTF-8 encoding. |
Captured constructs |
|
Importance | Medium |
Samples:
VAR
sVar : STRING;
pVar : POINTER TO STRING;
nVar : INT;
END_VAR
// 1) SA0175: Suspicious operation on string: Index access
sVar[2]; // => SA0175
// 2) SA0175: Suspicious operation on string: Possible index access
pVar := ADR(sVar); // => SA0175
// 3) SA0175: Suspicious operation on string: Possible index access
nVar := FIND(sVar, 'a'); // => SA0175
// 4) SA0175: Suspicious operation on string: Literal '<...>' contains Non-ASCII character
sVar := '99€'; // => SA0175
sVar := 'Ä'; // => SA0175