Regeln - Übersicht und Beschreibung
Strikte IEC-Regeln prüfen Die Prüfungen unterhalb des Knotens "Strikte IEC-Regeln prüfen" ermitteln Funktionalitäten und Datentypen, die in TwinCAT in Erweiterung der IEC61131-3 erlaubt sind. |
Gleichzeitigen/Konkurrierenden Zugriff prüfen Zu diesem Thema existierenden die folgenden Regeln: SA0006: Schreibzugriff aus mehreren Tasks SA0103: Gleichzeitiger Zugriff auf nicht-atomare Daten
Bitte beachten Sie, dass nur direkte Zugriffe erkannt werden können. Indirekte Zugriffe, beispielsweise per Pointer/Referenz, werden nicht aufgelistet. Bitte beachten Sie außerdem die Dokumentation zum Thema "Multitask-Datenzugriffs-Synchronisation in der SPS", in der einige Hinweise zur Notwendigkeit und den Möglichkeiten einer Datenzugriffs-Synchronisation enthalten sind. |
Übersicht
- SA0004: Mehrfacher Schreibzugriff auf Ausgang
- SA0006: Schreibzugriff aus mehreren Tasks
- SA0007: Adressoperator auf Konstanten
- SA0008: Unterbereichstypen prüfen
- SA0009: Nicht verwendete Rückgabewerte
- SA0010: Arrays mit nur einer Komponente
- SA0011: Nutzlose Deklarationen
- SA0012: Variablen, die als Konstanten deklariert werden könnten
- SA0013: Deklarationen mit gleichem Variablennamen
- SA0014: Zuweisungen auf Instanzen
- SA0015: Zugriff auf globale Daten über FB_init
- SA0016: Lücken in Strukturen
- SA0017: Nicht-reguläre Zuweisungen
- SA0018: Unübliche Bitzugriffe
- SA0020: Möglicherweise Zuweisung eines abgeschnittenen Wertes an REAL-Variable
- SA0021: Weitergabe der Adresse einer temporären Variablen
- SA0022: (Möglicherweise) nicht zurückgewiesene Rückgabewerte
- SA0023: Komplexe Rückgabewerte
- SA0024: Nicht getypte Literale
- SA0025: Unqualifizierte Enumerationskonstanten
- SA0026: Möglicherweise Abschneiden von Strings
- SA0027: Mehrfachverwendung des Namens
- SA0028: Überlappende Speicherbereiche
- SA0029: Notation in Implementierung und Deklaration unterschiedlich
- Nicht verwendete Objekte auflisten
- SA0031: Nicht verwendete Signaturen
- SA0032: Nicht verwendete Aufzählungskonstante
- SA0033: Nicht verwendete Variablen
- SA0035: Nicht verwendete Eingabevariablen
- SA0036: Nicht verwendete Ausgabevariablen
- SA0034: Enumerationsvariablen mit falscher Zuweisung
- SA0037: Schreibzugriff auf Eingabevariable
- SA0038: Lesezugriff auf Ausgabevariable
- SA0040: Mögliche Division durch Null
- SA0041: Möglicherweise schleifeninvarianter Code
- SA0042: Verwendung unterschiedlicher Zugriffspfade
- SA0043: Verwendung einer globalen Variablen in nur 1 POU
- SA0044: Deklarationen mit Schnittstellenreferenz
- Konvertierungen
- SA0019: Implizite Pointer-Konvertierungen
- SA0130: Implizite erweiternde Konvertierungen
- SA0131: Implizite einengende Konvertierungen
- SA0132: Implizite vorzeichenbehaftete/vorzeichenlose Konvertierungen
- SA0133: Explizite einschränkende Konvertierungen
- SA0134: Explizite vorzeichenbehaftete/vorzeichenlose Konvertierungen
- Verwendung direkter Adressen
- SA0005: Ungültige Adressen und Datentypen
- SA0047: Zugriff auf direkte Adressen
- SA0048: AT-Deklarationen auf direkte Adressen
- Regeln für Operatoren
- SA0051: Vergleichsoperatoren auf BOOL-Variablen
- SA0052: Unübliche Schiebeoperation
- SA0053: Zu große bitweise Verschiebung
- SA0054: Vergleich von REAL/LREAL auf Gleichheit/Ungleichheit
- SA0055: Unnötige Vergleichsoperationen von vorzeichenlosen Operanden
- SA0056: Konstante außerhalb des gültigen Bereichs
- SA0057: Möglicher Verlust von Nachkommastellen
- SA0058: Operation auf Enumerationsvariablen
- SA0059: Vergleichsoperationen, die immer TRUE oder FALSE liefern
- SA0060: Null als ungültiger Operand
- SA0061: Unübliche Operation auf Pointer
- SA0062: Verwendung von TRUE und FALSE in Ausdrücken
- SA0063: Möglicherweise nicht 16-bitkompatible Operationen
- SA0064: Addition eines Pointers
- SA0065: Pointer-Addition passt nicht zur Basisgröße
- SA0066: Verwendung von Zwischenergebnissen
- Regeln für Anweisungen
- FOR-Anweisungen
- SA0072: Ungültige Verwendung einer Zählervariablen
- SA0073: Verwendung einer nicht-temporären Zählervariablen
- SA0080: Schleifenindexvariable für Arrayindex überschreitet Array-Bereich
- SA0081: Obergrenze ist kein konstanter Wert
- CASE-Anweisungen
- SA0076: Fehlende Aufzählungskonstante
- SA0077: Datentypdiskrepanz bei CASE-Ausdruck
- SA0078: CASE-Anweisung ohne CASE-Zweig
- SA0090: Return-Anweisung vor Ende der Funktion
- SA0095: Zuweisung in Bedingung
- SA0100: Variablen größer als <n> Bytes
- SA0101: Namen mit unzulässiger Länge
- SA0102: Zugriff von außen auf lokale Variable
- SA0103: Gleichzeitiger Zugriff auf nicht-atomare Daten
- SA0105: Mehrfache Instanzaufrufe
- SA0106: Virtuelle Methodenaufrufe in FB_init
- SA0107: Fehlen von formalen Parametern
- Strikte IEC-Regeln prüfen
- SA0113: Variablen mit Datentyp WSTRING
- SA0114: Variablen mit Datentyp LTIME
- SA0115: Deklarationen mit Datentyp UNION
- SA0117: Variablen mit Datentyp BIT
- SA0119: Objektorientierte Funktionalität
- SA0121: Fehlende VAR_EXTERNAL Deklarationen
- SA0122: Als Ausdruck definierter Arrayindex
- SA0123: Verwendung von INI, ADR oder BITADR
- SA0147: Unübliche Schiebeoperation - strikt
- SA0148: Unüblicher Bitzugriff - strikt
- Regeln für Initialisierungen
- SA0118: Initialisierungen nicht mit Konstanten
- SA0124: Dereferenzierungszugriff in Initialisierungen
- SA0125: Referenzen in Initialisierungen
- SA0140: Auskommentierte Anweisungen
- Mögliche Verwendung nicht initialisierter Variablen
- SA0039: Mögliche Null-Pointer-Dereferenzierung
- SA0046: Mögliche Verwendung nicht initialisierter Schnittstellen
- SA0145: Mögliche Verwendung nicht initialisierter Referenzen
- SA0150: Verletzung von Unter- oder Obergrenzen von Metriken
- SA0161: Ungepackte Struktur in gepackter Struktur
- SA0163: Verschachtelte Kommentare
- SA0164: Mehrzeilige Kommentare
- SA0166: Maximale Anzahl an Eingabe-/Ausgabe-/VAR_IN_OUT Variablen
- SA0167: Temporäre Funktionsbausteininstanzen
- SA0168: Unnötige Zuweisungen
- SA0170: Adresse einer Ausgangsvariablen sollte nicht verwendet werden
- SA0171: Enumerationen sollten das Attribut 'strict' haben
- SA0175: Verdächtige Operation auf String
Detaillierte Beschreibung
SA0001: Unerreichbarer Code
Funktion | Ermittelt Code, der nicht ausgeführt wird, beispielweise wegen einer RETURN oder CONTINUE Anweisung. |
Begründung | Unerreichbarer Code sollte in jedem Fall vermieden werden. Häufig weist die Prüfung darauf hin, dass noch Testcode enthalten ist, der wieder entfernt werden sollte. |
Wichtigkeit | Hoch |
PLCopen-Regel | CP2 |
Beispiel 1 – RETURN:
PROGRAM MAIN
VAR
bReturnBeforeEnd : BOOL;
END_VAR
bReturnBeforeEnd := FALSE;
RETURN;
bReturnBeforeEnd := TRUE; // => SA0001
Beispiel 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: Leere Objekte
Funktion | Ermittelt POUs, GVLs oder Datentypdeklarationen, die keinen Code enthalten. |
Begründung | Leere Objekte sollten vermieden werden. Sie sind oft ein Zeichen dafür, dass ein Objekt nicht vollständig implementiert ist. Ausnahme: In manchen Fällen wird dem Rumpf eines Funktionsblocks kein Code geben, wenn dieser nur über Schnittstellen verwendet werden soll. In anderen Fällen wird eine Methode nur anlgelegt, weil sie von einer Schnittstelle gefordert wird, ohne dass für die Methode eine sinnvolle Implementierung möglich ist. In jedem Fall sollte eine solche Situation kommentiert werden. |
Wichtigkeit | Mittel |
SA0003: Leere Anweisungen
Funktion | Ermittelt Codezeilen, die ein Semikolon (;), aber keine Anweisung enthalten. |
Begründung | Eine leere Anweisung kann ein Anzeichen für fehlenden Code sein. |
Ausnahme | Es gibt sinnvolle Verwendungen leerer Anweisungen. Beispielsweise kann es sinnvoll sein, in einer CASE-Anweisung alle Fälle explizit auszuprogrammieren, auch die, in denen nichts zu tun ist. Wenn eine solche leere CASE-Anweisung mit einem Kommentar versehen ist, erzeugt die statische Codeanalyse keine Fehlermeldung. |
Wichtigkeit | Niedrig |
Beispiele:
; // => SA0003
(* comment *); // => SA0003
nVar; // => SA0003
Das folgende Beispiel erzeugt für den Zustand 2 den Fehler "SA0003: Empty statement".
CASE nVar OF
1: DoSomething();
2: ;
3: DoSomethingElse();
END_CASE
Das folgende Beispiel erzeugt keinen SA0003-Fehler.
CASE nVar OF
1: DoSomething();
2: ; // nothing to do
3: DoSomethingElse();
END_CASE
SA0004: Mehrfacher Schreibzugriff auf Ausgang
Funktion | Ermittelt Ausgänge, die an mehr als einer Position geschrieben werden. |
Begründung | Die Wartbarkeit leidet, wenn ein Ausgang an verschiedenen Stellen im Code geschrieben wird. Es ist dann unklar, welcher Schreibzugriff derjenige ist, der tatsächlich Auswirkungen im Prozess hat. Gute Praxis ist es, die Berechnung der Ausgangsvariablen in Hilfsvariablen durchzuführen und an einer Stelle am Ende des Zyklus den berechneten Wert zuzuweisen. |
Ausnahme | Es wird kein Fehler ausgegeben, wenn eine Ausgangsvariable in verschiedenen Zweigen von IF- bzw. CASE-Anweisungen geschrieben wird. |
Wichtigkeit | Hoch |
PLCopen-Regel | CP12 |
Diese Regel kann nicht über ein Pragma oder Attribut abgeschaltet werden! |
Beispiel:
Globale Variablenliste:
VAR_GLOBAL
bVar AT%QX0.0 : BOOL;
nSample AT%QW5 : INT;
END_VAR
Programm 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: Schreibzugriff aus mehreren Tasks
Funktion | Ermittelt Variablen, auf die von mehr als einer Task geschrieben wird. |
Begründung | Eine Variable, die in mehreren Tasks geschrieben wird, kann unter Umständen ihren Wert unerwartet ändern. Das kann zu verwirrenden Situationen führen. Stringvariablen und auf einigen 32-Bit-Systemen auch 64-Bit-Integer-Variablen können sogar einen inkonsistenten Zustand bekommen, wenn die Variable gleichzeitig in zwei Tasks geschrieben wird. |
Ausnahme | In bestimmten Fällen kann es nötig sein, dass mehrere Tasks eine Variable schreiben. Stellen Sie dann sicher, beispielsweise durch die Verwendung von Semaphoren, dass der Zugriff nicht zu einem inkonsistenten Zustand führt. |
Wichtigkeit | Hoch |
PLCopen-Regel | CP10 |
Sehen Sie auch die Regel SA0103. |
Aufruf entspricht Schreibzugriff Bitte beachten Sie, dass Aufrufe als Schreibzugriff interpretiert werden. Beispielsweise wird der Aufruf einer Methode für eine Funktionsbausteininstanz als Schreibzugriff auf die Funktionsbausteininstanz angesehen. Eine genauere Analyse der Zugriffe und Aufrufe ist z.B. aufgrund von virtuellen Aufrufen (Zeiger, Interface) nicht möglich. Wenn Sie die Regel SA0006 für eine Variable (z.B. für eine Funktionsbausteininstanz) deaktivieren möchten, können Sie das folgende Attribut oberhalb der Variablendeklaration einfügen: {attribute 'analysis' := '-6'} |
Beispiele:
Die beiden globalen Variablen nVar und bVar werden von zwei Tasks geschrieben.
Globale Variablenliste:
VAR_GLOBAL
nVar : INT;
bVar : BOOL;
END_VAR
Programm MAIN_Fast, aufgerufen von der Task PlcTaskFast:
nVar := nVar + 1; // => SA0006
bVar := (nVar > 10); // => SA0006
Programm MAIN_Slow, aufgerufen von der Task PlcTaskSlow:
nVar := nVar + 2; // => SA0006
bVar := (nVar < -50); // => SA0006
SA0007: Adressoperator auf Konstanten
Funktion | Ermittelt Stellen, an denen der ADR-Operator bei einer Konstanten angewendet wird. |
Begründung | Durch einen Pointer auf eine konstante Variable hebt man die CONSTANT-Eigenschaft der Variable auf. Über den Pointer kann die Variable verändert werden, ohne dass der Compiler dies meldet. |
Ausnahme | In seltenen Fällen kann es sinnvoll sein, einen Pointer auf eine Konstante an eine Funktion zu übergeben. Es muss dann allerdings gewährleistet sein, dass diese Funktion den übergebenen Wert nicht ändert. Verwenden Sie in diesem Fall wenn möglich VAR_IN_OUT CONSTANT. |
Wichtigkeit | Hoch |
Wenn die Option Konstanten ersetzen in den Compiler-Optionen der SPS-Projekteigenschaften aktiviert ist, ist der Adressoperator für skalare Konstanten (Integer, BOOL, REAL) nicht erlaubt und ein Übersetzungsfehler wird ausgegeben. (Konstante Strings, Strukturen und Arrays haben immer eine Adresse.) |
Beispiel:
PROGRAM MAIN
VAR CONSTANT
cValue : INT := INT#15;
END_VAR
VAR
pValue : POINTER TO INT;
END_VAR
pValue := ADR(cValue); // => SA0007
SA0008: Unterbereichstypen prüfen
Funktion | Ermittelt Bereichsüberschreitungen von Unterbereichstypen. Zugewiesene Literale werden bereits vom Compiler geprüft. Wenn Konstanten zugeordnet sind, müssen die Werte innerhalb des definierten Bereichs liegen. Wenn Variablen zugeordnet sind, müssen die Datentypen identisch sein. |
Begründung | Wenn Unterbereichstypen verwendet werden, dann sollte sichergestellt werden, dass dieser Unterbereich nicht verlassen wird. Der Compiler überprüft solche Unterbereichsverletzungen nur für Zuweisungen von Konstanten. |
Wichtigkeit | Niedrig |
Die Prüfung wird nicht für CFC-Objekte durchgeführt, da die Codestruktur dies nicht zulässt. |
Beispiel:
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: Nicht verwendete Rückgabewerte
Funktion | Ermittelt Funktions-, Methoden- und Eigenschaftenaufrufe, bei denen der Rückgabewert nicht verwendet wird. |
Begründung | Wenn eine Funktion oder eine Methode einen Rückgabewert liefert, dann sollte dieser auch ausgewertet werden. Häufig wird im Rückgabewert mitgeliefert, ob die Funktion erfolgreich ausgeführt werden konnte. Wenn keine Auswertung erfolgt, ist später nicht mehr erkennbar, ob der Rückgabewert übersehen wurde, oder ob er tatsächlich nicht benötigt wird. |
Ausnahme | Wenn ein Rückgabewert beim Aufruf nicht von Interesse ist, dann kann dies dokumentiert und die Zuweisung weglassen werden. Fehlerrückgaben sollten nie ignoriert werden! |
Wichtigkeit | Mittel |
PLCopen-Regel | CP7/CP17 |
Beispiel:
Funktion F_ReturnBOOL:
FUNCTION F_ReturnBOOL : BOOL
F_ReturnBOOL := TRUE;
Programm MAIN:
PROGRAM MAIN
VAR
bVar : BOOL;
END_VAR
F_ReturnBOOL(); // => SA0009
bVar := F_ReturnBOOL();
SA0010: Arrays mit nur einer Komponente
Funktion | Ermittelt Arrays, die nur eine einzige Komponente enthalten. |
Begründung | Ein Array mit einer Komponente kann durch eine Variable vom Basistyp ersetzt werden. Der Zugriff auf diese Variable ist deutlich schneller als der Zugriff mit Index auf eine Variable. |
Ausnahme | Häufig wird die Länge eines Arrays über eine Konstante festgelegt und ist ein Parameter für ein Programm. Das Programm kann dann mit Arrays von verschiedener Länge arbeiten und muss nicht geändert werden, wenn die Länge nur 1 beträgt. Eine solche Situation sollte entsprechend dokumentiert werden. |
Wichtigkeit | Niedrig |
Beispiele:
PROGRAM MAIN
VAR
aEmpty1 : ARRAY [0..0] OF INT; // => SA0010
aEmpty2 : ARRAY [15..15] OF REAL; // => SA0010
END_VAR
SA0011: Nutzlose Deklarationen
Funktion | Ermittelt Strukturen, Unions oder Enumerationen mit höchstens einer Komponente. |
Begründung | Eine solche Deklaration kann für einen Leser verwirrend sein. Eine Struktur mit nur einem Element kann durch einen Aliastyp ersetzt werden. Eine Enumeration mit einem Element kann durch eine Konstante ersetzt werden. |
Wichtigkeit | Niedrig |
PLCopen-Regel | CP22/CP24 |
Beispiel 1 – Struktur:
TYPE ST_SingleStruct : // => SA0011
STRUCT
nPart : INT;
END_STRUCT
END_TYPE
Beispiel 2 – Union:
TYPE U_SingleUnion : // => SA0011
UNION
fVar : LREAL;
END_UNION
END_TYPE
Beispiel 3 – Enumeration:
TYPE E_SingleEnum : // => SA0011
(
eOnlyOne := 1
);
END_TYPE
SA0012: Variablen, die als Konstanten deklariert werden könnten
Funktion | Ermittelt Variablen, auf die nicht schreibend zugegriffen wird und die deshalb als Konstante deklariert werden könnten. |
Begründung | Wenn eine Variable nur an der Deklarationsstelle geschrieben und sonst nur lesend verwendet wird, dann nimmt die statische Analyse an, dass die Variable auch nicht geändert werden soll. Eine Deklaration als Konstante führt dann erstens dazu, dass auch bei Programmänderungen überprüft wird, dass die Variable nicht verändert wird. Zweitens führt die Deklaration als Konstante unter Umständen zu schnellerem Code. |
Wichtigkeit | Niedrig |
Beispiel:
PROGRAM MAIN
VAR
nSample : INT := INT#17;
nVar : INT;
END_VAR
nVar := nVar + nSample; // => SA0012
SA0013: Deklarationen mit gleichem Variablennamen
Funktion | Ermittelt Variablen, die den gleichen Namen haben wie andere Variablen (Beispiel: globale und lokale Variablen mit gleichen Namen), oder wie Funktionen, Aktionen, Methoden oder Eigenschaften (Properties) innerhalb des gleichen Zugriffsbereichs. |
Begründung | Gleiche Namen können beim Lesen des Codes verwirrend sein und sie können zu Fehlern führen, wenn unbeabsichtigt auf das falsche Objekt zugegriffen wird. Es wird empfohlen, Namenskonventionen zu verwenden, deren Einhaltung solche Situationen vermeidet. |
Wichtigkeit | Mittel |
PLCopen-Regel | N5/N9 |
Beispiele:
Globale Variablenliste GVL_App:
VAR_GLOBAL
nVar : INT;
END_VAR
Programm MAIN, welches eine Methode mit dem Namen Sample beinhaltet:
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: Zuweisungen auf Instanzen
Funktion | Ermittelt Zuweisungen auf Funktionsbausteininstanzen. Bei Instanzen mit Pointer- oder Referenzvariablen können diese Zuweisungen riskant sein. |
Begründung | Dies ist eine Performance-Warnung. Wenn eine Instanz einer anderen Instanz zugewiesen wird, dann werden alle Elemente und Unterelemente von der einen Instanz in die andere kopiert. Pointer auf Daten werden mitkopiert, jedoch nicht deren referenzierte Daten, so dass die Zielinstanz und die Quellinstanz nach der Zuweisung die gleichen Daten enthalten. Je nach Größe der Instanzen kann eine solche Zuweisung sehr lange dauern. Wenn eine Instanz beispielsweise zur Bearbeitung an eine Funktion übergeben werden soll, dann ist es sehr viel performanter, einen Pointer auf die Instanz zu übergeben. Um selektiv Werte von einer Instanz in eine andere zu kopieren, kann eine Kopiermethode sinnvoll sein:
|
Wichtigkeit | Mittel |
Beispiel:
PROGRAM MAIN
VAR
fb1 : FB_Sample;
fb2 : FB_Sample;
END_VAR
fb1();
fb2 := fb1; // => SA0014
SA0015: Zugriff auf globale Daten über FB_init
Funktion | Ermittelt Zugriffe eines Funktionsbausteins auf globale Daten über die FB_init-Methode. Der Wert dieser Variablen hängt von der Reihenfolge der Initialisierungen ab! |
Begründung | Je nach Deklarationsstelle der Instanz eines Bausteins kann es sein, dass bei Verletzung der Regel auf eine nicht-initialisierte Variable zugegriffen wird. |
Wichtigkeit | Hoch |
Beispiel:
Globale Variablenliste GVL_App:
VAR_GLOBAL
nVar : INT;
END_VAR
Funktionsbaustein FB_Sample:
FUNCTION_BLOCK FB_Sample
VAR
nLocal : INT;
END_VAR
Methode 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
Programm MAIN:
PROGRAM MAIN
VAR
fbSample : FB_Sample;
END_VAR
SA0016: Lücken in Strukturen
Funktion | Ermittelt Lücken in Strukturen oder Funktionsbausteinen, verursacht durch die Alignment-Anforderungen des aktuell eingestellten Zielsystems. Wenn möglich, sollten Sie die Lücken durch Umsortieren der Strukturelemente oder durch Auffüllen mit einem Dummy-Elemententfernen. Wenn dies nicht möglich ist, dann können Sie die Regel für die betroffenen Strukturen durch das Attribut {attribute 'analysis' := '...'} deaktivieren. |
Begründung | Durch unterschiedliche Alignment-Anforderungen auf verschiedenen Plattformen, kann es für solche Strukturen zu einem unterschiedlichen Layout im Speicher kommen. Der Code kann sich dann je nach Plattform unterschiedlich verhalten. |
Wichtigkeit | Niedrig |
Beispiele:
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: Nicht-reguläre Zuweisungen
Funktion | Ermittelt Zuweisungen auf Pointer, die keine Adresse (ADR-Operator, Zeigervariablen) oder Konstante 0 sind. |
Begründung | Wenn ein Pointer einen Wert enthält, der keine gültige Adresse ist, dann kommt es bei der Dereferenzierung des Pointers zu einer Access Violation Exception. |
Wichtigkeit | Hoch |
Beispiel:
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: Unübliche Bitzugriffe
Funktion | Ermittelt Bitzugriffe auf vorzeichenbehaftete Variablen. Die Norm IEC 61131-3 erlaubt allerdings nur Bitzugriffe auf Bitfelder. Sehen Sie hierzu auch die strikte Regel SA0148. |
Begründung | Vorzeichenbehaftete Datentypen sollten nicht als Bitfelder verwendet werden und umgekehrt. Die Norm IEC 61131-3 sieht solche Zugriffe nicht vor. Diese Regel muss eingehalten werden, wenn der Code protierbar sein soll. |
Ausnahme | Ausnahme für Flag-Enumerationen: Wenn eine Enumeration mit Hilfe des Pragmaattributs {attribute 'flags'} als Flag deklariert ist, wird für Bitzugriffe mit den Operationen OR, AND oder NOT der Fehler SA0018 nicht ausgegeben. |
Wichtigkeit | Mittel |
Beispiele:
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: Möglicherweise Zuweisung eines abgeschnittenen Werts an REAL-Variable
Funktion | Ermittelt Operationen auf Integer-Variablen, bei denen möglicherweise ein abgeschnittener Wert an eine Variable vom Datentyp REAL zugewiesen wird. |
Begründung | Die statische Codeanalyse gibt einen Fehler aus, wenn das Ergebnis einer Integerberechnung einer REAL- oder LREAL-Variablen zugewiesen wird. Der Programmierer soll dabei auf eine möglicherweise fehlerhafte Interpretation einer solchen Zuweisung aufmerksam gemacht werden:
Da der Wertebereich von LREAL größer ist als der von DINT, könnte angenommen werden, dass das Ergebnis der Rechnung in jedem Fall in LREAL dargestellt wird. Das ist aber nicht der Fall. Der Prozessor berechnet das Ergebnis der Multiplikation als Integer und castet anschließend das Ergebnis nach LREAL. Ein Überlauf in der Integer-Berechnung würde verloren gehen. Um das Problem zu umgehen, muss die Rechnung bereits als REAL-Operation erfolgen:
|
Wichtigkeit | Hoch |
Beispiel:
PROGRAM MAIN
VAR
nVar1 : DWORD;
nVar2 : DWORD;
fVar : REAL;
END_VAR
nVar1 := nVar1 + DWORD#1;
nVar2 := nVar2 + DWORD#2;
fVar := nVar1 * nVar2; // => SA0020
SA0021: Weitergabe der Adresse einer temporären Variablen
Funktion | Ermittelt Zuweisungen von Adressen von temporären Variablen (Variablen auf dem Stack) zu nicht-temporären Variablen. |
Begründung | Lokale Variablen einer Funktion oder einer Methode werden auf dem Stack angelegt und existieren nur während der Abarbeitung der Funktion oder Methode. Zeigt ein Pointer nach Abarbeitung der Methode oder Funktion auf eine solche Variable, dann kann über diesen Pointer in undefinierten Speicher gegriffen, oder auf eine falsche Variable in einer anderen Funktion zugegriffen werden. Diese Situation ist in jedem Fall zu vermeiden. |
Wichtigkeit | Hoch |
Beispiel:
Methode FB_Sample.SampleMethod:
METHOD SampleMethod : XWORD
VAR
fVar : LREAL;
END_VAR
SampleMethod := ADR(fVar);
Programm MAIN:
PROGRAM MAIN
VAR
nReturn : XWORD;
fbSample : FB_Sample;
END_VAR
nReturn := fbSample.SampleMethod(); // => SA0021
SA0022: (Möglicherweise) nicht zugewiesene Rückgabewerte
Funktion | Ermittelt alle Funktionen und Methoden, die einen Ausführungsstrang ohne Zuweisung auf den Rückgabewert enthalten. |
Begründung | Ein nicht zugewiesener Rückgabewert in einer Funktion oder Methode deutet auf fehlenden Code hin. Auch wenn der Rückgabewert in jedem Fall einen Standardwert hat, ist es immer sinnvoll, diesen nochmal explizit zuzuweisen, um Unklarheiten zu vermeiden. |
Wichtigkeit | Mittel |
Beispiel:
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: Komplexe Rückgabewerte
Funktion | Ermittelt komplexe Rückgabewerte, die mit einer einfachen Registerkopie des Prozessors nicht zurückgegeben werden können. Dazu gehören Strukturen und Arrays sowie Rückgabewerte vom Typ STRING (unabhängig von der Größe des belegten Speicherplatzes). |
Begründung | Dies ist eine Performance-Warnung. Wenn große Werte als Ergebnis einer Funktion, Methode oder einer Eigenschaft zurückgeliefert werden, dann werden diese vom Prozessor bei der Ausführung des Codes mehrfach umkopiert. Das kann zu Laufzeitproblemen führen und sollte wenn möglich vermieden werden. Eine bessere Performance wird erreicht, wenn ein strukturierter Wert als VAR_IN_OUT an eine Funktion oder Methode übergeben wird und in der Funktion oder Methode gefüllt wird. |
Wichtigkeit | Mittel |
Beispiel:
Struktur ST_Sample:
TYPE ST_Sample :
STRUCT
n1 : INT;
n2 : BYTE;
END_STRUCT
END_TYPE
Beispielfunktionen mit Rückgabewert:
FUNCTION F_MyFunction1 : I_MyInterface // no error
FUNCTION F_MyFunction2 : ST_Sample // => SA0023
FUNCTION F_MyFunction3 : ARRAY[0..1] OF BOOL // => SA0023
SA0024: Nicht getypte Literale
Funktion | Ermittelt nicht getypte Literale/Konstanten (z.B. |
Begründung | TwinCAT vergibt die Typen für Literale je nach ihrer Verwendung. In einigen Fällen kann dies zu unerwarteten Situationen führen, die besser über ein getyptes Literal geklärt werden. Zum Beispiel:
|
Wichtigkeit | Niedrig |
Beispiel:
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: Unqualifizierte Enumerationskonstanten
Funktion | Ermittelt Aufzählungskonstanten, die nicht mit einem qualifizierten Namen verwendet werden, d.h. ohne dass der Name der Enumeration vorangestellt ist. |
Begründung | Qualifizierte Zugriffe machen den Code besser lesbar und besser wartbar. Ohne Erzwingen qualifizierter Variablennamen könnte bei Erweiterung des Programms eine weitere Enumeration eingefügt werden, die eine gleichnamige Konstante wie die einer bereits existierenden Enumeration enthält (siehe im Beispiel unten: “eRed”). Dann käme es zu einem nicht-eindeutigen Zugriff in diesem Codestück. Wir empfehlen in jedem Fall nur Enumerationen zu verwenden, die das {attribute ‘qualified-only’} tragen. |
Wichtigkeit | Mittel |
Beispiel:
Enumeration E_Color:
TYPE E_Color :
(
eRed,
eGreen,
eBlue
);
END_TYPE
Programm MAIN:
PROGRAM MAIN
VAR
eColor : E_Color;
END_VAR
eColor := E_Color.eGreen; // no error
eColor := eGreen; // => SA0025
SA0026: Möglicherweise Abschneiden von Strings
Funktion | Ermittelt String-Zuweisungen und -Initialisierungen, die keine ausreichende String-Länge verwenden. |
Begründung | Wenn Strings unterschiedlicher Länge zugewiesen werden, dann wird möglicherweise ein String abgeschnitten. Das Ergebnis ist dann nicht das erwartete. |
Wichtigkeit | Mittel |
Beispiele:
PROGRAM MAIN
VAR
sVar1 : STRING[10];
sVar2 : STRING[6];
sVar3 : STRING[6] := 'abcdefghi'; // => SA0026
END_VAR
sVar2 := sVar1; // => SA0026
SA0027: Mehrfachverwendung des Namens
Funktion | Ermittelt die Mehrfachverwendung eines Namens/Bezeichners einer Variable oder eines Objekts (POU) innerhalb des Gültigkeitsbereichs eines Projekts. Die folgenden Fälle werden abgedeckt:
|
Begründung | Gleiche Namen können beim Lesen des Codes verwirrend sein. Sie können zu Fehlern führen, wenn unbeabsichtigt auf das falsche Objekt zugegriffen wird. Definieren und befolgen Sie deshalb Namenskonventionen zur Vermeidung solcher Situationen. |
Ausnahme | Enumerationen, die mit dem Attribut 'qualified_only' deklariert sind, sind von der SA0027-Prüfung ausgenommen, da auf ihre Elemente nur qualifiziert zugegriffen werden kann. |
Wichtigkeit | Mittel |
Beispiel:
Das folgende Beispiel erzeugt Fehler/Warnung SA0027, da die Bibliothek Tc2_Standard im Projekt eingebunden ist, welche den Funktionsbaustein TON zur Verfügung stellt.
PROGRAM MAIN
VAR
ton : INT; // => SA0027
END_VAR
SA0028: Überlappende Speicherbereiche
Funktion | Ermittelt die Stellen, durch die zwei oder mehr Variablen denselben Speicherplatz belegen. |
Begründung | Wenn zwei Variablen auf dem gleichen Speicherplatz liegen, dann kann sich der Code sehr unerwartet verhalten. Dies ist in jedem Fall zu vermeiden. Wenn es unumgänglich ist, einen Wert in verschiedenen Interpretationen zu verwenden, zum Beispiel einmal als DINT und einmal als REAL, dann sollten Sie eine UNION definieren. Auch über einen Pointer können Sie auf einen Wert anders getypt zugreifen, ohne dass der Wert umgewandelt wird. |
Wichtigkeit | Hoch |
Beispiel:
In dem folgenden Beispiel verwenden beide Variablen Byte 21, d.h. die Speicherbereiche der Variablen überlappen.
PROGRAM MAIN
VAR
nVar1 AT%QB21 : INT; // => SA0028
nVar2 AT%QD5 : DWORD; // => SA0028
END_VAR
SA0029: Notation in Implementierung und Deklaration unterschiedlich
Funktion | Ermittelt die Codestellen (in der Implementierung), an denen sich die Notation eines Bezeichners zur Notation in dessen Deklaration unterscheidet. |
Begründung | Die Norm IEC 61131-3 definiert Bezeichner als nicht case-sensitiv. Das heißt, eine Variable die als “varx” deklariert wurde, kann im Code auch als “VaRx” verwendet werden. Dies ist jedoch verwirrend und irreführend und sollte daher vermieden werden. |
Wichtigkeit | Mittel |
Beispiele:
Funktion F_TEST:
FUNCTION F_TEST : BOOL
…
Programm MAIN:
PROGRAM MAIN
VAR
nVar : INT;
bReturn : BOOL;
END_VAR
nvar := nVar + 1; // => SA0029
bReturn := F_Test(); // => SA0029
SA0031: Nicht verwendete Signaturen
Funktion | Ermittelt Programme, Funktionsbausteine, Funktionen, Datentypen, Schnittstellen, Methoden, Eigenschaften, Aktionen etc., die innerhalb des kompilierten Programmcodes nicht aufgerufen werden. |
Begründung | Nicht verwendete Objekte vergrößern das Projekt unnötig und können beim Lesen des Codes verwirren. |
Wichtigkeit | Niedrig |
PLCopen-Regel | CP2 |
SA0032: Nicht verwendete Aufzählungskonstante
Funktion | Ermittelt Enumerationskonstanten, die nicht im kompilierten Programmcode verwendet werden. |
Begründung | Nicht verwendete Enumerationskonstanten vergrößern die Enumerationsdefinition unnötig und können beim Lesen des Programms verwirren. |
Wichtigkeit | Niedrig |
PLCopen-Regel | CP24 |
Beispiel:
Enumeration E_Sample:
TYPE E_Sample :
(
eNull,
eOne, // => SA0032
eTwo
);
END_TYPE
Programm MAIN:
PROGRAM MAIN
VAR
eSample : E_Sample;
END_VAR
eSample := E_Sample.eNull;
eSample := E_Sample.eTwo;
SA0033: Nicht verwendete Variablen
Funktion | Ermittelt Variablen, die deklariert sind, aber innerhalb des kompilierten Programmcodes nicht verwendet werden. |
Begründung | Nicht verwendete Variablen machen ein Programm weniger gut lesbar und wartbar. Nicht verwendete Variablen belegen unnötig Speicher und kosten bei der Initialisierung unnötig Laufzeit. |
Wichtigkeit | Mittel |
PLCopen-Regel | CP22/CP24 |
SA0035: Nicht verwendete Eingabevariablen
Funktion | Ermittelt Eingangsvariablen, die innerhalb des jeweiligen Funktionsbausteins nicht zugewiesen werden. |
Begründung | Nicht verwendete Variablen machen ein Programm weniger gut lesbar und wartbar. Nicht verwendete Variablen belegen unnötig Speicher und kosten bei der Initialisierung unnötig Laufzeit. |
Wichtigkeit | Mittel |
PLCopen-Regel | CP24 |
Beispiel:
Funktionsbaustein 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: Nicht verwendete Ausgabevariablen
Funktion | Ermittelt Ausgangsvariablen, die innerhalb des jeweiligen Funktionsbausteins nicht zugewiesen werden. |
Begründung | Nicht verwendete Variablen machen ein Programm weniger gut lesbar und wartbar. Nicht verwendete Variablen belegen unnötig Speicher und kosten bei der Initialisierung unnötig Laufzeit. |
Wichtigkeit | Mittel |
PLCopen-Regel | CP24 |
Beispiel:
Funktionsbaustein 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: Enumerationsvariablen mit falscher Zuweisung
Funktion | Ermittelt Werte, die einer Enumerationsvariablen zugewiesen sind. Einer Enumerationsvariablen dürfen nur definierte Enumerationskonstanten zugewiesen werden. |
Begründung | Eine Variable vom Typ einer Enumeration sollte auch nur die vorgesehenen Werte haben, anderfalls funktioniert Code, der diese Variable verwendet möglicherweise nicht richtig. Wir empfehlen, Enumerationen immer mit dem {attribute 'strict'} zu verwenden. Dann prüft bereits der Compiler die korrekte Verwendung der Enumerationskomponenten. |
Wichtigkeit | Hoch |
Beispiel:
Enumeration E_Color:
TYPE E_Color :
(
eRed := 1,
eBlue := 2,
eGreen := 3
);
END_TYPE
Programm MAIN:
PROGRAM MAIN
VAR
eColor : E_Color;
END_VAR
eColor := E_Color.eRed;
eColor := eBlue;
eColor := 1; // => SA0034
SA0037: Schreibzugriff auf Eingabevariable
Funktion | Ermittelt Eingangsvariablen (VAR_INPUT), auf die innerhalb der POU schreibend zugegriffen wird. |
Begründung | Nach der Norm IEC 61131-3 darf eine Eingabevariable nicht innerhalb eines Bausteins verändert werden. Ein solcher Zugriff ist außerdem eine Fehlerquelle und macht den Code schlecht wartbar. Es weist daraufhin, dass eine Variable als Eingang und gleichzeitig als Hilfsvariable verwendet wird. Eine solche Doppelverwendung sollte vermieden werden. |
Wichtigkeit | Mittel |
Beispiel:
Funktionsbaustein FB_Sample:
FUNCTION_BLOCK FB_Sample
VAR_INPUT
bIn : BOOL := TRUE;
nIn : INT := 100;
END_VAR
VAR_OUTPUT
bOut : BOOL;
END_VAR
Methode FB_Sample.SampleMethod:
IF bIn THEN
nIn := 500; // => SA0037
bOut := TRUE;
END_IF
SA0038: Lesezugriff auf Ausgabevariable
Funktion | Ermittelt Ausgangsvariablen (VAR_OUTPUT), auf die innerhalb der POU lesend zugegriffen wird. |
Begründung | Nach 61131-3 ist es verboten, einen Ausgang innerhalb eines Bausteins zu lesen. Es weist darauf hin, dass der Ausgang nicht nur als Ausgang sondern gleichzeitig als temporäre Variable für Zwischenergebnisse verwendet wird. Eine solche Doppelverwendung sollte vermieden werden. |
Wichtigkeit | Niedrig |
Beispiel:
Funktionsbaustein FB_Sample:
FUNCTION_BLOCK FB_Sample
VAR_OUTPUT
bOut : BOOL;
nOut : INT;
END_VAR
VAR
bLocal : BOOL;
nLocal : INT;
END_VAR
Methode FB_Sample.SampleMethod:
IF bOut THEN // => SA0038
bLocal := (nOut > 100); // => SA0038
nLocal := nOut; // => SA0038
nLocal := 2*nOut; // => SA0038
END_IF
SA0040: Mögliche Division durch Null
Funktion | Ermittelt Codestellen, an denen möglicherweise durch Null dividiert wird. |
Begründung | Eine Division durch 0 ist nicht erlaubt. Eine Variable durch die dividiert wird, sollte immer vorher auf 0 überprüft werden. Andernfalls kann es zu einer "Divide by Zero"-Exception zur Laufzeit kommen. |
Wichtigkeit | Hoch |
Beispiel:
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: Möglicherweise schleifeninvarianter Code
Funktion | Ermittelt möglicherweise schleifeninvarianten Code, d.h. Code innerhalb einer FOR-, WHILE- oder REPEAT-Schleife, der bei jedem Schleifendurchlauf zum gleichen Ergebnis führt, also unnötigerweise immer wieder ausgeführt wird. Dabei werden nur Berechnungen berücksichtigt, keine einfachen Zuweisungen. |
Begründung | Dies ist eine Performance-Warnung. Code, der in einer Schleife ausgeführt wird, aber in jedem Schleifendurchlauf das gleiche tut, kann außerhalb der Schleife durchgeführt werden. |
Wichtigkeit | Mittel |
Beispiel:
Im folgenden Beispiel wird SA0041 als Fehler/Warnung ausgegeben, da die Variablen nTest1 und nTest2 in der Schleife nicht verwendet werden.
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: Verwendung unterschiedlicher Zugriffspfade
Funktion | Ermittelt die Verwendung unterschiedlicher Zugriffspfade für die gleiche Variable. |
Begründung | Unterschiedlicher Zugriff auf das gleiche Element reduziert die Lesbarkeit und Wartbarkeit eines Programms. Wir empfehlen die konsequente Verwendung von {attribute 'qualified-only'} für Bibliotheken, globale Variablenlisten und Enumerationen. Dadurch wird der vollqualifizierte Zugriff erzwungen. |
Wichtigkeit | Niedrig |
Beispiele:
Im folgenden Beispiel wird SA0042 als Fehler/Warnung ausgegeben, da auf die globale Variable nGlobal einmal direkt und einmal über den GVL-Namensraum zugegriffen wird und da auf die Funktion CONCAT einmal direkt und einmal über den Bibliotheksnamensraum zugegriffen wird.
Globale Variablen:
VAR_GLOBAL
nGlobal : INT;
END_VAR
Programm 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: Verwendung einer globalen Variablen in nur 1 POU
Funktion | Ermittelt globale Variablen, die nur in einer einzigen POU verwendet werden. |
Begründung | Eine globale Variable, die nur an einer Stelle verwendet wird, sollte auch an dieser einen Stelle deklariert sein. |
Wichtigkeit | Mittel |
PLCopen-Regel | CP26 |
Beispiel:
Die globale Variable nGlobal1 wird nur im Programm MAIN verwendet.
Globale Variablen:
VAR_GLOBAL
nGlobal1 : INT; // => SA0043
nGlobal2 : INT;
END_VAR
Programm SubProgram:
nGlobal2 := 123;
Programm MAIN:
SubProgram();
nGlobal1 := nGlobal2;
SA0044: Deklarationen mit Schnittstellenreferenz
Funktion | Ermittelt Deklarationen mit REFERENCE TO <Schnittstelle> und Deklarationen von VAR_IN_OUT-Variablen mit dem Typ einer Schnittstelle (implizit über REFERENCE TO realisiert). |
Begründung | Ein Schnittstellentyp ist immer implizit eine Referenz auf eine Instanz eines Funktionsbausteins, der diese Schnittstelle implementiert. Eine Referenz auf eine Schnittstelle ist demnach eine Referenz auf eine Referenz und kann zu sehr unerwünschtem Verhalten führen. |
Wichtigkeit | Hoch |
Beispiele:
I_Sample ist eine im Projekt definierte Schnittstelle.
Funktionsbaustein 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
Programm MAIN:
PROGRAM MAIN
VAR
fbSample : FB_Sample;
iSample : I_Sample;
refItf : REFERENCE TO I_Sample; // => SA0044
END_VAR
SA0019: Implizite Pointer-Konvertierungen
Funktion | Ermittelt implizit erzeugte Pointer-Datentyp-Konvertierungen. |
Begründung | Pointer sind in TwinCAT nicht streng getypt und können einander beliebig zugewiesen werden. Dies wird häufig genutzt und deswegen auch nicht vom Compiler gemeldet. Allerdings kann es dadurch auch ungewollt zu unerwarteten Zugriffen kommen. Wenn ein POINTER TO BYTE einem POINTER TO DWORD zugewiesen wird, ist es möglich, dass über den letzten Pointer ungewollt Speicher überschrieben wird. Lassen Sie diese Regel deshalb in jedem Fall prüfen und unterdrücken Sie die Meldung nur in den Fällen, in denen Sie bewusst anders getypt auf einen Wert zugreifen wollen. Implizite Datentyp-Konvertierungen werden mit einer anderen Meldung gemeldet. |
Ausnahme | BOOL ↔ BIT |
Wichtigkeit | Hoch |
PLCopen-Regel | CP25 |
Beispiele:
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: Implizite erweiternde Konvertierungen
Funktion | Ermittelt implizit durchgeführte Konvertierungen von kleineren auf größere Datentypen. |
Begründung | Der Compiler erlaubt jegliche Zuweisungen von unterschiedlichen Typen, wenn der Wertebereich des Quelltyps vollständig im Wertebereich des Zieltyps enthalten ist. Allerdings baut der Compiler eine Konvertierung so spät wie möglich in den Code ein. Bei einer Zuweisung der folgenden Art:
führt der Compiler die implizite Konvertierung erst nach der Multiplikation durch:
Ein Überlauf wird daher abgeschnitten. Wenn Sie das verhindern wollen, können Sie die Konvertierung bereits für die Elemente durchführen lassen:
Es kann daher sinnvoll sein, sich Stellen melden zu lassen, an denen der Compiler implizite Konvertierungen einbaut, um zu prüfen, ob diese genau so gewollt sind. Außerdem können explizite Konvertierungen zu besserer Portierbarkeit auf andere Systeme dienen, wenn diese restriktivere Typprüfungen haben. |
Ausnahme | BOOL ↔ BIT |
Wichtigkeit | Niedrig |
Beispiele:
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: Implizite einengende Konvertierungen
Funktion | Ermittelt implizit durchgeführte Konvertierungen von größeren auf kleinere Datentypen. |
Ausnahme | BOOL ↔ BIT |
Wichtigkeit | Niedrig |
Diese Meldung ist mittlerweile obsolet, weil sie bereits vom Compiler als Warnung gemeldet wird. |
Beispiel:
PROGRAM MAIN
VAR
fREAL : REAL;
fLREAL : LREAL;
END_VAR
fREAL := fLREAL; // => SA0131
nBYTE.5 := FALSE; // no error (BIT-BOOL-conversion)
SA0132: Implizite vorzeichenbehaftete/vorzeichenlose Konvertierungen
Funktion | Ermittelt implizit durchgeführte Konvertierungen von vorzeichenbehafteten auf vorzeichenlose Datentypen oder umgekehrt. |
Wichtigkeit | Niedrig |
Diese Meldung ist mittlerweile obsolet, weil sie bereits vom Compiler als Warnung gemeldet wird. |
Beispiele:
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: Explizite einschränkende Konvertierungen
Funktion | Ermittelt explizit durchgeführte Konvertierungen von einem größeren auf einen kleineren Datentyp. |
Begründung | Eine große Zahl von Typkonvertierungen kann bedeuten, dass falsche Datentypen für Variablen gewählt wurden. Es gibt daher Programmierrichtlinien, die eine explizite Begründung für Datentypkonvertierungen fordern. |
Wichtigkeit | Niedrig |
Beispiele:
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: Explizite vorzeichenbehaftete/vorzeichenlose Konvertierungen
Funktion | Ermittelt explizit durchgeführte Konvertierungen von vorzeichenbehafteten auf vorzeichenlose Datentypen oder umgekehrt. |
Begründung | Ein übermäßiger Gebrauch von Typkonvertierungen kann bedeuten, dass falsche Datentypen für Variablen gewählt wurden. Es gibt daher Programmierrichtlinien, die eine explizite Begründung für Datentypkonvertierungen fordern. |
Wichtigkeit | Niedrig |
Beispiele:
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: Ungültige Adressen und Datentypen
Funktion | Ermittelt ungültige Adress- und Datentypspezifikationen. Für Adressen sind die folgenden Größenpräfixe gültig. Abweichungen davon führen zu einer ungültigen Adressspezifikation.
|
Begründung | Variablen, die auf direkten Adressen liegen, sollten möglichst mit einer Adresse assoziiert werden, die ihrer Datentypbreite entspricht. Es kann für den Leser des Codes zur Verwirrung führen, wenn beispielsweise ein DWORD auf eine BYTE-Adresse gelegt wird. |
Wichtigkeit | Niedrig |
Mit den empfohlenen Platzhaltern %I* oder %Q* wird eine flexible und optimierte Adressierung von TwinCAT automatisch durchgeführt. |
Beispiele:
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: Zugriff auf direkte Adressen
Funktion | Ermittelt direkte Adresszugriffe im Implementierungscode. |
Begründung | Symbolische Programmierung ist immer zu bevorzugen: Eine Variable hat einen Namen, der auch eine Bedeutung tragen kann. Einer Adresse ist nicht ansehbar, wofür diese verwendet wird. |
Wichtigkeit | Hoch |
PLCopen-Regel | N1/CP1 |
Beispiele:
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-Deklarationen auf direkte Adressen
Funktion | Ermittelt AT-Deklarationen auf direkte Adressen. |
Begründung | Die Verwendung von direkten Adressen im Code ist eine Fehlerquelle und führt zu schlechterer Lesbarkeit und Wartbarkeit des Codes. Daher wird die Verwendung der Platzhalter %I* oder %Q* empfohlen, bei denen TwinCAT eine flexible und optimierte Adressierung automatisch durchführt. |
Wichtigkeit | Hoch |
PLCopen-Regel | N1/CP1 |
Beispiele:
PROGRAMM MAIN
VAR
b1 AT%IX0.0 : BOOL; // => SA0048
b2 AT%I* : BOOL; // no error
END_VAR
SA0051: Vergleichsoperationen auf BOOL-Variablen
Funktion | Ermittelt Vergleichsoperationen auf Variablen vom Typ BOOL. |
Begründung | TwinCAT erlaubt solche Vergleiche, diese sind aber zumindest sehr unüblich und können verwirrend sein. Die Norm IEC-61131-3 sieht diese Vergleiche nicht vor, daher sollten Sie sie vermeiden. |
Wichtigkeit | Mittel |
Beispiel:
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: Unübliche Schiebeoperation
Funktion | Ermittelt Schiebeoperationen (Bit-Shift) auf vorzeichenbehaftete Variablen. Die Norm IEC 61131-3 erlaubt allerdings nur Schiebeoperationen auf Bitfelder. Sehen Sie hierzu auch die strikte Regel SA0147. |
Begründung | TwinCAT erlaubt Schiebeoperationen auf vorzeichenbehafteten Datentypen. Diese Operationen sind aber unüblich und können verwirrend sein. Die Norm IEC-61131-3 sieht solche Operationen nicht vor, daher sollten Sie sie vermeiden. |
Ausnahme | Im Falle von Schiebeoperationen auf Bitfeld-Datentypen (Byte, DWORD, LWORD, WORD) wird kein Fehler SA0052 ausgegeben. |
Wichtigkeit | Mittel |
Beispiele:
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: Zu große bitweise Verschiebung
Funktion | Ermittelt bei bitweiser Verschiebung (Bitverschiebung/Bit-Shift) von Operanden, ob dessen Datentyp-Breite überschritten wurde. |
Begründung | Wenn eine Verschiebeoperation über die Datentypbreite hinausgeht, wird eine Konstante 0 erzeugt. Wenn eine Rotationsverschiebung über die Datentypbreite hinausgeht, dann ist das schwer zu lesen und der Rotationswert sollte deswegen gekürzt werden. |
Wichtigkeit | Hoch |
Beispiele:
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: Vergleich von REAL/LREAL auf Gleichheit/Ungleichheit
Funktion | Ermittelt, wo die Vergleichsoperatoren = (Gleichzeit) und <> (Ungleichheit) Operanden vom Typ REAL oder LREAL vergleichen. |
Begründung | REAL/LREAL-Werte werden als Gleitzahlen nach dem Standard IEEE 754 implementiert. Dieser Standard bringt es mit sich, dass bestimmte scheinbar einfache Dezimalzahlen nicht exakt dargestellt werden können. Das hat zur Folge, dass es für dieselbe Dezimalzahl unterschiedliche Repräsentationen als LREAL geben kann. Beispiel:
bTest wird in diesem Fall FALSE liefern, auch wenn die Variablen fLREAL_a und fLREAL_b beide den Monitoring-Wert “2.2” liefern. Das ist kein Fehler des Compilers, sondern eine Eigenschaft der Gleitkomma-Einheiten aller üblichen Prozessoren. Vermeiden können Sie das, indem Sie einen Mindestwert angeben, um den sich die Werte unterscheiden dürfen:
|
Ausnahme | Ein Vergleich mit 0.0 wird nicht von dieser Analyse gemeldet. Für die 0 gibt es im Standard IEEE 754 eine exakte Darstellung und daher funktioniert der Vergleich normalerweise wie erwartet. Für eine bessere Performance ist es daher sinnvoll, hier einen direkten Vergleich zuzulassen. |
Wichtigkeit | Hoch |
PLCopen-Regel | CP54 |
Beispiele:
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: Unnötige Vergleichsoperationen von vorzeichenlosen Operanden
Funktion | Ermittelt unnötige Vergleiche mit vorzeichenlosen Operanden. Ein vorzeichenloser Datentyp ist nie kleiner Null. |
Begründung | Ein mit dieser Prüfung aufgedeckter Vergleich liefert ein konstantes Ergebnis und das deutet auf einen Fehler im Code hin. |
Wichtigkeit | Hoch |
Beispiele:
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: Konstante außerhalb des gültigen Bereichs
Funktion | Ermittelt Literale (Konstanten) außerhalb des für den Operator gültigen Bereichs. |
Begründung | Die Meldung wird für Fälle ausgegeben, in denen eine Variable mit einer Konstanten verglichen wird, die außerhalb des Wertebereichs dieser Variablen liegt. Der Vergleich liefert dann konstant TRUE oder FALSE. Dies deutet auf einen Programmierfehler hin. |
Wichtigkeit | Hoch |
Beispiele:
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: Möglicher Verlust von Nachkommastellen
Funktion | Ermittelt Anweisungen mit möglichem Verlust von Dezimalstellen. |
Begründung | Ein Codestück der folgenden Art:
kann zu einer Fehlinterpretation führen. Diese Codezeile kann zu der Annahme führen, die Division würde als REAL-Operation durchgeführt und das Ergebnis würde in diesem Fall REAL#0.5 sein. Dies ist jedoch nicht der Fall, die Operation wird als Integer-Operation durchgeführt, das Ergebnis wird auf REAL gecastet und fREAL erhält den Wert REAL#0. Um dies zu vermeiden, sollten Sie durch einen Cast dafür sorgen, dass die Operation als REAL-Operation durchgeführt wird:
|
Wichtigkeit | Mittel |
Beispiele:
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: Operation auf Enumerationsvariablen
Funktion | Ermittelt Operationen auf Variablen vom Typ einer Enumeration. Zuweisungen sind erlaubt. |
Begründung | Enumerationen sollten nicht als normale Integer-Werte verwendet werden. Alternativ kann ein Alias-Datentyp definiert oder einen Subrange-Typ verwendet werden. |
Ausnahme | Wenn eine Enumeration mit dem Attribut {attribute 'strict'} gekennzeichnet ist, dann meldet bereits der Compiler eine solche Operation. Wenn eine Enumeration mit Hilfe des Pragmaattributs {attribute 'flags'} als Flag deklariert ist, wird für Operationen mit AND, OR, NOT, XOR kein Fehler SA0058 ausgegeben. |
Wichtigkeit | Mittel |
Beispiel 1:
Enumeration E_Color:
TYPE E_Color :
(
eRed := 1,
eBlue := 2,
eGreen := 3
);
END_TYPE
Programm 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
Beispiel 2:
Enumeration E_State mit Attribut 'flags':
{attribute 'flags'}
TYPE E_State :
(
eUnknown := 16#00000001,
eStopped := 16#00000002,
eRunning := 16#00000004
) DWORD;
END_TYPE
Programm 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: Vergleichsoperationen, die immer TRUE oder FALSE liefern
Funktion | Ermittelt Vergleiche mit Literalen, die immer das Ergebnis TRUE bzw. FALSE haben und die bereits während der Kompilierung ausgewertet werden können. |
Begründung | Eine Operation, die konstant TRUE oder FALSE liefert, ist ein Hinweis auf einen Programmierfehler. |
Wichtigkeit | Hoch |
Beispiele:
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: Null als ungültiger Operand
Funktion | Ermittelt Operationen, in denen ein Operand mit Wert 0 zu einer ungültigen und nicht sinnvollen Operation führt. |
Begründung | Ein solcher Ausdruck kann auf einen Programmierfehler hindeuten. In jedem Fall kostet er unnötig Laufzeit. |
Wichtigkeit | Mittel |
Beispiele:
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: Unübliche Operation auf Pointer
Funktion | Ermittelt Operationen auf Variablen vom Typ POINTER TO, die nicht = (Gleichheit), <> (Ungleichheit), + (Addition) oder ADR sind. |
Begründung | In TwinCAT ist Pointer-Arithmetik grundsätzlich erlaubt und kann auch sinnvoll eingesetzt werden. Als übliche Operation auf Pointer wird daher die Addition eines Pointers mit einem Integerwert eingestuft. Damit ist es möglich, mit Hilfe eines Pointers ein Array mit variabler Länge zu bearbeiten. Alle anderen (unüblichen) Operationen mit Pointer werden mit SA0061 gemeldet. |
Wichtigkeit | Hoch |
PLCopen-Regel | E2/E3 |
Beispiele:
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: Verwendung von TRUE oder FALSE in Ausdrücken
Funktion | Ermittelt die Verwendung der Literale TRUE bzw. FALSE in Ausdrücken (z.B. |
Begründung | Ein solcher Ausdruck ist offensichtlich unnötig und kann auf einen Fehler hindeuten. In jedem Fall belastet ein solcher Ausdruck unnötig die Lesbarkeit und ggf. auch die Laufzeit. |
Wichtigkeit | Mittel |
Beispiele:
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: Möglicherweise nicht 16-bitkompatible Operationen
Funktion | Ermittelt 16-Bit-Operationen mit Zwischenergebnissen. Hintergrund: Auf 16-Bit-Systemen können 32-Bit-Zwischenergebnisse abgeschnitten werden. |
Begründung | Diese Meldung soll in dem sehr seltenen Fall vor Problemen schützen, wenn Code geschrieben wird, der sowohl auf einem 16-Bit-Prozessor als auch auf einem 32-Bi- Prozessor laufen soll. |
Wichtigkeit | Niedrig |
Beispiel:
(nVar+10) kann 16 Bit überschreiten.
PROGRAM MAIN
VAR
nVar : INT;
END_VAR
nVar := (nVar + 10) / 2; // => SA0063
SA0064: Addition eines Pointers
Funktion | Ermittelt alle Additionen von Pointern. |
Begründung | In TwinCAT ist Pointer-Arithmetik grundsätzlich erlaubt und kann auch sinnvoll eingesetzt werden. Allerdings ist es auch eine Fehlerquelle. Deswegen gibt es Programmiervorschriften, die Pointer-Arithmetik grundsätzlich verbieten. Eine solche Vorschrift kann mit diesem Test überprüft werden. |
Wichtigkeit | Mittel |
Beispiele:
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: Pointer-Addition passt nicht zur Basisgröße
Funktion | Ermittelt Pointer-Additionen, bei denen der zu addierende Wert nicht zur Basis-Datengröße des Pointers passt. Nur Literale der Basisgröße dürfen addiert werden. Es dürfen auch keine Multiplikationen der Basisgröße addiert werden. |
Begründung | In TwinCAT (im Gegensatz zu C und C++) wird bei einer Addition eines Pointers mit einem Integerwert nur dieser Integerwert als Anzahl der Bytes addiert, und nicht der Integerwert mit der Basisgröße multipliziert. Zum Beispiel in ST:
Dieser Code würde in C anders funktionieren:
Daher sollte in TwinCAT immer ein Vielfaches der Basisgröße des Pointers zu einem Pointer addiert werden. Andernfalls zeigt der Pointer möglicherweise auf einen nicht-alignten Speicher, was (je nach Prozessor) beim Zugriff zu einer "Alignment"-Exception führen kann. |
Wichtigkeit | Hoch |
Beispiele:
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: Verwendung von Zwischenergebnissen
Funktion | Ermittelt Verwendungen von Zwischenergebnissen in Anweisungen mit einem Datentyp, der kleiner als die Registergröße ist. Der implizite Cast in diesem Fall führt gegebenenfalls zu unerwünschten Ergebnissen. |
Begründung | TwinCAT führt aus Performancegründen Operationen auf der Registerbreite des Prozessors aus. Zwischenergebnisse werden nicht abgeschnitten! Das kann zu Fehlinterpretationen führen, wie im folgenden Fall:
In TwinCAT ist bError in diesem Fall TRUE, weil die Operation usintTest - 1 typischerweise als 32-Bit-Operation ausgeführt wird und das Ergebnis nicht auf die Größe von Byte gecastet wird. Im Register steht dann der Wert 16#ffffffff und dieser ist ungleich 255.Um dies zu umgehen müssen Sie das Zwischenergebnis explizit casten:
|
Wichtigkeit | Niedrig |
Wenn diese Meldung aktiviert ist, werden sehr viele eher unproblematische Stellen im Code gemeldet werden. Ein Problem kann zwar nur entstehen, wenn die Operation einen Überlauf oder Unterlauf im Datentyp produziert, die statische Analyse kann dies aber für die einzelnen Stellen nicht differenziert erkennen. Wenn Sie an allen gemeldeten Stellen einen expliziten Typcast einbauen, dann wird der Code deutlich langsamer und unleserlicher! |
Beispiel:
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: Ungültige Verwendung einer Zählervariablen
Funktion | Ermittelt Schreibzugriffe auf eine Zählervariablen innerhalb einer FOR-Schleife. |
Begründung | Eine Manipulation der Zählervariablen in einer FOR-Schleife kann leicht zu einer Endlosschleife führen. Um die Ausführung der Schleife für bestimmte Werte der Zählervariablen zu unterbinden, arbeiten Sie mit CONTINUE oder einfach mit einem IF. |
Wichtigkeit | Hoch |
PLCopen-Regel | L12 |
Beispiel:
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: Verwendung einer nicht-temporären Zählervariablen
Funktion | Ermittelt die Verwendung von nicht-temporären Variablen in FOR-Schleifen. |
Begründung | Dies ist eine Performance-Warnung. Eine Zählervariable wird in jedem Fall bei jedem Aufruf eines Programmierbausteins initialisiert. Sie können eine solche Variable als temporäre Variable (VAR_TEMP) anlegen, ein Zugriff darauf ist unter Umständen schneller und die Variable belegt keinen dauerhaften Speicherplatz. |
Wichtigkeit | Mittel |
PLCopen-Regel | CP21/L13 |
Beispiel:
PROGRAM MAIN
VAR
nIndex : INT;
nSum : INT;
END_VAR
FOR nIndex := 1 TO 10 BY 1 DO // => SA0073
nSum := nSum + nIndex;
END_FOR
SA0080: Schleifenindexvariable für Arrayindex überschreitet Array-Bereich
Funktion | Ermittelt FOR-Anweisungen, bei denen die Indexvariable für den Zugriff auf einen Array-Index verwendet wird und den Bereich des Array-Index überschreitet. |
Begründung | Arrays werden typischerweise in FOR-Schleifen bearbeitet. Anfangs- und Endwert der Zählervariable sollte dabei typischerweise mit der Unter- und Obergrenze des Arrays übereinstimmen oder zumindest diese nicht überschreiten. Hier wird eine typische Fehlerursache erkannt, wenn Arraygrenzen geändert werden und nicht sorgfältig mit Konstanten gearbeitet wird, oder wenn aus Versehen in der FOR-Schleife ein anderer Wert verwendet wird als bei der Array-Deklaration. |
Wichtigkeit | Hoch |
Beispiele:
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: Obergrenze ist kein konstanter Wert
Funktion | Ermittelt FOR-Anweisungen, bei denen die Obergrenze nicht mit einem konstanten Wert definiert ist. |
Begründung | Wenn die Obergrenze einer Schleife ein variabler Wert ist, dann lässt sich nicht mehr erkennen, wie oft eine Schleife ausgeführt wird. Dies kann zur Laufzeit zu gravierenden Problemen führen, im schlimmsten Fall zu einer Endlosschleife. |
Wichtigkeit | Hoch |
Beispiele:
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: Fehlendes ELSE
Funktion | Ermittelt CASE-Anweisungen ohne ELSE-Zweig. |
Begründung | Defensive Programmierung fordert das Vorhandensein eines ELSE in jeder CASE-Anweisung. Wenn im ELSE-Fall nichts zu tun ist, dann sollten Sie dies durch einen Kommentar kennzeichnen. Dem Leser des Codes ist dann klar, dass der Fall nicht einfach vergessen wurde. |
Ausnahme | Ein fehlender ELSE-Zweig wird nicht als fehlend berichtet, wenn in der CASE-Anweisung eine Enumeration verwendet wird, die mit dem Attribut 'strict' deklariert ist, und wenn in dieser CASE-Anweisung alle Enumerationskonstanten aufgeführt sind. |
Wichtigkeit | Niedrig |
PLCopen-Regel | L17 |
Beispiel:
{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: Fehlende Aufzählungskonstante
Funktion | Ermittelt Codepositionen, wo eine Enumerationsvariable als Bedingung verwendet wird und nicht alle Enumerationswerte als CASE-Zweige behandelt sind. |
Begründung | Defensive Programmierung erfordert die Bearbeitung aller möglichen Werte einer Enumeration. Wenn für einen bestimmten Enumerationswert keine Aktion nötig ist, dann sollten Sie dies explizit durch einen Kommentar kennzeichnen. Damit wird deutlich, dass der Wert nicht einfach vergessen wurde. |
Wichtigkeit | Niedrig |
Beispiel:
Im folgenden Beispiel wird der Enumerationswert eYellow nicht als CASE-Zweig behandelt.
Enumeration E_Color:
TYPE E_Color :
(
eRed,
eGreen,
eBlue,
eYellow
);
END_TYPE
Programm 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: Datentypdiskrepanz bei CASE-Ausdruck
Funktion | Ermittelt Codepositionen, wo der Datentyp einer Bedingung nicht mit dem des CASE-Zweigs übereinstimmt. |
Begründung | Wenn die Datentypen zwischen der CASE-Variable und dem CASE-Fall nicht übereinstimmen, dann könnte das auf einen Fehler hindeuten. |
Wichtigkeit | Niedrig |
Beispiel:
Enumeration E_Sample:
TYPE E_Sample :
(
eNull,
eOne,
eTwo
) DWORD;
END_TYPE
Programm 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: CASE-Anweisungen ohne CASE-Zweig
Funktion | Ermittelt CASE-Anweisungen ohne Fälle, d.h. mit nur einer ELSE-Anweisung. |
Begründung | Ein CASE-Statement ohne Fälle kostet nur Zeit in der Ausführung und ist schwer zu lesen. |
Wichtigkeit | Mittel |
Beispiel:
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-Anweisung vor Ende der Funktion
Funktion | Ermittelt Codepositionen, wo die RETURN-Anweisung nicht die letzte Anweisung in einer Funktion, Methode, Eigenschaft oder in einem Programm ist. |
Begründung | Ein RETURN im Code führt zu schlechterer Wartbarkeit, Testbarkeit und Lesbarkeit des Codes. Ein RETURN im Code wird leicht übersehen. Sie müssen Code, der auf alle Fälle beim Austritt einer Funktion ausgeführt werden sollte, vor jedem RETURN einfügen und das wird oft vergessen. |
Wichtigkeit | Mittel |
PLCopen-Regel | CP14 |
Beispiel:
FUNCTION F_TestFunction : BOOL
F_TestFunction := FALSE;
RETURN; // => SA0090
F_TestFunction := TRUE;
SA0095: Zuweisung in Bedingung
Funktion | Ermittelt Zuweisungen in Bedingungen von IF-, CASE-, WHILE- oder REPEAT-Konstrukten. |
Begründung | Ein Zuweisung (:=) und ein Vergleich (=) kann leicht verwechselt werden. Eine Zuweisung in einer Bedingung kann daher leicht unabsichtlich erfolgt sein und wird deswegen gemeldet. Auch der Leser des Codes kann dadurch verwirrt werden. |
Wichtigkeit | Hoch |
Beispiele:
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: Variablen größer als <n> Bytes
Funktion | Ermittelt Variablen, die mehr als n Bytes verwenden, wobei n durch die aktuelle Konfiguration vorgegeben ist. Den Parameter, der bei dieser Prüfung berücksichtigt wird, können Sie konfigurieren, indem Sie innerhalb der Regelkonfiguration auf die Zeile von Regel 100 doppelklicken (SPS-Projekteigenschaften > Kategorie "Static Analysis" > Registerkarte "Regeln" > Regel 100). In dem aufgehenden Dialog können Sie folgende Einstellungen vornehmen:
|
Begründung | Manche Programmierrichtlinien legen eine maximale Größe für eine einzelne Variable fest. Dies kann hiermit überprüft werden. |
Wichtigkeit | Niedrig |
Beispiel:
Im folgenden Beispiel ist die Variable aSample größer als 1024 Bytes.
PROGRAM MAIN
VAR
aSample : ARRAY [0..1024] OF BYTE; // => SA0100
END_VAR
SA0101: Namen mit unzulässiger Länge
Funktion | Ermittelt Namen mit unzulässiger Länge. Der Name von Objekten muss eine definierte Länge haben. Die Parameter, die bei dieser Prüfung berücksichtigt werden, können Sie konfigurieren, indem Sie innerhalb der Regelkonfiguration auf die Zeile von Regel 101 doppelklicken (SPS-Projekteigenschaften > Kategorie "Static Analysis" > Registerkarte "Regeln" > Regel 101). In dem aufgehenden Dialog können Sie folgende Einstellungen vornehmen:
|
Begründung | In manchen Programmierrichtlinien wird eine Mindestlänge für Variablennamen festgelegt. Die Einhaltung kann mit dieser Analyse überprüft werden. |
Wichtigkeit | Niedrig |
PLCopen-Regel | N6 |
Beispiele:
Regel 101 ist mit folgenden Parametern konfiguriert:
- Minimale Anzahl an Zeichen: 5
- Maximale Anzahl an Zeichen: 30
- Ausnahmen: MAIN, i
Programm PRG1:
PROGRAM PRG1 // => SA0101
VAR
END_VAR
Programm 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: Zugriff von außen auf lokale Variablen
Funktion | Ermittelt Zugriffe von außen auf lokale Variablen von Programmen oder Funktionsbausteinen. |
Begründung | TwinCAT ermittelt Schreibzugriffe von außen auf lokale Variablen von Programmen oder Funktionsbausteinen als Kompilierfehler. Da Lesezugriffe auf lokale Variablen nicht vom Compiler abgefangen werden und dies mit dem Grundsatz der Datenkapselung (Verbergen von Daten) bricht und nicht der Norm IEC 61131-3 entspricht, kann diese Regel verwendet werden, um Lesezugriffe auf lokale Variablen zu ermitteln. |
Wichtigkeit | Mittel |
Beispiele:
Funktionsbaustein FB_Base:
FUNCTION_BLOCK FB_Base
VAR
nLocal : INT;
END_VAR
Methode FB_Base.SampleMethod:
METHOD SampleMethod : INT
VAR_INPUT
END_VAR
nLocal := nLocal + 1;
Funktionsbaustein FB_Sub:
FUNCTION_BLOCK FB_Sub EXTENDS FB_Base
Methode FB_Sub.SampleMethod:
METHOD SampleMethod : INT
VAR_INPUT
END_VAR
nLocal := nLocal + 5;
Programm PRG_1:
PROGRAM PRG_1
VAR
bLocal : BOOL;
END_VAR
bLocal := NOT bLocal;
Programm 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: Gleichzeitiger Zugriff auf nicht-atomare Daten
Funktion | Ermittelt nicht-atomare Variablen (zum Beispiel mit Datentyp STRING, WSTRING, ARRAY, STRUCT, FB-Instanzen, 64-Bit Datentypen), die in mehr als einer Task verwendet werden. |
Begründung | Wenn keine Synchronisation beim Zugriff erfolgt, dann kann es bei gleichzeitigem Lesen in einer Task und Schreiben in einer anderen Task dazu kommen, dass inkonsistente Werte gelesen werden. |
Ausnahme | In folgenden Fällen greift diese Regel nicht:
|
Wichtigkeit | Mittel |
Sehen Sie auch die Regel SA0006. |
Beispiele:
Struktur ST_Sample:
TYPE ST_Sample :
STRUCT
bMember : BOOL;
nTest : INT;
END_STRUCT
END_TYPE
Funktionsbaustein 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
Programm MAIN1, aufgerufen von der 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;
Programm MAIN2, aufgerufen von der 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: Mehrfache Instanzaufrufe
Funktion | Ermittelt und meldet Instanzen von Funktionsbausteinen, die mehr als einmal aufgerufen werden. Damit eine Fehlermeldung für eine mehrfach aufgerufene Instanz eines Funktionsbausteins generiert wird, muss im Deklarationsteil des Funktionsbausteins das Attribut {attribute 'analysis:report-multiple-instance-calls'} hinzugefügt werden. |
Begründung | Einige Funktionsblöcke sind so designt, dass sie nur einmal im Zyklus aufgerufen werden können. Dieser Test prüft, ob ein Aufruf an mehreren Stellen erfolgt. |
Wichtigkeit | Niedrig |
PLCopen-Regel | CP16/CP20 |
Beispiel:
Im folgenden Beispiel wird die Statische Analyse einen Fehler für fb2 ausgeben, weil die Instanz mehr als einmal aufgerufen wird und der Funktionsbaustein mit dem benötigten Attribut deklariert ist.
Funktionsbaustein FB_Test1 ohne Attribut:
FUNCTION_BLOCK FB_Test1
Funktionsbaustein FB_Test2 mit Attribut:
{attribute 'analysis:report-multiple-instance-calls'}
FUNCTION_BLOCK FB_Test2
Programm MAIN:
PROGRAM MAIN
VAR
fb1 : FB_Test1;
fb2 : FB_Test2;
END_VAR
fb1();
fb1();
fb2(); // => SA0105
fb2(); // => SA0105
SA0106: Virtuelle Methodenaufrufe in FB_init
Funktion | Ermittelt Methodenaufrufe in der Methode FB_init eines Basis-Funktionsbausteins, die von einem vom Basis-FB abgeleiteten Funktionsbaustein überschrieben werden. |
Begründung | In solchen Fällen kann es sein, dass die Variablen in überschriebenen Methoden im Basis-FB nicht initialisiert sind. |
Wichtigkeit | Hoch |
Beispiel:
- Funktionsbaustein FB_Base hat die Methoden FB_init und MyInit. FB_init ruft MyInit zur Initialisierung auf.
- Funktionsbaustein FB_Sub ist von FB_Base abgeleitet.
- FB_Sub.MyInit überschreibt bzw. erweitert FB_Base.MyInit.
- MAIN instanziiert FB_Sub auf und verwendet dabei aufgrund der Aufrufreihenfolge während der Initialisierung die Instanzvariable nSub, bevor sie initialisiert wurde.
Funktionsbaustein FB_Base:
FUNCTION_BLOCK FB_Base
VAR
nBase : DINT;
END_VAR
Methode FB_Base.FB_init:
METHOD FB_init : BOOL
VAR_INPUT
bInitRetains : BOOL;
bInCopyCode : BOOL;
END_VAR
VAR
nLocal : DINT;
END_VAR
nLocal := MyInit(); // => SA0106
Methode FB_Base.MyInit:
METHOD MyInit : DINT
nBase := 123; // access to member of FB_Base
MyInit := nBase;
Funktionsbaustein FB_Sub:
FUNCTION_BLOCK FB_Sub EXTENDS FB_Base
VAR
nSub : DINT;
END_VAR
Methode FB_Sub.MyInit:
METHOD MyInit : DINT
nSub := 456; // access to member of FB_Sub
SUPER^.MyInit(); // call of base implementation
MyInit := nSub;
Programm MAIN:
PROGRAM MAIN
VAR
fbBase : FB_Base;
fbSub : FB_Sub;
END_VAR
Die Instanz MAIN.fbBase besitzt nach der Initialisierung folgende Variablenwerte:
- nBase ist 123
Die Instanz MAIN.fbSub besitzt nach der Initialisierung folgende Variablenwerte:
- nBase ist 123
- nSub ist 0
Die Variable MAIN.fbSub.nSub ist nach der Initialisierung 0, da während der Initialisierung von fbSub die folgende Aufrufreihenfolge durchgeführt wird:
- Initialisierung des Basisbausteins:
- implizite Initialisierung
- explizite Initialisierung: FB_Base.FB_init
- FB_Base.FB_init ruft FB_Sub.MyInit auf → SA0106
- FB_Sub.MyInit ruft FB_Base.MyInit auf (über SUPER-Zeiger)
- Initialisierung des abgeleiteten Bausteins:
- implizite Initialisierung
SA0107: Fehlen von formalen Parametern
Funktion | Ermittelt, wo formale Parameter fehlen. |
Begründung | Code wird lesbarer, wenn die Formalparameter beim Aufruf angegeben werden. |
Wichtigkeit | Niedrig |
Beispiel:
Funktion F_Sample:
FUNCTION F_Sample : BOOL
VAR_INPUT
bIn1 : BOOL;
bIn2 : BOOL;
END_VAR
F_Sample := bIn1 AND bIn2;
Programm 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: Zeigervariablen
Funktion | Ermittelt Variablen vom Typ POINTER TO. |
Begründung | Die Norm IEC 61131-3 erlaubt keine Pointer. |
Wichtigkeit | Niedrig |
Beispiel:
PROGRAM MAIN
VAR
pINT : POINTER TO INT; // => SA0111
END_VAR
SA0112: Referenzvariablen
Funktion | Ermittelt Variablen vom Typ REFERENCE TO. |
Begründung | Die Norm IEC 61131-3 erlaubt keine Referenzen. |
Wichtigkeit | Niedrig |
Beispiel:
PROGRAM MAIN
VAR
refInt : REFERENCE TO INT; // => SA0112
END_VAR
SA0113: Variablen mit Datentyp WSTRING
Funktion | Ermittelt Variablen vom Typ WSTRING. |
Begründung | Nicht alle Systeme unterstützen WSTRING. Der Code wird leichter portierbar, wenn auf WSTRING verzichtet wird. |
Wichtigkeit | Niedrig |
Beispiel:
PROGRAM MAIN
VAR
wsVar : WSTRING; // => SA0113
END_VAR
SA0114: Variablen mit Datentyp LTIME
Funktion | Ermittelt Variablen vom Typ LTIME. |
Begründung | Nicht alle Systeme unterstützen LTIME. Der Code wird portierbarer, wenn auf LTIME verzichtet wird. |
Wichtigkeit | Niedrig |
Beispiel:
PROGRAM MAIN
VAR
tVar : LTIME; // => SA0114
END_VAR
// no error SA0114 for the following code line:
tVar := tVar + LTIME#1000D15H23M12S34MS2US44NS;
SA0115: Deklarationen mit Datentyp UNION
Funktion | Ermittelt Deklarationen eines UNION-Datentyps und Deklarationen von Variablen vom Typ einer UNION. |
Begründung | Die Norm IEC-61131-3 kennt keinen Unions. Der Code wird leichter portierbar, wenn auf Unions verzichtet wird. |
Wichtigkeit | Niedrig |
Beispiele:
Union U_Sample:
TYPE U_Sample : // => SA0115
UNION
fVar : LREAL;
nVar : LINT;
END_UNION
END_TYPE
Programm MAIN:
PROGRAM MAIN
VAR
uSample : U_Sample; // => SA0115
END_VAR
SA0117: Variablen mit Datentyp BIT
Funktion | Ermittelt Deklarationen von Variablen vom Typ BIT (möglich innerhalb von Struktur- und Funktionsbausteindefinitionen). |
Begründung | Die IEC-61131-3 kennt keinen Datentyp BIT. Der Code wird leichter portierbar, wenn auf BIT verzichtet wird. |
Wichtigkeit | Niedrig |
Beispiele:
Struktur ST_Sample:
TYPE ST_Sample :
STRUCT
bBIT : BIT; // => SA0117
bBOOL : BOOL;
END_STRUCT
END_TYPE
Funktionsbaustein FB_Sample:
FUNCTION_BLOCK FB_Sample
VAR
bBIT : BIT; // => SA0117
bBOOL : BOOL;
END_VAR
SA0119: Objektorientierte Funktionalität
Funktion | Ermittelt die Verwendung objektorientierter Funktionalitäten, wie beispielsweise:
|
Begründung | Nicht alle Systeme unterstützen Objektorientierte Programmierung. Der Code wird leichter portierbar, wenn auf Objektorientierung verzichtet wird. |
Wichtigkeit | Niedrig |
Beispiele:
Schnittstelle I_Sample:
INTERFACE I_Sample // => SA0119
Funktionsbaustein FB_Base:
FUNCTION_BLOCK FB_Base IMPLEMENTS I_Sample // => SA0119
Funktionsbaustein FB_Sub:
FUNCTION_BLOCK FB_Sub EXTENDS FB_Base // => SA0119
Methode FB_Sub.SampleMethod:
METHOD SampleMethod : BOOL // no error
Get-Funktion der Eigenschaft FB_Sub.SampleProperty:
VAR // => SA0119
END_VAR
Set-Funktion der Eigenschaft FB_Sub.SampleProperty:
VAR // => SA0119
END_VAR
SA0120: Programmaufrufe
Funktion | Ermittelt Programmaufrufe. |
Begründung | Nach der Norm IEC 61131-3 können Programme nur in der Taskkonfiguration aufgerufen werden. Der Code wird leichter portierbar, wenn auf Programmaufrufe an anderen Stellen verzichtet wird. |
Wichtigkeit | Niedrig |
Beispiel:
Programm SubProgram:
PROGRAM SubProgram
Programm MAIN:
PROGRAM MAIN
SubProgram(); // => SA0120
SA0121: Fehlende VAR_EXTERNAL-Deklarationen
Funktion | Ermittelt die Verwendung einer globalen Variablen im Funktionsbaustein, ohne dass sie dort als VAR_EXTERNAL deklariert ist (erforderlich laut Norm). |
Begründung | Nach der Norm IEC 61131-3 ist der Zugriff auf globale Variablen nur über einen expliziten Import durch eine VAR_EXTERNAL-Deklaration erlaubt. |
Wichtigkeit | Niedrig |
PLCopen-Regel | CP18 |
In TwinCAT 3 PLC ist es nicht notwendig, Variablen als extern zu deklarieren. Das Schlüsselwort existiert, um die Kompatibilität zu IEC 61131-3 zu wahren. |
Beispiel:
Globale Variablen:
VAR_GLOBAL
nGlobal : INT;
END_VAR
Programm Prog1:
PROGRAM Prog1
VAR
nVar : INT;
END_VAR
nVar := nGlobal; // => SA0121
Programm Prog2:
PROGRAM Prog2
VAR
nVar : INT;
END_VAR
VAR_EXTERNAL
nGlobal : INT;
END_VAR
nVar := nGlobal; // no error
SA0122: Als Ausdruck definierter Arrayindex
Funktion | Ermittelt die Verwendung von Ausdrücken bei der Deklaration von Arraygrenzen. |
Begründung | Nicht alle Systeme erlauben Ausdrücke als Arraygrenzen. |
Wichtigkeit | Niedrig |
Beispiel:
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: Verwendung von INI, ADR oder BITADR
Funktion | Ermittelt die Verwendung der (TwinCAT spezifischen) Operatoren INI, ADR, BITADR. |
Begründung | TwinCAT-spezifische Operatoren verhindern die Portierbarkeit des Codes. |
Wichtigkeit | Niedrig |
Beispiel:
PROGRAM MAIN
VAR
nVar : INT;
pINT : POINTER TO INT;
END_VAR
pINT := ADR(nVar); // => SA0123
SA0147: Unübliche Schiebeoperation - strikt
Funktion | Ermittelt Bitshift-Operationen, die nicht auf Bitfeld-Datentypen (BYTE, WORD, DWORD, LWORD) erfolgen. |
Begründung | Die Norm IEC 61131-3 erlaubt nur Bitzugriffe auf Bitfeld-Datentypen. Der TwinCAT 3 Compiler erlaubt jedoch auch Bitshift-Operationen mit nicht vorzeichenbehafteten Datentypen. |
Wichtigkeit | Niedrig |
Sehen Sie auch die nicht strikte Regel SA0052. |
Beispiele:
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: Unüblicher Bitzugriff - strikt
Funktion | Ermittelt Bitzugriffe, die nicht auf Bitfeld-Datentypen (BYTE, WORD, DWORD, LWORD) erfolgen. |
Begründung | Die Norm IEC 61131-3 erlaubt nur Bitzugriffe auf Bitfeld-Datentypen. Der TwinCAT 3 Compiler erlaubt jedoch auch Bitzugriffe auf nicht vorzeichenbehaftete Datentypen. |
Wichtigkeit | Niedrig |
Sehen Sie auch die nicht strikte Regel SA0018. |
Beispiele:
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: Initialisierungen nicht mit Konstanten
Funktion | Ermittelt Initialisierungen, die nicht Konstanten zuweisen. |
Begründung | Initialisierungen sollten möglichst konstant sein und sich nicht auf andere Variablen beziehen. Insbesondere sollten Sie Funktionsaufrufe in der Initialisierung vermeiden, weil es dadurch zu einem Zugriff auf nicht initialisierte Daten kommen kann. |
Wichtigkeit | Mittel |
Beispiele:
Funktion F_ReturnDWORD:
FUNCTION F_ReturnDWORD : DWORD
Programm 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: Dereferenzierungszugriff in Initialisierungen
Funktion | Ermittelt alle Codestellen, an denen dereferenzierte Pointer im Deklarationsteil von POUs verwendet werden. |
Begründung | Pointer und Referenzen sollten nicht für Initialisierungen verwendet werden, weil es dadurch zur Laufzeit zu "Access Violations" kommen kann, wenn der Pointer nicht initialisiert worden ist. |
Wichtigkeit | Mittel |
Beispiele:
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
Überblick über die Regeln zum Thema „Dereferenzierung“
Pointer:
- Dereferenzierung von Pointern im Deklarationsteil => SA0124
- Mögliche Null-Pointer-Dereferenzierung im Implementierungsteil => SA0039
Referenzen:
- Verwendung von Referenzen im Deklarationsteil => SA0125
- Mögliche Verwendung nicht initialisierter Referenzen im Implementierungsteil => SA0145
Schnittstellen:
- Mögliche Verwendung nicht initialisierter Schnittstellen im Implementierungsteil => SA0046
SA0125: Referenzen in Initialisierungen
Funktion | Ermittelt alle Referenzvariablen, die zur Initialisierung im Deklarationsteil von POUs verwendet werden. |
Begründung | Pointer und Referenzen sollten nicht für Initialisierungen verwendet werden, weil es dadurch zur Laufzeit zu “Access Violations” kommen kann, wenn der Pointer nicht initialisiert worden ist. |
Wichtigkeit | Mittel |
Beispiele:
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
Überblick über die Regeln zum Thema „Dereferenzierung“
Pointer:
- Dereferenzierung von Pointern im Deklarationsteil => SA0124
- Mögliche Null-Pointer-Dereferenzierung im Implementierungsteil => SA0039
Referenzen:
- Verwendung von Referenzen im Deklarationsteil => SA0125
- Mögliche Verwendung nicht initialisierter Referenzen im Implementierungsteil => SA0145
Schnittstellen:
- Mögliche Verwendung nicht initialisierter Schnittstellen im Implementierungsteil => SA0046
SA0039: Mögliche Null-Pointer-Dereferenzierung
Funktion | Ermittelt Codestellen, an denen möglicherweise ein Null-Pointer dereferenziert wird. |
Begründung | Ein Pointer sollte vor jeder Dereferenzierung daraufhin geprüft werden, ob er ungleich 0 ist. Anderfalls kann es zu einer “Access Violation” zur Laufzeit kommen. |
Wichtigkeit | Hoch |
Beispiel 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
Beispiel 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
Überblick über die Regeln zum Thema „Dereferenzierung“
Pointer:
- Dereferenzierung von Pointern im Deklarationsteil => SA0124
- Mögliche Null-Pointer-Dereferenzierung im Implementierungsteil => SA0039
Referenzen:
- Verwendung von Referenzen im Deklarationsteil => SA0125
- Mögliche Verwendung nicht initialisierter Referenzen im Implementierungsteil => SA0145
Schnittstellen:
- Mögliche Verwendung nicht initialisierter Schnittstellen im Implementierungsteil => SA0046
SA0046: Mögliche Verwendung nicht initialisierter Schnittstellen
Funktion | Ermittelt die Verwendung von Schnittstellen, die vor der Verwendung möglicherweise nicht initialisiert wurden. |
Begründung | Eine Interface-Referenz sollte vor ihrer Verwendung auf <> 0 geprüft werden, weil es sonst beim Zugriff zur Laufzeit zu einer "Access Violation" kommen kann. |
Wichtigkeit | Hoch |
Beispiele:
Schnittstelle I_Sample:
INTERFACE I_Sample
METHOD SampleMethod : BOOL
VAR_INPUT
nInput : INT;
END_VAR
Funktionsbaustein FB_Sample:
FUNCTION_BLOCK FB_Sample IMPLEMENTS I_Sample
METHOD SampleMethod : BOOL
VAR_INPUT
nInput : INT;
END_VAR
Programm 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
Überblick über die Regeln zum Thema „Dereferenzierung“
Pointer:
- Dereferenzierung von Pointern im Deklarationsteil => SA0124
- Mögliche Null-Pointer-Dereferenzierung im Implementierungsteil => SA0039
Referenzen:
- Verwendung von Referenzen im Deklarationsteil => SA0125
- Mögliche Verwendung nicht initialisierter Referenzen im Implementierungsteil => SA0145
Schnittstellen:
- Mögliche Verwendung nicht initialisierter Schnittstellen im Implementierungsteil => SA0046
SA0145: Mögliche Verwendung nicht initialisierter Referenzen
Funktion | Ermittelt alle verwendeten Referenzvariablen, die möglicherweise vor der Verwendung nicht initialisiert werden und nicht durch den Operator __ISVALIDREF überprüft wurden. Diese Regel wird im Implementierungsteil von POUs angewendet. |
Begründung | Eine Referenz sollte vor dem Zugriff auf Gültigkeit geprüft werden, weil es sonst beim Zugriff zur Laufzeit zu einer "Access Violation" kommen kann. |
Wichtigkeit | Hoch |
Beispiele:
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
Überblick über die Regeln zum Thema „Dereferenzierung“
Pointer:
- Dereferenzierung von Pointern im Deklarationsteil => SA0124
- Mögliche Null-Pointer-Dereferenzierung im Implementierungsteil => SA0039
Referenzen:
- Verwendung von Referenzen im Deklarationsteil => SA0125
- Mögliche Verwendung nicht initialisierter Referenzen im Implementierungsteil => SA0145
Schnittstellen:
- Mögliche Verwendung nicht initialisierter Schnittstellen im Implementierungsteil => SA0046
SA0140: Auskommentierte Anweisungen
Funktion | Ermittelt Anweisungen, die auskommentiert sind. |
Begründung | Code wird oft zu Debugging-Zwecken auskommentiert. Wenn ein solcher Kommentar freigegeben wird, dann ist zu einem späteren Zeitpunkt nicht mehr klar, ob der Code gelöscht werden sollte oder ob er nur zu Debug-Zwecken auskommentiert wurde und versehentlich nicht mehr einkommentiert wurde. |
Wichtigkeit | Hoch |
PLCopen-Regel | C4 |
Beispiel:
//bStart := TRUE; // => SA0140
SA0150: Verletzung von Unter- oder Obergrenzen der Metriken
Funktion | Ermittelt die Bausteine, die die aktivierten Metriken an der Unter- oder Obergrenze verletzen. |
Begründung | Code, der bestimmte Metriken einhält, ist leichter lesbar, leichter wartbar und leichter zu testen. |
Wichtigkeit | Hoch |
PLCopen-Regel | CP9 |
Beispiel:
Die Metrik "Anzahl Aufrufe" ist in der Metriken-Konfiguration aktiviert und konfiguriert (SPS-Projekteigenschaften > Kategorie "Static Analysis" > Registerkarte "Metriken").
- Untergrenze: 0
- Obergrenze: 3
- Baustein Prog1 wird jedoch 5x aufgerufen
Beim Ausführen der Statischen Analyse wird die Verletzung von SA0150 als Fehler bzw. Warnung im Meldungsfenster ausgegeben.
// => SA0150: Metric violation for 'Prog1'. Result for metric 'Calls' (5) > 3"
SA0160: Rekursive Aufrufe
Funktion | Ermittelt rekursive Aufrufe von Programmen, Aktionen, Methoden und Eigenschaften. Ermittelt auch mögliche Rekursionen durch virtuelle Funktionsaufrufe und Schnittstellenaufrufe. |
Begründung | Rekursionen führen zu nicht-deterministischem Verhalten und sind daher eine Fehlerquelle. |
Wichtigkeit | Mittel |
PLCopen-Regel | CP13 |
Beispiel 1:
Methode FB_Sample.SampleMethod1:
METHOD SampleMethod1
VAR_INPUT
END_VAR
SampleMethod1(); (* => SA0160: Recursive call:
'MAIN -> FB_Sample.SampleMethod1 -> FB_Sample.SampleMethod1' *)
Methode FB_Sample.SampleMethod2:
METHOD SampleMethod2 : BOOL
VAR_INPUT
END_VAR
SampleMethod2 := THIS^.SampleMethod2();(* => SA0160: Recursive call:
'MAIN -> FB_Sample.SampleMethod2 -> FB_Sample.SampleMethod2' *)
Programm MAIN:
PROGRAM MAIN
VAR
fbSample : FB_Sample;
bReturn : BOOL;
END_VAR
fbSample.SampleMethod1();
bReturn := fbSample.SampleMethod2();
Beispiel 2:
Bitte beachten Sie bei Eigenschaften:
Für eine Eigenschaft wird implizit eine lokale Eingangsvariable mit dem Namen der Eigenschaft erzeugt. Die folgende Set-Funktion einer Eigenschaft weist somit den Wert der impliziten lokalen Eingangsvariablen der Eigenschaft einer FB-Variablen zu.
Funktionsbaustein FB_Sample:
FUNCTION_BLOCK FB_Sample
VAR
nParameter : INT;
END_VAR
Set-Funktion der Eigenschaft SampleProperty:
nParameter := SampleProperty;
Bei der folgenden Set-Funktion wird somit die implizite Eingangsvariable der Eigenschaft sich selbst zugewiesen. Die Zuweisung einer Variablen auf sich selbst bedeutet keine Rekursion, sodass diese Set-Funktion keinen Fehler SA0160 erzeugt.
Set-Funktion der Eigenschaft SampleProperty:
SampleProperty := SampleProperty; // no error SA0160
Der Zugriff auf eine Eigenschaft über den THIS-Zeiger ist hingegen qualifiziert. Durch Verwendung des THIS-Zeigers wird auf die Instanz und somit auf die Eigenschaft und nicht auf die implizite lokale Eingangsvariable zugegriffen. Das bedeutet, dass die Verschattung von impliziter lokaler Eingangsvariablen und der Eigenschaft selbst aufgehoben wird. Bei der folgenden Set-Funktion wird daher ein erneuter Aufruf der Eigenschaft erzeugt, der zu einer Rekursion und damit zum Fehler SA0160 führt.
Set-Funktion der Eigenschaft SampleProperty:
THIS^.SampleProperty := SampleProperty; // => SA0160
SA0161: Ungepackte Struktur in gepackter Struktur
Funktion | Ermittelt ungepackte Strukturen, die in gepackten Strukturen verwendet werden. |
Begründung | Eine ungepackte Struktur legt der Compiler normalerweise auf eine Adresse, die alignten Zugriff auf alle Elemente innerhalb der Struktur erlaubt. Wenn Sie diese Struktur in einer gepackten Struktur anlegen, dann ist ein alignter Zugriff nicht mehr möglich, und ein Zugriff auf ein Element in der ungepackten Struktur kann zur Laufzeit zu einer “Misalignment Exception” führen. |
Wichtigkeit | Hoch |
Beispiel:
Die Struktur ST_SingleDataRecord ist gepackt, enthält jedoch Instanzen der ungepackten Strukturen ST_4Byte und ST_9Byte. Dies resultiert jeweils in einer Fehlermeldung SA0161.
{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
Struktur ST_9Byte:
TYPE ST_9Byte :
STRUCT
nRotorSlots : USINT;
nMaxCurrent : UINT;
nVelocity : USINT;
nAcceleration : UINT;
nDeceleration : UINT;
nDirectionChange : USINT;
END_STRUCT
END_TYPE
Struktur ST_4Byte:
TYPE ST_4Byte :
STRUCT
fDummy : REAL;
END_STRUCT
END_TYPE
SA0162: Fehlende Kommentare
Funktion | Ermittelt Stellen im Programm, die nicht kommentiert sind. Kommentare sind erforderlich bei:
|
Begründung | Eine vollständige Kommentierung wird von vielen Programmierrichtlinien gefordert und erhöht die Lesbarkeit und Wartbarkeit des Codes. |
Wichtigkeit | Niedrig |
PLCopen-Regel | C2 |
Beispiele:
Das folgende Beispiel erzeugt für die Variable b1 den Fehler: "SA0162: Missing comment for 'b1'".
// Comment for MAIN program
PROGRAM MAIN
VAR
b1 : BOOL;
// Comment for variable b2
b2 : BOOL;
b3 : BOOL; // Comment for variable b3
END_VAR
SA0163: Verschachtelte Kommentare
Funktion | Ermittelt Codestellen, an denen verschachtelte Kommentare sind. |
Begründung | Verschachtelte Kommentare sind schwer zu lesen und sollten deswegen vermieden werden. |
Wichtigkeit | Niedrig |
PLCopen-Regel | C3 |
Beispiele:
Die im folgenden Beispiel entsprechend bezeichneten vier verschachtelten Kommentare führen jeweils zu dem Fehler: "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: Mehrzeilige Kommentare
Funktion | Ermittelt Codestellen, an denen der Mehrzeilenkommentar-Operator (* *) verwendet wird. Erlaubt sind nur die beiden Einzeilenkommentar-Operatoren // für Standardkommentare und /// für Dokumentationskommentare. |
Begründung | Einige Programmierrichtlinien verbieten mehrzeilige Kommentare im Code, weil Anfang und Ende eines Kommentars aus dem Blickfeld geraten könnten und die schließende Kommentarklammer durch einen Fehler gelöscht werden könnte. |
Wichtigkeit | Niedrig |
PLCopen-Regel | C5 |
Sie können diese Prüfung mit dem Pragma {analysis ...} deaktivieren, auch für Kommentare im Deklarationsteil. |
Beispiele:
(*
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: Maximale Anzahl an Eingabe-/Ausgabe-/VAR_IN_OUT-Variablen
Funktion | Die Prüfung ermittelt, ob eine definierte Anzahl an Eingabevariablen (VAR_INPUT), Ausgabevariablen (VAR_OUTPUT) oder VAR_IN_OUT-Variablen in einem Baustein überschritten wird. Die Parameter, die bei dieser Prüfung berücksichtigt werden, können Sie konfigurieren, indem Sie innerhalb der Regelkonfiguration auf die Zeile von Regel 166 doppelklicken (SPS-Projekteigenschaften > Kategorie "Static Analysis" > Registerkarte "Regeln" > Regel 166). In dem aufgehenden Dialog können Sie folgende Einstellungen vornehmen:
|
Begründung | Es geht um die Überprüfung von individuellen Programmierrichtlinien. Viele Programmierrichtlinien sehen für Bausteine eine maximale Anzahl an Parametern vor. Zu viele Parameter machen den Code unleserlich und die Bausteine schwer zu testen. |
Wichtigkeit | Mittel |
PLCopen-Regel | CP23 |
Beispiel:
Regel 166 ist mit folgenden Parametern konfiguriert:
- Maximale Anzahl an Eingängen: 0
- Maximale Anzahl an Ausgängen: 10
- Maximale Anzahl an Ein-/Ausgängen: 1
Für den folgenden Funktionsbaustein werden somit zwei Fehler SA0166 gemeldet, da zu viele Eingänge (> 0) und zu viele Ein-/Ausgänge (> 1) deklariert sind.
Funktionsbaustein 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: Temporäre Funktionsbausteininstanzen
Funktion | Ermittelt Funktionsbausteininstanzen, die als temporäre Variable deklariert sind. Dies betrifft Instanzen, die in einer Methode oder in einer Funktion oder als VAR_TEMP deklariert sind, und die deshalb in jedem Abarbeitungszyklus bzw. bei jedem Bausteinaufruf neu initialisiert werden. |
Begründung | Funktionsbausteine haben einen Zustand, der meist über mehrere SPS-Zyklen hinweg erhalten bleibt. Eine Instanz auf dem Stack existiert nur für die Dauer des Funktionsaufrufs. Es ist daher nur selten sinnvoll, eine Instanz als temporäre Variable anzulegen. Zweitens sind Funktionsbausteininstanzen häufig groß und verbrauchen sehr viel Platz auf dem Stack (der auf Steuerungen meist begrenzt ist). Drittens kann die Initialisierung und häufig auch die Terminierung eines Funktionsbausteins ziemlich viel Zeit in Anspruch nehmen. |
Wichtigkeit | Mittel |
Beispiele:
Methode FB_Sample.SampleMethod:
METHOD SampleMethod : INT
VAR_INPUT
END_VAR
VAR
fbTrigger : R_TRIG; // => SA0167
END_VAR
Funktion F_Sample:
FUNCTION F_Sample : INT
VAR_INPUT
END_VAR
VAR
fbSample : FB_Sample; // => SA0167
END_VAR
Programm MAIN:
PROGRAM MAIN
VAR_TEMP
fbSample : FB_Sample; // => SA0167
nReturn : INT;
END_VAR
nReturn := F_Sample();
SA0168: Unnötige Zuweisungen
Funktion | Ermittelt Zuweisungen auf Variablen, die keine Auswirkungen im Code haben. |
Begründung | Wenn einer Variablen mehrfach Werte zugewiesen werden, ohne dass die Variable zwischen den Zuweisungen ausgewertet wird, wirken sich die ersten Zuweisungen nicht auf das Programm aus. |
Wichtigkeit | Niedrig |
Beispiel:
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: Ignorierte Ausgänge
Funktion | Ermittelt die Ausgänge von Methoden und Funktionen, die beim Aufruf der Methode oder Funktion nicht angegeben werden. |
Begründung | Ignorierte Ausgänge können ein Hinweis auf nicht behandelte Fehler oder sinnlose Funktionsaufrufe sein, da Ergebnisse nicht verwendet werden. |
Wichtigkeit | Mittel |
Beispiel:
Funktion F_Sample:
FUNCTION F_Sample : BOOL
VAR_INPUT
bIn : BOOL;
END_VAR
VAR_OUTPUT
bOut : BOOL;
END_VAR
Programm MAIN:
PROGRAM MAIN
VAR
bReturn : BOOL;
bFunOut : BOOL;
END_VAR
bReturn := F_Sample(bIn := TRUE , bOut => bFunOut);
bReturn := F_Sample(bIn := TRUE); // => SA0169
SA0170: Adresse einer Ausgangsvariablen sollte nicht verwendet werden
Funktion | Ermittelt Codestellen, an denen die Adresse einer Ausgangsvariablen (VAR_OUTPUT, VAR_IN_OUT) eines Funktionsbausteins ermittelt wird. |
Begründung | Es ist nicht erlaubt, in folgender Weise die Adresse eines Funktionsbausteinausgangs zu verwenden:
|
Ausnahme | Es wird kein Fehler gemeldet, wenn der Ausgang innerhalb desselben Funktionsbausteins verwendet wird. |
Wichtigkeit | Mittel |
Beispiel:
Funktionsbaustein 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
Zugriffe innerhalb eines anderen Bausteins, in diesem Fall im Programm MAIN:
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: Enumerationen sollten das Attribut 'strict' haben
Funktion | Ermittelt Deklarationen von Enumerationen, die nicht mit dem Attribut {attribute 'strict'} versehen sind. |
Begründung | Das Attribut {attribute 'strict'} bewirkt, dass Compilerfehler ausgegeben werden, wenn der Code gegen strikte Programmierregeln für Enumerationen verstößt. Standardmäßig wird beim Anlegen einer neuen Enumeration die Deklaration automatisch mit dem Attribut 'strict' versehen. |
Wichtigkeit | Hoch |
Für weitere Informationen siehe: PLC > Referenz Programmierung > Pragmas > Attribut-Pragmas > Attribut 'strict'
Beispiel:
{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: Verdächtige Operation auf String
Funktion | Ermittelt Codestellen, die bei einer UTF-8-Kodierung verdächtig sind. |
Erfasste Konstrukte |
|
Wichtigkeit | Mittel |
Beispiele:
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