Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SONARPY-1829 tmp commit #1797

Draft
wants to merge 31 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c13089b
SONARPY-1797 Introduce new type inference model classes
maksim-grebeniuk-sonarsource Apr 9, 2024
cac6e8f
SONARPY-1753 Introduce a new symbol model decoupled from types
maksim-grebeniuk-sonarsource Apr 9, 2024
0fee527
SONARPY-1758 Create basic flow insensitive engine to populate the new…
guillaume-dequenne-sonarsource Apr 11, 2024
682f547
SONARPY-1764 Infer type of function declaration and function calls
guillaume-dequenne-sonarsource Apr 15, 2024
e20765a
SONARPY-1760 Infer types of imported serialized module (#1765)
maksim-grebeniuk-sonarsource Apr 22, 2024
84ac082
SONARPY-1785 Run the new symbol table builder and type inference engi…
guillaume-dequenne-sonarsource Apr 22, 2024
dfa7c15
SONARPY-1786 Add basic information to locally defined ClassType (pare…
guillaume-dequenne-sonarsource Apr 24, 2024
8b88fc1
SONARPY-1782 Migrate NonCallableCalledCheck to the new type model
guillaume-dequenne-sonarsource Apr 24, 2024
79ebca8
SONARPY-1789 Resolve inherited class members (#1770)
guillaume-dequenne-sonarsource Apr 25, 2024
968be3a
SONARPY-1794 Avoid inferring types for global variables (#1771)
guillaume-dequenne-sonarsource Apr 25, 2024
272c70c
SONARPY-1796 Infer types for set, dict and tuple literals (#1773)
guillaume-dequenne-sonarsource Apr 26, 2024
be9ce42
SONARPY-1798 Try to resolve built-in types for names which have no sy…
maksim-grebeniuk-sonarsource Apr 26, 2024
5ef0ca4
SONARPY-1800 NonCallableCallCheck migration: Use the display name of …
guillaume-dequenne-sonarsource Apr 29, 2024
d936bd4
SONARPY-1803 Store type definition location in the new type model (#1…
guillaume-dequenne-sonarsource Apr 29, 2024
f7d43fe
SONARPY-1807 SymbolTableBuilderV2 cleanup
maksim-grebeniuk-sonarsource May 1, 2024
36b6bd4
SONARPY-1807 Populate the SymbolTable out of SymbolTableBuilderV2 and…
maksim-grebeniuk-sonarsource May 1, 2024
c1b1d82
SONARPY-1810 Track types in case of multiple assignments in module scope
guillaume-dequenne-sonarsource May 2, 2024
3bf7fcf
Refactor ParameterV2 to record
guillaume-dequenne-sonarsource May 6, 2024
90e70d8
SONARPY-1819 Improve testing to compare types
guillaume-dequenne-sonarsource May 6, 2024
c35f63d
Remove redundant test
guillaume-dequenne-sonarsource May 6, 2024
be354d2
SONARPY-1818 Enable flow-sensitive type inference within functions
guillaume-dequenne-sonarsource May 6, 2024
f598d80
Implement PythonType#hasMember for UnionType
guillaume-dequenne-sonarsource May 6, 2024
27fde47
Basic implementation of ClassType and FunctionType toString
guillaume-dequenne-sonarsource May 6, 2024
7c38c22
SONARPY-1815 Enable AST-based type inference for functions/module con…
guillaume-dequenne-sonarsource May 6, 2024
b7005eb
SONARPY-1825 Return Python.UNKNOWN instead of ObjectType[PythonType.U…
guillaume-dequenne-sonarsource May 7, 2024
75910e2
SONARPY-1826 tmp
guillaume-dequenne-sonarsource May 7, 2024
57a4061
Use a single propagationByLhs instead of definitionsByLhs and assignm…
guillaume-dequenne-sonarsource May 7, 2024
964e13a
Minor changes
guillaume-dequenne-sonarsource May 7, 2024
97d943f
Add test on recursion
guillaume-dequenne-sonarsource May 8, 2024
a14f095
Represent ClassDef in the CFG
guillaume-dequenne-sonarsource May 7, 2024
4a5bb73
SONARPY-1829 tmp commit
guillaume-dequenne-sonarsource May 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,47 @@

import javax.annotation.Nullable;
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.types.InferredType;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.tree.CallExpression;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.TriBool;

import static org.sonar.python.tree.TreeUtils.nameFromExpression;

@Rule(key = "S5756")
public class NonCallableCalledCheck extends NonCallableCalled {
public class NonCallableCalledCheck extends PythonSubscriptionCheck {

@Override
public boolean isNonCallableType(InferredType type) {
return !type.canHaveMember("__call__");
public void initialize(Context context) {
context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, ctx -> {
CallExpression callExpression = (CallExpression) ctx.syntaxNode();
Expression callee = callExpression.callee();
PythonType type = callee.typeV2();
if (isNonCallableType(type)) {
String name = nameFromExpression(callee);
PreciseIssue preciseIssue = ctx.addIssue(callee, message(type, name));
type.definitionLocation()
.ifPresent(location -> preciseIssue.secondary(location, "Definition."));
}
});
}

@Override
public String message(InferredType calleeType, @Nullable String name) {
protected static String addTypeName(PythonType type) {
return type.displayName()
.map(d -> " has type " + d + " and it")
.orElse("");
}

public boolean isNonCallableType(PythonType type) {
return type.hasMember("__call__") == TriBool.FALSE;
}

public String message(PythonType typeV2, @Nullable String name) {
if (name != null) {
return String.format("Fix this call; \"%s\"%s is not callable.", name, addTypeName(calleeType));
return "Fix this call; \"%s\"%s is not callable.".formatted(name, addTypeName(typeV2));
}
return String.format("Fix this call; this expression%s is not callable.", addTypeName(calleeType));
return "Fix this call; this expression%s is not callable.".formatted(addTypeName(typeV2));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.Set;
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.cfg.CfgBlock;
import org.sonar.plugins.python.api.tree.ClassDef;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.python.cfg.fixpoint.LiveVariablesAnalysis.LiveVariables;
Expand All @@ -51,7 +52,11 @@ public static List<UnnecessaryAssignment> findUnnecessaryAssignments(CfgBlock bl
blockLiveVariables.getSymbolReadWrites(element).forEach((symbol, symbolReadWrite) -> {
if (symbolReadWrite.isWrite() && !symbolReadWrite.isRead()) {
if (!element.is(Tree.Kind.IMPORT_NAME) && !willBeRead.contains(symbol) && functionDef.localVariables().contains(symbol)) {
unnecessaryAssignments.add(new UnnecessaryAssignment(symbol, element));
Tree elementToReport = element;
if (elementToReport instanceof ClassDef classDefElement) {
elementToReport = classDefElement.name();
}
unnecessaryAssignments.add(new UnnecessaryAssignment(symbol, elementToReport));
}
willBeRead.remove(symbol);
} else if (symbolReadWrite.isRead()) {
Expand Down
5 changes: 5 additions & 0 deletions python-checks/src/test/resources/checks/deadStore.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,11 @@ class a: # Noncompliant {{Remove this assignment to local variable 'a'; the valu
# ^^^^^^< 1 {{'a' is reassigned here.}}
print(a)

def function_dead_store():
def my_func(): ...
my_func = 42 # FN
print(my_func)

def multiple_issues(a):
b = 2 # Noncompliant
if a:
Expand Down
65 changes: 61 additions & 4 deletions python-checks/src/test/resources/checks/nonCallableCalled.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ def call_noncallable(p):
dict_var = {}
dict_var() # Noncompliant

set_literal = {1, 2}
set_literal() # Noncompliant

set_var = set()
set_var() # Noncompliant

Expand All @@ -37,7 +40,11 @@ def call_noncallable(p):
x = 42
else:
x = 'str'
x() # Noncompliant {{Fix this call; "x" is not callable.}}
x() # Noncompliant


def call_no_name():
42() # Noncompliant {{Fix this call; this expression has type int and it is not callable.}}

def flow_sensitivity():
my_var = "hello"
Expand Down Expand Up @@ -69,16 +76,16 @@ def member_access():
def types_from_typeshed(foo):
from math import acos
from functools import wraps
acos(42)() # Noncompliant {{Fix this call; this expression has type float and it is not callable.}}
# ^^^^^^^^
acos(42)() # FN: declared return type of Typeshed
wraps(func)(foo) # OK, wraps returns a Callable

def with_metaclass():
class Factory: ...
class Base(metaclass=Factory): ...
class A(Base): ...
a = A()
a() # OK
# TODO: resolve type hierarchy and metaclasses
a() # Noncompliant


def decorators():
Expand Down Expand Up @@ -181,3 +188,53 @@ def typing_named_tuple_no_fp():
from typing import NamedTuple
Employee = NamedTuple('Employee', [('name', str), ('id', int)])
employee = Employee("Sam", 42)


class Parent:
def __call__(self):
...

class Child(Parent):
...


def inherited_call_method():
child = Child()
child() # OK


some_global_func = None

def assigning_global(my_func):
global some_global_func
some_global_func = my_func

def calling_global_func():
some_global_func() # OK


some_nonlocal_var = 42

def using_nonlocal_var():
nonlocal some_nonlocal_var
some_nonlocal_var() # Noncompliant


def reassigned_function():
if cond:
def my_callable(): ...
my_callable() # OK
else:
def my_callable(): ...
my_callable = 42
my_callable() # Noncompliant


def reassigned_class():
if cond:
class MyClass(): ...
MyClass() # OK
else:
def MyClass(): ...
MyClass = 42
MyClass() # Noncompliant
Original file line number Diff line number Diff line change
Expand Up @@ -19,38 +19,5 @@
*/
package org.sonar.plugins.python.api;

public class LocationInFile {
private final String fileId;
private final int startLine;
private final int startLineOffset;
private final int endLine;
private final int endLineOffset;

public LocationInFile(String fileId, int startLine, int startLineOffset, int endLine, int endLineOffset) {
this.fileId = fileId;
this.startLine = startLine;
this.startLineOffset = startLineOffset;
this.endLine = endLine;
this.endLineOffset = endLineOffset;
}

public String fileId() {
return fileId;
}

public int startLine() {
return startLine;
}

public int startLineOffset() {
return startLineOffset;
}

public int endLine() {
return endLine;
}

public int endLineOffset() {
return endLineOffset;
}
public record LocationInFile(String fileId, int startLine, int startLineOffset, int endLine, int endLineOffset) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
import org.sonar.python.caching.CacheContextImpl;
import org.sonar.python.semantic.ProjectLevelSymbolTable;
import org.sonar.python.semantic.SymbolTableBuilder;
import org.sonar.python.semantic.v2.ProjectLevelTypeTable;
import org.sonar.python.semantic.v2.SymbolTableBuilderV2;
import org.sonar.python.semantic.v2.TypeInferenceV2;

public class PythonVisitorContext extends PythonInputFileContext {

Expand All @@ -44,6 +47,8 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
this.parsingException = null;
SymbolTableBuilder symbolTableBuilder = packageName != null ? new SymbolTableBuilder(packageName, pythonFile) : new SymbolTableBuilder(pythonFile);
symbolTableBuilder.visitFileInput(rootTree);
var symbolTable = new SymbolTableBuilderV2(rootTree).build();
new TypeInferenceV2(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty()), pythonFile, symbolTable).inferTypes(rootTree);
}

public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
Expand All @@ -52,6 +57,10 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
this.rootTree = rootTree;
this.parsingException = null;
new SymbolTableBuilder(packageName, pythonFile, projectLevelSymbolTable).visitFileInput(rootTree);

var symbolTable = new SymbolTableBuilderV2(rootTree)
.build();
new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile, symbolTable).inferTypes(rootTree);
}

public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
Expand All @@ -60,6 +69,9 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
this.rootTree = rootTree;
this.parsingException = null;
new SymbolTableBuilder(packageName, pythonFile, projectLevelSymbolTable).visitFileInput(rootTree);
var symbolTable = new SymbolTableBuilderV2(rootTree)
.build();
new TypeInferenceV2(new ProjectLevelTypeTable(projectLevelSymbolTable), pythonFile, symbolTable).inferTypes(rootTree);
}

public PythonVisitorContext(PythonFile pythonFile, RecognitionException parsingException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,18 @@
import com.google.common.annotations.Beta;
import org.sonar.plugins.python.api.types.InferredType;
import org.sonar.python.types.InferredTypes;
import org.sonar.python.types.v2.PythonType;

public interface Expression extends Tree {

@Beta
default InferredType type() {
return InferredTypes.anyType();
}

@Beta
default PythonType typeV2() {
return PythonType.UNKNOWN;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
*/
package org.sonar.plugins.python.api.tree;

import org.sonar.python.semantic.v2.SymbolV2;

/**
* See https://docs.python.org/3/reference/expressions.html#atom-identifiers
*/
Expand All @@ -28,4 +30,6 @@ public interface Name extends Expression, HasSymbol {

// FIXME: we should create a separate tree for Variables
boolean isVariable();

SymbolV2 symbolV2();
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import javax.annotation.CheckForNull;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.symbols.Usage;
import org.sonar.python.types.v2.PythonType;

/**
* Qualified expression like "foo.bar"
Expand Down Expand Up @@ -56,4 +57,9 @@ default Symbol symbol() {
default Usage usage() {
return name().usage();
}

@Override
default PythonType typeV2() {
return name().typeV2();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ private PythonCfgBlock build(Statement statement, PythonCfgBlock currentBlock) {

private PythonCfgBlock buildClassDefStatement(ClassDef classDef, PythonCfgBlock currentBlock) {
PythonCfgBlock block = build(classDef.body().statements(), currentBlock);
block.addElement(classDef.name()); // represents binding to class name
block.addElement(classDef); // represents binding to class name
classDef.decorators().stream().forEach(currentBlock::addElement);
return block;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public static List<Expression> assignmentsLhs(AssignmentStatement assignmentStat
.toList();
}

static List<Name> boundNamesFromExpression(@CheckForNull Tree tree) {
public static List<Name> boundNamesFromExpression(@CheckForNull Tree tree) {
List<Name> names = new ArrayList<>();
if (tree == null) {
return names;
Expand Down