Skip to content

Commit cd840d3

Browse files
SONARPY-869 Deserialize protobuf files containing symbol information … (#938)
1 parent a549054 commit cd840d3

File tree

13 files changed

+923
-34
lines changed

13 files changed

+923
-34
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@
9494
<sonar-analyzer-commons.version>1.15.0.699</sonar-analyzer-commons.version>
9595
<sonarlint-core.version>6.0.0.32513</sonarlint-core.version>
9696
<sslr.version>1.23</sslr.version>
97+
<protobuf.version>3.17.3</protobuf.version>
9798
</properties>
9899

99100
<dependencyManagement>

python-frontend/pom.xml

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,68 @@
3737
<groupId>com.google.guava</groupId>
3838
<artifactId>guava</artifactId>
3939
</dependency>
40+
<dependency>
41+
<groupId>com.google.protobuf</groupId>
42+
<artifactId>protobuf-java</artifactId>
43+
<version>${protobuf.version}</version>
44+
</dependency>
4045
</dependencies>
41-
46+
<build>
47+
<extensions>
48+
<extension>
49+
<groupId>kr.motd.maven</groupId>
50+
<artifactId>os-maven-plugin</artifactId>
51+
<version>1.6.2</version>
52+
</extension>
53+
</extensions>
54+
<plugins>
55+
<plugin>
56+
<groupId>org.xolstice.maven.plugins</groupId>
57+
<artifactId>protobuf-maven-plugin</artifactId>
58+
<version>0.6.1</version>
59+
<executions>
60+
<execution>
61+
<id>generate-protobuf-java-sources</id>
62+
<goals>
63+
<goal>compile</goal>
64+
</goals>
65+
<configuration>
66+
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}
67+
</protocArtifact>
68+
<protoSourceRoot>${project.basedir}/src/main/protobuf</protoSourceRoot>
69+
<outputDirectory>${project.build.directory}/generated-sources/protobuf</outputDirectory>
70+
</configuration>
71+
</execution>
72+
</executions>
73+
<dependencies>
74+
<dependency>
75+
<groupId>com.google.protobuf</groupId>
76+
<artifactId>protoc</artifactId>
77+
<version>${protobuf.version}</version>
78+
<type>exe</type>
79+
<classifier>${os.detected.classifier}</classifier>
80+
</dependency>
81+
</dependencies>
82+
</plugin>
83+
<plugin>
84+
<groupId>org.codehaus.mojo</groupId>
85+
<artifactId>build-helper-maven-plugin</artifactId>
86+
<version>3.2.0</version>
87+
<executions>
88+
<execution>
89+
<id>add-protobuf-generated-sources</id>
90+
<phase>generate-sources</phase>
91+
<goals>
92+
<goal>add-source</goal>
93+
</goals>
94+
<configuration>
95+
<sources>
96+
<source>${project.build.directory}/generated-sources/protobuf</source>
97+
</sources>
98+
</configuration>
99+
</execution>
100+
</executions>
101+
</plugin>
102+
</plugins>
103+
</build>
42104
</project>

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,16 @@
4040
import org.sonar.plugins.python.api.symbols.ClassSymbol;
4141
import org.sonar.plugins.python.api.symbols.Symbol;
4242
import org.sonar.plugins.python.api.tree.ClassDef;
43+
import org.sonar.python.types.TypeShed;
44+
import org.sonar.python.types.protobuf.SymbolsProtos;
4345

4446
import static org.sonar.python.semantic.SymbolUtils.pathOf;
4547
import static org.sonar.python.tree.TreeUtils.locationInFile;
4648

4749
public class ClassSymbolImpl extends SymbolImpl implements ClassSymbol {
4850

4951
private final List<Symbol> superClasses = new ArrayList<>();
52+
private List<String> superClassesFqns = new ArrayList<>();
5053
private Set<Symbol> allSuperClasses = null;
5154
private Set<Symbol> allSuperClassesIncludingAmbiguousSymbols = null;
5255
private boolean hasSuperClassWithoutSymbol = false;
@@ -88,6 +91,23 @@ public ClassSymbolImpl(String name, @Nullable String fullyQualifiedName, @Nullab
8891
setKind(Kind.CLASS);
8992
}
9093

94+
public ClassSymbolImpl(SymbolsProtos.ClassSymbol classSymbolProto) {
95+
super(classSymbolProto.getName(), classSymbolProto.getFullyQualifiedName());
96+
setKind(Kind.CLASS);
97+
classDefinitionLocation = null;
98+
hasDecorators = classSymbolProto.getHasDecorators();
99+
hasMetaClass = classSymbolProto.getHasMetaclass();
100+
metaclassFQN = classSymbolProto.getMetaclassName();
101+
supportsGenerics = classSymbolProto.getIsGeneric();
102+
List<SymbolsProtos.FunctionSymbol> methodsList = classSymbolProto.getMethodsList();
103+
Set<Symbol> methods = methodsList.stream().map(m -> new FunctionSymbolImpl(m, true)).collect(Collectors.toSet());
104+
for (SymbolsProtos.OverloadedFunctionSymbol overloadedMethod : classSymbolProto.getOverloadedMethodsList()) {
105+
methods.add(AmbiguousSymbolImpl.create(overloadedMethod.getDefinitionsList().stream().map(m -> new FunctionSymbolImpl(m, true)).collect(Collectors.toSet())));
106+
}
107+
addMembers(methods);
108+
superClassesFqns = classSymbolProto.getSuperClassesList();
109+
}
110+
91111
@Override
92112
ClassSymbolImpl copyWithoutUsages() {
93113
ClassSymbolImpl copiedClassSymbol = new ClassSymbolImpl(name(), fullyQualifiedName(), definitionLocation(), hasDecorators, hasMetaClass, metaclassFQN, supportsGenerics);
@@ -111,10 +131,21 @@ ClassSymbolImpl copyWithoutUsages() {
111131

112132
@Override
113133
public List<Symbol> superClasses() {
134+
// In case of symbols coming from TypeShed protobuf, we resolve superclasses lazily
135+
if (!hasAlreadyReadSuperClasses && superClasses.isEmpty() && !superClassesFqns.isEmpty()) {
136+
superClassesFqns.stream().map(ClassSymbolImpl::resolveSuperClass).forEach(this::addSuperClass);
137+
}
114138
hasAlreadyReadSuperClasses = true;
115139
return Collections.unmodifiableList(superClasses);
116140
}
117141

142+
private static Symbol resolveSuperClass(String superClassFqn) {
143+
String[] fqnSplitByDot = superClassFqn.split("\\.");
144+
String localName = fqnSplitByDot[fqnSplitByDot.length - 1];
145+
Symbol symbol = TypeShed.symbolWithFQN(superClassFqn);
146+
return symbol == null ? new SymbolImpl(localName, superClassFqn) : symbol;
147+
}
148+
118149
public void addSuperClass(Symbol symbol) {
119150
if (hasAlreadyReadSuperClasses) {
120151
throw new IllegalStateException("Cannot call addSuperClass, super classes were already read");

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.sonar.plugins.python.api.types.InferredType;
4242
import org.sonar.python.tree.TreeUtils;
4343
import org.sonar.python.types.InferredTypes;
44+
import org.sonar.python.types.protobuf.SymbolsProtos;
4445

4546
import static org.sonar.python.semantic.SymbolUtils.isTypeShedFile;
4647
import static org.sonar.python.semantic.SymbolUtils.pathOf;
@@ -80,6 +81,36 @@ public class FunctionSymbolImpl extends SymbolImpl implements FunctionSymbol {
8081
functionDefinitionLocation = locationInFile(functionDef.name(), fileId);
8182
}
8283

84+
public FunctionSymbolImpl(SymbolsProtos.FunctionSymbol functionSymbolProto) {
85+
this(functionSymbolProto, false);
86+
}
87+
88+
public FunctionSymbolImpl(SymbolsProtos.FunctionSymbol functionSymbolProto, boolean insideClass) {
89+
super(functionSymbolProto.getName(), functionSymbolProto.getFullyQualifiedName());
90+
setKind(Kind.FUNCTION);
91+
isInstanceMethod = insideClass && !functionSymbolProto.getIsStatic() && !functionSymbolProto.getIsClassMethod();
92+
isAsynchronous = functionSymbolProto.getIsCoroutine() || functionSymbolProto.getIsAsyncGenerator();
93+
hasDecorators = functionSymbolProto.getHasDecorators();
94+
decorators = functionSymbolProto.getResolvedDecoratorNamesList();
95+
SymbolsProtos.Type returnAnnotation = functionSymbolProto.getReturnAnnotation();
96+
String returnTypeName = returnAnnotation.getSimpleName();
97+
annotatedReturnTypeName = returnTypeName.isEmpty() ? null : returnTypeName;
98+
for (SymbolsProtos.ParameterSymbol parameterSymbol : functionSymbolProto.getParametersList()) {
99+
ParameterState parameterState = new ParameterState();
100+
parameterState.positionalOnly = parameterSymbol.getKind() == SymbolsProtos.ParameterKind.POSITIONAL_ONLY;
101+
parameterState.keywordOnly = parameterSymbol.getKind() == SymbolsProtos.ParameterKind.KEYWORD_ONLY;
102+
boolean isVariadic = (parameterSymbol.getKind() == SymbolsProtos.ParameterKind.VAR_KEYWORD) || parameterSymbol.getKind() == SymbolsProtos.ParameterKind.VAR_POSITIONAL;
103+
hasVariadicParameter |= isVariadic;
104+
ParameterImpl parameter = new ParameterImpl(
105+
parameterSymbol.getName(), InferredTypes.fromTypeshedProtobuf(parameterSymbol.getTypeAnnotation()), parameterSymbol.getHasDefault(), isVariadic, parameterState, null);
106+
parameters.add(parameter);
107+
}
108+
functionDefinitionLocation = null;
109+
declaredReturnType = InferredTypes.fromTypeshedProtobuf(returnAnnotation);
110+
isStub = true;
111+
isDjangoView = false;
112+
}
113+
83114
public void setParametersWithType(ParameterList parametersList) {
84115
this.parameters.clear();
85116
createParameterNames(parametersList.all(), functionDefinitionLocation == null ? null : functionDefinitionLocation.fileId());

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.sonar.plugins.python.api.types.BuiltinTypes;
4646
import org.sonar.plugins.python.api.types.InferredType;
4747
import org.sonar.python.tree.TreeUtils;
48+
import org.sonar.python.types.protobuf.SymbolsProtos;
4849

4950
import static org.sonar.plugins.python.api.symbols.Symbol.Kind.CLASS;
5051

@@ -154,6 +155,27 @@ public static InferredType fromTypeshedTypeAnnotation(TypeAnnotation typeAnnotat
154155
return runtimeTypefromTypeAnnotation(typeAnnotation.expression(), builtins);
155156
}
156157

158+
public static InferredType fromTypeshedProtobuf(SymbolsProtos.Type type) {
159+
switch (type.getKind()) {
160+
case INSTANCE:
161+
String typeName = type.getSimpleName();
162+
return typeName.isEmpty() ? anyType() : runtimeType(TypeShed.symbolWithFQN(typeName));
163+
case TYPE_ALIAS:
164+
case CALLABLE:
165+
return fromTypeshedProtobuf(type.getArgs(0));
166+
case UNION:
167+
return union(type.getArgsList().stream().map(InferredTypes::fromTypeshedProtobuf));
168+
case TUPLE:
169+
return TUPLE;
170+
case NONE:
171+
return NONE;
172+
case TYPED_DICT:
173+
return DICT;
174+
default:
175+
return anyType();
176+
}
177+
}
178+
157179
@CheckForNull
158180
private static DeclaredType declaredTypeFromTypeAnnotation(Expression expression, Map<String, Symbol> builtinSymbols) {
159181
if (expression.is(Kind.NAME) && !((Name) expression).name().equals("Any")) {

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

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.sonar.python.types;
2121

2222
import com.sonar.sslr.api.AstNode;
23+
import java.io.IOException;
2324
import java.io.InputStream;
2425
import java.util.Arrays;
2526
import java.util.Collection;
@@ -33,7 +34,10 @@
3334
import java.util.stream.Collectors;
3435
import javax.annotation.CheckForNull;
3536
import javax.annotation.Nullable;
37+
import org.sonar.api.utils.log.Logger;
38+
import org.sonar.api.utils.log.Loggers;
3639
import org.sonar.plugins.python.api.PythonFile;
40+
import org.sonar.plugins.python.api.symbols.AmbiguousSymbol;
3741
import org.sonar.plugins.python.api.symbols.ClassSymbol;
3842
import org.sonar.plugins.python.api.symbols.FunctionSymbol;
3943
import org.sonar.plugins.python.api.symbols.Symbol;
@@ -54,6 +58,8 @@
5458
import org.sonar.python.semantic.SymbolTableBuilder;
5559
import org.sonar.python.tree.FunctionDefImpl;
5660
import org.sonar.python.tree.PythonTreeMaker;
61+
import org.sonar.python.types.protobuf.SymbolsProtos.ModuleSymbol;
62+
import org.sonar.python.types.protobuf.SymbolsProtos.OverloadedFunctionSymbol;
5763

5864
import static org.sonar.plugins.python.api.types.BuiltinTypes.NONE_TYPE;
5965

@@ -62,7 +68,7 @@ public class TypeShed {
6268
private static final String TYPING = "typing";
6369
private static final String TYPING_EXTENSIONS = "typing_extensions";
6470
private static Map<String, Symbol> builtins;
65-
private static final Map<String, Set<Symbol>> typeShedSymbols = new HashMap<>();
71+
private static final Map<String, Map<String, Symbol>> typeShedSymbols = new HashMap<>();
6672
private static final Map<String, Set<Symbol>> builtinGlobalSymbols = new HashMap<>();
6773
private static final Set<String> modulesInProgress = new HashSet<>();
6874

@@ -73,6 +79,9 @@ public class TypeShed {
7379
private static final String THIRD_PARTY_2 = "typeshed/third_party/2/";
7480
private static final String THIRD_PARTY_3 = "typeshed/third_party/3/";
7581
private static final String CUSTOM_THIRD_PARTY = "custom/";
82+
private static final String PROTOBUF = "protobuf/";
83+
84+
private static final Logger LOG = Loggers.get(TypeShed.class);
7685

7786
private TypeShed() {
7887
}
@@ -161,10 +170,11 @@ static Set<Symbol> typingExtensionsSymbols(Map<String, Set<Symbol>> typingSymbol
161170
public static Set<Symbol> symbolsForModule(String moduleName) {
162171
if (!TypeShed.typeShedSymbols.containsKey(moduleName)) {
163172
Set<Symbol> symbols = searchTypeShedForModule(moduleName);
164-
typeShedSymbols.put(moduleName, symbols);
173+
Map<String, Symbol> symbolsByFqn = symbols.stream().collect(Collectors.toMap(Symbol::fullyQualifiedName, s -> s));
174+
typeShedSymbols.put(moduleName, symbolsByFqn);
165175
return symbols;
166176
}
167-
return TypeShed.typeShedSymbols.get(moduleName);
177+
return new HashSet<>(TypeShed.typeShedSymbols.get(moduleName).values());
168178
}
169179

170180
@CheckForNull
@@ -195,6 +205,11 @@ private static Set<Symbol> searchTypeShedForModule(String moduleName) {
195205
return new HashSet<>();
196206
}
197207
modulesInProgress.add(moduleName);
208+
Collection<Symbol> symbolsFromProtobuf = getSymbolsFromProtobufModule(moduleName);
209+
if (!symbolsFromProtobuf.isEmpty()) {
210+
modulesInProgress.remove(moduleName);
211+
return new HashSet<>(symbolsFromProtobuf);
212+
}
198213
Set<Symbol> customSymbols = new HashSet<>(getModuleSymbols(moduleName, CUSTOM_THIRD_PARTY, builtinGlobalSymbols).values());
199214
if (!customSymbols.isEmpty()) {
200215
modulesInProgress.remove(moduleName);
@@ -268,7 +283,7 @@ public static ClassSymbol typeShedClass(String fullyQualifiedName) {
268283

269284
public static Collection<Symbol> stubFilesSymbols() {
270285
Set<Symbol> symbols = new HashSet<>(TypeShed.builtinSymbols().values());
271-
typeShedSymbols.values().forEach(symbols::addAll);
286+
typeShedSymbols.values().forEach(symbolsByFqn -> symbols.addAll(symbolsByFqn.values()));
272287
return symbols;
273288
}
274289

@@ -340,4 +355,57 @@ private static class ModuleDescription {
340355
}
341356
}
342357

358+
359+
private static Collection<Symbol> getSymbolsFromProtobufModule(String moduleName) {
360+
InputStream resource = TypeShed.class.getResourceAsStream(PROTOBUF + moduleName + ".protobuf");
361+
if (resource == null) {
362+
return Collections.emptySet();
363+
}
364+
return getSymbolsFromProtobufModule(deserializedModule(moduleName, resource)).values();
365+
}
366+
367+
@CheckForNull
368+
static ModuleSymbol deserializedModule(String moduleName, InputStream resource) {
369+
try {
370+
return ModuleSymbol.parseFrom(resource);
371+
} catch (IOException e) {
372+
LOG.debug("Error while deserializing protobuf for module " + moduleName, e);
373+
return null;
374+
}
375+
}
376+
377+
static Map<String, Symbol> getSymbolsFromProtobufModule(@Nullable ModuleSymbol moduleSymbol) {
378+
if (moduleSymbol == null) {
379+
return Collections.emptyMap();
380+
}
381+
Map<String, Symbol> deserializedSymbols = new HashMap<>();
382+
moduleSymbol.getClassesList().forEach(proto -> deserializedSymbols.put(proto.getFullyQualifiedName(), new ClassSymbolImpl(proto)));
383+
moduleSymbol.getFunctionsList().forEach(proto -> deserializedSymbols.put(proto.getFullyQualifiedName(), new FunctionSymbolImpl(proto)));
384+
moduleSymbol.getOverloadedFunctionsList().forEach(proto -> deserializedSymbols.put(proto.getFullname(), fromOverloadedFunction(proto)));
385+
return deserializedSymbols;
386+
}
387+
388+
private static AmbiguousSymbol fromOverloadedFunction(OverloadedFunctionSymbol overloadedFunctionSymbol) {
389+
Set<Symbol> overloadedSymbols = overloadedFunctionSymbol.getDefinitionsList().stream()
390+
.map(FunctionSymbolImpl::new)
391+
.collect(Collectors.toSet());
392+
return AmbiguousSymbolImpl.create(overloadedSymbols);
393+
}
394+
395+
@CheckForNull
396+
public static Symbol symbolWithFQN(String fullyQualifiedName) {
397+
String[] fqnSplittedByDot = fullyQualifiedName.split("\\.");
398+
String localName = fqnSplittedByDot[fqnSplittedByDot.length - 1];
399+
if (fqnSplittedByDot.length == 2 && fqnSplittedByDot[0].equals("builtins") && builtins.containsKey(localName)) {
400+
return builtins.get(localName);
401+
}
402+
for (Map<String, Symbol> symbolsByFqn : typeShedSymbols.values()) {
403+
Symbol symbol = symbolsByFqn.get(fullyQualifiedName);
404+
if (symbol != null) {
405+
return symbol;
406+
}
407+
}
408+
return null;
409+
}
410+
343411
}

0 commit comments

Comments
 (0)