-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add LinearCast rule to check for improper object and pointer casts
- Loading branch information
Showing
5 changed files
with
379 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
59 changes: 59 additions & 0 deletions
59
delphi-checks/src/main/java/au/com/integradev/delphi/checks/NonLinearCastCheck.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* Sonar Delphi Plugin | ||
* Copyright (C) 2023 Integrated Application Development | ||
* | ||
* This program is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU Lesser General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public | ||
* License along with this program; if not, write to the Free Software | ||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | ||
*/ | ||
package au.com.integradev.delphi.checks; | ||
|
||
import org.sonar.check.Rule; | ||
import org.sonar.plugins.communitydelphi.api.type.Type; | ||
import org.sonar.plugins.communitydelphi.api.type.Type.PointerType; | ||
import org.sonar.plugins.communitydelphi.api.type.Type.StructType; | ||
|
||
@Rule(key = "NonLinearCast") | ||
public class NonLinearCastCheck extends AbstractCastCheck { | ||
@Override | ||
protected boolean isViolation(Type originalType, Type castType) { | ||
if (originalType.isStruct() && castType.isStruct()) { | ||
return !isValidStructToStructCast((StructType) originalType, (StructType) castType); | ||
} else if (originalType.isStruct() && castType.isPointer()) { | ||
return !isValidStructToPointerCast((StructType) originalType, (PointerType) castType); | ||
} else { | ||
return false; | ||
} | ||
} | ||
|
||
private boolean isValidStructToStructCast(StructType from, StructType to) { | ||
if (!from.isClass() || !to.isClass()) { | ||
return true; | ||
} else { | ||
return from.is(to) || from.isSubTypeOf(to) || to.isSubTypeOf(from); | ||
} | ||
} | ||
|
||
private boolean isValidStructToPointerCast(StructType from, PointerType to) { | ||
if (!from.isClass() || to.isUntypedPointer() || !to.dereferencedType().isClass()) { | ||
return true; | ||
} else { | ||
return isValidStructToStructCast(from, (StructType) to.dereferencedType()); | ||
} | ||
} | ||
|
||
@Override | ||
protected String getIssueMessage() { | ||
return "Remove this unsafe object cast."; | ||
} | ||
} |
39 changes: 39 additions & 0 deletions
39
...checks/src/main/resources/org/sonar/l10n/delphi/rules/community-delphi/NonLinearCast.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
<h2>Why is this an issue?</h2> | ||
<p>Casting an object to a type that is not guaranteed to have the same memory configuration can | ||
cause access violations if used improperly.</p> | ||
<h2>How to fix it</h2> | ||
<p>Remove the improper cast by refactoring the code. If the intention is to provide additional | ||
methods to operate on an object, consider using a class helper instead. | ||
</p> | ||
<pre data-diff-id="1" data-diff-type="noncompliant"> | ||
type | ||
TAdditionalFunctionality = class(TObject) | ||
public | ||
procedure DebugPrint; | ||
end; | ||
|
||
procedure DoStuff(MyObj: TSecretString); | ||
begin | ||
TAdditionalFunctionality(MyObj).DebugPrint; | ||
end; | ||
</pre> | ||
<pre data-diff-id="2" data-diff-type="compliant"> | ||
type | ||
TAdditionalFunctionality = class helper for TSecretString | ||
public | ||
procedure DebugPrint; | ||
end; | ||
|
||
procedure DoStuff(MyObj: TSecretString); | ||
begin | ||
MyObj.DebugPrint; | ||
end; | ||
</pre> | ||
<h2>Resources</h2> | ||
<ul> | ||
<li> | ||
<a href="https://docwiki.embarcadero.com/RADStudio/en/Class_and_Record_Helpers_(Delphi)"> | ||
RAD Studio documentation: Class and Record Helpers (Delphi) | ||
</a> | ||
</li> | ||
</ul> |
19 changes: 19 additions & 0 deletions
19
...checks/src/main/resources/org/sonar/l10n/delphi/rules/community-delphi/NonLinearCast.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"title": "Object casts should only be to ancestor or child classes", | ||
"type": "BUG", | ||
"status": "ready", | ||
"remediation": { | ||
"func": "Constant/Issue", | ||
"constantCost": "10min" | ||
}, | ||
"code": { | ||
"attribute": "LOGICAL", | ||
"impacts": { | ||
"RELIABILITY": "MEDIUM" | ||
} | ||
}, | ||
"tags": ["unpredictable", "suspicious"], | ||
"defaultSeverity": "Critical", | ||
"scope": "ALL", | ||
"quickfix": "unknown" | ||
} |
261 changes: 261 additions & 0 deletions
261
delphi-checks/src/test/java/au/com/integradev/delphi/checks/NonLinearCastCheckTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,261 @@ | ||
/* | ||
* Sonar Delphi Plugin | ||
* Copyright (C) 2023 Integrated Application Development | ||
* | ||
* This program is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU Lesser General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public | ||
* License along with this program; if not, write to the Free Software | ||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 | ||
*/ | ||
package au.com.integradev.delphi.checks; | ||
|
||
import au.com.integradev.delphi.builders.DelphiTestUnitBuilder; | ||
import au.com.integradev.delphi.checks.verifier.CheckVerifier; | ||
import org.junit.jupiter.api.Test; | ||
|
||
class NonLinearCastCheckTest { | ||
@Test | ||
void testCastToGrandparentClassShouldNotAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TGrandparent = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TParent = class(TGrandparent)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TChild = class(TParent)") | ||
.appendDecl(" end;") | ||
.appendImpl("function AsGrandparent(Obj: TChild): TGrandparent;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := TGrandparent(Obj);") | ||
.appendImpl("end;")) | ||
.verifyNoIssues(); | ||
} | ||
|
||
@Test | ||
void testCastToParentClassShouldNotAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TParent = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TChild = class(TParent)") | ||
.appendDecl(" end;") | ||
.appendImpl("function AsParent(Obj: TChild): TParent;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := TParent(Obj);") | ||
.appendImpl("end;")) | ||
.verifyNoIssues(); | ||
} | ||
|
||
@Test | ||
void testCastToChildClassShouldNotAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TParent = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TChild = class(TParent)") | ||
.appendDecl(" end;") | ||
.appendImpl("function AsChild(Obj: TParent): TChild;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := TChild(Obj);") | ||
.appendImpl("end;")) | ||
.verifyNoIssues(); | ||
} | ||
|
||
@Test | ||
void testCastToGrandchildClassShouldNotAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TParent = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TChild = class(TParent)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TGrandchild = class(TChild)") | ||
.appendDecl(" end;") | ||
.appendImpl("function AsGrandchild(Obj: TParent): TGrandchild;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := TGrandchild(Obj);") | ||
.appendImpl("end;")) | ||
.verifyNoIssues(); | ||
} | ||
|
||
@Test | ||
void testCastToTObjectShouldNotAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TMyType = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendImpl("function AsObject(Obj: TMyType): TObject;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := TObject(Obj);") | ||
.appendImpl("end;")) | ||
.verifyNoIssues(); | ||
} | ||
|
||
@Test | ||
void testCastToSelfShouldNotAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TMyType = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendImpl("function AsSame(Obj: TMyType): TMyType;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := TMyType(Obj);") | ||
.appendImpl("end;")) | ||
.verifyNoIssues(); | ||
} | ||
|
||
@Test | ||
void testCastToSiblingClassShouldAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TParent = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TChild = class(TParent)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TOtherChild = class(TParent)") | ||
.appendDecl(" end;") | ||
.appendImpl("function AsOtherChild(Obj: TChild): TOtherChild;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := TOtherChild(Obj);") | ||
.appendImpl("end;")) | ||
.verifyIssueOnLine(17); | ||
} | ||
|
||
@Test | ||
void testCastToUnrelatedObjectShouldAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TDog = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TCat = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendImpl("function AsCat(Obj: TDog): TCat;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := TCat(Obj);") | ||
.appendImpl("end;")) | ||
.verifyIssueOnLine(15); | ||
} | ||
|
||
@Test | ||
void testCastToUntypedPointerShouldNotAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TMyType = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendImpl("function AsPointer(Obj: TMyType): Pointer;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := Pointer(Obj);") | ||
.appendImpl("end;")) | ||
.verifyNoIssues(); | ||
} | ||
|
||
@Test | ||
void testCastToSelfTypedPointerShouldNotAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TMyType = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendDecl(" PMyType = ^TMyType;") | ||
.appendImpl("function AsPointer(Obj: TMyType): PMyType;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := PMyType(Obj);") | ||
.appendImpl("end;")) | ||
.verifyNoIssues(); | ||
} | ||
|
||
@Test | ||
void testCastToParentTypedPointerShouldNotAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TParent = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TChild = class(TParent)") | ||
.appendDecl(" end;") | ||
.appendDecl(" PParent = ^TParent;") | ||
.appendImpl("function AsPointer(Obj: TChild): PParent;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := PParent(Obj);") | ||
.appendImpl("end;")) | ||
.verifyNoIssues(); | ||
} | ||
|
||
@Test | ||
void testCastToChildTypedPointerShouldNotAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TParent = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TChild = class(TParent)") | ||
.appendDecl(" end;") | ||
.appendDecl(" PChild = ^TChild;") | ||
.appendImpl("function AsPointer(Obj: TParent): PChild;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := PChild(Obj);") | ||
.appendImpl("end;")) | ||
.verifyNoIssues(); | ||
} | ||
|
||
@Test | ||
void testCastToUnrelatedPointerShouldAddIssue() { | ||
CheckVerifier.newVerifier() | ||
.withCheck(new NonLinearCastCheck()) | ||
.onFile( | ||
new DelphiTestUnitBuilder() | ||
.appendDecl("type") | ||
.appendDecl(" TDog = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendDecl(" TCat = class(TObject)") | ||
.appendDecl(" end;") | ||
.appendDecl(" PCat = ^TCat;") | ||
.appendImpl("function AsPointer(Obj: TDog): PCat;") | ||
.appendImpl("begin") | ||
.appendImpl(" Result := PCat(Obj);") | ||
.appendImpl("end;")) | ||
.verifyIssueOnLine(16); | ||
} | ||
} |