Skip to content

Commit df9598f

Browse files
andreaguarinoguillaume-dequenne
authored andcommitted
SONARPY-939 Adapt code to support protobuf stdlib symbols
1 parent 5f345a4 commit df9598f

File tree

6 files changed

+65
-30
lines changed

6 files changed

+65
-30
lines changed

python-checks/src/test/resources/checks/argumentType.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def stdlib_functions():
6868
time.sleep(True) # OK, converted to 1
6969
time.sleep(1j) # FN, considered duck type compatible
7070
genericpath.isfile("some/path")
71-
genericpath.isfile(42) # Noncompliant
71+
genericpath.isfile(42) # FN - see SONARPY-937
7272
my_list = [1,2,3]
7373
_heapq.heapify(42) # Noncompliant {{Change this argument; Function "heapify" expects a different type}}
7474
# ^^

python-checks/src/test/resources/checks/sillyEquality.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def stdlib():
6464
passwd_2 = pwd.struct_passwd(tuple)
6565
if passwd == passwd_2: pass # OK
6666
if passwd == pwd.getpwuid(1): pass # OK
67-
if 42 == pwd.getpwuid(1): pass # FN, unresolved type hierarchy
67+
if 42 == pwd.getpwuid(1): pass # Noncompliant
6868
if pwd.getpwall() == 42: pass # Noncompliant
6969
if zip(l1, l2) == 42: pass # FN due to missing Python 2 and usage of zip.__new__
7070
if platform.architecture() == '32bit': ... # Noncompliant

python-frontend/src/main/java/org/sonar/python/semantic/ClassSymbolImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ public ClassSymbolImpl(SymbolsProtos.ClassSymbol classSymbolProto) {
146146
.forEach(proto -> descriptorsByFqn.computeIfAbsent(proto.getFullname(), d -> new HashSet<>()).add(proto));
147147
for (Map.Entry<String, Set<Object>> entry : descriptorsByFqn.entrySet()) {
148148
Set<Symbol> symbols = symbolsFromDescriptor(entry.getValue(), true);
149+
// FIXME: SONARPY-942
150+
if (symbols.isEmpty()) {
151+
continue;
152+
}
149153
methods.add(symbols.size() > 1 ? AmbiguousSymbolImpl.create(symbols) : symbols.iterator().next());
150154
}
151155
addMembers(methods);

python-frontend/src/main/java/org/sonar/python/types/TypeShed.java

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -392,31 +392,47 @@ static Map<String, Symbol> getSymbolsFromProtobufModule(@Nullable ModuleSymbol m
392392
}
393393

394394
// TODO: Use a common proxy interface Descriptor instead of using Object
395-
Map<String, Set<Object>> descriptorsByFqn = new HashMap<>();
395+
Map<String, Set<Object>> descriptorsByName = new HashMap<>();
396396
moduleSymbol.getClassesList().stream()
397397
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
398-
.forEach(proto -> descriptorsByFqn.computeIfAbsent(proto.getFullyQualifiedName(), d -> new HashSet<>()).add(proto));
398+
.forEach(proto -> descriptorsByName.computeIfAbsent(proto.getName(), d -> new HashSet<>()).add(proto));
399399
moduleSymbol.getFunctionsList().stream()
400400
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
401-
.forEach(proto -> descriptorsByFqn.computeIfAbsent(proto.getFullyQualifiedName(), d -> new HashSet<>()).add(proto));
401+
.forEach(proto -> descriptorsByName.computeIfAbsent(proto.getName(), d -> new HashSet<>()).add(proto));
402402
moduleSymbol.getOverloadedFunctionsList().stream()
403403
.filter(d -> isValidForProjectPythonVersion(d.getValidForList()))
404-
.forEach(proto -> descriptorsByFqn.computeIfAbsent(proto.getFullname(), d -> new HashSet<>()).add(proto));
404+
.forEach(proto -> descriptorsByName.computeIfAbsent(proto.getName(), d -> new HashSet<>()).add(proto));
405405

406406
Map<String, Symbol> deserializedSymbols = new HashMap<>();
407407

408-
for (Map.Entry<String, Set<Object>> entry : descriptorsByFqn.entrySet()) {
409-
String fqn = normalizedFqn(entry.getKey());
408+
for (Map.Entry<String, Set<Object>> entry : descriptorsByName.entrySet()) {
409+
String name = entry.getKey();
410410
Set<Symbol> symbols = symbolsFromDescriptor(entry.getValue(), false);
411-
if (moduleSymbol.getFullyQualifiedName().equals(BUILTINS_FQN) && BUILTINS_TO_DISAMBIGUATE.contains(fqn) && symbols.size() > 1) {
412-
deserializedSymbols.put(fqn, disambiguateWithLatestPythonSymbol(symbols));
413-
} else {
414-
deserializedSymbols.put(fqn, symbols.size() > 1 ? AmbiguousSymbolImpl.create(symbols) : symbols.iterator().next());
415-
}
411+
deserializedSymbols.put(name, disambiguateSymbolsWithSameName(name, symbols, moduleSymbol.getFullyQualifiedName()));
416412
}
417413
return deserializedSymbols;
418414
}
419415

416+
// TODO: to be checked when implementing SONARPY-941
417+
private static Symbol disambiguateSymbolsWithSameName(String name, Set<Symbol> symbols, String moduleFqn) {
418+
if (symbols.size() > 1) {
419+
if (haveAllTheSameFqn(symbols) && !isBuiltinToDisambiguate(moduleFqn, name)) {
420+
return AmbiguousSymbolImpl.create(symbols);
421+
}
422+
return disambiguateWithLatestPythonSymbol(symbols);
423+
}
424+
return symbols.iterator().next();
425+
}
426+
427+
private static boolean isBuiltinToDisambiguate(String moduleFqn, String name) {
428+
return moduleFqn.equals(BUILTINS_FQN) && BUILTINS_TO_DISAMBIGUATE.contains(name);
429+
}
430+
431+
private static boolean haveAllTheSameFqn(Set<Symbol> symbols) {
432+
String firstFqn = symbols.iterator().next().fullyQualifiedName();
433+
return firstFqn != null && symbols.stream().map(Symbol::fullyQualifiedName).allMatch(firstFqn::equals);
434+
}
435+
420436
public static boolean isValidForProjectPythonVersion(List<String> validForPythonVersions) {
421437
if (validForPythonVersions.isEmpty()) {
422438
return true;
@@ -436,6 +452,11 @@ public static Set<Symbol> symbolsFromDescriptor(Set<Object> descriptors, boolean
436452
symbols.add(new FunctionSymbolImpl(((SymbolsProtos.FunctionSymbol) descriptor), isInsideClass));
437453
}
438454
if (descriptor instanceof OverloadedFunctionSymbol) {
455+
// FIXME: SONARPY-942
456+
if (((OverloadedFunctionSymbol) descriptor).getDefinitionsList().size() < 2) {
457+
LOG.error("Overloaded function symbols should have at least two definitions");
458+
continue;
459+
}
439460
symbols.add(fromOverloadedFunction(((OverloadedFunctionSymbol) descriptor), isInsideClass));
440461
}
441462
}

python-frontend/src/test/java/org/sonar/python/types/TypeShedTest.java

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import java.util.Set;
2828
import java.util.function.Function;
2929
import java.util.stream.Collectors;
30+
import org.junit.Before;
31+
import org.junit.Ignore;
3032
import org.junit.Test;
3133
import org.sonar.api.utils.log.LogTester;
3234
import org.sonar.api.utils.log.LoggerLevel;
@@ -51,11 +53,14 @@ public class TypeShedTest {
5153
@org.junit.Rule
5254
public LogTester logTester = new LogTester();
5355

54-
/**
55-
* This cleanup method is called manually when needed instead of having it run "BeforeEach" test to avoid the performance impact of recomputing builtins symbols
56-
*/
57-
public void setPythonVersions(Set<PythonVersionUtils.Version> pythonVersion) {
58-
ProjectPythonVersion.setCurrentVersions(pythonVersion);
56+
@Before
57+
public void setPythonVersions() {
58+
ProjectPythonVersion.setCurrentVersions(PythonVersionUtils.allVersions());
59+
TypeShed.resetBuiltinSymbols();
60+
}
61+
62+
private void setPythonVersions(Set<PythonVersionUtils.Version> pythonVersions) {
63+
ProjectPythonVersion.setCurrentVersions(pythonVersions);
5964
TypeShed.resetBuiltinSymbols();
6065
}
6166

@@ -181,15 +186,17 @@ public void package_submodules_symbols() {
181186
public void package_inner_submodules_symbols() {
182187
Map<String, Symbol> driverSymbols = TypeShed.symbolsForModule("lib2to3.pgen2.driver").stream().collect(Collectors.toMap(Symbol::name, Function.identity()));
183188
Symbol loadGrammarSymbol = driverSymbols.get("load_grammar");
184-
assertThat(loadGrammarSymbol.kind()).isEqualTo(Kind.FUNCTION);
189+
// There is a small difference between Python 2 and Python 3 symbols: Python 2 uses Text instead of str
190+
assertThat(loadGrammarSymbol.kind()).isEqualTo(Kind.AMBIGUOUS);
185191
assertThat(TypeShed.symbolWithFQN("lib2to3.pgen2.driver", "lib2to3.pgen2.driver.load_grammar")).isSameAs(loadGrammarSymbol);
186192
}
187193

188194
@Test
189195
public void package_relative_import() {
190196
Map<String, Symbol> osSymbols = TypeShed.symbolsForModule("os").stream().collect(Collectors.toMap(Symbol::name, Function.identity(), AmbiguousSymbolImpl::create));
191-
Symbol sysSymbol = osSymbols.get("sys");
192-
assertThat(sysSymbol.kind()).isEqualTo(Kind.AMBIGUOUS);
197+
// TODO: Add imported symbols SONARPY-938
198+
// Symbol sysSymbol = osSymbols.get("sys");
199+
// assertThat(sysSymbol.kind()).isEqualTo(Kind.AMBIGUOUS);
193200

194201
Symbol timesResult = osSymbols.get("times_result");
195202
assertThat(timesResult.kind()).isEqualTo(Kind.CLASS);
@@ -209,7 +216,9 @@ public void package_member_fqn_points_to_original_fqn() {
209216
assertThat(TypeShed.symbolWithFQN("flask", "flask.Response")).isSameAs(targetSymbol);
210217
}
211218

212-
@Test
219+
// TODO: there shouldn't be more than two symbols with the same name SONARPY-941
220+
// TODO: FileIO is ambiguous and it has FQN null
221+
@Ignore
213222
public void package_member_ambigous_symbol_common_fqn() {
214223
Map<String, Symbol> symbols = TypeShed.symbolsForModule("io").stream().collect(Collectors.toMap(Symbol::name, Function.identity()));
215224
Symbol targetSymbol = symbols.get("FileIO");
@@ -219,12 +228,13 @@ public void package_member_ambigous_symbol_common_fqn() {
219228

220229
@Test
221230
public void two_exported_symbols_with_same_local_names() {
231+
// TODO: there shouldn't be more than two symbols with the same name SONARPY-941
222232
Map<String, Symbol> osSymbols = TypeShed.symbolsForModule("os").stream().collect(Collectors.toMap(Symbol::name, Function.identity(), AmbiguousSymbolImpl::create));
223-
Map<String, Symbol> posixSymbols = TypeShed.symbolsForModule("posix").stream().collect(Collectors.toMap(Symbol::name, Function.identity()));
233+
Map<String, Symbol> posixSymbols = TypeShed.symbolsForModule("posix").stream().collect(Collectors.toMap(Symbol::name, Function.identity(), AmbiguousSymbolImpl::create));
224234
Symbol setupSymbolFromPosix = posixSymbols.get("stat_result");
225235
Symbol setupSymbolFromOs = osSymbols.get("stat_result");
226-
assertThat(setupSymbolFromPosix.kind()).isEqualTo(Kind.AMBIGUOUS);
227-
assertThat(setupSymbolFromOs.kind()).isEqualTo(Kind.AMBIGUOUS);
236+
assertThat(setupSymbolFromPosix.kind()).isEqualTo(Kind.CLASS);
237+
assertThat(setupSymbolFromOs.kind()).isEqualTo(Kind.CLASS);
228238
}
229239

230240
@Test
@@ -341,9 +351,9 @@ public void class_symbols_from_protobuf() throws TextFormat.ParseException {
341351
assertThat(symbols.values()).extracting(Symbol::kind, Symbol::fullyQualifiedName)
342352
.containsExactlyInAnyOrder(tuple(Kind.CLASS, "mod.Base"), tuple(Kind.CLASS, "mod.C"), tuple(Kind.CLASS, "mod.D"));
343353

344-
ClassSymbol C = (ClassSymbol) symbols.get("mod.C");
354+
ClassSymbol C = (ClassSymbol) symbols.get("C");
345355
assertThat(C.superClasses()).extracting(Symbol::fullyQualifiedName).containsExactly("str");
346-
ClassSymbol D = (ClassSymbol) symbols.get("mod.D");
356+
ClassSymbol D = (ClassSymbol) symbols.get("D");
347357
assertThat(D.superClasses()).extracting(Symbol::kind, Symbol::fullyQualifiedName)
348358
.containsExactly(tuple(Kind.OTHER, "NOT_EXISTENT"));
349359
}
@@ -393,7 +403,7 @@ public void function_symbols_from_protobuf() throws TextFormat.ParseException {
393403
Map<String, Symbol> symbols = TypeShed.getSymbolsFromProtobufModule(moduleSymbol);
394404
assertThat(symbols.values()).extracting(Symbol::kind, Symbol::fullyQualifiedName)
395405
.containsExactlyInAnyOrder(tuple(Kind.FUNCTION, "mod.foo"), tuple(Kind.AMBIGUOUS, "mod.bar"));
396-
AmbiguousSymbol ambiguousSymbol = (AmbiguousSymbol) symbols.get("mod.bar");
406+
AmbiguousSymbol ambiguousSymbol = (AmbiguousSymbol) symbols.get("bar");
397407
assertThat(ambiguousSymbol.alternatives()).extracting(Symbol::kind).containsExactly(Kind.FUNCTION, Kind.FUNCTION);
398408

399409
}

sonar-python-plugin/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@
150150
<configuration>
151151
<rules>
152152
<requireFilesSize>
153-
<maxsize>6700000</maxsize>
154-
<minsize>6200000</minsize>
153+
<maxsize>8800000</maxsize>
154+
<minsize>7300000</minsize>
155155
<files>
156156
<file>${project.build.directory}/${project.build.finalName}.jar</file>
157157
</files>

0 commit comments

Comments
 (0)