Skip to content

Commit ce4da1a

Browse files
guillaume-dequennesonartech
authored andcommitted
SONARPY-3289 Rule S7695: "super()" calls should not be used in TorchScript methods (#474)
GitOrigin-RevId: b328d90ab0663517880711b44d231d2f3895fd0f
1 parent a81cfec commit ce4da1a

4 files changed

Lines changed: 136 additions & 0 deletions

File tree

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<p>This rule raises an issue when <code>super()</code> calls are used in methods that are decorated with TorchScript decorators like
2+
<code>@torch.jit.script_method</code> or within classes likely to be compiled with TorchScript.</p>
3+
<h2>Why is this an issue?</h2>
4+
<p>TorchScript has limited support for Python’s <code>super()</code> mechanism, which can lead to compilation errors when converting PyTorch models
5+
for deployment.</p>
6+
<p>TorchScript is PyTorch’s way to create serializable and optimizable models from PyTorch code. It allows you to run models independently from
7+
Python, which is essential for production deployment, mobile applications, and performance optimization.</p>
8+
<p>However, TorchScript operates with a subset of Python’s features. The <code>super()</code> function relies on Python’s method resolution order
9+
(MRO) and dynamic attribute lookup, which are not fully supported in TorchScript’s static compilation environment.</p>
10+
<p>When TorchScript encounters <code>super()</code> calls, it may fail to properly resolve the method calls during compilation, resulting in runtime
11+
errors or unexpected behavior. This is particularly problematic in <code>forward()</code> methods of neural network modules, where inheritance is
12+
commonly used.</p>
13+
<h3>What is the potential impact?</h3>
14+
<p>Using <code>super()</code> calls in TorchScript methods can cause:</p>
15+
<ul>
16+
<li> Compilation failures when converting models to TorchScript format </li>
17+
<li> Runtime errors in deployed models </li>
18+
<li> Inconsistent behavior between eager mode and TorchScript execution </li>
19+
<li> Deployment issues in production environments and mobile applications </li>
20+
</ul>
21+
<h3>How to fix in PyTorch?</h3>
22+
<p>Replace super() calls with direct method calls or refactor the inheritance structure to avoid super() usage in TorchScript methods.</p>
23+
<h4>Non-compliant code example</h4>
24+
<pre data-diff-id="1" data-diff-type="noncompliant">
25+
import torch
26+
import torch.nn as nn
27+
28+
class MyModel(nn.Module):
29+
@torch.jit.script_method
30+
def forward(self, x):
31+
return super().forward(x) # Noncompliant
32+
</pre>
33+
<h4>Compliant code example</h4>
34+
<pre data-diff-id="1" data-diff-type="compliant">
35+
import torch
36+
import torch.nn as nn
37+
38+
class MyModel(nn.Module):
39+
def forward(self, x):
40+
# Avoid super() in TorchScript methods
41+
return self.process(x)
42+
43+
def process(self, x):
44+
return x
45+
</pre>
46+
<p>For complex inheritance scenarios, explicitly call parent class methods by name instead of using super().</p>
47+
<h4>Non-compliant code example</h4>
48+
<pre data-diff-id="2" data-diff-type="noncompliant">
49+
import torch
50+
import torch.nn as nn
51+
52+
class BaseModel(nn.Module):
53+
def forward(self, x):
54+
return x * 2
55+
56+
class DerivedModel(BaseModel):
57+
@torch.jit.script_method
58+
def forward(self, x):
59+
result = super().forward(x) # Noncompliant
60+
return result + 1
61+
</pre>
62+
<h4>Compliant code example</h4>
63+
<pre data-diff-id="2" data-diff-type="compliant">
64+
import torch
65+
import torch.nn as nn
66+
67+
class BaseModel(nn.Module):
68+
def forward(self, x):
69+
return x * 2
70+
71+
def base_forward(self, x):
72+
return x * 2
73+
74+
class DerivedModel(BaseModel):
75+
@torch.jit.script_method
76+
def forward(self, x):
77+
result = self.base_forward(x)
78+
return result + 1
79+
</pre>
80+
<h3>Documentation</h3>
81+
<ul>
82+
<li> TorchScript Language Reference - <a href="https://pytorch.org/docs/stable/jit_language_reference.html">Official documentation on TorchScript
83+
language features and limitations</a> </li>
84+
<li> TorchScript Unsupported Features - <a href="https://pytorch.org/docs/stable/jit_unsupported.html">List of Python features not supported in
85+
TorchScript</a> </li>
86+
<li> PyTorch JIT Tutorial - <a href="https://pytorch.org/tutorials/beginner/Intro_to_TorchScript_tutorial.html">Introduction to TorchScript and best
87+
practices</a> </li>
88+
</ul>
89+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"title": "\"super()\" calls should not be used in TorchScript methods",
3+
"type": "BUG",
4+
"status": "ready",
5+
"remediation": {
6+
"func": "Constant\/Issue",
7+
"constantCost": "30 min"
8+
},
9+
"tags": [
10+
"pytorch",
11+
"torchscript",
12+
"deployment"
13+
],
14+
"defaultSeverity": "Blocker",
15+
"ruleSpecification": "RSPEC-7695",
16+
"sqKey": "S7695",
17+
"scope": "Main",
18+
"quickfix": "unknown",
19+
"code": {
20+
"impacts": {
21+
"RELIABILITY": "BLOCKER",
22+
"MAINTAINABILITY": "BLOCKER"
23+
},
24+
"attribute": "CONVENTIONAL"
25+
}
26+
}

python-frontend/src/main/java/org/sonar/python/types/v2/TypeCheckBuilder.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ public TriBool test(PythonType pythonType) {
229229
if (types.stream().anyMatch(t -> (t instanceof ClassType ct) && ct.fullyQualifiedName().equals(expectedFqnName))) {
230230
return TriBool.TRUE;
231231
}
232+
if (expectedType instanceof UnknownType && types.stream().anyMatch(t -> new IsTypeWithFullyQualifiedNamePredicate(expectedFqnName).test(t).isTrue())) {
233+
return TriBool.TRUE;
234+
}
232235
}
233236
if (expectedType instanceof ClassType expectedClassType) {
234237
return isClassInheritedFrom(pythonType, expectedClassType);

python-frontend/src/test/java/org/sonar/python/types/v2/TypeCheckerTest.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,24 @@ class C: ...
432432
assertThat(localTypeChecker.typeCheckBuilder().isSubtypeOf("my_package.unknown.Other").check(bType)).isEqualTo(TriBool.UNKNOWN);
433433
assertThat(localTypeChecker.typeCheckBuilder().isSubtypeOf("my_package.lib.LibClass").check(bType)).isEqualTo(TriBool.FALSE);
434434
assertThat(localTypeChecker.typeCheckBuilder().isSubtypeOf("my_package.lib.UnresolvedLibClass").check(bType)).isEqualTo(TriBool.UNKNOWN);
435+
}
436+
437+
@Test
438+
void isSubtypeOfUnresolvedTest() {
439+
ProjectLevelSymbolTable projectLevelSymbolTable = ProjectLevelSymbolTable.empty();
440+
PythonFile modFile = PythonTestUtils.pythonFile("mod.py");
441+
String input = """
442+
from unknown import UnresolvedBase
443+
class A(UnresolvedBase): ...
444+
A
445+
""";
446+
var modFileInput = parseWithoutSymbols(input);
447+
projectLevelSymbolTable.addModule(modFileInput, "my_package", modFile);
448+
ProjectLevelTypeTable projectLevelTypeTable = new ProjectLevelTypeTable(projectLevelSymbolTable);
435449

450+
var aType = ((ExpressionStatement) modFileInput.statements().statements().get(2)).expressions().get(0).typeV2();
451+
TypeChecker checker = new TypeChecker(projectLevelTypeTable);
452+
TypeCheckBuilder subtypeOf = checker.typeCheckBuilder().isSubtypeOf("unknown.UnresolvedBase");
453+
assertThat(subtypeOf.check(aType).isTrue()).isTrue();
436454
}
437455
}

0 commit comments

Comments
 (0)