diff --git a/python-checks/src/main/java/org/sonar/python/checks/TempFileCreationCheck.java b/python-checks/src/main/java/org/sonar/python/checks/TempFileCreationCheck.java index c4269f5f4d..3df94afa71 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/TempFileCreationCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/TempFileCreationCheck.java @@ -27,7 +27,7 @@ import org.sonar.python.PythonSubscriptionCheck; import org.sonar.python.api.tree.PyCallExpressionTree; import org.sonar.python.api.tree.Tree; -import org.sonar.python.semantic.Symbol; +import org.sonar.python.semantic.TreeSymbol; @Rule(key = "S5445") public class TempFileCreationCheck extends PythonSubscriptionCheck { @@ -38,15 +38,15 @@ public class TempFileCreationCheck extends PythonSubscriptionCheck { public void initialize(Context context) { context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, ctx -> { PyCallExpressionTree callExpr = (PyCallExpressionTree) ctx.syntaxNode(); - Symbol symbol = ctx.symbolTable().getSymbol(callExpr); + TreeSymbol symbol = callExpr.calleeSymbol(); isInsecureTempFile(symbol).ifPresent(s -> ctx.addIssue(callExpr, String.format("'%s' is insecure. Use 'tempfile.TemporaryFile' instead", s))); }); } - private static Optional isInsecureTempFile(@Nullable Symbol symbol) { + private static Optional isInsecureTempFile(@Nullable TreeSymbol symbol) { if (symbol == null) { return Optional.empty(); } - return SUSPICIOUS_CALLS.stream().filter(symbol.qualifiedName()::equals).findFirst(); + return SUSPICIOUS_CALLS.stream().filter(call -> call.equals(symbol.fullyQualifiedName())).findFirst(); } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/hotspots/ClearTextProtocolsCheck.java b/python-checks/src/main/java/org/sonar/python/checks/hotspots/ClearTextProtocolsCheck.java index b40dfddf82..f306b0867b 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/hotspots/ClearTextProtocolsCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/hotspots/ClearTextProtocolsCheck.java @@ -33,7 +33,7 @@ import org.sonar.python.api.tree.PyCallExpressionTree; import org.sonar.python.api.tree.PyStringElementTree; import org.sonar.python.api.tree.Tree; -import org.sonar.python.semantic.Symbol; +import org.sonar.python.semantic.TreeSymbol; @Rule(key = "S5332") public class ClearTextProtocolsCheck extends PythonSubscriptionCheck { @@ -59,7 +59,7 @@ public void initialize(Context context) { .ifPresent(protocol -> ctx.addIssue(node, message(protocol))); }); context.registerSyntaxNodeConsumer(Tree.Kind.CALL_EXPR, ctx -> { - Symbol symbol = ctx.symbolTable().getSymbol((PyCallExpressionTree) ctx.syntaxNode()); + TreeSymbol symbol = ((PyCallExpressionTree) ctx.syntaxNode()).calleeSymbol(); isUnsafeLib(symbol).ifPresent(protocol -> ctx.addIssue(ctx.syntaxNode(), message(protocol))); }); } @@ -89,9 +89,9 @@ private static Optional unsafeProtocol(String literalValue) { return Optional.empty(); } - private static Optional isUnsafeLib(@Nullable Symbol symbol) { + private static Optional isUnsafeLib(@Nullable TreeSymbol symbol) { if (symbol != null) { - String qualifiedName = symbol.qualifiedName(); + String qualifiedName = symbol.fullyQualifiedName(); if ("telnetlib.Telnet".equals(qualifiedName)) { return Optional.of("telnet"); } diff --git a/python-checks/src/main/java/org/sonar/python/checks/hotspots/DebugModeCheck.java b/python-checks/src/main/java/org/sonar/python/checks/hotspots/DebugModeCheck.java index a65f6b0941..e58d56e936 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/hotspots/DebugModeCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/hotspots/DebugModeCheck.java @@ -23,9 +23,9 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import javax.annotation.CheckForNull; import org.sonar.check.Rule; import org.sonar.python.PythonSubscriptionCheck; -import org.sonar.python.SubscriptionContext; import org.sonar.python.api.tree.PyArgumentTree; import org.sonar.python.api.tree.PyAssignmentStatementTree; import org.sonar.python.api.tree.PyCallExpressionTree; @@ -34,7 +34,7 @@ import org.sonar.python.api.tree.PyNameTree; import org.sonar.python.api.tree.PyQualifiedExpressionTree; import org.sonar.python.api.tree.Tree.Kind; -import org.sonar.python.semantic.Symbol; +import org.sonar.python.semantic.TreeSymbol; @Rule(key = DebugModeCheck.CHECK_KEY) public class DebugModeCheck extends PythonSubscriptionCheck { @@ -50,8 +50,7 @@ public void initialize(Context context) { if (!(callExpression.callee() instanceof PyQualifiedExpressionTree)) { return; } - PyQualifiedExpressionTree callee = (PyQualifiedExpressionTree) callExpression.callee(); - if (getQualifiedName(callee.qualifier(), ctx).equals("django.conf.settings") && callee.name().name().equals("configure") && !arguments.isEmpty()) { + if ("django.conf.settings.configure".equals(getQualifiedName(callExpression)) && !arguments.isEmpty()) { arguments.stream().filter(DebugModeCheck::isDebugArgument).forEach(arg -> ctx.addIssue(arg, MESSAGE)); } }); @@ -86,8 +85,9 @@ private static boolean isDebugArgument(PyArgumentTree argument) { return false; } - private static String getQualifiedName(PyExpressionTree node, SubscriptionContext ctx) { - Symbol symbol = ctx.symbolTable().getSymbol(node); - return symbol != null ? symbol.qualifiedName() : ""; + @CheckForNull + private static String getQualifiedName(PyCallExpressionTree callExpression) { + TreeSymbol symbol = callExpression.calleeSymbol(); + return symbol != null ? symbol.fullyQualifiedName() : ""; } } diff --git a/python-checks/src/main/java/org/sonar/python/checks/hotspots/PubliclyWritableDirectoriesCheck.java b/python-checks/src/main/java/org/sonar/python/checks/hotspots/PubliclyWritableDirectoriesCheck.java index bd50273e38..885ed50ba1 100644 --- a/python-checks/src/main/java/org/sonar/python/checks/hotspots/PubliclyWritableDirectoriesCheck.java +++ b/python-checks/src/main/java/org/sonar/python/checks/hotspots/PubliclyWritableDirectoriesCheck.java @@ -25,17 +25,15 @@ import java.util.regex.Pattern; import org.sonar.check.Rule; import org.sonar.python.PythonSubscriptionCheck; +import org.sonar.python.api.tree.HasSymbol; import org.sonar.python.api.tree.PyArgumentTree; import org.sonar.python.api.tree.PyCallExpressionTree; import org.sonar.python.api.tree.PyExpressionTree; -import org.sonar.python.api.tree.PyNameTree; -import org.sonar.python.api.tree.PyQualifiedExpressionTree; import org.sonar.python.api.tree.PyStringElementTree; import org.sonar.python.api.tree.PyStringLiteralTree; import org.sonar.python.api.tree.PySubscriptionExpressionTree; import org.sonar.python.api.tree.Tree.Kind; -import org.sonar.python.semantic.Symbol; -import org.sonar.python.semantic.SymbolTable; +import org.sonar.python.semantic.TreeSymbol; @Rule(key = "S5443") public class PubliclyWritableDirectoriesCheck extends PythonSubscriptionCheck { @@ -62,7 +60,7 @@ public void initialize(Context context) { context.registerSyntaxNodeConsumer(Kind.CALL_EXPR, ctx -> { PyCallExpressionTree tree = (PyCallExpressionTree) ctx.syntaxNode(); List arguments = tree.arguments(); - if (isOsEnvironGetter(tree.callee(), ctx.symbolTable()) && + if (isOsEnvironGetter(tree) && arguments.stream().map(PyArgumentTree::expression) .anyMatch(PubliclyWritableDirectoriesCheck::isNonCompliantOsEnvironArgument)) { ctx.addIssue(tree, MESSAGE); @@ -71,7 +69,7 @@ public void initialize(Context context) { context.registerSyntaxNodeConsumer(Kind.SUBSCRIPTION, ctx -> { PySubscriptionExpressionTree tree = (PySubscriptionExpressionTree) ctx.syntaxNode(); - if (isOsEnvironQualifiedExpression(tree.object(), ctx.symbolTable()) && tree.subscripts().expressions().stream() + if (isOsEnvironQualifiedExpression(tree.object()) && tree.subscripts().expressions().stream() .anyMatch(PubliclyWritableDirectoriesCheck::isNonCompliantOsEnvironArgument)) { ctx.addIssue(tree, MESSAGE); } @@ -88,30 +86,17 @@ private static boolean isNonCompliantOsEnvironArgument(PyExpressionTree expressi ((PyStringLiteralTree) expression).stringElements().stream().map(s -> s.trimmedQuotesValue().toLowerCase(Locale.ENGLISH)).anyMatch(NONCOMPLIANT_ENVIRON_VARIABLES::contains); } - // Could be either `os.environ.get` or `environ.get` - private static boolean isOsEnvironGetter(PyExpressionTree expression, SymbolTable symbolTable) { - if (!expression.is(Kind.QUALIFIED_EXPR)) { - return false; - } - PyQualifiedExpressionTree qualifiedExpression = (PyQualifiedExpressionTree) expression; - if (qualifiedExpression.name().name().equals("get")) { - return isOsEnvironQualifiedExpression(qualifiedExpression.qualifier(), symbolTable); - } - return false; + private static boolean isOsEnvironGetter(PyCallExpressionTree callExpressionTree) { + TreeSymbol symbol = callExpressionTree.calleeSymbol(); + return symbol != null && "os.environ.get".equals(symbol.fullyQualifiedName()); } - // Could be either `os.environ` or `from os import environ; ... ; environ` - private static boolean isOsEnvironQualifiedExpression(PyExpressionTree expression, SymbolTable symbolTable) { - Symbol symbol = symbolTable.getSymbol(expression); - if (symbol != null) { - return symbol.qualifiedName().equals("os.environ"); - } - if (!expression.is(Kind.QUALIFIED_EXPR)) { - return false; - } - PyQualifiedExpressionTree qualifiedExpression = (PyQualifiedExpressionTree) expression; - if (qualifiedExpression.qualifier().is(Kind.NAME)) { - return ((PyNameTree) qualifiedExpression.qualifier()).name().equals("os") && qualifiedExpression.name().name().equals("environ"); + private static boolean isOsEnvironQualifiedExpression(PyExpressionTree expression) { + if (expression instanceof HasSymbol) { + TreeSymbol symbol = ((HasSymbol) expression).symbol(); + if (symbol != null) { + return "os.environ".equals(symbol.fullyQualifiedName()); + } } return false; } diff --git a/python-checks/src/test/resources/checks/hotspots/debugMode/debugModeActivated.py b/python-checks/src/test/resources/checks/hotspots/debugMode/debugModeActivated.py index 516732e9a9..8874e9fefb 100644 --- a/python-checks/src/test/resources/checks/hotspots/debugMode/debugModeActivated.py +++ b/python-checks/src/test/resources/checks/hotspots/debugMode/debugModeActivated.py @@ -9,9 +9,11 @@ # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ mysettings.configure(DEBUG=True) # OK settings.otherFn(DEBUG=True) # OK +settings.configure() configure(DEBUG=True) # OK configure() # OK +foo.configure(DEBUG=True) # OK def custom_config(config): settings.configure(default_settings=config, DEBUG=True) # Noncompliant diff --git a/python-checks/src/test/resources/checks/hotspots/publiclyWritableDirectories.py b/python-checks/src/test/resources/checks/hotspots/publiclyWritableDirectories.py index 24fc99e7d7..b1ae263859 100644 --- a/python-checks/src/test/resources/checks/hotspots/publiclyWritableDirectories.py +++ b/python-checks/src/test/resources/checks/hotspots/publiclyWritableDirectories.py @@ -42,6 +42,7 @@ def environ_variables(): tmp_dir = os.environ['OTHER'] # OK tmp_dir = os.other['TMPDIR'] # OK tmp_dir = other['TMPDIR'] # OK + tmp_dir = foo()['TMPDIR'] # OK tmp_dir = os.foo.environ['TMPDIR'] # OK tmp_dir = environ['TMPDIR'] # Noncompliant tmp_dir = environ.get('TMPDIR') # Noncompliant