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-2013 Make it possible to OR type predicates #1871

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions its/ruling/src/test/resources/expected/python-S3699.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
{
"project:biopython/Bio/Restriction/Restriction.py": [
2432
],
"project:django-2.2.3/django/core/files/locks.py": [
108,
112
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,27 +19,22 @@
*/
package org.sonar.python.checks;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.sonar.check.Rule;
import org.sonar.plugins.python.api.PythonSubscriptionCheck;
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.Name;
import org.sonar.plugins.python.api.tree.RaiseStatement;
import org.sonar.plugins.python.api.tree.Tree.Kind;
import org.sonar.plugins.python.api.types.InferredType;
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.TriBool;
import org.sonar.python.types.v2.TypeCheckerContext;

import static org.sonar.plugins.python.api.types.BuiltinTypes.BASE_EXCEPTION;
import static org.sonar.plugins.python.api.types.BuiltinTypes.EXCEPTION;

@Rule(key = "S112")
public class GenericExceptionRaisedCheck extends PythonSubscriptionCheck {

private static final Set<String> GENERIC_EXCEPTION_NAMES = new HashSet<>(Arrays.asList(EXCEPTION, BASE_EXCEPTION));

@Override
public void initialize(Context context) {
context.registerSyntaxNodeConsumer(Kind.RAISE_STMT, ctx -> {
Expand All @@ -49,19 +44,16 @@ public void initialize(Context context) {
return;
}
Expression expression = expressions.get(0);
InferredType type = expression.type();
if (GENERIC_EXCEPTION_NAMES.stream().anyMatch(type::canOnlyBe) || isGenericExceptionClass(expression)) {
PythonType pythonType = expression.typeV2();
TypeCheckerContext typeCheckerContext = ctx.typeChecker();
TriBool isExceptionOrBaseException = typeCheckerContext.or(
typeCheckerContext.typeChecker().isBuiltinWithName(EXCEPTION),
typeCheckerContext.typeChecker().isBuiltinWithName(BASE_EXCEPTION)
)
.check(pythonType);
if (isExceptionOrBaseException == TriBool.TRUE) {
ctx.addIssue(expression, "Replace this generic exception class with a more specific one.");
}
});
}

private static boolean isGenericExceptionClass(Expression expression) {
if (expression.is(Kind.NAME)) {
Symbol symbol = ((Name) expression).symbol();
return symbol != null && GENERIC_EXCEPTION_NAMES.contains(symbol.fullyQualifiedName());
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.sonar.plugins.python.api.tree.ComprehensionExpression;
import org.sonar.plugins.python.api.tree.ComprehensionIf;
import org.sonar.plugins.python.api.tree.ConditionalExpression;
import org.sonar.plugins.python.api.tree.DictCompExpression;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.ExpressionList;
import org.sonar.plugins.python.api.tree.FunctionDef;
Expand Down Expand Up @@ -194,7 +195,7 @@ public void visitComprehensionIf(ComprehensionIf tree) {
}

@Override
public void visitDictCompExpression(DictCompExpressionImpl tree) {
public void visitDictCompExpression(DictCompExpression tree) {
// ignore, not broken down in the cfg
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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.TypeCheckerContext;
import org.sonar.python.types.v2.PythonType;
import org.sonar.python.types.v2.TriBool;

Expand All @@ -39,7 +40,7 @@ public void initialize(Context context) {
CallExpression callExpression = (CallExpression) ctx.syntaxNode();
Expression callee = callExpression.callee();
PythonType type = callee.typeV2();
if (isNonCallableType(type)) {
if (isNonCallableType(type, ctx.typeChecker())) {
String name = nameFromExpression(callee);
PreciseIssue preciseIssue = ctx.addIssue(callee, message(type, name));
type.definitionLocation()
Expand All @@ -54,8 +55,8 @@ protected static String addTypeName(PythonType type) {
.orElse("");
}

public boolean isNonCallableType(PythonType type) {
return type.hasMember("__call__") == TriBool.FALSE;
public boolean isNonCallableType(PythonType type, TypeCheckerContext typeCheckerContext) {
return typeCheckerContext.typeChecker().hasMember("__call__").check(type) == TriBool.FALSE;
}

public String message(PythonType typeV2, @Nullable String name) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
import org.sonar.plugins.python.api.tree.CallExpression;
import org.sonar.plugins.python.api.tree.Expression;
import org.sonar.plugins.python.api.tree.RegularArgument;
import org.sonar.python.types.InferredTypes;
import org.sonar.python.types.v2.TriBool;
import org.sonar.python.types.v2.TypeChecker;

import static org.sonar.plugins.python.api.tree.Tree.Kind.ASSIGNMENT_STMT;
import static org.sonar.plugins.python.api.tree.Tree.Kind.CALL_EXPR;
Expand All @@ -45,7 +46,12 @@ public void initialize(Context context) {
}

private static void checkReturnValue(Expression expression, SubscriptionContext ctx) {
if (expression.is(CALL_EXPR) && expression.type().equals(InferredTypes.NONE)) {
if (!expression.is(CALL_EXPR)) {
return;
}
TypeChecker typeChecker = ctx.typeChecker().typeChecker().isBuiltinWithName("NoneType");
boolean noneType = typeChecker.check(expression.typeV2()) == TriBool.TRUE;
if (noneType) {
CallExpression callExpression = (CallExpression) expression;
Optional.ofNullable(callExpression.calleeSymbol())
.ifPresent(symbol -> ctx.addIssue(expression, String.format(MESSAGE, symbol.name(), symbol.name())));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,40 @@ def win32():
# We should not raise issues as the stubs of win32 are inaccurate
path = win32pdh.MakeCounterPath((None, None, None, None, -1, None)) # OK
hc = win32pdh.AddCounter(None, path)


class SomeClass:
def __init__(self):
...

class SomeOtherClass:
...

def foo():
# FN: we should fallback to "type.__init__" when actual type not available
x = SomeClass.__init__(...) # FN
y = SomeOtherClass.__init__(...) # FN SONARPY-2007


def using_fcntl():
import fcntl
ret = fcntl.flock(..., ...) # Noncompliant



def list_comprehensions():
exp_list = [x() for y in unknown()]
actual_list = [...]
for _ in range(len(exp_list)):
actual_list.append(smth())
self.assertEqual(
exp_list.sort(), # Noncompliant
actual_list.sort() # Noncompliant
)


def import_in_different_branch():
if x:
import fcntl
def lock():
ret = fcntl.flock(..., ...) # Noncompliant
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.sonar.plugins.python.api.PythonCheck.PreciseIssue;
import org.sonar.plugins.python.api.caching.CacheContext;
import org.sonar.plugins.python.api.tree.FileInput;
import org.sonar.python.types.v2.TypeCheckerContext;
import org.sonar.python.caching.CacheContextImpl;
import org.sonar.python.semantic.ProjectLevelSymbolTable;
import org.sonar.python.semantic.SymbolTableBuilder;
Expand All @@ -41,6 +42,7 @@ public class PythonVisitorContext extends PythonInputFileContext {
private final FileInput rootTree;
private final RecognitionException parsingException;
private List<PreciseIssue> issues = new ArrayList<>();
private final TypeCheckerContext typeCheckerContext;

public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, @Nullable String packageName) {
super(pythonFile, workingDirectory, CacheContextImpl.dummyCache());
Expand All @@ -51,6 +53,7 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
var symbolTable = new SymbolTableBuilderV2(rootTree).build();
var projectLevelTypeTable = new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty(), new TypeShed(ProjectLevelSymbolTable.empty()));
new TypeInferenceV2(projectLevelTypeTable, pythonFile, symbolTable).inferTypes(rootTree);
this.typeCheckerContext = new TypeCheckerContext(projectLevelTypeTable);
}

public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
Expand All @@ -64,6 +67,7 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
.build();
var projectLevelTypeTable = new ProjectLevelTypeTable(projectLevelSymbolTable, new TypeShed(projectLevelSymbolTable));
new TypeInferenceV2(projectLevelTypeTable, pythonFile, symbolTable).inferTypes(rootTree);
this.typeCheckerContext = new TypeCheckerContext(projectLevelTypeTable);
}

public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable File workingDirectory, String packageName,
Expand All @@ -76,24 +80,31 @@ public PythonVisitorContext(FileInput rootTree, PythonFile pythonFile, @Nullable
.build();
var projectLevelTypeTable = new ProjectLevelTypeTable(projectLevelSymbolTable, typeShed);
new TypeInferenceV2(projectLevelTypeTable, pythonFile, symbolTable).inferTypes(rootTree);
this.typeCheckerContext = new TypeCheckerContext(projectLevelTypeTable);
}

public PythonVisitorContext(PythonFile pythonFile, RecognitionException parsingException) {
super(pythonFile, null, CacheContextImpl.dummyCache());
this.rootTree = null;
this.parsingException = parsingException;
this.typeCheckerContext = new TypeCheckerContext(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty()));
}

public PythonVisitorContext(PythonFile pythonFile, RecognitionException parsingException, SonarProduct sonarProduct) {
super(pythonFile, null, CacheContextImpl.dummyCache(), sonarProduct);
this.rootTree = null;
this.parsingException = parsingException;
this.typeCheckerContext = new TypeCheckerContext(new ProjectLevelTypeTable(ProjectLevelSymbolTable.empty()));
}

public FileInput rootTree() {
return rootTree;
}

public TypeCheckerContext typeChecker() {
return typeCheckerContext;
}

public RecognitionException parsingException() {
return parsingException;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.sonar.plugins.python.api.symbols.Symbol;
import org.sonar.plugins.python.api.tree.Token;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.python.types.v2.TypeCheckerContext;

public interface SubscriptionContext {
Tree syntaxNode();
Expand Down Expand Up @@ -67,4 +68,6 @@ public interface SubscriptionContext {

@Beta
CacheContext cacheContext();

TypeCheckerContext typeChecker();
}
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ public void visitKeyValuePattern(KeyValuePattern keyValuePattern) {
}

@Override
public void visitDictCompExpression(DictCompExpressionImpl tree) {
public void visitDictCompExpression(DictCompExpression tree) {
scan(tree.keyExpression());
scan(tree.valueExpression());
scan(tree.comprehensionFor());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ public interface TreeVisitor {

void visitKeyValuePattern(KeyValuePattern keyValuePattern);

void visitDictCompExpression(DictCompExpressionImpl dictCompExpression);
void visitDictCompExpression(DictCompExpression dictCompExpression);

void visitCompoundAssignment(CompoundAssignmentStatement compoundAssignmentStatement);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import org.sonar.plugins.python.api.tree.Token;
import org.sonar.plugins.python.api.tree.Tree;
import org.sonar.plugins.python.api.tree.Tree.Kind;
import org.sonar.python.types.v2.TypeCheckerContext;
import org.sonar.python.regex.PythonAnalyzerRegexSource;
import org.sonar.python.regex.PythonRegexIssueLocation;
import org.sonar.python.regex.RegexContext;
Expand Down Expand Up @@ -182,6 +183,11 @@ public CacheContext cacheContext() {
return pythonVisitorContext.cacheContext();
}

@Override
public TypeCheckerContext typeChecker() {
return pythonVisitorContext.typeChecker();
}

public RegexParseResult regexForStringElement(StringElement stringElement, FlagSet flagSet) {
return regexCache.computeIfAbsent(stringElement.hashCode() + "-" + flagSet.getMask(),
s -> new RegexParser(new PythonAnalyzerRegexSource(stringElement), flagSet).parse());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.sonar.plugins.python.api.tree.ComprehensionExpression;
import org.sonar.plugins.python.api.tree.ComprehensionFor;
import org.sonar.plugins.python.api.tree.Decorator;
import org.sonar.plugins.python.api.tree.DictCompExpression;
import org.sonar.plugins.python.api.tree.DottedName;
import org.sonar.plugins.python.api.tree.ExceptClause;
import org.sonar.plugins.python.api.tree.Expression;
Expand Down Expand Up @@ -294,7 +295,7 @@ public void visitLambda(LambdaExpression pyLambdaExpressionTree) {
}

@Override
public void visitDictCompExpression(DictCompExpressionImpl tree) {
public void visitDictCompExpression(DictCompExpression tree) {
createScope(tree, currentScope());
enterScope(tree);
super.visitDictCompExpression(tree);
Expand Down Expand Up @@ -662,7 +663,7 @@ public void visitPyListOrSetCompExpression(ComprehensionExpression tree) {
}

@Override
public void visitDictCompExpression(DictCompExpressionImpl tree) {
public void visitDictCompExpression(DictCompExpression tree) {
enterScope(tree);
scan(tree.keyExpression());
scan(tree.valueExpression());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.sonar.plugins.python.api.tree.ClassDef;
import org.sonar.plugins.python.api.tree.ComprehensionExpression;
import org.sonar.plugins.python.api.tree.ComprehensionFor;
import org.sonar.plugins.python.api.tree.DictCompExpression;
import org.sonar.plugins.python.api.tree.FileInput;
import org.sonar.plugins.python.api.tree.FunctionDef;
import org.sonar.plugins.python.api.tree.LambdaExpression;
Expand Down Expand Up @@ -91,7 +92,7 @@ public void visitPyListOrSetCompExpression(ComprehensionExpression tree) {
}

@Override
public void visitDictCompExpression(DictCompExpressionImpl tree) {
public void visitDictCompExpression(DictCompExpression tree) {
enterScope(tree);
scan(tree.keyExpression());
scan(tree.valueExpression());
Expand Down
Loading