Skip to content

Commit

Permalink
Add LinearCast rule to check for improper object and pointer casts
Browse files Browse the repository at this point in the history
  • Loading branch information
fourls committed Oct 17, 2023
1 parent 0887ef4 commit ef210fa
Show file tree
Hide file tree
Showing 5 changed files with 379 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public final class CheckList {
MissingSemicolonCheck.class,
MixedNamesCheck.class,
NilComparisonCheck.class,
NonLinearCastCheck.class,
NoSonarCheck.class,
InstanceInvokedConstructorCheck.class,
ObjectTypeCheck.class,
Expand Down
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.";
}
}
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>
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"
}
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);
}
}

0 comments on commit ef210fa

Please sign in to comment.