diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S1130.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S1130.json index 269f85f020b..8170eb89460 100644 --- a/its/ruling/src/test/resources/expected/commons-beanutils/java-S1130.json +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S1130.json @@ -44,15 +44,9 @@ "commons-beanutils:src/test/java/org/apache/commons/beanutils2/converters/ClassReloader.java": [ 57 ], -"commons-beanutils:src/test/java/org/apache/commons/beanutils2/converters/DoubleConverterTestCase.java": [ -79 -], "commons-beanutils:src/test/java/org/apache/commons/beanutils2/converters/FileConverterTestCase.java": [ 72 ], -"commons-beanutils:src/test/java/org/apache/commons/beanutils2/converters/FloatConverterTestCase.java": [ -79 -], "commons-beanutils:src/test/java/org/apache/commons/beanutils2/converters/IntegerConverterTestCase.java": [ 80 ], diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S1226.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S1226.json new file mode 100644 index 00000000000..02f0c2b46bf --- /dev/null +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S1226.json @@ -0,0 +1,91 @@ +{ +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/BasicDynaClass.java": [ +83 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanMap.java": [ +762 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java": [ +362, +414, +434, +900 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/ConstructorUtils.java": [ +107, +146, +149, +219, +259, +263 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java": [ +185 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/LazyDynaBean.java": [ +650 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/MethodUtils.java": [ +214, +263, +266, +344, +386, +390, +436, +440, +536, +586, +589, +668, +787, +808, +815, +872, +1137, +1327 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java": [ +439, +604, +786, +787, +791, +793, +795, +797, +834, +930, +931, +935, +1121, +1122, +1126, +1354, +1355, +1359, +1364, +1443, +1444, +1448, +1453, +1540, +1719, +1885, +1886, +1964 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/AbstractConverter.java": [ +145 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/ArrayConverter.java": [ +442, +444 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/LocaleBeanUtilsBean.java": [ +674 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/DateLocaleConverter.java": [ +274 +] +} diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S1905.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S1905.json new file mode 100644 index 00000000000..06c5c23990d --- /dev/null +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S1905.json @@ -0,0 +1,8 @@ +{ +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanMap.java": [ +451 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/AbstractConverter.java": [ +457 +] +} diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S2159.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2159.json new file mode 100644 index 00000000000..d0a33b6bbfa --- /dev/null +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2159.json @@ -0,0 +1,9 @@ +{ +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/BaseDynaBeanMapDecorator.java": [ +373 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/WeakFastHashMap.java": [ +354, +374 +] +} diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S2175.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2175.json new file mode 100644 index 00000000000..7565c6bf7f9 --- /dev/null +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2175.json @@ -0,0 +1,65 @@ +{ +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/BasicDynaBean.java": [ +119, +146, +200, +232, +274, +341, +383 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/BasicDynaClass.java": [ +180 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanMap.java": [ +247, +270, +335, +393, +403, +502, +531, +555, +602, +612, +628, +640, +730, +733 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/ConvertUtilsBean.java": [ +550, +565 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java": [ +217, +714, +2206 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/WeakFastHashMap.java": [ +159, +162, +206, +209, +223, +226, +293, +299, +350, +350, +354, +370, +370, +374, +517, +523, +532, +538, +581, +584 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/WrapDynaClass.java": [ +181, +251 +] +} diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S2209.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2209.json new file mode 100644 index 00000000000..f55c6be1bb9 --- /dev/null +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2209.json @@ -0,0 +1,6 @@ +{ +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java": [ +1364, +1453 +] +} diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S2221.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2221.json index c16ae298552..f9cdb3140f4 100644 --- a/its/ruling/src/test/resources/expected/commons-beanutils/java-S2221.json +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2221.json @@ -26,9 +26,6 @@ "commons-beanutils:src/main/java/org/apache/commons/beanutils2/LazyDynaMap.java": [ 283 ], -"commons-beanutils:src/main/java/org/apache/commons/beanutils2/ResultSetDynaClass.java": [ -243 -], "commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/DateTimeConverter.java": [ 530 ], diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S2387.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2387.json new file mode 100644 index 00000000000..a0950531d1e --- /dev/null +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2387.json @@ -0,0 +1,14 @@ +{ +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/LocaleBeanUtilsBean.java": [ +82 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/DateLocaleConverter.java": [ +48 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/DecimalLocaleConverter.java": [ +47 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/locale/converters/StringLocaleConverter.java": [ +50 +] +} diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S2658.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2658.json index babbbc19b7b..e2b8fc1536d 100644 --- a/its/ruling/src/test/resources/expected/commons-beanutils/java-S2658.json +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S2658.json @@ -6,9 +6,6 @@ 514, 523 ], -"commons-beanutils:src/main/java/org/apache/commons/beanutils2/ResultSetDynaClass.java": [ -241 -], "commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/ClassConverter.java": [ 94, 103 diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S3032.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S3032.json index 6bf7a2fd23e..2b15d27693b 100644 --- a/its/ruling/src/test/resources/expected/commons-beanutils/java-S3032.json +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S3032.json @@ -5,9 +5,6 @@ "commons-beanutils:src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java": [ 521 ], -"commons-beanutils:src/main/java/org/apache/commons/beanutils2/ResultSetDynaClass.java": [ -241 -], "commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/ClassConverter.java": [ 102 ] diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S3252.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S3252.json new file mode 100644 index 00000000000..f55c6be1bb9 --- /dev/null +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S3252.json @@ -0,0 +1,6 @@ +{ +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/PropertyUtilsBean.java": [ +1364, +1453 +] +} diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S9357.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S9357.json index 8de5fa38ff1..6cd53f55b88 100644 --- a/its/ruling/src/test/resources/expected/commons-beanutils/java-S9357.json +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S9357.json @@ -9,6 +9,13 @@ 128, 137 ], +"commons-beanutils:src/test/java/org/apache/commons/beanutils2/BeanificationTestCase.java": [ +399 +], +"commons-beanutils:src/test/java/org/apache/commons/beanutils2/ConvertUtilsTestCase.java": [ +621, +628 +], "commons-beanutils:src/test/java/org/apache/commons/beanutils2/bugs/Jira509TestCase.java": [ 60 ] diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S9396.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S9396.json new file mode 100644 index 00000000000..5a7a5549fe2 --- /dev/null +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S9396.json @@ -0,0 +1,28 @@ +{ +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/WeakFastHashMap.java": [ +659, +660, +661 +], +"commons-beanutils:src/test/java/org/apache/commons/beanutils2/BeanWithInnerBean.java": [ +35 +], +"commons-beanutils:src/test/java/org/apache/commons/beanutils2/BeanificationTestCase.java": [ +488, +489, +490, +491, +492, +493 +], +"commons-beanutils:src/test/java/org/apache/commons/beanutils2/bugs/Jira411TestCase.java": [ +46 +], +"commons-beanutils:src/test/java/org/apache/commons/beanutils2/locale/LocaleBeanificationTestCase.java": [ +533, +534, +535, +536, +537 +] +} diff --git a/its/ruling/src/test/resources/expected/commons-beanutils/java-S9439.json b/its/ruling/src/test/resources/expected/commons-beanutils/java-S9439.json index 100125b8532..2ce4e62b4e9 100644 --- a/its/ruling/src/test/resources/expected/commons-beanutils/java-S9439.json +++ b/its/ruling/src/test/resources/expected/commons-beanutils/java-S9439.json @@ -2,6 +2,12 @@ "commons-beanutils:src/main/java/org/apache/commons/beanutils2/BeanUtilsBean.java": [ 1108 ], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/DefaultBeanIntrospector.java": [ +56 +], +"commons-beanutils:src/main/java/org/apache/commons/beanutils2/MappedPropertyDescriptor.java": [ +65 +], "commons-beanutils:src/main/java/org/apache/commons/beanutils2/converters/ArrayConverter.java": [ 135 ], @@ -47,6 +53,12 @@ "commons-beanutils:src/test/java/org/apache/commons/beanutils2/TestBean.java": [ 514 ], +"commons-beanutils:src/test/java/org/apache/commons/beanutils2/TestResultSet.java": [ +90 +], +"commons-beanutils:src/test/java/org/apache/commons/beanutils2/TestResultSetMetaData.java": [ +79 +], "commons-beanutils:src/test/java/org/apache/commons/beanutils2/bugs/Jira345TestCase.java": [ 98, 100 diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S112.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S112.json index 5ad5c72d34c..e748c99903d 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S112.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S112.json @@ -31,7 +31,6 @@ "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java": [ 454, 475, -722, 766 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionCache.java": [ diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1128.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1128.json index caf8ad69f1a..5c4090e8c36 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1128.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1128.json @@ -1,7 +1,27 @@ { +"eclipse-jetty:jetty-http/src/main/java/module-info.java": [ +19, +20 +], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java": [ 23 ], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/session/HouseKeeperTest.java": [ +28 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/session/SessionHandlerTest.java": [ +25 +], +"eclipse-jetty:jetty-slf4j-impl/src/main/java/module-info.java": [ +19, +20 +], +"eclipse-jetty:jetty-util/src/main/java/module-info.java": [ +19 +], +"eclipse-jetty:jetty-xml/src/main/java/module-info.java": [ +19 +], "eclipse-jetty:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java": [ 67 ] diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1130.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1130.json index b1e41fcfa4f..ab57cd0a6ae 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1130.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1130.json @@ -1,6 +1,5 @@ { "eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java": [ -197, 358 ], "eclipse-jetty:jetty-http/src/test/java/org/eclipse/jetty/http/HttpCookieTest.java": [ @@ -24,6 +23,9 @@ "eclipse-jetty:jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java": [ 411 ], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java": [ +137 +], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java": [ 71 ], @@ -39,9 +41,6 @@ "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java": [ 938 ], -"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java": [ -56 -], "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java": [ 265 ], @@ -107,12 +106,13 @@ "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/handler/ThreadLimitHandlerTest.java": [ 54 ], -"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLSelectChannelConnectorLoadTest.java": [ -334 -], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartOutputStream.java": [ +44, 54 ], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartWriter.java": [ +42 +], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/resource/EmptyResource.java": [ 125 ], @@ -186,8 +186,6 @@ 80 ], "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/security/CredentialTest.java": [ -35, -54, 66 ], "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/statistic/CounterStatisticTest.java": [ diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1144.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1144.json index ab7b39507a6..9748ad64f37 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1144.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1144.json @@ -1,6 +1,42 @@ { -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java": [ -433 +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java": [ +309 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [ +279, +284, +289, +295, +322, +420, +438 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java": [ +991, +1007, +1024 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartFormInputStream.java": [ +852 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java": [ +65 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java": [ +286 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java": [ +122 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java": [ +1021 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLReadEOFAfterResponseTest.java": [ +150 +], +"eclipse-jetty:jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLogger.java": [ +641, +648 ], "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/TestIntrospectionUtil.java": [ 71, diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1162.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1162.json index 9f6fb35a8de..8a7514ced77 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1162.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1162.json @@ -70,9 +70,6 @@ 1902, 2844 ], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java": [ -150 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/resource/SeekableByteChannelRangeWriter.java": [ 154, 160 diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S117.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S117.json index 051af20cfdf..4b5312edb64 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S117.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S117.json @@ -8,9 +8,6 @@ 393, 404 ], -"eclipse-jetty:jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java": [ -533 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java": [ 472 ], diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1172.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1172.json index 2bcc44ef51a..1cc1654f4e9 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1172.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1172.json @@ -20,9 +20,6 @@ "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/GracefulStopTest.java": [ 177 ], -"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestFixture.java": [ -112 -], "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/HttpWriterTest.java": [ 243 ], @@ -32,12 +29,6 @@ "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java": [ 622 ], -"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLCloseTest.java": [ -93 -], -"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SSLSelectChannelConnectorLoadTest.java": [ -334 -], "eclipse-jetty:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java": [ 100 ], diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1181.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1181.json index d44bec78066..a7833cd3572 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1181.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1181.json @@ -2,9 +2,6 @@ "eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java": [ 1594 ], -"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/PreEncodedHttpField.java": [ -55 -], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java": [ 221, 249 @@ -19,7 +16,6 @@ "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java": [ 176, 206, -335, 399, 412, 539, @@ -63,8 +59,6 @@ "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [ 540, 775, -802, -885, 930, 951, 1124, @@ -97,9 +91,6 @@ 674, 735 ], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequestLogWriter.java": [ -86 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/DetectorConnectionFactory.java": [ 216 ], @@ -112,7 +103,6 @@ 649, 787, 820, -878, 999, 1016, 1032 @@ -124,7 +114,6 @@ 1006 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java": [ -596, 705, 859, 934, @@ -154,10 +143,7 @@ 152 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Server.java": [ -405, 431, -442, -451, 493, 509, 520 diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1185.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1185.json index bdf77ec9e34..5f79bc1f715 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1185.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1185.json @@ -1,20 +1,9 @@ { -"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [ -508 -], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AcceptRateLimit.java": [ -269 -], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/HotSwapHandler.java": [ -85, -91 -], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/CachingSessionDataStore.java": [ -142, -148 -], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/HostMap.java": [ 51, 58 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java": [ +106 ] } diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1226.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1226.json new file mode 100644 index 00000000000..247d92d5c54 --- /dev/null +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1226.json @@ -0,0 +1,320 @@ +{ +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java": [ +519 +], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpField.java": [ +156 +], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java": [ +562 +], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpURI.java": [ +802, +806, +812, +816, +820, +825, +832, +836, +852, +857, +863, +870, +876, +882, +898, +908, +915, +921, +933, +939, +949, +970, +975, +995, +1001, +1012, +1017, +1022, +1045, +1051, +1056, +1074 +], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java": [ +185, +187, +189, +191, +198, +200 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ArrayByteBufferPool.java": [ +94, +96, +98 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java": [ +225 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java": [ +97 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java": [ +277, +447, +461 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [ +918 +], +"eclipse-jetty:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java": [ +306, +412 +], +"eclipse-jetty:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MetaData.java": [ +235, +242, +424 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java": [ +121 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java": [ +189, +193, +221 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequestLogWriter.java": [ +53 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Cookies.java": [ +66 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java": [ +597, +629, +841 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java": [ +87, +89, +144, +146 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/EncodingHttpWriter.java": [ +59, +60 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java": [ +636, +778, +844 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java": [ +362 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java": [ +900 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java": [ +800, +801, +848 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpWriter.java": [ +68, +69 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Iso88591HttpWriter.java": [ +66, +67 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java": [ +67 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Request.java": [ +188, +1248, +1262 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/RequestLogWriter.java": [ +79, +81 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Response.java": [ +515, +522, +531, +596, +646, +1096, +1393 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ResponseWriter.java": [ +311, +425, +481 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Utf8HttpWriter.java": [ +178, +179 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java": [ +1241, +1248, +1253, +1258, +1478, +1616, +1621, +1627, +1909, +1993, +1999, +2028, +2033, +2035, +2229, +2231 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java": [ +359, +527 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java": [ +157 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java": [ +52, +59 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java": [ +277, +340, +355, +391, +406, +434, +449 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java": [ +289, +299, +327, +337, +366, +376 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ArrayUtil.java": [ +89, +117 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/Attributes.java": [ +54, +71 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java": [ +837, +860, +874, +877, +900, +914, +917, +940, +1143 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/DeprecationWarning.java": [ +73 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/IO.java": [ +161, +205, +463, +464, +468 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/LazyList.java": [ +152, +168 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/RolloverFileOutputStream.java": [ +161, +165, +173, +175 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/StringUtil.java": [ +887 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java": [ +357, +388, +568 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java": [ +120, +126, +424 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java": [ +98, +182, +545, +868 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java": [ +152, +154 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/resource/PathResource.java": [ +265 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java": [ +483, +883 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/resource/URLResource.java": [ +295 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/security/Constraint.java": [ +51 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java": [ +161, +196, +211 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/security/Password.java": [ +174 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/security/UnixCrypt.java": [ +364, +383, +417 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java": [ +1119 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/X509.java": [ +106, +136 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java": [ +141 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/SerializedExecutor.java": [ +77, +87 +], +"eclipse-jetty:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java": [ +530, +804, +818, +824, +836, +870 +] +} diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S125.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S125.json index 16f4ee8f38b..a72560b18fd 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S125.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S125.json @@ -37,6 +37,8 @@ ], "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/AsyncRequestReadTest.java": [ 136, +136, +136, 182, 192, 210, @@ -88,6 +90,8 @@ 127, 158, 190, +219, +219, 219 ], "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java": [ diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1643.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1643.json index 911bba6f205..03a17910726 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1643.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1643.json @@ -8,6 +8,11 @@ 329, 351 ], +"eclipse-jetty:jetty-http/src/test/java/org/eclipse/jetty/http/HttpGeneratorServerHTTPTest.java": [ +176, +181, +186 +], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionIdManager.java": [ 271, 273 diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1699.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1699.json index 8dd85036326..3fb631f1ef3 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1699.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1699.json @@ -43,6 +43,9 @@ 197, 216 ], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AsyncRequestLogWriter.java": [ +61 +], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java": [ 307 ], @@ -98,6 +101,9 @@ "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java": [ 67 ], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java": [ +68 +], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java": [ 65 ], @@ -128,7 +134,8 @@ ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/MultiException.java": [ 59, -63 +63, +64 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/PathWatcher.java": [ 111 diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1854.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1854.json index a6725dec949..54e7021af84 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1854.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1854.json @@ -83,6 +83,9 @@ 175, 188 ], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SlowClientsTest.java": [ +100 +], "eclipse-jetty:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java": [ 586, 629, @@ -92,6 +95,9 @@ 149, 191 ], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/SocketAddressResolver.java": [ +156 +], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java": [ 214 ], @@ -99,6 +105,10 @@ 159, 162 ], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ExecutorThreadPool.java": [ +331, +357 +], "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/BufferUtilTest.java": [ 259 ], @@ -108,5 +118,11 @@ "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/PoolTest.java": [ 366, 519 +], +"eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/Utf8AppendableTest.java": [ +285 +], +"eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/thread/SerializedExecutorTest.java": [ +59 ] } diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1905.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1905.json index 8e43a1395b3..dc176856aec 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1905.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1905.json @@ -1,14 +1,31 @@ { +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpTokens.java": [ +67 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Cookies.java": [ +102 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java": [ +1043 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java": [ +287 +], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java": [ -279 +279, +2427 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java": [ -498 +593 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTernaryTrie.java": [ +157, +281, 305, 360, +378, 411, +422, 454 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ArrayTrie.java": [ @@ -18,14 +35,33 @@ 350, 389 ], -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/AtomicBiInteger.java": [ -268, -269, -282, -297 +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ServiceLoaderSpliterator.java": [ +99 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java": [ +442, +477, +479, +490, +492, +494, +496, +498, +500, +502, +504 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/URIUtil.java": [ +308, +320, +322 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java": [ +894, +899 ], -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/security/CertificateValidator.java": [ -175 +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/security/UnixCrypt.java": [ +363 ], "eclipse-jetty:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java": [ 269, diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1939.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1939.json index 954708884da..8b0b683ce23 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1939.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S1939.json @@ -2,9 +2,6 @@ "eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java": [ 514 ], -"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java": [ -41 -], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java": [ 64 ], @@ -17,18 +14,12 @@ "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java": [ 142 ], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessSet.java": [ -33 -], "eclipse-jetty:jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLogger.java": [ 28 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java": [ 347 ], -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/InetAddressSet.java": [ -39 -], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java": [ 48 ] diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2160.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2160.json index 244cdac6b19..bbdcdb25cf6 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2160.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2160.json @@ -15,6 +15,15 @@ "eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecSet.java": [ 30 ], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/RegexPathSpec.java": [ +24 +], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java": [ +26 +], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java": [ +41 +], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/IncludeExcludeConnectionStatistics.java": [ 78 ], diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2175.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2175.json new file mode 100644 index 00000000000..f67ec8e9c55 --- /dev/null +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2175.json @@ -0,0 +1,260 @@ +{ +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/CookieCompliance.java": [ +97 +], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java": [ +171, +192 +], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java": [ +145, +587, +1254, +1312 +], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpHeaderValue.java": [ +96 +], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpParser.java": [ +406, +411 +], +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java": [ +123 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/AbstractConnection.java": [ +70 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/IncludeExcludeConnectionStatistics.java": [ +91 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java": [ +982 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java": [ +116, +177 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java": [ +423 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java": [ +118, +119 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [ +210 +], +"eclipse-jetty:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java": [ +213, +277, +381, +414, +414, +416 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java": [ +503, +781 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/CachedContentFactory.java": [ +154, +177, +233, +278, +322, +475 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ConnectionLimit.java": [ +248, +265 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java": [ +569, +571 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java": [ +156 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartParser.java": [ +299 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ProxyCustomizer.java": [ +104, +105 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java": [ +85 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Request.java": [ +1837 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Response.java": [ +1299, +1361 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java": [ +398, +399, +400, +401 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Server.java": [ +251 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java": [ +127, +135 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java": [ +455, +681, +685, +686, +690, +693, +696, +714, +721, +723 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/InetAccessSet.java": [ +46 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ManagedAttributeListener.java": [ +57, +64, +71, +81, +91 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java": [ +258 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java": [ +553 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionCache.java": [ +96, +125 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java": [ +838 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java": [ +779, +781, +783, +1045, +1050, +1051 +], +"eclipse-jetty:jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java": [ +155 +], +"eclipse-jetty:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONObjectConvertor.java": [ +102 +], +"eclipse-jetty:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSONPojoConvertor.java": [ +183 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/DecoratedObjectFactory.java": [ +77 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/HostMap.java": [ +60 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExcludeSet.java": [ +58 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/LazyList.java": [ +206, +401 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/LeakDetector.java": [ +100, +149 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/MultiMap.java": [ +60, +102, +196, +215, +234, +264, +284, +289, +292, +313, +371 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/PathWatcher.java": [ +1079 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/Pool.java": [ +312 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/RegexSet.java": [ +64 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java": [ +537, +727 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/TopologicalSort.java": [ +143, +175 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java": [ +236, +247, +304 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java": [ +207, +215, +224 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java": [ +497, +663, +675 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/FileDestroyable.java": [ +73, +79 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java": [ +353 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslSelectionDump.java": [ +134 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/X509.java": [ +137, +137, +144 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java": [ +714 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java": [ +310 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java": [ +118, +128 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/Sweeper.java": [ +124, +139 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPoolBudget.java": [ +67 +], +"eclipse-jetty:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java": [ +1497, +1795, +1801 +] +} diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2184.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2184.json index ab44f94f89b..01bf7181824 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2184.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2184.json @@ -3,5 +3,11 @@ 290, 325, 330 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java": [ +1166 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java": [ +866 ] } diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2185.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2185.json new file mode 100644 index 00000000000..b663f0dba58 --- /dev/null +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2185.json @@ -0,0 +1,5 @@ +{ +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java": [ +1166 +] +} diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2187.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2187.json new file mode 100644 index 00000000000..820d2574363 --- /dev/null +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2187.json @@ -0,0 +1,5 @@ +{ +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorHttpServerTest.java": [ +26 +] +} diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2209.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2209.json new file mode 100644 index 00000000000..bc9e17567d2 --- /dev/null +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2209.json @@ -0,0 +1,11 @@ +{ +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java": [ +384 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java": [ +616 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java": [ +81 +] +} diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2221.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2221.json index af2bd559d4e..47f2e404871 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2221.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2221.json @@ -1,7 +1,4 @@ { -"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/CookieCutter.java": [ -208 -], "eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/DateParser.java": [ 84, 100 @@ -13,7 +10,6 @@ 487 ], "eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpGenerator.java": [ -252, 432 ], "eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/QuotedQualityCSV.java": [ @@ -52,8 +48,7 @@ 244 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java": [ -279, -305 +279 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartFormInputStream.java": [ 453, @@ -109,6 +104,7 @@ 152 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionIdManager.java": [ +306, 366 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/FileSessionDataStore.java": [ diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2293.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2293.json index d485e33db4f..853c4a7b6c9 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2293.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2293.json @@ -61,9 +61,6 @@ "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java": [ 43 ], -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/Sweeper.java": [ -92 -], "eclipse-jetty:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java": [ 61, 64, diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2325.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2325.json index 8edb8ac14dd..135d0eccd5a 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2325.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2325.json @@ -28,7 +28,6 @@ 574 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java": [ -351, 461 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java": [ @@ -46,22 +45,15 @@ "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java": [ 219 ], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java": [ -234 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java": [ 305 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionContext.java": [ -111, 131 ], "eclipse-jetty:jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerConfiguration.java": [ 220 ], -"eclipse-jetty:jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/StdErrAppender.java": [ -188 -], "eclipse-jetty:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/AsyncJSON.java": [ 1105 ], diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2387.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2387.json new file mode 100644 index 00000000000..74ec0b0c184 --- /dev/null +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2387.json @@ -0,0 +1,57 @@ +{ +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java": [ +34 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java": [ +47 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java": [ +32 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/NetworkTrafficSocketChannelEndPoint.java": [ +35 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java": [ +44 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [ +83 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java": [ +51 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/DefaultHandler.java": [ +55 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java": [ +53 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java": [ +74 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java": [ +156 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/NullSessionCache.java": [ +37 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/StopLifeCycle.java": [ +29 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java": [ +38 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java": [ +39 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java": [ +118, +141 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java": [ +48 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/strategy/EatWhatYouKill.java": [ +60 +] +} diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2440.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2440.json new file mode 100644 index 00000000000..940fbf408e1 --- /dev/null +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2440.json @@ -0,0 +1,5 @@ +{ +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java": [ +243 +] +} diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2694.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2694.json index 3c9682178eb..d015d15460d 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2694.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2694.json @@ -1,7 +1,4 @@ { -"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java": [ -209 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java": [ 532 ], diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2698.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2698.json index fe5630ee3aa..29a591ff594 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2698.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2698.json @@ -1741,6 +1741,33 @@ 567, 569 ], +"eclipse-jetty:jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java": [ +292, +293, +321, +322, +328, +329, +352, +353, +359, +383, +384, +390, +391, +397, +398, +403, +428, +430, +450, +468, +472, +492, +499, +513, +565 +], "eclipse-jetty:jetty-io/src/test/java/org/eclipse/jetty/io/SslEngineBehaviorTest.java": [ 77, 83, diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2925.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2925.json index a33638d6511..830ed979429 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2925.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2925.json @@ -51,6 +51,9 @@ 410, 439 ], +"eclipse-jetty:jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java": [ +561 +], "eclipse-jetty:jetty-io/src/test/java/org/eclipse/jetty/io/WriteFlusherTest.java": [ 384 ], @@ -106,6 +109,10 @@ 118, 127 ], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/DelayedServerTest.java": [ +80, +100 +], "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/DetectorConnectionTest.java": [ 140, 160, @@ -126,6 +133,9 @@ 260, 343 ], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java": [ +978 +], "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java": [ 485, 487, @@ -232,6 +242,9 @@ 221, 308 ], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/handler/NcsaRequestLogTest.java": [ +589 +], "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java": [ 250, 315 diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2972.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2972.json index 19aa9f73d62..c0146df5a66 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2972.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S2972.json @@ -81,11 +81,6 @@ "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java": [ 527 ], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/DebugListener.java": [ -198, -239, -293 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/DetectorConnectionFactory.java": [ 143 ], @@ -162,9 +157,6 @@ "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/MovedContextHandler.java": [ 101 ], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/StatisticsHandler.java": [ -72 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ThreadLimitHandler.java": [ 329, 403 diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3032.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3032.json index 80eadccb129..d6743e24c8e 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3032.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3032.json @@ -37,10 +37,6 @@ 559, 637 ], -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java": [ -48, -49 -], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/resource/Resource.java": [ 243 ], diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3242.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3242.json index 4bc82ccd16b..b6f12ba8bd0 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3242.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3242.json @@ -1,12 +1,10 @@ { -"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnectionFactory.java": [ -96 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java": [ 565 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionData.java": [ -73 +73, +130 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/FutureCallback.java": [ 176 @@ -21,9 +19,6 @@ 211, 271 ], -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/UrlEncoded.java": [ -95 -], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java": [ 591 ] diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3252.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3252.json index 71e09650c3d..8eec864ec29 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3252.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3252.json @@ -1,7 +1,4 @@ { -"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java": [ -1311 -], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ByteArrayEndPoint.java": [ 56 ], @@ -11,6 +8,12 @@ 599, 601 ], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java": [ +45 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Request.java": [ +296 +], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java": [ 122, 415, @@ -26,5 +29,8 @@ ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/MultiReleaseJarFile.java": [ 73 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java": [ +81 ] } diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3398.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3398.json index 97db50a4134..1a471d4587c 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3398.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3398.json @@ -1,26 +1,12 @@ { -"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java": [ -309 -], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [ -279, -284, -289, -295, -322, -420, -438 +300, +316 ], "eclipse-jetty:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MetaData.java": [ 252 ], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java": [ -286 -], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java": [ -234 -], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java": [ -122 +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/CachedContentFactory.java": [ +296 ] } diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3457.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3457.json index fd025f35e01..3a1301002a6 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3457.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3457.json @@ -37,6 +37,9 @@ "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java": [ 86 ], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/StressTest.java": [ +243 +], "eclipse-jetty:jetty-slf4j-impl/src/test/java/org/eclipse/jetty/logging/JettyLoggerTest.java": [ 62, 63, diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3740.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3740.json index b97b816a047..b3967c0a0d3 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3740.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3740.json @@ -1,8 +1,4 @@ { -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/IncludeExcludeSet.java": [ -98, -107 -], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java": [ 239, 255 diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3878.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3878.json index 68f90a4c621..b72893d1955 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3878.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S3878.json @@ -2,10 +2,60 @@ "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnectionFactory.java": [ 47 ], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ResourceService.java": [ +813 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java": [ +432, +460 +], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java": [ 77 ], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHandler.java": [ +445, +457, +469, +481, +493, +505 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java": [ +61 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java": [ +1235 +], +"eclipse-jetty:jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java": [ +141 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/BufferUtil.java": [ +109 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java": [ +204 +], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/preventers/GCThreadLeakPreventer.java": [ 47 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/resource/ResourceCollection.java": [ +81, +118, +176, +225, +298, +440 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java": [ +514, +542, +562, +592, +1272, +1306 +], +"eclipse-jetty:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java": [ +1656, +1697 ] } diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4144.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4144.json index e2821e99b11..c4d3abdd1b6 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4144.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4144.json @@ -8,9 +8,6 @@ "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java": [ 296 ], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContextEvent.java": [ -71 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java": [ 135 ], diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4174.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4174.json index b729f364a49..93aaf77418d 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4174.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4174.json @@ -2,9 +2,6 @@ "eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/MappedResource.java": [ 90 ], -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/MemoryUtils.java": [ -33 -], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/PathWatcher.java": [ 678 ], diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4838.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4838.json index 2ca72b275d8..28efd1abead 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4838.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4838.json @@ -1,8 +1,55 @@ { +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java": [ +472 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java": [ +195 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpConfiguration.java": [ +170 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartFormInputStream.java": [ +447 +], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Request.java": [ 1470 ], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Server.java": [ +725 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java": [ +440 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java": [ +1076 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/PathWatcher.java": [ +1317, +1342 +], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java": [ -839 +673, +818, +839, +860, +881, +900, +921 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/AbstractLifeCycle.java": [ +255, +266, +276, +288, +299 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/ContainerLifeCycle.java": [ +879 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ShutdownThread.java": [ +135 +], +"eclipse-jetty:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java": [ +1812 ] } diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4973.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4973.json index fa9abcc9143..03df83a5075 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4973.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S4973.json @@ -2,11 +2,6 @@ "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/security/Credential.java": [ 103 ], -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SniX509ExtendedKeyManager.java": [ -143, -181, -195 -], "eclipse-jetty:tests/jetty-http-tools/src/main/java/org/eclipse/jetty/http/tools/matchers/HttpFieldsContainsHeaderValue.java": [ 63 ] diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5669.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5669.json new file mode 100644 index 00000000000..badace86ff0 --- /dev/null +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5669.json @@ -0,0 +1,5 @@ +{ +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/JDBCSessionDataStore.java": [ +524 +] +} diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5778.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5778.json index d67a8d03e4a..5f2d91be0b1 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5778.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5778.json @@ -44,8 +44,7 @@ 531 ], "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/Utf8AppendableTest.java": [ -78, -286 +78 ], "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/resource/FileSystemResourceTest.java": [ 218, diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5783.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5783.json index e2fee54e7e5..af5b1f6bf7c 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5783.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5783.json @@ -1,9 +1,5 @@ { "eclipse-jetty:jetty-io/src/test/java/org/eclipse/jetty/io/IOTest.java": [ 264 -], -"eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/SharedBlockingCallbackTest.java": [ -109, -134 ] } diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5785.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5785.json index ab24e763083..281108f174b 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5785.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S5785.json @@ -10,6 +10,9 @@ 422, 477 ], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ResourceCacheTest.java": [ +213 +], "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/ArrayUtilTest.java": [ 49, 78, diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S6212.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S6212.json index 8f567ec3f26..c937bbcdecf 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S6212.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S6212.json @@ -22,8 +22,7 @@ ], "eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java": [ 150, -158, -164 +158 ], "eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpCookie.java": [ 241, @@ -209,11 +208,6 @@ 176 ], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/AbstractEndPoint.java": [ -69, -126, -190, -286, -300, 411, 436, 489 @@ -246,22 +240,11 @@ 325 ], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/CyclicTimeout.java": [ -93, 97, -133, 135, -155, -211, 221, -224, 232 ], -"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/FillInterest.java": [ -102, -123, -137, -150 -], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java": [ 97, 98, @@ -283,10 +266,6 @@ 1067 ], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/MappedByteBufferPool.java": [ -116, -144, -167, -177, 226 ], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/NegotiatingClientConnection.java": [ @@ -296,25 +275,17 @@ 57 ], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/SelectorManager.java": [ -214, 237 ], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java": [ -136, -137, 189 ], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java": [ -234, 283, -314, -364, 373, 418, 431, -441, -484, -577 +441 ], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java": [ 106, @@ -324,8 +295,6 @@ 158 ], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [ -309, -312, 409, 549, 617, @@ -352,15 +321,13 @@ 245 ], "eclipse-jetty:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MBeanContainer.java": [ -183, 201, 226, 245, 249, 351, 360, -379, -416 +379 ], "eclipse-jetty:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/MetaData.java": [ 205, @@ -457,11 +424,9 @@ ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/DetectorConnectionFactory.java": [ 83, -86, 112, 168, 237, -240, 248 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java": [ @@ -504,8 +469,7 @@ 398, 399, 462, -472, -872 +472 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java": [ 101, @@ -547,19 +511,13 @@ "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java": [ 173, 205, -255, 279, 340, 398, -575, -590, 623, 838, 949, 1041, -1111, -1126, -1141, 1345, 1359, 1726 @@ -649,17 +607,12 @@ 642, 647 ], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ProxyCustomizer.java": [ -58, -61 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/PushBuilderImpl.java": [ 170, 191 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Request.java": [ 258, -357, 367, 534, 553, @@ -692,7 +645,6 @@ 2238, 2241, 2243, -2277, 2283 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ResourceContentFactory.java": [ @@ -721,13 +673,9 @@ 845 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Response.java": [ -216, -244, 275, 319, 320, -734, -810, 822, 1154, 1158, @@ -826,13 +774,9 @@ 546, 549, 550, -744, -786, -805, 950, 987, 988, -1051, 1124, 1179, 1224, @@ -841,7 +785,6 @@ 1311, 1324, 1343, -1419, 1481, 1491, 1816, @@ -862,7 +805,6 @@ 3011 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java": [ -85, 105, 117, 118, @@ -895,13 +837,8 @@ 501 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerCollection.java": [ -75, -137, -178, 179, -193, 194, -204, 207 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/HandlerList.java": [ @@ -915,7 +852,6 @@ ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/HotSwapHandler.java": [ 59, -72, 99, 109, 119 @@ -932,8 +868,7 @@ ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ResourceHandler.java": [ 86, -89, -100 +89 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/SecuredRedirectHandler.java": [ 44, @@ -978,12 +913,6 @@ "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/AbstractHandlerMBean.java": [ 58 ], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/jmx/ContextHandlerMBean.java": [ -43, -55, -62, -69 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/jmx/AbstractConnectorMBean.java": [ 40 ], @@ -993,7 +922,6 @@ ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionCache.java": [ 347, -369, 647, 756, 835 @@ -1007,9 +935,6 @@ 283 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/DefaultSessionCache.java": [ -96, -104, -125, 139, 180, 187 @@ -1130,7 +1055,6 @@ ], "eclipse-jetty:jetty-slf4j-impl/src/main/java/org/eclipse/jetty/logging/JettyLoggerFactory.java": [ 43, -122, 165, 170 ], @@ -1182,9 +1106,7 @@ 1139, 1232, 1274, -1330, -1372, -1382 +1330 ], "eclipse-jetty:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java": [ 134, @@ -1195,7 +1117,6 @@ 396, 506, 517, -583, 629, 633, 760, @@ -1302,7 +1223,6 @@ 184 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ClassLoadingObjectInputStream.java": [ -77, 93, 96, 99, @@ -1318,12 +1238,10 @@ 238 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/DecoratedObjectFactory.java": [ -92, 138 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/Fields.java": [ -145, -170 +145 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/FutureCallback.java": [ 158 @@ -1435,7 +1353,6 @@ 934, 990, 1110, -1172, 1184, 1198, 1199, @@ -1451,8 +1368,6 @@ ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/Pool.java": [ 167, -186, -213, 237, 460, 462, @@ -1507,11 +1422,7 @@ 212, 213, 364, -386, -729, -745, -756, -783 +386 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/SearchPattern.java": [ 78, @@ -1702,8 +1613,7 @@ "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/Dumpable.java": [ 62, 172, -187, -239 +187 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/FileDestroyable.java": [ 53, @@ -1711,12 +1621,8 @@ ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/component/Graceful.java": [ 127, -160, 169 ], -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/compression/CompressionPool.java": [ -120 -], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/preventers/AbstractLeakPreventer.java": [ 45 ], @@ -1727,14 +1633,11 @@ 47 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarFileResource.java": [ -144, 168, 193, 207, 308, -328, 338, -390, 396 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/resource/JarResource.java": [ @@ -1934,7 +1837,6 @@ 265, 281, 316, -331, 363 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/QueuedThreadPool.java": [ @@ -1964,9 +1866,6 @@ "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/SerializedExecutor.java": [ 46 ], -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/Sweeper.java": [ -170 -], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPoolBudget.java": [ 115, 169 @@ -1993,7 +1892,6 @@ 174, 178, 184, -211, 366, 377, 378, diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S8786.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S8786.json index 7341be74edf..d10709da982 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S8786.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S8786.json @@ -2,6 +2,9 @@ "eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpCompliance.java": [ 137 ], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/CustomRequestLog.java": [ +472 +], "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/ssl/SslContextFactoryTest.java": [ 366 ] diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9391.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9391.json index 17c87b6b4ea..b8d151665f8 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9391.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9391.json @@ -9,10 +9,6 @@ "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java": [ 723 ], -"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java": [ -300, -433 -], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandlerCollection.java": [ 344 ], @@ -28,9 +24,6 @@ "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/PathWatcher.java": [ 835 ], -"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java": [ -406 -], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/SocketAddressResolver.java": [ 67, 176 diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9395.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9395.json index d8595eab348..bb89bcd8f3c 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9395.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9395.json @@ -5,6 +5,9 @@ 763, 768 ], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/statistic/SampleStatistic.java": [ +113 +], "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/MultiExceptionTest.java": [ 201 ] diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9396.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9396.json index 80ec0b00fb8..aebc3e903b7 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9396.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9396.json @@ -1,26 +1,398 @@ { +"eclipse-jetty:jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java": [ +1339, +1340 +], +"eclipse-jetty:jetty-http/src/test/java/org/eclipse/jetty/http/CookieCutterLenientTest.java": [ +183, +184 +], +"eclipse-jetty:jetty-http/src/test/java/org/eclipse/jetty/http/CookieCutterTest.java": [ +295 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/CyclicTimeout.java": [ +194, +195, +196 +], "eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ManagedSelector.java": [ -703 +496, +497, +703, +748, +749, +816, +817, +818, +885, +886, +887, +888, +945, +946, +996, +1018, +1019, +1053, +1054 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/SocketChannelEndPoint.java": [ +58 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/WriteFlusher.java": [ +211, +212 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslClientConnectionFactory.java": [ +144 +], +"eclipse-jetty:jetty-io/src/main/java/org/eclipse/jetty/io/ssl/SslConnection.java": [ +129, +461, +462 +], +"eclipse-jetty:jetty-io/src/test/java/org/eclipse/jetty/io/SslConnectionTest.java": [ +191 +], +"eclipse-jetty:jetty-jmx/src/main/java/org/eclipse/jetty/jmx/ConnectorServer.java": [ +222, +223 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java": [ +689, 690 ], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/CachedContentFactory.java": [ +384, +385, +386, +387, +388, +389, +390, +391, +392, +393, +394, +395, +396, +397, +398, +680, +681, +682 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/DetectorConnectionFactory.java": [ +145, +146 +], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/Dispatcher.java": [ -369 +246, +247, +248, +249, +250, +251, +365, +366, +367, +368, +369, +370 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ForwardedRequestCustomizer.java": [ +571, +572, +574, +575, +576, +577, +578 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java": [ +1217, +1218, +1219, +1220 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java": [ +711, +712, +713, +714, +715, +716, +717, +718 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java": [ -1566 +1496, +1524, +1566, +1596, +1597, +1598, +1599, +1693, +1694, +1695, +1696, +1769, +1770, +1771, +1772 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/LocalConnector.java": [ +237, +238 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/LowResourceMonitor.java": [ +413, +447, +495, +496, +534, +535 ], "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/MultiPartFormInputStream.java": [ -116 +111, +112, +113, +114, +115, +116, +117, +118, +119, +663, +664, +665, +666 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java": [ +438, +439, +440, +441, +442, +443, +444 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/SecureRequestCustomizer.java": [ +319, +320, +322, +323, +324, +325, +326 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/ShutdownMonitor.java": [ +321 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/BufferedResponseHandler.java": [ +177, +178, +179, +180, +181 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/ContextHandler.java": [ +2084, +2085 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java": [ +267, +268, +269 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/AbstractSessionDataStore.java": [ +54, +55 +], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/Session.java": [ +112 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/AbstractHttpTest.java": [ +109, +110 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ConnectorCloseTestBase.java": [ +183, +185, +186, +187 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ForwardedRequestCustomizerTest.java": [ +784 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/HttpInputTest.java": [ +69, +123 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToAsyncCommitTest.java": [ +118, +119, +192, +193, +265, +266, +346, +347, +429, +430, +512, +513, +599, +600, +681, +682, +764, +765, +842, +843, +922, +923 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/HttpManyWaysToCommitTest.java": [ +520 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/HttpServerTestBase.java": [ +1865 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/LocalAsyncContextTest.java": [ +262, +263, +264, +265, +266, +267, +268 ], "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java": [ -2142 +1970, +1971, +1972, +2025, +2027, +2048, +2049, +2106, +2142, +2143, +2144 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ResponseTest.java": [ +1463 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ShutdownMonitorTest.java": [ +244, +245 +], +"eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/handler/ScopedHandlerTest.java": [ +145, +183 +], +"eclipse-jetty:jetty-util-ajax/src/main/java/org/eclipse/jetty/util/ajax/JSON.java": [ +1258, +1259 ], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/BlockingArrayQueue.java": [ -840 +840, +841 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/LeakDetector.java": [ +174, +175, +176 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/MultiReleaseJarFile.java": [ +156, +157, +158, +159, +160 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/PathWatcher.java": [ +534, +535, +536, +537, +538, +539 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/Pool.java": [ +377, +382 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/Scanner.java": [ +160, +161, +162 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/SharedBlockingCallback.java": [ +100 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java": [ +2037 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ReservedThreadExecutor.java": [ +268, +269 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/SerializedExecutor.java": [ +95, +96 +], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/thread/ThreadPoolBudget.java": [ +49, +50 +], +"eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/TestIntrospectionUtil.java": [ +55, +56, +57, +58, +63, +64, +65, +66 ], "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/component/LifeCycleListenerTest.java": [ +161, +162, +163, +164, +165, +167, +168, +169, +170, +172 +], +"eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/thread/QueuedThreadPoolTest.java": [ +106, +107, +108, +109, +110, 172 +], +"eclipse-jetty:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlConfiguration.java": [ +1464, +1465, +1466, +1467, +1592, +1593, +1594 +], +"eclipse-jetty:jetty-xml/src/main/java/org/eclipse/jetty/xml/XmlParser.java": [ +304, +305, +330, +331, +332, +333 ] } diff --git a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9439.json b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9439.json index b275233d73e..9478dbe7305 100644 --- a/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9439.json +++ b/its/ruling/src/test/resources/expected/eclipse-jetty/java-S9439.json @@ -20,6 +20,9 @@ "eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/handler/gzip/GzipHttpOutputInterceptor.java": [ 47 ], +"eclipse-jetty:jetty-server/src/main/java/org/eclipse/jetty/server/session/SessionHandler.java": [ +132 +], "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/HttpChannelEventTest.java": [ 111 ], @@ -55,6 +58,9 @@ "eclipse-jetty:jetty-server/src/test/java/org/eclipse/jetty/server/ssl/SelectChannelServerSslTest.java": [ 193 ], +"eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/TypeUtil.java": [ +66 +], "eclipse-jetty:jetty-util/src/main/java/org/eclipse/jetty/util/Utf8Appendable.java": [ 56 ], @@ -82,6 +88,9 @@ "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/SearchPatternTest.java": [ 227 ], +"eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/TestIntrospectionUtil.java": [ +35 +], "eclipse-jetty:jetty-util/src/test/java/org/eclipse/jetty/util/TrieTest.java": [ 86 ], diff --git a/its/ruling/src/test/resources/expected/guava/java-S107.json b/its/ruling/src/test/resources/expected/guava/java-S107.json index 74f9f1a1738..1968c293527 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S107.json +++ b/its/ruling/src/test/resources/expected/guava/java-S107.json @@ -41,7 +41,8 @@ 110 ], "guava:src/com/google/common/collect/MapMakerInternalMap.java": [ -3917 +3917, +3990 ], "guava:src/com/google/common/primitives/Longs.java": [ 305 diff --git a/its/ruling/src/test/resources/expected/guava/java-S1128.json b/its/ruling/src/test/resources/expected/guava/java-S1128.json index dce09dde4a7..6e9a7f67b42 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1128.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1128.json @@ -1,19 +1,4 @@ { -"guava:src/com/google/common/cache/LocalCache.java": [ -67 -], -"guava:src/com/google/common/collect/ArrayTable.java": [ -36 -], -"guava:src/com/google/common/collect/DenseImmutableTable.java": [ -24 -], -"guava:src/com/google/common/collect/ImmutableMultiset.java": [ -23 -], -"guava:src/com/google/common/collect/RegularImmutableTable.java": [ -20 -], "guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ 26 ] diff --git a/its/ruling/src/test/resources/expected/guava/java-S1130.json b/its/ruling/src/test/resources/expected/guava/java-S1130.json index 3c43b763676..d8a98614e42 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1130.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1130.json @@ -1,5 +1,11 @@ { +"guava:src/com/google/common/io/Closer.java": [ +145, +167, +191 +], "guava:src/com/google/common/io/Files.java": [ +312, 678 ], "guava:src/com/google/common/io/LittleEndianDataInputStream.java": [ diff --git a/its/ruling/src/test/resources/expected/guava/java-S1144.json b/its/ruling/src/test/resources/expected/guava/java-S1144.json new file mode 100644 index 00000000000..983ca4e5574 --- /dev/null +++ b/its/ruling/src/test/resources/expected/guava/java-S1144.json @@ -0,0 +1,75 @@ +{ +"guava:src/com/google/common/cache/LocalCache.java": [ +4736 +], +"guava:src/com/google/common/collect/AbstractMapBasedMultimap.java": [ +1135 +], +"guava:src/com/google/common/collect/BinaryTreeTraverser.java": [ +207 +], +"guava:src/com/google/common/collect/CartesianList.java": [ +67 +], +"guava:src/com/google/common/collect/Constraints.java": [ +281, +332 +], +"guava:src/com/google/common/collect/FilteredEntryMultimap.java": [ +71 +], +"guava:src/com/google/common/collect/Iterables.java": [ +506 +], +"guava:src/com/google/common/collect/LinkedHashMultimap.java": [ +144, +148 +], +"guava:src/com/google/common/collect/LinkedListMultimap.java": [ +310 +], +"guava:src/com/google/common/collect/MapConstraints.java": [ +190, +216, +250, +268, +770, +780 +], +"guava:src/com/google/common/collect/Maps.java": [ +455 +], +"guava:src/com/google/common/collect/Multimaps.java": [ +826, +847, +2051 +], +"guava:src/com/google/common/collect/RegularContiguousSet.java": [ +97 +], +"guava:src/com/google/common/collect/StandardRowSortedTable.java": [ +63 +], +"guava:src/com/google/common/collect/StandardTable.java": [ +162, +185 +], +"guava:src/com/google/common/collect/Synchronized.java": [ +830 +], +"guava:src/com/google/common/collect/TreeRangeMap.java": [ +532 +], +"guava:src/com/google/common/eventbus/SubscriberRegistry.java": [ +184 +], +"guava:src/com/google/common/io/FileBackedOutputStream.java": [ +131 +], +"guava:src/com/google/common/net/MediaType.java": [ +754 +], +"guava:src/com/google/common/reflect/TypeResolver.java": [ +205 +] +} diff --git a/its/ruling/src/test/resources/expected/guava/java-S1160.json b/its/ruling/src/test/resources/expected/guava/java-S1160.json index 4fa898baf52..9d5c19f53c5 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1160.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1160.json @@ -2,19 +2,12 @@ "guava:src/com/google/common/base/Throwables.java": [ 127 ], -"guava:src/com/google/common/io/Closer.java": [ -166, -190 -], "guava:src/com/google/common/io/Files.java": [ 677 ], "guava:src/com/google/common/reflect/Invokable.java": [ 100 ], -"guava:src/com/google/common/util/concurrent/CheckedFuture.java": [ -77 -], "guava:src/com/google/common/util/concurrent/Uninterruptibles.java": [ 169 ] diff --git a/its/ruling/src/test/resources/expected/guava/java-S1161.json b/its/ruling/src/test/resources/expected/guava/java-S1161.json index e2edab58462..565c9301517 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1161.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1161.json @@ -1,4 +1,7 @@ { +"guava:src/com/google/common/cache/LongAdder.java": [ +56 +], "guava:src/com/google/common/collect/EmptyContiguousSet.java": [ 167 ], @@ -6,6 +9,9 @@ 358, 406 ], +"guava:src/com/google/common/collect/ImmutableSetMultimap.java": [ +395 +], "guava:src/com/google/common/reflect/Parameter.java": [ 89, 106, diff --git a/its/ruling/src/test/resources/expected/guava/java-S1162.json b/its/ruling/src/test/resources/expected/guava/java-S1162.json index a3977494918..9e9c7a99e85 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1162.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1162.json @@ -87,12 +87,6 @@ "guava:src/com/google/common/util/concurrent/Futures.java": [ 1339 ], -"guava:src/com/google/common/util/concurrent/FuturesGetChecked.java": [ -59, -77, -79, -207 -], "guava:src/com/google/common/util/concurrent/Monitor.java": [ 503, 774 diff --git a/its/ruling/src/test/resources/expected/guava/java-S1181.json b/its/ruling/src/test/resources/expected/guava/java-S1181.json index 62ebc53d434..838d317bebe 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1181.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1181.json @@ -67,9 +67,6 @@ 175, 213 ], -"guava:src/com/google/common/util/concurrent/AggregateFuture.java": [ -227 -], "guava:src/com/google/common/util/concurrent/Futures.java": [ 776, 787, diff --git a/its/ruling/src/test/resources/expected/guava/java-S1226.json b/its/ruling/src/test/resources/expected/guava/java-S1226.json new file mode 100644 index 00000000000..4af03e18693 --- /dev/null +++ b/its/ruling/src/test/resources/expected/guava/java-S1226.json @@ -0,0 +1,239 @@ +{ +"guava:src/com/google/common/base/Ascii.java": [ +604 +], +"guava:src/com/google/common/base/CharMatcher.java": [ +909, +913, +947, +1005 +], +"guava:src/com/google/common/base/Preconditions.java": [ +410 +], +"guava:src/com/google/common/base/Throwables.java": [ +175, +202 +], +"guava:src/com/google/common/base/internal/Finalizer.java": [ +173 +], +"guava:src/com/google/common/cache/LocalCache.java": [ +1796, +1797, +1798, +1799, +1800 +], +"guava:src/com/google/common/cache/Striped64.java": [ +196, +230 +], +"guava:src/com/google/common/collect/Hashing.java": [ +59 +], +"guava:src/com/google/common/collect/ImmutableCollection.java": [ +181 +], +"guava:src/com/google/common/collect/ImmutableEnumMap.java": [ +82 +], +"guava:src/com/google/common/collect/ImmutableEnumSet.java": [ +83, +99 +], +"guava:src/com/google/common/collect/ImmutableList.java": [ +312 +], +"guava:src/com/google/common/collect/ImmutableMultimap.java": [ +692 +], +"guava:src/com/google/common/collect/ImmutableMultiset.java": [ +286 +], +"guava:src/com/google/common/collect/ImmutableSortedMultiset.java": [ +232 +], +"guava:src/com/google/common/collect/MapMakerInternalMap.java": [ +1815, +1816, +1817, +1818, +1819 +], +"guava:src/com/google/common/collect/MinMaxPriorityQueue.java": [ +558, +576, +715 +], +"guava:src/com/google/common/collect/ObjectArrays.java": [ +145, +168 +], +"guava:src/com/google/common/collect/RegularImmutableBiMap.java": [ +123 +], +"guava:src/com/google/common/collect/RegularImmutableMap.java": [ +98 +], +"guava:src/com/google/common/collect/RegularImmutableSortedSet.java": [ +85 +], +"guava:src/com/google/common/collect/Sets.java": [ +1581 +], +"guava:src/com/google/common/collect/SortedLists.java": [ +262 +], +"guava:src/com/google/common/collect/TreeRangeSet.java": [ +442 +], +"guava:src/com/google/common/escape/ArrayBasedCharEscaper.java": [ +104, +105 +], +"guava:src/com/google/common/escape/ArrayBasedUnicodeEscaper.java": [ +113, +114 +], +"guava:src/com/google/common/escape/UnicodeEscaper.java": [ +216 +], +"guava:src/com/google/common/hash/BloomFilter.java": [ +342, +451 +], +"guava:src/com/google/common/hash/HashCode.java": [ +95 +], +"guava:src/com/google/common/hash/Murmur3_128HashFunction.java": [ +185, +186, +187, +188, +189, +194, +195, +196, +201, +202, +203 +], +"guava:src/com/google/common/hash/Murmur3_32HashFunction.java": [ +131, +132, +133, +138, +139, +140, +146, +147, +148, +149, +150, +151 +], +"guava:src/com/google/common/io/BaseEncoding.java": [ +224, +653, +870 +], +"guava:src/com/google/common/io/ByteSource.java": [ +670 +], +"guava:src/com/google/common/io/ByteStreams.java": [ +628, +649, +719 +], +"guava:src/com/google/common/io/CharSource.java": [ +543 +], +"guava:src/com/google/common/io/CharStreams.java": [ +169 +], +"guava:src/com/google/common/math/BigIntegerMath.java": [ +405 +], +"guava:src/com/google/common/math/DoubleMath.java": [ +182 +], +"guava:src/com/google/common/math/IntMath.java": [ +209, +217, +384, +386, +400, +403, +404, +479, +482, +529 +], +"guava:src/com/google/common/math/LongMath.java": [ +254, +262, +478, +480, +494, +497, +498, +595, +598, +654, +717, +718, +823, +906, +910, +921 +], +"guava:src/com/google/common/net/InetAddresses.java": [ +192 +], +"guava:src/com/google/common/net/InternetDomainName.java": [ +143, +146 +], +"guava:src/com/google/common/net/PercentEscaper.java": [ +105, +197, +199, +201, +213, +215, +217, +219, +232, +234, +236, +238, +240, +242 +], +"guava:src/com/google/common/primitives/Longs.java": [ +274 +], +"guava:src/com/google/common/primitives/UnsignedLongs.java": [ +384, +389 +], +"guava:src/com/google/common/reflect/AbstractInvocationHandler.java": [ +66 +], +"guava:src/com/google/common/util/concurrent/AggregateFuture.java": [ +280 +], +"guava:src/com/google/common/util/concurrent/Monitor.java": [ +1127 +], +"guava:src/com/google/common/util/concurrent/MoreExecutors.java": [ +717 +], +"guava:src/com/google/common/util/concurrent/SmoothRateLimiter.java": [ +247 +], +"guava:src/com/google/common/util/concurrent/Striped.java": [ +459 +] +} diff --git a/its/ruling/src/test/resources/expected/guava/java-S1698.json b/its/ruling/src/test/resources/expected/guava/java-S1698.json index 398ed54b1d9..146f2e57e24 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1698.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1698.json @@ -21,15 +21,9 @@ 364, 533 ], -"guava:src/com/google/common/collect/Range.java": [ -355, -356, -365, -393 -], -"guava:src/com/google/common/collect/TreeRangeSet.java": [ -506, -559 +"guava:src/com/google/common/collect/TreeMultiset.java": [ +425, +467 ], "guava:src/com/google/common/io/Files.java": [ 387 diff --git a/its/ruling/src/test/resources/expected/guava/java-S1699.json b/its/ruling/src/test/resources/expected/guava/java-S1699.json index 721f8dc38ce..ae13c0a0290 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1699.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1699.json @@ -39,6 +39,12 @@ "guava:src/com/google/common/collect/TreeMultimap.java": [ 130 ], +"guava:src/com/google/common/reflect/TypeParameter.java": [ +47 +], +"guava:src/com/google/common/reflect/TypeToken.java": [ +112 +], "guava:src/com/google/common/util/concurrent/CycleDetectingLockFactory.java": [ 529, 534, diff --git a/its/ruling/src/test/resources/expected/guava/java-S1845.json b/its/ruling/src/test/resources/expected/guava/java-S1845.json index 8b2d61ace5c..995d42da6ce 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1845.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1845.json @@ -9,20 +9,61 @@ ], "guava:src/com/google/common/cache/LocalCache.java": [ 732, -4356 +4356, +4588 +], +"guava:src/com/google/common/collect/Constraints.java": [ +146, +208 +], +"guava:src/com/google/common/collect/MapConstraints.java": [ +408, +410, +412, +431, +432, +521 ], "guava:src/com/google/common/collect/MapMaker.java": [ +115, +116, 117, 127, 479 ], "guava:src/com/google/common/collect/MapMakerInternalMap.java": [ 615, -3687 +3687, +3915 +], +"guava:src/com/google/common/collect/Maps.java": [ +1472, +1474 +], +"guava:src/com/google/common/collect/Multimaps.java": [ +496, +497, +498, +499, +500 +], +"guava:src/com/google/common/collect/Multisets.java": [ +98, +110, +122 +], +"guava:src/com/google/common/collect/SingletonImmutableBiMap.java": [ +82 ], "guava:src/com/google/common/collect/StandardTable.java": [ 270 ], +"guava:src/com/google/common/collect/Tables.java": [ +460 +], +"guava:src/com/google/common/collect/TreeRangeSet.java": [ +89 +], "guava:src/com/google/common/io/Closer.java": [ 107 ], diff --git a/its/ruling/src/test/resources/expected/guava/java-S1905.json b/its/ruling/src/test/resources/expected/guava/java-S1905.json index 607efda4232..a41e3401fe0 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1905.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1905.json @@ -1,15 +1,157 @@ { +"guava:src/com/google/common/base/Ascii.java": [ +446, +479, +495, +528, +667 +], +"guava:src/com/google/common/base/CharMatcher.java": [ +947, +1358 +], "guava:src/com/google/common/base/Converter.java": [ 456 ], +"guava:src/com/google/common/base/Equivalence.java": [ +208 +], +"guava:src/com/google/common/cache/CacheBuilder.java": [ +483, +738 +], +"guava:src/com/google/common/cache/LocalCache.java": [ +1050, +4037 +], +"guava:src/com/google/common/collect/Collections2.java": [ +316 +], +"guava:src/com/google/common/collect/ForwardingSortedMap.java": [ +118 +], +"guava:src/com/google/common/collect/ForwardingSortedSet.java": [ +101 +], +"guava:src/com/google/common/collect/ImmutableListMultimap.java": [ +298 +], +"guava:src/com/google/common/collect/ImmutableMap.java": [ +318 +], +"guava:src/com/google/common/collect/ImmutableMultimap.java": [ +507 +], +"guava:src/com/google/common/collect/ImmutableSet.java": [ +193 +], +"guava:src/com/google/common/collect/ImmutableSetMultimap.java": [ +379 +], +"guava:src/com/google/common/collect/ImmutableSortedMap.java": [ +849 +], +"guava:src/com/google/common/collect/ImmutableSortedMultiset.java": [ +64 +], +"guava:src/com/google/common/collect/ImmutableSortedSet.java": [ +530 +], +"guava:src/com/google/common/collect/ImmutableTable.java": [ +198 +], +"guava:src/com/google/common/collect/Iterators.java": [ +693 +], +"guava:src/com/google/common/collect/MapMakerInternalMap.java": [ +902 +], +"guava:src/com/google/common/collect/Multimaps.java": [ +866, +879, +894 +], +"guava:src/com/google/common/collect/Multisets.java": [ +79, +107, +130 +], +"guava:src/com/google/common/collect/Range.java": [ +126, +138, +307 +], +"guava:src/com/google/common/collect/RegularImmutableAsList.java": [ +56 +], +"guava:src/com/google/common/collect/RegularImmutableList.java": [ +81 +], +"guava:src/com/google/common/collect/RegularImmutableSet.java": [ +74 +], "guava:src/com/google/common/collect/RegularImmutableSortedMultiset.java": [ 114 ], +"guava:src/com/google/common/collect/Tables.java": [ +251, +469, +588 +], +"guava:src/com/google/common/collect/TreeBasedTable.java": [ +207 +], +"guava:src/com/google/common/collect/TreeRangeMap.java": [ +222 +], +"guava:src/com/google/common/hash/BloomFilter.java": [ +453 +], "guava:src/com/google/common/hash/Hashing.java": [ -570 +398, +427 +], +"guava:src/com/google/common/hash/SipHashFunction.java": [ +94 +], +"guava:src/com/google/common/io/BaseEncoding.java": [ +647 +], +"guava:src/com/google/common/math/BigIntegerMath.java": [ +135 +], +"guava:src/com/google/common/net/InetAddresses.java": [ +707 +], +"guava:src/com/google/common/reflect/Invokable.java": [ +158, +164 +], +"guava:src/com/google/common/reflect/TypeToken.java": [ +179, +289, +299, +325, +336, +377, +649, +663, +698, +728, +745, +1009, +1012, +1039, +1071 +], +"guava:src/com/google/common/util/concurrent/AggregateFutureState.java": [ +40 +], +"guava:src/com/google/common/util/concurrent/Futures.java": [ +1487, +1487 ], -"guava:src/com/google/common/hash/Murmur3_128HashFunction.java": [ -136, -153 +"guava:src/com/google/common/util/concurrent/FuturesGetChecked.java": [ +228 ] } diff --git a/its/ruling/src/test/resources/expected/guava/java-S1939.json b/its/ruling/src/test/resources/expected/guava/java-S1939.json index 805fac2b31d..6fa74d8a8a5 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1939.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1939.json @@ -8,17 +8,6 @@ "guava:src/com/google/common/collect/MapMaker.java": [ 869 ], -"guava:src/com/google/common/collect/MapMakerInternalMap.java": [ -1014, -1061, -1096, -1268, -1316, -1352, -1526, -1574, -1610 -], "guava:src/com/google/common/collect/Synchronized.java": [ 1109 ], diff --git a/its/ruling/src/test/resources/expected/guava/java-S1948.json b/its/ruling/src/test/resources/expected/guava/java-S1948.json index 5d472ac5984..281f93d0a74 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S1948.json +++ b/its/ruling/src/test/resources/expected/guava/java-S1948.json @@ -128,6 +128,9 @@ "guava:src/com/google/common/collect/ImmutableEnumMap.java": [ 107 ], +"guava:src/com/google/common/collect/ImmutableEnumSet.java": [ +132 +], "guava:src/com/google/common/collect/ImmutableList.java": [ 588 ], diff --git a/its/ruling/src/test/resources/expected/guava/java-S2118.json b/its/ruling/src/test/resources/expected/guava/java-S2118.json index c456f8ac47e..1bef6ed0607 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S2118.json +++ b/its/ruling/src/test/resources/expected/guava/java-S2118.json @@ -1,13 +1,47 @@ { +"guava:src/com/google/common/collect/AbstractBiMap.java": [ +424 +], +"guava:src/com/google/common/collect/ConcurrentHashMultiset.java": [ +591 +], +"guava:src/com/google/common/collect/EnumBiMap.java": [ +138, +139 +], +"guava:src/com/google/common/collect/EnumHashBiMap.java": [ +111 +], +"guava:src/com/google/common/collect/EnumMultiset.java": [ +86 +], "guava:src/com/google/common/collect/ImmutableSetMultimap.java": [ 508 ], +"guava:src/com/google/common/collect/LinkedHashMultimap.java": [ +545, +549, +550 +], +"guava:src/com/google/common/collect/LinkedListMultimap.java": [ +802, +803 +], +"guava:src/com/google/common/collect/MapMakerInternalMap.java": [ +3943 +], "guava:src/com/google/common/collect/Multimaps.java": [ 139, 218, 296, 380 ], +"guava:src/com/google/common/collect/Serialization.java": [ +62, +63, +152, +155 +], "guava:src/com/google/common/collect/TreeMultimap.java": [ 248, 249 diff --git a/its/ruling/src/test/resources/expected/guava/java-S2159.json b/its/ruling/src/test/resources/expected/guava/java-S2159.json new file mode 100644 index 00000000000..ec1ac0a73e3 --- /dev/null +++ b/its/ruling/src/test/resources/expected/guava/java-S2159.json @@ -0,0 +1,20 @@ +{ +"guava:src/com/google/common/cache/LocalCache.java": [ +4417, +4417 +], +"guava:src/com/google/common/collect/ImmutableMapEntrySet.java": [ +75 +], +"guava:src/com/google/common/collect/SingletonImmutableBiMap.java": [ +52, +61, +65 +], +"guava:src/com/google/common/collect/SingletonImmutableSet.java": [ +58 +], +"guava:src/com/google/common/reflect/Types.java": [ +449 +] +} diff --git a/its/ruling/src/test/resources/expected/guava/java-S2175.json b/its/ruling/src/test/resources/expected/guava/java-S2175.json new file mode 100644 index 00000000000..42ccdc56ded --- /dev/null +++ b/its/ruling/src/test/resources/expected/guava/java-S2175.json @@ -0,0 +1,274 @@ +{ +"guava:src/com/google/common/base/Predicates.java": [ +564 +], +"guava:src/com/google/common/cache/AbstractCache.java": [ +69 +], +"guava:src/com/google/common/cache/LocalCache.java": [ +2252, +2253, +2596, +3263, +3264, +3296, +3297, +3669, +3806, +3962, +4226, +4485, +4490, +4520, +4545, +4557, +4777 +], +"guava:src/com/google/common/collect/AbstractBiMap.java": [ +153, +159, +295 +], +"guava:src/com/google/common/collect/AbstractMapBasedMultimap.java": [ +186, +258, +547, +570, +584, +836, +842, +1304, +1366 +], +"guava:src/com/google/common/collect/AbstractMapBasedMultiset.java": [ +243, +255 +], +"guava:src/com/google/common/collect/AbstractMultimap.java": [ +48, +58, +59, +64, +65 +], +"guava:src/com/google/common/collect/AbstractTable.java": [ +60 +], +"guava:src/com/google/common/collect/Collections2.java": [ +193, +193 +], +"guava:src/com/google/common/collect/ConcurrentHashMultiset.java": [ +302, +302, +344, +344, +394, +394, +436, +436, +448, +448 +], +"guava:src/com/google/common/collect/DescendingImmutableSortedSet.java": [ +38 +], +"guava:src/com/google/common/collect/FilteredEntryMultimap.java": [ +172, +238 +], +"guava:src/com/google/common/collect/ForwardingCollection.java": [ +69, +79, +89 +], +"guava:src/com/google/common/collect/ForwardingConcurrentMap.java": [ +49, +49 +], +"guava:src/com/google/common/collect/ForwardingList.java": [ +81, +86 +], +"guava:src/com/google/common/collect/ForwardingMap.java": [ +80, +90, +95, +100 +], +"guava:src/com/google/common/collect/ImmutableAsList.java": [ +42 +], +"guava:src/com/google/common/collect/ImmutableEnumMap.java": [ +68, +73 +], +"guava:src/com/google/common/collect/ImmutableEnumSet.java": [ +77 +], +"guava:src/com/google/common/collect/ImmutableList.java": [ +354, +525, +530, +536 +], +"guava:src/com/google/common/collect/ImmutableMap.java": [ +441, +446, +546, +551 +], +"guava:src/com/google/common/collect/ImmutableRangeSet.java": [ +500 +], +"guava:src/com/google/common/collect/ImmutableTable.java": [ +327 +], +"guava:src/com/google/common/collect/Iterables.java": [ +366 +], +"guava:src/com/google/common/collect/LinkedListMultimap.java": [ +459, +473, +572, +577 +], +"guava:src/com/google/common/collect/Lists.java": [ +1119 +], +"guava:src/com/google/common/collect/MapMakerInternalMap.java": [ +2310, +2313, +2866, +2867, +2886, +2887, +3168, +3304, +3795, +3800, +3829, +3856, +3868 +], +"guava:src/com/google/common/collect/Maps.java": [ +792, +808, +1947, +1953, +1954, +1962, +1963, +2652, +2652, +2656, +2665, +2665, +2738, +2799, +2800, +3016, +3021, +3031, +3590, +3594, +3595, +3760, +3809, +3835, +3845, +3868 +], +"guava:src/com/google/common/collect/Multimaps.java": [ +945, +950, +1022, +1025, +1255, +1692, +1711, +1722, +1726 +], +"guava:src/com/google/common/collect/Multisets.java": [ +347, +892 +], +"guava:src/com/google/common/collect/RegularContiguousSet.java": [ +71 +], +"guava:src/com/google/common/collect/RegularImmutableMultiset.java": [ +152 +], +"guava:src/com/google/common/collect/Sets.java": [ +602, +602, +659, +659, +696, +696, +747, +747 +], +"guava:src/com/google/common/collect/StandardTable.java": [ +155, +157, +711, +821 +], +"guava:src/com/google/common/collect/Synchronized.java": [ +141, +167, +174, +342, +349, +944, +951, +968, +1006, +1164 +], +"guava:src/com/google/common/collect/TreeRangeMap.java": [ +227, +480, +517, +550 +], +"guava:src/com/google/common/collect/TreeRangeSet.java": [ +311, +606, +694, +714 +], +"guava:src/com/google/common/eventbus/SubscriberRegistry.java": [ +87, +109, +110, +126, +140 +], +"guava:src/com/google/common/reflect/ClassPath.java": [ +395 +], +"guava:src/com/google/common/reflect/TypeToken.java": [ +540 +], +"guava:src/com/google/common/reflect/TypeVisitor.java": [ +93 +], +"guava:src/com/google/common/util/concurrent/AtomicLongMap.java": [ +245, +306, +426 +], +"guava:src/com/google/common/util/concurrent/CycleDetectingLockFactory.java": [ +309, +537 +], +"guava:src/com/google/common/util/concurrent/SimpleTimeLimiter.java": [ +112 +], +"guava:src/com/google/common/util/concurrent/Striped.java": [ +426 +] +} diff --git a/its/ruling/src/test/resources/expected/guava/java-S2178.json b/its/ruling/src/test/resources/expected/guava/java-S2178.json index 00f641e30b0..81485221236 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S2178.json +++ b/its/ruling/src/test/resources/expected/guava/java-S2178.json @@ -18,8 +18,7 @@ ], "guava:src/com/google/common/math/IntMath.java": [ 63, -323, -481 +323 ], "guava:src/com/google/common/math/LongMath.java": [ 63, @@ -57,7 +56,6 @@ 762, 762, 763, -1382, 1382 ], "guava:src/com/google/common/util/concurrent/Monitor.java": [ diff --git a/its/ruling/src/test/resources/expected/guava/java-S2184.json b/its/ruling/src/test/resources/expected/guava/java-S2184.json new file mode 100644 index 00000000000..b51fa673958 --- /dev/null +++ b/its/ruling/src/test/resources/expected/guava/java-S2184.json @@ -0,0 +1,27 @@ +{ +"guava:src/com/google/common/collect/TreeMultiset.java": [ +183, +205, +833 +], +"guava:src/com/google/common/io/MultiInputStream.java": [ +116 +], +"guava:src/com/google/common/math/DoubleMath.java": [ +450 +], +"guava:src/com/google/common/math/DoubleUtils.java": [ +99 +], +"guava:src/com/google/common/primitives/UnsignedBytes.java": [ +394, +395 +], +"guava:src/com/google/common/util/concurrent/SmoothRateLimiter.java": [ +260, +334, +343, +356, +392 +] +} diff --git a/its/ruling/src/test/resources/expected/guava/java-S2293.json b/its/ruling/src/test/resources/expected/guava/java-S2293.json index bb908fc04cb..05a07beaf9a 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S2293.json +++ b/its/ruling/src/test/resources/expected/guava/java-S2293.json @@ -13,7 +13,6 @@ "guava:src/com/google/common/base/Enums.java": [ 84, 91, -93, 120 ], "guava:src/com/google/common/base/Equivalence.java": [ @@ -110,9 +109,7 @@ 2261, 2436, 2443, -3163, -4733, -4829 +3163 ], "guava:src/com/google/common/cache/Striped64.java": [ 125 @@ -134,7 +131,6 @@ 78, 91, 101, -105, 130 ], "guava:src/com/google/common/collect/ArrayTable.java": [ @@ -163,13 +159,10 @@ ], "guava:src/com/google/common/collect/ComputingConcurrentHashMap.java": [ 56, -135, -361, -365 +135 ], "guava:src/com/google/common/collect/ConcurrentHashMultiset.java": [ 88, -88, 128 ], "guava:src/com/google/common/collect/Constraints.java": [ @@ -197,8 +190,7 @@ 57 ], "guava:src/com/google/common/collect/EnumHashBiMap.java": [ -56, -122 +56 ], "guava:src/com/google/common/collect/EnumMultiset.java": [ 42, @@ -227,9 +219,6 @@ ], "guava:src/com/google/common/collect/HashBasedTable.java": [ 76, -76, -76, -92, 92 ], "guava:src/com/google/common/collect/HashBiMap.java": [ @@ -242,13 +231,11 @@ "guava:src/com/google/common/collect/HashMultimap.java": [ 60, 73, -84, -88 +84 ], "guava:src/com/google/common/collect/HashMultiset.java": [ 42, -53, -71 +53 ], "guava:src/com/google/common/collect/ImmutableBiMap.java": [ 49, @@ -295,7 +282,6 @@ ], "guava:src/com/google/common/collect/ImmutableMultimap.java": [ 126, -136, 166, 284, 525, @@ -340,7 +326,6 @@ ], "guava:src/com/google/common/collect/ImmutableSetMultimap.java": [ 134, -143, 272, 321, 335, @@ -352,11 +337,7 @@ 67, 74, 100, -101, 353, -354, -354, -355, 365, 377, 385, @@ -369,7 +350,6 @@ 74, 262, 270, -271, 282, 305, 364, @@ -412,7 +392,6 @@ "guava:src/com/google/common/collect/LinkedHashMultimap.java": [ 90, 104, -227, 231, 246, 428, @@ -421,9 +400,7 @@ ], "guava:src/com/google/common/collect/LinkedHashMultiset.java": [ 52, -63, -83, -105 +63 ], "guava:src/com/google/common/collect/LinkedListMultimap.java": [ 174, @@ -431,7 +408,6 @@ 197, 205, 221, -224, 232, 789 ], @@ -464,7 +440,6 @@ 294, 341, 380, -381, 460, 782 ], @@ -611,7 +586,6 @@ ], "guava:src/com/google/common/collect/MutableClassToInstanceMap.java": [ 45, -46, 56 ], "guava:src/com/google/common/collect/Ordering.java": [ @@ -723,7 +697,6 @@ 955, 1162, 1188, -1188, 1266, 1333, 1426, @@ -739,8 +712,6 @@ 151 ], "guava:src/com/google/common/collect/SparseImmutableTable.java": [ -42, -46, 69, 76 ], @@ -787,17 +758,13 @@ 106, 122, 132, -140, -141, 197 ], "guava:src/com/google/common/collect/TreeMultimap.java": [ 89, 103, 115, -121, -142, -260 +142 ], "guava:src/com/google/common/collect/TreeMultiset.java": [ 74, @@ -813,20 +780,15 @@ 977 ], "guava:src/com/google/common/collect/TreeRangeMap.java": [ -61, -129, -158 +61 ], "guava:src/com/google/common/collect/TreeRangeSet.java": [ 54, -54, 280, 434, 443, -612, 658, -665, -808 +665 ], "guava:src/com/google/common/collect/TreeTraverser.java": [ 88, @@ -901,11 +863,7 @@ 191, 327 ], -"guava:src/com/google/common/util/concurrent/AbstractService.java": [ -411 -], "guava:src/com/google/common/util/concurrent/AtomicLongMap.java": [ -66, 66 ], "guava:src/com/google/common/util/concurrent/Atomics.java": [ @@ -942,8 +900,7 @@ 1621 ], "guava:src/com/google/common/util/concurrent/FuturesGetChecked.java": [ -155, -180 +155 ], "guava:src/com/google/common/util/concurrent/JdkFutureAdapters.java": [ 63, @@ -963,8 +920,7 @@ 57 ], "guava:src/com/google/common/util/concurrent/ServiceManager.java": [ -202, -517 +202 ], "guava:src/com/google/common/util/concurrent/SettableFuture.java": [ 39 diff --git a/its/ruling/src/test/resources/expected/guava/java-S2325.json b/its/ruling/src/test/resources/expected/guava/java-S2325.json index 9cee686bada..75b803f7541 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S2325.json +++ b/its/ruling/src/test/resources/expected/guava/java-S2325.json @@ -2,12 +2,19 @@ "guava:src/com/google/common/cache/LocalCache.java": [ 1964 ], +"guava:src/com/google/common/collect/FilteredEntryMultimap.java": [ +395 +], "guava:src/com/google/common/collect/HashBiMap.java": [ 358 ], "guava:src/com/google/common/collect/MapMakerInternalMap.java": [ 1975 ], +"guava:src/com/google/common/hash/BloomFilterStrategies.java": [ +130, +135 +], "guava:src/com/google/common/io/ByteSource.java": [ 213, 236 @@ -15,7 +22,14 @@ "guava:src/com/google/common/io/CharSource.java": [ 154 ], +"guava:src/com/google/common/math/LongMath.java": [ +810, +817 +], "guava:src/com/google/common/util/concurrent/CycleDetectingLockFactory.java": [ 773 +], +"guava:src/com/google/common/util/concurrent/Futures.java": [ +1335 ] } diff --git a/its/ruling/src/test/resources/expected/guava/java-S2384.json b/its/ruling/src/test/resources/expected/guava/java-S2384.json index 2d75ded93c2..5c7467caf71 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S2384.json +++ b/its/ruling/src/test/resources/expected/guava/java-S2384.json @@ -22,10 +22,6 @@ "guava:src/com/google/common/collect/ConcurrentHashMultiset.java": [ 144 ], -"guava:src/com/google/common/collect/Constraints.java": [ -69, -112 -], "guava:src/com/google/common/collect/EvictingQueue.java": [ 80 ], @@ -41,16 +37,11 @@ "guava:src/com/google/common/collect/ImmutableMapEntrySet.java": [ 42 ], -"guava:src/com/google/common/collect/Lists.java": [ -813 -], "guava:src/com/google/common/collect/MapConstraints.java": [ -309, 653, 658 ], "guava:src/com/google/common/collect/Maps.java": [ -767, 1316, 1320, 3133, diff --git a/its/ruling/src/test/resources/expected/guava/java-S2387.json b/its/ruling/src/test/resources/expected/guava/java-S2387.json index b2438d3406c..54a0a64ec20 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S2387.json +++ b/its/ruling/src/test/resources/expected/guava/java-S2387.json @@ -4,6 +4,18 @@ 4230, 4239 ], +"guava:src/com/google/common/collect/DenseImmutableTable.java": [ +41 +], +"guava:src/com/google/common/collect/ImmutableSetMultimap.java": [ +434 +], +"guava:src/com/google/common/collect/ImmutableSortedMap.java": [ +505 +], +"guava:src/com/google/common/collect/Iterables.java": [ +85 +], "guava:src/com/google/common/collect/MapMakerInternalMap.java": [ 1241, 1499, @@ -13,5 +25,8 @@ "guava:src/com/google/common/collect/Maps.java": [ 3384, 3395 +], +"guava:src/com/google/common/util/concurrent/WrappingScheduledExecutorService.java": [ +33 ] } diff --git a/its/ruling/src/test/resources/expected/guava/java-S2388.json b/its/ruling/src/test/resources/expected/guava/java-S2388.json index 9bd0096e58f..e8d6df90e58 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S2388.json +++ b/its/ruling/src/test/resources/expected/guava/java-S2388.json @@ -12,9 +12,6 @@ 990, 1000 ], -"guava:src/com/google/common/collect/ConcurrentHashMultiset.java": [ -579 -], "guava:src/com/google/common/collect/MapMakerInternalMap.java": [ 1182, 1439, @@ -25,12 +22,5 @@ ], "guava:src/com/google/common/io/BaseEncoding.java": [ 870 -], -"guava:src/com/google/common/util/concurrent/AbstractExecutionThreadService.java": [ -58 -], -"guava:src/com/google/common/util/concurrent/AbstractScheduledService.java": [ -207, -237 ] } diff --git a/its/ruling/src/test/resources/expected/guava/java-S2629.json b/its/ruling/src/test/resources/expected/guava/java-S2629.json index 5b107c96c03..ff76e61c2b6 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S2629.json +++ b/its/ruling/src/test/resources/expected/guava/java-S2629.json @@ -1,7 +1,4 @@ { -"guava:src/com/google/common/io/Closer.java": [ -253 -], "guava:src/com/google/common/reflect/ClassPath.java": [ 448 ], diff --git a/its/ruling/src/test/resources/expected/guava/java-S2694.json b/its/ruling/src/test/resources/expected/guava/java-S2694.json index 48dc4130fd5..cf2dd4a1bd8 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S2694.json +++ b/its/ruling/src/test/resources/expected/guava/java-S2694.json @@ -1,6 +1,6 @@ { -"guava:src/com/google/common/cache/LocalCache.java": [ -4449 +"guava:src/com/google/common/collect/FilteredKeySetMultimap.java": [ +70 ], "guava:src/com/google/common/reflect/Types.java": [ 137 diff --git a/its/ruling/src/test/resources/expected/guava/java-S2885.json b/its/ruling/src/test/resources/expected/guava/java-S2885.json new file mode 100644 index 00000000000..95379632116 --- /dev/null +++ b/its/ruling/src/test/resources/expected/guava/java-S2885.json @@ -0,0 +1,6 @@ +{ +"guava:src/com/google/common/base/Throwables.java": [ +314, +321 +] +} diff --git a/its/ruling/src/test/resources/expected/guava/java-S2972.json b/its/ruling/src/test/resources/expected/guava/java-S2972.json index ebb84361644..3c996aca155 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S2972.json +++ b/its/ruling/src/test/resources/expected/guava/java-S2972.json @@ -77,7 +77,6 @@ "guava:src/com/google/common/cache/LocalCache.java": [ 372, 442, -687, 851, 926, 1064, @@ -141,7 +140,6 @@ 604 ], "guava:src/com/google/common/collect/ComparisonChain.java": [ -78, 135 ], "guava:src/com/google/common/collect/ComputingConcurrentHashMap.java": [ @@ -182,7 +180,9 @@ "guava:src/com/google/common/collect/HashBiMap.java": [ 390, 435, -526 +477, +526, +634 ], "guava:src/com/google/common/collect/ImmutableBiMap.java": [ 121 @@ -248,7 +248,6 @@ 67 ], "guava:src/com/google/common/collect/Iterators.java": [ -74, 1154, 1287 ], @@ -425,7 +424,6 @@ "guava:src/com/google/common/collect/TreeRangeMap.java": [ 68, 216, -268, 326 ], "guava:src/com/google/common/collect/TreeRangeSet.java": [ @@ -450,8 +448,6 @@ 85 ], "guava:src/com/google/common/hash/BloomFilterStrategies.java": [ -44, -92, 142 ], "guava:src/com/google/common/hash/Crc32cHashFunction.java": [ @@ -577,18 +573,11 @@ 456, 531 ], -"guava:src/com/google/common/util/concurrent/AbstractExecutionThreadService.java": [ -43 -], "guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ 857 ], -"guava:src/com/google/common/util/concurrent/AbstractIdleService.java": [ -47 -], "guava:src/com/google/common/util/concurrent/AbstractScheduledService.java": [ 112, -164, 414 ], "guava:src/com/google/common/util/concurrent/AbstractService.java": [ diff --git a/its/ruling/src/test/resources/expected/guava/java-S3038.json b/its/ruling/src/test/resources/expected/guava/java-S3038.json index dd559a80e79..078892f4894 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S3038.json +++ b/its/ruling/src/test/resources/expected/guava/java-S3038.json @@ -10,10 +10,19 @@ 32, 79 ], +"guava:src/com/google/common/collect/ImmutableBiMap.java": [ +295 +], +"guava:src/com/google/common/collect/ImmutableSortedMultiset.java": [ +293, +338, +352 +], +"guava:src/com/google/common/collect/ImmutableTable.java": [ +290, +317 +], "guava:src/com/google/common/collect/MapMakerInternalMap.java": [ 3608 -], -"guava:src/com/google/common/hash/Hashing.java": [ -320 ] } diff --git a/its/ruling/src/test/resources/expected/guava/java-S3077.json b/its/ruling/src/test/resources/expected/guava/java-S3077.json index d46251e104f..4746b9f5790 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S3077.json +++ b/its/ruling/src/test/resources/expected/guava/java-S3077.json @@ -11,9 +11,6 @@ "guava:src/com/google/common/cache/Striped64.java": [ 138 ], -"guava:src/com/google/common/collect/ComputingConcurrentHashMap.java": [ -292 -], "guava:src/com/google/common/collect/MapConstraints.java": [ 359 ], diff --git a/its/ruling/src/test/resources/expected/guava/java-S3252.json b/its/ruling/src/test/resources/expected/guava/java-S3252.json new file mode 100644 index 00000000000..80622a74c9e --- /dev/null +++ b/its/ruling/src/test/resources/expected/guava/java-S3252.json @@ -0,0 +1,11 @@ +{ +"guava:src/com/google/common/collect/ImmutableCollection.java": [ +290 +], +"guava:src/com/google/common/reflect/TypeToken.java": [ +205 +], +"guava:src/com/google/common/util/concurrent/CycleDetectingLockFactory.java": [ +522 +] +} diff --git a/its/ruling/src/test/resources/expected/guava/java-S3398.json b/its/ruling/src/test/resources/expected/guava/java-S3398.json index 3f7874561bb..494b43c3a9a 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S3398.json +++ b/its/ruling/src/test/resources/expected/guava/java-S3398.json @@ -1,51 +1,36 @@ { +"guava:src/com/google/common/collect/AbstractBiMap.java": [ +140, +152 +], "guava:src/com/google/common/collect/AbstractMapBasedMultimap.java": [ -607, -1135 +607 ], "guava:src/com/google/common/collect/Collections2.java": [ 679 ], -"guava:src/com/google/common/collect/Constraints.java": [ -281 -], "guava:src/com/google/common/collect/HashBiMap.java": [ -306 +233 +], +"guava:src/com/google/common/collect/ImmutableSetMultimap.java": [ +305 ], "guava:src/com/google/common/collect/MapConstraints.java": [ -190, -216, -250, -268, -770, -780 +291 ], "guava:src/com/google/common/collect/Maps.java": [ -455, 978, -3164 -], -"guava:src/com/google/common/collect/Multimaps.java": [ -826, -847 -], -"guava:src/com/google/common/collect/SortedMultisets.java": [ -156, -163 -], -"guava:src/com/google/common/collect/StandardRowSortedTable.java": [ -63 +997 ], "guava:src/com/google/common/collect/StandardTable.java": [ -162, -185 +179 ], "guava:src/com/google/common/collect/Synchronized.java": [ +816, +816, +816, 1507 ], -"guava:src/com/google/common/collect/TreeRangeMap.java": [ -263 -], "guava:src/com/google/common/collect/TreeRangeSet.java": [ 132 ], diff --git a/its/ruling/src/test/resources/expected/guava/java-S3457.json b/its/ruling/src/test/resources/expected/guava/java-S3457.json index aa75d24c7b8..66d8aaab872 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S3457.json +++ b/its/ruling/src/test/resources/expected/guava/java-S3457.json @@ -1,7 +1,4 @@ { -"guava:src/com/google/common/io/Closer.java": [ -252 -], "guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ 822 ], diff --git a/its/ruling/src/test/resources/expected/guava/java-S3740.json b/its/ruling/src/test/resources/expected/guava/java-S3740.json index d2cc045dd5a..8d32850c72c 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S3740.json +++ b/its/ruling/src/test/resources/expected/guava/java-S3740.json @@ -1,19 +1,4 @@ { -"guava:src/com/google/common/base/Converter.java": [ -464, -464 -], -"guava:src/com/google/common/cache/RemovalNotification.java": [ -54 -], -"guava:src/com/google/common/collect/ConcurrentHashMultiset.java": [ -76 -], -"guava:src/com/google/common/collect/ImmutableMultimap.java": [ -344, -346, -348 -], "guava:src/com/google/common/collect/ImmutableSortedMap.java": [ 64, 66, @@ -41,17 +26,5 @@ ], "guava:src/com/google/common/collect/ReverseNaturalOrdering.java": [ 30 -], -"guava:src/com/google/common/collect/TreeRangeMap.java": [ -267, -268 -], -"guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ -106, -107, -108, -842, -845, -848 ] } diff --git a/its/ruling/src/test/resources/expected/guava/java-S3878.json b/its/ruling/src/test/resources/expected/guava/java-S3878.json index 2ac01f3fb60..63ab1776ee3 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S3878.json +++ b/its/ruling/src/test/resources/expected/guava/java-S3878.json @@ -1,5 +1,13 @@ { "guava:src/com/google/common/collect/ArrayTable.java": [ 360 +], +"guava:src/com/google/common/hash/Hashing.java": [ +485, +506 +], +"guava:src/com/google/common/util/concurrent/ServiceManager.java": [ +642, +751 ] } diff --git a/its/ruling/src/test/resources/expected/guava/java-S4248.json b/its/ruling/src/test/resources/expected/guava/java-S4248.json index 0a214cd2ea2..b0244ea3c1b 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S4248.json +++ b/its/ruling/src/test/resources/expected/guava/java-S4248.json @@ -1,7 +1,4 @@ { -"guava:src/com/google/common/net/InternetDomainName.java": [ -510 -], "guava:src/com/google/common/net/PercentEscaper.java": [ 100 ] diff --git a/its/ruling/src/test/resources/expected/guava/java-S4738.json b/its/ruling/src/test/resources/expected/guava/java-S4738.json index a61f4ed0963..253319d4067 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S4738.json +++ b/its/ruling/src/test/resources/expected/guava/java-S4738.json @@ -192,16 +192,14 @@ 149 ], "guava:src/com/google/common/collect/ImmutableCollection.java": [ -288, -290 +288 ], "guava:src/com/google/common/collect/ImmutableList.java": [ 252, 256, 274, 305, -374, -376 +374 ], "guava:src/com/google/common/collect/ImmutableListMultimap.java": [ 299 diff --git a/its/ruling/src/test/resources/expected/guava/java-S4838.json b/its/ruling/src/test/resources/expected/guava/java-S4838.json new file mode 100644 index 00000000000..43a028374fe --- /dev/null +++ b/its/ruling/src/test/resources/expected/guava/java-S4838.json @@ -0,0 +1,8 @@ +{ +"guava:src/com/google/common/collect/Maps.java": [ +3867 +], +"guava:src/com/google/common/collect/RegularImmutableMultiset.java": [ +67 +] +} diff --git a/its/ruling/src/test/resources/expected/guava/java-S5803.json b/its/ruling/src/test/resources/expected/guava/java-S5803.json index ae193f2ba4f..b144ac3d4fe 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S5803.json +++ b/its/ruling/src/test/resources/expected/guava/java-S5803.json @@ -1,8 +1,5 @@ { -"guava:src/com/google/common/io/Closer.java": [ -252 -], "guava:src/com/google/common/util/concurrent/SmoothRateLimiter.java": [ -213 +327 ] } diff --git a/its/ruling/src/test/resources/expected/guava/java-S6201.json b/its/ruling/src/test/resources/expected/guava/java-S6201.json index ec62bcf76c5..2f5c145319d 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S6201.json +++ b/its/ruling/src/test/resources/expected/guava/java-S6201.json @@ -29,6 +29,9 @@ "guava:src/com/google/common/collect/ImmutableClassToInstanceMap.java": [ 150 ], +"guava:src/com/google/common/collect/ImmutableEnumSet.java": [ +82 +], "guava:src/com/google/common/collect/ImmutableSet.java": [ 243 ], @@ -220,6 +223,7 @@ "guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ 470, 472, +519, 569, 692 ], diff --git a/its/ruling/src/test/resources/expected/guava/java-S6212.json b/its/ruling/src/test/resources/expected/guava/java-S6212.json index 5c84a3e8390..d953d14d623 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S6212.json +++ b/its/ruling/src/test/resources/expected/guava/java-S6212.json @@ -99,10 +99,6 @@ 195, 218 ], -"guava:src/com/google/common/base/Suppliers.java": [ -131, -199 -], "guava:src/com/google/common/base/Throwables.java": [ 215, 335 @@ -120,15 +116,13 @@ 202 ], "guava:src/com/google/common/cache/AbstractCache.java": [ -71, -72 +71 ], "guava:src/com/google/common/cache/CacheBuilder.java": [ 831 ], "guava:src/com/google/common/cache/CacheBuilderSpec.java": [ 137, -147, 418, 439 ], @@ -140,13 +134,10 @@ 305, 1890, 2145, -2183, 2216, 2230, 2237, 2302, -2331, -2367, 2419, 2486, 2500, @@ -174,7 +165,6 @@ 3342, 3376, 3536, -3538, 3699, 3836, 3888, @@ -189,12 +179,9 @@ 3979, 3980, 3985, -4002, 4032, 4034, 4065, -4067, -4068, 4130, 4131, 4138, @@ -224,24 +211,13 @@ 260, 284 ], -"guava:src/com/google/common/collect/AbstractBiMap.java": [ -135, -153, -342, -353 -], "guava:src/com/google/common/collect/AbstractIterator.java": [ 157 ], "guava:src/com/google/common/collect/AbstractMapBasedMultimap.java": [ -827, 973, 1138, -1288, -1341 -], -"guava:src/com/google/common/collect/AbstractMapBasedMultiset.java": [ -107 +1288 ], "guava:src/com/google/common/collect/AbstractMapEntry.java": [ 58, @@ -257,11 +233,6 @@ 626, 716 ], -"guava:src/com/google/common/collect/BinaryTreeTraverser.java": [ -114, -152, -192 -], "guava:src/com/google/common/collect/CartesianList.java": [ 54 ], @@ -269,25 +240,17 @@ 170, 437, 438, -532, 632 ], "guava:src/com/google/common/collect/CompoundOrdering.java": [ 41 ], "guava:src/com/google/common/collect/ComputingConcurrentHashMap.java": [ -83, 93, -105, -113, -159, -190, -327, -437 +327 ], "guava:src/com/google/common/collect/ConcurrentHashMultiset.java": [ 169, -198, 239, 252, 382, @@ -297,55 +260,32 @@ 56, 57, 58, -60, -61, 64, 127, -258, -259, 260 ], "guava:src/com/google/common/collect/FilteredEntryMultimap.java": [ 151, -154, 187, 199, -203, -263, -309, 361, -362, -365 +362 ], "guava:src/com/google/common/collect/FilteredKeyMultimap.java": [ 61, 72 ], -"guava:src/com/google/common/collect/FluentIterable.java": [ -329 -], "guava:src/com/google/common/collect/ForwardingCollection.java": [ 241 ], -"guava:src/com/google/common/collect/ForwardingMap.java": [ -168 -], "guava:src/com/google/common/collect/ForwardingMapEntry.java": [ 114, 115 ], "guava:src/com/google/common/collect/GeneralRange.java": [ -160, -169, -186, 187, -201, 202 ], -"guava:src/com/google/common/collect/HashBiMap.java": [ -496, -653 -], "guava:src/com/google/common/collect/Hashing.java": [ 60 ], @@ -372,9 +312,6 @@ 632, 646 ], -"guava:src/com/google/common/collect/ImmutableMapEntrySet.java": [ -74 -], "guava:src/com/google/common/collect/ImmutableMultiset.java": [ 385, 395 @@ -407,10 +344,7 @@ 325, 326, 329, -339, -342, -343, -344 +342 ], "guava:src/com/google/common/collect/ImmutableSortedMultiset.java": [ 263, @@ -424,39 +358,23 @@ 378, 380 ], -"guava:src/com/google/common/collect/Interners.java": [ -49, -81 -], "guava:src/com/google/common/collect/Iterables.java": [ 182, -183, -186, -236, -876 +183 ], "guava:src/com/google/common/collect/Iterators.java": [ 199, 240, -307, 312, 314, 363, 641, 642, -669, -712, 789, -790, -891, -991, -1010, -1174, -1320 +1174 ], "guava:src/com/google/common/collect/LinkedHashMultimap.java": [ 329, -382, 443, 561, 563, @@ -486,8 +404,7 @@ ], "guava:src/com/google/common/collect/MapMaker.java": [ 593, -841, -842 +841 ], "guava:src/com/google/common/collect/MapMakerInternalMap.java": [ 226, @@ -502,7 +419,6 @@ 2409, 2442, 2480, -2482, 2516, 2522, 2592, @@ -540,16 +456,12 @@ 4006 ], "guava:src/com/google/common/collect/Maps.java": [ -442, 527, 799, 810, 940, -1135, -1207, 2635, -2656, -2911, +3833, 4078 ], "guava:src/com/google/common/collect/MinMaxPriorityQueue.java": [ @@ -569,17 +481,13 @@ 935 ], "guava:src/com/google/common/collect/Multimaps.java": [ -1510, 1623 ], "guava:src/com/google/common/collect/Multisets.java": [ 328, -488, -626, 696, 743, 779, -818, 830 ], "guava:src/com/google/common/collect/ObjectArrays.java": [ @@ -588,19 +496,13 @@ 230 ], "guava:src/com/google/common/collect/Ordering.java": [ -504, -580, -723, 725, -730, -736, 745, 748, 783 ], "guava:src/com/google/common/collect/Queues.java": [ 269, -275, 304, 305 ], @@ -609,14 +511,10 @@ ], "guava:src/com/google/common/collect/RegularImmutableBiMap.java": [ 68, -70, -73, -74 +70 ], "guava:src/com/google/common/collect/RegularImmutableMap.java": [ -65, -67, -68 +65 ], "guava:src/com/google/common/collect/RegularImmutableMultiset.java": [ 51, @@ -642,7 +540,6 @@ 196 ], "guava:src/com/google/common/collect/Sets.java": [ -902, 1219, 1224, 1291, @@ -662,18 +559,11 @@ "guava:src/com/google/common/collect/SparseImmutableTable.java": [ 48, 49, -50, -52, -53, -54, -59 +50 ], "guava:src/com/google/common/collect/StandardTable.java": [ 117, -155, -168, 415, -421, 445, 592, 608, @@ -690,24 +580,15 @@ 272, 276, 305, -328, -363, -385 +328 ], "guava:src/com/google/common/collect/TreeRangeMap.java": [ 517 ], -"guava:src/com/google/common/collect/TreeTraverser.java": [ -157, -210 -], "guava:src/com/google/common/escape/ArrayBasedCharEscaper.java": [ 119, 120 ], -"guava:src/com/google/common/escape/ArrayBasedEscaperMap.java": [ -82 -], "guava:src/com/google/common/escape/ArrayBasedUnicodeEscaper.java": [ 154, 155, @@ -721,8 +602,7 @@ ], "guava:src/com/google/common/escape/CharEscaperBuilder.java": [ 58, -59, -115 +59 ], "guava:src/com/google/common/escape/Escapers.java": [ 239, @@ -906,8 +786,7 @@ 141 ], "guava:src/com/google/common/io/Closer.java": [ -208, -212 +208 ], "guava:src/com/google/common/io/FileBackedOutputStream.java": [ 158, @@ -1236,8 +1115,7 @@ 171, 222, 227, -258, -285 +258 ], "guava:src/com/google/common/primitives/UnsignedLong.java": [ 229 @@ -1284,7 +1162,6 @@ 190, 195, 197, -261, 320, 335, 398, @@ -1307,14 +1184,10 @@ 139, 215, 294, -380, 436, 490, 569 ], -"guava:src/com/google/common/util/concurrent/AbstractExecutionThreadService.java": [ -45 -], "guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ 168, 190, @@ -1440,10 +1313,6 @@ ], "guava:src/com/google/common/util/concurrent/ServiceManager.java": [ 271, -475, -588, -589, -632, 690, 698 ], @@ -1454,10 +1323,7 @@ 150, 151, 158, -321, -363, -426, -430 +321 ], "guava:src/com/google/common/util/concurrent/ThreadFactoryBuilder.java": [ 154, diff --git a/its/ruling/src/test/resources/expected/guava/java-S6548.json b/its/ruling/src/test/resources/expected/guava/java-S6548.json index 196122772ff..15bd8d5a8fc 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S6548.json +++ b/its/ruling/src/test/resources/expected/guava/java-S6548.json @@ -9,6 +9,9 @@ 1435, 1451 ], +"guava:src/com/google/common/base/Converter.java": [ +463 +], "guava:src/com/google/common/base/Equivalence.java": [ 330, 351 diff --git a/its/ruling/src/test/resources/expected/guava/java-S9391.json b/its/ruling/src/test/resources/expected/guava/java-S9391.json index 985b323d4de..d793a052532 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S9391.json +++ b/its/ruling/src/test/resources/expected/guava/java-S9391.json @@ -1,10 +1,4 @@ { -"guava:src/com/google/common/base/Predicates.java": [ -707 -], -"guava:src/com/google/common/collect/MinMaxPriorityQueue.java": [ -222 -], "guava:src/com/google/common/hash/Hashing.java": [ 502 ] diff --git a/its/ruling/src/test/resources/expected/guava/java-S9396.json b/its/ruling/src/test/resources/expected/guava/java-S9396.json index 2c63c085104..c4d039268de 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S9396.json +++ b/its/ruling/src/test/resources/expected/guava/java-S9396.json @@ -1,2 +1,206 @@ { +"guava:src/com/google/common/cache/LocalCache.java": [ +4262, +4263, +4264, +4265, +4266, +4267, +4268, +4394, +4395, +4450, +4495 +], +"guava:src/com/google/common/collect/AbstractBiMap.java": [ +243, +281 +], +"guava:src/com/google/common/collect/AbstractMapBasedMultimap.java": [ +356, +357, +358, +359, +460, +461, +1148, +1149, +1150, +1151, +1263, +1377, +1378, +1441 +], +"guava:src/com/google/common/collect/AbstractMapBasedMultiset.java": [ +161, +162, +163, +164 +], +"guava:src/com/google/common/collect/ArrayTable.java": [ +588, +678 +], +"guava:src/com/google/common/collect/BinaryTreeTraverser.java": [ +100, +135, +136, +180, +181 +], +"guava:src/com/google/common/collect/DenseImmutableTable.java": [ +139, +163 +], +"guava:src/com/google/common/collect/FilteredEntryMultimap.java": [ +76 +], +"guava:src/com/google/common/collect/HashBiMap.java": [ +391, +392, +393, +478, +635 +], +"guava:src/com/google/common/collect/ImmutableList.java": [ +392, +393 +], +"guava:src/com/google/common/collect/ImmutableMultimap.java": [ +563, +564, +565 +], +"guava:src/com/google/common/collect/ImmutableRangeSet.java": [ +199, +202, +204, +391, +398 +], +"guava:src/com/google/common/collect/LinkedHashMultimap.java": [ +311, +312, +313, +314, +318, +319 +], +"guava:src/com/google/common/collect/LinkedListMultimap.java": [ +318, +319, +320, +321, +322, +412, +413, +414, +415, +450, +451, +452, +453, +454 +], +"guava:src/com/google/common/collect/MapMakerInternalMap.java": [ +3594, +3595, +3596, +3597, +3598, +3599, +3600, +3725, +3726 +], +"guava:src/com/google/common/collect/MinMaxPriorityQueue.java": [ +504, +505, +764, +765, +766, +767, +768, +769 +], +"guava:src/com/google/common/collect/StandardTable.java": [ +229, +231, +232, +262, +268, +388, +484, +656, +657, +658 +], +"guava:src/com/google/common/collect/TreeBasedTable.java": [ +181, +182, +248 +], +"guava:src/com/google/common/collect/TreeRangeMap.java": [ +218, +328 +], +"guava:src/com/google/common/collect/TreeRangeSet.java": [ +89, +805 +], +"guava:src/com/google/common/collect/TreeTraverser.java": [ +85, +145, +191 +], +"guava:src/com/google/common/hash/AbstractNonStreamingHashFunction.java": [ +83 +], +"guava:src/com/google/common/hash/ChecksumHashFunction.java": [ +61 +], +"guava:src/com/google/common/io/ByteSink.java": [ +140 +], +"guava:src/com/google/common/io/ByteSource.java": [ +450, +472, +473 +], +"guava:src/com/google/common/reflect/TypeToken.java": [ +630, +673, +674, +722 +], +"guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ +259 +], +"guava:src/com/google/common/util/concurrent/AbstractScheduledService.java": [ +422, +425, +431, +438, +442 +], +"guava:src/com/google/common/util/concurrent/AggregateFuture.java": [ +90, +91, +92 +], +"guava:src/com/google/common/util/concurrent/CollectionFuture.java": [ +38 +], +"guava:src/com/google/common/util/concurrent/CycleDetectingLockFactory.java": [ +791, +871, +872, +874, +912, +974 +], +"guava:src/com/google/common/util/concurrent/TrustedListenableFutureTask.java": [ +96 +] } diff --git a/its/ruling/src/test/resources/expected/guava/java-S9434.json b/its/ruling/src/test/resources/expected/guava/java-S9434.json index 72964a4892c..e1a48b315e9 100644 --- a/its/ruling/src/test/resources/expected/guava/java-S9434.json +++ b/its/ruling/src/test/resources/expected/guava/java-S9434.json @@ -1,5 +1,12 @@ { "guava:src/com/google/common/collect/TreeRangeMap.java": [ 547 +], +"guava:src/com/google/common/util/concurrent/AbstractFuture.java": [ +728 +], +"guava:src/com/google/common/util/concurrent/ListenerCallQueue.java": [ +106, +131 ] } diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S112.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S112.json index 6a09551c09e..f112534bb67 100644 --- a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S112.json +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S112.json @@ -20,6 +20,16 @@ 35, 37 ], +"jboss-ejb3-tutorial:ejb21_client_adaptors/src/org/jboss/tutorial/ejb21_client_adaptors/bean/DeploymentDescriptorSession1Bean.java": [ +44 +], +"jboss-ejb3-tutorial:ejb21_client_adaptors/src/org/jboss/tutorial/ejb21_client_adaptors/bean/Session1Bean.java": [ +54 +], +"jboss-ejb3-tutorial:ejb21_client_adaptors/src/org/jboss/tutorial/ejb21_client_adaptors/client/Client.java": [ +38, +50 +], "jboss-ejb3-tutorial:extended_pc/src/org/jboss/tutorial/extended/client/Client.java": [ 41 ], @@ -44,6 +54,9 @@ "jboss-ejb3-tutorial:reference21_30/ejb3_app/src/main/java/org/jboss/tutorial/reference21_30/bean/Stateless3.java": [ 30 ], +"jboss-ejb3-tutorial:reference21_30/ejb3_app/src/main/java/org/jboss/tutorial/reference21_30/bean/Stateless3Bean.java": [ +52 +], "jboss-ejb3-tutorial:relationships/src/org/jboss/tutorial/relationships/bean/EntityTest.java": [ 34, 36 diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1128.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1128.json index 94d124bab65..af111261852 100644 --- a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1128.json +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1128.json @@ -5,5 +5,14 @@ "jboss-ejb3-tutorial:service_deployment_descriptor/src/org/jboss/tutorial/service_deployment_descriptor/bean/ServiceThreeManagement.java": [ 24, 25 +], +"jboss-ejb3-tutorial:service_deployment_descriptor/src/org/jboss/tutorial/service_deployment_descriptor/client/Client.java": [ +25 +], +"jboss-ejb3-tutorial:stateful_deployment_descriptor/src/org/jboss/tutorial/stateful_deployment_descriptor/client/Client.java": [ +31 +], +"jboss-ejb3-tutorial:stateless/src/org/jboss/tutorial/stateless/client/Client.java": [ +25 ] } diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1161.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1161.json new file mode 100644 index 00000000000..741bf4a7c01 --- /dev/null +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1161.json @@ -0,0 +1,5 @@ +{ +"jboss-ejb3-tutorial:interceptor/src/org/jboss/tutorial/interceptor/bean/AccountsConfirmInterceptor.java": [ +58 +] +} diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1948.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1948.json index 8ee6dcd1326..16cf35fcf96 100644 --- a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1948.json +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S1948.json @@ -2,5 +2,17 @@ "jboss-ejb3-tutorial:blob/src/org/jboss/tutorial/blob/bean/BlobEntity.java": [ 45, 46 +], +"jboss-ejb3-tutorial:composite/src/org/jboss/tutorial/composite/bean/Customer.java": [ +41 +], +"jboss-ejb3-tutorial:composite/src/org/jboss/tutorial/composite/bean/Flight.java": [ +45 +], +"jboss-ejb3-tutorial:relationships/src/org/jboss/tutorial/relationships/bean/Customer.java": [ +51 +], +"jboss-ejb3-tutorial:relationships/src/org/jboss/tutorial/relationships/bean/Flight.java": [ +51 ] } diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2057.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2057.json index 1df27f35376..dfd2db06e17 100644 --- a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2057.json +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2057.json @@ -53,6 +53,12 @@ "jboss-ejb3-tutorial:jndibinding/src/org/jboss/tutorial/jndibinding/bean/Customer.java": [ 37 ], +"jboss-ejb3-tutorial:joininheritance/src/org/jboss/tutorial/joininheritance/bean/Cat.java": [ +30 +], +"jboss-ejb3-tutorial:joininheritance/src/org/jboss/tutorial/joininheritance/bean/Dog.java": [ +33 +], "jboss-ejb3-tutorial:joininheritance/src/org/jboss/tutorial/joininheritance/bean/Pet.java": [ 34 ], @@ -71,6 +77,12 @@ "jboss-ejb3-tutorial:secondary/src/org/jboss/tutorial/secondary/bean/Customer.java": [ 36 ], +"jboss-ejb3-tutorial:singleinheritance/src/org/jboss/tutorial/singleinheritance/bean/Cat.java": [ +35 +], +"jboss-ejb3-tutorial:singleinheritance/src/org/jboss/tutorial/singleinheritance/bean/Dog.java": [ +35 +], "jboss-ejb3-tutorial:singleinheritance/src/org/jboss/tutorial/singleinheritance/bean/Pet.java": [ 36 ], @@ -80,6 +92,12 @@ "jboss-ejb3-tutorial:stateful_deployment_descriptor/src/org/jboss/tutorial/stateful_deployment_descriptor/bean/ShoppingCartBean.java": [ 27 ], +"jboss-ejb3-tutorial:tableperinheritance/src/org/jboss/tutorial/tableperinheritance/bean/Cat.java": [ +27 +], +"jboss-ejb3-tutorial:tableperinheritance/src/org/jboss/tutorial/tableperinheritance/bean/Dog.java": [ +27 +], "jboss-ejb3-tutorial:tableperinheritance/src/org/jboss/tutorial/tableperinheritance/bean/Pet.java": [ 33 ] diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2175.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2175.json new file mode 100644 index 00000000000..f04d9ec9f9a --- /dev/null +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2175.json @@ -0,0 +1,33 @@ +{ +"jboss-ejb3-tutorial:blob/src/org/jboss/tutorial/blob/client/Client.java": [ +43 +], +"jboss-ejb3-tutorial:injection/src/org/jboss/tutorial/injection/bean/ShoppingCartBean.java": [ +52, +54 +], +"jboss-ejb3-tutorial:injection/src/org/jboss/tutorial/injection/client/Client.java": [ +56 +], +"jboss-ejb3-tutorial:jboss_deployment_descriptor/src/org/jboss/tutorial/jboss_deployment_descriptor/bean/ShoppingCartBean.java": [ +33, +35 +], +"jboss-ejb3-tutorial:jboss_deployment_descriptor/src/org/jboss/tutorial/jboss_deployment_descriptor/client/Client.java": [ +91 +], +"jboss-ejb3-tutorial:stateful/src/org/jboss/tutorial/stateful/bean/ShoppingCartBean.java": [ +39, +41 +], +"jboss-ejb3-tutorial:stateful/src/org/jboss/tutorial/stateful/client/Client.java": [ +54 +], +"jboss-ejb3-tutorial:stateful_deployment_descriptor/src/org/jboss/tutorial/stateful_deployment_descriptor/bean/ShoppingCartBean.java": [ +33, +35 +], +"jboss-ejb3-tutorial:stateful_deployment_descriptor/src/org/jboss/tutorial/stateful_deployment_descriptor/client/Client.java": [ +59 +] +} diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2184.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2184.json new file mode 100644 index 00000000000..019cda039e3 --- /dev/null +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2184.json @@ -0,0 +1,5 @@ +{ +"jboss-ejb3-tutorial:entity/src/org/jboss/tutorial/entity/bean/Order.java": [ +73 +] +} diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2221.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2221.json new file mode 100644 index 00000000000..ab46d4c7887 --- /dev/null +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2221.json @@ -0,0 +1,11 @@ +{ +"jboss-ejb3-tutorial:dependency/src/org/jboss/tutorial/dependency/client/Client.java": [ +46 +], +"jboss-ejb3-tutorial:reference21_30/ejb21_app/src/main/java/org/jboss/tutorial/reference21_30/bean/Stateless2Bean.java": [ +46 +], +"jboss-ejb3-tutorial:reference21_30/ejb3_app/src/main/java/org/jboss/tutorial/reference21_30/bean/Stateless3Bean.java": [ +67 +] +} diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2384.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2384.json index 7c65bf26334..c31b733e4e5 100644 --- a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2384.json +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S2384.json @@ -3,6 +3,14 @@ 59, 64 ], +"jboss-ejb3-tutorial:cachedentity/src/org/jboss/tutorial/cachedentity/bean/Customer.java": [ +82, +87 +], +"jboss-ejb3-tutorial:entity/src/org/jboss/tutorial/entity/bean/Order.java": [ +81, +86 +], "jboss-ejb3-tutorial:injection/src/org/jboss/tutorial/injection/bean/ShoppingCartBean.java": [ 66 ], diff --git a/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S4275.json b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S4275.json new file mode 100644 index 00000000000..f13f27a9dda --- /dev/null +++ b/its/ruling/src/test/resources/expected/jboss-ejb3-tutorial/java-S4275.json @@ -0,0 +1,5 @@ +{ +"jboss-ejb3-tutorial:injection/src/org/jboss/tutorial/injection/bean/ShoppingCartBean.java": [ +44 +] +} diff --git a/its/ruling/src/test/resources/expected/mall/java-S1128.json b/its/ruling/src/test/resources/expected/mall/java-S1128.json index 7dfdd6cb290..3f8f4380195 100644 --- a/its/ruling/src/test/resources/expected/mall/java-S1128.json +++ b/its/ruling/src/test/resources/expected/mall/java-S1128.json @@ -2,6 +2,10 @@ "mall:mall-admin/src/main/java/com/macro/mall/config/SpringDocConfig.java": [ 10 ], +"mall:mall-demo/src/main/java/com/macro/mall/demo/config/SecurityConfig.java": [ +10, +13 +], "mall:mall-mbg/src/main/java/com/macro/mall/model/CmsHelp.java": [ 3 ], @@ -59,7 +63,20 @@ "mall:mall-portal/src/main/java/com/macro/mall/portal/component/OrderTimeOutCancelTask.java": [ 8 ], +"mall:mall-search/src/main/java/com/macro/mall/search/service/impl/EsProductServiceImpl.java": [ +4, +10, +27 +], "mall:mall-search/src/test/java/com/macro/mall/search/MallSearchApplicationTests.java": [ 10 +], +"mall:mall-security/src/main/java/com/macro/mall/security/component/RestAuthenticationEntryPoint.java": [ +7, +9 +], +"mall:mall-security/src/main/java/com/macro/mall/security/component/RestfulAccessDeniedHandler.java": [ +7, +9 ] } diff --git a/its/ruling/src/test/resources/expected/mall/java-S1155.json b/its/ruling/src/test/resources/expected/mall/java-S1155.json index 4a082891ae5..803c40c8198 100644 --- a/its/ruling/src/test/resources/expected/mall/java-S1155.json +++ b/its/ruling/src/test/resources/expected/mall/java-S1155.json @@ -1,7 +1,14 @@ { +"mall:mall-admin/src/main/java/com/macro/mall/service/impl/UmsAdminServiceImpl.java": [ +69, +88 +], "mall:mall-common/src/main/java/com/macro/mall/common/log/WebLogAspect.java": [ 119 ], +"mall:mall-demo/src/main/java/com/macro/mall/demo/config/SecurityConfig.java": [ +53 +], "mall:mall-mbg/src/main/java/com/macro/mall/model/CmsHelpCategoryExample.java": [ 49, 75 diff --git a/its/ruling/src/test/resources/expected/mall/java-S1226.json b/its/ruling/src/test/resources/expected/mall/java-S1226.json new file mode 100644 index 00000000000..09559d36e7c --- /dev/null +++ b/its/ruling/src/test/resources/expected/mall/java-S1226.json @@ -0,0 +1,5 @@ +{ +"mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/OmsPortalOrderServiceImpl.java": [ +355 +] +} diff --git a/its/ruling/src/test/resources/expected/mall/java-S2184.json b/its/ruling/src/test/resources/expected/mall/java-S2184.json index ef80ccf08ed..4c5f905bf75 100644 --- a/its/ruling/src/test/resources/expected/mall/java-S2184.json +++ b/its/ruling/src/test/resources/expected/mall/java-S2184.json @@ -1,5 +1,8 @@ { "mall:mall-admin/src/main/java/com/macro/mall/service/impl/OssServiceImpl.java": [ 59 +], +"mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/OmsPortalOrderServiceImpl.java": [ +331 ] } diff --git a/its/ruling/src/test/resources/expected/mall/java-S2325.json b/its/ruling/src/test/resources/expected/mall/java-S2325.json index ef96d7635c4..fa3dec85465 100644 --- a/its/ruling/src/test/resources/expected/mall/java-S2325.json +++ b/its/ruling/src/test/resources/expected/mall/java-S2325.json @@ -9,8 +9,7 @@ 96 ], "mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/OmsPortalOrderServiceImpl.java": [ -738, -753 +738 ], "mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/OmsPromotionServiceImpl.java": [ 265 diff --git a/its/ruling/src/test/resources/expected/mall/java-S2384.json b/its/ruling/src/test/resources/expected/mall/java-S2384.json index 2c46fbbcfd6..9ccf9ab6b3c 100644 --- a/its/ruling/src/test/resources/expected/mall/java-S2384.json +++ b/its/ruling/src/test/resources/expected/mall/java-S2384.json @@ -1,4 +1,7 @@ { +"mall:mall-admin/src/main/java/com/macro/mall/bo/AdminUserDetails.java": [ +25 +], "mall:mall-common/src/main/java/com/macro/mall/common/api/CommonPage.java": [ 86, 90 diff --git a/its/ruling/src/test/resources/expected/mall/java-S3740.json b/its/ruling/src/test/resources/expected/mall/java-S3740.json index 38d1d417981..280f0607ba8 100644 --- a/its/ruling/src/test/resources/expected/mall/java-S3740.json +++ b/its/ruling/src/test/resources/expected/mall/java-S3740.json @@ -1,11 +1,5 @@ { "mall:mall-admin/src/main/java/com/macro/mall/service/impl/PmsProductServiceImpl.java": [ 310 -], -"mall:mall-common/src/main/java/com/macro/mall/common/exception/GlobalExceptionHandler.java": [ -24, -33, -47, -61 ] } diff --git a/its/ruling/src/test/resources/expected/mall/java-S6204.json b/its/ruling/src/test/resources/expected/mall/java-S6204.json index 03df599b9a0..bba40c0eaeb 100644 --- a/its/ruling/src/test/resources/expected/mall/java-S6204.json +++ b/its/ruling/src/test/resources/expected/mall/java-S6204.json @@ -1,13 +1,44 @@ { +"mall:mall-admin/src/main/java/com/macro/mall/bo/AdminUserDetails.java": [ +33 +], +"mall:mall-admin/src/main/java/com/macro/mall/controller/UmsAdminController.java": [ +101 +], +"mall:mall-admin/src/main/java/com/macro/mall/service/impl/OmsOrderServiceImpl.java": [ +55, +75 +], +"mall:mall-admin/src/main/java/com/macro/mall/service/impl/PmsProductServiceImpl.java": [ +181, +192 +], "mall:mall-admin/src/main/java/com/macro/mall/service/impl/UmsAdminCacheServiceImpl.java": [ +65, +77, 87 ], +"mall:mall-admin/src/main/java/com/macro/mall/service/impl/UmsMenuServiceImpl.java": [ +82, +102 +], +"mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/OmsPortalOrderServiceImpl.java": [ +379 +], +"mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/PmsPortalProductServiceImpl.java": [ +80, +100, +140 +], +"mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/UmsMemberCouponServiceImpl.java": [ +180, +189 +], "mall:mall-search/src/main/java/com/macro/mall/search/service/impl/EsProductServiceImpl.java": [ 156, 208 ], "mall:mall-security/src/main/java/com/macro/mall/security/component/DynamicAuthorizationManager.java": [ -58, -65 +58 ] } diff --git a/its/ruling/src/test/resources/expected/mall/java-S6212.json b/its/ruling/src/test/resources/expected/mall/java-S6212.json index 8c0fa98e49f..780354d26fe 100644 --- a/its/ruling/src/test/resources/expected/mall/java-S6212.json +++ b/its/ruling/src/test/resources/expected/mall/java-S6212.json @@ -6,9 +6,7 @@ "mall:mall-admin/src/main/java/com/macro/mall/controller/MinioController.java": [ 47, 57, -58, 65, -69, 76, 107 ], @@ -696,6 +694,9 @@ 21, 30 ], +"mall:mall-portal/src/main/java/com/macro/mall/portal/config/JacksonConfig.java": [ +23 +], "mall:mall-portal/src/main/java/com/macro/mall/portal/controller/MemberAttentionController.java": [ 61 ], @@ -726,7 +727,8 @@ 165 ], "mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/MemberAttentionServiceImpl.java": [ -36 +36, +45 ], "mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/MemberCollectionServiceImpl.java": [ 34 @@ -762,7 +764,8 @@ 161, 195, 229, -240 +240, +243 ], "mall:mall-portal/src/main/java/com/macro/mall/portal/service/impl/PmsPortalProductServiceImpl.java": [ 86, @@ -798,13 +801,11 @@ ], "mall:mall-search/src/main/java/com/macro/mall/search/service/impl/EsProductServiceImpl.java": [ 49, -67, 78, 95, 100, 128, 150, -165, 170, 197, 202, @@ -822,9 +823,6 @@ 34, 35 ], -"mall:mall-security/src/main/java/com/macro/mall/security/component/DynamicAuthorizationManager.java": [ -59 -], "mall:mall-security/src/main/java/com/macro/mall/security/component/JwtAuthenticationTokenFilter.java": [ 42, 46, diff --git a/its/ruling/src/test/resources/expected/regex-examples/java-S5854.json b/its/ruling/src/test/resources/expected/regex-examples/java-S5854.json index d8adee9865d..56ff1300368 100644 --- a/its/ruling/src/test/resources/expected/regex-examples/java-S5854.json +++ b/its/ruling/src/test/resources/expected/regex-examples/java-S5854.json @@ -32,6 +32,7 @@ 1863 ], "regex-examples:src/main/java/org/regex/examples/RegexDatabase7.java": [ +965, 1381, 1397, 1419, diff --git a/its/ruling/src/test/resources/expected/regex-examples/java-S5857.json b/its/ruling/src/test/resources/expected/regex-examples/java-S5857.json index fe0e040ce7c..80eb375f7fd 100644 --- a/its/ruling/src/test/resources/expected/regex-examples/java-S5857.json +++ b/its/ruling/src/test/resources/expected/regex-examples/java-S5857.json @@ -74,6 +74,7 @@ 1405 ], "regex-examples:src/main/java/org/regex/examples/RegexDatabase7.java": [ +353, 571, 571, 701, diff --git a/its/ruling/src/test/resources/expected/regex-examples/java-S5867.json b/its/ruling/src/test/resources/expected/regex-examples/java-S5867.json index c21be2bcc30..a91a60a5e54 100644 --- a/its/ruling/src/test/resources/expected/regex-examples/java-S5867.json +++ b/its/ruling/src/test/resources/expected/regex-examples/java-S5867.json @@ -152,6 +152,7 @@ 819, 821, 829, +831, 835, 837, 857, @@ -247,6 +248,11 @@ 1335, 1339, 1341, +1345, +1347, +1349, +1351, +1353, 1355, 1357, 1363, @@ -2401,6 +2407,7 @@ 249, 251, 253, +257, 267, 281, 283, @@ -2529,7 +2536,18 @@ 1057, 1059, 1063, +1065, +1067, +1071, +1075, +1077, +1079, +1081, +1083, +1089, +1093, 1101, +1103, 1133, 1151, 1153, @@ -2819,6 +2837,7 @@ 733, 739, 775, +779, 795, 801, 825, @@ -2855,6 +2874,12 @@ 1003, 1007, 1009, +1011, +1013, +1015, +1017, +1019, +1021, 1031, 1045, 1047, diff --git a/its/ruling/src/test/resources/expected/regex-examples/java-S5869.json b/its/ruling/src/test/resources/expected/regex-examples/java-S5869.json index a801edbe4fb..774470a3480 100644 --- a/its/ruling/src/test/resources/expected/regex-examples/java-S5869.json +++ b/its/ruling/src/test/resources/expected/regex-examples/java-S5869.json @@ -17,10 +17,6 @@ 979, 1007, 1019, -1065, -1067, -1069, -1071, 1081, 1083, 1085, @@ -32,8 +28,6 @@ 1151, 1153, 1473, -1701, -1701, 1779, 1779, 1911 @@ -48,9 +42,6 @@ 465, 471, 471, -491, -491, -491, 599, 607, 621, @@ -67,7 +58,6 @@ 1235, 1385, 1387, -1417, 1421, 1445, 1451, @@ -82,7 +72,6 @@ 1823, 1823, 1827, -1827, 1835, 1845, 1913, @@ -100,15 +89,12 @@ 31, 39, 41, -83, -83, 449, 509, 735, 889, 901, 1115, -1149, 1195, 1283, 1285, @@ -121,7 +107,6 @@ 151, 355, 371, -487, 525, 557, 559, @@ -138,9 +123,7 @@ 769, 769, 769, -1091, 1163, -1261, 1303, 1315, 1315, @@ -168,7 +151,6 @@ 1495, 1509, 1517, -1519, 1823, 1833, 1863, @@ -178,37 +160,12 @@ "regex-examples:src/main/java/org/regex/examples/RegexDatabase5.java": [ 45, 47, -51, -51, -51, -51, -51, 71, 139, 215, 217, 285, 415, -689, -699, -699, -699, -699, -699, -699, -699, -699, -699, -699, -699, -699, -699, -699, -699, -699, -699, -699, -699, 755, 883, 883, @@ -219,9 +176,6 @@ 967, 1099, 1099, -1143, -1143, -1143, 1193, 1219, 1225, @@ -234,7 +188,6 @@ 1557, 1557, 1557, -1595, 1645, 1645, 1759, @@ -248,13 +201,9 @@ 33, 33, 33, -67, -67, -67, 113, 209, 209, -233, 395, 473, 473, @@ -263,28 +212,13 @@ 575, 595, 613, -665, -665, -665, -665, -665, 781, 799, 799, 803, -823, -823, -823, -823, -823, 869, 917, 1003, -1015, -1015, -1017, -1017, -1041, 1055, 1247, 1247, @@ -292,24 +226,14 @@ 1369, 1407, 1585, -1585, 1589, 1601, 1619, -1741, -1741, -1741, -1741, 1775, 1819, -1895, -1895, -1895, -1895, 1921 ], "regex-examples:src/main/java/org/regex/examples/RegexDatabase7.java": [ -45, 61, 61, 61, @@ -324,16 +248,8 @@ 233, 235, 271, -301, 329, 593, -725, -725, -725, -725, -737, -759, -759, 883, 967, 1151, @@ -341,7 +257,6 @@ 1153, 1213, 1221, -1235, 1251, 1331, 1367, @@ -373,17 +288,7 @@ 545, 547, 547, -825, -825, -825, -825, -825, -825, -825, 831, -869, -869, -869, 911, 913, 931, @@ -391,7 +296,6 @@ 971, 1283, 1283, -1309, 1557 ] } diff --git a/its/ruling/src/test/resources/expected/regex-examples/java-S5996.json b/its/ruling/src/test/resources/expected/regex-examples/java-S5996.json index 72f7ee3c2a6..bd35ce4889c 100644 --- a/its/ruling/src/test/resources/expected/regex-examples/java-S5996.json +++ b/its/ruling/src/test/resources/expected/regex-examples/java-S5996.json @@ -4,11 +4,40 @@ 347, 347, 347, +1069, +1069, +1069, +1069, +1069, +1071, +1071, 1613, 1613, 1695, 1695 ], +"regex-examples:src/main/java/org/regex/examples/RegexDatabase3.java": [ +385, +385, +385, +1451, +1453 +], +"regex-examples:src/main/java/org/regex/examples/RegexDatabase4.java": [ +155, +155, +155, +155, +155, +155, +155, +155, +381, +509, +795, +795, +927 +], "regex-examples:src/main/java/org/regex/examples/RegexDatabase5.java": [ 533, 533, @@ -21,6 +50,8 @@ 753, 753, 773, +857, +857, 1305, 1987 ], diff --git a/its/ruling/src/test/resources/expected/regex-examples/java-S6019.json b/its/ruling/src/test/resources/expected/regex-examples/java-S6019.json index 9fdab1b4df9..f61627fdbf1 100644 --- a/its/ruling/src/test/resources/expected/regex-examples/java-S6019.json +++ b/its/ruling/src/test/resources/expected/regex-examples/java-S6019.json @@ -27,7 +27,6 @@ 1603 ], "regex-examples:src/main/java/org/regex/examples/RegexDatabase7.java": [ -353, 1731, 1735 ], diff --git a/its/ruling/src/test/resources/expected/regex-examples/java-S6326.json b/its/ruling/src/test/resources/expected/regex-examples/java-S6326.json index 12ab6f910a2..79f4ab89f91 100644 --- a/its/ruling/src/test/resources/expected/regex-examples/java-S6326.json +++ b/its/ruling/src/test/resources/expected/regex-examples/java-S6326.json @@ -35,6 +35,32 @@ 495, 711, 711, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, +857, 1693 ], "regex-examples:src/main/java/org/regex/examples/RegexDatabase7.java": [ diff --git a/its/ruling/src/test/resources/expected/regex-examples/java-S8786.json b/its/ruling/src/test/resources/expected/regex-examples/java-S8786.json index 60c1a6d04a8..3ce87b5ec1d 100644 --- a/its/ruling/src/test/resources/expected/regex-examples/java-S8786.json +++ b/its/ruling/src/test/resources/expected/regex-examples/java-S8786.json @@ -2,6 +2,7 @@ "regex-examples:src/main/java/org/regex/examples/RegexDatabase1.java": [ 33, 49, +77, 145, 195, 205, @@ -120,11 +121,13 @@ 473, 475, 479, +489, 551, 557, 717, 737, 779, +795, 881, 925, 953, @@ -314,7 +317,6 @@ 187, 189, 203, -211, 213, 221, 261, @@ -325,6 +327,7 @@ 341, 355, 357, +361, 373, 385, 415, @@ -746,7 +749,6 @@ 965, 975, 995, -1039, 1191, 1249, 1263, diff --git a/its/ruling/src/test/resources/expected/regex-examples/java-S9353.json b/its/ruling/src/test/resources/expected/regex-examples/java-S9353.json index e71c3f87d8b..37ccd0c89ed 100644 --- a/its/ruling/src/test/resources/expected/regex-examples/java-S9353.json +++ b/its/ruling/src/test/resources/expected/regex-examples/java-S9353.json @@ -1,8 +1,5 @@ { "regex-examples:src/main/java/org/regex/examples/RegexDatabase2.java": [ 457 -], -"regex-examples:src/main/java/org/regex/examples/RegexDatabase8.java": [ -1129 ] } diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1105.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1105.json new file mode 100644 index 00000000000..356473bfd1c --- /dev/null +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1105.json @@ -0,0 +1,30 @@ +{ +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/ExtractReportStep.java": [ +63 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistIssuesStep.java": [ +59 +], +"sonarqube-6.5:src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java": [ +99 +], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/index/PermissionIndexerDao.java": [ +187 +], +"sonarqube-6.5:src/main/java/org/sonar/server/platform/BackendCleanup.java": [ +81, +112 +], +"sonarqube-6.5:src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitor.java": [ +126 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/RegisterQualityProfiles.java": [ +74 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/ws/BackupAction.java": [ +70 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/ws/ExportAction.java": [ +137 +] +} diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1128.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1128.json index 75ff2982239..461121e7811 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1128.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1128.json @@ -1,19 +1,150 @@ { +"sonarqube-6.5:src/main/java/org/sonar/server/issue/notification/NewIssuesStatistics.java": [ +34, +35, +36, +37, +38 +], "sonarqube-6.5:src/main/java/org/sonar/server/notification/DefaultNotificationManager.java": [ 24 ], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/ws/SearchProjectPermissionsData.java": [ +33 +], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesData.java": [ +29 +], "sonarqube-6.5:src/main/java/org/sonar/server/qualitygate/QualityGates.java": [ 47 ], +"sonarqube-6.5:src/main/java/org/sonar/server/rule/RuleUpdate.java": [ +33, +34 +], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java": [ 29 ], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/api/measurecomputer/MeasureComputerContextImplTest.java": [ +46 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/batch/BatchReportReaderImplTest.java": [ +35 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/filemove/MutableMovedFilesRepositoryImplTest.java": [ +31 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/formula/AverageFormulaExecutionTest.java": [ +36 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/formula/DistributionFormulaExecutionTest.java": [ +35 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/formula/SumFormulaExecutionTest.java": [ +35 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/formula/ViewsFormulaExecutorComponentVisitorTest.java": [ +41 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/formula/counter/IntSumCounterTest.java": [ +28 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/formula/counter/LongSumCounterTest.java": [ +28 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortAggregatorTest.java": [ +37 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/issue/RuleRepositoryImplTest.java": [ +37 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/measure/BatchMeasureToMeasureTest.java": [ +37 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MapBasedRawMeasureRepositoryTest.java": [ +56 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureDtoToMeasureTest.java": [ +37 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureRepositoryImplTest.java": [ +56 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateHolderImplTest.java": [ +26 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateServiceImplTest.java": [ +37 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/scm/DbScmInfoTest.java": [ +29 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoRepositoryImplTest.java": [ +55 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ComputeQProfileMeasureStepTest.java": [ +43 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/CustomMeasuresCopyStepTest.java": [ +41 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/DuplicationDataMeasuresStepTest.java": [ +32 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/NewSizeMeasuresStepTest.java": [ +38 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateLoadingStepTest.java": [ +40 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportCommentMeasuresStepTest.java": [ +31 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportComplexityMeasuresStepTest.java": [ +31 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportDuplicationMeasuresStepTest.java": [ +33 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportLanguageDistributionMeasuresStepTest.java": [ +32 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportNewCoverageMeasuresStepTest.java": [ +41 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportPersistComponentsStepTest.java": [ +47 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportUnitTestMeasuresStepTest.java": [ +32 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ViewsCommentMeasuresStepTest.java": [ +31 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ViewsComplexityMeasuresStepTest.java": [ +31 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ViewsDuplicationMeasuresStepTest.java": [ +30 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ViewsLanguageDistributionMeasuresStepTest.java": [ +30 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ViewsNewCoverageMeasuresStepTest.java": [ +36 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ViewsUnitTestMeasuresStepTest.java": [ +30 +], "sonarqube-6.5:src/test/java/org/sonar/server/measure/index/ProjectsEsModuleTest.java": [ 24 ], "sonarqube-6.5:src/test/java/org/sonar/server/platform/web/SecurityServletFilterTest.java": [ 29 ], +"sonarqube-6.5:src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java": [ +35 +], "sonarqube-6.5:src/test/java/org/sonar/server/qualitygate/ws/CreateConditionActionTest.java": [ 45 ], @@ -41,9 +172,18 @@ 30, 36 ], +"sonarqube-6.5:src/test/java/org/sonar/server/setting/ws/SettingsFinderTest.java": [ +46 +], +"sonarqube-6.5:src/test/java/org/sonar/server/test/index/TestIndexTest.java": [ +32 +], "sonarqube-6.5:src/test/java/org/sonar/server/usertoken/ws/SearchActionTest.java": [ 37 ], +"sonarqube-6.5:src/test/java/org/sonar/server/ws/WebServiceFilterTest.java": [ +42 +], "sonarqube-6.5:src/test/java/org/sonar/server/ws/WsTester.java": [ 23 ] diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1130.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1130.json index d6a35022a8b..5d8dfd854c1 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1130.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1130.json @@ -32,12 +32,7 @@ 81, 92, 104, -111, -120, -132, -141, -154, -168 +111 ], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/CookiesTest.java": [ 44, @@ -52,12 +47,7 @@ 116 ], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java": [ -69, -82, -99, -112, -128, -145 +99 ], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/IdentityProviderRepositoryTest.java": [ 52, @@ -74,12 +64,6 @@ 57, 62, 71, -79, -89, -99, -109, -120, -131, 142, 150, 156, @@ -107,22 +91,11 @@ 299 ], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/JwtSerializerTest.java": [ -60, -70, -85, -100, -112, 129, 144, -160, -178, -196, -214, 231, 241, 250, -281, -293, 301, 309 ], @@ -131,69 +104,36 @@ 80 ], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/OAuth2ContextFactoryTest.java": [ -88, 99, 110, 128, 139, -150, 181 ], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/OAuthCsrfVerifierTest.java": [ 58, 64, -74, -90, -100, -110, -120 +74 ], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/RealmAuthenticatorTest.java": [ 87, -92, -114, -135, -151, -166, -182, -196, -210, -226, -242, 259, 265, 275 ], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java": [ 119, -127, -140, -152, -167, -180, -196, 209, -224, -240, 255, -272, -286, -302, -318, 330, -342, -352 +342 ], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/UserSessionInitializerTest.java": [ 85, 93, 125, 137, -152, -165, -177, -198, -212 +152 ], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/event/AuthenticationEventImplTest.java": [ 56 @@ -214,25 +154,14 @@ 78 ], "sonarqube-6.5:src/test/java/org/sonar/server/batch/IssuesActionTest.java": [ -86, -124, -161, -197, -233, -269, 296 ], "sonarqube-6.5:src/test/java/org/sonar/server/batch/ProjectActionTest.java": [ -48, -73 +48 ], "sonarqube-6.5:src/test/java/org/sonar/server/batch/ProjectDataLoaderTest.java": [ 61 ], -"sonarqube-6.5:src/test/java/org/sonar/server/ce/ws/ActivityActionTest.java": [ -239, -271 -], "sonarqube-6.5:src/test/java/org/sonar/server/ce/ws/CeWsTest.java": [ 34 ], @@ -289,33 +218,8 @@ 195 ], "sonarqube-6.5:src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java": [ -55, -70, -79, -88, -97, -105, -113, -121, -130, -139, -147, -155, -164, -173, -181, -189, -198, -207, -216, -233, -241, 249, 256, -263, -277, -288, -297, 308 ], "sonarqube-6.5:src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidatorTest.java": [ @@ -358,15 +262,9 @@ 216 ], "sonarqube-6.5:src/test/java/org/sonar/server/component/ws/SearchActionTest.java": [ -124, -135, -148, -162, -174, 189 ], "sonarqube-6.5:src/test/java/org/sonar/server/component/ws/SearchProjectsActionTest.java": [ -909, 923, 938, 954, @@ -375,45 +273,7 @@ "sonarqube-6.5:src/test/java/org/sonar/server/component/ws/ShowActionTest.java": [ 69, 98, -111, -147, -161, -173, -187, -202, -218, -228, -238, -247 -], -"sonarqube-6.5:src/test/java/org/sonar/server/component/ws/SuggestionsActionTest.java": [ -160, -185, -209, -225, -250, -266, -292, -310, -340, -373, -398, -414, -424, -443, -462, -487, -511, -533, -556, -572 -], -"sonarqube-6.5:src/test/java/org/sonar/server/component/ws/TreeActionTest.java": [ -108, -138, -168, -186, -210 +111 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/queue/ReportSubmitterTest.java": [ 89, @@ -455,11 +315,6 @@ 156, 165, 174, -183, -193, -203, -213, -223, 233, 242, 253 @@ -500,11 +355,6 @@ 41, 137 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ComponentRootBuilderTest.java": [ -493, -500, -507 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/FileAttributesTest.java": [ 34, 43, @@ -514,12 +364,10 @@ 75 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ReportVisitorsCrawlerTest.java": [ -56, 88, 123 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ViewsVisitorsCrawlerTest.java": [ -55, 87, 106 ], @@ -635,8 +483,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/issue/TrackerRawInputFactoryTest.java": [ 73, 86, -94, -127, 146, 162, 178 @@ -698,19 +544,12 @@ "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/measure/ViewsMeasureComputersVisitorTest.java": [ 119, 146, -167, 190 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/metric/ReportMetricValidatorImplTest.java": [ -47, 56, 65 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/ConditionEvaluatorTest.java": [ -288, -297, -306 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/qualitygate/QualityGateServiceImplTest.java": [ 62 ], @@ -739,10 +578,6 @@ 294, 309 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/RatingGridTest.java": [ -71, -80 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitorTest.java": [ 126, 146, @@ -773,8 +608,6 @@ 49, 68, 83, -100, -108, 119, 133, 147 @@ -801,14 +634,10 @@ "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoRepositoryImplTest.java": [ 92, 113, -126, 138, 152, -167, -173, 182, -221, -235 +221 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/source/LastCommitVisitorTest.java": [ 186, @@ -822,9 +651,7 @@ 55, 62, 72, -82, -93, -101 +93 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/BuildComponentTreeStepTest.java": [ 274, @@ -834,7 +661,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ComputeQProfileMeasureStepTest.java": [ 107, 112, -125, 135 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/CustomMeasuresCopyStepTest.java": [ @@ -849,11 +675,6 @@ 225, 232 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ExecuteVisitorsStepTest.java": [ -74, -90, -109 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ExtractReportStepTest.java": [ 65 ], @@ -880,11 +701,6 @@ 183, 193 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityProfilesStepTest.java": [ -53, -80, -94 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepTest.java": [ 78, 83, @@ -895,9 +711,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/PersistDevelopersStepTest.java": [ 35 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportCommentMeasuresStepTest.java": [ -81 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportComplexityMeasuresStepTest.java": [ 116, 121, @@ -914,13 +727,6 @@ 38, 53 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputeMeasureVariationsStepTest.java": [ -176, -256 -], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportCoverageMeasuresStepTest.java": [ -70 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportDuplicationMeasuresStepTest.java": [ 275, 300, @@ -930,9 +736,6 @@ 400, 421 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportLanguageDistributionMeasuresStepTest.java": [ -72 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/SendIssueNotificationsStepTest.java": [ 108, 125, @@ -942,9 +745,6 @@ 108, 129 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ViewsCommentMeasuresStepTest.java": [ -80 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ViewsComplexityMeasuresStepTest.java": [ 115, 120, @@ -957,9 +757,6 @@ 183, 188 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ViewsCoverageMeasuresStepTest.java": [ -67 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ViewsDuplicationMeasuresStepTest.java": [ 203, 224, @@ -1007,13 +804,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/es/SortingTest.java": [ 40 ], -"sonarqube-6.5:src/test/java/org/sonar/server/es/textsearch/ComponentTextSearchQueryFactoryTest.java": [ -37, -62, -71, -79, -87 -], "sonarqube-6.5:src/test/java/org/sonar/server/exceptions/BadRequestExceptionTest.java": [ 42, 49, @@ -1124,14 +914,8 @@ 166, 182, 198, -215, 241, 263, -283, -305, -327, -347, -371, 397, 423, 436, @@ -1140,17 +924,6 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/issue/ws/ChangelogActionTest.java": [ 72, -78, -95, -114, -127, -142, -157, -172, -186, -199, -212, -226, 258 ], "sonarqube-6.5:src/test/java/org/sonar/server/issue/ws/ComponentTagsActionTest.java": [ @@ -1191,28 +964,16 @@ 170, 176 ], -"sonarqube-6.5:src/test/java/org/sonar/server/issue/ws/SearchActionComponentsMediumTest.java": [ -139 -], "sonarqube-6.5:src/test/java/org/sonar/server/issue/ws/SetSeverityActionTest.java": [ 100, -115, 139, 145, 154 ], "sonarqube-6.5:src/test/java/org/sonar/server/issue/ws/SetTagsActionTest.java": [ -165, 190, 197 ], -"sonarqube-6.5:src/test/java/org/sonar/server/issue/ws/SetTypeActionTest.java": [ -101, -114, -138, -144, -155 -], "sonarqube-6.5:src/test/java/org/sonar/server/issue/ws/TagsActionTest.java": [ 82, 94, @@ -1224,16 +985,6 @@ 176 ], "sonarqube-6.5:src/test/java/org/sonar/server/license/ws/ListActionTest.java": [ -77, -103, -127, -152, -169, -186, -199, -213, -228, -245, 256 ], "sonarqube-6.5:src/test/java/org/sonar/server/measure/custom/ws/MetricsActionTest.java": [ @@ -1244,12 +995,10 @@ 424, 433, 442, -460, 546, 695, 849, 1007, -1098, 1211 ], "sonarqube-6.5:src/test/java/org/sonar/server/measure/index/ProjectMeasuresIndexerTest.java": [ @@ -1259,22 +1008,12 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/measure/index/ProjectMeasuresQueryTest.java": [ 41, -48, -57, -66, 75, 82, -87, -95, -103 +87 ], "sonarqube-6.5:src/test/java/org/sonar/server/measure/ws/SearchActionTest.java": [ -90, -109, -127, -151, -179, -196 +90 ], "sonarqube-6.5:src/test/java/org/sonar/server/organization/OrganizationCreationImplTest.java": [ 370, @@ -1318,172 +1057,26 @@ 151 ], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/AddGroupActionTest.java": [ -66, -79, -94, -107, -123, -139, -156, -169, -176, -183, -190, -221, -238, -251, -263, -275, -287, -302, -319, -333, -351, 449 ], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/AddUserActionTest.java": [ -68, -80, -95, -112, -127, -142, -155, -162, -169, -176, -196, -208, -222, -235, -246, -257, -273, -285, -302, -333 -], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/GroupsActionTest.java": [ -77, -116, -127, -142, -156, -181, -204, -223, -240, -259, -269, -279 -], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java": [ -70, -84, -98, -116, -134, -164, -188, -195, -202, -209, -229, -241, -253, -266, 281, -288, -297, -309, -326 -], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/RemoveUserActionTest.java": [ -75, -89, -103, -119, -135, -151, -164, -176, -183, -190, -197, -217, -230, -241, -252, -268, -280, -297 -], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/SearchGlobalPermissionsActionTest.java": [ -61, -92, -118, -128, -138, -150, -159 +288 ], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/SearchProjectPermissionsActionTest.java": [ -123, 162, -171, -183, -201, -214, -229, -244, -260, -269, 278 ], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/UsersActionTest.java": [ -60, -77, -87, -117, -140, -164, -184, -204, -220, -232, -292, -304, -315, -326, -341 -], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/AddGroupToTemplateActionTest.java": [ -74, -87, 193 ], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/AddProjectCreatorToTemplateActionTest.java": [ -59, -71, -95, -107, -119 -], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/AddUserToTemplateActionTest.java": [ -73, -86, -178, -192, 211 ], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/ApplyTemplateActionTest.java": [ -109, 195 ], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/BulkApplyTemplateActionTest.java": [ -115, -203, -213, 222, 230, 238 @@ -1504,55 +1097,14 @@ 175 ], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/RemoveProjectCreatorFromTemplateActionTest.java": [ -60, -83, -93, -103, -113, -125 +83 ], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/RemoveUserFromTemplateActionTest.java": [ -73, 190 ], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/SearchTemplatesActionTest.java": [ -173 -], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/SetDefaultTemplateActionTest.java": [ -84, 174 ], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/TemplateGroupsActionTest.java": [ -60, -107, -140, -171, -195, -216, -235, -256, -273, -286, -299, -313, -324, -336 -], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/TemplateUsersActionTest.java": [ -58, -73, -100, -125, -148, -174, -193, -208, -219, -229, -239, -251, -261 -], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/UpdateTemplateActionTest.java": [ 200 ], @@ -1622,9 +1174,6 @@ 55 ], "sonarqube-6.5:src/test/java/org/sonar/server/project/ws/CreateActionTest.java": [ -95, -112, -127, 239, 267, 275, @@ -1659,14 +1208,6 @@ 197 ], "sonarqube-6.5:src/test/java/org/sonar/server/project/ws/SearchActionTest.java": [ -90, -126, -138, -155, -167, -179, -193, -208, 222, 230, 237, @@ -1718,10 +1259,6 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/qualitygate/QgateProjectFinderTest.java": [ 73, -80, -90, -109, -127, 145, 164, 179, @@ -1742,18 +1279,7 @@ 60, 68 ], -"sonarqube-6.5:src/test/java/org/sonar/server/qualitygate/ws/AppActionTest.java": [ -64, -86, -105, -119, -134, -149, -164, -173 -], "sonarqube-6.5:src/test/java/org/sonar/server/qualitygate/ws/CreateActionTest.java": [ -60, 73, 83 ], @@ -1846,9 +1372,6 @@ 163 ], "sonarqube-6.5:src/test/java/org/sonar/server/qualityprofile/ws/CopyActionTest.java": [ -96, -130, -158, 240, 253 ], @@ -1888,8 +1411,6 @@ 205 ], "sonarqube-6.5:src/test/java/org/sonar/server/qualityprofile/ws/RestoreActionTest.java": [ -91, -112, 135, 147, 157, @@ -1936,7 +1457,6 @@ 213 ], "sonarqube-6.5:src/test/java/org/sonar/server/rule/ws/DeleteActionTest.java": [ -73, 100, 112 ], @@ -1956,23 +1476,7 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/rule/ws/SearchActionTest.java": [ 224, -240, -260, -276, -288, -300, -317, -360, -380, -415, -451, -486, -510, -534, -559, -676, -697, -730 +240 ], "sonarqube-6.5:src/test/java/org/sonar/server/rule/ws/ShowActionTest.java": [ 97, @@ -1997,11 +1501,6 @@ 330 ], "sonarqube-6.5:src/test/java/org/sonar/server/serverid/ws/ShowActionTest.java": [ -64, -81, -96, -109, -123, 137 ], "sonarqube-6.5:src/test/java/org/sonar/server/setting/ws/EncryptActionTest.java": [ @@ -2009,19 +1508,6 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/setting/ws/ListDefinitionsActionTest.java": [ 92, -166, -179, -196, -227, -265, -279, -293, -303, -316, -329, -343, -357, -370, 383 ], "sonarqube-6.5:src/test/java/org/sonar/server/setting/ws/ResetActionTest.java": [ @@ -2050,10 +1536,7 @@ 65, 78, 87, -112, -127, -137, -163 +127 ], "sonarqube-6.5:src/test/java/org/sonar/server/setting/ws/SettingsUpdaterTest.java": [ 68, @@ -2069,42 +1552,11 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/setting/ws/ValuesActionTest.java": [ 97, -103, -120, -146, -160, -180, -201, -215, -229, -245, -262, -275, -288, -300, -314, -330, -353, 369, 387, 405, 428, 453, -466, -483, -501, -518, -539, -560, -585, -597, -615, -636, -646, -664, -681, -698, -715, 728, 738 ], @@ -2144,12 +1596,17 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/test/index/TestIndexTest.java": [ 42, -55, 68, 81, -99, 126 ], +"sonarqube-6.5:src/test/java/org/sonar/server/test/index/TestResultSetIteratorTest.java": [ +90, +118, +152, +167, +182 +], "sonarqube-6.5:src/test/java/org/sonar/server/test/ws/ListActionTest.java": [ 84, 197, @@ -2237,16 +1694,11 @@ 187 ], "sonarqube-6.5:src/test/java/org/sonar/server/user/ws/CreateActionTest.java": [ -98, 129, 136, 143, 159, 175, -190, -205, -219, -233, 251, 264, 277, @@ -2257,16 +1709,6 @@ 318 ], "sonarqube-6.5:src/test/java/org/sonar/server/user/ws/GroupsActionTest.java": [ -72, -82, -98, -112, -126, -140, -151, -171, -184, -200, 221, 234, 244, @@ -2336,13 +1778,6 @@ 239 ], "sonarqube-6.5:src/test/java/org/sonar/server/usergroups/ws/SearchActionTest.java": [ -68, -87, -106, -123, -150, -167, -181, 191, 202 ], @@ -2367,14 +1802,7 @@ 71, 83, 97, -115, -139, -165, -189, -205, -235, -258, -298 +189 ], "sonarqube-6.5:src/test/java/org/sonar/server/view/index/ViewIndexTest.java": [ 41, diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1144.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1144.json index faa02974e6a..29c82bce94c 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1144.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1144.json @@ -1,16 +1,95 @@ { +"sonarqube-6.5:src/main/java/org/sonar/server/component/ws/ComponentDtoToWsComponent.java": [ +57 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/api/measurecomputer/MeasureComputerContextImpl.java": [ +110 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/api/posttask/ConditionToCondition.java": [ +78 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java": [ +147 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/CustomMeasuresCopyStep.java": [ +66 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/DuplicationDataMeasuresStep.java": [ +128 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadDuplicationsFromReportStep.java": [ +84, +92 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityGateStep.java": [ +73 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistComponentsStep.java": [ +316, +333, +348, +408 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PurgeDatastoresStep.java": [ +75 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateEventsStep.java": [ +77 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStep.java": [ +110 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/QualityProfileEventsStep.java": [ +83 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/UnitTestMeasuresStep.java": [ +101 +], +"sonarqube-6.5:src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java": [ +104 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java": [ +138 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/QProfileExporters.java": [ +146 +], +"sonarqube-6.5:src/test/java/org/sonar/server/ce/ws/TaskActionTest.java": [ +352 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/batch/BatchReportReaderRule.java": [ +70 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/TreeComponentProvider.java": [ +36 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/formula/ViewsFormulaExecutorComponentVisitorTest.java": [ +364 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/period/PeriodHolderRule.java": [ +44 +], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ComputeQProfileMeasureStepTest.java": [ 153 ], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/CustomMeasuresCopyStepTest.java": [ +247 +], "sonarqube-6.5:src/test/java/org/sonar/server/issue/IssueServiceMediumTest.java": [ 175 ], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java": [ -288 +"sonarqube-6.5:src/test/java/org/sonar/server/notification/NotificationDaemonTest.java": [ +251 ], "sonarqube-6.5:src/test/java/org/sonar/server/platform/web/WebPagesFilterTest.java": [ 143 ], +"sonarqube-6.5:src/test/java/org/sonar/server/projectanalysis/ws/SearchActionTest.java": [ +352 +], +"sonarqube-6.5:src/test/java/org/sonar/server/qualityprofile/RegisterQualityProfilesNotificationTest.java": [ +304 +], "sonarqube-6.5:src/test/java/org/sonar/server/qualityprofile/ws/BackupActionTest.java": [ 170 ], diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1166.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1166.json index f6b31ae3321..d2050b7b39d 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1166.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1166.json @@ -44,6 +44,7 @@ 114 ], "sonarqube-6.5:src/main/java/org/sonar/server/platform/web/PlatformServletContextListener.java": [ +46, 59 ], "sonarqube-6.5:src/main/java/org/sonar/server/project/ws/ProjectsWsSupport.java": [ diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1612.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1612.json index 9b625db50c5..fecd5d2aa58 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1612.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1612.json @@ -4,14 +4,5 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStepTest.java": [ 528 -], -"sonarqube-6.5:src/test/java/org/sonar/server/organization/ws/DeleteActionTest.java": [ -107 -], -"sonarqube-6.5:src/test/java/org/sonar/server/organization/ws/UpdateActionTest.java": [ -79 -], -"sonarqube-6.5:src/test/java/org/sonar/server/rule/ws/AppActionTest.java": [ -72 ] } diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1699.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1699.json index 312ba818f7d..8e4bbca6dee 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1699.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1699.json @@ -2,13 +2,13 @@ "sonarqube-6.5:src/main/java/org/sonar/server/computation/task/container/TaskContainerImpl.java": [ 44 ], -"sonarqube-6.5:src/main/java/org/sonar/server/es/DefaultIndexSettingsElement.java": [ -292 -], "sonarqube-6.5:src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java": [ 44, 50 ], +"sonarqube-6.5:src/main/java/org/sonar/server/plugins/UpdateCenterClient.java": [ +79 +], "sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/index/ActiveRuleDoc.java": [ 40 ], diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1854.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1854.json index ed09e5ee8d2..1378152e450 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1854.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1854.json @@ -1,4 +1,13 @@ { +"sonarqube-6.5:src/main/java/org/sonar/server/permission/ws/UsersAction.java": [ +148 +], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/ws/template/TemplateUsersAction.java": [ +131 +], +"sonarqube-6.5:src/main/java/org/sonar/server/setting/ws/ResetAction.java": [ +96 +], "sonarqube-6.5:src/test/java/org/sonar/server/authentication/UserIdentityAuthenticatorTest.java": [ 291 ], diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1905.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1905.json index 3e94dc55421..361cda5f9d7 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1905.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S1905.json @@ -1,10 +1,6 @@ { -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCounter.java": [ -198, -210 -], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/ComputeMeasureVariationsStep.java": [ -124 +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/filemove/SourceSimilarityImpl.java": [ +35 ], "sonarqube-6.5:src/main/java/org/sonar/server/issue/AbstractChangeTagsAction.java": [ 58 diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2118.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2118.json new file mode 100644 index 00000000000..75e0a80937c --- /dev/null +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2118.json @@ -0,0 +1,5 @@ +{ +"sonarqube-6.5:src/main/java/org/sonar/server/util/cache/DiskCache.java": [ +96 +] +} diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2175.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2175.json new file mode 100644 index 00000000000..e5f0ee3fe49 --- /dev/null +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2175.json @@ -0,0 +1,268 @@ +{ +"sonarqube-6.5:src/main/java/org/sonar/server/authentication/JwtCsrfVerifier.java": [ +89 +], +"sonarqube-6.5:src/main/java/org/sonar/server/component/ComponentFinder.java": [ +123, +127 +], +"sonarqube-6.5:src/main/java/org/sonar/server/component/ws/ComponentDtoToWsComponent.java": [ +73 +], +"sonarqube-6.5:src/main/java/org/sonar/server/component/ws/ProjectMeasuresQueryValidator.java": [ +61 +], +"sonarqube-6.5:src/main/java/org/sonar/server/component/ws/SearchProjectsAction.java": [ +281, +281, +434, +437, +443 +], +"sonarqube-6.5:src/main/java/org/sonar/server/component/ws/SuggestionsAction.java": [ +265, +338, +339, +340 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/api/measurecomputer/MeasureComputerContextImpl.java": [ +170 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/api/measurecomputer/MeasureImpl.java": [ +45 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/component/Component.java": [ +53, +57 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/filemove/FileMoveDetectionStep.java": [ +339, +339 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/ComponentsWithUnprocessedIssues.java": [ +42 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureRepositoryImpl.java": [ +112 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/metric/ReportMetricValidatorImpl.java": [ +48 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadMeasureComputersStep.java": [ +207, +207, +225, +226, +237 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistMeasuresStep.java": [ +111 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistProjectLinksStep.java": [ +111, +134, +134 +], +"sonarqube-6.5:src/main/java/org/sonar/server/es/Facets.java": [ +149, +157, +165, +182 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/AssignAction.java": [ +93 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/IssueQuery.java": [ +407 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/IssueQueryFactory.java": [ +194 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/RemoveTagsAction.java": [ +39 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/workflow/StateMachine.java": [ +77, +78 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/ws/SearchAction.java": [ +390, +398 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/ws/SearchResponseData.java": [ +158 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/ws/SearchResponseLoader.java": [ +344 +], +"sonarqube-6.5:src/main/java/org/sonar/server/measure/ws/ComponentAction.java": [ +226 +], +"sonarqube-6.5:src/main/java/org/sonar/server/measure/ws/ComponentTreeAction.java": [ +300, +301, +304, +306 +], +"sonarqube-6.5:src/main/java/org/sonar/server/measure/ws/ComponentTreeDataLoader.java": [ +314 +], +"sonarqube-6.5:src/main/java/org/sonar/server/measure/ws/SearchAction.java": [ +158 +], +"sonarqube-6.5:src/main/java/org/sonar/server/notification/NotificationService.java": [ +104 +], +"sonarqube-6.5:src/main/java/org/sonar/server/notification/ws/AddAction.java": [ +163, +168 +], +"sonarqube-6.5:src/main/java/org/sonar/server/notification/ws/ListAction.java": [ +161, +161 +], +"sonarqube-6.5:src/main/java/org/sonar/server/notification/ws/RemoveAction.java": [ +161, +166 +], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/GroupPermissionChanger.java": [ +72, +102, +106, +121 +], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/PermissionChange.java": [ +48, +50 +], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/PermissionTemplateService.java": [ +90, +184 +], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/UserPermissionChanger.java": [ +73, +86, +90, +99 +], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/index/PermissionIndexer.java": [ +136 +], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/ws/GroupsAction.java": [ +155 +], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/ws/PermissionRequestValidator.java": [ +51, +57, +71, +82 +], +"sonarqube-6.5:src/main/java/org/sonar/server/permission/ws/template/TemplateGroupsAction.java": [ +146 +], +"sonarqube-6.5:src/main/java/org/sonar/server/platform/ServerIdGenerator.java": [ +111 +], +"sonarqube-6.5:src/main/java/org/sonar/server/platform/web/SecurityServletFilter.java": [ +53 +], +"sonarqube-6.5:src/main/java/org/sonar/server/plugins/ServerPluginRepository.java": [ +171, +176 +], +"sonarqube-6.5:src/main/java/org/sonar/server/plugins/ws/PendingAction.java": [ +134 +], +"sonarqube-6.5:src/main/java/org/sonar/server/project/ws/GhostsAction.java": [ +136, +142 +], +"sonarqube-6.5:src/main/java/org/sonar/server/project/ws/ProvisionedAction.java": [ +136, +142 +], +"sonarqube-6.5:src/main/java/org/sonar/server/project/ws/UpdateVisibilityAction.java": [ +118 +], +"sonarqube-6.5:src/main/java/org/sonar/server/projectanalysis/ws/EventValidator.java": [ +43 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualitygate/QualityGateConditionsUpdater.java": [ +215 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualitygate/ValidRatingMetrics.java": [ +40 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java": [ +110 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileUpdateImpl.java": [ +57 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/QProfileBackuperImpl.java": [ +200 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/QProfileResetImpl.java": [ +69 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/RuleActivation.java": [ +43 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/ws/DeleteAction.java": [ +101 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/ws/SearchData.java": [ +87 +], +"sonarqube-6.5:src/main/java/org/sonar/server/rule/RegisterRules.java": [ +507 +], +"sonarqube-6.5:src/main/java/org/sonar/server/rule/RuleCreator.java": [ +103 +], +"sonarqube-6.5:src/main/java/org/sonar/server/rule/RuleUpdater.java": [ +146 +], +"sonarqube-6.5:src/main/java/org/sonar/server/rule/index/RuleIndexer.java": [ +187 +], +"sonarqube-6.5:src/main/java/org/sonar/server/rule/index/RuleQuery.java": [ +129, +191 +], +"sonarqube-6.5:src/main/java/org/sonar/server/rule/ws/RuleMapper.java": [ +314 +], +"sonarqube-6.5:src/main/java/org/sonar/server/rule/ws/SearchAction.java": [ +381, +428 +], +"sonarqube-6.5:src/main/java/org/sonar/server/setting/ws/SettingValidations.java": [ +86, +88 +], +"sonarqube-6.5:src/main/java/org/sonar/server/ui/ws/ComponentAction.java": [ +178 +], +"sonarqube-6.5:src/main/java/org/sonar/server/user/AbstractUserSession.java": [ +124 +], +"sonarqube-6.5:src/main/java/org/sonar/server/user/ServerUserSession.java": [ +118, +158 +], +"sonarqube-6.5:src/main/java/org/sonar/server/usergroups/ws/AddUserAction.java": [ +97 +], +"sonarqube-6.5:src/main/java/org/sonar/server/usergroups/ws/SearchAction.java": [ +144, +147, +150 +], +"sonarqube-6.5:src/main/java/org/sonar/server/util/StringListTypeValidation.java": [ +38 +], +"sonarqube-6.5:src/main/java/org/sonar/server/ws/WsParameterBuilder.java": [ +104 +] +} diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2184.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2184.json new file mode 100644 index 00000000000..4574fe15d16 --- /dev/null +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2184.json @@ -0,0 +1,15 @@ +{ +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewMaintainabilityMeasuresVisitor.java": [ +134 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/ComputeMeasureVariationsStep.java": [ +138, +140 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/UnitTestMeasuresStep.java": [ +115 +], +"sonarqube-6.5:src/main/java/org/sonar/server/source/SourceService.java": [ +72 +] +} diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2187.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2187.json index 9129ec44c02..e6b51799f70 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2187.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2187.json @@ -1,4 +1,10 @@ { +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/issue/commonrule/BranchCoverageRuleTest.java": [ +26 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/issue/commonrule/LineCoverageRuleTest.java": [ +26 +], "sonarqube-6.5:src/test/java/org/sonar/server/rule/ws/SearchActionMediumTest.java": [ 22 ] diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2221.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2221.json index be5e67dc1e0..6e5d1ea4558 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2221.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2221.json @@ -109,9 +109,6 @@ 68, 84 ], -"sonarqube-6.5:src/main/java/org/sonar/server/platform/BackendCleanup.java": [ -100 -], "sonarqube-6.5:src/main/java/org/sonar/server/platform/Platform.java": [ 290 ], @@ -152,9 +149,6 @@ "sonarqube-6.5:src/main/java/org/sonar/server/util/ObjectInputStreamIterator.java": [ 45 ], -"sonarqube-6.5:src/main/java/org/sonar/server/view/index/ViewIndexer.java": [ -127 -], "sonarqube-6.5:src/main/java/org/sonar/server/ws/ServletRequest.java": [ 101 ], diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2325.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2325.json index 48a0b2e235e..4c64e49ad22 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2325.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2325.json @@ -6,12 +6,12 @@ 68, 76 ], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/commonrule/CommentDensityRule.java": [ -66 -], "sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/qualitygate/ConditionEvaluator.java": [ 36 ], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/scm/ScmInfoRepositoryImpl.java": [ +140 +], "sonarqube-6.5:src/main/java/org/sonar/server/es/IndexDefinitionHash.java": [ 86 ], @@ -22,28 +22,16 @@ 155 ], "sonarqube-6.5:src/main/java/org/sonar/server/issue/ws/SearchAction.java": [ -421, 433 ], -"sonarqube-6.5:src/main/java/org/sonar/server/issue/ws/SearchResponseFormat.java": [ -122, -354 -], -"sonarqube-6.5:src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java": [ -85 -], "sonarqube-6.5:src/main/java/org/sonar/server/platform/web/PlatformServletContextListener.java": [ 56 ], -"sonarqube-6.5:src/main/java/org/sonar/server/projectanalysis/ws/SearchData.java": [ -50 -], "sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/QProfileCopier.java": [ 80 ], "sonarqube-6.5:src/main/java/org/sonar/server/rule/RegisterRules.java": [ 199, -279, 307, 375 ], diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2384.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2384.json index 2f608f7fa81..cbf9e864b87 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2384.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2384.json @@ -347,9 +347,6 @@ "sonarqube-6.5:src/main/java/org/sonar/server/plugins/ws/PluginsWs.java": [ 31 ], -"sonarqube-6.5:src/main/java/org/sonar/server/project/Visibility.java": [ -68 -], "sonarqube-6.5:src/main/java/org/sonar/server/project/ws/ProjectsWs.java": [ 32 ], @@ -363,8 +360,7 @@ 29 ], "sonarqube-6.5:src/main/java/org/sonar/server/projectanalysis/ws/SearchData.java": [ -91, -108 +91 ], "sonarqube-6.5:src/main/java/org/sonar/server/projectlink/ws/ProjectLinksWs.java": [ 29 diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2698.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2698.json index c2cf85b84b6..2f55d994587 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2698.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2698.json @@ -361,8 +361,6 @@ 109, 110, 111, -163, -173, 189, 190, 191, @@ -496,18 +494,11 @@ "sonarqube-6.5:src/test/java/org/sonar/server/batch/IssuesActionTest.java": [ 107, 108, -109, 110, 111, -112, -113, -114, 115, 116, 117, -118, -119, -120, 145, 146, 147, @@ -518,7 +509,6 @@ 152, 153, 154, -155, 156, 157, 181, @@ -531,7 +521,6 @@ 188, 189, 190, -191, 192, 193, 217, @@ -544,12 +533,10 @@ 224, 225, 226, -227, 228, 229, 253, 254, -255, 256, 257, 258, @@ -557,7 +544,6 @@ 260, 261, 262, -263, 264, 265, 290, @@ -566,8 +552,7 @@ "sonarqube-6.5:src/test/java/org/sonar/server/batch/ProjectActionTest.java": [ 64, 65, -66, -83 +66 ], "sonarqube-6.5:src/test/java/org/sonar/server/batch/ProjectDataLoaderMediumTest.java": [ 106, @@ -638,8 +623,6 @@ 222, 223, 224, -254, -267, 281, 282, 293, @@ -799,9 +782,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/component/NewComponentTest.java": [ 139 ], -"sonarqube-6.5:src/test/java/org/sonar/server/component/index/ComponentIndexCombinationTest.java": [ -36 -], "sonarqube-6.5:src/test/java/org/sonar/server/component/index/ComponentIndexHighlightTest.java": [ 71 ], @@ -859,10 +839,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/component/ws/ProjectMeasuresQueryFactoryTest.java": [ 64, 101, -126, -135, -160, -169, 194, 203, 229, @@ -897,11 +873,6 @@ 118, 119, 120, -131, -144, -158, -170, -185, 200, 203 ], @@ -911,46 +882,24 @@ 146, 147, 148, -149, 150, -158, 159, 160, 163, -164, 182, -183, 186, -187, 190, -191, -229, 240, 241, 242, 243, -246, 255, 257, 258, 259, -280, 296, 299, 302, -318, -333, -348, -363, -376, -390, -404, -418, -433, -446, -460, -474, -488, 500, 501, 503, @@ -965,9 +914,6 @@ 547, 549, 563, -564, -578, -589, 609, 634, 655, @@ -981,7 +927,6 @@ 851, 876, 900, -919, 932, 934, 948, @@ -989,10 +934,7 @@ 964, 966, 988, -991, -1013, -1038, -1057 +991 ], "sonarqube-6.5:src/test/java/org/sonar/server/component/ws/ShowActionTest.java": [ 72, @@ -1013,21 +955,10 @@ 120, 131, 141, -142, -143, 156, -157, 168, -169, -183, -198, -214, -223, 224, -233, -234, -243, -253 +234 ], "sonarqube-6.5:src/test/java/org/sonar/server/component/ws/SuggestionsActionTest.java": [ 116, @@ -1044,57 +975,16 @@ 135, 136, 137, -175, -179, -199, -203, -220, -240, -244, -261, -282, -286, -306, -330, -352, -369, -388, -392, -409, -410, -420, -436, -439, -456, -481, -500, -506, -527, -550, -568, -592, -722, -729, -734, 738 ], "sonarqube-6.5:src/test/java/org/sonar/server/component/ws/TreeActionTest.java": [ 132, 133, -134, 162, 163, -164, -182, 204, 205, -206, -226, -247, -248, -249, 260, -261, 262, 263, 264, @@ -1494,50 +1384,6 @@ 242, 244 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ReportPostOrderDepthTraversalTypeAwareCrawlerTest.java": [ -59, -69, -79, -89, -99, -107, -117, -127, -137, -145, -153, -163, -173, -181, -189, -197, -206, -225, -240, -253 -], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ReportPreOrderDepthTraversalTypeAwareCrawlerTest.java": [ -60, -70, -80, -90, -100, -108, -118, -128, -138, -146, -154, -164, -174, -182, -190, -198, -207, -226, -241, -254 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ReportVisitorsCrawlerTest.java": [ 111, 119 @@ -1558,34 +1404,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ViewsPathAwareVisitorTest.java": [ 209 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ViewsPostOrderDepthTraversalTypeAwareCrawlerTest.java": [ -57, -67, -77, -87, -95, -105, -115, -123, -131, -140, -159, -174 -], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ViewsPreOrderDepthTraversalTypeAwareCrawlerTest.java": [ -57, -67, -77, -87, -95, -105, -115, -123, -131, -140, -159, -174 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ViewsVisitorsCrawlerTest.java": [ 129, 137 @@ -1814,9 +1632,6 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/formula/IntSumFormulaTest.java": [ 58, -71, -72, -73, 152 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/formula/LongSumFormulaTest.java": [ @@ -2400,7 +2215,6 @@ 283, 284, 285, -303, 309, 320, 331 @@ -2418,9 +2232,6 @@ 69, 71 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureLevelTest.java": [ -36 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/measure/MeasureRepositoryImplTest.java": [ 150, 250, @@ -3035,7 +2846,6 @@ 182, 183, 185, -186, 201, 202, 203, @@ -3048,7 +2858,6 @@ 235, 236, 238, -239, 251, 265, 266, @@ -3056,7 +2865,6 @@ 268, 269, 271, -272, 284, 296, 309, @@ -3065,7 +2873,6 @@ 312, 313, 315, -316, 327, 340, 341, @@ -3090,7 +2897,6 @@ 416, 417, 419, -420, 432 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadQualityProfilesStepTest.java": [ @@ -3313,8 +3119,6 @@ 190, 191, 192, -193, -194, 208, 228, 241, @@ -3449,7 +3253,6 @@ 229, 230, 250, -252, 269 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ReportCoverageMeasuresStepTest.java": [ @@ -3751,16 +3554,8 @@ 138, 154, 155, -156, -157, -158, -159, 175, 176, -177, -178, -179, -180, 196, 197, 198, @@ -3854,7 +3649,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ViewsPersistComponentsStepTest.java": [ 187, 352, -370, 456, 460, 479, @@ -3911,7 +3705,6 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/webhook/WebhookCallerImplTest.java": [ 65, -66, 67, 68, 69, @@ -3936,7 +3729,6 @@ 108, 109, 125, -126, 127, 128, 129, @@ -4679,16 +4471,10 @@ 112 ], "sonarqube-6.5:src/test/java/org/sonar/server/favorite/ws/SearchActionTest.java": [ -79, -82, 92, -93, 105, 106, 118, -121, -136, -149, 169, 170 ], @@ -5639,13 +5425,8 @@ 56, 57, 58, -59, 64 ], -"sonarqube-6.5:src/test/java/org/sonar/server/issue/workflow/IsBeingClosedTest.java": [ -33, -34 -], "sonarqube-6.5:src/test/java/org/sonar/server/issue/workflow/IssueWorkflowTest.java": [ 55, 57, @@ -5801,42 +5582,11 @@ 461 ], "sonarqube-6.5:src/test/java/org/sonar/server/issue/ws/ChangelogActionTest.java": [ -86, 87, 88, 89, -90, -91, -106, -107, -108, -110, -121, -123, -135, 136, 137, -138, -149, -150, -151, -152, -153, -164, -165, -166, -167, -168, -180, -182, -194, -195, -207, -208, -222, -234, -235, -245, 275, 276, 277, @@ -5844,22 +5594,8 @@ 279 ], "sonarqube-6.5:src/test/java/org/sonar/server/issue/ws/ComponentTagsActionTest.java": [ -53, -54, -55, -56, 57, -58, -61, -62, -63, -65, -66, -67, -69, 70, -71, -72, 102, 103, 104, @@ -5948,18 +5684,7 @@ 173, 174, 175, -209, -210, -211, -212, -213, -216, -217, -218, -220, 221, -222, -223, 256, 259, 262 @@ -5981,47 +5706,23 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/issue/ws/TagsActionTest.java": [ 165, -195, -196, -197, -198, -199, 202, -203, -204, -205, 208, -209, 210, -211, -212, 215, -216, -217, -218, -219, 220 ], "sonarqube-6.5:src/test/java/org/sonar/server/language/ws/LanguageWsTest.java": [ 63, -64, -65, 66, 67, -68, 71, -72, -73, -74, -75, -76, -77 +73 ], "sonarqube-6.5:src/test/java/org/sonar/server/license/ws/LicensesWsModuleTest.java": [ 32 ], "sonarqube-6.5:src/test/java/org/sonar/server/license/ws/ListActionTest.java": [ -85, 87, 88, 89, @@ -6034,7 +5735,6 @@ 97, 98, 99, -110, 112, 113, 114, @@ -6046,7 +5746,6 @@ 121, 122, 123, -134, 136, 137, 138, @@ -6059,30 +5758,23 @@ 146, 147, 148, -160, 162, 163, 164, 165, -177, 179, 180, 181, 182, -193, 195, -206, 208, 209, -221, 223, 224, -236, 238, 239, 240, 241, -252, 267, 268, 269, @@ -6269,30 +5961,19 @@ ], "sonarqube-6.5:src/test/java/org/sonar/server/measure/ws/ComponentTreeActionTest.java": [ 132, -133, -134, -135, -166, 169, 170, -171, -207, 210, 211, 214, 215, 246, 248, -288, 289, 290, 291, -317, 318, -352, 354, -380, -411, 430, 431 ], @@ -6338,15 +6019,12 @@ 141, 143, 144, -147, -173, 189, 191, 192, 207, 209, 210, -228, 241, 245, 370, @@ -6357,36 +6035,15 @@ 375 ], "sonarqube-6.5:src/test/java/org/sonar/server/measure/ws/SearchHistoryActionTest.java": [ -116, 117, -121, -135, -136, -137, -147, -168, -169, 175, -177, 180, -181, 185, -187, -204, -205, -223, -224, 238, -239, -240, 245, -246, -247, 258, 269, 270, -271, -272, 322, 323, 324, @@ -6595,15 +6252,6 @@ 156 ], "sonarqube-6.5:src/test/java/org/sonar/server/notification/ws/ListActionTest.java": [ -104, -111, -118, -131, -142, -153, -167, -186, -207, 254, 255, 256, @@ -6788,11 +6436,8 @@ 557, 617, 618, -620, 622, -625, 627, -630, 632, 636, 637, @@ -6918,30 +6563,10 @@ 183, 197, 201, -214, -215, -218, -219, -222, -223, -226, -227, 237 ], "sonarqube-6.5:src/test/java/org/sonar/server/organization/ws/SearchMembersActionTest.java": [ -81, -84, -99, -121, 134, -147, -167, -188, -205, -208, -222, -236, -252, 302, 303, 304, @@ -6950,8 +6575,6 @@ 307, 308, 309, -312, -313, 314 ], "sonarqube-6.5:src/test/java/org/sonar/server/organization/ws/SearchMyOrganizationsActionTest.java": [ @@ -7165,34 +6788,7 @@ 272 ], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/AddGroupActionTest.java": [ -75, -90, -103, -118, -119, -134, -135, -151, -152, -362, -380, -397, -412, -429, -446 -], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/AddUserActionTest.java": [ -76, -91, -107, -108, -122, -123, -137, -138, -313, -363, -379 +380 ], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/GroupsActionTest.java": [ 123, @@ -7223,25 +6819,6 @@ 77, 78 ], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/RemoveGroupActionTest.java": [ -80, -94, -111, -112, -129, -130, -147, -148, -338, -356 -], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/RemoveUserActionTest.java": [ -85, -115, -131, -147, -309 -], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/SearchGlobalPermissionsActionTest.java": [ 107, 110, @@ -7375,24 +6952,6 @@ 200, 201 ], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/TemplateGroupsActionTest.java": [ -133, -134, -135, -136, -165, -166, -167, -191, -212, -231, -249, -250, -251, -252, -268, -269 -], "sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/TemplateUsersActionTest.java": [ 93, 94, @@ -7685,7 +7244,6 @@ 62, 65, 68, -80, 83, 86 ], @@ -8015,21 +7573,13 @@ 123, 124, 137, -139, 253, 254, 255, 257 ], "sonarqube-6.5:src/test/java/org/sonar/server/project/ws/CreateActionTest.java": [ -105, 108, -123, -140, -155, -170, -185, -200, 217, 305, 306, @@ -8059,8 +7609,6 @@ 72, 75, 76, -77, -78, 110, 134, 149, @@ -8082,8 +7630,6 @@ 77, 81, 84, -85, -86, 87, 120, 138, @@ -8091,16 +7637,6 @@ 169 ], "sonarqube-6.5:src/test/java/org/sonar/server/project/ws/SearchActionTest.java": [ -98, -110, -122, -134, -151, -163, -175, -189, -204, -218, 248, 249, 250, @@ -8132,17 +7668,14 @@ 121, 122, 138, -140, 156, -157, 171, 172, 186, 187, 204, 205, -226, -227 +226 ], "sonarqube-6.5:src/test/java/org/sonar/server/project/ws/UpdateKeyActionTest.java": [ 120, @@ -8202,7 +7735,6 @@ 126, 127, 128, -143, 159, 174, 176, @@ -8239,14 +7771,7 @@ 154, 156, 157, -175, 196, -221, -223, -242, -262, -283, -304, 312, 313, 314, @@ -8317,7 +7842,6 @@ 117, 118, 130, -132, 147, 148, 316, @@ -8332,8 +7856,6 @@ 42 ], "sonarqube-6.5:src/test/java/org/sonar/server/projecttag/ws/SearchActionTest.java": [ -89, -98, 105, 106, 107, @@ -8362,15 +7884,12 @@ 451 ], "sonarqube-6.5:src/test/java/org/sonar/server/qualitygate/QgateProjectFinderTest.java": [ -86, 102, 122, 140, 234, 235, -239, -243, -244 +243 ], "sonarqube-6.5:src/test/java/org/sonar/server/qualitygate/QualityGateConditionsUpdaterTest.java": [ 314, @@ -8431,16 +7950,8 @@ 79, 80, 81, -82, 98, 100, -101, -114, -130, -145, -160, -169, -178, 209, 210, 211, @@ -8474,11 +7985,8 @@ 163, 164, 166, -168, 171, -173, -176, -178 +176 ], "sonarqube-6.5:src/test/java/org/sonar/server/qualitygate/ws/DeselectActionTest.java": [ 219, @@ -8528,85 +8036,60 @@ "sonarqube-6.5:src/test/java/org/sonar/server/qualitygate/ws/QualityGatesWsTest.java": [ 90, 91, -92, -93, 96, 97, 98, -99, -100, 103, 104, 105, -106, 107, -108, 111, 112, 113, -114, 115, -116, 119, 120, 121, -122, 123, 124, -125, 128, 129, 130, -131, 132, -133, 136, 137, 138, -139, 140, 141, -142, 145, 146, 147, -148, 149, -150, 153, 154, 155, -156, -157, 160, 161, 162, -163, 164, 165, 166, 167, 168, 169, -170, 173, 174, 175, -176, 177, 178, 179, 180, 181, 182, -183, 186, 187, 188, -189, 190, -191, -194, 347, 369 ], @@ -8630,11 +8113,8 @@ 171, 172, 174, -176, 179, -181, -184, -186 +184 ], "sonarqube-6.5:src/test/java/org/sonar/server/qualityprofile/ActiveRuleChangeTest.java": [ 44, @@ -8753,73 +8233,26 @@ "sonarqube-6.5:src/test/java/org/sonar/server/qualityprofile/QProfileComparisonMediumTest.java": [ 95, 96, -97, -98, -99, -100, -101, 113, 114, -115, -116, -117, -118, -119, 129, 130, -131, -132, -133, -134, -135, 144, 145, -146, -147, -148, -149, -150, 160, 161, -162, -163, -164, -165, -166, 176, 177, -178, -179, -180, -181, -182, 185, 186, -187, 197, 198, -199, -200, -201, -202, -203, 206, -207, -208, 210, 211, 221, 222, -223, -224, -225, -226, -227, -230, -231, -232, -233, -234 +230 ], "sonarqube-6.5:src/test/java/org/sonar/server/qualityprofile/QProfileCopierTest.java": [ 66, @@ -8915,13 +8348,11 @@ 84, 88, 92, -95, 99, 100, 104, 105, 106, -109, 114, 116, 118, @@ -9243,15 +8674,9 @@ 138, 139, 140, -141, -142, -143, -144, 155, 156, 157, -167, -168, 184, 200, 209, @@ -9315,8 +8740,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/qualityprofile/ws/InheritanceActionTest.java": [ 181, 182, -184, -185, 207, 208, 235, @@ -9420,9 +8843,7 @@ 341, 351, 362, -367, 377, -382, 404, 426, 427, @@ -9548,22 +8969,6 @@ 117, 120, 121, -128, -138, -152, -167, -181, -195, -207, -220, -234, -250, -267, -285, -304, -320, -371, -388, 403, 404, 405, @@ -9600,19 +9005,13 @@ 99, 111, 121, -132, 146, 147, 148, 171, -201, -221, -234, 246, 258, -270, -285, -301 +270 ], "sonarqube-6.5:src/test/java/org/sonar/server/root/ws/RootsWsModuleTest.java": [ 38 @@ -10164,7 +9563,6 @@ 639, 674, 678, -679, 683, 687, 691, @@ -10331,13 +9729,6 @@ 218, 219, 220, -271, -284, -296, -310, -312, -330, -333, 371, 372, 375, @@ -10351,7 +9742,6 @@ 405, 406, 407, -408, 409, 410, 411, @@ -10364,8 +9754,6 @@ 440, 441, 442, -443, -444, 446, 447, 468, @@ -10377,14 +9765,11 @@ 476, 477, 478, -479, 480, -481, 482, 500, 501, 504, -505, 506, 524, 525, @@ -10401,21 +9786,15 @@ 604, 605, 606, -607, 612, 613, 661, 662, 663, 664, -665, 668, 669, 688, -689, -726, -734, -735, 755, 756, 757, @@ -10438,10 +9817,7 @@ 221, 222, 223, -224, -225, -226, -227 +226 ], "sonarqube-6.5:src/test/java/org/sonar/server/rule/ws/TagsActionTest.java": [ 71, @@ -10553,28 +9929,7 @@ 73, 74, 75, -76, -77, -88, 89, -90, -91, -92, -101, -102, -103, -104, -105, -115, -116, -117, -118, -119, -129, -130, -131, -132, -133, 149, 150, 151, @@ -10629,7 +9984,6 @@ 258 ], "sonarqube-6.5:src/test/java/org/sonar/server/setting/ws/CheckSecretKeyActionTest.java": [ -75, 82, 83, 84, @@ -10654,7 +10008,6 @@ 75 ], "sonarqube-6.5:src/test/java/org/sonar/server/setting/ws/ListDefinitionsActionTest.java": [ -112, 114, 115, 116, @@ -10663,7 +10016,6 @@ 119, 120, 121, -133, 135, 136, 137, @@ -10674,17 +10026,13 @@ 142, 143, 144, -158, 160, 161, 162, -173, 174, 175, -189, 191, 192, -208, 210, 211, 213, @@ -10697,19 +10045,6 @@ 221, 222, 223, -237, -238, -248, -261, -275, -289, -299, -312, -325, -339, -353, -366, -379, 395, 396, 397, @@ -10787,49 +10122,22 @@ 32 ], "sonarqube-6.5:src/test/java/org/sonar/server/setting/ws/ValuesActionTest.java": [ -112, 114, 115, 116, -134, 137, 138, 141, 142, -153, 155, 156, -173, 175, -194, 196, -210, -224, -240, -258, -270, 282, 283, 284, -296, -310, -325, -345, -363, -476, 478, 479, -497, -534, -555, -580, -593, -611, -631, -642, -677, -694, -710, 724, 784, 785, @@ -11064,7 +10372,6 @@ 191 ], "sonarqube-6.5:src/test/java/org/sonar/server/test/ws/ListActionTest.java": [ -98, 100, 101, 102, @@ -11075,11 +10382,6 @@ 107, 108, 109, -121, -136, -151, -166, -181, 191, 192, 193 @@ -11238,13 +10540,8 @@ 79 ], "sonarqube-6.5:src/test/java/org/sonar/server/user/DoPrivilegedTest.java": [ -49, -50, -51, 54, -72, -75, -76 +72 ], "sonarqube-6.5:src/test/java/org/sonar/server/user/ExternalIdentityTest.java": [ 36, @@ -11567,7 +10864,6 @@ 128 ], "sonarqube-6.5:src/test/java/org/sonar/server/user/ws/CreateActionTest.java": [ -111, 114, 115, 116, @@ -11579,28 +10875,10 @@ 155, 171, 186, -201, -215, -229, -247, 336, 340 ], "sonarqube-6.5:src/test/java/org/sonar/server/user/ws/CurrentActionTest.java": [ -75, -96, -97, -98, -99, -100, -112, -124, -140, -163, -164, -168, -169, -170, 199, 200, 201, @@ -11634,20 +10912,11 @@ 364 ], "sonarqube-6.5:src/test/java/org/sonar/server/user/ws/GroupsActionTest.java": [ -78, -92, -108, -122, -136, -147, -164, 165, 166, 167, 178, 180, -194, -217, 317, 318, 319, @@ -11796,22 +11065,6 @@ 172 ], "sonarqube-6.5:src/test/java/org/sonar/server/usergroups/ws/SearchActionTest.java": [ -78, -97, -116, -132, -133, -139, -140, -145, -146, -155, -157, -159, -161, -163, -177, -187, 224, 225, 226, diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2699.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2699.json index cf0f347abd4..88313d44c7a 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2699.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S2699.json @@ -34,7 +34,6 @@ 276 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java": [ -270, 307 ], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/QualityGateMeasuresStepTest.java": [ @@ -52,8 +51,7 @@ 43 ], "sonarqube-6.5:src/test/java/org/sonar/server/es/RecoveryIndexerTest.java": [ -103, -204 +103 ], "sonarqube-6.5:src/test/java/org/sonar/server/es/request/ProxyClearCacheRequestBuilderTest.java": [ 43 @@ -94,9 +92,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/es/request/ProxySearchRequestBuilderTest.java": [ 43 ], -"sonarqube-6.5:src/test/java/org/sonar/server/measure/ws/SearchActionTest.java": [ -317 -], "sonarqube-6.5:src/test/java/org/sonar/server/notification/email/EmailNotificationChannelTest.java": [ 161 ], @@ -106,9 +101,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/organization/ws/RemoveMemberActionTest.java": [ 272 ], -"sonarqube-6.5:src/test/java/org/sonar/server/permission/ws/template/TemplateGroupsActionTest.java": [ -86 -], "sonarqube-6.5:src/test/java/org/sonar/server/platform/DatabaseServerCompatibilityTest.java": [ 79 ], @@ -134,9 +126,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java": [ 132 ], -"sonarqube-6.5:src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregateBuilderTest.java": [ -49 -], "sonarqube-6.5:src/test/java/org/sonar/server/projectlink/ws/SearchActionTest.java": [ 152, 162 @@ -151,9 +140,6 @@ 173, 183 ], -"sonarqube-6.5:src/test/java/org/sonar/server/qualityprofile/RuleActivatorTest.java": [ -728 -], "sonarqube-6.5:src/test/java/org/sonar/server/setting/ws/ResetActionTest.java": [ 183, 294, @@ -164,15 +150,9 @@ "sonarqube-6.5:src/test/java/org/sonar/server/startup/RegisterServletFiltersTest.java": [ 35 ], -"sonarqube-6.5:src/test/java/org/sonar/server/ui/ws/ComponentActionTest.java": [ -390 -], "sonarqube-6.5:src/test/java/org/sonar/server/user/NewUserNotifierTest.java": [ 33 ], -"sonarqube-6.5:src/test/java/org/sonar/server/user/UserSessionFilterTest.java": [ -236 -], "sonarqube-6.5:src/test/java/org/sonar/server/usertoken/ws/RevokeActionTest.java": [ 93 ] diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3242.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3242.json index 473d296c75c..204addbf49b 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3242.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3242.json @@ -2,6 +2,10 @@ "sonarqube-6.5:src/main/java/org/sonar/server/component/index/ComponentIndexer.java": [ 113 ], +"sonarqube-6.5:src/main/java/org/sonar/server/es/EsUtils.java": [ +69, +78 +], "sonarqube-6.5:src/main/java/org/sonar/server/issue/index/IssueIndexer.java": [ 137 ], diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3252.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3252.json index 95bba3454b1..770faa2d577 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3252.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3252.json @@ -1,20 +1,53 @@ { +"sonarqube-6.5:src/main/java/org/sonar/server/app/ServerProcessLogging.java": [ +40 +], +"sonarqube-6.5:src/main/java/org/sonar/server/component/index/ComponentIndexer.java": [ +58 +], "sonarqube-6.5:src/main/java/org/sonar/server/debt/DebtModelXMLExporter.java": [ 132 ], "sonarqube-6.5:src/main/java/org/sonar/server/debt/DebtRulesXMLImporter.java": [ 100 ], -"sonarqube-6.5:src/main/java/org/sonar/server/permission/PermissionTemplateService.java": [ -215 +"sonarqube-6.5:src/main/java/org/sonar/server/issue/IssueQuery.java": [ +52 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/index/IssueIndexer.java": [ +68 ], -"sonarqube-6.5:src/main/java/org/sonar/server/permission/ws/template/DeleteTemplateAction.java": [ -118 +"sonarqube-6.5:src/main/java/org/sonar/server/measure/custom/ws/CustomMeasureJsonWriter.java": [ +53 ], -"sonarqube-6.5:src/main/java/org/sonar/server/permission/ws/template/SearchTemplatesAction.java": [ -72 +"sonarqube-6.5:src/main/java/org/sonar/server/measure/index/ProjectMeasuresIndexer.java": [ +58 +], +"sonarqube-6.5:src/main/java/org/sonar/server/measure/ws/ComponentTreeSort.java": [ +58 +], +"sonarqube-6.5:src/main/java/org/sonar/server/metric/ws/MetricJsonWriter.java": [ +43 ], "sonarqube-6.5:src/main/java/org/sonar/server/platform/ServerLogging.java": [ 60 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/BuiltInQProfileInsertImpl.java": [ +218 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/index/ActiveRuleIndexer.java": [ +86 +], +"sonarqube-6.5:src/main/java/org/sonar/server/test/index/TestIndexer.java": [ +76 +], +"sonarqube-6.5:src/main/java/org/sonar/server/user/index/UserIndexer.java": [ +63 +], +"sonarqube-6.5:src/main/java/org/sonar/server/user/ws/UserJsonWriter.java": [ +48 +], +"sonarqube-6.5:src/main/java/org/sonar/server/view/index/ViewIndexer.java": [ +52 ] } diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3740.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3740.json index ee71bf7d56f..7dc2699a7c8 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3740.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3740.json @@ -7,29 +7,6 @@ 195, 207 ], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/component/VisitorsCrawler.java": [ -161 -], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/formula/FormulaExecutorComponentVisitor.java": [ -66, -68, -100, -149, -150, -163, -164, -173, -173, -185, -185, -220, -220, -222, -222, -223, -235, -235 -], "sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/NewEffortCalculator.java": [ 110 ], @@ -47,38 +24,10 @@ 117, 143 ], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/CommentMeasuresStep.java": [ -57, -95, -105, -172, -173 -], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/ComplexityMeasuresStep.java": [ -53 -], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/CoverageMeasuresStep.java": [ -48 -], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/DuplicationMeasuresStep.java": [ -65 -], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LanguageDistributionMeasuresStep.java": [ -56 -], "sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadMeasureComputersStep.java": [ 153, 163 ], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/NewCoverageMeasuresStep.java": [ -65 -], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/SizeMeasuresStep.java": [ -57 -], -"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/UnitTestMeasuresStep.java": [ -55 -], "sonarqube-6.5:src/main/java/org/sonar/server/es/IndexDefinitionHash.java": [ 46, 68, @@ -119,9 +68,6 @@ "sonarqube-6.5:src/main/java/org/sonar/server/metric/DefaultMetricFinder.java": [ 90 ], -"sonarqube-6.5:src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationExecutorServiceImpl.java": [ -32 -], "sonarqube-6.5:src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java": [ 136, 137 diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3878.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3878.json new file mode 100644 index 00000000000..636e2885192 --- /dev/null +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S3878.json @@ -0,0 +1,14 @@ +{ +"sonarqube-6.5:src/main/java/org/sonar/ce/http/CeHttpClient.java": [ +106 +], +"sonarqube-6.5:src/main/java/org/sonar/server/ce/ws/ActivityAction.java": [ +242 +], +"sonarqube-6.5:src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java": [ +171 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/ws/CreateAction.java": [ +78 +] +} diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4144.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4144.json index 0cfbf1f25a2..fc92712bee3 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4144.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4144.json @@ -20,12 +20,6 @@ "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/FileAttributesTest.java": [ 43 ], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ReportPostOrderDepthTraversalTypeAwareCrawlerTest.java": [ -159 -], -"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ReportPreOrderDepthTraversalTypeAwareCrawlerTest.java": [ -160 -], "sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/component/ReportVisitorsCrawlerWithPostOrderTypeAwareVisitorTest.java": [ 180 ], diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4248.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4248.json index 649c0f0930e..bdcc4b6026c 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4248.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4248.json @@ -1,22 +1,10 @@ { -"sonarqube-6.5:src/main/java/org/sonar/server/component/ws/SuggestionsAction.java": [ -252 -], "sonarqube-6.5:src/main/java/org/sonar/server/debt/DebtModelXMLExporter.java": [ 102 ], "sonarqube-6.5:src/main/java/org/sonar/server/es/Facets.java": [ 118 ], -"sonarqube-6.5:src/main/java/org/sonar/server/es/textsearch/ComponentTextSearchQueryFactory.java": [ -95 -], -"sonarqube-6.5:src/main/java/org/sonar/server/measure/index/ProjectsTextSearchQueryFactory.java": [ -104 -], -"sonarqube-6.5:src/main/java/org/sonar/server/platform/UrlSettings.java": [ -47 -], "sonarqube-6.5:src/main/java/org/sonar/server/platform/web/WebPagesFilter.java": [ 79 ], diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4551.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4551.json index bf5a91bc9e3..3e254f37905 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4551.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S4551.json @@ -20,14 +20,10 @@ 71 ], "sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/NewReliabilityAndSecurityRatingMeasuresVisitor.java": [ -134, -134, 160, 162 ], "sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/qualitymodel/ReliabilityAndSecurityRatingMeasuresVisitor.java": [ -122, -122, 151, 153 ], @@ -37,15 +33,6 @@ 210, 211 ], -"sonarqube-6.5:src/main/java/org/sonar/server/license/ws/ListAction.java": [ -92 -], -"sonarqube-6.5:src/main/java/org/sonar/server/measure/ws/ComponentTreeSort.java": [ -128 -], -"sonarqube-6.5:src/main/java/org/sonar/server/organization/ws/SearchMembersAction.java": [ -152 -], "sonarqube-6.5:src/main/java/org/sonar/server/platform/ws/ChangeLogLevelAction.java": [ 72 ], @@ -55,9 +42,6 @@ "sonarqube-6.5:src/main/java/org/sonar/server/property/ws/IndexAction.java": [ 222 ], -"sonarqube-6.5:src/main/java/org/sonar/server/qualitygate/ValidRatingMetrics.java": [ -31 -], "sonarqube-6.5:src/main/java/org/sonar/server/rule/CachingRuleFinder.java": [ 145, 180 @@ -76,9 +60,6 @@ "sonarqube-6.5:src/main/java/org/sonar/server/rule/index/RuleIndex.java": [ 119 ], -"sonarqube-6.5:src/main/java/org/sonar/server/setting/ws/ScannerSettings.java": [ -66 -], "sonarqube-6.5:src/main/java/org/sonar/server/setting/ws/SetAction.java": [ 200 ], @@ -91,9 +72,5 @@ "sonarqube-6.5:src/main/java/org/sonar/server/setting/ws/ValuesAction.java": [ 226, 245 -], -"sonarqube-6.5:src/main/java/org/sonar/server/ui/PageRepository.java": [ -105, -107 ] } diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S5845.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S5845.json index 0f7fe29a807..352a0b519e2 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S5845.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S5845.json @@ -10,9 +10,6 @@ 143, 232 ], -"sonarqube-6.5:src/test/java/org/sonar/server/qualitygate/ws/CreateActionTest.java": [ -66 -], "sonarqube-6.5:src/test/java/org/sonar/server/qualityprofile/RuleActivatorTest.java": [ 1107, 1108, diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S6073.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S6073.json new file mode 100644 index 00000000000..ab070e6a158 --- /dev/null +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S6073.json @@ -0,0 +1,39 @@ +{ +"sonarqube-6.5:src/test/java/org/sonar/server/authentication/BasicAuthenticatorTest.java": [ +150 +], +"sonarqube-6.5:src/test/java/org/sonar/server/authentication/CredentialsAuthenticatorTest.java": [ +78 +], +"sonarqube-6.5:src/test/java/org/sonar/server/authentication/OAuth2CallbackFilterTest.java": [ +93, +122 +], +"sonarqube-6.5:src/test/java/org/sonar/server/authentication/OAuth2ContextFactoryTest.java": [ +95, +155 +], +"sonarqube-6.5:src/test/java/org/sonar/server/authentication/RealmAuthenticatorTest.java": [ +110, +131, +147, +162, +178, +192, +206 +], +"sonarqube-6.5:src/test/java/org/sonar/server/authentication/SsoAuthenticatorTest.java": [ +136, +148, +163, +176, +192, +205, +236, +251, +282, +298, +314, +326 +] +} diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S8786.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S8786.json index 3aca0ef27eb..f4212caf66b 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S8786.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S8786.json @@ -5,9 +5,6 @@ "sonarqube-6.5:src/main/java/org/sonar/server/debt/DebtModelXMLExporter.java": [ 102 ], -"sonarqube-6.5:src/main/java/org/sonar/server/platform/UrlSettings.java": [ -47 -], "sonarqube-6.5:src/main/java/org/sonar/server/util/MetricKeyValidator.java": [ 50 ] diff --git a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S9396.json b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S9396.json index 2c63c085104..9857c27bd9b 100644 --- a/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S9396.json +++ b/its/ruling/src/test/resources/expected/sonarqube-6.5/java-S9396.json @@ -1,2 +1,208 @@ { +"sonarqube-6.5:src/main/java/org/sonar/server/authentication/BaseContextFactory.java": [ +55, +56, +57 +], +"sonarqube-6.5:src/main/java/org/sonar/server/authentication/OAuth2ContextFactory.java": [ +67, +68, +69 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/api/measurecomputer/MeasureComputerContextImpl.java": [ +198 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/formula/AverageFormula.java": [ +102, +104, +105 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/formula/DistributionFormula.java": [ +60, +61 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/formula/FormulaExecutorComponentVisitor.java": [ +192, +241, +242 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/EffortAggregator.java": [ +104, +105, +106 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/TrackerBaseInputFactory.java": [ +57, +59 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/issue/TrackerRawInputFactory.java": [ +70 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/CommentMeasuresStep.java": [ +78 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/ComputeMeasureVariationsStep.java": [ +92, +93, +94 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/ComputeQProfileMeasureStep.java": [ +68 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/LoadDuplicationsFromReportStep.java": [ +101 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistAnalysisStep.java": [ +68, +69 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistComponentsStep.java": [ +145, +146 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStep.java": [ +75, +76 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistEventsStep.java": [ +76, +77 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistFileSourcesStep.java": [ +95, +97, +98 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistMeasuresStep.java": [ +95 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistProjectLinksStep.java": [ +80 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/PersistTestsStep.java": [ +90, +91, +92, +93, +94 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/SizeMeasuresStep.java": [ +94, +95, +96 +], +"sonarqube-6.5:src/main/java/org/sonar/server/computation/task/projectanalysis/step/ValidateProjectStep.java": [ +98, +99, +100, +101, +103 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/ws/BulkChangeAction.java": [ +283, +284, +285, +286, +287, +288, +289 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/ws/ChangelogAction.java": [ +151, +152, +153 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/ws/DeleteCommentAction.java": [ +108, +109 +], +"sonarqube-6.5:src/main/java/org/sonar/server/issue/ws/EditCommentAction.java": [ +129, +130, +131 +], +"sonarqube-6.5:src/main/java/org/sonar/server/measure/ws/SearchAction.java": [ +102, +103, +104, +105, +106, +107 +], +"sonarqube-6.5:src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel.java": [ +145 +], +"sonarqube-6.5:src/main/java/org/sonar/server/property/ws/IndexAction.java": [ +187, +188 +], +"sonarqube-6.5:src/main/java/org/sonar/server/qualityprofile/ws/InheritanceAction.java": [ +145, +146 +], +"sonarqube-6.5:src/main/java/org/sonar/server/setting/ws/ValuesAction.java": [ +178, +179, +181, +182, +183, +184 +], +"sonarqube-6.5:src/main/java/org/sonar/server/util/cache/DiskCache.java": [ +79 +], +"sonarqube-6.5:src/main/java/org/sonar/server/ws/DefaultLocalResponse.java": [ +62, +64 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/formula/ReportFormulaExecutorComponentVisitorTest.java": [ +291, +342 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/formula/ViewsFormulaExecutorComponentVisitorTest.java": [ +266, +315 +], +"sonarqube-6.5:src/test/java/org/sonar/server/computation/task/projectanalysis/step/ExecuteVisitorsStepTest.java": [ +223 +], +"sonarqube-6.5:src/test/java/org/sonar/server/es/RecoveryIndexerTest.java": [ +314, +328, +342, +357, +377, +399, +400, +401 +], +"sonarqube-6.5:src/test/java/org/sonar/server/platform/web/WebPagesFilterTest.java": [ +155 +], +"sonarqube-6.5:src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java": [ +307 +], +"sonarqube-6.5:src/test/java/org/sonar/server/rule/ws/RuleQueryFactoryTest.java": [ +300, +301 +], +"sonarqube-6.5:src/test/java/org/sonar/server/setting/ThreadLocalSettingsTest.java": [ +250, +251, +252 +], +"sonarqube-6.5:src/test/java/org/sonar/server/startup/RegisterMetricsTest.java": [ +146 +], +"sonarqube-6.5:src/test/java/org/sonar/server/user/DoPrivilegedTest.java": [ +81 +], +"sonarqube-6.5:src/test/java/org/sonar/server/ws/DumbResponse.java": [ +46, +48 +], +"sonarqube-6.5:src/test/java/org/sonar/server/ws/WsTester.java": [ +158, +159 +] } diff --git a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java index d0e1076e027..04eb2fc7f1f 100644 --- a/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java +++ b/java-checks-common/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java @@ -77,7 +77,7 @@ public static CompilationUnitTree parse(File file) { public static CompilationUnitTree parse(String source) { List classpath = Arrays.asList(new File("target/test-classes"), new File("target/classes")); JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; - return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), "test", source); + return JParser.parse(version.toString(), "test", source, classpath); } } diff --git a/java-checks/src/test/java/org/sonar/java/checks/DeadStoreCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/DeadStoreCheckTest.java index 5b8121a0fe1..23b15e4ea90 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/DeadStoreCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/DeadStoreCheckTest.java @@ -65,7 +65,7 @@ private static class EraseSymbols extends BaseTreeVisitor { @Override public void visitVariable(VariableTree tree) { if (tree instanceof VariableTreeImpl varImpl) { - varImpl.setSymbol(Symbol.UNKNOWN_SYMBOL); + varImpl.variableSymbol = null; } super.visitVariable(tree); } diff --git a/java-checks/src/test/java/org/sonar/java/checks/ParsingErrorCheckTest.java b/java-checks/src/test/java/org/sonar/java/checks/ParsingErrorCheckTest.java index debf45c33a4..59936381065 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/ParsingErrorCheckTest.java +++ b/java-checks/src/test/java/org/sonar/java/checks/ParsingErrorCheckTest.java @@ -47,12 +47,9 @@ void test() { assertThat(issue.getMessage()).isEqualTo("Parse error"); } - /** - * Related to SONARJAVA-3486: discrepancies between JLS, javac (more tolerant than JLS) and ECJ (strict to JLS). - * According to JLS (§7.3 Compilation Units), empty statements are not allowed in import section. - */ + /** An empty statement among the imports is kept and the file is still analyzed. */ @Test - void javacEmptyStatementsInImportsBug() { + void empty_statement_in_imports_is_analyzed() { SonarComponents sonarComponents = mock(SonarComponents.class); when(sonarComponents.inputFileContents(any())).thenCallRealMethod(); @@ -60,10 +57,6 @@ void javacEmptyStatementsInImportsBug() { .withSonarComponents(sonarComponents) .build(); JavaAstScanner.scanSingleFileForTests(TestUtils.inputFile("src/test/files/checks/parsing/EmptyStatementsInImportsBug.java"), visitorsBridge); - Set issues = visitorsBridge.testContexts().get(0).getIssues(); - assertThat(issues).hasSize(1); - AnalyzerMessage issue = issues.iterator().next(); - assertThat(issue.getLine()).isEqualTo(3); - assertThat(issue.getMessage()).isEqualTo("Parse error"); + assertThat(visitorsBridge.testContexts().get(0).getIssues()).isEmpty(); } } diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java index fd512787b13..4cb739ca49f 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/JParserTestUtils.java @@ -19,7 +19,6 @@ import com.google.common.io.Files; import java.io.File; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import org.sonar.java.model.JParser; @@ -83,9 +82,9 @@ public static CompilationUnitTree parse(File file) { } public static CompilationUnitTree parse(String source) { - List classpath = Arrays.asList(new File("target/test-classes"), new File("target/classes")); + List classpath = List.of(new File("target/test-classes"), new File("target/classes")); JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; - return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), "test", source); + return JParser.parse(version.toString(), "test", source, classpath); } } diff --git a/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java b/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java index e5c715f9739..8a2f5168e9e 100644 --- a/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java +++ b/java-checks/src/test/java/org/sonar/java/checks/helpers/SpringContextModelTestUtils.java @@ -174,6 +174,6 @@ private static CompilationUnitTree parse(File file, List classpath) { throw new IllegalStateException("Unable to read file '" + file.getAbsolutePath() + "'", e); } JavaVersion version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; - return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), file.getName(), source); + return JParser.parse(version.toString(), file.getName(), source); } } diff --git a/java-frontend/pom.xml b/java-frontend/pom.xml index 016dbbde0b9..82c006418f7 100644 --- a/java-frontend/pom.xml +++ b/java-frontend/pom.xml @@ -22,8 +22,8 @@ ${project.groupId} jdt-package 1.9.0.1840 + test - org.sonarsource.api.plugin sonar-plugin-api @@ -92,6 +92,16 @@ com.google.code.gson gson + + org.apache.commons + commons-compress + 1.27.1 + + + org.tukaani + xz + 1.10 + org.sonarsource.java @@ -144,6 +154,18 @@ + + + src/main/resources + + + ${maven.multiModuleProjectDirectory}/semsitter-bridge/prebuilt + + bridge-*.xz + + bridge + + org.apache.maven.plugins diff --git a/java-frontend/src/main/java/org/eclipse/jdt/core/dom/ASTUtils.java b/java-frontend/src/main/java/org/eclipse/jdt/core/dom/ASTUtils.java deleted file mode 100644 index 80bc65622b0..00000000000 --- a/java-frontend/src/main/java/org/eclipse/jdt/core/dom/ASTUtils.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * SonarQube Java - * Copyright (C) SonarSource Sàrl - * mailto:info AT sonarsource DOT com - * - * You can redistribute and/or modify this program under the terms of - * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the Sonar Source-Available License for more details. - * - * You should have received a copy of the Sonar Source-Available License - * along with this program; if not, see https://sonarsource.com/license/ssal/ - */ -package org.eclipse.jdt.core.dom; - -import java.util.Objects; -import javax.annotation.Nullable; -import org.eclipse.jdt.core.compiler.CharOperation; -import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation; -import org.eclipse.jdt.internal.compiler.env.IBinaryType; -import org.eclipse.jdt.internal.compiler.env.INameEnvironment; -import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer; -import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding; -import org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding; -import org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment; -import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding; -import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public final class ASTUtils { - - private static final Logger LOG = LoggerFactory.getLogger(ASTUtils.class); - - private static final IAnnotationBinding[] NO_ANNOTATIONS = new IAnnotationBinding[0]; - - private ASTUtils() { - } - - public static void mayTolerateMissingType(AST ast) { - ast.getBindingResolver().lookupEnvironment().mayTolerateMissingType = true; - } - - public static Runnable getEnvironmentCleaner(AST ast) { - return new EnvironmentCleaner(ast.getBindingResolver().lookupEnvironment().nameEnvironment); - } - - @Nullable - public static ITypeBinding resolveType(AST ast, String name) { - try { - BindingResolver bindingResolver = ast.getBindingResolver(); - ReferenceBinding referenceBinding = bindingResolver - .lookupEnvironment() - .getType(CharOperation.splitOn('.', name.toCharArray())); - return bindingResolver.getTypeBinding(referenceBinding); - } catch (Exception e) { - // exception on ECJ side when trying to resolve a Type, recover on null type - LOG.error("ECJ Unable to resolve type " + name, e); - return null; - } - } - - public static IAnnotationBinding[] resolvePackageAnnotations(AST ast, String packageName) { - // See org.eclipse.jdt.core.dom.PackageBinding#getAnnotations() - BindingResolver bindingResolver = ast.getBindingResolver(); - LookupEnvironment lookupEnvironment = bindingResolver.lookupEnvironment(); - NameEnvironmentAnswer answer = lookupEnvironment.nameEnvironment.findType( - TypeConstants.PACKAGE_INFO_NAME, - CharOperation.splitOn('.', packageName.toCharArray()) - ); - if (answer == null) { - return NO_ANNOTATIONS; - } - IBinaryType type = answer.getBinaryType(); - if (type == null) { - // Can happen for instance with ant, as ant only generates 'package-info.class' - // when there is annotations in the package-info.java file. - return NO_ANNOTATIONS; - } - IBinaryAnnotation[] binaryAnnotations = type.getAnnotations(); - AnnotationBinding[] binaryInstances = - BinaryTypeBinding.createAnnotations(binaryAnnotations, lookupEnvironment, type.getMissingTypeNames()); - AnnotationBinding[] allInstances = - AnnotationBinding.addStandardAnnotations(binaryInstances, type.getTagBits(), lookupEnvironment); - - IAnnotationBinding[] domInstances = new IAnnotationBinding[allInstances.length]; - for (int i = 0; i < allInstances.length; i++) { - // FIXME can be null if annotation can not be resolved e.g. due to incomplete classpath - domInstances[i] = bindingResolver.getAnnotationInstance(allInstances[i]); - } - return domInstances; - } - - public static String signature(IMethodBinding methodBinding) { - char[] signature = ((MethodBinding) methodBinding).binding.signature(); - return new String(signature); - } - - private static class EnvironmentCleaner implements Runnable { - private final INameEnvironment env; - - private EnvironmentCleaner(INameEnvironment env) { - this.env = env; - } - - @Override - public void run() { - env.cleanup(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - EnvironmentCleaner that = (EnvironmentCleaner) o; - return Objects.equals(env, that.env); - } - - @Override - public int hashCode() { - return Objects.hash(env); - } - } - -} diff --git a/java-frontend/src/main/java/org/eclipse/jdt/core/dom/package-info.java b/java-frontend/src/main/java/org/eclipse/jdt/core/dom/package-info.java deleted file mode 100644 index 41ae9dc837b..00000000000 --- a/java-frontend/src/main/java/org/eclipse/jdt/core/dom/package-info.java +++ /dev/null @@ -1,17 +0,0 @@ -/* - * SonarQube Java - * Copyright (C) SonarSource Sàrl - * mailto:info AT sonarsource DOT com - * - * You can redistribute and/or modify this program under the terms of - * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the Sonar Source-Available License for more details. - * - * You should have received a copy of the Sonar Source-Available License - * along with this program; if not, see https://sonarsource.com/license/ssal/ - */ -package org.eclipse.jdt.core.dom; diff --git a/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java b/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java index 74e3e81452c..dd1c71e52a2 100644 --- a/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java +++ b/java-frontend/src/main/java/org/sonar/java/ProgressMonitor.java @@ -19,12 +19,11 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BooleanSupplier; -import org.eclipse.core.runtime.IProgressMonitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.java.annotations.VisibleForTesting; -public class ProgressMonitor implements IProgressMonitor, Runnable { +public class ProgressMonitor implements Runnable { private final Logger logger; private final long period; @@ -84,7 +83,6 @@ public void run() { } } - @Override public void beginTask(String name, int totalWork) { if (totalWork <= 0) { unknownTotalWork = true; @@ -96,7 +94,6 @@ public void beginTask(String name, int totalWork) { thread.start(); } - @Override public void done() { if (success && analysisProgress.isLastBatch()) { log("100% analyzed"); @@ -107,7 +104,6 @@ public void done() { join(); } - @Override public boolean isCanceled() { if (isCanceled.getAsBoolean()) { log("Batch processing: Cancelled!"); @@ -116,7 +112,6 @@ public boolean isCanceled() { return false; } - @Override public void setCanceled(boolean value) { // do nothing } @@ -129,7 +124,6 @@ private void join() { } } - @Override public void worked(int work) { processedWork += work; if (processedWork == totalWork) { @@ -137,17 +131,14 @@ public void worked(int work) { } } - @Override public void internalWorked(double work) { // do nothing } - @Override public void setTaskName(String name) { // do nothing } - @Override public void subTask(String name) { // do nothing } diff --git a/java-frontend/src/main/java/org/sonar/java/model/AbstractTypedTree.java b/java-frontend/src/main/java/org/sonar/java/model/AbstractTypedTree.java index e9945b8a8f2..f6577e7b2e4 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/AbstractTypedTree.java +++ b/java-frontend/src/main/java/org/sonar/java/model/AbstractTypedTree.java @@ -17,6 +17,7 @@ package org.sonar.java.model; import java.util.Objects; +import javax.annotation.Nullable; import org.sonar.plugins.java.api.semantic.Type; /** @@ -24,14 +25,15 @@ */ public abstract class AbstractTypedTree extends JavaTree { - private Type symbolType = Type.UNKNOWN; + @Nullable + public Type type; public void setSymbolType(Type symbolType) { - this.symbolType = Objects.requireNonNull(symbolType); + this.type = Objects.requireNonNull(symbolType); } public Type symbolType() { - return symbolType; + return type != null ? type : Type.UNKNOWN; } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/CompilerWarnings.java b/java-frontend/src/main/java/org/sonar/java/model/CompilerWarnings.java new file mode 100644 index 00000000000..e856073c38f --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/CompilerWarnings.java @@ -0,0 +1,266 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.model; + +import java.util.ArrayList; +import java.util.EnumMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; +import org.sonar.plugins.java.api.tree.BaseTreeVisitor; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.IdentifierTree; +import org.sonar.plugins.java.api.tree.ImportClauseTree; +import org.sonar.plugins.java.api.tree.ImportTree; +import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; +import org.sonar.plugins.java.api.tree.Tree; + +/** + * The compiler diagnostics the checks read from the compilation unit, derived from the tree and its symbols: + * unused imports and assignments without effect. + */ +final class CompilerWarnings extends BaseTreeVisitor { + + private final Map> warnings = new EnumMap<>(JProblem.Type.class); + private final List identifiers = new ArrayList<>(); + private final JSema sema; + + private CompilerWarnings(JSema sema) { + this.sema = sema; + } + + static void addTo(JavaTree.CompilationUnitTreeImpl unit) { + CompilerWarnings visitor = new CompilerWarnings((JSema) unit.sema); + visitor.scan(unit.packageDeclaration()); + visitor.scan(unit.types()); + unit.imports().forEach(visitor::scan); + visitor.reportUnusedImports(unit); + unit.addWarnings(visitor.warnings); + } + + private void add(JProblem.Type type, String message, Tree tree) { + warnings.computeIfAbsent(type, key -> new LinkedHashSet<>()).add(new JWarning(message, type, tree)); + } + + @Override + public void visitImport(ImportTree tree) { + // names inside an import are not uses + } + + @Override + public void visitIdentifier(IdentifierTree tree) { + identifiers.add(tree); + } + + /** Only simple names use an import: the member of a qualified name is not one. */ + @Override + public void visitMemberSelectExpression(MemberSelectExpressionTree tree) { + scan(tree.expression()); + if (tree.identifier() instanceof JavaTree.AnnotatedTypeTree annotated) { + scan(annotated.annotations()); + } + } + + @Override + public void visitTypeCast(org.sonar.plugins.java.api.tree.TypeCastTree tree) { + Type cast = tree.type().symbolType(); + Type operand = tree.expression().symbolType(); + if (!cast.isUnknown() && !operand.isUnknown() && operand.isSubtypeOf(cast)) { + if (cast.erasure().fullyQualifiedName().equals(operand.erasure().fullyQualifiedName())) { + add(JProblem.Type.REDUNDANT_CAST, "Unnecessary cast to " + cast.erasure().name(), tree); + } + } + super.visitTypeCast(tree); + } + + @Override + public void visitAssignmentExpression(AssignmentExpressionTree tree) { + if (tree.is(Tree.Kind.ASSIGNMENT)) { + Symbol target = assignedVariable(tree.variable()); + if (target != null && target.equals(assignedVariable(tree.expression()))) { + add(JProblem.Type.ASSIGNMENT_HAS_NO_EFFECT, "The assignment to variable " + target.name() + " has no effect", tree); + } + } + super.visitAssignmentExpression(tree); + } + + /** The variable a simple name, {@code this.name} or {@code Type.name} designates. */ + private static Symbol assignedVariable(ExpressionTree tree) { + ExpressionTree expression = tree; + while (expression instanceof org.sonar.plugins.java.api.tree.ParenthesizedTree parenthesized) { + expression = parenthesized.expression(); + } + IdentifierTree name = null; + if (expression instanceof IdentifierTree identifier) { + name = identifier; + } else if (expression instanceof MemberSelectExpressionTree select) { + ExpressionTree qualifier = select.expression(); + if (qualifier instanceof IdentifierTree owner && ("this".equals(owner.name()) || owner.symbol().isTypeSymbol())) { + name = select.identifier(); + } + } + if (name == null || !name.symbol().isVariableSymbol() || name.symbol().isUnknown()) { + return null; + } + return name.symbol(); + } + + private void reportUnusedImports(JavaTree.CompilationUnitTreeImpl unit) { + Set seen = new HashSet<>(); + String currentPackage = unit.packageDeclaration() == null ? "" : dotted(unit.packageDeclaration().packageName()); + for (ImportClauseTree clause : unit.imports()) { + if (!(clause instanceof ImportTree importTree)) { + continue; + } + String name = dotted(importTree.qualifiedIdentifier()); + String key = (importTree.isStatic() ? "static " : "") + name; + boolean duplicate = !seen.add(key); + if (isResolvable(importTree, name, currentPackage) && (duplicate || isOwnPackage(importTree, name, currentPackage) || !isUsed(importTree, name))) { + add(JProblem.Type.UNUSED_IMPORT, "The import " + (name.endsWith(".*") ? name.substring(0, name.length() - 2) : name) + " is never used", importTree); + } + } + } + + /** Like the compiler, only an import that resolves can be reported as unused. */ + private boolean isResolvable(ImportTree importTree, String name, String currentPackage) { + if (importTree instanceof JavaTree.ImportTreeImpl impl && impl.isModule()) { + return true; + } + boolean onDemand = name.endsWith(".*"); + String qualifier = onDemand ? name.substring(0, name.length() - 2) : name; + if (onDemand && !importTree.isStatic() && qualifier.equals(currentPackage)) { + return true; + } + return resolvesType(importTree.isStatic() && !onDemand ? qualifier.substring(0, Math.max(0, qualifier.lastIndexOf('.'))) : qualifier); + } + + private boolean resolvesType(String dotted) { + return binaryName(dotted) != null; + } + + /** The binary name of the type a dotted name denotes, trying each dot as a nesting boundary. */ + private String binaryName(String dotted) { + String candidate = dotted; + while (!candidate.isEmpty()) { + if (sema.declaresType(candidate)) { + return candidate; + } + int dot = candidate.lastIndexOf('.'); + if (dot < 0) { + return null; + } + candidate = candidate.substring(0, dot) + "$" + candidate.substring(dot + 1); + } + return null; + } + + /** The package's own types are imported implicitly, so such an import is never counted as used. */ + private static boolean isOwnPackage(ImportTree importTree, String name, String currentPackage) { + if (importTree.isStatic() || currentPackage.isEmpty()) { + return false; + } + String qualifier = name.endsWith(".*") ? name.substring(0, name.length() - 2) : name.substring(0, Math.max(0, name.lastIndexOf('.'))); + return (qualifier.equals(currentPackage) && !name.endsWith(".*")) || name.equals(currentPackage + ".*"); + } + + private boolean isUsed(ImportTree importTree, String name) { + if (importTree instanceof JavaTree.ImportTreeImpl impl && impl.isModule()) { + return true; + } + boolean onDemand = name.endsWith(".*"); + String qualifier = onDemand ? name.substring(0, name.length() - 2) : name.substring(0, Math.max(0, name.lastIndexOf('.'))); + for (IdentifierTree identifier : identifiers) { + Symbol symbol = identifier.symbol(); + // a symbol found in the scope of the file does not consult the import + boolean inScope = !symbol.isUnknown() && symbol.declaration() != null; + if (!inScope && uses(importTree, name, qualifier, identifier)) { + return true; + } + } + return false; + } + + private boolean uses(ImportTree importTree, String name, String qualifier, IdentifierTree identifier) { + Symbol symbol = identifier.symbol(); + if (name.endsWith(".*")) { + return declaredIn(symbol, qualifier, importTree.isStatic()); + } + if (!identifier.name().equals(name.substring(name.lastIndexOf('.') + 1))) { + return false; + } + if (symbol.isUnknown()) { + return true; + } + return importTree.isStatic() ? isMemberOf(symbol, qualifier) : denotesType(symbol, name, identifier); + } + + /** A static import also brings in the members the named type inherits. */ + private boolean isMemberOf(Symbol symbol, String qualifier) { + if (!(symbol.owner() instanceof Symbol.TypeSymbol owner)) { + return false; + } + if (dottedType(owner).equals(qualifier)) { + return true; + } + String binary = binaryName(qualifier); + return binary != null && sema.getClassType(binary).isSubtypeOf(owner.type()); + } + + private static boolean denotesType(Symbol symbol, String name, IdentifierTree identifier) { + if (symbol.isTypeSymbol()) { + return dottedType(symbol).equals(name); + } + if (!(symbol.isMethodSymbol() && "".equals(symbol.name()))) { + return false; + } + if (symbol.owner() instanceof Symbol.TypeSymbol owner && dottedType(owner).equals(name)) { + return true; + } + Type written = identifier.symbolType(); + return written != null && !written.isUnknown() && written.erasure().fullyQualifiedName().replace('$', '.').equals(name); + } + + private static boolean declaredIn(Symbol symbol, String qualifier, boolean isStatic) { + if (symbol.isUnknown()) { + return false; + } + if (isStatic) { + return symbol.owner() instanceof Symbol.TypeSymbol owner && dottedType(owner).equals(qualifier); + } + Symbol type = symbol; + if ("".equals(symbol.name())) { + type = symbol.owner(); + } + return type.isTypeSymbol() && !type.isUnknown() && dottedType(type).startsWith(qualifier + "."); + } + + private static String dottedType(Symbol symbol) { + return symbol.type().fullyQualifiedName().replace('$', '.'); + } + + private static String dotted(Tree tree) { + if (tree instanceof MemberSelectExpressionTree select) { + return dotted(select.expression()) + "." + select.identifier().name(); + } + return tree instanceof IdentifierTree identifier ? identifier.name() : ""; + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/EffectivelyFinalVariables.java b/java-frontend/src/main/java/org/sonar/java/model/EffectivelyFinalVariables.java new file mode 100644 index 00000000000..eeb101c9909 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/EffectivelyFinalVariables.java @@ -0,0 +1,92 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.model; + +import java.util.ArrayList; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; +import org.sonar.plugins.java.api.tree.BaseTreeVisitor; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.IdentifierTree; +import org.sonar.plugins.java.api.tree.Tree; +import org.sonar.plugins.java.api.tree.UnaryExpressionTree; +import org.sonar.plugins.java.api.tree.VariableTree; + +/** + * Marks the local variables and parameters that are never reassigned (JLS 4.12.4), as the compiler does. + * A variable declared without a value stays effectively final after one plain assignment. + */ +final class EffectivelyFinalVariables extends BaseTreeVisitor { + + private final Map assignments = new IdentityHashMap<>(); + private final List variables = new ArrayList<>(); + + private EffectivelyFinalVariables() { + } + + static void mark(JavaTree.CompilationUnitTreeImpl unit) { + EffectivelyFinalVariables visitor = new EffectivelyFinalVariables(); + visitor.scan(unit); + for (VariableTree variable : visitor.variables) { + if (variable.symbol() instanceof JVariableSymbol symbol) { + int[] counts = visitor.assignments.getOrDefault(symbol, new int[2]); + boolean unassigned = variable.initializer() == null && isLocalDeclaration(variable); + symbol.effectivelyFinal(counts[1] == 0 && counts[0] <= (unassigned ? 1 : 0)); + } + } + } + + private static boolean isLocalDeclaration(VariableTree variable) { + Tree parent = variable.parent(); + return parent != null && parent.is(Tree.Kind.BLOCK, Tree.Kind.CASE_GROUP, Tree.Kind.STATIC_INITIALIZER, Tree.Kind.INITIALIZER, Tree.Kind.FOR_STATEMENT); + } + + @Override + public void visitVariable(VariableTree tree) { + if (tree.symbol() instanceof JVariableSymbol symbol && (symbol.isLocalVariable() || symbol.isParameter())) { + variables.add(tree); + } + super.visitVariable(tree); + } + + @Override + public void visitAssignmentExpression(AssignmentExpressionTree tree) { + countAssignment(tree.variable(), tree.is(Tree.Kind.ASSIGNMENT) ? 0 : 1); + super.visitAssignmentExpression(tree); + } + + @Override + public void visitUnaryExpression(UnaryExpressionTree tree) { + if (tree.is(Tree.Kind.PREFIX_INCREMENT, Tree.Kind.PREFIX_DECREMENT, Tree.Kind.POSTFIX_INCREMENT, Tree.Kind.POSTFIX_DECREMENT)) { + countAssignment(tree.expression(), 1); + } + super.visitUnaryExpression(tree); + } + + private void countAssignment(ExpressionTree target, int slot) { + ExpressionTree expression = target; + while (expression instanceof org.sonar.plugins.java.api.tree.ParenthesizedTree parenthesized) { + expression = parenthesized.expression(); + } + if (expression instanceof IdentifierTree identifier) { + assignments.computeIfAbsent(identifier.symbol(), key -> new int[2])[slot]++; + } + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java index 3f15b5f9a6b..8cbde9dbd22 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JMethodSymbol.java @@ -16,186 +16,365 @@ */ package org.sonar.java.model; +import com.google.gson.JsonObject; import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedHashSet; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.Predicate; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ASTUtils; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.Modifier; -import org.sonar.java.annotations.VisibleForTesting; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.tree.ArrayTypeTree; import org.sonar.plugins.java.api.tree.MethodTree; +import org.sonar.plugins.java.api.tree.TypeTree; import org.sonar.plugins.java.api.tree.VariableTree; final class JMethodSymbol extends JSymbol implements Symbol.MethodSymbol { - /** - * Cache for {@link #parameterTypes()}. - */ - private List parameterTypes; + private List parameterTypes = List.of(); + private TypeSymbol returnTypeSymbol = TypeSymbol.UNKNOWN_TYPE; + private boolean varArgs; + private boolean lambda; + private boolean parametrized; + private final String signature; + @Nullable + private Integer returnTypeParameter; + private boolean returnsSelf; + @Nullable + private List parameterVariables; + @Nullable + private List declaredParameters; + private boolean declaredReturnLoaded; + @Nullable + private JsonObject declaredReturn; + private List overridden; + @Nullable + private List thrownTypes; + @Nullable + private String descriptorSignature; - private List parameters; + JMethodSymbol(JSema sema, @Nullable String id, String name) { + super(sema, Kind.METHOD, id, name); + this.signature = id == null ? name : JSema.erase(id); + } - /** - * Cache for {@link #returnType()}. - */ - private TypeSymbol returnType; + void parameterTypes(List parameterTypes) { + this.parameterTypes = List.copyOf(parameterTypes); + } - /** - * Cache for {@link #thrownTypes()}. - */ - private List thrownTypes; + void returnTypeSymbol(TypeSymbol returnTypeSymbol) { + this.returnTypeSymbol = returnTypeSymbol == null ? TypeSymbol.UNKNOWN_TYPE : returnTypeSymbol; + } - /** - * Cache for {@link #overriddenSymbols()}. - */ - private List overriddenSymbols; + void varArgs(boolean varArgs) { + this.varArgs = varArgs; + } - private final String signature; + void lambda(boolean lambda) { + this.lambda = lambda; + } - JMethodSymbol(JSema sema, IMethodBinding methodBinding) { - super(sema, methodBinding); - this.signature = methodBinding().getDeclaringClass().getBinaryName() - + "#" + name() - + ASTUtils.signature(methodBinding().getMethodDeclaration()); + boolean isLambda() { + return lambda; + } + + void parametrized(boolean parametrized) { + this.parametrized = parametrized; + } + + void returnTypeParameter(@Nullable Integer returnTypeParameter) { + this.returnTypeParameter = returnTypeParameter; + } + + @Nullable + Integer returnTypeParameter() { + return returnTypeParameter; + } + + void returnsSelf(boolean returnsSelf) { + this.returnsSelf = returnsSelf; + } + + boolean returnsSelf() { + return returnsSelf; + } + + void parameterVariables(List parameterVariables) { + this.parameterVariables = parameterVariables; + } + + @Nullable + List parameterVariables() { + return parameterVariables; + } + + void declaredParameters(List declaredParameters) { + this.declaredParameters = declaredParameters; + } + + @Nullable + List declaredParameters() { + return declaredParameters; + } + + void declaredReturn(@Nullable JsonObject declaredReturn) { + this.declaredReturnLoaded = true; + this.declaredReturn = declaredReturn; + } + + boolean declaredReturnLoaded() { + return declaredReturnLoaded; + } + + @Nullable + JsonObject declaredReturn() { + return declaredReturn; } - IMethodBinding methodBinding() { - return (IMethodBinding) binding; + /** + * A per-invocation view with substituted parameter and return types. Equality stays on the SemSitter id, + * so the view still denotes the same method as the declaration. + */ + JMethodSymbol withSignature(List parameterTypes, Type returnType) { + JMethodSymbol view = new JMethodSymbol(sema, id, name()); + view.owner(owner()); + view.addModifiers(modifiersCopy()); + view.parameterTypes = List.copyOf(parameterTypes); + view.parameterVariables = parameterVariables; + view.declaredParameters = declaredParameters; + view.declaredReturnLoaded = declaredReturnLoaded; + view.declaredReturn = declaredReturn; + view.returnTypeSymbol = returnType.symbol(); + view.varArgs = varArgs; + view.lambda = lambda; + view.parametrized = parametrized || !returnType.equals(returnTypeSymbol.type()) || !view.parameterTypes.equals(this.parameterTypes); + view.returnTypeParameter = returnTypeParameter; + view.returnsSelf = returnsSelf; + view.thrownTypes = thrownTypes; + view.unknown(isUnknown()); + view.deprecated(isDeprecated()); + view.descriptorSignature = signature(); + return view; } - public boolean isLambda() { - return methodBinding().getDeclaringMember() != null; + private List modifiersCopy() { + List copy = new ArrayList<>(); + if (isPublic()) { + copy.add("public"); + } + if (isPrivate()) { + copy.add("private"); + } + if (isProtected()) { + copy.add("protected"); + } + if (isStatic()) { + copy.add("static"); + } + if (isFinal()) { + copy.add("final"); + } + if (hasModifier("abstract")) { + copy.add("abstract"); + } + if (hasModifier("default")) { + copy.add("default"); + } + if (hasModifier("native")) { + copy.add("native"); + } + if (hasModifier("synchronized")) { + copy.add("synchronized"); + } + return copy; } @Override public List parameterTypes() { - if (parameterTypes == null) { - parameterTypes = sema.types(methodBinding().getParameterTypes()); - } return parameterTypes; } @Override public List declarationParameters() { - if (parameters == null) { - MethodTree declaration = declaration(); - if (declaration != null && !isCompactConstructor(declaration)) { - parameters = declaration.parameters().stream().map(VariableTree::symbol).toList(); - } else { - parameters = new ArrayList<>(); - IMethodBinding methodBinding = methodBinding(); - ITypeBinding[] parameterTypeBindings = methodBinding.getParameterTypes(); - for (int i = 0; i < parameterTypeBindings.length; i++) { - parameters.add(new JVariableSymbol.ParameterPlaceholderSymbol(i, sema, methodBinding.getMethodDeclaration(), parameterTypeBindings[i])); - } - } + if (declaration() instanceof MethodTree method && method.closeParenToken() != null) { + return method.parameters().stream().map(parameter -> (Symbol) parameter.symbol()).toList(); } - return parameters; - } - - private static boolean isCompactConstructor(MethodTree methodTree) { - return methodTree.closeParenToken() == null; + List placeholders = new ArrayList<>(); + for (int i = 0; i < parameterTypes.size(); i++) { + placeholders.add(new JVariableSymbol.ParameterPlaceholderSymbol(i, this, parameterTypes.get(i))); + } + return placeholders; } - /** - * @since 6.0 returns void type for constructors instead of {@code null} - */ @Override public TypeSymbol returnType() { - if (returnType == null) { - ITypeBinding methodBindingReturnType = methodBinding().getReturnType(); - // In rare circumstances, when the semantic information is incomplete, methodBindingReturnType can be null. - if (methodBindingReturnType == null) { - return TypeSymbol.UNKNOWN_TYPE; - } - this.returnType = sema.typeSymbol(methodBindingReturnType); - } - return returnType; + return returnTypeSymbol; + } + + void thrownTypes(List thrownTypes) { + this.thrownTypes = List.copyOf(thrownTypes); } @Override public List thrownTypes() { - if (thrownTypes == null) { - thrownTypes = sema.types(methodBinding().getExceptionTypes()); + if (thrownTypes != null) { + return thrownTypes; + } + MethodTree method = declaration(); + if (method == null) { + return Collections.emptyList(); } - return thrownTypes; + return method.throwsClauses().stream().map(TypeTree::symbolType).toList(); } @Override public List overriddenSymbols() { - if (overriddenSymbols == null) { - overriddenSymbols = findOverriddenSymbols(); + if (overridden == null) { + overridden = findOverridden(); } - return overriddenSymbols; + return overridden; } - private List findOverriddenSymbols() { - Set results = new LinkedHashSet<>(); - IMethodBinding methodBinding = methodBinding(); - findOverridesInParentTypes(results, methodBinding::overrides, methodBinding.getDeclaringClass()); - return new ArrayList<>(results); + private List findOverridden() { + if (!(owner() instanceof JTypeSymbol typeSymbol) || isUnknown() || isStatic() || isPrivate() || "".equals(name())) { + return List.of(); + } + List found = new ArrayList<>(); + Set visited = new HashSet<>(); + if (typeSymbol.isInterface()) { + match(found, sema.typeSymbol("java.lang.Object")); + } + walk(found, visited, typeSymbol.superClass()); + for (Type iface : typeSymbol.interfaces()) { + walk(found, visited, iface); + } + return found; } - @VisibleForTesting - void findOverridesInParentTypes(Collection accumulator, Predicate overridesCondition, ITypeBinding type) { - if (type.isInterface()) { - // check Object for interfaces forcing overrides from Object - findOverridesInTypes(accumulator, overridesCondition, sema.resolveType("java.lang.Object")); - } else if (!"java.lang.Object".equals(type.getQualifiedName())) { - findOverridesInTypes(accumulator, overridesCondition, type.getSuperclass()); + private void walk(List found, Set visited, @Nullable Type type) { + if (type == null || type.isUnknown() || !visited.add(type.fullyQualifiedName())) { + return; + } + if (type.symbol() instanceof JTypeSymbol parent) { + match(found, parent); + walk(found, visited, parent.superClass()); + for (Type iface : parent.interfaces()) { + walk(found, visited, iface); + } } - findOverridesInTypes(accumulator, overridesCondition, type.getInterfaces()); } - private void findOverridesInTypes(Collection accumulator, Predicate overridesCondition, ITypeBinding... types) { - for (ITypeBinding type : types) { - if (type != null) { - for (IMethodBinding declaredMethod : type.getDeclaredMethods()) { - if (overridesCondition.test(declaredMethod)) { - accumulator.add(sema.methodSymbol(declaredMethod)); - break; - } - } - findOverridesInParentTypes(accumulator, overridesCondition, type); + private void match(List found, TypeSymbol parent) { + for (Symbol member : parent.memberSymbols()) { + if (member.isMethodSymbol() && name().equals(member.name()) && sameSignature((MethodSymbol) member)) { + found.add((MethodSymbol) member); + return; + } + } + } + + private boolean sameSignature(MethodSymbol other) { + List theirs = other.parameterTypes(); + if (parameterTypes.size() != theirs.size()) { + return false; + } + for (int i = 0; i < parameterTypes.size(); i++) { + if (!parameterTypes.get(i).erasure().is(theirs.get(i).erasure().fullyQualifiedName())) { + return false; } } + return true; } + /** + * The ECJ form, {@code binaryOwner#name(descriptors)returnDescriptor}, from the erased parameter and return + * types. Falls back to the SemSitter id when the owner is not a type. + */ @Override public String signature() { - return signature; + if (descriptorSignature == null) { + descriptorSignature = descriptorSignature(); + } + return descriptorSignature; + } + + private String descriptorSignature() { + Symbol owner = owner(); + if (!(owner instanceof TypeSymbol ownerType) || isUnknown()) { + return signature; + } + StringBuilder out = new StringBuilder(ownerType.type().fullyQualifiedName()).append('#').append(name()).append('('); + parameterTypes.forEach(parameter -> out.append(descriptor(parameter))); + out.append(')'); + if ("".equals(name())) { + out.append('V'); + } else { + out.append(descriptor(returnTypeSymbol.type())); + } + return out.toString(); + } + + private static String descriptor(@Nullable Type type) { + if (type == null || type.isUnknown()) { + return "Ljava/lang/Object;"; + } + Type erased = type.erasure(); + if (erased.isArray()) { + return "[" + descriptor(((Type.ArrayType) erased).elementType()); + } + return switch (erased.fullyQualifiedName()) { + case "byte" -> "B"; + case "char" -> "C"; + case "double" -> "D"; + case "float" -> "F"; + case "int" -> "I"; + case "long" -> "J"; + case "short" -> "S"; + case "boolean" -> "Z"; + case "void" -> "V"; + default -> "L" + erased.fullyQualifiedName().replace('.', '/') + ";"; + }; } @Override public boolean isOverridable() { - return !isUnknown() && !(isPrivate() || isStatic() || isFinal() || owner().isFinal()); + Symbol owner = owner(); + return !isUnknown() && !(isPrivate() || isStatic() || isFinal() || (owner != null && owner.isFinal())); } @Override public boolean isParametrizedMethod() { - return !isUnknown() && (methodBinding().isParameterizedMethod() || methodBinding().isGenericMethod()); + return !isUnknown() && parametrized; } @Override public boolean isDefaultMethod() { - return !isUnknown() && Modifier.isDefault(binding.getModifiers()); + return !isUnknown() && hasModifier("default"); } @Override public boolean isSynchronizedMethod() { - return !isUnknown() && Modifier.isSynchronized(binding.getModifiers()); + return !isUnknown() && hasModifier("synchronized"); } @Override public boolean isVarArgsMethod() { - return !isUnknown() && methodBinding().isVarargs(); + if (isUnknown()) { + return false; + } + if (varArgs) { + return true; + } + if (declaration() instanceof MethodTree method) { + List parameters = method.parameters(); + if (parameters.isEmpty()) { + return false; + } + return parameters.get(parameters.size() - 1).type() instanceof ArrayTypeTree arrayType + && arrayType.ellipsisToken() != null; + } + return !parameterTypes.isEmpty() && parameterTypes.get(parameterTypes.size() - 1).isArray(); } @Nullable @@ -206,12 +385,6 @@ public MethodTree declaration() { @Override public boolean isNativeMethod() { - return !isUnknown() && Modifier.isNative(binding.getModifiers()); - } - - /** This is for debugging and doesn't follow a guaranteed format. */ - @Override - public String toString() { - return binding.getKey(); + return !isUnknown() && hasModifier("native"); } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JPackageSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JPackageSymbol.java index 1f7680c909e..bd313da3d9d 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JPackageSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JPackageSymbol.java @@ -16,12 +16,14 @@ */ package org.sonar.java.model; -import org.eclipse.jdt.core.dom.IPackageBinding; - final class JPackageSymbol extends JSymbol { - JPackageSymbol(JSema sema, IPackageBinding binding) { - super(sema, binding); + JPackageSymbol(JSema sema, String name) { + super(sema, Kind.PACKAGE, name, name); } + @Override + public boolean isUnknown() { + return false; + } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JParser.java b/java-frontend/src/main/java/org/sonar/java/model/JParser.java index 911c60bc7d6..21c7ccb338c 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JParser.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JParser.java @@ -16,2913 +16,136 @@ */ package org.sonar.java.model; +import com.google.gson.JsonObject; import com.sonar.sslr.api.RecognitionException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Deque; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; +import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Comparator; import java.util.List; -import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.IntStream; import java.util.stream.Stream; import javax.annotation.Nullable; -import org.eclipse.jdt.core.compiler.IProblem; -import org.eclipse.jdt.core.compiler.InvalidInputException; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTNode; -import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.ASTUtils; -import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; -import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration; -import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration; -import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; -import org.eclipse.jdt.core.dom.ArrayAccess; -import org.eclipse.jdt.core.dom.ArrayCreation; -import org.eclipse.jdt.core.dom.ArrayInitializer; -import org.eclipse.jdt.core.dom.ArrayType; -import org.eclipse.jdt.core.dom.AssertStatement; -import org.eclipse.jdt.core.dom.Assignment; -import org.eclipse.jdt.core.dom.Block; -import org.eclipse.jdt.core.dom.BodyDeclaration; -import org.eclipse.jdt.core.dom.BooleanLiteral; -import org.eclipse.jdt.core.dom.BreakStatement; -import org.eclipse.jdt.core.dom.CastExpression; -import org.eclipse.jdt.core.dom.CatchClause; -import org.eclipse.jdt.core.dom.CharacterLiteral; -import org.eclipse.jdt.core.dom.ClassInstanceCreation; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.ConditionalExpression; -import org.eclipse.jdt.core.dom.ConstructorInvocation; -import org.eclipse.jdt.core.dom.ContinueStatement; -import org.eclipse.jdt.core.dom.CreationReference; -import org.eclipse.jdt.core.dom.Dimension; -import org.eclipse.jdt.core.dom.DoStatement; -import org.eclipse.jdt.core.dom.EmptyStatement; -import org.eclipse.jdt.core.dom.EnhancedForStatement; -import org.eclipse.jdt.core.dom.EnumConstantDeclaration; -import org.eclipse.jdt.core.dom.EnumDeclaration; -import org.eclipse.jdt.core.dom.ExportsDirective; -import org.eclipse.jdt.core.dom.Expression; -import org.eclipse.jdt.core.dom.ExpressionMethodReference; -import org.eclipse.jdt.core.dom.ExpressionStatement; -import org.eclipse.jdt.core.dom.FieldAccess; -import org.eclipse.jdt.core.dom.FieldDeclaration; -import org.eclipse.jdt.core.dom.ForStatement; -import org.eclipse.jdt.core.dom.GuardedPattern; -import org.eclipse.jdt.core.dom.IBinding; -import org.eclipse.jdt.core.dom.IExtendedModifier; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.IVariableBinding; -import org.eclipse.jdt.core.dom.IfStatement; -import org.eclipse.jdt.core.dom.ImportDeclaration; -import org.eclipse.jdt.core.dom.InfixExpression; -import org.eclipse.jdt.core.dom.Initializer; -import org.eclipse.jdt.core.dom.InstanceofExpression; -import org.eclipse.jdt.core.dom.IntersectionType; -import org.eclipse.jdt.core.dom.LabeledStatement; -import org.eclipse.jdt.core.dom.LambdaExpression; -import org.eclipse.jdt.core.dom.MemberValuePair; -import org.eclipse.jdt.core.dom.MethodDeclaration; -import org.eclipse.jdt.core.dom.MethodInvocation; -import org.eclipse.jdt.core.dom.ModuleDeclaration; -import org.eclipse.jdt.core.dom.ModuleDirective; -import org.eclipse.jdt.core.dom.ModuleModifier; -import org.eclipse.jdt.core.dom.Name; -import org.eclipse.jdt.core.dom.NameQualifiedType; -import org.eclipse.jdt.core.dom.NormalAnnotation; -import org.eclipse.jdt.core.dom.NullLiteral; -import org.eclipse.jdt.core.dom.NumberLiteral; -import org.eclipse.jdt.core.dom.OpensDirective; -import org.eclipse.jdt.core.dom.ParameterizedType; -import org.eclipse.jdt.core.dom.ParenthesizedExpression; -import org.eclipse.jdt.core.dom.Pattern; -import org.eclipse.jdt.core.dom.PatternInstanceofExpression; -import org.eclipse.jdt.core.dom.PostfixExpression; -import org.eclipse.jdt.core.dom.PrefixExpression; -import org.eclipse.jdt.core.dom.PrimitiveType; -import org.eclipse.jdt.core.dom.ProvidesDirective; -import org.eclipse.jdt.core.dom.QualifiedName; -import org.eclipse.jdt.core.dom.QualifiedType; -import org.eclipse.jdt.core.dom.RecordDeclaration; -import org.eclipse.jdt.core.dom.RecordPattern; -import org.eclipse.jdt.core.dom.RequiresDirective; -import org.eclipse.jdt.core.dom.ReturnStatement; -import org.eclipse.jdt.core.dom.SimpleName; -import org.eclipse.jdt.core.dom.SimpleType; -import org.eclipse.jdt.core.dom.SingleMemberAnnotation; -import org.eclipse.jdt.core.dom.SingleVariableDeclaration; -import org.eclipse.jdt.core.dom.Statement; -import org.eclipse.jdt.core.dom.StringLiteral; -import org.eclipse.jdt.core.dom.SuperConstructorInvocation; -import org.eclipse.jdt.core.dom.SuperFieldAccess; -import org.eclipse.jdt.core.dom.SuperMethodInvocation; -import org.eclipse.jdt.core.dom.SuperMethodReference; -import org.eclipse.jdt.core.dom.SwitchCase; -import org.eclipse.jdt.core.dom.SwitchExpression; -import org.eclipse.jdt.core.dom.SwitchStatement; -import org.eclipse.jdt.core.dom.SynchronizedStatement; -import org.eclipse.jdt.core.dom.TextBlock; -import org.eclipse.jdt.core.dom.ThisExpression; -import org.eclipse.jdt.core.dom.ThrowStatement; -import org.eclipse.jdt.core.dom.TryStatement; -import org.eclipse.jdt.core.dom.Type; -import org.eclipse.jdt.core.dom.TypeDeclaration; -import org.eclipse.jdt.core.dom.TypeDeclarationStatement; -import org.eclipse.jdt.core.dom.TypeLiteral; -import org.eclipse.jdt.core.dom.TypeMethodReference; -import org.eclipse.jdt.core.dom.TypeParameter; -import org.eclipse.jdt.core.dom.TypePattern; -import org.eclipse.jdt.core.dom.UnionType; -import org.eclipse.jdt.core.dom.UsesDirective; -import org.eclipse.jdt.core.dom.VariableDeclaration; -import org.eclipse.jdt.core.dom.VariableDeclarationExpression; -import org.eclipse.jdt.core.dom.VariableDeclarationFragment; -import org.eclipse.jdt.core.dom.VariableDeclarationStatement; -import org.eclipse.jdt.core.dom.WhileStatement; -import org.eclipse.jdt.core.dom.WildcardType; -import org.eclipse.jdt.core.dom.YieldStatement; -import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; -import org.eclipse.jdt.internal.compiler.parser.Scanner; -import org.eclipse.jdt.internal.compiler.parser.TerminalToken; -import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions; -import org.eclipse.jdt.internal.formatter.Token; -import org.eclipse.jdt.internal.formatter.TokenManager; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.java.annotations.VisibleForTesting; -import org.sonar.java.ast.parser.ArgumentListTreeImpl; -import org.sonar.java.ast.parser.FormalParametersListTreeImpl; -import org.sonar.java.ast.parser.InitializerListTreeImpl; -import org.sonar.java.ast.parser.ModuleNameListTreeImpl; -import org.sonar.java.ast.parser.ModuleNameTreeImpl; -import org.sonar.java.ast.parser.QualifiedIdentifierListTreeImpl; -import org.sonar.java.ast.parser.ResourceListTreeImpl; -import org.sonar.java.ast.parser.StatementListTreeImpl; -import org.sonar.java.ast.parser.TypeParameterListTreeImpl; -import org.sonar.java.model.declaration.AnnotationTreeImpl; -import org.sonar.java.model.declaration.ClassTreeImpl; -import org.sonar.java.model.declaration.EnumConstantTreeImpl; -import org.sonar.java.model.declaration.ExportsDirectiveTreeImpl; -import org.sonar.java.model.declaration.MethodTreeImpl; -import org.sonar.java.model.declaration.ModifierKeywordTreeImpl; -import org.sonar.java.model.declaration.ModifiersTreeImpl; -import org.sonar.java.model.declaration.ModuleDeclarationTreeImpl; -import org.sonar.java.model.declaration.OpensDirectiveTreeImpl; -import org.sonar.java.model.declaration.ProvidesDirectiveTreeImpl; -import org.sonar.java.model.declaration.RequiresDirectiveTreeImpl; -import org.sonar.java.model.declaration.UsesDirectiveTreeImpl; -import org.sonar.java.model.declaration.VariableTreeImpl; -import org.sonar.java.model.expression.ArrayAccessExpressionTreeImpl; -import org.sonar.java.model.expression.AssignmentExpressionTreeImpl; -import org.sonar.java.model.expression.BinaryExpressionTreeImpl; -import org.sonar.java.model.expression.ConditionalExpressionTreeImpl; -import org.sonar.java.model.expression.IdentifierTreeImpl; -import org.sonar.java.model.expression.InstanceOfTreeImpl; -import org.sonar.java.model.expression.InternalPostfixUnaryExpression; -import org.sonar.java.model.expression.InternalPrefixUnaryExpression; -import org.sonar.java.model.expression.LambdaExpressionTreeImpl; -import org.sonar.java.model.expression.LiteralTreeImpl; -import org.sonar.java.model.expression.MemberSelectExpressionTreeImpl; -import org.sonar.java.model.expression.MethodInvocationTreeImpl; -import org.sonar.java.model.expression.MethodReferenceTreeImpl; -import org.sonar.java.model.expression.NewArrayTreeImpl; -import org.sonar.java.model.expression.NewClassTreeImpl; -import org.sonar.java.model.expression.ParenthesizedTreeImpl; -import org.sonar.java.model.expression.TypeArgumentListTreeImpl; -import org.sonar.java.model.expression.TypeCastExpressionTreeImpl; -import org.sonar.java.model.expression.VarTypeTreeImpl; -import org.sonar.java.model.pattern.DefaultPatternTreeImpl; -import org.sonar.java.model.pattern.GuardedPatternTreeImpl; -import org.sonar.java.model.pattern.NullPatternTreeImpl; -import org.sonar.java.model.pattern.RecordPatternTreeImpl; -import org.sonar.java.model.pattern.TypePatternTreeImpl; -import org.sonar.java.model.statement.AssertStatementTreeImpl; -import org.sonar.java.model.statement.BlockTreeImpl; -import org.sonar.java.model.statement.BreakStatementTreeImpl; -import org.sonar.java.model.statement.CaseGroupTreeImpl; -import org.sonar.java.model.statement.CaseLabelTreeImpl; -import org.sonar.java.model.statement.CatchTreeImpl; -import org.sonar.java.model.statement.ContinueStatementTreeImpl; -import org.sonar.java.model.statement.DoWhileStatementTreeImpl; -import org.sonar.java.model.statement.EmptyStatementTreeImpl; -import org.sonar.java.model.statement.ExpressionStatementTreeImpl; -import org.sonar.java.model.statement.ForEachStatementImpl; -import org.sonar.java.model.statement.ForStatementTreeImpl; -import org.sonar.java.model.statement.IfStatementTreeImpl; -import org.sonar.java.model.statement.LabeledStatementTreeImpl; -import org.sonar.java.model.statement.ReturnStatementTreeImpl; -import org.sonar.java.model.statement.StaticInitializerTreeImpl; -import org.sonar.java.model.statement.SwitchExpressionTreeImpl; -import org.sonar.java.model.statement.SwitchStatementTreeImpl; -import org.sonar.java.model.statement.SynchronizedStatementTreeImpl; -import org.sonar.java.model.statement.ThrowStatementTreeImpl; -import org.sonar.java.model.statement.TryStatementTreeImpl; -import org.sonar.java.model.statement.WhileStatementTreeImpl; -import org.sonar.java.model.statement.YieldStatementTreeImpl; -import org.sonar.plugins.java.api.tree.AnnotationTree; -import org.sonar.plugins.java.api.tree.ArrayDimensionTree; -import org.sonar.plugins.java.api.tree.ArrayTypeTree; -import org.sonar.plugins.java.api.tree.CatchTree; -import org.sonar.plugins.java.api.tree.ExpressionTree; -import org.sonar.plugins.java.api.tree.IdentifierTree; -import org.sonar.plugins.java.api.tree.ImportClauseTree; -import org.sonar.plugins.java.api.tree.InferedTypeTree; -import org.sonar.plugins.java.api.tree.ListTree; -import org.sonar.plugins.java.api.tree.Modifier; -import org.sonar.plugins.java.api.tree.ModifierTree; -import org.sonar.plugins.java.api.tree.ModuleDeclarationTree; -import org.sonar.plugins.java.api.tree.ModuleDirectiveTree; -import org.sonar.plugins.java.api.tree.PackageDeclarationTree; -import org.sonar.plugins.java.api.tree.PatternTree; -import org.sonar.plugins.java.api.tree.StatementTree; -import org.sonar.plugins.java.api.tree.SyntaxToken; -import org.sonar.plugins.java.api.tree.SyntaxTrivia; -import org.sonar.plugins.java.api.tree.SyntaxTrivia.CommentKind; -import org.sonar.plugins.java.api.tree.Tree; -import org.sonar.plugins.java.api.tree.TypeParameterTree; -import org.sonar.plugins.java.api.tree.TypeTree; -import org.sonar.plugins.java.api.tree.VariableTree; +import org.sonar.java.semsitter.BridgeExtractor; +import org.sonar.java.semsitter.PlatformDetection; +import org.sonar.java.semsitter.SemsitterBridge; -@SuppressWarnings({"rawtypes", "unchecked"}) -public class JParser { +public final class JParser { - private static final Logger LOG = LoggerFactory.getLogger(JParser.class); - - private static final Set WRONGLY_CATEGORIZED_AS_SYNTAX_ERROR = Set.of( - // Accept missing default clause, it may be due to missing semantic information of the switch expression, - // in this case, an enum fully covered with the switch cases will be seen as something that is not an enum - // when it is unknown, and the parser will wrongly consider the missing default clause as a syntax error. - IProblem.SwitchExpressionsYieldMissingDefaultCase, - // Accept missing default clause, it may be due the switch expression being an enum from a wrong dependency. - // In this case, the parser will wrongly consider the missing default clause as a syntax error. - IProblem.SwitchExpressionsYieldMissingEnumConstantCase - ); - - private static final Predicate IS_SYNTAX_ERROR = error -> ((error.getID() & IProblem.Syntax) != 0) && - !WRONGLY_CATEGORIZED_AS_SYNTAX_ERROR.contains(error.getID()); - - private static final Predicate IS_UNDEFINED_TYPE_ERROR = error -> (error.getID() & IProblem.UndefinedType) != 0; + private JParser() { + } /** - * @param unitName see {@link ASTParser#setUnitName(String)} - * @throws RecognitionException in case of syntax errors + * Parses and attributes one compilation unit on its own, against the running JDK only. Batch analysis goes + * through {@link JParserConfig}, which scans every file of the project before translating. */ - public static JavaTree.CompilationUnitTreeImpl parse(ASTParser astParser, String version, String unitName, String source) { - astParser.setUnitName(unitName); - astParser.setSource(source.toCharArray()); - - CompilationUnit astNode; - try { - astNode = (CompilationUnit) astParser.createAST(null); - } catch (Exception e) { - LOG.error("ECJ: Unable to parse file", e); - throw new RecognitionException(-1, "ECJ: Unable to parse file.", e); - } - - return convert(version, unitName, source, astNode); - } - - static JavaTree.CompilationUnitTreeImpl convert(String version, String unitName, String source, CompilationUnit astNode) { - List errors = Stream.of(astNode.getProblems()).filter(IProblem::isError).toList(); - Optional possibleSyntaxError = errors.stream().filter(IS_SYNTAX_ERROR).findFirst(); - LineColumnConverter lineColumnConverter = new LineColumnConverter(source); - if (possibleSyntaxError.isPresent()) { - IProblem syntaxError = possibleSyntaxError.get(); - LineColumnConverter.Pos pos = lineColumnConverter.toPos(syntaxError.getSourceStart()); - String message = String.format("Parse error at line %d column %d: %s", pos.line(), pos.columnOffset() + 1, syntaxError.getMessage()); - // interrupt parsing - throw new RecognitionException(pos.line(), message); - } - - Set undefinedTypes = errors.stream() - .filter(IS_UNDEFINED_TYPE_ERROR) - .map(i -> new JProblem( - i.getMessage(), - (i.getID() & IProblem.PreviewFeatureUsed) == 0 ? JProblem.Type.UNDEFINED_TYPE : JProblem.Type.PREVIEW_FEATURE_USED)) - .collect(Collectors.toSet()); - - JParser converter = new JParser(); - converter.sema = new JSema(astNode.getAST()); - converter.compilationUnit = astNode; - converter.tokenManager = createTokenManager(version, unitName, source); - converter.lineColumnConverter = lineColumnConverter; - - JavaTree.CompilationUnitTreeImpl tree = converter.convertCompilationUnit(astNode); - tree.setSemanticModel(converter.sema, undefinedTypes, converter.sema.getEnvironmentCleaner()); - JWarning.Mapper.warningsFor(astNode, converter.lineColumnConverter).mappedInto(tree); - - ASTUtils.mayTolerateMissingType(astNode.getAST()); - - setParents(tree); - return tree; + public static JavaTree.CompilationUnitTreeImpl parse(String version, String unitName, String source) { + return parse(version, unitName, source, List.of()); } - @VisibleForTesting - static TokenManager createTokenManager(String version, String unitName, String source) { - return new TokenManager(lex(version, unitName, source.toCharArray()), source, new DefaultCodeFormatterOptions(new HashMap<>())); - } - - private static void setParents(Tree node) { - Iterator childrenIterator = iteratorFor(node); - while (childrenIterator.hasNext()) { - Tree child = childrenIterator.next(); - ((JavaTree) child).setParent(node); - setParents(child); - } - } - - private static Iterator iteratorFor(Tree node) { - if (node.kind() == Tree.Kind.INFERED_TYPE || node.kind() == Tree.Kind.TOKEN) { - // getChildren throws exception in this case - return Collections.emptyIterator(); - } - return ((JavaTree) node).getChildren().iterator(); - } - - @VisibleForTesting - static List lex(String version, String unitName, char[] sourceChars) { - List tokens = new ArrayList<>(); - Scanner scanner = new Scanner( - true, - false, - false, - CompilerOptions.versionToJdkLevel(version), - null, - null, - false - ); - scanner.fakeInModule = "module-info.java".equals(unitName); - scanner.setSource(sourceChars); - while (true) { - try { - TerminalToken tokenType = scanner.getNextToken(); - Token token = Token.fromCurrent(scanner, tokenType); - tokens.add(token); - if (tokenType == TerminalToken.TokenNameEOF) { - break; + /** {@link #parse(String, String, String)} with a classpath, as ECJ attributed single sources. */ + public static JavaTree.CompilationUnitTreeImpl parse(String version, String unitName, String source, List classpath) { + Objects.requireNonNull(version); + Objects.requireNonNull(unitName); + Objects.requireNonNull(source); + Path directory = null; + try { + SemsitterBridge bridge = unitBridge(); + directory = privateTempDirectory(); + Path file = directory.resolve(Path.of(unitName.endsWith(".java") ? unitName : (unitName + ".java")).getFileName().toString()); + Files.writeString(file, source, StandardCharsets.UTF_8); + JsonObject document; + synchronized (bridge) { + bridge.analyze(directory, List.of(file), JParserConfig.bridgeClasspath(classpath)); + document = bridge.cst(file.toString()); + } + JSema sema = new JSema(); + sema.ingestDocument(document); + // Types load lazily while rules run, so the bridge outlives this call. + sema.loadTypes(names -> { + try { + return bridge.types(names); + } catch (IOException failure) { + throw new UncheckedIOException(failure); } - } catch (InvalidInputException e) { - throw new IllegalStateException(e); - } - } - return tokens; - } - - private CompilationUnit compilationUnit; - - private TokenManager tokenManager; - private LineColumnConverter lineColumnConverter; - - private JSema sema; - - private final Deque labels = new LinkedList<>(); - - private void declaration(@Nullable IBinding binding, Tree node) { - if (binding == null) { - return; - } - sema.declarations.put(binding, node); - } - - private void usage(@Nullable IBinding binding, IdentifierTree node) { - if (binding == null) { - return; - } - binding = JSema.declarationBinding(binding); - sema.usages.computeIfAbsent(binding, k -> new ArrayList<>()).add(node); - } - - private void usageLabel(@Nullable IdentifierTreeImpl node) { - if (node == null) { - return; - } - labels.stream() - .filter(symbol -> symbol.name().equals(node.name())) - .findFirst() - .ifPresent(labelSymbol -> { - labelSymbol.usages.add(node); - node.labelSymbol = labelSymbol; }); - } - - private int firstTokenIndexAfter(ASTNode e) { - int index = tokenManager.firstIndexAfter(e, ANY_TOKEN); - while (isComment(tokenManager.get(index))) { - index++; - } - return index; - } - - /** - * @param tokenType {@link TerminalToken} - */ - private int nextTokenIndex(int tokenIndex, TerminalToken tokenType) { - assert tokenType != ANY_TOKEN; - do { - tokenIndex += 1; - } while (tokenManager.get(tokenIndex).tokenType != tokenType); - return tokenIndex; - } - - /** - * @param tokenType {@link TerminalToken} - */ - private InternalSyntaxToken firstTokenBefore(ASTNode e, TerminalToken tokenType) { - return createSyntaxToken(tokenManager.firstIndexBefore(e, tokenType)); - } - - /** - * @param tokenType {@link TerminalToken} - */ - private InternalSyntaxToken firstTokenAfter(ASTNode e, TerminalToken tokenType) { - return createSyntaxToken(tokenManager.firstIndexAfter(e, tokenType)); - } - - /** - * @param tokenType {@link TerminalToken} - */ - private InternalSyntaxToken firstTokenIn(ASTNode e, TerminalToken tokenType) { - return createSyntaxToken(tokenManager.firstIndexIn(e, tokenType)); - } - - /** - * @param tokenTypeCandidateA {@link TerminalToken} - * @param tokenTypeCandidateB {@link TerminalToken} - * @return {@link TerminalToken} - */ - @VisibleForTesting - static int firstIndexIn(TokenManager tokenManager, ASTNode e, TerminalToken tokenTypeCandidateA, TerminalToken tokenTypeCandidateB) { - int first = tokenManager.firstIndexIn(e, ANY_TOKEN); - int last = tokenManager.lastIndexIn(e, ANY_TOKEN); - return IntStream.rangeClosed(first, last) - .filter(tokenIndex -> { - Token token = tokenManager.get(tokenIndex); - return token.tokenType == tokenTypeCandidateA || token.tokenType == tokenTypeCandidateB; - }) - .findFirst() - .orElseThrow(() -> new IllegalStateException("Failed to find token " + tokenTypeCandidateA + " or " + tokenTypeCandidateB + - " in the tokens of a " + ASTNode.nodeClassForType(e.getNodeType()).getName())); - } - - /** - * @param tokenType {@link TerminalToken} - */ - private InternalSyntaxToken lastTokenIn(ASTNode e, TerminalToken tokenType) { - return createSyntaxToken(tokenManager.lastIndexIn(e, tokenType)); - } - - private InternalSyntaxToken createSyntaxToken(int tokenIndex) { - Token t = tokenManager.get(tokenIndex); - String value; - boolean isEOF; - if (t.tokenType == TerminalToken.TokenNameEOF) { - isEOF = true; - value = ""; - } else { - isEOF = false; - value = t.toString(tokenManager.getSource()); - } - LineColumnConverter.Pos pos = lineColumnConverter.toPos(t.originalStart); - return new InternalSyntaxToken(pos.line(), pos.columnOffset(), value, collectComments(tokenIndex), isEOF); - } - - private InternalSyntaxToken createSpecialToken(int tokenIndex) { - Token t = tokenManager.get(tokenIndex); - List comments = t.tokenType == TerminalToken.TokenNameGREATER - ? collectComments(tokenIndex) - : Collections.emptyList(); - LineColumnConverter.Pos pos = lineColumnConverter.toPos(t.originalEnd); - return new InternalSyntaxToken(pos.line(), pos.columnOffset(), ">", comments, false); - } - - private List collectComments(int tokenIndex) { - int commentIndex = tokenIndex; - while (commentIndex > 0 && isComment(tokenManager.get(commentIndex - 1))) { - commentIndex--; - } - return IntStream.range(commentIndex, tokenIndex).mapToObj(i -> { - Token t = tokenManager.get(i); - LineColumnConverter.Pos pos = lineColumnConverter.toPos(t.originalStart); - return new InternalSyntaxTrivia(convertTokenTypeToCommentKind(t), - t.toString(tokenManager.getSource()), - pos.line(), - pos.columnOffset() - ); - }) - .toList(); - } - - @VisibleForTesting - static CommentKind convertTokenTypeToCommentKind(Token token) { - return switch (token.tokenType) { - case TokenNameCOMMENT_BLOCK -> CommentKind.BLOCK; - case TokenNameCOMMENT_JAVADOC -> CommentKind.JAVADOC; - case TokenNameCOMMENT_LINE -> CommentKind.LINE; - case TokenNameCOMMENT_MARKDOWN -> CommentKind.MARKDOWN; - default -> throw new IllegalStateException("Unexpected value: " + token.tokenType); - }; - } - - /** - * {@link Token#isComment()} has an issue https://github.com/eclipse-jdt/eclipse.jdt.core/issues/3914 - * it does not support Markdown comments. This method has to be used instead. - */ - @VisibleForTesting - static boolean isComment(Token token) { - return switch (token.tokenType) { - case TokenNameCOMMENT_BLOCK, TokenNameCOMMENT_JAVADOC, TokenNameCOMMENT_LINE, TokenNameCOMMENT_MARKDOWN -> true; - default -> false; - }; - } - - private void addEmptyStatementsToList(int tokenIndex, List list) { - while (true) { - Token token; - do { - tokenIndex++; - token = tokenManager.get(tokenIndex); - } while (isComment(token)); - - if (token.tokenType != TerminalToken.TokenNameSEMICOLON) { - break; - } - list.add(new EmptyStatementTreeImpl(createSyntaxToken(tokenIndex))); - } - } - - private JavaTree.CompilationUnitTreeImpl convertCompilationUnit(CompilationUnit e) { - PackageDeclarationTree packageDeclaration = null; - if (e.getPackage() != null) { - packageDeclaration = new JavaTree.PackageDeclarationTreeImpl( - convertAnnotations(e.getPackage().annotations()), - firstTokenIn(e.getPackage(), TerminalToken.TokenNamepackage), - convertExpression(e.getPackage().getName()), - firstTokenIn(e.getPackage(), TerminalToken.TokenNameSEMICOLON) - ); - } - - List imports = new ArrayList<>(); - for (int i = 0; i < e.imports().size(); i++) { - ImportDeclaration e2 = (ImportDeclaration) e.imports().get(i); - ExpressionTree name = convertImportName(e2.getName()); - - boolean isModuleImport = org.eclipse.jdt.core.dom.Modifier.isModule(e2.getModifiers()); - - // "on demand" means `import pkg.*;` - if (e2.isOnDemand() && !isModuleImport) { - InternalSyntaxToken dotToken = lastTokenIn(e2, TerminalToken.TokenNameDOT); - InternalSyntaxToken identifierToken = lastTokenIn(e2, TerminalToken.TokenNameMULTIPLY); - name = new MemberSelectExpressionTreeImpl(name, dotToken, new IdentifierTreeImpl(identifierToken)); - } - - InternalSyntaxToken staticKeyword = e2.isStatic() ? firstTokenIn(e2, TerminalToken.TokenNamestatic) : null; - InternalSyntaxToken moduleKeyword = isModuleImport ? firstTokenIn(e2, TerminalToken.TokenNamemodule) : null; - - JavaTree.ImportTreeImpl t = new JavaTree.ImportTreeImpl( - firstTokenIn(e2, TerminalToken.TokenNameimport), - staticKeyword, - moduleKeyword, - name, - lastTokenIn(e2, TerminalToken.TokenNameSEMICOLON) - ); - - if (!isModuleImport) { - // There is no method to resolve bindings for a module import. - t.setSymbol(sema.importSymbolOrUnknown(e2.resolveBinding())); - } - - imports.add(t); - - int tokenIndex = tokenManager.lastIndexIn(e2, TerminalToken.TokenNameSEMICOLON); - addEmptyStatementsToList(tokenIndex, imports); - } - - List types = new ArrayList<>(); - for (Object type : e.types()) { - processBodyDeclaration((AbstractTypeDeclaration) type, types); - } - - if (e.imports().isEmpty() && e.types().isEmpty()) { - addEmptyStatementsToList(-1, imports); - } - - return new JavaTree.CompilationUnitTreeImpl( - packageDeclaration, - imports, - types, - convertModuleDeclaration(compilationUnit.getModule()), - firstTokenAfter(e, TerminalToken.TokenNameEOF) - ); - } - - private ExpressionTree convertImportName(Name node) { - switch (node.getNodeType()) { - case ASTNode.SIMPLE_NAME: { - return new IdentifierTreeImpl(firstTokenIn(node, TerminalToken.TokenNameIdentifier)); - } - case ASTNode.QUALIFIED_NAME: { - QualifiedName e = (QualifiedName) node; - return new MemberSelectExpressionTreeImpl( - convertImportName(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - (IdentifierTreeImpl) convertImportName(e.getName())); - } - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); + return SemsitterTranslator.translate(sema, document); + } catch (RecognitionException syntaxError) { + throw syntaxError; + } catch (Exception failure) { + discardUnitBridge(); + throw new RecognitionException(1, "SemSitter: unable to parse " + unitName, failure); + } finally { + deleteQuietly(directory); } } @Nullable - private ModuleDeclarationTree convertModuleDeclaration(@Nullable ModuleDeclaration e) { - if (e == null) { - return null; - } - List moduleDirectives = e.moduleStatements().stream() - .map(o -> convertModuleDirective((ModuleDirective) o)) - .toList(); - return new ModuleDeclarationTreeImpl( - convertAnnotations(e.annotations()), - e.isOpen() ? firstTokenIn(e, TerminalToken.TokenNameopen) : null, - firstTokenBefore(e.getName(), TerminalToken.TokenNamemodule), - convertModuleName(e.getName()), - firstTokenAfter(e.getName(), TerminalToken.TokenNameLBRACE), - moduleDirectives, - lastTokenIn(e, TerminalToken.TokenNameRBRACE) - ); - } - - private ModuleNameTreeImpl convertModuleName(Name node) { - switch (node.getNodeType()) { - case ASTNode.QUALIFIED_NAME: { - QualifiedName e = (QualifiedName) node; - ModuleNameTreeImpl t = convertModuleName(e.getQualifier()); - t.add(new IdentifierTreeImpl(firstTokenIn(e.getName(), TerminalToken.TokenNameIdentifier))); - return t; - } - case ASTNode.SIMPLE_NAME: { - ModuleNameTreeImpl t = ModuleNameTreeImpl.emptyList(); - t.add(new IdentifierTreeImpl(firstTokenIn(node, TerminalToken.TokenNameIdentifier))); - return t; - } - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - } - - private ModuleNameListTreeImpl convertModuleNames(List list) { - ModuleNameListTreeImpl t = ModuleNameListTreeImpl.emptyList(); - for (int i = 0; i < list.size(); i++) { - Name o = (Name) list.get(i); - if (i > 0) { - t.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - t.add(convertModuleName(o)); - } - return t; - } - - private ModuleDirectiveTree convertModuleDirective(ModuleDirective node) { - switch (node.getNodeType()) { - case ASTNode.REQUIRES_DIRECTIVE: { - RequiresDirective e = (RequiresDirective) node; - List modifiers = new ArrayList<>(); - for (Object o : e.modifiers()) { - switch (((ModuleModifier) o).getKeyword().toString()) { - case "static": - modifiers.add(new ModifierKeywordTreeImpl(Modifier.STATIC, firstTokenIn((ASTNode) o, ANY_TOKEN))); - break; - case "transitive": - modifiers.add(new ModifierKeywordTreeImpl(Modifier.TRANSITIVE, firstTokenIn((ASTNode) o, ANY_TOKEN))); - break; - default: - throw new IllegalStateException(); - } - } - return new RequiresDirectiveTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamerequires), - new ModifiersTreeImpl(modifiers), - convertModuleName(e.getName()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - case ASTNode.EXPORTS_DIRECTIVE: { - ExportsDirective e = (ExportsDirective) node; - return new ExportsDirectiveTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameexports), - convertExpression(e.getName()), - e.modules().isEmpty() ? null : firstTokenAfter(e.getName(), TerminalToken.TokenNameto), - convertModuleNames(e.modules()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - case ASTNode.OPENS_DIRECTIVE: { - OpensDirective e = (OpensDirective) node; - return new OpensDirectiveTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameopens), - convertExpression(e.getName()), - e.modules().isEmpty() ? null : firstTokenAfter(e.getName(), TerminalToken.TokenNameto), - convertModuleNames(e.modules()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - case ASTNode.USES_DIRECTIVE: { - UsesDirective e = (UsesDirective) node; - return new UsesDirectiveTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameuses), - (TypeTree) convertExpression(e.getName()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - case ASTNode.PROVIDES_DIRECTIVE: { - ProvidesDirective e = (ProvidesDirective) node; - QualifiedIdentifierListTreeImpl typeNames = QualifiedIdentifierListTreeImpl.emptyList(); - for (int i = 0; i < e.implementations().size(); i++) { - Name o = (Name) e.implementations().get(i); - if (i > 0) { - typeNames.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - typeNames.add((TypeTree) convertExpression(o)); - } - return new ProvidesDirectiveTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameprovides), - (TypeTree) convertExpression(e.getName()), - firstTokenAfter(e.getName(), TerminalToken.TokenNamewith), - typeNames, - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - } - - /** Converts implicitly declared unnamed class at the top level of a compact compilation unit. */ - private ClassTreeImpl convertUnnamedClassDeclaration(AbstractTypeDeclaration e) { - List members = new ArrayList<>(); - - for (Object o : e.bodyDeclarations()) { - processBodyDeclaration((BodyDeclaration) o, members); - } - - ClassTreeImpl t = new ClassTreeImpl(Tree.Kind.IMPLICIT_CLASS, null, members, null); - - ITypeBinding binding = e.resolveBinding(); - t.setSymbol(sema.typeSymbolOrUnknown(binding)); - declaration(binding, t); - - return t; - } - - private ClassTreeImpl convertTypeDeclaration(AbstractTypeDeclaration e) { - if (e.getNodeType() == ASTNode.UNNAMED_CLASS) { - return convertUnnamedClassDeclaration(e); - } - - List members = new ArrayList<>(); - - int leftBraceTokenIndex = findLeftBraceTokenIndex(e); - addEmptyStatementsToList(leftBraceTokenIndex, members); - - for (Object o : e.bodyDeclarations()) { - processBodyDeclaration((BodyDeclaration) o, members); - } - - ModifiersTreeImpl modifiers = convertModifiers(e.modifiers()); - IdentifierTreeImpl name = createSimpleName(e.getName()); - - InternalSyntaxToken openBraceToken = createSyntaxToken(leftBraceTokenIndex); - InternalSyntaxToken closeBraceToken = lastTokenIn(e, TerminalToken.TokenNameRBRACE); - - final ClassTreeImpl t; - switch (e.getNodeType()) { - case ASTNode.TYPE_DECLARATION: - t = convertTypeDeclaration((TypeDeclaration) e, modifiers, name, openBraceToken, members, closeBraceToken); - break; - case ASTNode.ENUM_DECLARATION: - t = convertEnumDeclaration((EnumDeclaration) e, modifiers, name, openBraceToken, members, closeBraceToken); - break; - case ASTNode.RECORD_DECLARATION: - t = convertRecordDeclaration((RecordDeclaration) e, modifiers, name, openBraceToken, members, closeBraceToken); - break; - case ASTNode.ANNOTATION_TYPE_DECLARATION: - t = convertAnnotationTypeDeclaration((AnnotationTypeDeclaration) e, modifiers, name, openBraceToken, members, closeBraceToken); - break; - default: - throw new IllegalStateException(ASTNode.nodeClassForType(e.getNodeType()).toString()); - } - - // no-op for annotation-types - completeSuperInterfaces(e, t); - - ITypeBinding binding = e.resolveBinding(); - t.setSymbol(sema.typeSymbolOrUnknown(binding)); - declaration(binding, t); - - return t; - } - - private ClassTreeImpl convertTypeDeclaration(TypeDeclaration e, ModifiersTreeImpl modifiers, IdentifierTreeImpl name, - InternalSyntaxToken openBraceToken, List members, InternalSyntaxToken closeBraceToken) { - InternalSyntaxToken declarationKeyword = firstTokenBefore(e.getName(), e.isInterface() ? TerminalToken.TokenNameinterface : TerminalToken.TokenNameclass); - ClassTreeImpl t = new ClassTreeImpl(e.isInterface() ? Tree.Kind.INTERFACE : Tree.Kind.CLASS, openBraceToken, members, closeBraceToken) - .complete(modifiers, declarationKeyword, name) - .completeTypeParameters(convertTypeParameters(e.typeParameters())); - - if (!e.permittedTypes().isEmpty()) { - List permittedTypes = e.permittedTypes(); - InternalSyntaxToken permitsKeyword = firstTokenBefore((Type) permittedTypes.get(0), TerminalToken.TokenNameRestrictedIdentifierpermits); - QualifiedIdentifierListTreeImpl classPermittedTypes = QualifiedIdentifierListTreeImpl.emptyList(); - - convertSeparatedTypeList(permittedTypes, classPermittedTypes); - t.completePermittedTypes(permitsKeyword, classPermittedTypes); - } - - if (!e.isInterface() && e.getSuperclassType() != null) { - Type superclassType = e.getSuperclassType(); - t.completeSuperclass(firstTokenBefore(superclassType, TerminalToken.TokenNameextends), convertType(superclassType)); - } - return t; - } - - private ClassTreeImpl convertEnumDeclaration(EnumDeclaration e, ModifiersTreeImpl modifiers, IdentifierTreeImpl name, - InternalSyntaxToken openBraceToken, List members, InternalSyntaxToken closeBraceToken) { - List enumConstants = ((List) e.enumConstants()).stream() - .map(EnumConstantDeclaration.class::cast) - .map(this::processEnumConstantDeclaration) - .toList(); - members.addAll(0, enumConstants); - - InternalSyntaxToken declarationKeyword = firstTokenBefore(e.getName(), TerminalToken.TokenNameenum); - return new ClassTreeImpl(Tree.Kind.ENUM, openBraceToken, members, closeBraceToken) - .complete(modifiers, declarationKeyword, name); - } - - private ClassTreeImpl convertAnnotationTypeDeclaration(AnnotationTypeDeclaration e, ModifiersTreeImpl modifiers, IdentifierTreeImpl name, - InternalSyntaxToken openBraceToken, List members, InternalSyntaxToken closeBraceToken) { - InternalSyntaxToken declarationKeyword = firstTokenBefore(e.getName(), TerminalToken.TokenNameinterface); - return new ClassTreeImpl(Tree.Kind.ANNOTATION_TYPE, openBraceToken, members, closeBraceToken) - .complete(modifiers, declarationKeyword, name) - .completeAtToken(firstTokenBefore(e.getName(), TerminalToken.TokenNameAT)); - } - - private ClassTreeImpl convertRecordDeclaration(RecordDeclaration e, ModifiersTreeImpl modifiers, IdentifierTreeImpl name, - InternalSyntaxToken openBraceToken, List members, InternalSyntaxToken closeBraceToken) { - InternalSyntaxToken declarationKeyword = firstTokenBefore(e.getName(), TerminalToken.TokenNameRestrictedIdentifierrecord); - List recordComponents = e.recordComponents(); - InternalSyntaxToken openParen = firstTokenAfter(e.getName(), TerminalToken.TokenNameLPAREN); - InternalSyntaxToken closeParen = firstTokenAfter( - recordComponents.isEmpty() ? e.getName() : (ASTNode) recordComponents.get(recordComponents.size() - 1), - TerminalToken.TokenNameRPAREN); - return new ClassTreeImpl(Tree.Kind.RECORD, openBraceToken, members, closeBraceToken) - .complete(modifiers, declarationKeyword, name) - .completeTypeParameters(convertTypeParameters(e.typeParameters())) - .completeRecordComponents(openParen, convertRecordComponents(e), closeParen); - } - - private List convertRecordComponents(RecordDeclaration e) { - List recordComponents = new ArrayList<>(); - - for (int i = 0; i < e.recordComponents().size(); i++) { - SingleVariableDeclaration o = (SingleVariableDeclaration) e.recordComponents().get(i); - VariableTreeImpl recordComponent = convertVariable(o); - if (i < e.recordComponents().size() - 1) { - recordComponent.setEndToken(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } - recordComponents.add(recordComponent); - } - return recordComponents; - } - - private int findLeftBraceTokenIndex(AbstractTypeDeclaration e) { - // TODO try to simplify, note that type annotations can contain LBRACE - if (e.getNodeType() == ASTNode.ENUM_DECLARATION) { - EnumDeclaration enumDeclaration = (EnumDeclaration) e; - if (!enumDeclaration.enumConstants().isEmpty()) { - return tokenManager.firstIndexBefore((ASTNode) enumDeclaration.enumConstants().get(0), TerminalToken.TokenNameLBRACE); - } - if (!enumDeclaration.bodyDeclarations().isEmpty()) { - return tokenManager.firstIndexBefore((ASTNode) e.bodyDeclarations().get(0), TerminalToken.TokenNameLBRACE); - } - return tokenManager.lastIndexIn(e, TerminalToken.TokenNameLBRACE); - } - if (!e.bodyDeclarations().isEmpty()) { - // for records, bodyDeclarations may not be in the order encountered in file, for classes they are - List bodyDeclarations = e.bodyDeclarations(); - BodyDeclaration firstDeclaration = bodyDeclarations.get(0); - for (int i = 1; i < bodyDeclarations.size(); i++) { - BodyDeclaration declaration = bodyDeclarations.get(i); - if (firstDeclaration.getStartPosition() > declaration.getStartPosition()) { - firstDeclaration = declaration; - } - } - return tokenManager.firstIndexBefore(firstDeclaration, TerminalToken.TokenNameLBRACE); - } - return tokenManager.lastIndexIn(e, TerminalToken.TokenNameLBRACE); - } - - private void completeSuperInterfaces(AbstractTypeDeclaration e, ClassTreeImpl t) { - List superInterfaces = superInterfaceTypes(e); - if (!superInterfaces.isEmpty()) { - QualifiedIdentifierListTreeImpl interfaces = QualifiedIdentifierListTreeImpl.emptyList(); - convertSeparatedTypeList(superInterfaces, interfaces); - - ASTNode firstInterface = (ASTNode) superInterfaces.get(0); - InternalSyntaxToken keyword = firstTokenBefore(firstInterface, t.is(Tree.Kind.INTERFACE) ? TerminalToken.TokenNameextends : TerminalToken.TokenNameimplements); - t.completeInterfaces(keyword, interfaces); - } - } - - private static List superInterfaceTypes(AbstractTypeDeclaration e) { - switch (e.getNodeType()) { - case ASTNode.TYPE_DECLARATION: - return ((TypeDeclaration) e).superInterfaceTypes(); - case ASTNode.ENUM_DECLARATION: - return ((EnumDeclaration) e).superInterfaceTypes(); - case ASTNode.RECORD_DECLARATION: - return ((RecordDeclaration) e).superInterfaceTypes(); - case ASTNode.ANNOTATION_TYPE_DECLARATION, ASTNode.UNNAMED_CLASS: - return Collections.emptyList(); - default: - throw new IllegalStateException(ASTNode.nodeClassForType(e.getNodeType()).toString()); - } - } - - private void convertSeparatedTypeList(List source, ListTree target) { - for (int i = 0; i < source.size(); i++) { - Type o = source.get(i); - T tree = (T) convertType(o); - if (i > 0) { - target.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - target.add(tree); - } - } + private static SemsitterBridge unitBridge; - private EnumConstantTreeImpl processEnumConstantDeclaration(EnumConstantDeclaration e) { - final int openParTokenIndex = firstTokenIndexAfter(e.getName()); - final InternalSyntaxToken openParToken; - final InternalSyntaxToken closeParToken; - if (tokenManager.get(openParTokenIndex).tokenType == TerminalToken.TokenNameLPAREN) { - openParToken = createSyntaxToken(openParTokenIndex); - closeParToken = firstTokenAfter( - e.arguments().isEmpty() - ? e.getName() - : (ASTNode) e.arguments().get(e.arguments().size() - 1), - TerminalToken.TokenNameRPAREN); - } else { - openParToken = null; - closeParToken = null; + /** One bridge per JVM for single-source parses, stopped at shutdown. */ + private static synchronized SemsitterBridge unitBridge() throws IOException { + if (unitBridge == null) { + SemsitterBridge started = SemsitterBridge.start(bridgePath()); + Runtime.getRuntime().addShutdownHook(new Thread(started::close)); + unitBridge = started; } - - final ArgumentListTreeImpl arguments = convertArguments(openParToken, e.arguments(), closeParToken); - ClassTreeImpl classBody = null; - if (e.getAnonymousClassDeclaration() != null) { - List members = new ArrayList<>(); - for (Object o : e.getAnonymousClassDeclaration().bodyDeclarations()) { - processBodyDeclaration((BodyDeclaration) o, members); - } - classBody = new ClassTreeImpl( - Tree.Kind.CLASS, - firstTokenIn(e.getAnonymousClassDeclaration(), TerminalToken.TokenNameLBRACE), - members, - lastTokenIn(e.getAnonymousClassDeclaration(), TerminalToken.TokenNameRBRACE) - ); - ITypeBinding binding = e.getAnonymousClassDeclaration().resolveBinding(); - classBody.setSymbol(sema.typeSymbolOrUnknown(binding)); - declaration(binding, classBody); - } - - final int separatorTokenIndex = firstTokenIndexAfter(e); - final InternalSyntaxToken separatorToken; - switch (tokenManager.get(separatorTokenIndex).tokenType) { - case TokenNameCOMMA, - TokenNameSEMICOLON: - separatorToken = createSyntaxToken(separatorTokenIndex); - break; - case TokenNameRBRACE: - separatorToken = null; - break; - default: - throw new IllegalStateException(); - } - - IdentifierTreeImpl identifier = createSimpleName(e.getName()); - IMethodBinding constructorBinding; - if (e.getAnonymousClassDeclaration() == null) { - constructorBinding = excludeRecovery(e.resolveConstructorBinding(), arguments.size()); - } else { - constructorBinding = findConstructorForAnonymousClass(e.getAST(), e.getName().resolveTypeBinding(), e.resolveConstructorBinding()); - } - identifier.setSymbol(sema.symbolOrUnknown(constructorBinding)); - usage(constructorBinding, identifier); - - EnumConstantTreeImpl t = new EnumConstantTreeImpl( - convertModifiers(e.modifiers()), - identifier, - new NewClassTreeImpl(identifier, arguments, classBody), - separatorToken - ); - IVariableBinding binding = e.resolveVariable(); - t.setSymbol(sema.variableSymbolOrUnknown(binding)); - declaration(binding, t); - return t; - } - - private void processBodyDeclaration(BodyDeclaration node, List members) { - final int lastTokenIndex; - - switch (node.getNodeType()) { - case ASTNode.ANNOTATION_TYPE_DECLARATION, - ASTNode.ENUM_DECLARATION, - ASTNode.RECORD_DECLARATION, - ASTNode.TYPE_DECLARATION, - ASTNode.UNNAMED_CLASS: - lastTokenIndex = processTypeDeclaration((AbstractTypeDeclaration) node, members); - break; - case ASTNode.ANNOTATION_TYPE_MEMBER_DECLARATION: - lastTokenIndex = processAnnotationTypeMemberDeclaration((AnnotationTypeMemberDeclaration) node, members); - break; - case ASTNode.INITIALIZER: - lastTokenIndex = processInitializerDeclaration((Initializer) node, members); - break; - case ASTNode.METHOD_DECLARATION: - lastTokenIndex = processMethodDeclaration((MethodDeclaration) node, members); - break; - case ASTNode.FIELD_DECLARATION: - lastTokenIndex = processFieldDeclaration((FieldDeclaration) node, members); - break; - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - - addEmptyStatementsToList(lastTokenIndex, members); - } - - private int processTypeDeclaration(AbstractTypeDeclaration node, List members) { - members.add(convertTypeDeclaration(node)); - return tokenManager.lastIndexIn(node, TerminalToken.TokenNameRBRACE); - } - - private int processAnnotationTypeMemberDeclaration(AnnotationTypeMemberDeclaration e, List members) { - FormalParametersListTreeImpl parameters = new FormalParametersListTreeImpl( - firstTokenAfter(e.getName(), TerminalToken.TokenNameLPAREN), - firstTokenAfter(e.getName(), TerminalToken.TokenNameRPAREN)); - - Expression defaultExpression = e.getDefault(); - InternalSyntaxToken defaultToken = defaultExpression == null ? null : firstTokenBefore(defaultExpression, TerminalToken.TokenNamedefault); - ExpressionTree defaultValue = defaultExpression == null ? null : convertExpression(defaultExpression); - - MethodTreeImpl t = new MethodTreeImpl(parameters, defaultToken, defaultValue) - .complete(convertType(e.getType()), createSimpleName(e.getName()), lastTokenIn(e, TerminalToken.TokenNameSEMICOLON)) - .completeWithModifiers(convertModifiers(e.modifiers())); - - IMethodBinding binding = e.resolveBinding(); - t.setSymbol(sema.methodSymbolOrUnknown(binding)); - declaration(binding, t); - - members.add(t); - return tokenManager.lastIndexIn(e, TerminalToken.TokenNameSEMICOLON); - } - - private int processInitializerDeclaration(Initializer e, List members) { - BlockTreeImpl blockTree = convertBlock(e.getBody()); - if (org.eclipse.jdt.core.dom.Modifier.isStatic(e.getModifiers())) { - members.add(new StaticInitializerTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamestatic), - (InternalSyntaxToken) blockTree.openBraceToken(), - blockTree.body(), - (InternalSyntaxToken) blockTree.closeBraceToken())); - } else { - members.add(new BlockTreeImpl( - Tree.Kind.INITIALIZER, - (InternalSyntaxToken) blockTree.openBraceToken(), - blockTree.body(), - (InternalSyntaxToken) blockTree.closeBraceToken())); - } - return tokenManager.lastIndexIn(e, TerminalToken.TokenNameRBRACE); - } - - private int processMethodDeclaration(MethodDeclaration e, List members) { - List p = e.parameters(); - final FormalParametersListTreeImpl formalParameters; - if (e.isCompactConstructor()) { - // only used for records - formalParameters = new FormalParametersListTreeImpl(null, null); - } else { - InternalSyntaxToken openParen = firstTokenAfter(e.getName(), TerminalToken.TokenNameLPAREN); - InternalSyntaxToken closeParen = firstTokenAfter(p.isEmpty() ? e.getName() : (ASTNode) p.get(p.size() - 1), TerminalToken.TokenNameRPAREN); - formalParameters = new FormalParametersListTreeImpl(openParen, closeParen); - } - - for (int i = 0; i < p.size(); i++) { - SingleVariableDeclaration o = (SingleVariableDeclaration) p.get(i); - VariableTreeImpl parameter = convertVariable(o); - if (i < p.size() - 1) { - parameter.setEndToken(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } - formalParameters.add(parameter); - } - - QualifiedIdentifierListTreeImpl thrownExceptionTypes = QualifiedIdentifierListTreeImpl.emptyList(); - List tt = e.thrownExceptionTypes(); - convertSeparatedTypeList(tt, thrownExceptionTypes); - - Block body = e.getBody(); - Type returnType = e.getReturnType2(); - InternalSyntaxToken throwsToken = tt.isEmpty() ? null : firstTokenBefore((Type) tt.get(0), TerminalToken.TokenNamethrows); - InternalSyntaxToken semcolonToken = body == null ? lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) : null; - MethodTreeImpl t = new MethodTreeImpl( - returnType == null ? null : applyExtraDimensions(convertType(returnType), e.extraDimensions(), returnType.resolveBinding()), - createSimpleName(e.getName()), - formalParameters, - throwsToken, - thrownExceptionTypes, - body == null ? null : convertBlock(body), - semcolonToken - ).completeWithModifiers( - convertModifiers(e.modifiers()) - ).completeWithTypeParameters( - convertTypeParameters(e.typeParameters()) - ); - IMethodBinding binding = e.resolveBinding(); - t.setSymbol(sema.methodSymbolOrUnknown(binding)); - declaration(binding, t); - - members.add(t); - return tokenManager.lastIndexIn(e, body == null ? TerminalToken.TokenNameSEMICOLON : TerminalToken.TokenNameRBRACE); - } - - private int processFieldDeclaration(FieldDeclaration fieldDeclaration, List members) { - ModifiersTreeImpl modifiers = convertModifiers(fieldDeclaration.modifiers()); - TypeTree type = convertType(fieldDeclaration.getType()); - - for (int i = 0; i < fieldDeclaration.fragments().size(); i++) { - VariableDeclarationFragment fragment = (VariableDeclarationFragment) fieldDeclaration.fragments().get(i); - VariableTreeImpl t = new VariableTreeImpl(createSimpleName(fragment.getName())) - .completeModifiersAndType(modifiers, applyExtraDimensions(type, fragment.extraDimensions(), fieldDeclaration.getType().resolveBinding())); - - if (fragment.getInitializer() != null) { - t.completeTypeAndInitializer(t.type(), firstTokenAfter(fragment.getName(), TerminalToken.TokenNameEQUAL), convertExpression(fragment.getInitializer())); - } - - t.setEndToken(firstTokenAfter(fragment, i + 1 < fieldDeclaration.fragments().size() ? TerminalToken.TokenNameCOMMA : TerminalToken.TokenNameSEMICOLON)); - IVariableBinding binding = fragment.resolveBinding(); - t.setSymbol(sema.variableSymbolOrUnknown(binding)); - declaration(binding, t); - - members.add(t); - } - return tokenManager.lastIndexIn(fieldDeclaration, TerminalToken.TokenNameSEMICOLON); + return unitBridge; } - private ArgumentListTreeImpl convertArguments(@Nullable InternalSyntaxToken openParen, List list, @Nullable InternalSyntaxToken closeParen) { - ArgumentListTreeImpl arguments = ArgumentListTreeImpl.emptyList().complete(openParen, closeParen); - for (int i = 0; i < list.size(); i++) { - Expression o = (Expression) list.get(i); - arguments.add(convertExpression(o)); - if (i < list.size() - 1) { - arguments.separators().add(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } + private static synchronized void discardUnitBridge() { + if (unitBridge != null) { + unitBridge.close(); + unitBridge = null; } - return arguments; } - @Nullable - private TypeArgumentListTreeImpl convertTypeArguments(List list) { - if (list.isEmpty()) { - return null; - } - ASTNode last = (ASTNode) list.get(list.size() - 1); - int tokenIndex = tokenManager.firstIndexAfter(last, ANY_TOKEN); - while (isComment(tokenManager.get(tokenIndex))) { - tokenIndex++; + private static void deleteQuietly(@Nullable Path directory) { + if (directory == null) { + return; } - return convertTypeArguments( - firstTokenBefore((ASTNode) list.get(0), TerminalToken.TokenNameLESS), - list, - // TerminalToken.TokenNameUNSIGNED_RIGHT_SHIFT vs TerminalToken.TokenNameGREATER - createSpecialToken(tokenIndex) - ); - } - - private TypeArgumentListTreeImpl convertTypeArguments(InternalSyntaxToken l, List list, InternalSyntaxToken g) { - TypeArgumentListTreeImpl typeArguments = new TypeArgumentListTreeImpl(l, g); - for (int i = 0; i < list.size(); i++) { - Type o = (Type) list.get(i); - if (i > 0) { - typeArguments.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - typeArguments.add(convertType(o)); + try (Stream paths = Files.walk(directory)) { + paths.sorted(Comparator.reverseOrder()).forEach(JParser::deleteIfPresent); + } catch (IOException | UncheckedIOException ignored) { + // A leftover temporary file does not affect the analysis. } - return typeArguments; } - private TypeParameterListTreeImpl convertTypeParameters(List list) { - if (list.isEmpty()) { - return new TypeParameterListTreeImpl(); - } - ASTNode last = (ASTNode) list.get(list.size() - 1); - int tokenIndex = tokenManager.firstIndexAfter(last, ANY_TOKEN); - while (isComment(tokenManager.get(tokenIndex))) { - tokenIndex++; - } - TypeParameterListTreeImpl t = new TypeParameterListTreeImpl( - firstTokenBefore((ASTNode) list.get(0), TerminalToken.TokenNameLESS), - // TerminalToken.TokenNameUNSIGNED_RIGHT_SHIFT vs TerminalToken.TokenNameGREATER - createSpecialToken(tokenIndex) - ); - for (int i = 0; i < list.size(); i++) { - TypeParameter o = (TypeParameter) list.get(i); - if (i > 0) { - t.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - t.add(convertTypeParameter(o)); + private static void deleteIfPresent(Path path) { + try { + Files.deleteIfExists(path); + } catch (IOException failure) { + throw new UncheckedIOException(failure); } - return t; } - private TypeParameterTree convertTypeParameter(TypeParameter e) { - IdentifierTreeImpl i = createSimpleName(e.getName()); - // TODO why ECJ uses IExtendedModifier here instead of Annotation ? - i.complete(convertAnnotations(e.modifiers())); - TypeParameterTreeImpl t; - List typeBounds = e.typeBounds(); - if (typeBounds.isEmpty()) { - t = new TypeParameterTreeImpl(i); - } else { - QualifiedIdentifierListTreeImpl bounds = QualifiedIdentifierListTreeImpl.emptyList(); - for (int j = 0; j < typeBounds.size(); j++) { - Object o = typeBounds.get(j); - bounds.add(convertType((Type) o)); - if (j < typeBounds.size() - 1) { - bounds.separators().add(firstTokenAfter((ASTNode) o, TerminalToken.TokenNameAND)); - } - } - t = new TypeParameterTreeImpl( - i, - firstTokenAfter(e.getName(), TerminalToken.TokenNameextends), - bounds - ); + /** A temporary directory only the current user can read, where the file system supports it. */ + private static Path privateTempDirectory() throws IOException { + if (FileSystems.getDefault().supportedFileAttributeViews().contains("posix")) { + return Files.createTempDirectory("sonar-java-unit", PosixFilePermissions.asFileAttribute(PosixFilePermissions.fromString("rwx------"))); } - t.setSymbol(sema.symbolOrUnknown(e.resolveBinding())); - return t; + return Files.createTempDirectory("sonar-java-unit"); } - /** - * @param extraDimensions list of {@link org.eclipse.jdt.core.dom.Dimension} - */ - private TypeTree applyExtraDimensions(TypeTree type, List extraDimensions, @Nullable ITypeBinding typeBinding) { - for (int i = 0; i < extraDimensions.size(); i++) { - Dimension e = (Dimension) extraDimensions.get(i); - type = new JavaTree.ArrayTypeTreeImpl( - type, - (List) convertAnnotations(e.annotations()), - firstTokenIn(e, TerminalToken.TokenNameLBRACKET), - firstTokenIn(e, TerminalToken.TokenNameRBRACKET) - ); - if (typeBinding != null) { - ((JavaTree.ArrayTypeTreeImpl) type).setSymbolType(sema.typeOrUnknown(typeBinding.createArrayType(i + 1))); - } + static Path bridgePath() { + String configured = System.getProperty("sonar.java.semsitter.bridge"); + if (configured == null || configured.isBlank()) { + configured = System.getenv("SEMSITTER_BRIDGE"); } - return type; - } - - private VariableTreeImpl convertVariable(TypePattern typePattern) { - if (typePattern.getAST().apiLevel() < AST.JLS22) { - return convertVariable(typePattern.getPatternVariable()); + if (configured != null && !configured.isBlank()) { + return Path.of(configured); } - VariableDeclaration variableDeclaration = typePattern.getPatternVariable2(); - if (variableDeclaration instanceof VariableDeclarationFragment declarationFragment) { - return convertVariable(declarationFragment); - } else { - return convertVariable((SingleVariableDeclaration) variableDeclaration); + try { + Path target = Path.of(System.getProperty("java.io.tmpdir"), "sonar-java", "semsitter-bridge"); + return BridgeExtractor.extract(target, new PlatformDetection().getPlatform()); + } catch (Exception failure) { + throw new RecognitionException(1, "SemSitter bridge is unavailable: " + failure.getMessage(), failure); } } - private VariableTreeImpl convertVariable(VariableDeclarationFragment declarationFragment) { - return completeInitializerAndBinding( - new VariableTreeImpl(createSimpleName(declarationFragment.getName())), - declarationFragment); - } - - private VariableTreeImpl convertVariable(SingleVariableDeclaration e) { - // TODO are extraDimensions and varargs mutually exclusive? - TypeTree type = convertType(e.getType()); - ITypeBinding typeBinding = e.getType().resolveBinding(); - type = applyExtraDimensions(type, e.extraDimensions(), typeBinding); - if (typeBinding != null && !e.extraDimensions().isEmpty()) { - typeBinding = typeBinding.createArrayType(e.extraDimensions().size()); - } - if (e.isVarargs()) { - type = new JavaTree.ArrayTypeTreeImpl( - type, - (List) convertAnnotations(e.varargsAnnotations()), - firstTokenAfter(e.getType(), TerminalToken.TokenNameELLIPSIS) - ); - if (typeBinding != null) { - ((JavaTree.ArrayTypeTreeImpl) type).setSymbolType(sema.typeOrUnknown(typeBinding.createArrayType(1))); - } - } - - VariableTreeImpl t = new VariableTreeImpl( - convertModifiers(e.modifiers()), - type, - createSimpleName(e.getName()) - ); - return completeInitializerAndBinding(t, e); - } - - private VariableTreeImpl completeInitializerAndBinding(VariableTreeImpl t, VariableDeclaration e) { - if (e.getInitializer() != null) { - t.completeTypeAndInitializer( - t.type(), - firstTokenAfter(e.getName(), TerminalToken.TokenNameEQUAL), - convertExpression(e.getInitializer()) - ); - } - IVariableBinding binding = e.resolveBinding(); - t.setSymbol(sema.variableSymbolOrUnknown(binding)); - if (binding != null) { - if (t.type() instanceof InferedTypeTree inferredType) { - inferredType.setSymbolType(sema.typeOrUnknown(binding.getType())); - } - declaration(binding, t); - } - return t; - } - - private void addVariableToList(VariableDeclarationExpression e2, List list) { - ModifiersTreeImpl modifiers = convertModifiers(e2.modifiers()); - TypeTree type = convertType(e2.getType()); - - for (int i = 0; i < e2.fragments().size(); i++) { - VariableDeclarationFragment fragment = (VariableDeclarationFragment) e2.fragments().get(i); - VariableTreeImpl t = new VariableTreeImpl(createSimpleName(fragment.getName())); - t.completeModifiers(modifiers); - if (fragment.getInitializer() == null) { - t.completeType(applyExtraDimensions(type, fragment.extraDimensions(), e2.getType().resolveBinding())); - } else { - t.completeTypeAndInitializer( - applyExtraDimensions(type, fragment.extraDimensions(), e2.getType().resolveBinding()), - firstTokenBefore(fragment.getInitializer(), TerminalToken.TokenNameEQUAL), - convertExpression(fragment.getInitializer()) - ); - } - if (i < e2.fragments().size() - 1) { - t.setEndToken(firstTokenAfter(fragment, TerminalToken.TokenNameCOMMA)); - } - IVariableBinding binding = fragment.resolveBinding(); - t.setSymbol(sema.variableSymbolOrUnknown(binding)); - declaration(binding, t); - list.add(t); - } - } - - private VarTypeTreeImpl convertVarType(SimpleType simpleType) { - VarTypeTreeImpl varTree = new VarTypeTreeImpl(firstTokenIn(simpleType.getName(), TerminalToken.TokenNameIdentifier)); - varTree.setSymbolType(sema.typeOrUnknown(simpleType.resolveBinding())); - return varTree; - } - - private IdentifierTreeImpl createSimpleName(SimpleName e) { - int tokenIndex = firstIndexIn(tokenManager, e, TerminalToken.TokenNameIdentifier, TerminalToken.TokenNameUNDERSCORE); - Token token = tokenManager.get(tokenIndex); - boolean isUnnamedVariable = token.tokenType == TerminalToken.TokenNameUNDERSCORE; - IdentifierTreeImpl t = new IdentifierTreeImpl(createSyntaxToken(tokenIndex), isUnnamedVariable); - t.setSymbolType(sema.typeOrUnknown(e.resolveTypeBinding())); - t.setSymbol(sema.symbolOrUnknown(e.resolveBinding())); - return t; - } - - private BlockTreeImpl convertBlock(Block e) { - List statements = new ArrayList<>(); - for (Object o : e.statements()) { - addStatementToList((Statement) o, statements); - } - return new BlockTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameLBRACE), - statements, - lastTokenIn(e, TerminalToken.TokenNameRBRACE) - ); - } - - private void addStatementToList(Statement node, List statements) { - if (node.getNodeType() == ASTNode.VARIABLE_DECLARATION_STATEMENT) { - VariableDeclarationStatement e = (VariableDeclarationStatement) node; - TypeTree tType = convertType(e.getType()); - ModifiersTreeImpl modifiers = convertModifiers(e.modifiers()); - for (int i = 0; i < e.fragments().size(); i++) { - VariableDeclarationFragment fragment = (VariableDeclarationFragment) e.fragments().get(i); - VariableTreeImpl t = new VariableTreeImpl(createSimpleName(fragment.getName())) - .completeType(applyExtraDimensions(tType, fragment.extraDimensions(), e.getType().resolveBinding())) - .completeModifiers(modifiers); - Expression initalizer = fragment.getInitializer(); - if (initalizer != null) { - InternalSyntaxToken equalToken = firstTokenAfter(fragment.getName(), TerminalToken.TokenNameEQUAL); - t.completeTypeAndInitializer(t.type(), equalToken, convertExpression(initalizer)); - } - TerminalToken endTokenType = i < e.fragments().size() - 1 ? TerminalToken.TokenNameCOMMA : TerminalToken.TokenNameSEMICOLON; - t.setEndToken(firstTokenAfter(fragment, endTokenType)); - - IVariableBinding binding = fragment.resolveBinding(); - t.setSymbol(sema.variableSymbolOrUnknown(binding)); - declaration(binding, t); - statements.add(t); - } - } else { - statements.add(createStatement(node)); - } - } - - private StatementTree createStatement(Statement node) { - switch (node.getNodeType()) { - case ASTNode.BLOCK: - return convertBlock((Block) node); - case ASTNode.EMPTY_STATEMENT: - return convertEmptyStatement((EmptyStatement) node); - case ASTNode.RETURN_STATEMENT: - return convertReturn((ReturnStatement) node); - case ASTNode.FOR_STATEMENT: - return convertFor((ForStatement) node); - case ASTNode.WHILE_STATEMENT: - return convertWhile((WhileStatement) node); - case ASTNode.IF_STATEMENT: - return convertIf((IfStatement) node); - case ASTNode.BREAK_STATEMENT: - return convertBreak((BreakStatement) node); - case ASTNode.DO_STATEMENT: - return convertDoWhile((DoStatement) node); - case ASTNode.ASSERT_STATEMENT: - return convertAssert((AssertStatement) node); - case ASTNode.SWITCH_STATEMENT: - return convertSwitchStatement((SwitchStatement) node); - case ASTNode.SYNCHRONIZED_STATEMENT: - return convertSynchronized((SynchronizedStatement) node); - case ASTNode.EXPRESSION_STATEMENT: - return convertExpressionStatement((ExpressionStatement) node); - case ASTNode.CONTINUE_STATEMENT: - return convertContinue((ContinueStatement) node); - case ASTNode.LABELED_STATEMENT: - return convertLabel((LabeledStatement) node); - case ASTNode.ENHANCED_FOR_STATEMENT: - return convertForeach((EnhancedForStatement) node); - case ASTNode.THROW_STATEMENT: - return convertThrow((ThrowStatement) node); - case ASTNode.TRY_STATEMENT: - return convertTry((TryStatement) node); - case ASTNode.TYPE_DECLARATION_STATEMENT: - return convertTypeDeclaration(((TypeDeclarationStatement) node).getDeclaration()); - case ASTNode.CONSTRUCTOR_INVOCATION: - return convertConstructorInvocation((ConstructorInvocation) node); - case ASTNode.SUPER_CONSTRUCTOR_INVOCATION: - return convertSuperConstructorInvocation((SuperConstructorInvocation) node); - case ASTNode.YIELD_STATEMENT: - return convertYield((YieldStatement) node); - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - } - - private EmptyStatementTreeImpl convertEmptyStatement(EmptyStatement e) { - return new EmptyStatementTreeImpl(lastTokenIn(e, TerminalToken.TokenNameSEMICOLON)); - } - - private ReturnStatementTreeImpl convertReturn(ReturnStatement e) { - Expression expression = e.getExpression(); - return new ReturnStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamereturn), - expression == null ? null : convertExpression(expression), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private ForStatementTreeImpl convertFor(ForStatement e) { - StatementListTreeImpl forInitStatement = StatementListTreeImpl.emptyList(); - for (int i = 0; i < e.initializers().size(); i++) { - Expression o = (Expression) e.initializers().get(i); - if (i > 0) { - forInitStatement.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - if (ASTNode.VARIABLE_DECLARATION_EXPRESSION == o.getNodeType()) { - addVariableToList((VariableDeclarationExpression) o, forInitStatement); - } else { - forInitStatement.add(new ExpressionStatementTreeImpl(convertExpression(o), null)); - } - } - - StatementListTreeImpl forUpdateStatement = StatementListTreeImpl.emptyList(); - for (int i = 0; i < e.updaters().size(); i++) { - Expression o = (Expression) e.updaters().get(i); - if (i > 0) { - forUpdateStatement.separators().add(firstTokenBefore(o, TerminalToken.TokenNameCOMMA)); - } - forUpdateStatement.add(new ExpressionStatementTreeImpl(convertExpression(o), null)); - } - - final int firstSemicolonTokenIndex = e.initializers().isEmpty() - ? tokenManager.firstIndexIn(e, TerminalToken.TokenNameSEMICOLON) - : tokenManager.firstIndexAfter((ASTNode) e.initializers().get(e.initializers().size() - 1), TerminalToken.TokenNameSEMICOLON); - Expression expression = e.getExpression(); - final int secondSemicolonTokenIndex = expression == null - ? nextTokenIndex(firstSemicolonTokenIndex, TerminalToken.TokenNameSEMICOLON) - : tokenManager.firstIndexAfter(expression, TerminalToken.TokenNameSEMICOLON); - - return new ForStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamefor), - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - forInitStatement, - createSyntaxToken(firstSemicolonTokenIndex), - expression == null ? null : convertExpression(expression), - createSyntaxToken(secondSemicolonTokenIndex), - forUpdateStatement, - firstTokenBefore(e.getBody(), TerminalToken.TokenNameRPAREN), - createStatement(e.getBody()) - ); - } - - private WhileStatementTreeImpl convertWhile(WhileStatement e) { - Expression expression = e.getExpression(); - return new WhileStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamewhile), - firstTokenBefore(expression, TerminalToken.TokenNameLPAREN), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - createStatement(e.getBody()) - ); - } - - private IfStatementTreeImpl convertIf(IfStatement e) { - Expression expression = e.getExpression(); - Statement thenStatement = e.getThenStatement(); - Statement elseStatement = e.getElseStatement(); - return new IfStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameif), - firstTokenBefore(expression, TerminalToken.TokenNameLPAREN), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - createStatement(thenStatement), - elseStatement == null ? null : firstTokenAfter(thenStatement, TerminalToken.TokenNameelse), - elseStatement == null ? null : createStatement(elseStatement) - ); - } - - private BreakStatementTreeImpl convertBreak(BreakStatement e) { - IdentifierTreeImpl identifier = e.getLabel() == null ? null : createSimpleName(e.getLabel()); - usageLabel(identifier); - return new BreakStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamebreak), - identifier, - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private DoWhileStatementTreeImpl convertDoWhile(DoStatement e) { - Statement body = e.getBody(); - Expression expression = e.getExpression(); - return new DoWhileStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamedo), - createStatement(body), - firstTokenAfter(body, TerminalToken.TokenNamewhile), - firstTokenBefore(expression, TerminalToken.TokenNameLPAREN), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private AssertStatementTreeImpl convertAssert(AssertStatement e) { - Expression message = e.getMessage(); - AssertStatementTreeImpl t = new AssertStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameassert), - convertExpression(e.getExpression()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - if (message != null) { - t.complete(firstTokenBefore(message, TerminalToken.TokenNameCOLON), convertExpression(message)); - } - return t; - } - - private SwitchStatementTreeImpl convertSwitchStatement(SwitchStatement e) { - Expression expression = e.getExpression(); - return new SwitchStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameswitch), - firstTokenBefore(expression, TerminalToken.TokenNameLPAREN), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - firstTokenAfter(expression, TerminalToken.TokenNameLBRACE), - convertSwitchStatements(e.statements()), - lastTokenIn(e, TerminalToken.TokenNameRBRACE) - ); - } - - private List convertSwitchStatements(List list) { - List groups = new ArrayList<>(); - List caselabels = null; - StatementListTreeImpl body = null; - - for (Object o : list) { - if (o instanceof SwitchCase c) { - if (caselabels == null) { - caselabels = new ArrayList<>(); - body = StatementListTreeImpl.emptyList(); - } - - List expressions = ((List) c.expressions()).stream() - .map(Expression.class::cast) - .map(this::convertExpressionFromCase) - .toList(); - - caselabels.add(new CaseLabelTreeImpl( - firstTokenIn(c, c.isDefault() ? TerminalToken.TokenNamedefault : TerminalToken.TokenNamecase), - expressions, - lastTokenIn(c, /* TerminalToken.TokenNameCOLON or TerminalToken.TokenNameARROW */ ANY_TOKEN) - )); - } else { - if (caselabels != null) { - groups.add(new CaseGroupTreeImpl(caselabels, body)); - } - caselabels = null; - addStatementToList((Statement) o, Objects.requireNonNull(body)); - } - } - if (caselabels != null) { - groups.add(new CaseGroupTreeImpl(caselabels, body)); - } - return groups; - } - - private ExpressionTree convertExpressionFromCase(Expression e) { - if (e.getNodeType() == ASTNode.CASE_DEFAULT_EXPRESSION) { - return new DefaultPatternTreeImpl(firstTokenIn(e, TerminalToken.TokenNamedefault), sema.typeOrUnknown(e.resolveTypeBinding())); - } - if (e.getNodeType() == ASTNode.NULL_LITERAL) { - return new NullPatternTreeImpl((LiteralTreeImpl) convertExpression(e)); - } - if (e instanceof Pattern pattern) { - return convertPattern(pattern); - } - return convertExpression(e); - } - - private PatternTree convertPattern(Pattern p) { - switch (p.getNodeType()) { - case ASTNode.TYPE_PATTERN: - TypePattern typePattern = (TypePattern) p; - return new TypePatternTreeImpl(convertVariable(typePattern), sema.typeOrUnknown(typePattern.resolveTypeBinding())); - case ASTNode.RECORD_PATTERN: - RecordPattern recordPattern = (RecordPattern) p; - List nestedPatterns = recordPattern.patterns().stream() - .map(this::convertPattern) - .toList(); - - TypeTree patternType = convertType(recordPattern.getPatternType()); - var openParenToken = firstTokenIn(recordPattern, TerminalToken.TokenNameLPAREN); - var closeParenToken = lastTokenIn(recordPattern, TerminalToken.TokenNameRPAREN); - return new RecordPatternTreeImpl(patternType, openParenToken, nestedPatterns, closeParenToken); - case ASTNode.GUARDED_PATTERN: - GuardedPattern g = (GuardedPattern) p; - return new GuardedPatternTreeImpl( - convertPattern(g.getPattern()), - firstTokenBefore(g.getExpression(), TerminalToken.TokenNameRestrictedIdentifierWhen), - convertExpression(g.getExpression()), - sema.typeOrUnknown(g.resolveTypeBinding())); - case ASTNode.NULL_PATTERN: - // It is not clear how to reach this one, it seems to be possible only with badly constructed AST - // fall-through. Do nothing for now. - default: - // JEP-405 (not released as part of any JDK yet): ArrayPattern, RecordPattern - throw new IllegalStateException(ASTNode.nodeClassForType(p.getNodeType()).toString()); - } - } - - private SynchronizedStatementTreeImpl convertSynchronized(SynchronizedStatement e) { - Expression expression = e.getExpression(); - return new SynchronizedStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamesynchronized), - firstTokenBefore(expression, TerminalToken.TokenNameLPAREN), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - convertBlock(e.getBody()) - ); - } - - private ExpressionStatementTreeImpl convertExpressionStatement(ExpressionStatement e) { - return new ExpressionStatementTreeImpl( - convertExpression(e.getExpression()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private ContinueStatementTreeImpl convertContinue(ContinueStatement e) { - SimpleName label = e.getLabel(); - IdentifierTreeImpl i = label == null ? null : createSimpleName(label); - usageLabel(i); - return new ContinueStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamecontinue), - i, - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private LabeledStatementTreeImpl convertLabel(LabeledStatement e) { - IdentifierTreeImpl i = createSimpleName(e.getLabel()); - - JLabelSymbol symbol = new JLabelSymbol(i.name()); - labels.push(symbol); - - LabeledStatementTreeImpl t = new LabeledStatementTreeImpl( - i, - firstTokenAfter(e.getLabel(), TerminalToken.TokenNameCOLON), - createStatement(e.getBody()) - ); - - labels.pop(); - symbol.declaration = t; - t.labelSymbol = symbol; - return t; - } - - private ForEachStatementImpl convertForeach(EnhancedForStatement e) { - SingleVariableDeclaration parameter = e.getParameter(); - Expression expression = e.getExpression(); - return new ForEachStatementImpl( - firstTokenIn(e, TerminalToken.TokenNamefor), - firstTokenBefore(parameter, TerminalToken.TokenNameLPAREN), - convertVariable(parameter), - firstTokenAfter(parameter, TerminalToken.TokenNameCOLON), - convertExpression(expression), - firstTokenAfter(expression, TerminalToken.TokenNameRPAREN), - createStatement(e.getBody()) - ); - } - - private ThrowStatementTreeImpl convertThrow(ThrowStatement e) { - return new ThrowStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNamethrow), - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameSEMICOLON) - ); - } - - private TryStatementTreeImpl convertTry(TryStatement e) { - ResourceListTreeImpl resources = convertResources(e); - List catches = convertCatchClauses(e); - - Block f = e.getFinally(); - return new TryStatementTreeImpl( - firstTokenIn(e, TerminalToken.TokenNametry), - resources.isEmpty() ? null : firstTokenIn(e, TerminalToken.TokenNameLPAREN), - resources, - resources.isEmpty() ? null : firstTokenBefore(e.getBody(), TerminalToken.TokenNameRPAREN), - convertBlock(e.getBody()), - catches, - f == null ? null : firstTokenBefore(f, TerminalToken.TokenNamefinally), - f == null ? null : convertBlock(f) - ); - } - - private ResourceListTreeImpl convertResources(TryStatement e) { - List r = e.resources(); - ResourceListTreeImpl resources = ResourceListTreeImpl.emptyList(); - for (int i = 0; i < r.size(); i++) { - Expression o = (Expression) r.get(i); - if (ASTNode.VARIABLE_DECLARATION_EXPRESSION == o.getNodeType()) { - addVariableToList((VariableDeclarationExpression) o, resources); - } else { - resources.add(convertExpression(o)); - } - addSeparatorToList(e, o, resources.separators(), i < e.resources().size() - 1); - } - return resources; - } - - private void addSeparatorToList(TryStatement tryStatement, Expression resource, List separators, boolean isLast) { - if (isLast) { - separators.add(firstTokenAfter(resource, TerminalToken.TokenNameSEMICOLON)); - } else { - int tokenIndex = tokenManager.firstIndexBefore(tryStatement.getBody(), TerminalToken.TokenNameRPAREN); - while (true) { - Token token; - do { - tokenIndex--; - token = tokenManager.get(tokenIndex); - } while (isComment(token)); - - if (token.tokenType != TerminalToken.TokenNameSEMICOLON) { - break; - } - separators.add(createSyntaxToken(tokenIndex)); - } - } - } - - private List convertCatchClauses(TryStatement e) { - List catches = new ArrayList<>(); - for (Object o : e.catchClauses()) { - CatchClause c = (CatchClause) o; - catches.add(new CatchTreeImpl( - firstTokenIn(c, TerminalToken.TokenNamecatch), - firstTokenBefore(c.getException(), TerminalToken.TokenNameLPAREN), - convertVariable(c.getException()), - firstTokenAfter(c.getException(), TerminalToken.TokenNameRPAREN), - convertBlock(c.getBody()) - )); - } - return catches; - } - - private ExpressionStatementTreeImpl convertConstructorInvocation(ConstructorInvocation e) { - ArgumentListTreeImpl arguments = convertArguments( - e.arguments().isEmpty() ? lastTokenIn(e, TerminalToken.TokenNameLPAREN) : firstTokenBefore((ASTNode) e.arguments().get(0), TerminalToken.TokenNameLPAREN), - e.arguments(), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - - IdentifierTreeImpl i = new IdentifierTreeImpl(e.arguments().isEmpty() - ? lastTokenIn(e, TerminalToken.TokenNamethis) - : firstTokenBefore((ASTNode) e.arguments().get(0), TerminalToken.TokenNamethis)); - MethodInvocationTreeImpl t = new MethodInvocationTreeImpl( - i, - convertTypeArguments(e.typeArguments()), - arguments - ); - IMethodBinding binding = e.resolveConstructorBinding(); - if (binding != null) { - t.setSymbolType(sema.typeOrUnknown(binding.getDeclaringClass())); - binding = excludeRecovery(binding, arguments.size()); - } - t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); - i.setSymbol(sema.symbolOrUnknown(binding)); - usage(binding, i); - return new ExpressionStatementTreeImpl( - t, - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private ExpressionStatementTreeImpl convertSuperConstructorInvocation(SuperConstructorInvocation e) { - IdentifierTreeImpl i = new IdentifierTreeImpl(firstTokenIn(e, TerminalToken.TokenNamesuper)); - ExpressionTree methodSelect = i; - if (e.getExpression() != null) { - methodSelect = new MemberSelectExpressionTreeImpl( - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameDOT), - i - ); - } - - ArgumentListTreeImpl arguments = convertArguments( - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - e.arguments(), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - - MethodInvocationTreeImpl t = new MethodInvocationTreeImpl( - methodSelect, - convertTypeArguments(e.typeArguments()), - arguments - ); - IMethodBinding binding = e.resolveConstructorBinding(); - if (binding != null) { - t.setSymbolType(sema.typeOrUnknown(binding.getDeclaringClass())); - binding = excludeRecovery(binding, arguments.size()); - } - t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); - i.setSymbol(sema.symbolOrUnknown(binding)); - usage(binding, i); - return new ExpressionStatementTreeImpl( - t, - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private YieldStatementTreeImpl convertYield(YieldStatement e) { - InternalSyntaxToken yieldKeyword = null; - if (!e.isImplicit()) { - yieldKeyword = firstTokenIn(e, TerminalToken.TokenNameRestrictedIdentifierYield); - } - return new YieldStatementTreeImpl( - yieldKeyword, - convertExpression(e.getExpression()), - lastTokenIn(e, TerminalToken.TokenNameSEMICOLON) - ); - } - - private ExpressionTree convertExpression(Expression node) { - ExpressionTree t = createExpression(node); - ((AbstractTypedTree) t).setSymbolType(sema.typeOrUnknown(node.resolveTypeBinding())); - return t; - } - - private ExpressionTree createExpression(Expression node) { - switch (node.getNodeType()) { - case ASTNode.SIMPLE_NAME: - return convertSimpleName((SimpleName) node); - case ASTNode.QUALIFIED_NAME: - return convertQualifiedName((QualifiedName) node); - case ASTNode.FIELD_ACCESS: - return convertFieldAccess((FieldAccess) node); - case ASTNode.SUPER_FIELD_ACCESS: - return convertFieldAccess((SuperFieldAccess) node); - case ASTNode.THIS_EXPRESSION: - return convertThisExpression((ThisExpression) node); - case ASTNode.ARRAY_ACCESS: - return convertArrayAccess((ArrayAccess) node); - case ASTNode.ARRAY_CREATION: - return convertArrayCreation((ArrayCreation) node); - case ASTNode.ARRAY_INITIALIZER: - return convertArrayInitializer((ArrayInitializer) node); - case ASTNode.ASSIGNMENT: - return convertAssignment((Assignment) node); - case ASTNode.CAST_EXPRESSION: - return convertTypeCastExpression((CastExpression) node); - case ASTNode.CLASS_INSTANCE_CREATION: - return convertClassInstanceCreation((ClassInstanceCreation) node); - case ASTNode.CONDITIONAL_EXPRESSION: - return convertConditionalExpression((ConditionalExpression) node); - case ASTNode.INFIX_EXPRESSION: - return convertInfixExpression((InfixExpression) node); - case ASTNode.METHOD_INVOCATION: - return convertMethodInvocation((MethodInvocation) node); - case ASTNode.SUPER_METHOD_INVOCATION: - return convertMethodInvocation((SuperMethodInvocation) node); - case ASTNode.PARENTHESIZED_EXPRESSION: - return convertParenthesizedExpression((ParenthesizedExpression) node); - case ASTNode.POSTFIX_EXPRESSION: - return convertPostfixExpression((PostfixExpression) node); - case ASTNode.PREFIX_EXPRESSION: - return convertPrefixExpression((PrefixExpression) node); - case ASTNode.INSTANCEOF_EXPRESSION: - return convertInstanceOf((InstanceofExpression) node); - case ASTNode.PATTERN_INSTANCEOF_EXPRESSION: - return convertInstanceOf((PatternInstanceofExpression) node); - case ASTNode.LAMBDA_EXPRESSION: - return convertLambdaExpression((LambdaExpression) node); - case ASTNode.CREATION_REFERENCE: - return convertMethodReference((CreationReference) node); - case ASTNode.EXPRESSION_METHOD_REFERENCE: - return convertMethodReference((ExpressionMethodReference) node); - case ASTNode.TYPE_METHOD_REFERENCE: - return convertMethodReference((TypeMethodReference) node); - case ASTNode.SUPER_METHOD_REFERENCE: - return convertMethodReference((SuperMethodReference) node); - case ASTNode.SWITCH_EXPRESSION: - return convertSwitchExpression((SwitchExpression) node); - case ASTNode.TYPE_LITERAL: - return convertTypeLiteral((TypeLiteral) node); - case ASTNode.NULL_LITERAL: - return convertLiteral((NullLiteral) node); - case ASTNode.NUMBER_LITERAL: - return convertLiteral((NumberLiteral) node); - case ASTNode.CHARACTER_LITERAL: - return convertLiteral((CharacterLiteral) node); - case ASTNode.BOOLEAN_LITERAL: - return convertLiteral((BooleanLiteral) node); - case ASTNode.STRING_LITERAL: - return convertLiteral((StringLiteral) node); - case ASTNode.TEXT_BLOCK: - return convertTextBlock((TextBlock) node); - case ASTNode.NORMAL_ANNOTATION, - ASTNode.MARKER_ANNOTATION, - ASTNode.SINGLE_MEMBER_ANNOTATION: - return convertAnnotation((Annotation) node); - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - } - - private IdentifierTreeImpl convertSimpleName(SimpleName e) { - IdentifierTreeImpl t = createSimpleName(e); - usage(e.resolveBinding(), t); - return t; - } - - private MemberSelectExpressionTreeImpl convertQualifiedName(QualifiedName e) { - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - usage(e.getName().resolveBinding(), rhs); - return new MemberSelectExpressionTreeImpl( - convertExpression(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - rhs - ); - } - - private MemberSelectExpressionTreeImpl convertFieldAccess(FieldAccess e) { - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - usage(e.getName().resolveBinding(), rhs); - return new MemberSelectExpressionTreeImpl( - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameDOT), - rhs - ); - } - - private MemberSelectExpressionTreeImpl convertFieldAccess(SuperFieldAccess e) { - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - usage(e.getName().resolveBinding(), rhs); - if (e.getQualifier() == null) { - // super.name - return new MemberSelectExpressionTreeImpl( - unqualifiedKeywordSuper(e), - firstTokenIn(e, TerminalToken.TokenNameDOT), - rhs - ); - } - // qualifier.super.name - AbstractTypedTree qualifier = (AbstractTypedTree) convertExpression(e.getQualifier()); - ITypeBinding qualifierBinding = e.getQualifier().resolveTypeBinding(); - KeywordSuper keywordSuper = new KeywordSuper(firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamesuper), sema.superSymbolOrUnknown(qualifierBinding)); - MemberSelectExpressionTreeImpl qualifiedSuper = new MemberSelectExpressionTreeImpl( - (ExpressionTree) qualifier, - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - keywordSuper - ); - if (qualifierBinding != null) { - qualifiedSuper.setSymbolType(sema.typeOrUnknown(qualifierBinding.getSuperclass())); - } - return new MemberSelectExpressionTreeImpl( - qualifiedSuper, - firstTokenBefore(e.getName(), TerminalToken.TokenNameDOT), - rhs - ); - } - - private ExpressionTree convertThisExpression(ThisExpression e) { - if (e.getQualifier() == null) { - return new KeywordThis(firstTokenIn(e, TerminalToken.TokenNamethis), sema.thisSymbolOrUnknown(e.resolveTypeBinding())); - } - KeywordThis keywordThis = new KeywordThis( - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamethis), - sema.thisSymbolOrUnknown(e.resolveTypeBinding()) - ); - keywordThis.setSymbolType(sema.typeOrUnknown(e.resolveTypeBinding())); - return new MemberSelectExpressionTreeImpl( - convertExpression(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - keywordThis - ); - } - - private MemberSelectExpressionTreeImpl convertTypeLiteral(TypeLiteral e) { - return new MemberSelectExpressionTreeImpl( - (ExpressionTree) convertType(e.getType()), - lastTokenIn(e, TerminalToken.TokenNameDOT), - new IdentifierTreeImpl( - lastTokenIn(e, TerminalToken.TokenNameclass) - ) - ); - } - - private ArrayAccessExpressionTreeImpl convertArrayAccess(ArrayAccess e) { - Expression index = e.getIndex(); - return new ArrayAccessExpressionTreeImpl( - convertExpression(e.getArray()), - new ArrayDimensionTreeImpl( - firstTokenBefore(index, TerminalToken.TokenNameLBRACKET), - convertExpression(index), - firstTokenAfter(index, TerminalToken.TokenNameRBRACKET) - ) - ); - } - - private NewArrayTreeImpl convertArrayCreation(ArrayCreation e) { - List dimensions = ((List) e.dimensions()).stream() - .map(Expression.class::cast) - .map(dimension -> new ArrayDimensionTreeImpl( - firstTokenBefore(dimension, TerminalToken.TokenNameLBRACKET), - convertExpression(dimension), - firstTokenAfter(dimension, TerminalToken.TokenNameRBRACKET) - )) - .collect(Collectors.toCollection(ArrayList::new)); - InitializerListTreeImpl initializers = InitializerListTreeImpl.emptyList(); - if (e.getInitializer() != null) { - assert dimensions.isEmpty(); - - TypeTree type = convertType(e.getType()); - while (type.is(Tree.Kind.ARRAY_TYPE)) { - ArrayTypeTree arrayType = (ArrayTypeTree) type; - ArrayDimensionTreeImpl dimension = new ArrayDimensionTreeImpl( - arrayType.openBracketToken(), - null, - arrayType.closeBracketToken() - ).completeAnnotations(arrayType.annotations()); - dimensions.add(/* TODO suboptimal */ 0, dimension); - type = arrayType.type(); - } - - return ((NewArrayTreeImpl) convertExpression(e.getInitializer())) - .completeWithNewKeyword(firstTokenIn(e, TerminalToken.TokenNamenew)) - .complete(type) - .completeDimensions(dimensions); - } - TypeTree type = convertType(e.getType()); - int index = dimensions.size() - 1; - while (type.is(Tree.Kind.ARRAY_TYPE)) { - if (!type.annotations().isEmpty()) { - ((ArrayDimensionTreeImpl) dimensions.get(index)) - .completeAnnotations(type.annotations()); - } - index--; - type = ((ArrayTypeTree) type).type(); - } - - return new NewArrayTreeImpl(dimensions, initializers) - .complete(type) - .completeWithNewKeyword(firstTokenIn(e, TerminalToken.TokenNamenew)); - } - - private NewArrayTreeImpl convertArrayInitializer(ArrayInitializer e) { - InitializerListTreeImpl initializers = InitializerListTreeImpl.emptyList(); - for (int i = 0; i < e.expressions().size(); i++) { - Expression o = (Expression) e.expressions().get(i); - initializers.add(convertExpression(o)); - final int commaTokenIndex = firstTokenIndexAfter(o); - if (tokenManager.get(commaTokenIndex).tokenType == TerminalToken.TokenNameCOMMA) { - initializers.separators().add(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } - } - return new NewArrayTreeImpl(Collections.emptyList(),initializers) - .completeWithCurlyBraces( - firstTokenIn(e, TerminalToken.TokenNameLBRACE), - lastTokenIn(e, TerminalToken.TokenNameRBRACE) - ); - } - - private AssignmentExpressionTreeImpl convertAssignment(Assignment e) { - Op op = operators.get(e.getOperator()); - return new AssignmentExpressionTreeImpl( - op.kind, - convertExpression(e.getLeftHandSide()), - firstTokenAfter(e.getLeftHandSide(), op.tokenType), - convertExpression(e.getRightHandSide()) - ); - } - - private TypeCastExpressionTreeImpl convertTypeCastExpression(CastExpression e) { - Type type = e.getType(); - if (type.getNodeType() == ASTNode.INTERSECTION_TYPE) { - List intersectionTypes = ((IntersectionType) type).types(); - QualifiedIdentifierListTreeImpl bounds = QualifiedIdentifierListTreeImpl.emptyList(); - for (int i = 1; i < intersectionTypes.size(); i++) { - Type o = (Type) intersectionTypes.get(i); - bounds.add(convertType(o)); - if (i < intersectionTypes.size() - 1) { - bounds.separators().add(firstTokenAfter(o, TerminalToken.TokenNameAND)); - } - } - return new TypeCastExpressionTreeImpl( - firstTokenBefore(type, TerminalToken.TokenNameLPAREN), - convertType((Type) intersectionTypes.get(0)), - firstTokenAfter((Type) intersectionTypes.get(0), TerminalToken.TokenNameAND), - bounds, - firstTokenAfter(type, TerminalToken.TokenNameRPAREN), - convertExpression(e.getExpression()) - ); - } - return new TypeCastExpressionTreeImpl( - firstTokenBefore(type, TerminalToken.TokenNameLPAREN), - convertType(type), - firstTokenAfter(type, TerminalToken.TokenNameRPAREN), - convertExpression(e.getExpression()) - ); - } - - private NewClassTreeImpl convertClassInstanceCreation(ClassInstanceCreation e) { - ArgumentListTreeImpl arguments = convertArguments( - firstTokenAfter(e.getType(), TerminalToken.TokenNameLPAREN), - e.arguments(), - firstTokenAfter(e.arguments().isEmpty() ? e.getType() : (ASTNode) e.arguments().get(e.arguments().size() - 1), TerminalToken.TokenNameRPAREN) - ); - - ClassTreeImpl classBody = null; - if (e.getAnonymousClassDeclaration() != null) { - List members = new ArrayList<>(); - for (Object o : e.getAnonymousClassDeclaration().bodyDeclarations()) { - processBodyDeclaration((BodyDeclaration) o, members); - } - classBody = new ClassTreeImpl( - Tree.Kind.CLASS, - firstTokenIn(e.getAnonymousClassDeclaration(), TerminalToken.TokenNameLBRACE), - members, - lastTokenIn(e.getAnonymousClassDeclaration(), TerminalToken.TokenNameRBRACE) - ); - ITypeBinding binding = e.getAnonymousClassDeclaration().resolveBinding(); - classBody.setSymbol(sema.typeSymbolOrUnknown(binding)); - declaration(binding, classBody); - } - - NewClassTreeImpl t = new NewClassTreeImpl( - convertType(e.getType()), - arguments, - classBody - ).completeWithNewKeyword( - e.getExpression() == null ? firstTokenIn(e, TerminalToken.TokenNamenew) : firstTokenAfter(e.getExpression(), TerminalToken.TokenNamenew) - ).completeWithTypeArguments( - convertTypeArguments(e.typeArguments()) - ); - if (e.getExpression() != null) { - t.completeWithEnclosingExpression(convertExpression(e.getExpression())); - t.completeWithDotToken(firstTokenAfter(e.getExpression(), TerminalToken.TokenNameDOT)); - } - - IdentifierTreeImpl i = (IdentifierTreeImpl) t.getConstructorIdentifier(); - int nbArguments = arguments.size(); - IMethodBinding binding; - if (e.getAnonymousClassDeclaration() == null) { - binding = excludeRecovery(e.resolveConstructorBinding(), nbArguments); - } else { - Type constructorType = e.getType() instanceof ParameterizedType parameterizedType ? parameterizedType.getType() : e.getType(); - binding = excludeRecovery(findConstructorForAnonymousClass(e.getAST(), constructorType.resolveBinding(), e.resolveConstructorBinding()), nbArguments); - } - i.setSymbol(sema.symbolOrUnknown(binding)); - usage(binding, i); - - return t; - } - - private ConditionalExpressionTreeImpl convertConditionalExpression(ConditionalExpression e) { - return new ConditionalExpressionTreeImpl( - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameQUESTION), - convertExpression(e.getThenExpression()), - firstTokenAfter(e.getThenExpression(), TerminalToken.TokenNameCOLON), - convertExpression(e.getElseExpression()) - ); - } - - private BinaryExpressionTreeImpl convertInfixExpression(InfixExpression e) { - Op op = operators.get(e.getOperator()); - BinaryExpressionTreeImpl t = new BinaryExpressionTreeImpl( - op.kind, - convertExpression(e.getLeftOperand()), - firstTokenAfter(e.getLeftOperand(), op.tokenType), - convertExpression(e.getRightOperand()) - ); - for (Object o : e.extendedOperands()) { - Expression e2 = (Expression) o; - t.setSymbolType(sema.typeOrUnknown(e.resolveTypeBinding())); - t = new BinaryExpressionTreeImpl( - op.kind, - t, - firstTokenBefore(e2, op.tokenType), - convertExpression(e2) - ); - } - return t; - } - - private MethodInvocationTreeImpl convertMethodInvocation(MethodInvocation e) { - ArgumentListTreeImpl arguments = convertArguments( - firstTokenAfter(e.getName(), TerminalToken.TokenNameLPAREN), - e.arguments(), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - ExpressionTree memberSelect; - if (e.getExpression() == null) { - memberSelect = rhs; - } else { - memberSelect = new MemberSelectExpressionTreeImpl( - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameDOT), - rhs - ); - } - MethodInvocationTreeImpl t = new MethodInvocationTreeImpl( - memberSelect, - convertTypeArguments(e.typeArguments()), - arguments - ); - IMethodBinding binding = excludeRecovery(e.resolveMethodBinding(), arguments.size()); - t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); - rhs.setSymbol(sema.symbolOrUnknown(binding)); - usage(binding, rhs); - return t; - } - - private MethodInvocationTreeImpl convertMethodInvocation(SuperMethodInvocation e) { - ArgumentListTreeImpl arguments = convertArguments( - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - e.arguments(), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - - IdentifierTreeImpl rhs = createSimpleName(e.getName()); - - ExpressionTree outermostSelect; - if (e.getQualifier() == null) { - outermostSelect = new MemberSelectExpressionTreeImpl( - unqualifiedKeywordSuper(e), - firstTokenIn(e, TerminalToken.TokenNameDOT), - rhs - ); - } else { - final int firstDotTokenIndex = tokenManager.firstIndexAfter(e.getQualifier(), TerminalToken.TokenNameDOT); - AbstractTypedTree qualifier = (AbstractTypedTree) convertExpression(e.getQualifier()); - ITypeBinding qualifierBinding = e.getQualifier().resolveTypeBinding(); - KeywordSuper keywordSuper = new KeywordSuper(firstTokenAfter(e.getQualifier(), TerminalToken.TokenNamesuper), sema.superSymbolOrUnknown(qualifierBinding)); - MemberSelectExpressionTreeImpl qualifiedSuper = new MemberSelectExpressionTreeImpl( - (ExpressionTree) qualifier, - createSyntaxToken(firstDotTokenIndex), - keywordSuper - ); - if (qualifierBinding != null) { - qualifiedSuper.setSymbolType(sema.typeOrUnknown(qualifierBinding.getSuperclass())); - } - outermostSelect = new MemberSelectExpressionTreeImpl( - qualifiedSuper, - createSyntaxToken(nextTokenIndex(firstDotTokenIndex, TerminalToken.TokenNameDOT)), - rhs - ); - } - - MethodInvocationTreeImpl t = new MethodInvocationTreeImpl( - outermostSelect, - null, - arguments - ); - IMethodBinding binding = excludeRecovery(e.resolveMethodBinding(), arguments.size()); - t.setMethodSymbol(sema.methodSymbolOrUnknown(binding)); - rhs.setSymbol(sema.symbolOrUnknown(binding)); - usage(binding, rhs); - return t; - } - - private ParenthesizedTreeImpl convertParenthesizedExpression(ParenthesizedExpression e) { - return new ParenthesizedTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameRPAREN) - ); - } - - private InternalPostfixUnaryExpression convertPostfixExpression(PostfixExpression e) { - Op op = operators.get(e.getOperator()); - return new InternalPostfixUnaryExpression( - op.kind, - convertExpression(e.getOperand()), - firstTokenAfter(e.getOperand(), op.tokenType) - ); - } - - private InternalPrefixUnaryExpression convertPrefixExpression(PrefixExpression e) { - Op op = operators.get(e.getOperator()); - return new InternalPrefixUnaryExpression( - op.kind, - firstTokenIn(e, op.tokenType), - convertExpression(e.getOperand()) - ); - } - - private InstanceOfTreeImpl convertInstanceOf(InstanceofExpression e) { - Expression leftOperand = e.getLeftOperand(); - InternalSyntaxToken instanceofToken = firstTokenAfter(leftOperand, TerminalToken.TokenNameinstanceof); - return new InstanceOfTreeImpl(convertExpression(leftOperand), instanceofToken, convertType(e.getRightOperand())); - } - - private InstanceOfTreeImpl convertInstanceOf(PatternInstanceofExpression e) { - Expression leftOperand = e.getLeftOperand(); - InternalSyntaxToken instanceofToken = firstTokenAfter(leftOperand, TerminalToken.TokenNameinstanceof); - return new InstanceOfTreeImpl(convertExpression(leftOperand), instanceofToken, convertPattern(e.getPattern())); - } - - private LambdaExpressionTreeImpl convertLambdaExpression(LambdaExpression e) { - List parameters = new ArrayList<>(); - for (int i = 0; i < e.parameters().size(); i++) { - VariableDeclaration o = (VariableDeclaration) e.parameters().get(i); - VariableTreeImpl t; - if (o.getNodeType() == ASTNode.VARIABLE_DECLARATION_FRAGMENT) { - t = new VariableTreeImpl(createSimpleName(o.getName())); - IVariableBinding variableBinding = o.resolveBinding(); - if (variableBinding != null) { - t.setSymbol(sema.variableSymbolOrUnknown(variableBinding)); - ((InferedTypeTree) t.type()).setSymbolType(sema.typeOrUnknown(variableBinding.getType())); - declaration(variableBinding, t); - } - } else { - t = convertVariable((SingleVariableDeclaration) o); - } - parameters.add(t); - if (i < e.parameters().size() - 1) { - t.setEndToken(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } - } - ASTNode body = e.getBody(); - var tree = new LambdaExpressionTreeImpl( - e.hasParentheses() ? firstTokenIn(e, TerminalToken.TokenNameLPAREN) : null, - parameters, - e.hasParentheses() ? firstTokenBefore(body, TerminalToken.TokenNameRPAREN) : null, - firstTokenBefore(body, TerminalToken.TokenNameARROW), - body.getNodeType() == ASTNode.BLOCK ? convertBlock((Block) body) : convertExpression((Expression) body) - ); - tree.setSymbol(sema.methodSymbolOrUnknown(e.resolveMethodBinding())); - return tree; - } - - private MethodReferenceTreeImpl convertMethodReference(CreationReference e) { - MethodReferenceTreeImpl t = new MethodReferenceTreeImpl( - convertType(e.getType()), - lastTokenIn(e, TerminalToken.TokenNameCOLON_COLON) - ); - IdentifierTreeImpl i = new IdentifierTreeImpl(lastTokenIn(e, TerminalToken.TokenNamenew)); - IMethodBinding binding = e.resolveMethodBinding(); - i.setSymbol(sema.symbolOrUnknown(binding)); - usage(binding, i); - t.complete(convertTypeArguments(e.typeArguments()), i); - return t; - } - - private MethodReferenceTreeImpl convertMethodReference(ExpressionMethodReference e) { - MethodReferenceTreeImpl t = new MethodReferenceTreeImpl( - convertExpression(e.getExpression()), - firstTokenAfter(e.getExpression(), TerminalToken.TokenNameCOLON_COLON) - ); - IdentifierTreeImpl i = createSimpleName(e.getName()); - usage(e.getName().resolveBinding(), i); - t.complete(convertTypeArguments(e.typeArguments()), i); - return t; - } - - private MethodReferenceTreeImpl convertMethodReference(TypeMethodReference e) { - MethodReferenceTreeImpl t = new MethodReferenceTreeImpl( - convertType(e.getType()), - firstTokenAfter(e.getType(), TerminalToken.TokenNameCOLON_COLON) - ); - IdentifierTreeImpl i = createSimpleName(e.getName()); - usage(e.getName().resolveBinding(), i); - t.complete(convertTypeArguments(e.typeArguments()), i); - return t; - } - - private MethodReferenceTreeImpl convertMethodReference(SuperMethodReference e) { - MethodReferenceTreeImpl t; - if (e.getQualifier() != null) { - t = new MethodReferenceTreeImpl( - new MemberSelectExpressionTreeImpl( - convertExpression(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - unqualifiedKeywordSuper(e) - ), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameCOLON_COLON) - ); - } else { - t = new MethodReferenceTreeImpl( - unqualifiedKeywordSuper(e), - firstTokenIn(e, TerminalToken.TokenNameCOLON_COLON) - ); - } - IdentifierTreeImpl i = createSimpleName(e.getName()); - usage(e.getName().resolveBinding(), i); - t.complete(convertTypeArguments(e.typeArguments()), i); - return t; - } - - private SwitchExpressionTreeImpl convertSwitchExpression(SwitchExpression e) { - Expression expr = e.getExpression(); - return new SwitchExpressionTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameswitch), - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - convertExpression(expr), - firstTokenAfter(expr, TerminalToken.TokenNameRPAREN), - firstTokenAfter(expr, TerminalToken.TokenNameLBRACE), - convertSwitchStatements(e.statements()), - lastTokenIn(e, TerminalToken.TokenNameRBRACE) - ); - } - - private LiteralTreeImpl convertLiteral(NullLiteral e) { - return new LiteralTreeImpl(Tree.Kind.NULL_LITERAL, firstTokenIn(e, TerminalToken.TokenNamenull)); - } - - private ExpressionTree convertLiteral(NumberLiteral e) { - int tokenIndex = tokenManager.findIndex(e.getStartPosition(), ANY_TOKEN, true); - TerminalToken tokenType = tokenManager.get(tokenIndex).tokenType; - boolean unaryMinus = tokenType == TerminalToken.TokenNameMINUS; - if (unaryMinus) { - tokenIndex++; - tokenType = tokenManager.get(tokenIndex).tokenType; - } - ExpressionTree result; - switch (tokenType) { - case TokenNameIntegerLiteral: - result = new LiteralTreeImpl(Tree.Kind.INT_LITERAL, createSyntaxToken(tokenIndex)); - break; - case TokenNameLongLiteral: - result = new LiteralTreeImpl(Tree.Kind.LONG_LITERAL, createSyntaxToken(tokenIndex)); - break; - case TokenNameFloatingPointLiteral: - result = new LiteralTreeImpl(Tree.Kind.FLOAT_LITERAL, createSyntaxToken(tokenIndex)); - break; - case TokenNameDoubleLiteral: - result = new LiteralTreeImpl(Tree.Kind.DOUBLE_LITERAL, createSyntaxToken(tokenIndex)); - break; - default: - throw new IllegalStateException(); - } - ((LiteralTreeImpl) result).setSymbolType(sema.typeOrUnknown(e.resolveTypeBinding())); - if (unaryMinus) { - return new InternalPrefixUnaryExpression(Tree.Kind.UNARY_MINUS, createSyntaxToken(tokenIndex - 1), result); - } - return result; - } - - private LiteralTreeImpl convertLiteral(CharacterLiteral e) { - return new LiteralTreeImpl(Tree.Kind.CHAR_LITERAL, firstTokenIn(e, TerminalToken.TokenNameCharacterLiteral)); - } - - private LiteralTreeImpl convertLiteral(BooleanLiteral e) { - InternalSyntaxToken value = firstTokenIn(e, e.booleanValue() ? TerminalToken.TokenNametrue : TerminalToken.TokenNamefalse); - return new LiteralTreeImpl(Tree.Kind.BOOLEAN_LITERAL, value); - } - - private LiteralTreeImpl convertLiteral(StringLiteral e) { - return new LiteralTreeImpl(Tree.Kind.STRING_LITERAL, firstTokenIn(e, TerminalToken.TokenNameStringLiteral)); - } - - private LiteralTreeImpl convertTextBlock(TextBlock e) { - return new LiteralTreeImpl(Tree.Kind.TEXT_BLOCK, firstTokenIn(e, TerminalToken.TokenNameTextBlock)); - } - - private AnnotationTreeImpl convertAnnotation(Annotation e) { - ArgumentListTreeImpl arguments = ArgumentListTreeImpl.emptyList(); - if (e.getNodeType() == ASTNode.SINGLE_MEMBER_ANNOTATION) { - arguments.add(convertExpression(((SingleMemberAnnotation) e).getValue())); - arguments.complete( - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - } else if (e.getNodeType() == ASTNode.NORMAL_ANNOTATION) { - for (int i = 0; i < ((NormalAnnotation) e).values().size(); i++) { - MemberValuePair o = (MemberValuePair) ((NormalAnnotation) e).values().get(i); - arguments.add(new AssignmentExpressionTreeImpl( - Tree.Kind.ASSIGNMENT, - createSimpleName(o.getName()), - firstTokenAfter(o.getName(), TerminalToken.TokenNameEQUAL), - convertExpression(o.getValue()) - )); - if (i < ((NormalAnnotation) e).values().size() - 1) { - arguments.separators().add(firstTokenAfter(o, TerminalToken.TokenNameCOMMA)); - } - } - arguments.complete( - firstTokenIn(e, TerminalToken.TokenNameLPAREN), - lastTokenIn(e, TerminalToken.TokenNameRPAREN) - ); - } - return new AnnotationTreeImpl( - firstTokenIn(e, TerminalToken.TokenNameAT), - (TypeTree) convertExpression(e.getTypeName()), - arguments - ); - } - - private KeywordSuper unqualifiedKeywordSuper(ASTNode node) { - InternalSyntaxToken token = firstTokenIn(node, TerminalToken.TokenNamesuper); - do { - if (node instanceof AbstractTypeDeclaration abstractTypeDeclaration) { - return new KeywordSuper(token, sema.superSymbolOrUnknown(abstractTypeDeclaration.resolveBinding())); - } - if (node instanceof AnonymousClassDeclaration anonymousClassDeclaration) { - return new KeywordSuper(token, sema.superSymbolOrUnknown(anonymousClassDeclaration.resolveBinding())); - } - node = node.getParent(); - } while (true); - } - - private TypeTree convertType(Type node) { - switch (node.getNodeType()) { - case ASTNode.PRIMITIVE_TYPE: - return convertPrimitiveType((PrimitiveType) node); - case ASTNode.SIMPLE_TYPE: - return convertSimpleType((SimpleType) node); - case ASTNode.UNION_TYPE: - return convertUnionType((UnionType) node); - case ASTNode.ARRAY_TYPE: - return convertArrayType((ArrayType) node); - case ASTNode.PARAMETERIZED_TYPE: - return convertParameterizedType((ParameterizedType) node); - case ASTNode.QUALIFIED_TYPE: - return convertQualifiedType((QualifiedType) node); - case ASTNode.NAME_QUALIFIED_TYPE: - return convertNamedQualifiedType((NameQualifiedType) node); - case ASTNode.WILDCARD_TYPE: - return convertWildcardType((WildcardType) node); - default: - throw new IllegalStateException(ASTNode.nodeClassForType(node.getNodeType()).toString()); - } - } - - private JavaTree.PrimitiveTypeTreeImpl convertPrimitiveType(PrimitiveType e) { - final JavaTree.PrimitiveTypeTreeImpl t; - switch (e.getPrimitiveTypeCode().toString()) { - case "byte": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamebyte)); - break; - case "short": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNameshort)); - break; - case "char": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamechar)); - break; - case "int": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNameint)); - break; - case "long": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamelong)); - break; - case "float": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamefloat)); - break; - case "double": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamedouble)); - break; - case "boolean": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNameboolean)); - break; - case "void": - t = new JavaTree.PrimitiveTypeTreeImpl(lastTokenIn(e, TerminalToken.TokenNamevoid)); - break; - default: - throw new IllegalStateException(e.getPrimitiveTypeCode().toString()); - } - t.complete(convertAnnotations(e.annotations())); - t.setSymbolType(sema.typeOrUnknown(e.resolveBinding())); - return t; - } - - private JavaTree.AnnotatedTypeTree convertSimpleType(SimpleType e) { - List annotations = ((List) e.annotations()).stream() - .map(Annotation.class::cast) - .map(annotation -> (AnnotationTree) convertExpression(annotation)) - .toList(); - JavaTree.AnnotatedTypeTree t = e.isVar() ? convertVarType(e) : (JavaTree.AnnotatedTypeTree) convertExpression(e.getName()); - t.complete(annotations); - // typeBinding is assigned by convertVarType or convertExpression - return t; - } - - private JavaTree.UnionTypeTreeImpl convertUnionType(UnionType e) { - QualifiedIdentifierListTreeImpl alternatives = QualifiedIdentifierListTreeImpl.emptyList(); - for (int i = 0; i < e.types().size(); i++) { - Type o = (Type) e.types().get(i); - alternatives.add(convertType(o)); - if (i < e.types().size() - 1) { - alternatives.separators().add(firstTokenAfter(o, TerminalToken.TokenNameOR)); - } - } - JavaTree.UnionTypeTreeImpl t = new JavaTree.UnionTypeTreeImpl(alternatives); - t.setSymbolType(sema.typeOrUnknown(e.resolveBinding())); - return t; - } - - private TypeTree convertArrayType(ArrayType e) { - @Nullable ITypeBinding elementTypeBinding = e.getElementType().resolveBinding(); - TypeTree t = convertType(e.getElementType()); - int tokenIndex = tokenManager.firstIndexAfter(e.getElementType(), TerminalToken.TokenNameLBRACKET); - for (int i = 0; i < e.dimensions().size(); i++) { - if (i > 0) { - tokenIndex = nextTokenIndex(tokenIndex, TerminalToken.TokenNameLBRACKET); - } - t = new JavaTree.ArrayTypeTreeImpl( - t, - (List) convertAnnotations(((Dimension) e.dimensions().get(i)).annotations()), - createSyntaxToken(tokenIndex), - createSyntaxToken(nextTokenIndex(tokenIndex, TerminalToken.TokenNameRBRACKET)) - ); - if (elementTypeBinding != null) { - ((JavaTree.ArrayTypeTreeImpl) t).setSymbolType(sema.typeOrUnknown(elementTypeBinding.createArrayType(i + 1))); - } - } - return t; - } - - private JavaTree.ParameterizedTypeTreeImpl convertParameterizedType(ParameterizedType e) { - LineColumnConverter.Pos pos = lineColumnConverter.toPos(e.getStartPosition() + e.getLength() - 1); - JavaTree.ParameterizedTypeTreeImpl t = new JavaTree.ParameterizedTypeTreeImpl( - convertType(e.getType()), - convertTypeArguments( - firstTokenAfter(e.getType(), TerminalToken.TokenNameLESS), - e.typeArguments(), - new InternalSyntaxToken( - pos.line(), - pos.columnOffset(), - ">", - /* TODO */ Collections.emptyList(), - false - ) - ) - ); - t.setSymbolType(sema.typeOrUnknown(e.resolveBinding())); - return t; - } - - private MemberSelectExpressionTreeImpl convertQualifiedType(QualifiedType e) { - MemberSelectExpressionTreeImpl t = new MemberSelectExpressionTreeImpl( - (ExpressionTree) convertType(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - createSimpleName(e.getName()) - ); - ((IdentifierTreeImpl) t.identifier()).complete(convertAnnotations(e.annotations())); - t.setSymbolType(sema.typeOrUnknown(e.resolveBinding())); - return t; - } - - private MemberSelectExpressionTreeImpl convertNamedQualifiedType(NameQualifiedType e) { - MemberSelectExpressionTreeImpl t = new MemberSelectExpressionTreeImpl( - convertExpression(e.getQualifier()), - firstTokenAfter(e.getQualifier(), TerminalToken.TokenNameDOT), - createSimpleName(e.getName()) - ); - ((IdentifierTreeImpl) t.identifier()).complete(convertAnnotations(e.annotations())); - t.setSymbolType(sema.typeOrUnknown(e.resolveBinding())); - return t; - } - - private JavaTree.WildcardTreeImpl convertWildcardType(WildcardType e) { - final InternalSyntaxToken questionToken = e.annotations().isEmpty() - ? firstTokenIn(e, TerminalToken.TokenNameQUESTION) - : firstTokenAfter((ASTNode) e.annotations().get(e.annotations().size() - 1), TerminalToken.TokenNameQUESTION); - JavaTree.WildcardTreeImpl t; - Type bound = e.getBound(); - if (bound == null) { - t = new JavaTree.WildcardTreeImpl(questionToken); - } else { - boolean isUpperBound = e.isUpperBound(); - t = new JavaTree.WildcardTreeImpl( - isUpperBound ? Tree.Kind.EXTENDS_WILDCARD : Tree.Kind.SUPER_WILDCARD, - firstTokenBefore(bound, isUpperBound ? TerminalToken.TokenNameextends : TerminalToken.TokenNamesuper), - convertType(bound) - ).complete(questionToken); - } - t.complete(convertAnnotations(e.annotations())); - t.setSymbolType(sema.typeOrUnknown(e.resolveBinding())); - return t; - } - - @Nullable - private static IMethodBinding excludeRecovery(@Nullable IMethodBinding methodBinding, int arguments) { - if (methodBinding == null) { - return null; - } - if (methodBinding.isVarargs()) { - if (arguments + 1 < methodBinding.getParameterTypes().length) { - return null; - } - } else { - if (arguments != methodBinding.getParameterTypes().length) { - return null; - } - } - return methodBinding; - } - - @Nullable - private static IMethodBinding findConstructorForAnonymousClass(AST ast, @Nullable ITypeBinding typeBinding, @Nullable IMethodBinding methodBinding) { - if (typeBinding == null || methodBinding == null) { - return null; - } - if (typeBinding.isInterface()) { - typeBinding = ast.resolveWellKnownType("java.lang.Object"); - } - return Stream.of(typeBinding.getDeclaredMethods()) - .filter(methodBinding::isSubsignature) - .findFirst() - .orElse(null); - } - - private List convertAnnotations(List e) { - return e.stream() - .map(o -> (AnnotationTree) convertExpression((Annotation) o)) - .toList(); - } - - private ModifiersTreeImpl convertModifiers(List source) { - return new ModifiersTreeImpl(source.stream() - .map(o -> convertModifier((IExtendedModifier) o)) - .toList()); - } - - private ModifierTree convertModifier(IExtendedModifier node) { - switch (((ASTNode) node).getNodeType()) { - case ASTNode.NORMAL_ANNOTATION, - ASTNode.MARKER_ANNOTATION, - ASTNode.SINGLE_MEMBER_ANNOTATION: - return (AnnotationTree) convertExpression((Expression) node); - case ASTNode.MODIFIER: - return convertModifier((org.eclipse.jdt.core.dom.Modifier) node); - default: - throw new IllegalStateException(ASTNode.nodeClassForType(((ASTNode) node).getNodeType()).toString()); - } - } - - private ModifierTree convertModifier(org.eclipse.jdt.core.dom.Modifier node) { - switch (node.getKeyword().toString()) { - case "public": - return new ModifierKeywordTreeImpl(Modifier.PUBLIC, firstTokenIn(node, TerminalToken.TokenNamepublic)); - case "protected": - return new ModifierKeywordTreeImpl(Modifier.PROTECTED, firstTokenIn(node, TerminalToken.TokenNameprotected)); - case "private": - return new ModifierKeywordTreeImpl(Modifier.PRIVATE, firstTokenIn(node, TerminalToken.TokenNameprivate)); - case "static": - return new ModifierKeywordTreeImpl(Modifier.STATIC, firstTokenIn(node, TerminalToken.TokenNamestatic)); - case "abstract": - return new ModifierKeywordTreeImpl(Modifier.ABSTRACT, firstTokenIn(node, TerminalToken.TokenNameabstract)); - case "final": - return new ModifierKeywordTreeImpl(Modifier.FINAL, firstTokenIn(node, TerminalToken.TokenNamefinal)); - case "native": - return new ModifierKeywordTreeImpl(Modifier.NATIVE, firstTokenIn(node, TerminalToken.TokenNamenative)); - case "synchronized": - return new ModifierKeywordTreeImpl(Modifier.SYNCHRONIZED, firstTokenIn(node, TerminalToken.TokenNamesynchronized)); - case "transient": - return new ModifierKeywordTreeImpl(Modifier.TRANSIENT, firstTokenIn(node, TerminalToken.TokenNametransient)); - case "volatile": - return new ModifierKeywordTreeImpl(Modifier.VOLATILE, firstTokenIn(node, TerminalToken.TokenNamevolatile)); - case "strictfp": - return new ModifierKeywordTreeImpl(Modifier.STRICTFP, firstTokenIn(node, TerminalToken.TokenNamestrictfp)); - case "default": - return new ModifierKeywordTreeImpl(Modifier.DEFAULT, firstTokenIn(node, TerminalToken.TokenNamedefault)); - case "sealed": - return new ModifierKeywordTreeImpl(Modifier.SEALED, firstTokenIn(node, TerminalToken.TokenNameRestrictedIdentifiersealed)); - case "non-sealed": { - return new ModifierKeywordTreeImpl(Modifier.NON_SEALED, firstTokenIn(node, TerminalToken.TokenNamenon_sealed)); - } - default: - throw new IllegalStateException(node.getKeyword().toString()); - } - } - - private static final TerminalToken ANY_TOKEN = TerminalToken.TokenNameInvalid; - - private static final Map operators = new HashMap<>(); - - private static class Op { - final Tree.Kind kind; - - /** - * {@link TerminalToken} - */ - final TerminalToken tokenType; - - Op(Tree.Kind kind, TerminalToken tokenType) { - this.kind = kind; - this.tokenType = tokenType; - } - } - - static { - operators.put(PrefixExpression.Operator.PLUS, new Op(Tree.Kind.UNARY_PLUS, TerminalToken.TokenNamePLUS)); - operators.put(PrefixExpression.Operator.MINUS, new Op(Tree.Kind.UNARY_MINUS, TerminalToken.TokenNameMINUS)); - operators.put(PrefixExpression.Operator.NOT, new Op(Tree.Kind.LOGICAL_COMPLEMENT, TerminalToken.TokenNameNOT)); - operators.put(PrefixExpression.Operator.COMPLEMENT, new Op(Tree.Kind.BITWISE_COMPLEMENT, TerminalToken.TokenNameTWIDDLE)); - operators.put(PrefixExpression.Operator.DECREMENT, new Op(Tree.Kind.PREFIX_DECREMENT, TerminalToken.TokenNameMINUS_MINUS)); - operators.put(PrefixExpression.Operator.INCREMENT, new Op(Tree.Kind.PREFIX_INCREMENT, TerminalToken.TokenNamePLUS_PLUS)); - - operators.put(PostfixExpression.Operator.DECREMENT, new Op(Tree.Kind.POSTFIX_DECREMENT, TerminalToken.TokenNameMINUS_MINUS)); - operators.put(PostfixExpression.Operator.INCREMENT, new Op(Tree.Kind.POSTFIX_INCREMENT, TerminalToken.TokenNamePLUS_PLUS)); - - operators.put(InfixExpression.Operator.TIMES, new Op(Tree.Kind.MULTIPLY, TerminalToken.TokenNameMULTIPLY)); - operators.put(InfixExpression.Operator.DIVIDE, new Op(Tree.Kind.DIVIDE, TerminalToken.TokenNameDIVIDE)); - operators.put(InfixExpression.Operator.REMAINDER, new Op(Tree.Kind.REMAINDER, TerminalToken.TokenNameREMAINDER)); - operators.put(InfixExpression.Operator.PLUS, new Op(Tree.Kind.PLUS, TerminalToken.TokenNamePLUS)); - operators.put(InfixExpression.Operator.MINUS, new Op(Tree.Kind.MINUS, TerminalToken.TokenNameMINUS)); - operators.put(InfixExpression.Operator.LEFT_SHIFT, new Op(Tree.Kind.LEFT_SHIFT, TerminalToken.TokenNameLEFT_SHIFT)); - operators.put(InfixExpression.Operator.RIGHT_SHIFT_SIGNED, new Op(Tree.Kind.RIGHT_SHIFT, TerminalToken.TokenNameRIGHT_SHIFT)); - operators.put(InfixExpression.Operator.RIGHT_SHIFT_UNSIGNED, new Op(Tree.Kind.UNSIGNED_RIGHT_SHIFT, TerminalToken.TokenNameUNSIGNED_RIGHT_SHIFT)); - operators.put(InfixExpression.Operator.LESS, new Op(Tree.Kind.LESS_THAN, TerminalToken.TokenNameLESS)); - operators.put(InfixExpression.Operator.GREATER, new Op(Tree.Kind.GREATER_THAN, TerminalToken.TokenNameGREATER)); - operators.put(InfixExpression.Operator.LESS_EQUALS, new Op(Tree.Kind.LESS_THAN_OR_EQUAL_TO, TerminalToken.TokenNameLESS_EQUAL)); - operators.put(InfixExpression.Operator.GREATER_EQUALS, new Op(Tree.Kind.GREATER_THAN_OR_EQUAL_TO, TerminalToken.TokenNameGREATER_EQUAL)); - operators.put(InfixExpression.Operator.EQUALS, new Op(Tree.Kind.EQUAL_TO, TerminalToken.TokenNameEQUAL_EQUAL)); - operators.put(InfixExpression.Operator.NOT_EQUALS, new Op(Tree.Kind.NOT_EQUAL_TO, TerminalToken.TokenNameNOT_EQUAL)); - operators.put(InfixExpression.Operator.XOR, new Op(Tree.Kind.XOR, TerminalToken.TokenNameXOR)); - operators.put(InfixExpression.Operator.OR, new Op(Tree.Kind.OR, TerminalToken.TokenNameOR)); - operators.put(InfixExpression.Operator.AND, new Op(Tree.Kind.AND, TerminalToken.TokenNameAND)); - operators.put(InfixExpression.Operator.CONDITIONAL_OR, new Op(Tree.Kind.CONDITIONAL_OR, TerminalToken.TokenNameOR_OR)); - operators.put(InfixExpression.Operator.CONDITIONAL_AND, new Op(Tree.Kind.CONDITIONAL_AND, TerminalToken.TokenNameAND_AND)); - - operators.put(Assignment.Operator.ASSIGN, new Op(Tree.Kind.ASSIGNMENT, TerminalToken.TokenNameEQUAL)); - operators.put(Assignment.Operator.PLUS_ASSIGN, new Op(Tree.Kind.PLUS_ASSIGNMENT, TerminalToken.TokenNamePLUS_EQUAL)); - operators.put(Assignment.Operator.MINUS_ASSIGN, new Op(Tree.Kind.MINUS_ASSIGNMENT, TerminalToken.TokenNameMINUS_EQUAL)); - operators.put(Assignment.Operator.TIMES_ASSIGN, new Op(Tree.Kind.MULTIPLY_ASSIGNMENT, TerminalToken.TokenNameMULTIPLY_EQUAL)); - operators.put(Assignment.Operator.DIVIDE_ASSIGN, new Op(Tree.Kind.DIVIDE_ASSIGNMENT, TerminalToken.TokenNameDIVIDE_EQUAL)); - operators.put(Assignment.Operator.BIT_AND_ASSIGN, new Op(Tree.Kind.AND_ASSIGNMENT, TerminalToken.TokenNameAND_EQUAL)); - operators.put(Assignment.Operator.BIT_OR_ASSIGN, new Op(Tree.Kind.OR_ASSIGNMENT, TerminalToken.TokenNameOR_EQUAL)); - operators.put(Assignment.Operator.BIT_XOR_ASSIGN, new Op(Tree.Kind.XOR_ASSIGNMENT, TerminalToken.TokenNameXOR_EQUAL)); - operators.put(Assignment.Operator.REMAINDER_ASSIGN, new Op(Tree.Kind.REMAINDER_ASSIGNMENT, TerminalToken.TokenNameREMAINDER_EQUAL)); - operators.put(Assignment.Operator.LEFT_SHIFT_ASSIGN, new Op(Tree.Kind.LEFT_SHIFT_ASSIGNMENT, TerminalToken.TokenNameLEFT_SHIFT_EQUAL)); - operators.put(Assignment.Operator.RIGHT_SHIFT_SIGNED_ASSIGN, new Op(Tree.Kind.RIGHT_SHIFT_ASSIGNMENT, TerminalToken.TokenNameRIGHT_SHIFT_EQUAL)); - operators.put(Assignment.Operator.RIGHT_SHIFT_UNSIGNED_ASSIGN, new Op(Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, TerminalToken.TokenNameUNSIGNED_RIGHT_SHIFT_EQUAL)); - } - } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java b/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java index 79cffd79a90..37aee277ec6 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JParserConfig.java @@ -16,32 +16,22 @@ */ package org.sonar.java.model; +import com.google.gson.JsonObject; import java.io.File; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.concurrent.TimeUnit; +import org.sonar.java.semsitter.SemsitterBridge; import java.util.function.BiConsumer; import java.util.function.BooleanSupplier; -import java.util.stream.StreamSupport; -import org.eclipse.core.runtime.OperationCanceledException; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.CompilationUnit; -import org.eclipse.jdt.core.dom.FileASTRequestor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.InputFile; -import org.sonar.java.AnalysisException; import org.sonar.java.AnalysisProgress; import org.sonar.java.ExecutionTimeReport; -import org.sonar.java.ProgressMonitor; import org.sonar.java.annotations.VisibleForTesting; import org.sonar.plugins.java.api.JavaVersion; import org.sonarsource.analyzer.commons.ProgressReport; @@ -53,12 +43,10 @@ public abstract class JParserConfig { private static final Logger LOG = LoggerFactory.getLogger(JParserConfig.class); - private static final String MAXIMUM_ECJ_WARNINGS = "42000"; - private static final Set JRE_JARS = new HashSet<>(Arrays.asList("rt.jar", "jrt-fs.jar", "android.jar")); - final JavaVersion javaVersion; final List classpath; final boolean shouldIgnoreUnnamedModuleForSplitPackage; + private SemsitterBridge bridge; private JParserConfig(JavaVersion javaVersion, List classpath, boolean shouldIgnoreUnnamedModuleForSplitPackage) { this.javaVersion = javaVersion; @@ -113,36 +101,6 @@ public JavaTree.CompilationUnitTreeImpl get() throws Exception { } } - public ASTParser astParser() { - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - Map options = new HashMap<>(JavaCore.getOptions()); - JavaCore.setComplianceOptions(javaVersion.effectiveJavaVersionAsString(), options); - options.put(JavaCore.COMPILER_PB_MAX_PER_UNIT, MAXIMUM_ECJ_WARNINGS); - if (shouldIgnoreUnnamedModuleForSplitPackage) { - options.put(JavaCore.COMPILER_IGNORE_UNNAMED_MODULE_FOR_SPLIT_PACKAGE, "enabled"); - } - if (shouldEnablePreviewFlag(javaVersion)) { - options.put(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, "enabled"); - } - // enabling all supported compiler warnings - JProblem.Type.compilerOptions() - .forEach(option -> options.put(option, "warning")); - - astParser.setCompilerOptions(options); - - boolean includeRunningVMBootclasspath = classpath.stream() - .noneMatch(f -> JRE_JARS.contains(f.getName())); - - astParser.setEnvironment(classpath.stream() - .map(File::getAbsolutePath) - .toArray(String[]::new), new String[] {}, new String[] {}, includeRunningVMBootclasspath); - - astParser.setResolveBindings(true); - astParser.setBindingsRecovery(true); - - return astParser; - } - @VisibleForTesting static class Batch extends JParserConfig { @@ -153,70 +111,24 @@ static class Batch extends JParserConfig { @Override public void parse(Iterable inputFiles, BooleanSupplier isCanceled, AnalysisProgress analysisProgress, BiConsumer action) { - - List sourceFilePaths = new ArrayList<>(); - Set notYetAnalyzedFiles = new LinkedHashSet<>(); - List encodings = new ArrayList<>(); - Map inputs = new HashMap<>(); - for (InputFile inputFile : inputFiles) { - String sourceFilePath = inputFile.absolutePath(); - inputs.put(new File(sourceFilePath), inputFile); - sourceFilePaths.add(sourceFilePath); - encodings.add(inputFile.charset().name()); - notYetAnalyzedFiles.add(inputFile); - } - ExecutionTimeReport executionTimeReport = new ExecutionTimeReport(); - ProgressMonitor monitor = new ProgressMonitor(isCanceled, analysisProgress); PerformanceMeasure.Duration batchPerformance = PerformanceMeasure.start("ParseAsBatch"); + List files = new ArrayList<>(); + inputFiles.forEach(files::add); try { - astParser().createASTs(sourceFilePaths.toArray(new String[0]), encodings.toArray(new String[0]), new String[0], new FileASTRequestor() { - @Override - public void acceptAST(String sourceFilePath, CompilationUnit ast) { - PerformanceMeasure.Duration convertDuration = PerformanceMeasure.start("Convert"); - InputFile inputFile = inputs.get(new File(sourceFilePath)); - executionTimeReport.start(inputFile); - Result result; - try { - result = new Result(JParser.convert(javaVersion.effectiveJavaVersionAsString(), inputFile.filename(), inputFile.contents(), ast)); - } catch (Exception e) { - result = new Result(e); - } - convertDuration.stop(); - PerformanceMeasure.Duration analyzeDuration = PerformanceMeasure.start("Analyze"); - action.accept(inputFile, result); - - notYetAnalyzedFiles.remove(inputFile); - executionTimeReport.end(); - analyzeDuration.stop(); + openBridge(files); + for (InputFile inputFile : files) { + if (isCanceled.getAsBoolean()) { + break; } - }, monitor); - if (!notYetAnalyzedFiles.isEmpty()) { - String message = String.format("%d/%d files were not analyzed by the batch mode", notYetAnalyzedFiles.size(), sourceFilePaths.size()); - throw new AnalysisException(message); - } - } catch (OperationCanceledException e) { - throw e; - } catch (RuntimeException e) { - LOG.warn("Unexpected {}: {}", e.getClass().getSimpleName(), e.getMessage()); - if (!notYetAnalyzedFiles.isEmpty()) { - fallbackToFileByFileMode(notYetAnalyzedFiles.stream().toList(), isCanceled, action); + executionTimeReport.start(inputFile); + action.accept(inputFile, parseFile(inputFile)); + executionTimeReport.end(); } } finally { + closeBridge(); batchPerformance.stop(); - // ExecutionTimeReport will not include the parsing time by file when using batch mode. executionTimeReport.reportAsBatch(); - monitor.done(); - } - } - - private void fallbackToFileByFileMode(List inputFiles, BooleanSupplier isCanceled, BiConsumer action) { - LOG.warn("Fallback to file by file analysis for {} files", inputFiles.size()); - for (InputFile inputFile : inputFiles) { - if (isCanceled.getAsBoolean()) { - break; - } - FileByFile.parse(astParser(), inputFile, javaVersion, action); } } @@ -236,23 +148,25 @@ public void parse(Iterable inputFiles, BooleanSupplier isCa ExecutionTimeReport executionTimeReport = new ExecutionTimeReport(); ProgressReport progressReport = new ProgressReport("Report about progress of Java AST analyzer", TimeUnit.SECONDS.toMillis(10)); - List filesNames = StreamSupport.stream(inputFiles.spliterator(), false) - .map(InputFile::toString) - .toList(); + List files = new ArrayList<>(); + inputFiles.forEach(files::add); + List filesNames = files.stream().map(InputFile::toString).toList(); progressReport.start(filesNames); try { - for (InputFile inputFile : inputFiles) { + openBridge(files); + for (InputFile inputFile : files) { if (isCanceled.getAsBoolean()) { cancelled = true; break; } executionTimeReport.start(inputFile); - parse(astParser(), inputFile, javaVersion, action); + action.accept(inputFile, parseFile(inputFile)); executionTimeReport.end(); progressReport.nextFile(); } successfullyCompleted = !cancelled; } finally { + closeBridge(); if (successfullyCompleted) { progressReport.stop(); } else { @@ -261,18 +175,69 @@ public void parse(Iterable inputFiles, BooleanSupplier isCa executionTimeReport.report(); } } + } - private static void parse(ASTParser astParser, InputFile inputFile, JavaVersion javaVersion, BiConsumer action) { - Result result; - PerformanceMeasure.Duration parseDuration = PerformanceMeasure.start("JParser"); - try { - result = new Result(JParser.parse(astParser, javaVersion.effectiveJavaVersionAsString(), inputFile.filename(), inputFile.contents())); - } catch (Exception e) { - result = new Result(e); - } finally { - parseDuration.stop(); + final Result parseFile(InputFile inputFile) { + PerformanceMeasure.Duration parseDuration = PerformanceMeasure.start("JParser"); + try { + if (bridge == null) { + return new Result(JParser.parse(javaVersion.effectiveJavaVersionAsString(), inputFile.filename(), inputFile.contents())); + } + JsonObject document = bridge.cst(inputFile.path().toAbsolutePath().toString()); + JSema sema = new JSema(); + sema.ingestDocument(document); + sema.loadTypes(names -> { + if (bridge == null) { + return null; + } + try { + return bridge.types(names); + } catch (IOException failure) { + throw new UncheckedIOException(failure); + } + }); + return new Result(SemsitterTranslator.translate(sema, document)); + } catch (Exception e) { + return new Result(e); + } finally { + parseDuration.stop(); + } + } + + void openBridge(List files) { + try { + bridge = SemsitterBridge.start(JParser.bridgePath()); + if (!files.isEmpty()) { + Path root = files.get(0).path().toAbsolutePath().getParent(); + bridge.analyze( + root == null ? Path.of(".") : root, + files.stream().map(file -> file.path().toAbsolutePath()).toList(), + bridgeClasspath(classpath)); } - action.accept(inputFile, result); + } catch (Exception failure) { + closeBridge(); + LOG.warn("SemSitter bridge did not start ({})", failure.toString()); + } + } + + /** + * The project classpath preceded by the runtime image of the running JDK, which is the JDK ECJ resolved + * against, so JDK classes win over a library copy as on the boot classpath. + */ + static List bridgeClasspath(List classpath) { + List entries = new ArrayList<>(); + Path image = Path.of(System.getProperty("java.home"), "lib", "modules"); + if (image.toFile().isFile()) { + entries.add(image.toString()); + } + classpath.stream().map(File::getAbsolutePath).forEach(entries::add); + return entries; + } + + void closeBridge() { + if (bridge != null) { + bridge.close(); + bridge = null; } } @@ -280,6 +245,7 @@ private static void parse(ASTParser astParser, InputFile inputFile, JavaVersion static boolean shouldEnablePreviewFlag(JavaVersion currentVersion) { return currentVersion.arePreviewFeaturesEnabled(); } + @FunctionalInterface public interface ParserConfigConstructor { JParserConfig apply(JavaVersion version, List files, Boolean shouldIgnoreUnnamedModuleForSplitPackage); diff --git a/java-frontend/src/main/java/org/sonar/java/model/JProblem.java b/java-frontend/src/main/java/org/sonar/java/model/JProblem.java index 38abd459dad..e6102354e53 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JProblem.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JProblem.java @@ -21,8 +21,6 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Stream; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.compiler.IProblem; import org.sonar.plugins.java.api.tree.Tree; import org.sonarsource.analyzer.commons.collections.SetUtils; @@ -80,12 +78,12 @@ public int hashCode() { } public enum Type { - UNDEFINED_TYPE(IProblem.UndefinedType), - PREVIEW_FEATURE_USED(IProblem.PreviewFeatureUsed), - UNUSED_IMPORT(IProblem.UnusedImport, JavaCore.COMPILER_PB_UNUSED_IMPORT, Tree.Kind.IMPORT), - REDUNDANT_CAST(IProblem.UnnecessaryCast, JavaCore.COMPILER_PB_UNNECESSARY_TYPE_CHECK, Tree.Kind.TYPE_CAST, Tree.Kind.PARENTHESIZED_EXPRESSION), - ASSIGNMENT_HAS_NO_EFFECT(IProblem.AssignmentHasNoEffect, JavaCore.COMPILER_PB_NO_EFFECT_ASSIGNMENT, Tree.Kind.ASSIGNMENT), - MASKED_CATCH(IProblem.MaskedCatch, JavaCore.COMPILER_PB_HIDDEN_CATCH_BLOCK, Tree.Kind.IDENTIFIER, Tree.Kind.MEMBER_SELECT); + UNDEFINED_TYPE(16777218), + PREVIEW_FEATURE_USED(4195408), + UNUSED_IMPORT(268435844, "org.eclipse.jdt.core.compiler.problem.unusedImport", Tree.Kind.IMPORT), + REDUNDANT_CAST(553648309, "org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck", Tree.Kind.TYPE_CAST, Tree.Kind.PARENTHESIZED_EXPRESSION), + ASSIGNMENT_HAS_NO_EFFECT(536871090, "org.eclipse.jdt.core.compiler.problem.noEffectAssignment", Tree.Kind.ASSIGNMENT), + MASKED_CATCH(16777381, "org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock", Tree.Kind.IDENTIFIER, Tree.Kind.MEMBER_SELECT); private final int warningID; private final String compilerOptionKey; @@ -105,8 +103,8 @@ public enum Type { this.kinds = SetUtils.immutableSetOf(kinds); } - boolean matches(IProblem warning) { - return warning.getID() == warningID; + boolean matches(int problemId) { + return problemId == warningID; } public static Set compilerOptions() { diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSema.java b/java-frontend/src/main/java/org/sonar/java/model/JSema.java index 573ac8400cc..be8d59a5a3a 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSema.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSema.java @@ -16,216 +16,1861 @@ */ package org.sonar.java.model; -import java.util.Arrays; -import java.util.Collections; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.regex.Pattern; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTUtils; -import org.eclipse.jdt.core.dom.IAnnotationBinding; -import org.eclipse.jdt.core.dom.IBinding; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.IPackageBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.IVariableBinding; import org.sonar.plugins.java.api.semantic.Sema; import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.Tree; +/** + * Semantic model of one translated file. Symbols are created from the SemSitter dump and from the + * {@code types} response (project graph, classpath declarations including the JDK image, embedded JDK table). + */ public final class JSema implements Sema { + private static final String ANNOTATIONS = "annotations"; + private static final String STATIC = "static"; + private static final String ABSTRACT = "abstract"; + private static final String RECORD = "record"; + private static final String ARRAY = "array"; + + + private static final Set PRIMITIVES = Set.of("byte", "short", "int", "long", "char", "float", "double", "boolean", "void"); + private static final Set OBJECT_METHODS = Set.of("equals/1", "hashCode/0", "toString/0"); + private static final Pattern LAMBDA_SEGMENTS = Pattern.compile("^(\\$lambda\\$[^$()]*\\(\\))+"); + + private final Map facts = new HashMap<>(); + private final Map dumpSymbols = new HashMap<>(); + private final Map typeInfos = new HashMap<>(); + private final Map> libraryAnnotations = new HashMap<>(); + private final Map symbols = new HashMap<>(); + private final Map typesByName = new HashMap<>(); + private final Map rawTypes = new HashMap<>(); + private final Map binaryNames = new HashMap<>(); + private final Map sourceNames = new HashMap<>(); + final Map declarations = new HashMap<>(); + final Map> usages = new HashMap<>(); + private final Set undefinedTypes = new HashSet<>(); + private final Set creating = new HashSet<>(); + private final Set requestedTypes = new HashSet<>(); + private Function, JsonObject> typeLoader = names -> new JsonObject(); + private boolean typesLoaded; + private final JSymbol unknownSymbol = new JVariableSymbol(this, null, "", "local"); + + public JSema() { + unknownSymbol.unknown(true); + } + + /** + * Reads the symbols and graph facts of one CST document. Type declarations already known by + * erased name are reused, so a later {@link #ingestTypes} call fills them in instead of + * creating a second symbol. + */ + public void ingestDocument(JsonObject document) { + JsonObject factObject = document.getAsJsonObject("facts"); + if (factObject != null) { + for (Map.Entry entry : factObject.entrySet()) { + facts.put(entry.getKey(), entry.getValue().getAsJsonObject()); + } + } + JsonArray symbolArray = document.getAsJsonArray("symbols"); + if (symbolArray != null) { + for (JsonElement element : symbolArray) { + JsonObject symbol = element.getAsJsonObject(); + String id = text(symbol, "id"); + if (id != null) { + dumpSymbols.put(id, symbol); + if ("type".equals(text(symbol, "kind")) && id.indexOf('<') < 0) { + binaryName(erase(id)); + } + } + } + } + for (String id : dumpSymbols.keySet()) { + createDumpSymbol(id); + } + for (String id : new ArrayList<>(dumpSymbols.keySet())) { + linkDumpSymbol(id); + } + } + + /** + * The binary name Java gives the type SemSitter names {@code id}: a local class {@code p.C#f()$Local} becomes + * {@code p.C$1Local}, numbered by the first index not yet taken in its enclosing class, in declaration order. + */ + private String binaryName(String id) { + String known = binaryNames.get(id); + if (known != null) { + return known; + } + int hash = id.lastIndexOf('#'); + int open = id.indexOf('(', hash); + int close = open < 0 ? -1 : id.indexOf(')', open); + String rest = close < 0 ? "" : LAMBDA_SEGMENTS.matcher(id.substring(close + 1)).replaceFirst(""); + if (hash < 0 || !rest.startsWith("$") || rest.length() == 1) { + return id; + } + int nested = rest.indexOf('$', 1); + String name = nested < 0 ? rest.substring(1) : rest.substring(1, nested); + String localId = id.substring(0, close + 1) + "$" + name; + String local = binaryNames.get(localId); + if (local == null) { + String enclosing = binaryName(id.substring(0, hash)); + int index = 1; + while (sourceNames.containsKey(enclosing + "$" + index + name)) { + index++; + } + local = enclosing + "$" + index + name; + binaryNames.put(localId, local); + sourceNames.put(local, localId); + } + String binary = nested < 0 ? local : local + rest.substring(nested); + binaryNames.put(id, binary); + sourceNames.put(binary, id); + return binary; + } + + private String sourceName(String fqn) { + return sourceNames.getOrDefault(fqn, fqn); + } - private final AST ast; - final Map declarations = new HashMap<>(); - final Map> usages = new HashMap<>(); - private final Map types = new HashMap<>(); - private final Map symbols = new HashMap<>(); - private final Map initializerBlockSymbols = new HashMap<>(); - private final Map staticInitializerBlockSymbols = new HashMap<>(); - private final Map annotations = new HashMap<>(); - private final Map nameToTypeCache = new HashMap<>(); + /** + * Loads the declarations of every type the document references, then keeps {@code loader} to + * fetch the types reached later, like the return type of a library method. + */ + public void loadTypes(Function, JsonObject> loader) { + typeLoader = loader; + Set names = referencedTypeNames(); + requestedTypes.addAll(names); + ingestTypes(loader.apply(names)); + } - JSema(AST ast) { - this.ast = ast; + public void ingestTypes(JsonObject types) { + typesLoaded = true; + for (Map.Entry entry : types.entrySet()) { + JsonObject info = entry.getValue().getAsJsonObject(); + typeInfos.put(entry.getKey(), info); + JTypeSymbol symbol = typesByName.get(entry.getKey()); + if (symbol == null) { + symbol = new JTypeSymbol(this, entry.getKey(), simpleName(entry.getKey()), false); + symbol.owner(packageSymbol(packageName(entry.getKey()))); + symbol.type(rawNamed(entry.getKey())); + typesByName.put(entry.getKey(), symbol); + symbols.putIfAbsent(entry.getKey(), symbol); + } + applyTypeInfo(symbol, info); + } + } + + public Set referencedTypeNames() { + Set names = new HashSet<>(); + for (String id : facts.keySet()) { + addTypeName(names, id); + } + for (JsonObject symbol : dumpSymbols.values()) { + addTypeName(names, text(symbol, "id")); + JsonObject type = symbol.getAsJsonObject("facts"); + if (type != null && type.getAsJsonObject("type") != null) { + collectFactNames(names, type.getAsJsonObject("type")); + } + } + return names; } - Type typeOrUnknown(@Nullable ITypeBinding binding) { - return binding == null ? Type.UNKNOWN : type(binding); + private static void addTypeName(Set names, @Nullable String id) { + if (id == null || id.isEmpty()) { + return; + } + int hash = id.indexOf('#'); + String owner = hash < 0 ? id : id.substring(0, hash); + if (owner.indexOf('%') >= 0 || owner.indexOf('(') >= 0 || owner.indexOf('<') >= 0 && hash < 0) { + return; + } + String erased = erase(owner); + if (!erased.isEmpty() && !PRIMITIVES.contains(erased) && erased.indexOf(' ') < 0) { + names.add(erased); + } } - Symbol.TypeSymbol typeSymbolOrUnknown(@Nullable ITypeBinding binding) { - return binding == null ? Symbol.TypeSymbol.UNKNOWN_TYPE : typeSymbol(binding); + private static void collectFactNames(Set names, JsonObject fact) { + String id = text(fact, "id"); + if (id != null) { + addTypeName(names, id); + } + if (fact.has("args")) { + for (JsonElement arg : fact.getAsJsonArray("args")) { + if (arg.isJsonObject()) { + collectFactNames(names, arg.getAsJsonObject()); + } + } + } + if (fact.has("elem") && fact.get("elem").isJsonObject()) { + collectFactNames(names, fact.getAsJsonObject("elem")); + } } - Symbol.MethodSymbol methodSymbolOrUnknown(@Nullable IMethodBinding binding) { - return binding == null ? Symbol.MethodSymbol.UNKNOWN_METHOD : methodSymbol(binding); + public void declare(String id, Tree tree) { + declarations.put(id, tree); } - Symbol variableSymbolOrUnknown(@Nullable IVariableBinding binding) { - return binding == null ? Symbol.UNKNOWN_SYMBOL : variableSymbol(binding); + public void use(String id, IdentifierTree tree) { + usages.computeIfAbsent(id, key -> new ArrayList<>()).add(tree); } - Symbol symbolOrUnknown(@Nullable IBinding binding) { - if (binding == null) { - return Symbol.UNKNOWN_SYMBOL; + @Override + public Type getClassType(String fullyQualifiedName) { + if (fullyQualifiedName == null || fullyQualifiedName.isBlank() || "!Unknown!".equals(fullyQualifiedName)) { + return Type.UNKNOWN; + } + if ("".equals(fullyQualifiedName)) { + return JType.nullType(this); + } + if (fullyQualifiedName.endsWith("[]")) { + JType element = asJType(getClassType(fullyQualifiedName.substring(0, fullyQualifiedName.length() - 2))); + return (JType) arraySymbol(element).type(); + } + if (PRIMITIVES.contains(fullyQualifiedName)) { + return primitiveType(fullyQualifiedName); } - return switch (binding.getKind()) { - case IBinding.TYPE -> typeSymbol((ITypeBinding) binding); - case IBinding.METHOD -> methodSymbol((IMethodBinding) binding); - case IBinding.VARIABLE -> variableSymbol((IVariableBinding) binding); - case IBinding.PACKAGE -> packageSymbol((IPackageBinding) binding); - default -> Symbol.UNKNOWN_SYMBOL; + typeSymbol(fullyQualifiedName); + return rawNamed(fullyQualifiedName); + } + + public Runnable getEnvironmentCleaner() { + return () -> { }; } + public Set undefinedTypes() { + return undefinedTypes; + } + + JSymbol resolve(@Nullable String id) { + if (id == null || id.isEmpty()) { + return unknownSymbol; + } + JSymbol found = symbols.get(id); + if (found != null) { + return found; + } + if (dumpSymbols.containsKey(id)) { + createDumpSymbol(id); + linkDumpSymbol(id); + return symbols.getOrDefault(id, unknownSymbol); + } + if (id.indexOf('#') >= 0) { + return createMember(id); + } + if (id.indexOf('<') >= 0) { + return typeParameter(id); + } + return typeSymbol(erase(id)); + } + + Symbol.TypeSymbol resolveTypeSymbol(@Nullable String id) { + JSymbol symbol = resolve(id); + return symbol instanceof Symbol.TypeSymbol typeSymbol ? typeSymbol : Symbol.TypeSymbol.UNKNOWN_TYPE; + } + + JTypeSymbol typeSymbol(String name) { + String fqn = sourceName(name); + JTypeSymbol found = typesByName.get(fqn); + if (found != null) { + return found; + } + JTypeSymbol created = new JTypeSymbol(this, fqn, simpleName(fqn), false); + created.owner(packageSymbol(packageName(fqn))); + created.type(rawNamed(fqn)); + typesByName.put(fqn, created); + symbols.putIfAbsent(fqn, created); + JsonObject info = typeInfos.get(fqn); + if (info == null && typesLoaded && requestedTypes.add(fqn)) { + JsonObject loaded = typeLoader.apply(Set.of(fqn)); + if (loaded == null) { + return created; + } + ingestTypes(loaded); + if (typeInfos.containsKey(fqn)) { + return created; + } + } + if (info != null) { + applyTypeInfo(created, info); + } else if (typesLoaded && !isWellKnown(fqn)) { + created.unknown(true); + undefinedTypes.add(new JProblem(fqn, JProblem.Type.UNDEFINED_TYPE)); + } + return created; + } + + private static boolean isWellKnown(String fqn) { + return "java.lang.Object".equals(fqn) || "java.lang.Cloneable".equals(fqn) || "java.io.Serializable".equals(fqn); + } + + Symbol.TypeSymbol arraySymbol(JType element) { + String name = element.fullyQualifiedName() + "[]"; + JTypeSymbol found = typesByName.get(name); + if (found != null) { + return found; + } + JTypeSymbol symbol = new JTypeSymbol(this, name, element.name() + "[]", false); + symbol.elementFqn(element.fullyQualifiedName()); + symbol.owner(Symbol.ROOT_PACKAGE); + symbol.type(JType.array(this, element)); + typesByName.put(name, symbol); + return symbol; + } + + Symbol.TypeSymbol primitiveSymbol(String name) { + JTypeSymbol found = typesByName.get(name); + if (found != null) { + return found; + } + JTypeSymbol symbol = new JTypeSymbol(this, name, name, false); + symbol.owner(Symbol.ROOT_PACKAGE); + symbol.type(primitiveType(name)); + typesByName.put(name, symbol); + return symbol; + } + + List annotationNames(@Nullable String id) { + return declaredAnnotations(id).stream().map(DeclaredAnnotation::name).toList(); + } + + List declaredAnnotations(@Nullable String id) { + if (id == null) { + return List.of(); + } + JsonObject fact = facts.get(id); + List fromValues = fact == null ? List.of() : annotationValues(fact); + if (!fromValues.isEmpty()) { + return fromValues; + } + List fromFact = fact == null ? List.of() : strings(fact, ANNOTATIONS); + if (!fromFact.isEmpty()) { + List library = libraryAnnotations.getOrDefault(id, List.of()); + if (!library.isEmpty()) { + return library; + } + return fromFact.stream().map(name -> new DeclaredAnnotation(erase(name), List.of())).toList(); + } + return libraryAnnotations.getOrDefault(id, List.of()); + } + + private List annotationValues(JsonObject fact) { + if (!fact.has("props") || !fact.get("props").isJsonObject()) { + return List.of(); + } + List annotations = new ArrayList<>(); + for (String encoded : strings(fact.getAsJsonObject("props"), "annotationValues")) { + JsonElement parsed = JsonParser.parseString(encoded); + if (!parsed.isJsonObject()) { + continue; + } + JsonObject annotation = parsed.getAsJsonObject(); + String name = text(annotation, "name"); + if (name != null) { + annotations.add(new DeclaredAnnotation(erase(name), readAnnotationValues(annotation.get("values")))); + } + } + return annotations; + } + + /** Annotations and deprecation a {@code types} answer gives a library type or member. */ + private void libraryAnnotations(JSymbol symbol, String id, JsonObject info) { + List annotations = readAnnotations(info); + if (annotations.isEmpty()) { + return; + } + libraryAnnotations.put(id, annotations); + if (annotations.stream().anyMatch(annotation -> "java.lang.Deprecated".equals(annotation.name()))) { + symbol.deprecated(true); + } + } + + private List readAnnotations(JsonObject info) { + if (!info.has(ANNOTATIONS) || !info.get(ANNOTATIONS).isJsonArray()) { + return List.of(); + } + List annotations = new ArrayList<>(); + for (JsonElement element : info.getAsJsonArray(ANNOTATIONS)) { + if (element.isJsonPrimitive()) { + annotations.add(new DeclaredAnnotation(element.getAsString(), List.of())); + } else if (element.isJsonObject()) { + JsonObject annotation = element.getAsJsonObject(); + String name = text(annotation, "name"); + if (name != null) { + annotations.add(new DeclaredAnnotation(name, readAnnotationValues(annotation.get("values")))); + } + } + } + return annotations; + } + + private List readAnnotationValues(@Nullable JsonElement element) { + if (!(element instanceof JsonArray array)) { + return List.of(); + } + List values = new ArrayList<>(); + for (JsonElement item : array) { + if (item.isJsonObject()) { + JsonObject value = item.getAsJsonObject(); + String name = text(value, "name"); + values.add(new AnnotationValueImpl(name == null ? "value" : name, readAnnotationElement(value))); + } + } + return values; + } + @Nullable - Symbol importSymbolOrUnknown(@Nullable IBinding binding) { - if (binding == null || (binding.getKind() != IBinding.TYPE && binding.getKind() != IBinding.METHOD && binding.getKind() != IBinding.VARIABLE)) { + private Object readAnnotationElement(JsonObject value) { + String kind = text(value, "kind"); + JsonElement raw = value.get("value"); + if (kind == null) { return null; } - return symbolOrUnknown(binding); + return switch (kind) { + case "boolean" -> Boolean.valueOf(raw != null && raw.isJsonPrimitive() && raw.getAsBoolean()); + case "byte" -> raw == null || !raw.isJsonPrimitive() ? null : Byte.valueOf(raw.getAsByte()); + case "short" -> raw == null || !raw.isJsonPrimitive() ? null : Short.valueOf(raw.getAsShort()); + case "int" -> raw == null || !raw.isJsonPrimitive() ? null : Integer.valueOf(raw.getAsInt()); + case "long" -> raw == null || !raw.isJsonPrimitive() ? null : Long.valueOf(raw.getAsLong()); + case "float" -> raw == null || !raw.isJsonPrimitive() ? null : Float.valueOf(raw.getAsFloat()); + case "double" -> raw == null || !raw.isJsonPrimitive() ? null : Double.valueOf(raw.getAsDouble()); + case "char" -> raw == null || !raw.isJsonPrimitive() ? null : Character.valueOf((char) raw.getAsInt()); + case "string", "class" -> raw == null || !raw.isJsonPrimitive() ? null : raw.getAsString(); + case "enum" -> enumConstant(text(value, "type"), text(value, "const")); + case "array" -> readAnnotationArray(raw); + case "annotation" -> raw != null && raw.isJsonObject() ? text(raw.getAsJsonObject(), "name") : null; + default -> null; + }; } - Symbol thisSymbolOrUnknown(@Nullable ITypeBinding binding) { - return binding == null ? Symbol.UNKNOWN_SYMBOL : typeSymbol(binding).thisSymbol; + private Object[] readAnnotationArray(@Nullable JsonElement element) { + if (!(element instanceof JsonArray array)) { + return new Object[0]; + } + List values = new ArrayList<>(); + for (JsonElement item : array) { + values.add(item.isJsonObject() ? readAnnotationElement(item.getAsJsonObject()) : null); + } + return values.toArray(); } - Symbol superSymbolOrUnknown(@Nullable ITypeBinding binding) { - return binding == null ? Symbol.UNKNOWN_SYMBOL : typeSymbol(binding).superSymbol; + @Nullable + private Symbol.VariableSymbol enumConstant(@Nullable String typeName, @Nullable String constName) { + if (constName == null) { + return null; + } + String ownerName = typeName == null || typeName.isBlank() ? "" : erase(typeName); + String id = (ownerName.isEmpty() ? "enum" : ownerName) + "#" + constName; + if (symbols.get(id) instanceof Symbol.VariableSymbol variable) { + return variable; + } + JTypeSymbol type = ownerName.isEmpty() ? null : typeSymbol(ownerName); + JVariableSymbol created = new JVariableSymbol(this, id, constName, "enum_member"); + if (type != null) { + created.owner(type); + created.type(type.type()); + } + created.addModifiers(List.of("public", "static", "final")); + symbols.put(id, created); + return created; } - public JType type(ITypeBinding typeBinding) { - JType cached = types.get(typeBinding); - if (cached != null) { - return cached; + /** + * Return type of a call. A JDK member can name the class type parameter its erased return + * stands for ({@code List.get} returns {@code E}), which is taken from the receiver. + */ + Symbol receiverSymbol(Symbol.TypeSymbol type, String name, Type receiverType) { + String id = (type instanceof JSymbol symbol && symbol.id != null ? symbol.id : type.name()) + "#" + name; + JSymbol found = symbols.get(id); + if (found != null) { + return found; } - return types.computeIfAbsent(typeBinding, k -> new JType(this, JType.normalize(k))); + JVariableSymbol variable = new JVariableSymbol(this, id, name, "field"); + variable.owner(type); + variable.type(receiverType); + variable.addModifiers(List.of("final")); + variable.receiver(true); + symbols.put(id, variable); + return variable; } - List types(ITypeBinding[] typeBindings) { - if (typeBindings.length == 0) { - return Collections.emptyList(); + /** + * The type an invocation of {@code method} evaluates to. A self-typed method returns the receiver, or a + * capture of it when the receiver leaves its own type argument as a wildcard. + */ + Type invocationReturn(JMethodSymbol method, @Nullable Type receiver, List arguments) { + Integer index = method.returnTypeParameter(); + if (method.returnsSelf() && receiver instanceof JType returned && returned.isCapture()) { + return returned; } - Type[] result = new Type[typeBindings.length]; - for (int i = 0; i < typeBindings.length; i++) { - result[i] = type(typeBindings[i]); + Type declaring = index == null || receiver == null ? null : declaringType(method, receiver); + if (declaring != null && declaring.isParameterized() && declaring.typeArguments().size() > index) { + Type argument = declaring.typeArguments().get(index); + if (method.returnsSelf() && argument instanceof JType captured && captured.isCapture() && receiver instanceof JType self && self.isClass()) { + return JType.capture(this, self); + } + if (!argument.isTypeVar()) { + return argument; + } + } + if (method.returnsSelf() && receiver instanceof JType returned && returned.isClass()) { + boolean wildcard = returned.typeArguments().stream().anyMatch(argument -> argument instanceof JType type && type.isCapture()); + return wildcard ? JType.capture(this, returned) : returned; + } + JsonObject declaredReturn = declaredReturn(method); + if (declaredReturn != null) { + Map classSubstitution = receiver == null ? Map.of() : classSubstitution(method, receiver); + Map substitution = new HashMap<>(methodSubstitution(method, arguments)); + substitution.putAll(classSubstitution); + if (!substitution.isEmpty() || method.isStatic()) { + JType substituted = typeFromFact(declaredReturn, substitution, true); + boolean leaks = classSubstitution.isEmpty() ? mentionsTypeVariable(substituted) : mentionsMethodTypeVariable(method, substituted); + if (!substituted.isUnknown() && !leaks) { + return substituted; + } + } } - return Arrays.asList(result); + Type declared = method.returnType().type(); + return declared == null ? Type.UNKNOWN : declared; } - public Symbol packageSymbol(@Nullable IPackageBinding packageBinding) { - if (packageBinding == null) { - return Symbol.ROOT_PACKAGE; + /** Maps the type variables of {@code method} itself to the arguments they are inferred from. */ + private Map methodSubstitution(JMethodSymbol method, List arguments) { + JsonObject member = arguments.isEmpty() ? null : lookupGenericMember(method); + if (member == null || !(member.get("typeVars") instanceof JsonArray names)) { + return Map.of(); } - JSymbol cached = symbols.get(packageBinding); - if (cached != null) { - return cached; + List variables = parameterVariables(method); + Map substitution = new HashMap<>(); + for (int i = 0; i < names.size(); i++) { + Type inferred = methodArgument(variables, "m" + i, arguments); + if (inferred != null && !inferred.isUnknown()) { + substitution.put(names.get(i).getAsString(), inferred); + } } - return symbols.computeIfAbsent(packageBinding, k -> new JPackageSymbol(this, (IPackageBinding) k)); + return substitution; + } + + private static boolean mentionsTypeVariable(Type type) { + return mentionsTypeVariable(type, name -> true); } - public JTypeSymbol typeSymbol(ITypeBinding typeBinding) { - JTypeSymbol cached = (JTypeSymbol) symbols.get(typeBinding); - if (cached != null) { - return cached; + private boolean mentionsMethodTypeVariable(JMethodSymbol method, Type type) { + JsonObject member = lookupGenericMember(method); + if (member == null || !(member.get("typeVars") instanceof JsonArray names) || names.isEmpty()) { + return false; } - return (JTypeSymbol) symbols.computeIfAbsent(typeBinding, k -> new JTypeSymbol(this, JType.normalize((ITypeBinding) k))); + Set own = new HashSet<>(); + names.forEach(name -> own.add(name.getAsString())); + return mentionsTypeVariable(type, own::contains); } - public JMethodSymbol methodSymbol(IMethodBinding methodBinding) { - JMethodSymbol cached = (JMethodSymbol) symbols.get(methodBinding); - if (cached != null) { - return cached; + private static boolean mentionsTypeVariable(Type type, Predicate variable) { + if (type.isTypeVar()) { + return variable.test(type.name()); } - return (JMethodSymbol) symbols.computeIfAbsent(methodBinding, k -> new JMethodSymbol(this, (IMethodBinding) k)); + if (type.isArray()) { + return mentionsTypeVariable(((Type.ArrayType) type).elementType(), variable); + } + return type.isParameterized() && type.typeArguments().stream().anyMatch(argument -> mentionsTypeVariable(argument, variable)); } - public JInitializerBlockSymbol initializerBlockSymbol(JTypeSymbol owner) { - JInitializerBlockSymbol cached = initializerBlockSymbols.get(owner); - if (cached != null) { - return cached; + /** The supertype of {@code receiver} that declares {@code method}, with the receiver's type arguments applied. */ + @Nullable + private Type declaringType(JMethodSymbol method, Type receiver) { + Type owner = method.owner().type(); + if (owner == null || owner.isUnknown()) { + return receiver; } - return initializerBlockSymbols.computeIfAbsent(owner, k -> new JInitializerBlockSymbol(k, false)); + Type found = asSuperType(receiver, owner.erasure().fullyQualifiedName(), new HashSet<>()); + return found == null ? receiver : found; } - public JInitializerBlockSymbol staticInitializerBlockSymbol(JTypeSymbol owner) { - JInitializerBlockSymbol cached = staticInitializerBlockSymbols.get(owner); - if (cached != null) { - return cached; + /** + * The parameterization of {@code created} a diamond infers from {@code target}: each type parameter of the created + * class takes the target argument it is passed to through the supertype matching the target. + */ + Type inferDiamond(Type created, Type target) { + if (!(created.erasure() instanceof JType raw) || raw.isUnknown() || !(target instanceof JType expected) || !expected.isParameterized()) { + return created; + } + String targetFqn = expected.erasure().fullyQualifiedName(); + if (raw.fullyQualifiedName().equals(targetFqn)) { + return JType.named(this, raw.fullyQualifiedName(), expected.args); } - return staticInitializerBlockSymbols.computeIfAbsent(owner, k -> new JInitializerBlockSymbol(k, true)); + Type generic = raw.declaringType(); + Type declared = generic.isParameterized() ? asSuperType(generic, targetFqn, new HashSet<>()) : null; + if (declared == null || declared.typeArguments().size() != expected.args.size()) { + return created; + } + Map inferred = new HashMap<>(); + for (int i = 0; i < expected.args.size(); i++) { + if (declared.typeArguments().get(i).isTypeVar()) { + inferred.put(declared.typeArguments().get(i).fullyQualifiedName(), expected.args.get(i)); + } + } + List arguments = new ArrayList<>(); + for (Type variable : generic.typeArguments()) { + JType argument = inferred.get(variable.fullyQualifiedName()); + if (argument == null) { + return created; + } + arguments.add(argument); + } + return JType.named(this, raw.fullyQualifiedName(), arguments); } - public JVariableSymbol variableSymbol(IVariableBinding variableBinding) { - JVariableSymbol cached = (JVariableSymbol) symbols.get(variableBinding); - if (cached != null) { - return cached; + /** The type a lambda or method reference implements for a wildcard-parameterized target (JLS 9.9). */ + Type groundType(Type target) { + if (!(target instanceof JType type) || !type.isParameterized() || type.args.stream().noneMatch(JType::isWildcard)) { + return target; } - return (JVariableSymbol) symbols.computeIfAbsent(variableBinding, k -> new JVariableSymbol(this, (IVariableBinding) k)); + return JType.named(this, type.fullyQualifiedName(), type.args.stream().map(JType::groundType).toList()); } - JSymbolMetadata.JAnnotationInstance annotation(IAnnotationBinding annotationBinding) { - JSymbolMetadata.JAnnotationInstance cached = annotations.get(annotationBinding); - if (cached != null) { - return cached; + /** Capture conversion: a member is accessed through captures of the receiver's wildcard arguments. */ + private Type captureWildcards(Type receiver) { + if (!(receiver instanceof JType type) || !type.isParameterized() || type.args.stream().noneMatch(JType::isWildcard)) { + return receiver; } - return annotations.computeIfAbsent(annotationBinding, k -> new JSymbolMetadata.JAnnotationInstance(this, k)); + List captured = type.args.stream().map(argument -> argument.isWildcard() && argument.element != null ? JType.capture(this, argument.element) : argument).toList(); + return JType.named(this, type.fullyQualifiedName(), captured); } - static IBinding declarationBinding(IBinding binding) { - switch (binding.getKind()) { - case IBinding.TYPE: - return ((ITypeBinding) binding).getTypeDeclaration(); - case IBinding.METHOD: - return ((IMethodBinding) binding).getMethodDeclaration(); - case IBinding.VARIABLE: - return ((IVariableBinding) binding).getVariableDeclaration(); - default: - return binding; + /** The method as seen from {@code receiver}, so its owner keeps that parameterization. */ + JMethodSymbol invocationView(JMethodSymbol method, @Nullable Type receiver, List parameters, Type returned) { + JMethodSymbol view = method.withSignature(parameters, returned); + if (receiver != null && !method.isStatic() && method.owner().type() != null) { + Type declaring = asSuperType(captureWildcards(receiver), method.owner().type().erasure().fullyQualifiedName(), new HashSet<>()); + if (declaring != null && declaring.isParameterized()) { + view.owner(declaring.symbol()); + } } + return view; } - @Override - public Type getClassType(String fullyQualifiedName) { - Type cached = nameToTypeCache.get(fullyQualifiedName); - if (cached != null) { - return cached; + /** + * The parameter types of {@code method} at one invocation: type variables of the owner are replaced by + * the receiver's type arguments, and type variables of the method by the boxed type of the first argument + * they are inferred from. + */ + List invocationParameters(JMethodSymbol method, @Nullable Type receiver, List arguments) { + List parameters = method.parameterTypes(); + List variables = parameterVariables(method); + if (variables.size() != parameters.size()) { + return parameters; + } + List result = new ArrayList<>(parameters); + for (int i = 0; i < variables.size(); i++) { + String variable = variables.get(i); + if (variable.isEmpty()) { + continue; + } + String base = variable.replace("[]", ""); + Type bound = base.charAt(0) == 'c' + ? classArgument(method, receiver, Integer.parseInt(base.substring(1))) + : methodArgument(variables, base, arguments); + if (bound != null) { + JType type = asJType(bound); + for (int dims = (variable.length() - base.length()) / 2; dims > 0; dims--) { + type = JType.array(this, type); + } + result.set(i, type); + } + } + substituteDeclaredParameters(method, receiver, variables, result); + return result; + } + + /** + * Fills in the parameters that are not a type variable on their own but are written in terms of one, + * such as {@code Consumer}. Their declared form is substituted for the arguments the receiver gives + * the declaring type, which may be a supertype of the receiver's own type. + */ + private void substituteDeclaredParameters(JMethodSymbol method, @Nullable Type receiver, List variables, List result) { + List declared = declaredParameters(method); + if (declared.size() != result.size() || receiver == null) { + return; + } + Map substitution = classSubstitution(method, receiver); + if (substitution.isEmpty()) { + return; } - return nameToTypeCache.computeIfAbsent(fullyQualifiedName, t -> { - ITypeBinding typeBinding = resolveType(t); - return typeBinding != null ? type(typeBinding) : Type.UNKNOWN; - }); + for (int i = 0; i < declared.size(); i++) { + if (variables.isEmpty() || variables.get(i).isEmpty()) { + result.set(i, typeFromFact(declared.get(i), substitution)); + } + } + } + + /** Maps the type variables of the type declaring {@code method} to the arguments {@code receiver} gives them. */ + private Map classSubstitution(JMethodSymbol method, Type receiver) { + String ownerFqn = method.owner().type().erasure().fullyQualifiedName(); + JsonObject info = typeInfos.get(sourceName(ownerFqn)); + List names = info == null ? List.of() : strings(info, "typeVars"); + if (names.isEmpty()) { + return Map.of(); + } + Type declaring = asSuperType(receiver, ownerFqn, new HashSet<>()); + if (declaring == null || !declaring.isParameterized()) { + return Map.of(); + } + List arguments = declaring.typeArguments(); + Map substitution = new HashMap<>(); + for (int i = 0; i < names.size() && i < arguments.size(); i++) { + substitution.put(names.get(i), arguments.get(i)); + } + return substitution; } + /** The instantiation of {@code targetFqn} reached from {@code from}, which is itself or one of its supertypes. */ @Nullable - ITypeBinding resolveType(String name) { - int dimensions = 0; - int end = name.length() - 1; - while (name.charAt(end) == ']') { - end -= 2; - dimensions++; + private Type asSuperType(Type from, String targetFqn, Set visited) { + if (from.erasure().fullyQualifiedName().equals(targetFqn)) { + return from; } - name = name.substring(0, end + 1); + Symbol.TypeSymbol symbol = from.symbol(); + if (symbol.isUnknown() || !visited.add(from.erasure().fullyQualifiedName())) { + return null; + } + List supertypes = new ArrayList<>(symbol.interfaces()); + Type superClass = symbol.superClass(); + if (superClass != null) { + supertypes.add(0, superClass); + } + for (Type supertype : supertypes) { + Type found = asSuperType(instantiateSupertype(from, supertype), targetFqn, visited); + if (found != null) { + return found; + } + } + return null; + } - ITypeBinding typeBinding = ast.resolveWellKnownType(name); - if (typeBinding == null) { - typeBinding = ASTUtils.resolveType(ast, name); - if (typeBinding == null) { - return null; + /** Applies {@code from}'s type arguments to a supertype written with those type variables. */ + private Type instantiateSupertype(Type from, Type supertype) { + if (!from.isParameterized() || !(from instanceof JType fromType) || !(supertype instanceof JType superType)) { + return supertype; + } + return superType.substitute(typeArgumentsOf(fromType)); + } + + private Map typeArgumentsOf(JType type) { + JsonObject info = typeInfos.get(sourceName(type.erasure().fullyQualifiedName())); + List names = info == null ? List.of() : strings(info, "typeVars"); + List arguments = type.typeArguments(); + Map substitution = new HashMap<>(); + for (int i = 0; i < names.size() && i < arguments.size(); i++) { + substitution.put(names.get(i), arguments.get(i)); + } + return substitution; + } + + @Nullable + private JsonObject declaredReturn(JMethodSymbol method) { + if (method.declaredReturnLoaded()) { + return method.declaredReturn(); + } + JsonObject member = lookupGenericMember(method); + JsonObject retType = member != null && member.get("retType") instanceof JsonObject fact ? fact : null; + method.declaredReturn(retType); + return retType; + } + + private List declaredParameters(JMethodSymbol method) { + List cached = method.declaredParameters(); + if (cached == null) { + JsonObject member = lookupGenericMember(method); + cached = member == null ? List.of() : objects(member, "paramTypes"); + method.declaredParameters(cached); + } + return cached; + } + + @Nullable + private static Type classArgument(JMethodSymbol method, @Nullable Type receiver, int index) { + if (receiver == null || !receiver.isParameterized() || receiver.typeArguments().size() <= index + || !receiver.erasure().equals(method.owner().type().erasure())) { + return null; + } + return receiver.typeArguments().get(index); + } + + @Nullable + private static Type methodArgument(List variables, String base, List arguments) { + for (int i = 0; i < variables.size() && i < arguments.size(); i++) { + String variable = variables.get(i); + if (!variable.replace("[]", "").equals(base)) { + continue; + } + Type argument = arguments.get(i); + if (!variable.equals(base)) { + boolean last = i == variables.size() - 1; + if (arguments.size() == variables.size() && argument.isArray()) { + argument = ((Type.ArrayType) argument).elementType(); + } else if (!last) { + continue; + } + } + if (!argument.isUnknown() && !argument.isNullType()) { + return argument.isPrimitive() ? argument.primitiveWrapperType() : argument; } } - return dimensions == 0 ? typeBinding : typeBinding.createArrayType(dimensions); + return null; } - IAnnotationBinding[] resolvePackageAnnotations(String packageName) { - return ASTUtils.resolvePackageAnnotations(ast, packageName); + private List parameterVariables(JMethodSymbol method) { + List cached = method.parameterVariables(); + if (cached == null) { + cached = lookupParameterVariables(method); + method.parameterVariables(cached); + } + return cached; } - public Runnable getEnvironmentCleaner() { - return ASTUtils.getEnvironmentCleaner(ast); + private List lookupParameterVariables(JMethodSymbol method) { + JsonObject member = lookupGenericMember(method); + if (member == null) { + return List.of(); + } + List variables = new ArrayList<>(); + member.getAsJsonArray("paramVars").forEach(v -> variables.add(v.getAsString())); + return variables; + } + + /** The loaded declaration of {@code method}, when its declaring type is known and carries a generic signature. */ + @Nullable + private JsonObject lookupGenericMember(JMethodSymbol method) { + String ownerFqn = method.owner().type().erasure().fullyQualifiedName(); + if (!declaresType(ownerFqn)) { + return null; + } + JsonArray members = typeInfos.get(sourceName(ownerFqn)).getAsJsonArray("members"); + if (members == null) { + return null; + } + List erased = method.parameterTypes().stream().map(t -> t.erasure().fullyQualifiedName()).toList(); + for (JsonElement element : members) { + JsonObject member = element.getAsJsonObject(); + String descriptor = text(member, "desc"); + if (method.name().equals(text(member, "name")) && descriptor != null && member.has("paramVars") && member.get("paramVars").isJsonArray() + && parameterTypesOf(descriptor).stream().map(t -> t.erasure().fullyQualifiedName()).toList().equals(erased)) { + return member; + } + } + return null; + } + + Symbol memberFromTypeInfo(JTypeSymbol owner, JsonObject member) { + if (member.has("id") && !member.get("id").isJsonNull()) { + return resolve(member.get("id").getAsString()); + } + String name = text(member, "name"); + String descriptor = text(member, "desc"); + String id = (owner.id == null ? owner.name() : owner.id) + "#" + name + (descriptor == null ? "" : descriptor); + JSymbol existing = symbols.get(id); + if (existing != null) { + return existing; + } + if ("f".equals(text(member, "k"))) { + JVariableSymbol field = new JVariableSymbol(this, id, name == null ? "" : name, "field"); + field.owner(owner); + if (descriptor != null) { + field.type(readDescriptor(descriptor, new int[] {0})); + } + applyMemberFlags(field, member); + libraryAnnotations(field, id, member); + if (truth(member, "enum")) { + field.enumSymbol(true); + } + String constant = text(member, "constant"); + if (constant != null) { + field.constantValue(constant); + } + symbols.put(id, field); + return field; + } + JMethodSymbol method = new JMethodSymbol(this, id, name == null ? "" : name); + method.owner(owner); + if (descriptor != null && descriptor.startsWith("(")) { + method.parameterTypes(parameterTypesOf(descriptor)); + method.returnTypeSymbol(readDescriptor(descriptor, new int[] {descriptor.indexOf(')') + 1}).symbol()); + } + applyMemberFlags(method, member); + if (owner.isInterface() && !truth(member, ABSTRACT) && !truth(member, STATIC) && !"private".equals(text(member, "vis"))) { + method.addModifiers(List.of("default")); + } + libraryAnnotations(method, id, member); + List thrown = strings(member, "throws"); + if (!thrown.isEmpty()) { + method.thrownTypes(thrown.stream().map(this::typeFromName).toList()); + } + if (member.has("varargs") && member.get("varargs").getAsBoolean()) { + method.varArgs(true); + } + if (member.has("retParam") && !member.get("retParam").isJsonNull()) { + method.returnTypeParameter(member.get("retParam").getAsInt()); + } + if (member.get("retType") instanceof JsonObject retType) { + method.declaredReturn(retType); + } + method.returnsSelf(member.has("retSelf") && member.get("retSelf").getAsBoolean()); + symbols.put(id, method); + return method; + } + + private void createDumpSymbol(String id) { + if (symbols.containsKey(id)) { + return; + } + JsonObject dump = dumpSymbols.get(id); + String kind = text(dump, "kind"); + String name = text(dump, "name"); + if (name == null) { + name = ""; + } + if ("label".equals(kind)) { + return; + } + JSymbol symbol; + if ("type".equals(kind) && id.indexOf('<') >= 0) { + symbol = new JTypeSymbol(this, id, name, true); + } else if ("type".equals(kind)) { + symbol = new JTypeSymbol(this, id, simpleName(erase(id)), false); + typesByName.putIfAbsent(erase(id), (JTypeSymbol) symbol); + } else if ("method".equals(kind) || "constructor".equals(kind)) { + symbol = new JMethodSymbol(this, id, "constructor".equals(kind) ? "" : name); + } else { + symbol = new JVariableSymbol(this, id, name, kind == null ? "field" : kind); + } + symbols.put(id, symbol); } + private void linkDumpSymbol(String id) { + JSymbol symbol = symbols.get(id); + JsonObject dump = dumpSymbols.get(id); + if (symbol == null || dump == null) { + return; + } + JsonObject fact = facts.get(id); + String ownerId = text(dump, "owner"); + String parent = text(fact, "parent"); + if ((ownerId == null && symbol instanceof JMethodSymbol) || (ownerId != null && parent != null && !parent.contains("::") && !facts.containsKey(ownerId) && !dumpSymbols.containsKey(ownerId))) { + ownerId = parent; + } + if (ownerId != null) { + symbol.owner(resolve(ownerId)); + } else if (symbol instanceof JTypeSymbol typeSymbol && !typeSymbol.isTypeParameter()) { + symbol.owner(packageSymbol(packageName(erase(id)))); + } + if (fact != null) { + applyFact(symbol, fact); + } + if ((symbol.isMethodSymbol() || symbol.isVariableSymbol()) && (fact == null || !"IN_PROJECT".equals(text(fact, "tier")))) { + applyLibraryMember(symbol, id); + } + JsonObject details = dump.getAsJsonObject("facts"); + if (symbol.isVariableSymbol() && details != null && details.getAsJsonObject("type") != null) { + symbol.type(typeFromFact(details.getAsJsonObject("type"))); + } + if (symbol instanceof JMethodSymbol method) { + fillMethod(method, fact, id); + } else if (symbol instanceof JTypeSymbol typeSymbol && typeSymbol.isTypeParameter()) { + symbol.type(JType.typeVar(this, symbol.name(), id)); + } else if (symbol.isTypeSymbol()) { + symbol.type(rawNamed(erase(id))); + } + } + + /** Flags, annotations and thrown types the {@code types} answer gives a library member the dump names. */ + private void applyLibraryMember(JSymbol symbol, String id) { + MemberId parsed = parseMember(id); + if (parsed == null || parsed.arityOnly || ARRAY.equals(parsed.owner)) { + return; + } + String ownerName = erase(parsed.owner); + typeSymbol(ownerName); + JsonObject member = findMember(ownerName, parsed, new HashSet<>(), Match.TYPED); + if (member == null || member.has("id")) { + return; + } + applyMemberFlags(symbol, member); + libraryAnnotations(symbol, id, member); + List thrown = strings(member, "throws"); + if (symbol instanceof JMethodSymbol method && !thrown.isEmpty()) { + method.thrownTypes(thrown.stream().map(this::typeFromName).toList()); + } + } + + private JSymbol typeParameter(String id) { + int open = id.lastIndexOf('<'); + String name = open >= 0 && id.endsWith(">") ? id.substring(open + 1, id.length() - 1) : id; + JTypeSymbol symbol = new JTypeSymbol(this, id, name, true); + symbol.type(JType.typeVar(this, name, id)); + symbols.put(id, symbol); + return symbol; + } + + private JSymbol createMember(String id) { + if (!creating.add(id)) { + return unknownSymbol; + } + try { + return createMemberOnce(id); + } finally { + creating.remove(id); + } + } + + private JSymbol createMemberOnce(String id) { + MemberId parsed = parseMember(id); + if (parsed == null) { + return typeSymbol(erase(id)); + } + if (ARRAY.equals(parsed.owner) && "length".equals(parsed.name)) { + JVariableSymbol length = new JVariableSymbol(this, id, "length", "field"); + length.addModifiers(List.of("public", "final")); + length.type(primitiveType("int")); + length.owner(Symbol.ROOT_PACKAGE); + symbols.put(id, length); + return length; + } + String ownerName = erase(parsed.owner); + JTypeSymbol owner = typeSymbol(ownerName); + JsonObject member = null; + if (parsed.params != null && !parsed.arityOnly) { + member = findMember(ownerName, parsed, new HashSet<>(), Match.TYPED); + } + if (member == null) { + member = findMember(ownerName, parsed, new HashSet<>(), Match.ARITY); + } + if (member == null && parsed.params != null) { + member = findMember(ownerName, parsed, new HashSet<>(), Match.VARARGS); + } + String memberId = member == null ? null : text(member, "id"); + if (member != null && (memberId == null || !creating.contains(memberId))) { + Symbol resolved = memberFromTypeInfo(owner, member); + if (resolved instanceof JSymbol jSymbol) { + symbols.put(id, jSymbol); + return jSymbol; + } + } + if (parsed.params == null && parsed.name.contains("$lambda$")) { + JMethodSymbol lambda = new JMethodSymbol(this, id, parsed.name); + lambda.owner(owner); + lambda.lambda(true); + symbols.put(id, lambda); + return lambda; + } + if (parsed.params == null && parsed.name.contains(")$")) { + return createTypeInMethodBody(id, parsed); + } + if (parsed.params == null) { + JVariableSymbol field = new JVariableSymbol(this, id, parsed.name, "field"); + field.owner(owner); + field.unknown(true); + symbols.put(id, field); + return field; + } + JMethodSymbol method = new JMethodSymbol(this, id, parsed.name); + method.owner(parsed.arityOnly ? Symbol.TypeSymbol.UNKNOWN_TYPE : owner); + method.unknown(parsed.arityOnly); + fillMethod(method, facts.get(id), id); + if ("".equals(parsed.name) && facts.get(id) == null && member == null) { + // The implicit default constructor has the access of its class. + method.addModifiers(List.of("public", "protected", "private").stream().filter(owner::hasModifier).toList()); + } + if (method.parameterTypes().isEmpty() && !parsed.arityOnly && parsed.params != null && !parsed.params.isEmpty()) { + List types = new ArrayList<>(); + for (String param : splitParams(parsed.params)) { + types.add(typeFromName(param)); + } + method.parameterTypes(types); + } + symbols.put(id, method); + return method; + } + + /** + * The method {@code name} of {@code ownerFqn} or of one of its supertypes that Java would pick for {@code arguments}: + * applicable by subtyping first, then with boxing, then as varargs, and the most specific of those. + */ + @Nullable + Symbol.MethodSymbol declaredMethod(String ownerFqn, String name, List arguments) { + if (!declaresType(ownerFqn)) { + return null; + } + List candidates = new ArrayList<>(); + collectMethods(ownerFqn, name, new HashSet<>(), candidates); + for (int phase = 0; phase < 3; phase++) { + int current = phase; + List applicable = candidates.stream().filter(method -> applicable(method, arguments, current)).toList(); + if (!applicable.isEmpty()) { + return applicable.stream().filter(method -> applicable.stream().allMatch(other -> moreSpecific(method, other))).findFirst().orElse(applicable.get(0)); + } + } + List sameArity = candidates.stream() + .filter(method -> method.isVarArgsMethod() ? arguments.size() >= method.parameterTypes().size() - 1 : method.parameterTypes().size() == arguments.size()) + .toList(); + if (sameArity.size() == 1) { + return sameArity.get(0); + } + return null; + } + + /** ECJ leaves a call unresolved when no declaration takes this many arguments, rather than guessing one. */ + static boolean arityMatches(Symbol.MethodSymbol method, int argumentCount) { + int parameters = method.parameterTypes().size(); + return method.isVarArgsMethod() ? argumentCount >= parameters - 1 : argumentCount == parameters; + } + + /** The single abstract method of {@code type} when it is a functional interface, else null. */ + @Nullable + JMethodSymbol functionalMethod(Type type) { + String fqn = type.erasure().fullyQualifiedName(); + if (!type.isClass() || !type.symbol().isInterface() || !declaresType(fqn)) { + return null; + } + Map found = new LinkedHashMap<>(); + collectAbstractMethods(fqn, new HashSet<>(), found); + return found.size() == 1 ? found.values().iterator().next() : null; + } + + private void collectAbstractMethods(String ownerFqn, Set seen, Map found) { + if (!seen.add(ownerFqn) || !declaresType(ownerFqn)) { + return; + } + JsonObject info = typeInfos.get(sourceName(ownerFqn)); + JsonArray members = info.getAsJsonArray("members"); + if (members != null) { + for (JsonElement element : members) { + if (memberFromTypeInfo(typeSymbol(ownerFqn), element.getAsJsonObject()) instanceof JMethodSymbol method && !method.isUnknown() && method.isAbstract() + && !OBJECT_METHODS.contains(method.name() + "/" + method.parameterTypes().size())) { + found.putIfAbsent(method.name() + "/" + method.parameterTypes().size(), method); + } + } + } + parents(info).forEach(parent -> collectAbstractMethods(erase(parent), seen, found)); + } + + /** The field {@code name} that {@code ownerFqn} or one of its supertypes declares, if any. */ + @Nullable + Symbol.VariableSymbol declaredField(String ownerFqn, String name) { + return declaredField(ownerFqn, name, new HashSet<>()); + } + + @Nullable + private Symbol.VariableSymbol declaredField(String ownerFqn, String name, Set seen) { + if (!seen.add(ownerFqn) || !declaresType(ownerFqn)) { + return null; + } + JsonObject info = typeInfos.get(sourceName(ownerFqn)); + JsonArray members = info.getAsJsonArray("members"); + if (members != null) { + for (JsonElement element : members) { + JsonObject member = element.getAsJsonObject(); + MemberId id = member.has("id") ? parseMember(member.get("id").getAsString()) : null; + boolean named = id == null ? (name.equals(text(member, "name")) && "f".equals(text(member, "k"))) : (name.equals(id.name) && id.params == null); + if (named && memberFromTypeInfo(typeSymbol(ownerFqn), member) instanceof Symbol.VariableSymbol field && !field.isUnknown()) { + return field; + } + } + } + for (String parent : parents(info)) { + Symbol.VariableSymbol found = declaredField(erase(parent), name, seen); + if (found != null) { + return found; + } + } + return null; + } + + private void collectMethods(String ownerFqn, String name, Set seen, List found) { + if (!seen.add(ownerFqn) || !declaresType(ownerFqn)) { + return; + } + JsonObject info = typeInfos.get(sourceName(ownerFqn)); + JsonArray members = info.getAsJsonArray("members"); + if (members != null) { + for (JsonElement element : members) { + JsonObject member = element.getAsJsonObject(); + MemberId id = member.has("id") ? parseMember(member.get("id").getAsString()) : null; + boolean named = id == null ? (name.equals(text(member, "name")) && "m".equals(text(member, "k"))) : (name.equals(id.name) && id.params != null); + if (named && memberFromTypeInfo(typeSymbol(ownerFqn), member) instanceof Symbol.MethodSymbol method && !method.isUnknown()) { + found.add(method); + } + } + } + if (!"".equals(name)) { + parents(info).forEach(parent -> collectMethods(erase(parent), name, seen, found)); + } + } + + /** The overload that matches {@code arguments} when {@code candidate} is more specific than {@code current}. */ + Symbol.MethodSymbol preferOverload(Symbol.MethodSymbol current, @Nullable Symbol.MethodSymbol candidate, List arguments) { + if (candidate == null || candidate == current || candidate.isUnknown()) { + return current; + } + for (int phase = 0; phase < 3; phase++) { + boolean currentOk = applicable(current, arguments, phase); + boolean candidateOk = applicable(candidate, arguments, phase); + if (currentOk && !candidateOk) { + return current; + } + if (candidateOk && !currentOk) { + return candidate; + } + if (currentOk && candidateOk) { + return moreSpecific(candidate, current) && !moreSpecific(current, candidate) ? candidate : current; + } + } + return current; + } + + private static boolean applicable(Symbol.MethodSymbol method, List arguments, int phase) { + List parameters = method.parameterTypes(); + int count = parameters.size(); + boolean varargs = phase == 2 && method.isVarArgsMethod(); + if (varargs ? arguments.size() < count - 1 : arguments.size() != count) { + return false; + } + for (int i = 0; i < arguments.size(); i++) { + Type parameter = varargs && i >= count - 1 ? ((Type.ArrayType) parameters.get(count - 1)).elementType() : parameters.get(i); + if (!convertible(arguments.get(i), parameter, phase > 0)) { + return false; + } + } + return true; + } + + private static boolean convertible(Type argument, Type parameter, boolean boxing) { + Type target = parameter.erasure(); + if (argument.isUnknown() || target.isUnknown() || target.isTypeVar() || argument.erasure().isSubtypeOf(target)) { + return true; + } + if (!boxing || argument.isPrimitive() == target.isPrimitive()) { + return false; + } + Type converted = argument.isPrimitive() ? argument.primitiveWrapperType() : argument.primitiveType(); + return converted != null && converted.isSubtypeOf(target); + } + + private static boolean moreSpecific(Symbol.MethodSymbol method, Symbol.MethodSymbol other) { + List mine = method.parameterTypes(); + List theirs = other.parameterTypes(); + if (mine.size() != theirs.size()) { + return true; + } + for (int i = 0; i < mine.size(); i++) { + Type target = theirs.get(i).erasure(); + if (!target.isTypeVar() && !mine.get(i).erasure().isSubtypeOf(target)) { + return false; + } + } + return true; + } + + @Nullable + private JsonObject findMember(String ownerFqn, MemberId parsed, Set seen, Match match) { + if (!seen.add(ownerFqn)) { + return null; + } + JsonObject info = typeInfos.get(sourceName(ownerFqn)); + JsonObject found = matchMembers(info == null ? null : info.getAsJsonArray("members"), parsed, match); + if (found != null) { + return found; + } + JTypeSymbol type = typesByName.get(sourceName(ownerFqn)); + if (type != null) { + for (JsonObject member : type.memberFacts()) { + if (matches(member, parsed, match)) { + return member; + } + } + } + if (info == null || "".equals(parsed.name)) { + return null; + } + for (String parent : parents(info)) { + found = findMember(erase(parent), parsed, seen, match); + if (found != null) { + return found; + } + } + return null; + } + + @Nullable + private JsonObject matchMembers(@Nullable JsonArray members, MemberId parsed, Match match) { + if (members == null) { + return null; + } + for (JsonElement element : members) { + JsonObject member = element.getAsJsonObject(); + if (matches(member, parsed, match)) { + return member; + } + } + return null; + } + + private boolean matches(JsonObject member, MemberId parsed, Match match) { + if (member.has("id")) { + MemberId other = parseMember(member.get("id").getAsString()); + return other != null && parsed.name.equals(other.name) && parsed.arity == other.arity + && (parsed.arityOnly || other.arityOnly || Objects.equals(parsed.params, other.params)); + } + if (!parsed.name.equals(text(member, "name"))) { + return false; + } + boolean method = parsed.params != null; + if (method != "m".equals(text(member, "k"))) { + return false; + } + if (!method) { + return true; + } + String descriptor = text(member, "desc"); + if (descriptor == null) { + return false; + } + List declaredParams = parameterTypesOf(descriptor); + int declaredCount = declaredParams.size(); + return switch (match) { + case TYPED -> declaredCount == parsed.arity && sameErasures(declaredParams, splitParams(parsed.params)); + case ARITY -> declaredCount == parsed.arity; + case VARARGS -> declaredCount > 0 && parsed.arity >= declaredCount - 1 && declaredParams.get(declaredCount - 1).isArray(); + }; + } + + private static boolean sameErasures(List declared, List names) { + for (int i = 0; i < declared.size(); i++) { + String expected = declared.get(i).erasure().fullyQualifiedName().replace('$', '.'); + if (!expected.equals(erasedName(names.get(i)))) { + return false; + } + } + return true; + } + + private static String erasedName(String name) { + StringBuilder out = new StringBuilder(name.length()); + int depth = 0; + for (char current : erase(name).toCharArray()) { + if (current == '<') { + depth++; + } else if (current == '>') { + depth--; + } else if (depth == 0) { + out.append(current == '$' ? '.' : current); + } + } + return out.toString().replace("...", "[]").trim(); + } + + private enum Match { + TYPED, ARITY, VARARGS + } + + private void fillMethod(JMethodSymbol method, @Nullable JsonObject fact, String id) { + MemberId parsed = parseMember(id); + if (fact != null && fact.has("returns")) { + List returns = flat(fact, "returns"); + if (!returns.isEmpty()) { + method.returnTypeSymbol(typeFromName(returns.get(0)).symbol()); + } + } + List factParams = fact == null ? List.of() : flat(fact, "params"); + if (!factParams.isEmpty() && (parsed == null || parsed.arityOnly || factParams.size() == parsed.arity)) { + List types = new ArrayList<>(); + for (String param : factParams) { + types.add(typeFromName(param)); + } + method.parameterTypes(types); + } else if (parsed != null && !parsed.arityOnly && parsed.params != null && !parsed.params.isEmpty()) { + List types = new ArrayList<>(); + for (String param : splitParams(parsed.params)) { + types.add(typeFromName(param)); + } + method.parameterTypes(types); + } + if ("".equals(method.name())) { + method.returnTypeSymbol(primitiveSymbol("void")); + } + method.parametrized(id.indexOf('<') >= 0); + if (fact != null && fact.get("props") instanceof JsonObject props) { + List thrown = strings(props, "thrownTypes"); + if (!thrown.isEmpty()) { + method.thrownTypes(thrown.stream().map(this::typeFromName).toList()); + } + } + } + + private void applyFact(JSymbol symbol, JsonObject fact) { + String kind = text(fact, "kind"); + if (symbol instanceof JTypeSymbol type) { + boolean interfaceKind = "interface".equals(kind) || "annotation".equals(kind); + type.interfaceSymbol(interfaceKind); + type.annotationSymbol("annotation".equals(kind)); + List extend = strings(fact, "extends"); + List implement = strings(fact, "implements"); + type.enumSymbol("enum".equals(kind) || extend.stream().map(JSema::erase).anyMatch("java.lang.Enum"::equals)); + if (interfaceKind) { + type.interfaceNames(extend.isEmpty() ? implement : extend); + } else { + type.superName(extend.isEmpty() ? null : extend.get(0)); + type.interfaceNames(implement); + } + } + JsonObject props = fact.getAsJsonObject("props"); + if (props != null) { + symbol.addModifiers(flat(props, "modifiers")); + } + if (symbol instanceof JTypeSymbol) { + symbol.addModifiers(implicitTypeModifiers(kind, text(fact, "parent"))); + } + for (String annotation : strings(fact, ANNOTATIONS)) { + if ("java.lang.Deprecated".equals(erase(annotation))) { + symbol.deprecated(true); + } + } + if (symbol.isVariableSymbol() && ("enum".equals(kind) || "enum_member".equals(kind))) { + symbol.enumSymbol(true); + symbol.addModifiers(List.of("public", STATIC, "final")); + } + } + + /** + * Modifiers Java implies: member interfaces, enums, records and annotations are static; enums and records are + * final (an enum constant with a body is not modelled). + */ + private List implicitTypeModifiers(@Nullable String kind, @Nullable String parent) { + List implied = new ArrayList<>(); + boolean member = parent != null && !parent.endsWith("::") && facts.get(parent) != null + && isTypeFact(facts.get(parent)); + if (member && ("interface".equals(kind) || "enum".equals(kind) || RECORD.equals(kind) || "annotation".equals(kind))) { + implied.add(STATIC); + } + if ("enum".equals(kind) || RECORD.equals(kind)) { + implied.add("final"); + } + return implied; + } + + private static boolean isTypeFact(JsonObject fact) { + String kind = text(fact, "kind"); + return "class".equals(kind) || "interface".equals(kind) || "enum".equals(kind) || RECORD.equals(kind) + || "annotation".equals(kind) || "exception".equals(kind); + } + + private void applyTypeInfo(JTypeSymbol symbol, JsonObject info) { + String kind = text(info, "kind"); + boolean interfaceKind = "interface".equals(kind) || "annotation".equals(kind); + symbol.interfaceSymbol(interfaceKind); + symbol.annotationSymbol("annotation".equals(kind)); + symbol.enumSymbol("enum".equals(kind) || "java.lang.Enum".equals(text(info, "super"))); + if (interfaceKind) { + List extend = strings(info, "super"); + symbol.interfaceNames(extend.isEmpty() ? strings(info, "interfaces") : extend); + symbol.superName(null); + } else { + symbol.superName(text(info, "super")); + symbol.interfaceNames(strings(info, "interfaces")); + } + symbol.genericSupertypes( + info.get("superType") instanceof JsonObject superType ? superType : null, + objects(info, "interfaceTypes")); + symbol.addModifiers(flat(info, "modifiers")); + libraryAnnotations(symbol, symbol.id == null ? symbol.name() : symbol.id, info); + if (info.has("typeParameters") && !info.get("typeParameters").isJsonNull()) { + symbol.typeParameterCount(info.get("typeParameters").getAsInt()); + } + List members = new ArrayList<>(); + if (info.has("members")) { + for (JsonElement member : info.getAsJsonArray("members")) { + members.add(member.getAsJsonObject()); + } + } + symbol.memberFacts(members); + symbol.unknown(false); + } + + private static void applyMemberFlags(JSymbol symbol, JsonObject member) { + List modifiers = new ArrayList<>(); + if (truth(member, STATIC)) { + modifiers.add(STATIC); + } + if (truth(member, "final")) { + modifiers.add("final"); + } + if (truth(member, ABSTRACT)) { + modifiers.add(ABSTRACT); + } + String visibility = text(member, "vis"); + if (visibility != null && !"package".equals(visibility)) { + modifiers.add(visibility); + } + symbol.addModifiers(modifiers); + } + + private static List parents(JsonObject info) { + List parents = new ArrayList<>(); + String superName = text(info, "super"); + if (superName != null) { + parents.add(superName); + } + parents.addAll(strings(info, "interfaces")); + parents.addAll(strings(info, "super")); + return parents; + } + + private JType typeFromFact(JsonObject fact) { + return typeFromFact(fact, Map.of()); + } + + private JType typeFromFact(JsonObject fact, Map substitution) { + return typeFromFact(fact, substitution, false); + } + + private JType typeFromFact(JsonObject fact, Map substitution, boolean captureWildcard) { + String kind = text(fact, "kind"); + if (ARRAY.equals(kind) && fact.getAsJsonObject("elem") != null) { + return JType.array(this, typeFromFact(fact.getAsJsonObject("elem"), substitution, captureWildcard)); + } + if ("wildcard".equals(kind)) { + return wildcardFromFact(fact, substitution, captureWildcard); + } + if ("typevar".equals(kind)) { + String id = text(fact, "id"); + Type substituted = substitution.get(id); + if (substituted != null) { + return asJType(substituted); + } + int open = id == null ? -1 : id.lastIndexOf('<'); + String name = open >= 0 && id.endsWith(">") ? id.substring(open + 1, id.length() - 1) : id; + return JType.typeVar(this, name == null ? "" : name, id); + } + if ("unknown".equals(kind)) { + return JType.unknown(this); + } + String id = text(fact, "id"); + if (id == null) { + return JType.unknown(this); + } + List args = new ArrayList<>(); + if (fact.has("args")) { + for (JsonElement arg : fact.getAsJsonArray("args")) { + if (arg.isJsonObject()) { + args.add(typeFromFact(arg.getAsJsonObject(), substitution, captureWildcard)); + } + } + } + String erased = erase(id); + if (PRIMITIVES.contains(erased)) { + return primitiveType(erased); + } + return JType.named(this, erased, args); + } + + private JType wildcardFromFact(JsonObject fact, Map substitution, boolean capture) { + JType object = asJType(getClassType("java.lang.Object")); + JType upper = fact.get("extends") instanceof JsonObject bound ? typeFromFact(bound, substitution) : object; + if (capture) { + return JType.capture(this, upper.isUnknown() ? object : upper); + } + if (fact.get("super") instanceof JsonObject bound) { + return JType.superWildcard(this, typeFromFact(bound, substitution)); + } + return JType.wildcard(this, upper); + } + + /** A supertype fact: the loaded raw type, parameterized when the fact has type arguments. */ + Type supertypeFromFact(JsonObject fact) { + Type raw = getClassType(erase(text(fact, "id"))); + JsonArray args = fact.getAsJsonArray("args"); + if (args == null || args.isEmpty() || !(raw instanceof JType rawType)) { + return raw; + } + List resolved = new ArrayList<>(); + for (JsonElement arg : args) { + if (arg.isJsonObject()) { + resolved.add(typeFromFact(arg.getAsJsonObject())); + } + } + return JType.named(this, rawType.fullyQualifiedName(), resolved); + } + + private static List objects(JsonObject object, String key) { + if (!object.has(key) || !object.get(key).isJsonArray()) { + return List.of(); + } + List values = new ArrayList<>(); + for (JsonElement item : object.getAsJsonArray(key)) { + if (item.isJsonObject()) { + values.add(item.getAsJsonObject()); + } + } + return values; + } + + private Type typeFromName(String name) { + String erased = erase(name); + if (PRIMITIVES.contains(erased)) { + return primitiveType(erased); + } + if (erased.endsWith("[]")) { + return JType.array(this, asJType(typeFromName(erased.substring(0, erased.length() - 2)))); + } + return getClassType(erased); + } + + private JType primitiveType(String name) { + return rawTypes.computeIfAbsent(name, key -> JType.primitive(this, key)); + } + + private JType asJType(Type type) { + return type instanceof JType jType ? jType : JType.unknown(this); + } + + private JType rawNamed(String fqn) { + return rawTypes.computeIfAbsent(sourceName(fqn), name -> JType.named(this, binaryNames.getOrDefault(name, name), List.of())); + } + + private List parameterTypesOf(String descriptor) { + List types = new ArrayList<>(); + int[] index = {1}; + while (index[0] < descriptor.length() && descriptor.charAt(index[0]) != ')') { + types.add(readDescriptor(descriptor, index)); + } + return types; + } + + private Type readDescriptor(String descriptor, int[] index) { + int array = 0; + while (descriptor.charAt(index[0]) == '[') { + array++; + index[0]++; + } + Type type; + char kind = descriptor.charAt(index[0]); + if (kind == 'L') { + int semi = descriptor.indexOf(';', index[0]); + String binary = descriptor.substring(index[0] + 1, semi).replace('/', '.'); + index[0] = semi + 1; + type = getClassType(binary); + } else { + index[0]++; + type = primitiveType(switch (kind) { + case 'B' -> "byte"; + case 'C' -> "char"; + case 'D' -> "double"; + case 'F' -> "float"; + case 'I' -> "int"; + case 'J' -> "long"; + case 'S' -> "short"; + case 'Z' -> "boolean"; + default -> "void"; + }); + } + for (int i = 0; i < array; i++) { + type = JType.array(this, asJType(type)); + } + return type; + } + + /** Whether the project, the classpath or the JDK declares the type {@code fqn}. */ + boolean declaresType(String name) { + String fqn = sourceName(name); + if (!typeInfos.containsKey(fqn) && typesLoaded && requestedTypes.add(fqn)) { + ingestTypes(typeLoader.apply(Set.of(fqn))); + } + return typeInfos.containsKey(fqn); + } + + /** The unknown type a written name stands for when nothing declares it. */ + JTypeSymbol recoveredType(String name) { + JTypeSymbol recovered = typeSymbol(name); + if (!recovered.isUnknown() && !declaresType(name)) { + recovered.unknown(true); + } + return recovered; + } + + Symbol packageSymbol(String name) { + if (name == null || name.isEmpty()) { + return Symbol.ROOT_PACKAGE; + } + String key = "pkg:" + name; + JSymbol found = symbols.get(key); + if (found != null) { + return found; + } + JPackageSymbol pkg = new JPackageSymbol(this, name); + symbols.put(key, pkg); + pkg.owner(Symbol.ROOT_PACKAGE); + return pkg; + } + + @Nullable + /** + * Local and anonymous classes are identified as members of their enclosing type, with the declaring + * method embedded in the member name, as in {@code com.acme.Owner#create()$Local$1}. They carry no + * declaration of their own, so the symbol is rebuilt from the identifier and owned by whatever + * declares it, which is the method for a local class and the local class for a nested anonymous one. + */ + private JSymbol createTypeInMethodBody(String id, MemberId parsed) { + int lastSegment = parsed.name.lastIndexOf('$'); + String simpleName = parsed.name.substring(lastSegment + 1); + boolean anonymous = simpleName.chars().allMatch(Character::isDigit); + JTypeSymbol symbol = new JTypeSymbol(this, id, anonymous ? "" : simpleName, false); + symbols.put(id, symbol); + typesByName.putIfAbsent(id, symbol); + symbol.owner(resolve(parsed.owner + "#" + parsed.name.substring(0, lastSegment))); + symbol.type(rawNamed(id)); + JsonObject fact = facts.get(id); + if (fact != null) { + applyFact(symbol, fact); + } + return symbol; + } + + private static MemberId parseMember(String id) { + int hash = id.indexOf('#'); + if (hash < 0) { + return null; + } + String owner = id.substring(0, hash); + String rest = id.substring(hash + 1); + int paren = rest.endsWith(")") ? rest.lastIndexOf('(') : -1; + if (paren < 0) { + return new MemberId(owner, rest, null, false, -1); + } + String name = rest.substring(0, paren); + String params = rest.substring(paren + 1, rest.length() - 1); + boolean arityOnly = !params.isEmpty() && params.chars().allMatch(Character::isDigit); + int arity = arityOnly ? Integer.parseInt(params) : (params.isEmpty() ? 0 : splitParams(params).size()); + return new MemberId(owner, name, params, arityOnly, arity); + } + + private static List splitParams(String params) { + if (params.isEmpty()) { + return List.of(); + } + List result = new ArrayList<>(); + int depth = 0; + int start = 0; + for (int i = 0; i < params.length(); i++) { + char current = params.charAt(i); + if (current == '<') { + depth++; + } else if (current == '>') { + depth--; + } else if (current == ',' && depth == 0) { + result.add(params.substring(start, i)); + start = i + 1; + } + } + result.add(params.substring(start)); + return result; + } + + static String erase(String id) { + StringBuilder out = new StringBuilder(id.length()); + for (int i = 0; i < id.length(); i++) { + char current = id.charAt(i); + if (current == '`') { + while (i + 1 < id.length() && Character.isDigit(id.charAt(i + 1))) { + i++; + } + } else { + out.append(current); + } + } + return out.toString(); + } + + private static String simpleName(String fqn) { + int boundary = Math.max(fqn.lastIndexOf('.'), fqn.lastIndexOf('$')); + return boundary < 0 ? fqn : fqn.substring(boundary + 1); + } + + private static String packageName(String fqn) { + String top = fqn; + int nested = top.indexOf('$'); + if (nested >= 0) { + top = top.substring(0, nested); + } + int dot = top.lastIndexOf('.'); + return dot < 0 ? "" : top.substring(0, dot); + } + + @Nullable + private static String text(JsonObject object, String key) { + if (object == null || !object.has(key) || object.get(key).isJsonNull()) { + return null; + } + JsonElement element = object.get(key); + return element.isJsonPrimitive() ? element.getAsString() : null; + } + + private static boolean truth(JsonObject object, String key) { + return object.has(key) && object.get(key).isJsonPrimitive() && object.get(key).getAsBoolean(); + } + + private static List strings(JsonObject object, String key) { + if (object == null || !object.has(key) || object.get(key).isJsonNull()) { + return List.of(); + } + JsonElement element = object.get(key); + if (element.isJsonPrimitive()) { + return List.of(element.getAsString()); + } + if (!element.isJsonArray()) { + return List.of(); + } + List values = new ArrayList<>(); + for (JsonElement item : element.getAsJsonArray()) { + if (item.isJsonPrimitive()) { + values.add(item.getAsString()); + } + } + return values; + } + + private static List flat(JsonObject object, String key) { + List values = new ArrayList<>(); + for (String value : strings(object, key)) { + for (String part : value.split(",")) { + if (!part.isBlank()) { + values.add(part.trim()); + } + } + } + return values; + } + + private record MemberId(String owner, String name, @Nullable String params, boolean arityOnly, int arity) { + } + + record DeclaredAnnotation(String name, List values) { + } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java index d63bd1380e7..1e747e409bf 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java @@ -16,468 +16,295 @@ */ package org.sonar.java.model; -import java.util.Arrays; +import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Objects; +import java.util.Set; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.IBinding; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.IVariableBinding; -import org.eclipse.jdt.core.dom.Modifier; -import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.Tree; +/** + * A symbol keyed by its SemSitter id. Two symbols with the same id are equal across files, + * which is what a rule needs when the declaration and the use were translated separately. + */ abstract class JSymbol implements Symbol { - protected final JSema sema; - protected final IBinding binding; - - /** - * Cache for {@link #hashCode()}. - */ - private int hashCode; - - /** - * Cache for {@link #owner()}. - */ - private Symbol owner; + enum Kind { + TYPE, METHOD, VARIABLE, PACKAGE + } - /** - * Cache for {@link #metadata()}. - */ + final JSema sema; + final Kind kind; + @Nullable + final String id; + private final String name; + private Symbol owner = Symbol.UNKNOWN_SYMBOL; + private Type type = Type.UNKNOWN; + private boolean unknown; + private boolean enumSymbol; + private boolean interfaceSymbol; + private boolean annotationSymbol; + private boolean receiver; + private boolean deprecated; + private static final String PRIVATE = "private"; + private final Set modifiers = new HashSet<>(); private SymbolMetadata metadata; + private int hash; - JSymbol(JSema sema, IBinding binding) { + JSymbol(JSema sema, Kind kind, @Nullable String id, String name) { this.sema = Objects.requireNonNull(sema); - this.binding = Objects.requireNonNull(binding); + this.kind = kind; + this.id = id; + this.name = name == null ? "" : name; } - @Override - public final boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof JSymbol)) { - return false; - } - JSymbol other = (JSymbol) obj; - if (this.binding.getKind() != other.binding.getKind()) { - return false; - } - switch (this.binding.getKind()) { - case IBinding.TYPE: - return JType.areEqual( - (ITypeBinding) this.binding, - (ITypeBinding) other.binding - ); - case IBinding.VARIABLE: - return areEqualVariables( - this, - other - ); - case IBinding.METHOD: - return areEqualMethodSymbols((JMethodSymbol) this, (JMethodSymbol) other); - default: - return super.equals(obj); - } + void owner(Symbol owner) { + this.owner = owner == null ? Symbol.UNKNOWN_SYMBOL : owner; } - private static boolean areEqualVariables(JSymbol thisVariableSymbol, JSymbol otherVariableSymbol) { - IVariableBinding thisBinding = (IVariableBinding) thisVariableSymbol.binding; - IVariableBinding otherBinding = (IVariableBinding) otherVariableSymbol.binding; - return thisBinding.getVariableId() == otherBinding.getVariableId() - && thisVariableSymbol.owner().equals(otherVariableSymbol.owner()); - } - - /** - * Method symbols are considered equal if they have the same name, same owner, same parameter types, same type parameters and same type arguments. - * In this example: - *
-   * {@code   boolean foo(T t, String s);
-   *  foo(43, "bar");}
-   * 
- * The first symbol {@code foo} has type parameter {@code T}, parameter types {@code T} and {@code String}, and no type arguments. - * The symbol {@code foo} on the second line has no type parameter, parameter types are {@code Integer} and {@code String}, and has type argument {@code Integer}. - * So the two {@code foo} symbols are not equal, although they may refer to the same method. - */ - private static boolean areEqualMethodSymbols(JMethodSymbol thisMethodSymbol, JMethodSymbol otherMethodSymbol) { - IMethodBinding thisBinding = (IMethodBinding) thisMethodSymbol.binding; - IMethodBinding otherBinding = (IMethodBinding) otherMethodSymbol.binding; - // In the case of lambdas, ecj doesn't assign a name to the different symbols, unlike the Java compiler. - // We work around that by comparing keys provided by ecj. - if (thisMethodSymbol.isLambda() && otherMethodSymbol.isLambda()) { - return Objects.equals(thisBinding.getKey(), otherBinding.getKey()); - } - return thisMethodSymbol.name().equals(otherMethodSymbol.name()) - && thisMethodSymbol.owner().equals(otherMethodSymbol.owner()) - && Arrays.equals(thisBinding.getParameterTypes(), otherBinding.getParameterTypes()) - && Arrays.equals(thisBinding.getTypeParameters(), otherBinding.getTypeParameters()) - && Arrays.equals(thisBinding.getTypeArguments(), otherBinding.getTypeArguments()); + void type(Type type) { + this.type = type == null ? Type.UNKNOWN : type; } - @Override - public final int hashCode() { - if (hashCode == 0) { - Symbol resolvedOwner = owner(); - hashCode = resolvedOwner == null ? 0 : (resolvedOwner.hashCode() * 31); - hashCode += name().hashCode(); - } - return hashCode; + void unknown(boolean unknown) { + this.unknown = unknown; } - /** - * @see JType#name() - */ - @Override - public final String name() { - if (binding.getKind() == IBinding.METHOD && ((IMethodBinding) binding).isConstructor()) { - return ""; - } - if (binding.getKind() == IBinding.TYPE && ((ITypeBinding) binding).isParameterizedType()) { - // without names of parameters - return ((ITypeBinding) binding).getErasure().getName(); - } - return binding.getName(); + void enumSymbol(boolean enumSymbol) { + this.enumSymbol = enumSymbol; } - /** - * @see #enclosingClass() - */ - @Override - public final Symbol owner() { - if (isUnknown()) { - return Symbol.UNKNOWN_SYMBOL; - } - if (owner == null) { - owner = convertOwner(); - } - return owner; + void interfaceSymbol(boolean interfaceSymbol) { + this.interfaceSymbol = interfaceSymbol; } - private Symbol convertOwner() { - switch (binding.getKind()) { - case IBinding.PACKAGE: - return Symbol.ROOT_PACKAGE; - case IBinding.TYPE: - return typeOwner((ITypeBinding) binding); - case IBinding.METHOD: - return methodOwner((IMethodBinding) binding); - case IBinding.VARIABLE: - return variableOwner((IVariableBinding) binding); - default: - throw new IllegalStateException(unexpectedBinding()); - } + void annotationSymbol(boolean annotationSymbol) { + this.annotationSymbol = annotationSymbol; } - private String unexpectedBinding() { - return "Unexpected binding Kind: " + binding.getKind(); + boolean isAnnotationSymbol() { + return annotationSymbol; } - private Symbol typeOwner(ITypeBinding typeBinding) { - if (typeBinding.isPrimitive()) { - return Symbol.ROOT_PACKAGE; - } - IMethodBinding declaringMethod = typeBinding.getDeclaringMethod(); - if (declaringMethod != null) { - // local type - return sema.methodSymbol(declaringMethod); - } - ITypeBinding declaringClass = typeBinding.getDeclaringClass(); - if (declaringClass != null) { - // member type - return sema.typeSymbol(declaringClass); - } - // top-level type - return sema.packageSymbol(typeBinding.getPackage()); + /** {@code this} and {@code super}, which have no visibility. */ + void receiver(boolean receiver) { + this.receiver = receiver; } - private Symbol methodOwner(IMethodBinding methodBinding) { - return sema.typeSymbol(methodBinding.getDeclaringClass()); + void deprecated(boolean deprecated) { + this.deprecated = deprecated; } - private Symbol variableOwner(IVariableBinding variableBinding) { - if (!variableBinding.isRecordComponent()) { - IMethodBinding declaringMethod = variableBinding.getDeclaringMethod(); - if (declaringMethod != null) { - return sema.methodSymbol(declaringMethod); - } - ITypeBinding declaringClass = variableBinding.getDeclaringClass(); - if (declaringClass != null) { - // field - return sema.typeSymbol(declaringClass); + void copyStateFrom(JSymbol other) { + owner = other.owner; + unknown = other.unknown; + enumSymbol = other.enumSymbol; + interfaceSymbol = other.interfaceSymbol; + annotationSymbol = other.annotationSymbol; + deprecated = other.deprecated; + modifiers.addAll(other.modifiers); + } + + void addModifiers(Collection values) { + for (String value : values) { + for (String part : value.split(",")) { + if (!part.isBlank()) { + modifiers.add(part.trim()); + } } } - return ownerOfRecordComponentConstant(variableBinding); } - private Symbol ownerOfRecordComponentConstant(IVariableBinding variableBinding) { - Tree node = sema.declarations.get(variableBinding); - if (node == null) { - // array.length - return Symbol.UNKNOWN_SYMBOL; + boolean hasModifier(String modifier) { + return modifiers.contains(modifier); + } + + @Override + public final boolean equals(Object obj) { + if (this == obj) { + return true; } - boolean initializerBlock = false; - boolean staticInitializerBlock = false; - while (true) { - node = node.parent(); - switch (node.kind()) { - case CLASS, - RECORD, - ENUM: - JTypeSymbol typeSymbol = (JTypeSymbol) ((ClassTreeImpl) node).symbol(); - if (initializerBlock) { - return sema.initializerBlockSymbol(typeSymbol); - } - if (staticInitializerBlock) { - return sema.staticInitializerBlockSymbol(typeSymbol); - } - // Record parameters - return typeSymbol; - case INITIALIZER: - initializerBlock = true; - break; - case STATIC_INITIALIZER: - staticInitializerBlock = true; - break; - case METHOD, - CONSTRUCTOR: - // local variable declaration in recovered method - // and recovered methods do not have bindings - return MethodSymbol.UNKNOWN_METHOD; - default: - // continue exploring parent - break; - } + if (!(obj instanceof JSymbol other) || kind != other.kind) { + return false; } + if (id != null && other.id != null) { + return id.equals(other.id); + } + return name.equals(other.name) && Objects.equals(owner, other.owner); } @Override - public final Type type() { - switch (binding.getKind()) { - case IBinding.TYPE: - return sema.type((ITypeBinding) binding); - case IBinding.VARIABLE: - ITypeBinding variableType = ((IVariableBinding) binding).getType(); - return variableType != null ? sema.type(variableType) : Type.UNKNOWN; - case IBinding.PACKAGE, - IBinding.METHOD: - return Type.UNKNOWN; - default: - throw new IllegalStateException(unexpectedBinding()); + public final int hashCode() { + if (hash == 0) { + hash = Objects.hash(kind, id != null ? id : name); + if (hash == 0) { + hash = 1; + } } + return hash; + } + + @Override + public final String name() { + return name; + } + + @Override + public final Symbol owner() { + return owner; + } + + @Override + public Type type() { + return type; } @Override public final boolean isVariableSymbol() { - return binding.getKind() == IBinding.VARIABLE; + return kind == Kind.VARIABLE; } @Override public final boolean isTypeSymbol() { - return !isUnknown() && binding.getKind() == IBinding.TYPE; + return kind == Kind.TYPE; } @Override public final boolean isMethodSymbol() { - return binding.getKind() == IBinding.METHOD; + return kind == Kind.METHOD; } @Override public final boolean isPackageSymbol() { - return binding.getKind() == IBinding.PACKAGE; + return kind == Kind.PACKAGE; } @Override public final boolean isStatic() { - return Modifier.isStatic(binding.getModifiers()); + return hasModifier("static") || (inInterface() && (isVariableSymbol() || isTypeSymbol())) || enumConstant(); } @Override public final boolean isFinal() { - return Modifier.isFinal(binding.getModifiers()); + return hasModifier("final") || (inInterface() && isVariableSymbol()) || enumConstant(); + } + + /** Whether this is a member of an interface or annotation type, which gives it implicit modifiers. */ + private boolean inInterface() { + return owner instanceof JSymbol ownerType && ownerType.isTypeSymbol() && ownerType.interfaceSymbol + && !(this instanceof JTypeSymbol typeSymbol && typeSymbol.isTypeParameter()); + } + + private boolean enumConstant() { + return isVariableSymbol() && enumSymbol; + } + + /** Enum constructors are private whatever they declare. */ + private boolean enumConstructor() { + return isMethodSymbol() && "".equals(name) && owner instanceof JSymbol ownerType && ownerType.isTypeSymbol() && ownerType.enumSymbol; } @Override public final boolean isEnum() { - switch (binding.getKind()) { - case IBinding.TYPE: - return ((ITypeBinding) binding).isEnum(); - case IBinding.VARIABLE: - return ((IVariableBinding) binding).isEnumConstant(); - default: - return false; - } + return enumSymbol; } @Override public final boolean isInterface() { - return binding.getKind() == IBinding.TYPE - && ((ITypeBinding) binding).isInterface(); + return interfaceSymbol; } @Override public final boolean isAbstract() { - return Modifier.isAbstract(binding.getModifiers()); + if (interfaceSymbol && isTypeSymbol()) { + return false; + } + return hasModifier("abstract") + || (inInterface() && isMethodSymbol() && !hasModifier("default") && !hasModifier("static") && !hasModifier(PRIVATE)); } @Override public final boolean isPublic() { - return Modifier.isPublic(binding.getModifiers()); + return hasModifier("public") || (inInterface() && !hasModifier(PRIVATE)) || enumConstant(); } @Override public final boolean isPrivate() { - return Modifier.isPrivate(binding.getModifiers()); + return hasModifier(PRIVATE) || enumConstructor(); } @Override public final boolean isProtected() { - return Modifier.isProtected(binding.getModifiers()); + return hasModifier("protected"); } @Override public final boolean isPackageVisibility() { - return !isPublic() && !isPrivate() && !isProtected(); + return !receiver && !isPublic() && !isPrivate() && !isProtected(); } @Override public final boolean isDeprecated() { - return binding.isDeprecated(); + return deprecated; } @Override public final boolean isVolatile() { - return Modifier.isVolatile(binding.getModifiers()); + return hasModifier("volatile"); } @Override - public final boolean isUnknown() { - return binding.isRecovered(); + public boolean isUnknown() { + return unknown; } @Override public final SymbolMetadata metadata() { if (metadata == null) { - try { - metadata = convertMetadata(); - } catch (RuntimeException e) { - // ECJ raises exception in rare occasions, when it is the case, we don't want to prevent the whole analysis of the file - // ECJ 3.41.0: no longer throws the exception on the code that was used in the test - metadata = Symbols.EMPTY_METADATA; - } + metadata = new JSymbolMetadata(this); } return metadata; } - private SymbolMetadata convertMetadata() { - switch (binding.getKind()) { - case IBinding.PACKAGE: - return new JSymbolMetadata(sema, this, sema.resolvePackageAnnotations(binding.getName())); - case IBinding.VARIABLE: - ITypeBinding type = ((IVariableBinding) binding).getType(); - if (type == null) { - return new JSymbolMetadata(sema, this, binding.getAnnotations()); - } - return convertMetadata(type); - case IBinding.METHOD: - ITypeBinding returnType = ((IMethodBinding) binding).getReturnType(); - // In rare circumstances, when the semantic information is incomplete, returnType can be null. - if (returnType == null) { - return Symbols.EMPTY_METADATA; - } - return convertMetadata(returnType); - default: - return new JSymbolMetadata(sema, this, binding.getAnnotations()); - } - } - - private SymbolMetadata convertMetadata(ITypeBinding type) { - return JSymbolMetadata.of(sema, this, type, binding.getAnnotations()); - } - - /** - * @see #owner() - */ @Nullable @Override - public final TypeSymbol enclosingClass() { - switch (binding.getKind()) { - case IBinding.PACKAGE: - return null; - case IBinding.TYPE: - return typeEnclosingClass((ITypeBinding) binding); - case IBinding.METHOD: - return methodEnclosingClass((IMethodBinding) binding); - case IBinding.VARIABLE: - return variableEnclosingClass((IVariableBinding) binding); - default: - throw new IllegalStateException(unexpectedBinding()); - } - } - - private TypeSymbol typeEnclosingClass(ITypeBinding typeBinding) { - ITypeBinding declaringClass = typeBinding.getDeclaringClass(); - if (declaringClass != null) { - // nested (member or local) type - return sema.typeSymbol(declaringClass); + public TypeSymbol enclosingClass() { + if (this instanceof JTypeSymbol typeSymbol && typeSymbol.isTypeParameter()) { + return owner() instanceof TypeSymbol ownerType ? ownerType : null; } - // top-level type - return (TypeSymbol) this; - } - - private TypeSymbol methodEnclosingClass(IMethodBinding methodBinding) { - ITypeBinding declaringClass = methodBinding.getDeclaringClass(); - return sema.typeSymbol(declaringClass); - } - - private TypeSymbol variableEnclosingClass(IVariableBinding variableBinding) { - ITypeBinding declaringClass = variableBinding.getDeclaringClass(); - if (declaringClass != null) { - // field - return sema.typeSymbol(declaringClass); - } - IMethodBinding declaringMethod = variableBinding.getDeclaringMethod(); - if (declaringMethod != null) { - // local variable - return sema.typeSymbol(declaringMethod.getDeclaringClass()); - } - Tree node = sema.declarations.get(variableBinding); - if (node == null) { - // array.length - return TypeSymbol.UNKNOWN_TYPE; - } - do { - node = node.parent(); - switch (node.kind()) { - case CLASS, - RECORD, - ENUM: - // variable declaration in a static or instance initializer - // or local variable declaration in recovered method - return ((ClassTreeImpl) node).symbol(); - default: - // continue exploring parent - break; + Symbol current = this; + while (current != null && !current.isTypeSymbol()) { + if (current.isPackageSymbol() || current.isUnknown()) { + return null; } - } while (true); + current = current.owner(); + } + return (TypeSymbol) current; } @Override public final List usages() { - List usages = sema.usages.get(JSema.declarationBinding(binding)); - return usages != null ? usages : Collections.emptyList(); + if (id == null) { + return Collections.emptyList(); + } + List found = sema.usages.get(id); + return found == null ? Collections.emptyList() : found; } @Nullable @Override public Tree declaration() { - return sema.declarations.get(JSema.declarationBinding(binding)); + return id == null ? null : sema.declarations.get(id); } + @Override + public String toString() { + return id == null ? name : id; + } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java index 2b9c5b6b862..2098879108a 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadata.java @@ -16,106 +16,50 @@ */ package org.sonar.java.model; +import java.util.ArrayList; import java.util.Arrays; import java.util.EnumMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.function.Function; import java.util.function.Predicate; -import java.util.stream.Collectors; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.IAnnotationBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.IVariableBinding; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.AnnotationInstance; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityData; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityLevel; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityTarget; +import org.sonar.plugins.java.api.semantic.SymbolMetadata.NullabilityType; +import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.AnnotationTree; +import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.IdentifierTree; +import org.sonar.plugins.java.api.tree.LiteralTree; +import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; import org.sonar.plugins.java.api.tree.MethodTree; +import org.sonar.plugins.java.api.tree.NewArrayTree; import org.sonar.plugins.java.api.tree.Tree; -import static org.sonar.java.model.JSymbolMetadataNullabilityHelper.getNullabilityDataAtLevel; - +/** + * Annotations of one symbol, taken from its declaration tree when the file contains it and from + * the graph fact otherwise. Nullability walks those annotations and then overridden methods. + */ final class JSymbolMetadata implements SymbolMetadata { private static final NullabilityData[] NO_ANNOTATION_NULLABILITY = forEachLevel(level -> new JNullabilityData(NullabilityType.NO_ANNOTATION, level, null, null, false)); - private static final NullabilityData[] UNKNOWN_NULLABILITY = forEachLevel(level -> new JNullabilityData(NullabilityType.UNKNOWN, level, null, null, false)); - private final JSema sema; - private final Symbol symbol; - private final IAnnotationBinding[] symbolBindings; - private final SymbolMetadata[] parameterMetas; - private final IAnnotationBinding[] annotationBindings; - - /** - * Cache for {@link #annotations()}. - */ + private final JSymbol symbol; private List annotations; - - /** - * Cache for {@link #symbolAnnotations()}. - */ - private List symbolAnnotations; - private final Map nullabilityCache = new EnumMap<>(NullabilityTarget.class); - private final boolean isTypeArgMetadata; - - JSymbolMetadata(JSema sema, Symbol symbol, IAnnotationBinding[] annotationBindings) { - this.sema = Objects.requireNonNull(sema); + JSymbolMetadata(JSymbol symbol) { this.symbol = symbol; - this.symbolBindings = annotationBindings; - this.parameterMetas = new SymbolMetadata[0]; - this.isTypeArgMetadata = false; - - this.annotationBindings = annotationBindings; - } - - private JSymbolMetadata(JSema sema, Symbol symbol, IAnnotationBinding[] symbolAnnotations, JSymbolMetadata[] parameterMetaDatas, boolean isTypeArgMetadata) { - this.sema = Objects.requireNonNull(sema); - this.symbol = symbol; - this.symbolBindings = symbolAnnotations; - this.parameterMetas = parameterMetaDatas; - this.isTypeArgMetadata = isTypeArgMetadata; - - int total = symbolBindings.length; - for (JSymbolMetadata parameterMetaData : parameterMetaDatas) { - total += parameterMetaData.annotationBindings.length; - } - - this.annotationBindings = new IAnnotationBinding[total]; - int current = symbolAnnotations.length; - System.arraycopy(symbolAnnotations, 0, this.annotationBindings, 0, symbolAnnotations.length); - - for (JSymbolMetadata parameterMetaData : parameterMetaDatas) { - var paramBindings = parameterMetaData.annotationBindings; - System.arraycopy(paramBindings, 0, this.annotationBindings, current, paramBindings.length); - current += paramBindings.length; - } - } - - public static JSymbolMetadata of(JSema sema, Symbol owner, ITypeBinding type, IAnnotationBinding[] externalAnnotations) { - var symbolAnnotations = new IAnnotationBinding[externalAnnotations.length + type.getTypeAnnotations().length]; - System.arraycopy(externalAnnotations, 0, symbolAnnotations, 0, externalAnnotations.length); - System.arraycopy(type.getTypeAnnotations(), 0, symbolAnnotations, externalAnnotations.length, type.getTypeAnnotations().length); - - var parameterMetaDatas = Arrays.stream(type.getTypeArguments()) - .map(param -> ofTypeArg(sema, owner, param)) - .toArray(JSymbolMetadata[]::new); - - return new JSymbolMetadata(sema, owner, symbolAnnotations, parameterMetaDatas, false); - } - - private static JSymbolMetadata ofTypeArg(JSema sema, Symbol owner, ITypeBinding param) { - var symbolAnnotations = param.getTypeAnnotations(); - var parameterMetaDatas = Arrays.stream(param.getTypeArguments()) - .map(p -> ofTypeArg(sema, owner, p)) - .toArray(JSymbolMetadata[]::new); - return new JSymbolMetadata(sema, owner, symbolAnnotations, parameterMetaDatas, true); } private static NullabilityData[] forEachLevel(Function initializer) { @@ -133,27 +77,36 @@ public static NullabilityData unknownNullabilityAt(NullabilityLevel level) { @Override public List annotations() { if (annotations == null) { - annotations = Arrays.stream(annotationBindings) - .map(sema::annotation) - .collect(Collectors.toList()); + annotations = loadAnnotations(); } return annotations; } @Override public List symbolAnnotations() { - if (symbolAnnotations == null) { - symbolAnnotations = Arrays.stream(symbolBindings) - .map(sema::annotation) - .collect(Collectors.toList()); + return annotations(); + } + + private List loadAnnotations() { + Tree declaration = symbol.declaration(); + if (declaration != null) { + List fromTree = new ArrayList<>(); + for (AnnotationTree annotation : ModifiersUtils.getAnnotations(declaration)) { + fromTree.add(new TreeAnnotation(symbol.sema, annotation)); + } + return fromTree; } - return symbolAnnotations; + List fromFacts = new ArrayList<>(); + for (JSema.DeclaredAnnotation annotation : symbol.sema.declaredAnnotations(symbol.id)) { + fromFacts.add(new FactAnnotation(symbol.sema, JSema.erase(annotation.name()), annotation.values())); + } + return fromFacts; } @Override - public final boolean isAnnotatedWith(String fullyQualifiedName) { - for (AnnotationInstance a : annotations()) { - if (a.symbol().type().is(fullyQualifiedName)) { + public boolean isAnnotatedWith(String fullyQualifiedName) { + for (AnnotationInstance annotation : annotations()) { + if (annotation.symbol().type().is(fullyQualifiedName)) { return true; } } @@ -163,10 +116,9 @@ public final boolean isAnnotatedWith(String fullyQualifiedName) { @Nullable @Override public List valuesForAnnotation(String fullyQualifiedNameOfAnnotation) { - for (AnnotationInstance a : annotations()) { - if (a.symbol().type().is(fullyQualifiedNameOfAnnotation)) { - // TODO what about repeating annotations? - return a.values(); + for (AnnotationInstance annotation : annotations()) { + if (annotation.symbol().type().is(fullyQualifiedNameOfAnnotation)) { + return annotation.values(); } } return null; @@ -174,7 +126,7 @@ public List valuesForAnnotation(String fullyQualifiedNameOfAnno @Override public NullabilityData nullabilityData() { - NullabilityTarget target = getTarget(symbol); + NullabilityTarget target = target(symbol); if (target == null) { return unknownNullabilityAt(NullabilityLevel.UNKNOWN); } @@ -190,128 +142,101 @@ public NullabilityData nullabilityData(NullabilityTarget target) { @Override public AnnotationTree findAnnotationTree(AnnotationInstance annotationInstance) { Tree declaration = symbol.declaration(); - if (declaration != null) { - return findAnnotationTree(ModifiersUtils.getAnnotations(declaration), annotationInstance); + if (declaration == null) { + return null; + } + for (AnnotationTree annotation : ModifiersUtils.getAnnotations(declaration)) { + if (annotation.annotationType().symbolType().symbol().equals(annotationInstance.symbol()) + && annotation.arguments().size() == annotationInstance.values().size()) { + return annotation; + } } return null; } @Override public SymbolMetadata[] parametersMetadata() { - return parameterMetas; - } - - @Nullable - private static AnnotationTree findAnnotationTree(List annotations, AnnotationInstance annotationInstance) { - for (AnnotationTree annotationTree : annotations) { - if (annotationTree.symbolType().symbol() == annotationInstance.symbol() && - annotationTree.arguments().size() == annotationInstance.values().size()) { - return annotationTree; - } - } - return null; + return new SymbolMetadata[0]; } private NullabilityData resolveNullability(NullabilityTarget target) { - NullabilityLevel currentLevel = getLevel(symbol); - NullabilityData nullabilityDataAtLevel = getNullabilityDataAtLevel(this, target, currentLevel); - if (nullabilityDataAtLevel.type() != NullabilityType.NO_ANNOTATION) { - return nullabilityDataAtLevel; - } - - // Check nullability from the inheritance hierarchy. - // Skipped for type argument metadata: type arg nullability must not inherit from the enclosing - // method's return type annotations, which belong to the outer type, not the type argument. - if (symbol.isMethodSymbol() && !isTypeArgMetadata) { - NullabilityData nullabilityDataFromInheritance = getNullabilityDataFromInheritance((Symbol.MethodSymbol) symbol, target); - if (nullabilityDataFromInheritance.type() != NullabilityType.NO_ANNOTATION) { - return nullabilityDataFromInheritance; + NullabilityLevel level = level(symbol); + NullabilityData atLevel = JSymbolMetadataNullabilityHelper.getNullabilityDataAtLevel(this, target, level); + if (atLevel.type() != NullabilityType.NO_ANNOTATION) { + return atLevel; + } + if (symbol.isMethodSymbol()) { + NullabilityData inherited = inheritedNullability((Symbol.MethodSymbol) symbol, target); + if (inherited.type() != NullabilityType.NO_ANNOTATION) { + return inherited; } } - - // Not annotated or meta annotated, check upper level in the ownership hierarchy... if (symbol.isPackageSymbol()) { - return NO_ANNOTATION_NULLABILITY[currentLevel.ordinal()]; + return NO_ANNOTATION_NULLABILITY[level.ordinal()]; } - Symbol owner = getEffectiveOwner(symbol, currentLevel); - if (owner == null) return unknownNullabilityAt(currentLevel); - var metadata = owner.metadata(); - return metadata.nullabilityData(target); - } - - private static NullabilityData getNullabilityDataFromInheritance(Symbol.MethodSymbol methodSymbol, NullabilityTarget target) { - List overriddenSymbols = methodSymbol.overriddenSymbols(); - NullabilityLevel level = NullabilityLevel.METHOD; - for (Symbol.MethodSymbol overriddenSymbol : overriddenSymbols) { - SymbolMetadata metadata = overriddenSymbol.metadata(); - NullabilityData nullabilityData = getNullabilityDataAtLevel(metadata, target, level); - if (nullabilityData.type() != NullabilityType.NO_ANNOTATION && !nullabilityData.equals(unknownNullabilityAt(level))) { - return nullabilityData; - } + Symbol owner = symbol.owner(); + if (owner == null || owner.isUnknown()) { + return unknownNullabilityAt(level); } - return NO_ANNOTATION_NULLABILITY[level.ordinal()]; + return owner.metadata().nullabilityData(target); } - @CheckForNull - private static Symbol getEffectiveOwner(Symbol symbol, NullabilityLevel currentLevel) { - Symbol owner = symbol.owner(); - if (owner != null && owner.isMethodSymbol() && currentLevel == NullabilityLevel.CLASS) { - // Owner of anonymous class can be a method symbol, but the nullability of the method is unrelated to the ones from the anonymous classes. - return getEffectiveOwner(owner, currentLevel); + private static NullabilityData inheritedNullability(Symbol.MethodSymbol method, NullabilityTarget target) { + for (Symbol.MethodSymbol overridden : method.overriddenSymbols()) { + NullabilityData data = JSymbolMetadataNullabilityHelper.getNullabilityDataAtLevel(overridden.metadata(), target, NullabilityLevel.METHOD); + if (data.type() != NullabilityType.NO_ANNOTATION && !data.equals(unknownNullabilityAt(NullabilityLevel.METHOD))) { + return data; + } } - return owner; + return NO_ANNOTATION_NULLABILITY[NullabilityLevel.METHOD.ordinal()]; } - private static NullabilityLevel getLevel(Symbol symbol) { + private static NullabilityLevel level(Symbol symbol) { if (symbol.isVariableSymbol()) { return NullabilityLevel.VARIABLE; - } else if (symbol.isMethodSymbol()) { + } + if (symbol.isMethodSymbol()) { return NullabilityLevel.METHOD; - } else if (symbol.isTypeSymbol()) { + } + if (symbol.isTypeSymbol()) { return NullabilityLevel.CLASS; - } else if (symbol.isPackageSymbol()) { + } + if (symbol.isPackageSymbol()) { return NullabilityLevel.PACKAGE; } return NullabilityLevel.UNKNOWN; } @CheckForNull - private static NullabilityTarget getTarget(Symbol symbol) { + private static NullabilityTarget target(Symbol symbol) { if (symbol.isMethodSymbol()) { return NullabilityTarget.METHOD; - } else if (symbol.isVariableSymbol()) { - Symbol owner = symbol.owner(); - if (owner != null && owner.isTypeSymbol()) { - return NullabilityTarget.FIELD; - } - Tree variableTree = symbol.declaration(); - if (variableTree == null) { - return NullabilityTarget.PARAMETER; - } - Tree variableTreeParent = variableTree.parent(); - if (variableTreeParent == null || variableTreeParent instanceof MethodTree) { - return NullabilityTarget.PARAMETER; - } - // If variableTreeParent is not a lambda, we consider that the target is a local variable. - if (!variableTreeParent.is(Tree.Kind.LAMBDA_EXPRESSION)) { - return NullabilityTarget.LOCAL_VARIABLE; - } - // When variableTreeParent is an instance of LambdaExpressionTree we intentionally return null - // because parameters of lambdas are not supported. } - return null; + if (!symbol.isVariableSymbol()) { + return null; + } + Symbol owner = symbol.owner(); + if (owner != null && owner.isTypeSymbol()) { + return NullabilityTarget.FIELD; + } + Tree declaration = symbol.declaration(); + if (declaration == null || declaration.parent() == null || declaration.parent() instanceof MethodTree) { + return NullabilityTarget.PARAMETER; + } + if (declaration.parent().is(Tree.Kind.LAMBDA_EXPRESSION)) { + return null; + } + return NullabilityTarget.LOCAL_VARIABLE; } static final class JNullabilityData implements NullabilityData { private final NullabilityType type; private final NullabilityLevel level; - @Nullable private final AnnotationInstance annotation; @Nullable private final AnnotationTree annotationTree; - private final boolean metaAnnotation; public JNullabilityData(NullabilityType type, NullabilityLevel level, @Nullable AnnotationInstance annotation, @@ -340,29 +265,28 @@ public boolean metaAnnotation() { @Override public boolean isNonNull(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue) { - return testNullabilityType(minLevel, ignoreMetaAnnotation, defaultValue, t -> t == NullabilityType.NON_NULL); + return test(minLevel, ignoreMetaAnnotation, defaultValue, candidate -> candidate == NullabilityType.NON_NULL); } @Override public boolean isNullable(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue) { - return testNullabilityType(minLevel, ignoreMetaAnnotation, defaultValue, - t -> t == NullabilityType.STRONG_NULLABLE || t == NullabilityType.WEAK_NULLABLE); + return test(minLevel, ignoreMetaAnnotation, defaultValue, + candidate -> candidate == NullabilityType.STRONG_NULLABLE || candidate == NullabilityType.WEAK_NULLABLE); } @Override public boolean isStrongNullable(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue) { - return testNullabilityType(minLevel, ignoreMetaAnnotation, defaultValue, t -> t == NullabilityType.STRONG_NULLABLE); + return test(minLevel, ignoreMetaAnnotation, defaultValue, candidate -> candidate == NullabilityType.STRONG_NULLABLE); } - private boolean testNullabilityType(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue, Predicate typePredicate) { + private boolean test(NullabilityLevel minLevel, boolean ignoreMetaAnnotation, boolean defaultValue, Predicate typePredicate) { if (type == NullabilityType.NO_ANNOTATION) { return false; - } else if (type == NullabilityType.UNKNOWN || (ignoreMetaAnnotation && metaAnnotation)) { + } + if (type == NullabilityType.UNKNOWN || (ignoreMetaAnnotation && metaAnnotation)) { return defaultValue; - } else if (typePredicate.test(type)) { - return minLevel.ordinal() <= level.ordinal(); } - return false; + return typePredicate.test(type) && minLevel.ordinal() <= level.ordinal(); } @Nullable @@ -378,50 +302,96 @@ public Tree declaration() { } } - static final class JAnnotationInstance implements AnnotationInstance { - private final JSema sema; - private final IAnnotationBinding annotationBinding; + private static final class TreeAnnotation implements AnnotationInstance { - /** - * Cache for {@link #values()}. - */ - private List values; + private final JSema sema; + private final AnnotationTree tree; - JAnnotationInstance(JSema sema, IAnnotationBinding annotationBinding) { + private TreeAnnotation(JSema sema, AnnotationTree tree) { this.sema = sema; - this.annotationBinding = annotationBinding; + this.tree = tree; } @Override public Symbol symbol() { - return sema.typeSymbol(annotationBinding.getAnnotationType()); + Type type = tree.annotationType().symbolType(); + if (type.isUnknown() && tree.symbolType().isUnknown() && !"!Unknown!".equals(tree.symbolType().fullyQualifiedName())) { + // an unresolved annotation still carries its written name + type = tree.symbolType(); + } + return type.symbol(); } @Override public List values() { - if (values == null) { - values = Arrays.stream(annotationBinding.getDeclaredMemberValuePairs()) - .map(p -> new AnnotationValueImpl(p.getName(), convertAnnotationValue(p.getValue()))) - .collect(Collectors.toList()); + List values = new ArrayList<>(); + for (ExpressionTree argument : tree.arguments()) { + if (argument instanceof AssignmentExpressionTree assignment) { + String name = assignment.variable().toString(); + values.add(new AnnotationValueImpl(name, value(name, assignment.expression()))); + } else { + values.add(new AnnotationValueImpl("value", value("value", argument))); + } } return values; } - private Object convertAnnotationValue(Object value) { - return switch (value) { - case IVariableBinding iVariableBinding -> sema.variableSymbol(iVariableBinding); - case ITypeBinding iTypeBinding -> sema.typeSymbol(iTypeBinding); - case IAnnotationBinding iAnnotationBinding -> sema.annotation(iAnnotationBinding); - case Object[] a -> { - Object[] result = new Object[a.length]; - for (int i = 0; i < a.length; i++) { - result[i] = convertAnnotationValue(a[i]); - } - yield result; - } - case null, default -> value; - }; + /** + * The value of the member {@code name}, wrapped in an array when the member is array typed, as the value of an + * array typed member is always an array no matter how many elements the annotation spells out. + */ + private Object value(String name, ExpressionTree expression) { + Object value = literal(expression); + return value instanceof Object[] || !isArrayTyped(name) ? value : new Object[] {value}; + } + + private boolean isArrayTyped(String name) { + return symbol() instanceof Symbol.TypeSymbol annotation + && annotation.lookupSymbols(name).stream() + .anyMatch(member -> member instanceof Symbol.MethodSymbol method && method.returnType().type().isArray()); + } + + private Object literal(ExpressionTree expression) { + if (expression instanceof LiteralTree literal) { + return LiteralUtils.constantValue(literal); + } + if (expression instanceof AnnotationTree annotation) { + return new TreeAnnotation(sema, annotation); + } + if (expression instanceof NewArrayTree array) { + return array.initializers().stream().map(this::literal).toArray(); + } + if (expression instanceof MemberSelectExpressionTree select && "class".equals(select.identifier().name())) { + return select.expression().symbolType().symbol(); + } + Symbol symbol = expression instanceof MemberSelectExpressionTree select ? select.identifier().symbol() + : (expression instanceof IdentifierTree identifier ? identifier.symbol() : null); + if (symbol instanceof Symbol.VariableSymbol variable && !variable.isUnknown()) { + return variable.constantValue().orElse(variable); + } + return expression.toString(); } + } + private static final class FactAnnotation implements AnnotationInstance { + + private final Symbol symbol; + private final List values; + + private FactAnnotation(JSema sema, String fqn, List values) { + this.symbol = sema.typeSymbol(fqn); + this.values = values; + } + + @Override + public Symbol symbol() { + return symbol; + } + + @Override + public List values() { + return values; + } + } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JType.java b/java-frontend/src/main/java/org/sonar/java/model/JType.java index ee13c72eebe..475ed1da8a6 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JType.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JType.java @@ -16,51 +16,158 @@ */ package org.sonar.java.model; -import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; -import java.util.TreeSet; +import java.util.Set; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; +/** + * A type recovered from a SemSitter symbol fact or a JVM descriptor. + * Fully-qualified names are erased binary names ({@code java.util.Map$Entry}). + */ final class JType implements Type, Type.ArrayType { - private static final Logger LOG = LoggerFactory.getLogger(JType.class); + enum Kind { + NAMED, PRIMITIVE, VOID, NULL, ARRAY, TYPEVAR, UNKNOWN, INTERSECTION, CAPTURE, WILDCARD + } + + private static final Set NUMERIC = Set.of("byte", "char", "short", "int", "long", "float", "double"); final JSema sema; - final ITypeBinding typeBinding; + final Kind kind; + final String fqn; + final List args; + @Nullable + final JType element; + @Nullable + final String typeVarId; + private final List intersection; + @Nullable + private final JType lower; + + private JType(JSema sema, Kind kind, String fqn, List args, @Nullable JType element, @Nullable String typeVarId, List intersection) { + this(sema, kind, fqn, args, element, typeVarId, intersection, null); + } + + private JType(JSema sema, Kind kind, String fqn, List args, @Nullable JType element, @Nullable String typeVarId, List intersection, + @Nullable JType lower) { + this.sema = sema; + this.kind = kind; + this.fqn = fqn; + this.args = args; + this.element = element; + this.typeVarId = typeVarId; + this.intersection = intersection; + this.lower = lower; + } - private final String fullyQualifiedName; + static JType named(JSema sema, String fqn, List args) { + return new JType(sema, Kind.NAMED, fqn, List.copyOf(args), null, null, List.of()); + } - /** - * Cache for {@link #primitiveWrapperType()}. - */ - private Type primitiveWrapperType; + static JType primitive(JSema sema, String name) { + return new JType(sema, "void".equals(name) ? Kind.VOID : Kind.PRIMITIVE, name, List.of(), null, null, List.of()); + } - /** - * Cache for {@link #primitiveType()}. - */ - private Type primitiveType; + static JType nullType(JSema sema) { + return new JType(sema, Kind.NULL, "", List.of(), null, null, List.of()); + } - /** - * Cache for {@link #declaringType()}. - */ - private Type declaringType; + static JType unknown(JSema sema) { + return new JType(sema, Kind.UNKNOWN, "!Unknown!", List.of(), null, null, List.of()); + } - /** - * Cache for {@link #typeArguments()}. - */ - private List typeArguments; + static JType array(JSema sema, JType element) { + return new JType(sema, Kind.ARRAY, element.fullyQualifiedName() + "[]", List.of(), element, null, List.of()); + } - JType(JSema sema, ITypeBinding typeBinding) { - this.sema = Objects.requireNonNull(sema); - this.typeBinding = Objects.requireNonNull(typeBinding); - this.fullyQualifiedName = fullyQualifiedName(typeBinding); + static JType typeVar(JSema sema, String name, String id) { + return new JType(sema, Kind.TYPEVAR, name, List.of(), null, id, List.of()); + } + + /** Replaces type variables named in {@code substitution} inside this type. */ + JType substitute(Map substitution) { + if (substitution.isEmpty()) { + return this; + } + if (kind == Kind.TYPEVAR) { + Type found = typeVarId == null ? null : substitution.get(typeVarId); + if (found == null) { + found = substitution.get(fqn); + } + if (found instanceof JType replaced) { + return replaced; + } + } + if (kind == Kind.CAPTURE && element != null) { + JType replaced = element.substitute(substitution); + return replaced == element ? this : capture(sema, replaced); + } + if (kind == Kind.WILDCARD && lower != null) { + JType replaced = lower.substitute(substitution); + return replaced == lower ? this : superWildcard(sema, replaced); + } + if (kind == Kind.WILDCARD && element != null) { + JType replaced = element.substitute(substitution); + return replaced == element ? this : wildcard(sema, replaced); + } + if (kind == Kind.ARRAY && element != null) { + JType replaced = element.substitute(substitution); + return replaced == element ? this : array(sema, replaced); + } + if (kind == Kind.NAMED && !args.isEmpty()) { + List replaced = new ArrayList<>(); + boolean changed = false; + for (JType argument : args) { + JType next = argument.substitute(substitution); + changed |= next != argument; + replaced.add(next); + } + return changed ? named(sema, fqn, replaced) : this; + } + return this; + } + + static JType capture(JSema sema, JType upper) { + return new JType(sema, Kind.CAPTURE, "!capture!", List.of(), upper, null, List.of()); + } + + /** A wildcard type argument, named after the erasure of its upper bound as ECJ names it. */ + static JType wildcard(JSema sema, JType upper) { + if (upper.isUnknown()) { + return upper; + } + return new JType(sema, Kind.WILDCARD, upper.erasure().fullyQualifiedName(), List.of(), upper, null, List.of()); + } + + /** A {@code ? super lower} type argument, which ECJ names after its implicit upper bound {@code Object}. */ + static JType superWildcard(JSema sema, JType lower) { + JType object = sema.getClassType("java.lang.Object") instanceof JType type ? type : unknown(sema); + return new JType(sema, Kind.WILDCARD, object.fullyQualifiedName(), List.of(), object, null, List.of(), lower); + } + + boolean isWildcard() { + return kind == Kind.WILDCARD; + } + + /** The type a lambda sees for this type argument: the bound a wildcard is written with, or the type itself. */ + JType groundType() { + if (kind != Kind.WILDCARD) { + return this; + } + return lower != null ? lower : element; + } + + static JType intersection(JSema sema, List bounds) { + String name = bounds.stream().map(JType::fullyQualifiedName).sorted().reduce((a, b) -> a + " & " + b).orElse("!Unknown!"); + return new JType(sema, Kind.INTERSECTION, name, List.of(), null, null, List.copyOf(bounds)); } @Override @@ -75,52 +182,114 @@ public boolean isSubtypeOf(String fullyQualifiedName) { @Override public boolean isSubtypeOf(Type superType) { - return !superType.isUnknown() - && isSubtype(this.typeBinding, ((JType) superType).typeBinding); + return isSubtypeOf(superType, new HashSet<>()); } - private static boolean isSubtype(ITypeBinding left, ITypeBinding right) { - if (left.isRecovered()) { + private boolean isSubtypeOf(Type superType, Set visited) { + if ((kind == Kind.CAPTURE || kind == Kind.WILDCARD) && element != null) { + return element.isSubtypeOf(superType, visited); + } + if (superType == null || superType.isUnknown() || isUnknown()) { + return false; + } + if (isNullType()) { + return !superType.isPrimitive(); + } + if (!visited.add(fullyQualifiedName() + ">" + superType.fullyQualifiedName())) { + return false; + } + if (sameErasure(superType) && argumentsMatch(superType)) { + return true; + } + if (isPrimitive() && superType.isPrimitive()) { + return widensTo(fullyQualifiedName(), superType.fullyQualifiedName()); + } + if (isArray()) { + if (superType.isArray()) { + return elementType().isSubtypeOf(((Type.ArrayType) superType).elementType()); + } + return superType.is("java.lang.Object") || superType.is("java.lang.Cloneable") || superType.is("java.io.Serializable"); + } + if (isPrimitive() || superType.isPrimitive() || isTypeVar()) { return false; } - if (left.isNullType()) { - return !right.isPrimitive(); + Symbol.TypeSymbol owner = declaredSymbol(); + if (owner.isUnknown()) { + return false; + } + // Supertypes are walked erased: their arguments name the owner's type variables, not this type's arguments. + Type superClass = owner.superClass(); + if (superClass != null && ((JType) superClass.erasure()).isSubtypeOf(superType, visited)) { + return true; } - try { - return left.isSubTypeCompatible(right); - } catch (NullPointerException ex) { - // In rare circumstances, ECJ may produce a NPE while calling isSubTypeCompatible(), see SONARJAVA-4390 - LOG.debug("NullPointerException while resolving isSubTypeCompatible()", ex); + for (Type iface : owner.interfaces()) { + if (((JType) iface.erasure()).isSubtypeOf(superType, visited)) { + return true; + } + } + return false; + } + + private boolean sameErasure(Type superType) { + return fullyQualifiedName().equals(superType.erasure().fullyQualifiedName()); + } + + private boolean argumentsMatch(Type superType) { + if (!isParameterized() || !superType.isParameterized()) { + return true; + } + List theirs = superType.typeArguments(); + if (args.size() != theirs.size()) { return false; } + for (int i = 0; i < args.size(); i++) { + if (!args.get(i).is(theirs.get(i).fullyQualifiedName())) { + return false; + } + } + return true; + } + + private static boolean widensTo(String from, String to) { + if (from.equals(to)) { + return true; + } + return switch (from) { + case "byte" -> Set.of("short", "int", "long", "float", "double").contains(to); + case "short", "char" -> Set.of("int", "long", "float", "double").contains(to); + case "int" -> Set.of("long", "float", "double").contains(to); + case "long" -> Set.of("float", "double").contains(to); + case "float" -> "double".equals(to); + default -> false; + }; } @Override public boolean isArray() { - return typeBinding.isArray(); + return kind == Kind.ARRAY; + } + + boolean isCapture() { + return kind == Kind.CAPTURE; } @Override public boolean isClass() { - return typeBinding.isClass() - || typeBinding.isInterface() - || typeBinding.isEnum(); + return kind == Kind.NAMED && !isUnknown(); } @Override public boolean isVoid() { - return "void".equals(fullyQualifiedName()); + return kind == Kind.VOID; } @Override public boolean isPrimitive() { - return typeBinding.isPrimitive() - && !isVoid(); + return kind == Kind.PRIMITIVE; } @Override public boolean isPrimitive(Primitives primitive) { - // TODO suboptimal return primitive.name().toLowerCase(Locale.ROOT).equals(fullyQualifiedName()); } @@ -132,152 +301,88 @@ public boolean isPrimitiveWrapper() { @Nullable @Override public Type primitiveWrapperType() { - if (primitiveWrapperType == null) { - String name = JUtils.PRIMITIVE_TO_WRAPPER.get(fullyQualifiedName()); - if (name == null) { - return null; - } - ITypeBinding resolvedType = sema.resolveType(name); - if (resolvedType == null) { - return null; - } - primitiveWrapperType = sema.type(resolvedType); - } - return primitiveWrapperType; + String name = JUtils.PRIMITIVE_TO_WRAPPER.get(fullyQualifiedName()); + return name == null ? null : sema.getClassType(name); } @Nullable @Override public Type primitiveType() { - if (primitiveType == null) { - String name = JUtils.WRAPPER_TO_PRIMITIVE.get(fullyQualifiedName()); - if (name == null) { - return null; - } - ITypeBinding resolvedType = sema.resolveType(name); - if (resolvedType == null) { - return null; - } - primitiveType = sema.type(resolvedType); - } - return primitiveType; + String name = JUtils.WRAPPER_TO_PRIMITIVE.get(fullyQualifiedName()); + return name == null ? null : sema.getClassType(name); } @Override public boolean isNullType() { - return !isUnknown() && typeBinding.isNullType(); + return kind == Kind.NULL; } @Override public boolean isTypeVar() { - return !isUnknown() && typeBinding.isTypeVariable(); + return kind == Kind.TYPEVAR; } @Override public boolean isRawType() { - if (isUnknown()) { + if (kind != Kind.NAMED || isUnknown()) { return false; } - return typeBinding.isRawType(); + Symbol.TypeSymbol owner = declaredSymbol(); + return owner instanceof JTypeSymbol typeSymbol && typeSymbol.typeParameterCount() > 0 && args.isEmpty(); } @Override public Type declaringType() { - if (declaringType == null) { - if (isUnknown()) { - return this; + if (isUnknown() || kind != Kind.NAMED) { + return this; + } + if (isRawType()) { + Symbol.TypeSymbol owner = declaredSymbol(); + int count = owner instanceof JTypeSymbol typeSymbol ? typeSymbol.typeParameterCount() : 0; + List variables = new java.util.ArrayList<>(); + for (int i = 0; i < count; i++) { + variables.add(typeVar(sema, "T" + i, fqn + "<" + i + ">")); } - declaringType = sema.type(typeBinding.getTypeDeclaration()); + return named(sema, fqn, variables); } - return declaringType; + return sema.getClassType(fqn); } @Override public boolean isUnknown() { - return typeBinding.isRecovered(); - } - - @Override - public boolean isNumerical() { - switch (fullyQualifiedName()) { - case "byte", - "char", - "short", - "int", - "long", - "float", - "double": - return true; - default: - return false; + if (kind == Kind.UNKNOWN) { + return true; } - } - - @Override - public String fullyQualifiedName() { - return fullyQualifiedName; - } - - private static String fullyQualifiedName(ITypeBinding typeBinding) { - String qualifiedName; - if (typeBinding.isNullType()) { - qualifiedName = ""; - } else if (typeBinding.isPrimitive()) { - qualifiedName = typeBinding.getName(); - } else if (typeBinding.isArray()) { - qualifiedName = fullyQualifiedName(typeBinding.getComponentType()) + "[]"; - } else if (typeBinding.isCapture()) { - qualifiedName = "!capture!"; - } else if (typeBinding.isTypeVariable()) { - qualifiedName = typeBinding.getName(); - } else { - qualifiedName = typeBinding.getBinaryName(); - if (qualifiedName == null) { - // e.g. anonymous class in unreachable code - qualifiedName = typeBinding.getKey(); - } + if (kind == Kind.ARRAY && element != null) { + return element.isUnknown(); } - if (typeBinding.isIntersectionType()) { - TreeSet intersectionTypes = new TreeSet<>(); - intersectionTypes.add(qualifiedName); - for (ITypeBinding typeBound : typeBinding.getTypeBounds()) { - intersectionTypes.add(fullyQualifiedName(typeBound)); - } - qualifiedName = String.join(" & ", intersectionTypes); + if (kind == Kind.NAMED) { + Symbol.TypeSymbol owner = declaredSymbol(); + return owner.isUnknown(); } - return qualifiedName; + return false; } @Override - public boolean isIntersectionType() { - return typeBinding.isIntersectionType(); + public boolean isNumerical() { + return NUMERIC.contains(fullyQualifiedName()); } @Override - public Type[] getIntersectionTypes() { - if (!isIntersectionType()) { - return new Type[] { this }; - } - ITypeBinding[] bounds = typeBinding.getTypeBounds(); - Type[] types = new Type[bounds.length]; - for (int i = 0; i < bounds.length; i++) { - types[i] = sema.type(bounds[i]); - } - return types; + public String fullyQualifiedName() { + return fqn; } - /** - * @see JSymbol#name() - */ @Override public String name() { - if (typeBinding.isNullType()) { + if (kind == Kind.NULL) { return ""; - } else if (typeBinding.isParameterizedType()) { - // without names of parameters - return typeBinding.getErasure().getName(); } - return typeBinding.getName(); + if (kind == Kind.ARRAY) { + return element.name() + "[]"; + } + int boundary = Math.max(fqn.lastIndexOf('.'), fqn.lastIndexOf('$')); + return boundary < 0 ? fqn : fqn.substring(boundary + 1); } @Override @@ -287,80 +392,76 @@ public String toString() { @Override public Symbol.TypeSymbol symbol() { - return sema.typeSymbol(typeBinding); - } - - @Override - public Type erasure() { - return sema.type(typeBinding.getErasure()); + Symbol.TypeSymbol declared = declaredSymbol(); + return !args.isEmpty() && declared instanceof JTypeSymbol generic ? generic.parameterizedAs(this) : declared; } - @Override - public Type elementType() { - return sema.type(typeBinding.getComponentType()); + private Symbol.TypeSymbol declaredSymbol() { + return switch (kind) { + case ARRAY -> sema.arraySymbol(element); + case PRIMITIVE, VOID -> sema.primitiveSymbol(fqn); + case TYPEVAR -> sema.resolveTypeSymbol(typeVarId); + case NULL, UNKNOWN, INTERSECTION -> Symbol.TypeSymbol.UNKNOWN_TYPE; + case CAPTURE, WILDCARD -> element == null ? Symbol.TypeSymbol.UNKNOWN_TYPE : element.symbol(); + case NAMED -> sema.typeSymbol(fqn); + }; } @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; + public Type erasure() { + if ((kind == Kind.CAPTURE || kind == Kind.WILDCARD) && element != null) { + return element.erasure(); } - if (obj instanceof JType other) { - return areEqual(this.typeBinding, other.typeBinding); + if (kind == Kind.ARRAY) { + return JType.array(sema, (JType) element.erasure()); } - return false; + if (kind == Kind.NAMED && !args.isEmpty()) { + return sema.getClassType(fqn); + } + return this; } @Override - public int hashCode() { - return fullyQualifiedName().hashCode(); + public boolean isParameterized() { + return kind == Kind.NAMED && !args.isEmpty(); } - static boolean areEqual(@Nullable ITypeBinding binding1, @Nullable ITypeBinding binding2) { - if (binding1 == null || binding2 == null) { - return binding1 == binding2; - } - if (binding1.isWildcardType()) { - return binding2.isWildcardType() - && binding1.isUpperbound() == binding2.isUpperbound() - && areEqual(binding1.getBound(), binding2.getBound()); - } - // TODO compare declaring class, method, member - return binding1.getTypeDeclaration().equals(binding2.getTypeDeclaration()) - && isParameterizedOrGeneric(binding1) == isParameterizedOrGeneric(binding2) - && binding1.isRawType() == binding2.isRawType() - && Arrays.equals(binding1.getTypeParameters(), binding2.getTypeParameters()) - && Arrays.equals(binding1.getTypeArguments(), binding2.getTypeArguments()); + @Override + public List typeArguments() { + return Collections.unmodifiableList(args); } - static ITypeBinding normalize(ITypeBinding typeBinding) { - ITypeBinding typeDeclaration = typeBinding.getTypeDeclaration(); - if (typeBinding.isLocal()) { - return typeDeclaration; - } - return typeBinding.isParameterizedType() && Arrays.equals(typeDeclaration.getTypeParameters(), typeBinding.getTypeArguments()) - ? typeDeclaration - : typeBinding; + @Override + public Type elementType() { + return element == null ? Type.UNKNOWN : element; } - private static boolean isParameterizedOrGeneric(ITypeBinding typeBinding) { - return typeBinding.isParameterizedType() || typeBinding.isGenericType(); + @Override + public boolean isIntersectionType() { + return kind == Kind.INTERSECTION; } @Override - public boolean isParameterized() { - return typeBinding.isParameterizedType() - // when diamond operator is not fully resolved by ECJ, there is 0 typeArguments, while ECJ - // knows it is a Parameterized Type - && !typeArguments().isEmpty(); + public Type[] getIntersectionTypes() { + if (intersection.isEmpty()) { + return new Type[] {this}; + } + return intersection.toArray(Type[]::new); } @Override - public List typeArguments() { - if (typeArguments == null) { - typeArguments = sema.types(typeBinding.getTypeArguments()); + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof JType other)) { + return false; } - return typeArguments; + return kind == other.kind && fqn.equals(other.fqn) && args.equals(other.args) && Objects.equals(typeVarId, other.typeVarId); } + @Override + public int hashCode() { + return fqn.hashCode(); + } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java index 0d41214e309..dbb2592a0ff 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JTypeSymbol.java @@ -16,230 +16,299 @@ */ package org.sonar.java.model; +import com.google.gson.JsonObject; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.annotation.CheckForNull; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.ClassTree; -import org.sonar.plugins.java.api.tree.IdentifierTree; -import org.sonar.plugins.java.api.tree.VariableTree; final class JTypeSymbol extends JSymbol implements Symbol.TypeSymbol { - /** - * Cache for {@link #superClass()}. - */ - private Type superclass = Type.UNKNOWN; + private static final String ENUM = "java.lang.Enum"; - /** - * Cache for {@link #interfaces()}. - */ - private List interfaces; + private final boolean typeParameter; + private int typeParameterCount; + @Nullable + private String superName; + private List interfaceNames = List.of(); + @Nullable + private JsonObject superTypeFact; + private List interfaceTypeFacts = List.of(); + private List memberFacts = List.of(); + @Nullable + private String elementFqn; + private Collection members; + @Nullable + private Type declaredSuperClass; + private List declaredInterfaces = List.of(); + @Nullable + private JTypeSymbol generic; - /** - * Cache for {@link #memberSymbols()}. - */ - private Collection memberSymbols; + JTypeSymbol(JSema sema, @Nullable String id, String name, boolean typeParameter) { + super(sema, Kind.TYPE, id, name); + this.typeParameter = typeParameter; + } /** - * Cache for {@link #superTypes()}. + * A view of this generic type symbol whose {@link #type()} is the given parameterization, + * so that {@code type.symbol().type()} keeps the type arguments. */ - private Set superTypes; + JTypeSymbol parameterizedAs(JType parameterized) { + JTypeSymbol view = new JTypeSymbol(sema, id, name(), typeParameter); + view.copyStateFrom(this); + view.type(parameterized); + view.typeParameterCount = typeParameterCount; + view.superName = superName; + view.interfaceNames = interfaceNames; + view.superTypeFact = superTypeFact; + view.interfaceTypeFacts = interfaceTypeFacts; + view.memberFacts = memberFacts; + view.elementFqn = elementFqn; + view.declaredSuperClass = declaredSuperClass; + view.declaredInterfaces = declaredInterfaces; + view.generic = this; + return view; + } - final SpecialField superSymbol = new SpecialField() { - @Override - public String name() { - return "super"; - } + void declaredSupertypes(@Nullable Type superClass, List interfaces) { + this.declaredSuperClass = superClass; + this.declaredInterfaces = List.copyOf(interfaces); + } - @Override - public Type type() { - if (typeBinding().isInterface()) { - // JLS § 15.12.1: - // for "T.super.foo()", if T is an interface, 'super' keyword is used to access method of the interface itself - return JTypeSymbol.this.type(); - } - Type superClass = JTypeSymbol.this.superClass(); - return superClass == null ? Type.UNKNOWN : superClass; - } - }; + boolean isTypeParameter() { + return typeParameter; + } - final SpecialField thisSymbol = new SpecialField() { - @Override - public String name() { - return "this"; - } + int typeParameterCount() { + return typeParameterCount; + } - @Override - public Type type() { - return JTypeSymbol.this.type(); - } - }; + void typeParameterCount(int typeParameterCount) { + this.typeParameterCount = typeParameterCount; + } + + void superName(@Nullable String superName) { + this.superName = superName; + } - JTypeSymbol(JSema sema, ITypeBinding typeBinding) { - super(sema, typeBinding); + void interfaceNames(List interfaceNames) { + this.interfaceNames = interfaceNames; } - ITypeBinding typeBinding() { - return (ITypeBinding) binding; + /** Supertypes with their type arguments, used when they name the same classes as the erased ones. */ + void genericSupertypes(@Nullable JsonObject superType, List interfaceTypes) { + this.superTypeFact = superType; + this.interfaceTypeFacts = interfaceTypes; + } + + void memberFacts(List memberFacts) { + this.memberFacts = memberFacts; + this.members = null; + } + + List memberFacts() { + return memberFacts; + } + + void elementFqn(@Nullable String elementFqn) { + this.elementFqn = elementFqn; } @CheckForNull @Override public Type superClass() { - if (superclass == Type.UNKNOWN) { - superclass = convertSuperClass(); + if (typeParameter || elementFqn != null) { + return sema.getClassType("java.lang.Object"); + } + if (isInterface() || isAnnotation()) { + return sema.getClassType("java.lang.Object"); + } + String erased = JSema.erase(id == null ? name() : id); + if ("java.lang.Object".equals(erased)) { + return null; + } + boolean noSuper = superName == null || superName.isEmpty(); + if (isEnum() && superTypeFact == null && (noSuper || ENUM.equals(JSema.erase(superName))) && type() instanceof JType self) { + sema.getClassType(ENUM); + return JType.named(sema, ENUM, List.of(self)); + } + if (noSuper || inheritsFrom(erased) || interfaceDeclaredAsSuper() != null) { + return sema.getClassType("java.lang.Object"); + } + if (namesSameClass(superTypeFact, superName)) { + return sema.supertypeFromFact(superTypeFact); } - return superclass; + return withDeclaredArguments(sema.getClassType(JSema.erase(superName)), declaredSuperClass == null ? List.of() : List.of(declaredSuperClass)); } - @CheckForNull - private Type convertSuperClass() { - if (typeBinding().isInterface() || typeBinding().isArray()) { - ITypeBinding objectBinding = sema.resolveType("java.lang.Object"); - return objectBinding != null ? sema.type(objectBinding) : Type.UNKNOWN; - } else if (typeBinding().getSuperclass() == null) { - // java.lang.Object - return null; - } else { - return sema.type(typeBinding().getSuperclass()); + private static Type withDeclaredArguments(Type erased, List declared) { + return declared.stream().filter(type -> type.isParameterized() && type.erasure().equals(erased)).findFirst().orElse(erased); + } + + private boolean inheritsFrom(String erasedSelf) { + Set visited = new HashSet<>(); + String current = JSema.erase(superName); + while (visited.add(current)) { + if (current.equals(erasedSelf)) { + return true; + } + if (!(sema.getClassType(current).symbol() instanceof JTypeSymbol parent) || parent.superName == null || parent.superName.isEmpty()) { + return false; + } + current = JSema.erase(parent.superName); } + return false; } @Override public List interfaces() { - if (interfaces == null) { - interfaces = sema.types(typeBinding().getInterfaces()); + if (elementFqn != null) { + return List.of(sema.getClassType("java.lang.Cloneable"), sema.getClassType("java.io.Serializable")); + } + if (interfaceNames.isEmpty()) { + return withInterfaceSuper(isAnnotation() ? List.of(sema.getClassType("java.lang.annotation.Annotation")) : List.of()); + } + boolean generic = interfaceTypeFacts.size() == interfaceNames.size(); + List types = new ArrayList<>(); + for (int i = 0; i < interfaceNames.size(); i++) { + String name = interfaceNames.get(i); + JsonObject fact = generic ? interfaceTypeFacts.get(i) : null; + Type erased = sema.getClassType(JSema.erase(name)); + types.add(namesSameClass(fact, name) ? sema.supertypeFromFact(fact) : withDeclaredArguments(erased, declaredInterfaces)); + } + return withInterfaceSuper(types); + } + + /** + * An anonymous class whose named supertype is an interface is recorded with that interface as its superclass. + * Java implements the interface and extends Object. + */ + @Nullable + private Type interfaceDeclaredAsSuper() { + if (superName == null || superName.isEmpty() || isInterface() || isAnnotation() || typeParameter || elementFqn != null) { + return null; + } + String erased = JSema.erase(superName); + JTypeSymbol parent = sema.typeSymbol(erased); + if (!parent.isInterface() || parent.isUnknown()) { + return null; + } + return sema.getClassType(erased); + } + + private List withInterfaceSuper(List interfaces) { + Type superInterface = interfaceDeclaredAsSuper(); + if (superInterface == null || interfaces.stream().anyMatch(type -> type.erasure().equals(superInterface.erasure()))) { + return interfaces; } - return interfaces; + List combined = new ArrayList<>(interfaces.size() + 1); + combined.add(superInterface); + combined.addAll(interfaces); + return combined; + } + + private static boolean namesSameClass(@Nullable JsonObject fact, String name) { + return fact != null && fact.has("id") && fact.get("id").isJsonPrimitive() + && JSema.erase(fact.get("id").getAsString()).equals(JSema.erase(name)); } @Override public Collection memberSymbols() { - if (memberSymbols == null) { - memberSymbols = convertMemberSymbols(); + if (generic != null) { + return generic.memberSymbols(); + } + if (members == null) { + List resolved = new ArrayList<>(); + for (JsonObject member : memberFacts) { + resolved.add(sema.memberFromTypeInfo(this, member)); + } + addImplicitConstructor(resolved); + members = resolved; } - return memberSymbols; + return members; } - private Collection convertMemberSymbols() { - return Stream.concat( - Stream.concat( - Arrays.stream(typeBinding().getDeclaredTypes()).map(sema::typeSymbol), - Arrays.stream(typeBinding().getDeclaredFields()).map(sema::variableSymbol)), - Arrays.stream(typeBinding().getDeclaredMethods()).map(sema::methodSymbol)) - .collect(Collectors.toCollection(ArrayList::new)); + /** A class of this file that declares no constructor still has the default one as a member. */ + private void addImplicitConstructor(List resolved) { + if (id == null || isUnknown() || isTypeParameter() || isInterface() || declaration() == null + || resolved.stream().anyMatch(member -> member.isMethodSymbol() && "".equals(member.name()))) { + return; + } + Symbol constructor = sema.resolve(id + "#()"); + if (!constructor.isUnknown()) { + resolved.add(constructor); + } } @Override public Collection lookupSymbols(String name) { - return memberSymbols().stream() - .filter(m -> name.equals(m.name())) - .collect(Collectors.toSet()); + Set found = new HashSet<>(); + for (Symbol member : memberSymbols()) { + if (name.equals(member.name())) { + found.add(member); + } + } + return found; } @Nullable @Override public ClassTree declaration() { - return (ClassTree) super.declaration(); + return super.declaration() instanceof ClassTree classTree ? classTree : null; } @Override public Set superTypes() { - if (superTypes == null) { - if (isUnknown()) { - superTypes = Collections.emptySet(); - } else { - superTypes = new HashSet<>(); - JUtils.collectSuperTypes(superTypes, sema, typeBinding()); + if (isUnknown()) { + return Collections.emptySet(); + } + Set collected = new HashSet<>(); + collectSuperTypes(collected, new HashSet<>()); + return collected; + } + + private void collectSuperTypes(Set collected, Set visited) { + if (!visited.add(JSema.erase(id == null ? name() : id))) { + return; + } + Type superClass = superClass(); + if (superClass != null && !superClass.isUnknown()) { + collected.add(superClass); + if (superClass.symbol() instanceof JTypeSymbol parent) { + parent.collectSuperTypes(collected, visited); + } + } + for (Type iface : interfaces()) { + if (!iface.isUnknown()) { + collected.add(iface); + if (iface.symbol() instanceof JTypeSymbol parent) { + parent.collectSuperTypes(collected, visited); + } } } - return superTypes; } - @Override @Nullable + @Override public TypeSymbol outermostClass() { - Symbol symbol = this; - Symbol result = null; - while (symbol != null && !symbol.isPackageSymbol()) { - result = symbol; - symbol = symbol.owner(); + Symbol current = this; + Symbol result = this; + while (current != null && !current.isPackageSymbol()) { + result = current; + current = current.owner(); } - return (Symbol.TypeSymbol) result; + return result instanceof TypeSymbol typeSymbol ? typeSymbol : this; } @Override public boolean isAnnotation() { - return !isUnknown() && typeBinding().isAnnotation(); + return !isUnknown() && isAnnotationSymbol(); } - - abstract class SpecialField extends Symbols.DefaultSymbol implements Symbol.VariableSymbol { - @Override - public final Symbol owner() { - return JTypeSymbol.this; - } - - @Override - public final boolean isVariableSymbol() { - return true; - } - - @Override - public final boolean isFinal() { - return true; - } - - @Override - public final boolean isUnknown() { - return false; - } - - @Override - public final TypeSymbol enclosingClass() { - return JTypeSymbol.this; - } - - @Override - public final List usages() { - return Collections.emptyList(); - } - - @Nullable - @Override - public final VariableTree declaration() { - return null; - } - - @Override - public final boolean isEffectivelyFinal(){ - return false; - } - @Override - public final Optional constantValue() { - return Optional.empty(); - } - - @Override - public final boolean isLocalVariable() { - return false; - } - - @Override - public final boolean isParameter() { - return false; - } - } - } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JUtils.java b/java-frontend/src/main/java/org/sonar/java/model/JUtils.java index 6018e221558..1767701abe2 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JUtils.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JUtils.java @@ -16,7 +16,6 @@ */ package org.sonar.java.model; -import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedHashSet; @@ -26,7 +25,6 @@ import java.util.Set; import java.util.stream.Collectors; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; @@ -67,19 +65,7 @@ public static Type wrapTypeIfPrimitive(Type type) { } public static boolean isIntersectionType(Type type) { - return !type.isUnknown() && ((JType) type).typeBinding.isIntersectionType(); - } - - public static void collectSuperTypes(Set result, JSema sema, ITypeBinding typeBinding) { - ITypeBinding s = typeBinding.getSuperclass(); - if (s != null) { - result.add(sema.type(s)); - collectSuperTypes(result, sema, s); - } - Arrays.stream(typeBinding.getInterfaces()).forEach(i -> { - result.add(sema.type(i)); - collectSuperTypes(result, sema, i); - }); + return type.isIntersectionType(); } public static Symbol getPackage(Symbol symbol) { @@ -91,10 +77,7 @@ public static Symbol getPackage(Symbol symbol) { @Nullable public static Object defaultValue(Symbol.MethodSymbol method) { - if (method.isUnknown()) { - return null; - } - return ((JMethodSymbol) method).methodBinding().getDefaultValue(); + return null; } /** @@ -162,13 +145,13 @@ public static Set directSuperTypes(Type type) { return Collections.emptySet(); } Set result = new HashSet<>(); - JType t = (JType) type; - ITypeBinding superclass = t.typeBinding.getSuperclass(); - if (superclass != null) { - result.add(t.sema.type(superclass)); + Symbol.TypeSymbol symbol = type.symbol(); + Type superClass = symbol.superClass(); + if (superClass != null && !superClass.isUnknown()) { + result.add(superClass); } - Arrays.stream(t.typeBinding.getInterfaces()) - .map(t.sema::type) + symbol.interfaces().stream() + .filter(iface -> !iface.isUnknown()) .forEach(result::add); return result; } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java index ae6458f39de..bb82895682c 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JVariableSymbol.java @@ -20,23 +20,37 @@ import java.util.List; import java.util.Optional; import javax.annotation.Nullable; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; -import org.eclipse.jdt.core.dom.IVariableBinding; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.SymbolMetadata; import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.IdentifierTree; +import org.sonar.plugins.java.api.tree.LiteralTree; +import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.VariableTree; final class JVariableSymbol extends JSymbol implements Symbol.VariableSymbol { - // cache for this.constantValue() - private boolean constantValueComputed = false; - private Optional constantValue; + private final boolean parameter; + private final boolean local; + private boolean effectivelyFinal; + @Nullable + private Optional constant; + + JVariableSymbol(JSema sema, @Nullable String id, String name, String dumpKind) { + super(sema, Kind.VARIABLE, id, name); + this.parameter = "param".equals(dumpKind); + this.local = "local".equals(dumpKind); + this.effectivelyFinal = "constant".equals(dumpKind) || "enum_member".equals(dumpKind); + enumSymbol("enum_member".equals(dumpKind)); + } + + void effectivelyFinal(boolean effectivelyFinal) { + this.effectivelyFinal = effectivelyFinal; + } - JVariableSymbol(JSema sema, IVariableBinding variableBinding) { - super(sema, variableBinding); + void constantValue(@Nullable Object value) { + this.constant = Optional.ofNullable(value); } @Nullable @@ -47,53 +61,84 @@ public VariableTree declaration() { @Override public boolean isEffectivelyFinal() { - return ((IVariableBinding)binding).isEffectivelyFinal(); + return isFinal() || effectivelyFinal; } @Override public Optional constantValue() { - if (!constantValueComputed) { - constantValueComputed = true; - if (!isFinal() || !isStatic()) { - constantValue = Optional.empty(); - } else { - Object c = ((IVariableBinding) binding).getConstantValue(); - if (c instanceof Short shortValue) { - c = Integer.valueOf(shortValue); - } else if (c instanceof Byte byteValue) { - c = Integer.valueOf(byteValue); - } else if (c instanceof Character characterValue) { - c = Integer.valueOf(characterValue); - } - constantValue = Optional.ofNullable(c); + if (constant == null) { + constant = Optional.ofNullable(declaredConstant()); + } + return constant; + } + + /** A constant is a static final field with a compile time value, typed as its declaration. */ + @Nullable + private Object declaredConstant() { + VariableTree declaration = declaration(); + if (declaration == null || declaration.initializer() == null || declaration.type() == null || !isStaticFinalOrInterfaceMember()) { + return null; + } + ExpressionTree initializer = declaration.initializer(); + Object value = initializer.is(Tree.Kind.LONG_LITERAL, Tree.Kind.FLOAT_LITERAL, Tree.Kind.DOUBLE_LITERAL) + ? literalNumber((LiteralTree) initializer) + : ExpressionUtils.resolveAsConstant(initializer); + return convert(value, declaration.type().symbolType()); + } + + private boolean isStaticFinalOrInterfaceMember() { + boolean constantKind = isFinal() || owner().isInterface() || isEnum(); + return constantKind && (isStatic() || owner().isInterface()); + } + + @Nullable + private static Object literalNumber(LiteralTree literal) { + String text = literal.value().replace("_", ""); + try { + return switch (literal.kind()) { + case LONG_LITERAL -> LiteralUtils.longLiteralValue(literal); + case FLOAT_LITERAL -> Float.valueOf(text); + default -> Double.valueOf(text); + }; + } catch (NumberFormatException e) { + return null; + } + } + + @Nullable + private static Object convert(@Nullable Object value, Type type) { + if (value instanceof Number number) { + if (type.is("long")) { + return number.longValue(); + } else if (type.is("double")) { + return number.doubleValue(); + } else if (type.is("float")) { + return number.floatValue(); } } - return constantValue; + return value; } @Override public boolean isLocalVariable() { - Symbol owner = owner(); - return owner != null && owner.isMethodSymbol(); + return local || (owner().isMethodSymbol() && !parameter); } @Override public boolean isParameter() { - return ((IVariableBinding) binding).isParameter(); + return parameter; } - static class ParameterPlaceholderSymbol extends Symbols.DefaultSymbol implements Symbol.VariableSymbol { + private final String name; private final Symbol owner; private final Type type; - private final SymbolMetadata metadata; - ParameterPlaceholderSymbol(int index, JSema sema, IMethodBinding owner, ITypeBinding typeBinding) { + ParameterPlaceholderSymbol(int index, Symbol owner, Type type) { this.name = "arg" + index; - this.owner = sema.methodSymbol(owner); - this.type = sema.type(typeBinding); - this.metadata = JSymbolMetadata.of(sema, this, typeBinding, owner.getParameterAnnotations(index)); + this.owner = owner; + this.type = type == null ? Type.UNKNOWN : type; } @Override @@ -126,6 +171,7 @@ public List usages() { return Collections.emptyList(); } + @Nullable @Override public VariableTree declaration() { return null; @@ -133,7 +179,7 @@ public VariableTree declaration() { @Override public boolean isEffectivelyFinal() { - return false; + return true; } @Override @@ -153,7 +199,7 @@ public boolean isParameter() { @Override public SymbolMetadata metadata() { - return metadata; + return Symbols.EMPTY_METADATA; } @Override @@ -163,9 +209,7 @@ public boolean isVariableSymbol() { @Override public boolean isFinal() { - // From a caller perspective, it is not useful (and even not possible) to know if the parameter is final. return false; } - } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/JWarning.java b/java-frontend/src/main/java/org/sonar/java/model/JWarning.java index 8a95e692df6..c123b4326f0 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/JWarning.java +++ b/java-frontend/src/main/java/org/sonar/java/model/JWarning.java @@ -28,9 +28,6 @@ import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; -import javax.annotation.CheckForNull; -import org.eclipse.jdt.core.compiler.IProblem; -import org.eclipse.jdt.core.dom.CompilationUnit; import org.sonar.java.annotations.VisibleForTesting; import org.sonar.java.ast.visitors.SubscriptionVisitor; import org.sonar.java.model.location.InternalPosition; @@ -52,6 +49,13 @@ public final class JWarning extends JProblem { this.end = InternalPosition.atOffset(endLine, endColumnOffset); } + JWarning(String message, Type type, Tree syntaxTree) { + super(message, type); + this.start = startOf(syntaxTree); + this.end = endOf(syntaxTree); + this.syntaxTree = syntaxTree; + } + public Tree syntaxTree() { return syntaxTree; } @@ -97,30 +101,7 @@ public static class Mapper extends SubscriptionVisitor { private final PriorityQueue warnings = new PriorityQueue<>(Comparator.comparing(JWarning::start).thenComparing(JWarning::end)); - private Mapper(CompilationUnit ast, LineColumnConverter lineColumnConverter) { - Stream.of(ast.getProblems()) - .map(problem -> convert(problem, ast, lineColumnConverter)) - .filter(Objects::nonNull) - .forEach(warnings::add); - } - - @CheckForNull - private static JWarning convert(IProblem problem, CompilationUnit root, LineColumnConverter lineColumnConverter) { - for (Type type : Type.values()) { - if (type.matches(problem)) { - LineColumnConverter.Pos start = lineColumnConverter.toPos(problem.getSourceStart()); - LineColumnConverter.Pos end = lineColumnConverter.toPos(problem.getSourceStart()); - return new JWarning(problem.getMessage(), - type, - start.line(), start.columnOffset(), - end.line(), end.columnOffset()); - } - } - return null; - } - - public static Mapper warningsFor(CompilationUnit ast, LineColumnConverter lineColumnConverter) { - return new Mapper(ast, lineColumnConverter); + private Mapper() { } public void mappedInto(JavaTree.CompilationUnitTreeImpl cut) { diff --git a/java-frontend/src/main/java/org/sonar/java/model/KeywordSuper.java b/java-frontend/src/main/java/org/sonar/java/model/KeywordSuper.java index 6bd879ea8cb..a1a7dd82ad3 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/KeywordSuper.java +++ b/java-frontend/src/main/java/org/sonar/java/model/KeywordSuper.java @@ -22,8 +22,12 @@ final class KeywordSuper extends IdentifierTreeImpl { - KeywordSuper(InternalSyntaxToken token, Symbol symbol) { + KeywordSuper(InternalSyntaxToken token) { super(token); + } + + KeywordSuper(InternalSyntaxToken token, Symbol symbol) { + this(token); setSymbol(symbol); } @@ -32,4 +36,9 @@ public Type symbolType() { return symbol().type(); } + @Override + public Symbol symbol() { + return symbol != null ? symbol : Symbol.UNKNOWN_SYMBOL; + } + } diff --git a/java-frontend/src/main/java/org/sonar/java/model/KeywordThis.java b/java-frontend/src/main/java/org/sonar/java/model/KeywordThis.java index 90a35137502..396b4da6c03 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/KeywordThis.java +++ b/java-frontend/src/main/java/org/sonar/java/model/KeywordThis.java @@ -21,9 +21,18 @@ final class KeywordThis extends IdentifierTreeImpl { - KeywordThis(InternalSyntaxToken token, Symbol symbol) { + KeywordThis(InternalSyntaxToken token) { super(token); + } + + KeywordThis(InternalSyntaxToken token, Symbol symbol) { + this(token); setSymbol(symbol); } + @Override + public Symbol symbol() { + return symbol != null ? symbol : Symbol.UNKNOWN_SYMBOL; + } + } diff --git a/java-frontend/src/main/java/org/sonar/java/model/LiteralUtils.java b/java-frontend/src/main/java/org/sonar/java/model/LiteralUtils.java index 5632dcfbad1..ddc343328f7 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/LiteralUtils.java +++ b/java-frontend/src/main/java/org/sonar/java/model/LiteralUtils.java @@ -133,6 +133,23 @@ public static String trimQuotes(String value) { return value.substring(delimiterLength, value.length() - delimiterLength); } + /** + * The compile time value of {@code literal}, typed as the literal is: a {@link String} for text, a {@link Boolean} + * for booleans and an {@link Integer} for an int that fits. Anything else keeps its source spelling. + */ + public static Object constantValue(LiteralTree literal) { + String value = literal.value(); + return switch (literal.kind()) { + case STRING_LITERAL, TEXT_BLOCK -> trimQuotes(value); + case BOOLEAN_LITERAL -> Boolean.valueOf(value); + case INT_LITERAL -> { + Integer integer = intLiteralValue(literal); + yield integer == null ? value : integer; + } + default -> value; + }; + } + public static boolean isTextBlock(String value) { return value.startsWith("\"\"\""); } diff --git a/java-frontend/src/main/java/org/sonar/java/model/SemsitterTranslator.java b/java-frontend/src/main/java/org/sonar/java/model/SemsitterTranslator.java new file mode 100644 index 00000000000..a29726d3331 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/model/SemsitterTranslator.java @@ -0,0 +1,3347 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.model; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.sonar.sslr.api.RecognitionException; +import java.nio.file.Path; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.IdentityHashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.sonar.java.ast.parser.ArgumentListTreeImpl; +import org.sonar.java.ast.parser.FormalParametersListTreeImpl; +import org.sonar.java.ast.parser.InitializerListTreeImpl; +import org.sonar.java.ast.parser.ModuleNameListTreeImpl; +import org.sonar.java.ast.parser.ModuleNameTreeImpl; +import org.sonar.java.ast.parser.QualifiedIdentifierListTreeImpl; +import org.sonar.java.ast.parser.ResourceListTreeImpl; +import org.sonar.java.ast.parser.StatementListTreeImpl; +import org.sonar.java.ast.parser.TypeParameterListTreeImpl; +import org.sonar.java.model.declaration.AnnotationTreeImpl; +import org.sonar.java.model.declaration.ClassTreeImpl; +import org.sonar.java.model.declaration.EnumConstantTreeImpl; +import org.sonar.java.model.declaration.ExportsDirectiveTreeImpl; +import org.sonar.java.model.declaration.MethodTreeImpl; +import org.sonar.java.model.declaration.ModifierKeywordTreeImpl; +import org.sonar.java.model.declaration.ModifiersTreeImpl; +import org.sonar.java.model.declaration.ModuleDeclarationTreeImpl; +import org.sonar.java.model.declaration.OpensDirectiveTreeImpl; +import org.sonar.java.model.declaration.ProvidesDirectiveTreeImpl; +import org.sonar.java.model.declaration.RequiresDirectiveTreeImpl; +import org.sonar.java.model.declaration.UsesDirectiveTreeImpl; +import org.sonar.java.model.declaration.VariableTreeImpl; +import org.sonar.java.model.expression.ArrayAccessExpressionTreeImpl; +import org.sonar.java.model.expression.AssignmentExpressionTreeImpl; +import org.sonar.java.model.expression.BinaryExpressionTreeImpl; +import org.sonar.java.model.expression.ConditionalExpressionTreeImpl; +import org.sonar.java.model.expression.IdentifierTreeImpl; +import org.sonar.java.model.expression.InstanceOfTreeImpl; +import org.sonar.java.model.expression.InternalPostfixUnaryExpression; +import org.sonar.java.model.expression.InternalPrefixUnaryExpression; +import org.sonar.java.model.expression.LambdaExpressionTreeImpl; +import org.sonar.java.model.expression.LiteralTreeImpl; +import org.sonar.java.model.expression.MemberSelectExpressionTreeImpl; +import org.sonar.java.model.expression.MethodInvocationTreeImpl; +import org.sonar.java.model.expression.MethodReferenceTreeImpl; +import org.sonar.java.model.expression.NewArrayTreeImpl; +import org.sonar.java.model.expression.NewClassTreeImpl; +import org.sonar.java.model.expression.ParenthesizedTreeImpl; +import org.sonar.java.model.expression.TypeArgumentListTreeImpl; +import org.sonar.java.model.expression.TypeCastExpressionTreeImpl; +import org.sonar.java.model.expression.VarTypeTreeImpl; +import org.sonar.java.model.pattern.AbstractPatternTree; +import org.sonar.java.model.pattern.DefaultPatternTreeImpl; +import org.sonar.java.model.pattern.GuardedPatternTreeImpl; +import org.sonar.java.model.pattern.NullPatternTreeImpl; +import org.sonar.java.model.pattern.RecordPatternTreeImpl; +import org.sonar.java.model.pattern.TypePatternTreeImpl; +import org.sonar.java.model.statement.AssertStatementTreeImpl; +import org.sonar.java.model.statement.BlockTreeImpl; +import org.sonar.java.model.statement.BreakStatementTreeImpl; +import org.sonar.java.model.statement.CaseGroupTreeImpl; +import org.sonar.java.model.statement.CaseLabelTreeImpl; +import org.sonar.java.model.statement.CatchTreeImpl; +import org.sonar.java.model.statement.ContinueStatementTreeImpl; +import org.sonar.java.model.statement.DoWhileStatementTreeImpl; +import org.sonar.java.model.statement.EmptyStatementTreeImpl; +import org.sonar.java.model.statement.ExpressionStatementTreeImpl; +import org.sonar.java.model.statement.ForEachStatementImpl; +import org.sonar.java.model.statement.ForStatementTreeImpl; +import org.sonar.java.model.statement.IfStatementTreeImpl; +import org.sonar.java.model.statement.LabeledStatementTreeImpl; +import org.sonar.java.model.statement.ReturnStatementTreeImpl; +import org.sonar.java.model.statement.StaticInitializerTreeImpl; +import org.sonar.java.model.statement.SwitchExpressionTreeImpl; +import org.sonar.java.model.statement.SwitchStatementTreeImpl; +import org.sonar.java.model.statement.SynchronizedStatementTreeImpl; +import org.sonar.java.model.statement.ThrowStatementTreeImpl; +import org.sonar.java.model.statement.TryStatementTreeImpl; +import org.sonar.java.model.statement.WhileStatementTreeImpl; +import org.sonar.java.model.statement.YieldStatementTreeImpl; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonar.plugins.java.api.semantic.Type; +import org.sonar.plugins.java.api.tree.AnnotationTree; +import org.sonar.plugins.java.api.tree.ArrayDimensionTree; +import org.sonar.plugins.java.api.tree.ExpressionTree; +import org.sonar.plugins.java.api.tree.AssignmentExpressionTree; +import org.sonar.plugins.java.api.tree.BaseTreeVisitor; +import org.sonar.plugins.java.api.tree.CaseGroupTree; +import org.sonar.plugins.java.api.tree.CaseLabelTree; +import org.sonar.plugins.java.api.tree.ClassTree; +import org.sonar.plugins.java.api.tree.ExpressionStatementTree; +import org.sonar.plugins.java.api.tree.IdentifierTree; +import org.sonar.plugins.java.api.tree.ImportClauseTree; +import org.sonar.plugins.java.api.tree.LambdaExpressionTree; +import org.sonar.plugins.java.api.tree.MemberSelectExpressionTree; +import org.sonar.plugins.java.api.tree.MethodTree; +import org.sonar.plugins.java.api.tree.Modifier; +import org.sonar.plugins.java.api.tree.ModifierTree; +import org.sonar.plugins.java.api.tree.ModuleDeclarationTree; +import org.sonar.plugins.java.api.tree.ModuleDirectiveTree; +import org.sonar.plugins.java.api.tree.ParameterizedTypeTree; +import org.sonar.plugins.java.api.tree.PatternTree; +import org.sonar.plugins.java.api.tree.StatementTree; +import org.sonar.plugins.java.api.tree.SwitchExpressionTree; +import org.sonar.plugins.java.api.tree.SyntaxToken; +import org.sonar.plugins.java.api.tree.SyntaxTrivia; +import org.sonar.plugins.java.api.tree.Tree; +import org.sonar.plugins.java.api.tree.TypeParameterTree; +import org.sonar.plugins.java.api.tree.TypeTree; +import org.sonar.plugins.java.api.tree.VariableTree; +import org.sonar.plugins.java.api.tree.WildcardTree; +import org.sonar.plugins.java.api.tree.YieldStatementTree; + +/** + * Builds the SonarJava AST from one SemSitter CST document. + * Positions come from the CST. Symbols and types come from {@link JSema}. + */ +final class SemsitterTranslator { + + private static final Pattern ARITY_ONLY = Pattern.compile("[^#]+#[^#(]+\\(\\d+\\)"); + private final JSema sema; + private final Map tokens = new IdentityHashMap<>(); + private final Deque labels = new ArrayDeque<>(); + private final Deque types = new ArrayDeque<>(); + private InternalSyntaxToken eof = new InternalSyntaxToken(1, 0, "", List.of(), true); + private boolean switchExpression; + private static final List FINAL_MODIFIER = List.of("final"); + private String implicitClassName = ""; + private String packageName = ""; + private boolean naming; + private final Map singleImports = new HashMap<>(); + private final List onDemandImports = new ArrayList<>(); + private final Map staticImports = new HashMap<>(); + private final List staticOnDemandImports = new ArrayList<>(); + private final Deque> scopes = new ArrayDeque<>(); + private final Deque returnTargets = new ArrayDeque<>(); + + private SemsitterTranslator(JSema sema) { + this.sema = sema; + } + + static JavaTree.CompilationUnitTreeImpl translate(JSema sema, JsonObject document) { + boolean broken = document.has("hasError") && document.get("hasError").getAsBoolean(); + SemsitterTranslator translator = new SemsitterTranslator(sema); + if (document.has("path") && document.get("path").isJsonPrimitive()) { + String fileName = Path.of(document.get("path").getAsString()).getFileName().toString(); + translator.implicitClassName = fileName.endsWith(".java") ? fileName.substring(0, fileName.length() - 5) : fileName; + } + Node root = new Node(document.getAsJsonObject("tree")); + translator.indexTokens(root); + translator.declareGenericReturns(root); + JavaTree.CompilationUnitTreeImpl unit = translator.compilationUnit(root); + unit.setSemanticModel(sema, sema.undefinedTypes(), sema.getEnvironmentCleaner()); + setParents(unit); + CompilerWarnings.addTo(unit); + EffectivelyFinalVariables.mark(unit); + if (broken) { + throw syntaxError(root); + } + return unit; + } + + /** ECJ aborts the file on a syntax error and reports the location of the first broken token. */ + private static RecognitionException syntaxError(Node root) { + Node problem = firstProblem(root); + int line = problem == null ? 1 : problem.line(); + int column = 1; + if (problem != null) { + column = problem.col() + 1; + if (problem.missing() && problem.col() == problem.endCol()) { + column = Math.max(1, problem.col()); + } + } + return new RecognitionException(line, "Parse error at line " + line + " column " + column + ": SemSitter: syntax error"); + } + + @Nullable + private static Node firstProblem(Node node) { + if ("ERROR".equals(node.k()) || node.missing()) { + return node; + } + if (!node.json.has("c")) { + return null; + } + for (JsonElement element : node.json.getAsJsonArray("c")) { + Node problem = firstProblem(new Node(element.getAsJsonObject())); + if (problem != null) { + return problem; + } + } + return null; + } + + private void indexTokens(Node root) { + List leaves = new ArrayList<>(); + collectLeaves(root, leaves); + List pending = new ArrayList<>(); + for (Node leaf : leaves) { + if (isComment(leaf)) { + pending.add(trivia(leaf)); + } else { + tokens.put(leaf.json, new InternalSyntaxToken(leaf.line(), leaf.col(), leaf.text(), pending, false)); + pending = new ArrayList<>(); + } + } + int line = 1; + int column = 0; + if (root.json.has("p")) { + line = root.json.getAsJsonArray("p").get(2).getAsInt(); + column = root.json.getAsJsonArray("p").get(3).getAsInt(); + } else if (!leaves.isEmpty()) { + Node last = leaves.get(leaves.size() - 1); + line = last.json.getAsJsonArray("p").get(2).getAsInt(); + column = last.json.getAsJsonArray("p").get(3).getAsInt(); + } + eof = new InternalSyntaxToken(line, column, "", pending, true); + } + + private static void collectLeaves(Node node, List leaves) { + if (node.missing()) { + return; + } + if (isComment(node) || node.allChildren().isEmpty() || isLiteral(node.k())) { + if (!node.text().isEmpty() || isComment(node)) { + leaves.add(node); + } + return; + } + for (Node child : node.allChildren()) { + collectLeaves(child, leaves); + } + } + + private JavaTree.CompilationUnitTreeImpl compilationUnit(Node program) { + JavaTree.PackageDeclarationTreeImpl packageDeclaration = null; + List imports = new ArrayList<>(); + List types = new ArrayList<>(); + List compactMembers = new ArrayList<>(); + ModuleDeclarationTree module = null; + boolean[] compact = {false}; + for (Node child : program.children()) { + TopLevel collected = addTopLevel(child, imports, types, compactMembers, compact[0]); + packageDeclaration = collected.packageDeclaration == null ? packageDeclaration : collected.packageDeclaration; + module = collected.module == null ? module : collected.module; + compact[0] = collected.compact; + } + if (compact[0]) { + ClassTreeImpl implicitClass = new ClassTreeImpl(Tree.Kind.IMPLICIT_CLASS, null, compactMembers, null); + bindImplicitClass(implicitClass, compactMembers); + types.add(implicitClass); + } + return new JavaTree.CompilationUnitTreeImpl(packageDeclaration, imports, types, module, eof); + } + + /** One top-level construct. Children of an error node are kept, so a syntax error does not drop the rest of the file. */ + private TopLevel addTopLevel(Node child, List imports, List types, List compactMembers, boolean compact) { + if ("ERROR".equals(child.k())) { + JavaTree.PackageDeclarationTreeImpl packageDeclaration = null; + ModuleDeclarationTree module = null; + for (Node nested : child.children()) { + TopLevel collected = addTopLevel(nested, imports, types, compactMembers, compact); + packageDeclaration = collected.packageDeclaration == null ? packageDeclaration : collected.packageDeclaration; + module = collected.module == null ? module : collected.module; + compact = collected.compact; + } + return new TopLevel(packageDeclaration, module, compact); + } + return switch (child.k()) { + case "package_declaration" -> new TopLevel(packageDeclaration(child), null, compact); + case "module_declaration" -> new TopLevel(null, moduleDeclaration(child), compact); + case "import_declaration" -> { + imports.add(importDeclaration(child)); + yield new TopLevel(null, null, compact); + } + case ";" -> { + EmptyStatementTreeImpl empty = new EmptyStatementTreeImpl(token(child)); + if (compact) { + compactMembers.add(empty); + } else if (types.isEmpty()) { + imports.add(empty); + } else { + types.add(empty); + } + yield new TopLevel(null, null, compact); + } + case "class_declaration", "interface_declaration", "enum_declaration", "record_declaration", "annotation_type_declaration" -> { + addMember(compact ? compactMembers : types, child); + yield new TopLevel(null, null, compact); + } + case "method_declaration", "constructor_declaration", "field_declaration", "constant_declaration", "local_variable_declaration", "static_initializer", "block" -> { + if (!compact) { + compact = true; + compactMembers.addAll(types); + types.clear(); + } + addMember(compactMembers, child); + yield new TopLevel(null, null, compact); + } + default -> new TopLevel(null, null, compact); + }; + } + + private record TopLevel(@Nullable JavaTree.PackageDeclarationTreeImpl packageDeclaration, @Nullable ModuleDeclarationTree module, boolean compact) { + } + + private ModuleDeclarationTreeImpl moduleDeclaration(Node node) { + Node body = requiredField(node, "body", childKind(node, "module_body")); + List directives = new ArrayList<>(); + for (Node child : body.children()) { + if (child.k().endsWith("_module_directive")) { + directives.add(moduleDirective(child)); + } + } + return new ModuleDeclarationTreeImpl( + node.children().stream().filter(SemsitterTranslator::isAnnotation).map(this::annotation).toList(), + optionalToken(node, "open"), + token(required(node, "module")), + moduleName(requiredField(node, "name", firstNamed(node))), + token(required(body, "{")), + directives, + token(required(body, "}"))); + } + + private ModuleDirectiveTree moduleDirective(Node node) { + InternalSyntaxToken keyword = token(node.children().get(0)); + InternalSyntaxToken semicolon = token(required(node, ";")); + return switch (node.k()) { + case "requires_module_directive" -> new RequiresDirectiveTreeImpl(keyword, requiresModifiers(node), moduleName(requiredField(node, "module", lastNamed(node))), semicolon); + case "exports_module_directive" -> new ExportsDirectiveTreeImpl(keyword, expression(requiredField(node, "package", firstNamed(node))), optionalToken(node, "to"), moduleNames(node), semicolon); + case "opens_module_directive" -> new OpensDirectiveTreeImpl(keyword, expression(requiredField(node, "package", firstNamed(node))), optionalToken(node, "to"), moduleNames(node), semicolon); + case "uses_module_directive" -> new UsesDirectiveTreeImpl(keyword, (TypeTree) expression(requiredField(node, "type", firstNamed(node))), semicolon); + default -> new ProvidesDirectiveTreeImpl(keyword, (TypeTree) expression(requiredField(node, "provided", firstNamed(node))), token(required(node, "with")), providers(node), semicolon); + }; + } + + private ModifiersTreeImpl requiresModifiers(Node node) { + List modifiers = new ArrayList<>(); + for (Node child : node.children()) { + if ("requires_modifier".equals(child.k())) { + Node keyword = child.children().get(0); + modifiers.add(new ModifierKeywordTreeImpl("static".equals(keyword.text()) ? Modifier.STATIC : Modifier.TRANSITIVE, token(keyword))); + } + } + return new ModifiersTreeImpl(modifiers); + } + + private ModuleNameListTreeImpl moduleNames(Node node) { + ModuleNameListTreeImpl names = ModuleNameListTreeImpl.emptyList(); + for (Node child : node.children()) { + if ("modules".equals(child.f())) { + names.add(moduleName(child)); + } else if (",".equals(child.text())) { + names.separators().add(token(child)); + } + } + return names; + } + + private QualifiedIdentifierListTreeImpl providers(Node node) { + QualifiedIdentifierListTreeImpl providers = QualifiedIdentifierListTreeImpl.emptyList(); + boolean afterWith = false; + for (Node child : node.children()) { + if ("with".equals(child.text())) { + afterWith = true; + } else if (",".equals(child.text())) { + providers.separators().add(token(child)); + } else if (afterWith && !";".equals(child.text())) { + providers.add((TypeTree) expression(child)); + } + } + return providers; + } + + private ModuleNameTreeImpl moduleName(Node node) { + ModuleNameTreeImpl name = ModuleNameTreeImpl.emptyList(); + addModuleName(name, node); + return name; + } + + private void addModuleName(ModuleNameTreeImpl name, Node node) { + if ("scoped_identifier".equals(node.k())) { + addModuleName(name, requiredField(node, "scope", firstNamed(node))); + name.add(new IdentifierTreeImpl(token(requiredField(node, "name", lastNamed(node))))); + } else { + name.add(new IdentifierTreeImpl(token(node))); + } + } + + private JavaTree.PackageDeclarationTreeImpl packageDeclaration(Node node) { + naming = true; + ExpressionTree name = expression(requiredField(node, "name", firstOf(node, "scoped_identifier", "identifier"))); + naming = false; + packageName = dottedName(name); + bindPackage(name, packageName); + return new JavaTree.PackageDeclarationTreeImpl(annotations(node), token(required(node, "package")), name, token(required(node, ";"))); + } + + private JavaTree.ImportTreeImpl importDeclaration(Node node) { + InternalSyntaxToken staticToken = optionalToken(node, "static"); + naming = true; + ExpressionTree name = expression(requiredField(node, "name", firstNamed(node))); + naming = false; + if (keyword(node, "*") != null || keyword(node, "asterisk") != null) { + Node star = keyword(node, "*"); + if (star == null) { + star = keyword(node, "asterisk"); + } + (staticToken == null ? onDemandImports : staticOnDemandImports).add(dottedName(name)); + name = new MemberSelectExpressionTreeImpl(name, token(lastKeyword(node, ".")), new IdentifierTreeImpl(token(star))); + } else if (name instanceof MemberSelectExpressionTree select) { + if (staticToken == null) { + singleImports.putIfAbsent(select.identifier().name(), dottedName(name)); + } else { + staticImports.putIfAbsent(select.identifier().name(), dottedName(select.expression())); + } + } + JavaTree.ImportTreeImpl imported = new JavaTree.ImportTreeImpl(token(required(node, "import")), staticToken, optionalToken(node, "module"), name, token(required(node, ";"))); + if (staticToken == null && name instanceof MemberSelectExpressionTree select && !"*".equals(select.identifier().name())) { + String fqn = dottedName(name); + if (sema.declaresType(fqn) && sema.resolve(fqn) instanceof Symbol.TypeSymbol type) { + imported.setSymbol(type); + } + } + return imported; + } + + private void addMember(List members, Node node) { + switch (node.k()) { + case "field_declaration", "constant_declaration", "local_variable_declaration" -> members.addAll(variables(node)); + case "method_declaration", "constructor_declaration", "compact_constructor_declaration" -> members.add(method(node)); + case "annotation_type_element_declaration" -> members.add(annotationElement(node)); + case "class_declaration", "interface_declaration", "enum_declaration", "record_declaration", "annotation_type_declaration" -> members.add(typeDeclaration(node)); + case "static_initializer" -> members.add(staticInitializer(node)); + case "block" -> members.add(initializer(node)); + case "enum_constant" -> members.add(enumConstant(node, null)); + case ";" -> members.add(new EmptyStatementTreeImpl(token(node))); + case "{", "}" -> { + // punctuation of the body + } + case "ERROR" -> { + for (Node nested : node.children()) { + addMember(members, nested); + } + } + default -> { + // skipped construct + } + } + } + + private ClassTreeImpl typeDeclaration(Node node) { + Tree.Kind kind = switch (node.k()) { + case "interface_declaration" -> Tree.Kind.INTERFACE; + case "enum_declaration" -> Tree.Kind.ENUM; + case "record_declaration" -> Tree.Kind.RECORD; + case "annotation_type_declaration" -> Tree.Kind.ANNOTATION_TYPE; + default -> Tree.Kind.CLASS; + }; + Node body = field(node, "body"); + Node declaredName = nameNode(node); + if (declaredName != null && declaredName.decl() != null && sema.resolve(declaredName.decl()) instanceof Symbol.TypeSymbol typeSymbol) { + types.push(typeSymbol); + } else { + types.push(Symbol.TypeSymbol.UNKNOWN_TYPE); + } + List members; + try { + members = body == null ? new ArrayList<>() : membersOf(body); + } finally { + types.pop(); + } + InternalSyntaxToken open = body == null ? synthetic("{") : token(required(body, "{")); + InternalSyntaxToken close = body == null ? synthetic("}") : token(required(body, "}")); + ClassTreeImpl tree = new ClassTreeImpl(kind, open, members, close); + String keyword = switch (kind) { + case INTERFACE, ANNOTATION_TYPE -> "interface"; + case ENUM -> "enum"; + case RECORD -> "record"; + default -> "class"; + }; + IdentifierTreeImpl name = identifier(requiredField(node, "name", firstOf(node, "identifier", "type_identifier"))); + tree.complete(modifiers(node), token(required(node, keyword)), name); + Node typeParameters = field(node, "type_parameters"); + if (typeParameters != null) { + tree.completeTypeParameters(typeParameters(typeParameters)); + } + if (kind == Tree.Kind.ANNOTATION_TYPE) { + Node at = keyword(node, "@"); + if (at != null) { + tree.completeAtToken(token(at)); + } + } + if (kind == Tree.Kind.RECORD) { + Node parameters = field(node, "parameters"); + tree.completeRecordComponents( + parameters == null ? synthetic("(") : token(required(parameters, "(")), + parameters == null ? List.of() : parameters(parameters), + parameters == null ? synthetic(")") : token(required(parameters, ")"))); + } + Node superclass = childKind(node, "superclass"); + if (superclass != null) { + tree.completeSuperclass(token(required(superclass, "extends")), type(firstType(superclass))); + } + Node interfaces = childKind(node, "super_interfaces"); + if (interfaces == null) { + interfaces = childKind(node, "extends_interfaces"); + } + if (interfaces != null) { + tree.completeInterfaces(firstKeyword(interfaces), separatedTypes(interfaces)); + } + Node permits = childKind(node, "permits"); + if (permits != null) { + tree.completePermittedTypes(token(required(permits, "permits")), separatedTypes(permits)); + } + bindDeclaration(tree, nameNode(node), name); + if (tree.symbol() instanceof JTypeSymbol declared) { + if (tree.superClass() != null + && tree.superClass().symbolType().symbol() instanceof JTypeSymbol parent && parent.id != null && !parent.equals(declared)) { + declared.superName(parent.id); + } else if (kind == Tree.Kind.RECORD) { + declared.superName("java.lang.Record"); + } else if (kind == Tree.Kind.ENUM) { + declared.superName("java.lang.Enum"); + } + declared.declaredSupertypes(tree.superClass() == null ? null : tree.superClass().symbolType(), + tree.superInterfaces().stream().map(TypeTree::symbolType).toList()); + } + return tree; + } + + private List membersOf(Node body) { + List members = new ArrayList<>(); + List children = body.children(); + for (int i = 0; i < children.size(); i++) { + Node child = children.get(i); + if ("enum_constant".equals(child.k())) { + members.add(enumConstant(child, enumSeparator(children, i))); + } else if ("enum_body_declarations".equals(child.k())) { + List declarations = child.children(); + int j = 0; + while (childKind(body, "enum_constant") != null && j < declarations.size() && ";".equals(declarations.get(j).text())) { + j++; + } + for (; j < declarations.size(); j++) { + addMember(members, declarations.get(j)); + } + } else if (i + 1 < children.size() && brokenCompactConstructor(child, children.get(i + 1))) { + members.add(recoveredRecordConstructor(child, children.get(i + 1))); + i++; + } else { + addMember(members, child); + } + } + return members; + } + + /** Tree-sitter splits {@code Record throws Ex} into a field named {@code throws} and a compact constructor named {@code Ex}. */ + private static boolean brokenCompactConstructor(Node field, Node next) { + if (!"field_declaration".equals(field.k()) || !"compact_constructor_declaration".equals(next.k())) { + return false; + } + for (Node child : field.children()) { + if ("variable_declarator".equals(child.k())) { + Node name = field(child, "name"); + return name != null && "throws".equals(name.text()); + } + } + return false; + } + + private MethodTreeImpl recoveredRecordConstructor(Node field, Node compact) { + Node typeNode = field(field, "type"); + IdentifierTreeImpl name = identifier(typeNode == null ? field : typeNode); + Node throwsName = null; + for (Node child : field.children()) { + if ("variable_declarator".equals(child.k())) { + throwsName = field(child, "name"); + } + } + Node thrownName = nameNode(compact); + QualifiedIdentifierListTreeImpl thrown = QualifiedIdentifierListTreeImpl.emptyList(); + if (thrownName != null && !"throws".equals(thrownName.text())) { + thrown.add(type(thrownName)); + } + Node body = field(compact, "body"); + BlockTreeImpl block = body == null ? null : block(body); + MethodTreeImpl tree = new MethodTreeImpl( + null, + name, + new FormalParametersListTreeImpl(null, null), + throwsName == null ? null : token(throwsName), + thrown, + block, + null) + .completeWithModifiers(modifiers(field)); + bindDeclaration(tree, nameNode(compact), name); + return tree; + } + + @Nullable + private static Node enumSeparator(List siblings, int index) { + if (index + 1 >= siblings.size()) { + return null; + } + Node next = siblings.get(index + 1); + if (",".equals(next.text())) { + return next; + } + if ("enum_body_declarations".equals(next.k())) { + List declarations = next.children(); + if (!declarations.isEmpty() && ";".equals(declarations.get(0).text())) { + return declarations.get(0); + } + } + return null; + } + + private EnumConstantTreeImpl enumConstant(Node node, @Nullable Node separatorNode) { + IdentifierTreeImpl name = identifier(requiredField(node, "name", firstOf(node, "identifier"))); + Node arguments = field(node, "arguments"); + ArgumentListTreeImpl args = arguments == null ? ArgumentListTreeImpl.emptyList() : arguments(arguments); + Node body = field(node, "body"); + ClassTreeImpl classBody = null; + if (body != null) { + classBody = new ClassTreeImpl(Tree.Kind.CLASS, token(required(body, "{")), membersOf(body), token(required(body, "}"))); + bindType(classBody, body); + } + InternalSyntaxToken separator = separatorNode == null ? null : token(separatorNode); + EnumConstantTreeImpl tree = new EnumConstantTreeImpl(modifiers(node), name, new NewClassTreeImpl(name, args, classBody), separator); + bindDeclaration(tree, nameNode(node), name); + Symbol.TypeSymbol enumType = types.peek(); + if (enumType != null && !enumType.isUnknown()) { + String owner = enumType.type().fullyQualifiedName(); + Symbol.MethodSymbol constructor = sema.declaredMethod(owner, "", argumentTypes(args)); + name.symbol = constructor != null || !args.isEmpty() ? constructor : sema.resolve(owner + "#()"); + if (name.symbol instanceof Symbol.MethodSymbol method && !method.isUnknown()) { + recordUse(method, name); + } + ascribe(name, enumType.type()); + if (tree.symbol() instanceof JSymbol field && field.type().isUnknown()) { + field.type(enumType.type()); + } + } + return tree; + } + + private MethodTreeImpl annotationElement(Node node) { + FormalParametersListTreeImpl formals = new FormalParametersListTreeImpl(token(required(node, "(")), token(required(node, ")"))); + Node value = field(node, "value"); + IdentifierTreeImpl name = identifier(requiredField(node, "name", firstOf(node, "identifier"))); + MethodTreeImpl tree = new MethodTreeImpl(formals, value == null ? null : token(required(node, "default")), value == null ? null : expression(value)) + .complete(arrayType(type(requiredField(node, "type", firstType(node))), field(node, "dimensions")), name, token(required(node, ";"))) + .completeWithModifiers(modifiers(node)); + bindDeclaration(tree, nameNode(node), name); + return tree; + } + + private MethodTreeImpl method(Node node) { + scopes.addFirst(new HashMap<>()); + Node typeNode = field(node, "type"); + returnTargets.push(typeNode == null || field(node, "dimensions") != null ? Type.UNKNOWN : syntacticType(typeNode)); + try { + return methodInScope(node); + } finally { + scopes.removeFirst(); + returnTargets.pop(); + } + } + + private MethodTreeImpl methodInScope(Node node) { + Node parameters = field(node, "parameters"); + FormalParametersListTreeImpl formals = new FormalParametersListTreeImpl( + parameters == null ? null : token(required(parameters, "(")), + parameters == null ? null : token(required(parameters, ")"))); + if (parameters != null) { + VariableTree previous = null; + for (Node child : parameters.children()) { + if ("formal_parameter".equals(child.k()) || "spread_parameter".equals(child.k()) || "receiver_parameter".equals(child.k())) { + VariableTreeImpl parameter = variable(child, false); + formals.add(parameter); + previous = parameter; + } else if (",".equals(child.text()) && previous != null) { + ((VariableTreeImpl) previous).setEndToken(token(child)); + } + } + } + Node body = field(node, "body"); + BlockTreeImpl block = body == null ? null : block(body); + Node typeNode = field(node, "type"); + TypeTree returnType = typeNode == null || "constructor_declaration".equals(node.k()) ? null : arrayType(recoverQualifiedType(type(typeNode)), field(node, "dimensions")); + IdentifierTreeImpl name = identifier(requiredField(node, "name", firstOf(node, "identifier"))); + Node thrown = childKind(node, "throws"); + MethodTreeImpl tree = new MethodTreeImpl( + returnType, + name, + formals, + thrown == null ? null : token(required(thrown, "throws")), + thrown == null ? QualifiedIdentifierListTreeImpl.emptyList() : separatedTypes(thrown), + block, + body == null ? token(required(node, ";")) : null) + .completeWithModifiers(modifiers(node)); + Node typeParameters = field(node, "type_parameters"); + if (typeParameters != null) { + tree.completeWithTypeParameters(typeParameters(typeParameters)); + } + bindDeclaration(tree, nameNode(node), name); + return tree; + } + + private List parameters(Node parameters) { + List trees = new ArrayList<>(); + VariableTreeImpl previous = null; + for (Node child : parameters.children()) { + if ("formal_parameter".equals(child.k()) || "spread_parameter".equals(child.k())) { + previous = variable(child, false); + trees.add(previous); + } else if (",".equals(child.text()) && previous != null) { + previous.setEndToken(token(child)); + } + } + return trees; + } + + private List variables(Node declaration) { + ModifiersTreeImpl modifiers = modifiers(declaration); + Node typeNode = field(declaration, "type"); + TypeTree type; + if (typeNode != null) { + type = type(typeNode); + if ("field_declaration".equals(declaration.k()) || "constant_declaration".equals(declaration.k())) { + type = recoverQualifiedType(type); + } + } else if ("spread_parameter".equals(declaration.k())) { + type = spreadType(declaration); + } else { + Node catchTypeNode = childKind(declaration, "catch_type"); + type = catchTypeNode == null ? new VarTypeTreeImpl(synthetic("var")) : catchType(catchTypeNode); + } + List trees = new ArrayList<>(); + VariableTreeImpl previous = null; + for (Node child : declaration.children()) { + if ("variable_declarator".equals(child.k()) || "formal_parameter".equals(child.k())) { + previous = declarator(child, modifiers, type); + trees.add(previous); + } else if ("ERROR".equals(child.k()) && previous != null && previous.initializer() == null && containsNew(child)) { + InternalSyntaxToken equals = null; + for (Node part : child.children()) { + if ("=".equals(part.text())) { + equals = token(part); + } + } + previous.completeTypeAndInitializer(previous.type(), equals == null ? synthetic("=") : equals, arrayFromError(child)); + } else if ((",".equals(child.text()) || ";".equals(child.text())) && previous != null) { + previous.setEndToken(token(child)); + } + } + if (trees.isEmpty() && field(declaration, "name") != null) { + trees.add(declarator(declaration, modifiers, type)); + } + return trees; + } + + private static boolean containsNew(Node node) { + for (Node child : node.children()) { + if ("new".equals(child.text()) || containsNew(child)) { + return true; + } + } + return false; + } + + /** {@code new byte[]} left inside an error node beside the declarator. */ + private NewArrayTreeImpl arrayFromError(Node error) { + InternalSyntaxToken newKeyword = synthetic("new"); + Node typeNode = null; + List dims = new ArrayList<>(); + for (Node child : error.children()) { + if ("new".equals(child.text())) { + newKeyword = token(child); + } else if (isTypeKind(child.k())) { + typeNode = child; + } else if ("dimensions".equals(child.k()) || "dimensions_expr".equals(child.k())) { + dims.addAll(dimensions(child)); + } + } + if (dims.isEmpty()) { + dims.addAll(dimensions(error)); + } + NewArrayTreeImpl tree = new NewArrayTreeImpl(dims, InitializerListTreeImpl.emptyList()) + .complete(type(typeNode == null ? error : typeNode)) + .completeWithNewKeyword(newKeyword); + ascribe(tree, arrayTypeOf(tree.type(), Math.max(dims.size(), 1))); + return tree; + } + + private TypeTree spreadType(Node node) { + TypeTree element = type(firstType(node)); + JavaTree.ArrayTypeTreeImpl array = new JavaTree.ArrayTypeTreeImpl(element, List.of(), token(required(node, "..."))); + ascribe(array, JType.array(sema, element.symbolType() instanceof JType elementType ? elementType : JType.unknown(sema))); + return array; + } + + private VariableTreeImpl variable(Node parameter, boolean sharedModifiers) { + return variables(parameter).get(0); + } + + private VariableTreeImpl declarator(Node node, ModifiersTreeImpl modifiers, TypeTree type) { + IdentifierTreeImpl name = identifier(requiredField(node, "name", firstOf(node, "identifier"))); + VariableTreeImpl tree = new VariableTreeImpl(name).completeModifiersAndType(modifiers, applyDimensions(type, node)); + Node value = field(node, "value"); + if (value != null) { + ExpressionTree initializer = expression(value); + ascribeTarget(initializer, tree.type().symbolType()); + tree.completeTypeAndInitializer(tree.type(), token(required(node, "=")), initializer); + } + bindDeclaration(tree, nameNode(node) == null ? node : nameNode(node), name); + Type declared = tree.type().symbolType(); + if (declared.isParameterized() && tree.symbol() instanceof JSymbol symbol) { + symbol.type(declared); + } else if (tree.symbol().type() != null && !tree.symbol().type().isUnknown()) { + ascribe(tree.type(), tree.symbol().type()); + } + return tree; + } + + private TypeTree applyDimensions(TypeTree type, Node node) { + Node dimensions = field(node, "dimensions"); + if (dimensions == null) { + return type; + } + return arrayType(type, dimensions); + } + + private BlockTreeImpl staticInitializer(Node node) { + Node body = field(node, "body"); + if (body == null) { + body = node; + } + BlockTreeImpl block = block(body); + return new StaticInitializerTreeImpl(token(required(node, "static")), (InternalSyntaxToken) block.openBraceToken(), block.body(), (InternalSyntaxToken) block.closeBraceToken()); + } + + private BlockTreeImpl initializer(Node node) { + BlockTreeImpl block = block(node); + return new BlockTreeImpl(Tree.Kind.INITIALIZER, (InternalSyntaxToken) block.openBraceToken(), block.body(), (InternalSyntaxToken) block.closeBraceToken()); + } + + private BlockTreeImpl block(Node node) { + scopes.addFirst(new HashMap<>()); + try { + return blockInScope(node); + } finally { + scopes.removeFirst(); + } + } + + private BlockTreeImpl blockInScope(Node node) { + if (!"block".equals(node.k())) { + Node nested = childKind(node, "block"); + if (nested != null) { + node = nested; + } + } + InternalSyntaxToken open = token(required(node, "{")); + InternalSyntaxToken close = token(required(node, "}")); + List statements = new ArrayList<>(); + for (Node child : node.children()) { + if ("{".equals(child.text()) || "}".equals(child.text())) { + continue; + } + addStatement(statements, child); + } + return new BlockTreeImpl(open, statements, close); + } + + private void addStatement(List statements, Node node) { + if ("ERROR".equals(node.k())) { + for (Node nested : node.children()) { + if (nested.children().isEmpty() && nested.text().equals(nested.k())) { + continue; + } + addStatement(statements, nested); + } + return; + } + if ("local_variable_declaration".equals(node.k())) { + statements.addAll(variables(node)); + return; + } + if (";".equals(node.text())) { + statements.add(new EmptyStatementTreeImpl(token(node))); + return; + } + Node switchNode = childKind(node, "switch_expression"); + if ("expression_statement".equals(node.k()) && switchNode != null) { + statements.add(switchStatement(switchNode)); + statements.add(new EmptyStatementTreeImpl(token(required(node, ";")))); + return; + } + statements.add(statement(node)); + } + + private StatementTree statement(Node node) { + return switch (node.k()) { + case "block" -> block(node); + case "expression_statement" -> expressionStatement(node); + case "explicit_constructor_invocation" -> constructorInvocation(node); + case "if_statement" -> ifStatement(node); + case "while_statement" -> whileStatement(node); + case "do_statement" -> doStatement(node); + case "for_statement" -> forStatement(node); + case "enhanced_for_statement" -> forEach(node); + case "break_statement" -> breakStatement(node); + case "continue_statement" -> continueStatement(node); + case "return_statement" -> returnStatement(node); + case "yield_statement" -> yieldStatement(node); + case "throw_statement" -> throwStatement(node); + case "assert_statement" -> assertStatement(node); + case "labeled_statement" -> labeled(node); + case "try_statement", "try_with_resources_statement" -> tryStatement(node); + case "synchronized_statement" -> synchronizedStatement(node); + case "switch_statement", "switch_expression" -> switchStatement(node); + case "local_variable_declaration" -> variables(node).get(0); + case "class_declaration", "interface_declaration", "enum_declaration", "record_declaration" -> typeDeclaration(node); + case "ERROR" -> recoveredStatement(node); + default -> new EmptyStatementTreeImpl(";".equals(node.text()) ? token(node) : token(required(node, ";"))); + }; + } + + private ExpressionStatementTreeImpl expressionStatement(Node node) { + ExpressionTree expression = null; + InternalSyntaxToken semicolon = null; + for (Node child : node.children()) { + if (";".equals(child.text())) { + semicolon = token(child); + } else if (expression == null) { + expression = expression(child); + } + } + return new ExpressionStatementTreeImpl(expression == null ? literal(Tree.Kind.NULL_LITERAL, synthetic("null"), sema.getClassType("")) : expression, semicolon); + } + + private ExpressionStatementTreeImpl constructorInvocation(Node node) { + Node constructor = requiredField(node, "constructor", firstOf(node, "super", "this")); + IdentifierTreeImpl identifier = identifier(constructor); + bindUse(identifier, constructor); + Node object = field(node, "object"); + ExpressionTree select = object == null + ? identifier + : new MemberSelectExpressionTreeImpl(expression(object), token(required(node, ".")), identifier); + Node typeArguments = field(node, "type_arguments"); + MethodInvocationTreeImpl invocation = new MethodInvocationTreeImpl(select, typeArguments == null ? null : typeArguments(typeArguments), + arguments(requiredField(node, "arguments", childKind(node, "argument_list")))); + if (identifier.symbol instanceof Symbol.MethodSymbol method) { + invocation.methodSymbol = method; + ascribe(invocation, method.owner().type()); + ascribe(identifier, Type.UNKNOWN); + } + return new ExpressionStatementTreeImpl(invocation, token(required(node, ";"))); + } + + private IfStatementTreeImpl ifStatement(Node node) { + Parens condition = parens(requiredField(node, "condition", childKind(node, "parenthesized_expression"))); + Node consequence = field(node, "consequence"); + Node alternative = field(node, "alternative"); + return new IfStatementTreeImpl( + token(required(node, "if")), + condition.open, + condition.expr, + condition.close, + consequence == null ? new EmptyStatementTreeImpl(synthetic(";")) : statement(consequence), + alternative == null ? null : token(required(node, "else")), + alternative == null ? null : statement(alternative)); + } + + private WhileStatementTreeImpl whileStatement(Node node) { + Parens condition = parens(requiredField(node, "condition", childKind(node, "parenthesized_expression"))); + return new WhileStatementTreeImpl(token(required(node, "while")), condition.open, condition.expr, condition.close, statement(requiredField(node, "body", lastNamed(node)))); + } + + private DoWhileStatementTreeImpl doStatement(Node node) { + Node body = field(node, "body"); + Parens condition = parens(requiredField(node, "condition", childKind(node, "parenthesized_expression"))); + return new DoWhileStatementTreeImpl( + token(required(node, "do")), + body == null ? new EmptyStatementTreeImpl(synthetic(";")) : statement(body), + token(required(node, "while")), + condition.open, + condition.expr, + condition.close, + token(lastKeyword(node, ";"))); + } + + private ForStatementTreeImpl forStatement(Node node) { + scopes.addFirst(new HashMap<>()); + try { + return forStatementInScope(node); + } finally { + scopes.removeFirst(); + } + } + + private ForStatementTreeImpl forStatementInScope(Node node) { + StatementListTreeImpl init = StatementListTreeImpl.emptyList(); + StatementListTreeImpl update = StatementListTreeImpl.emptyList(); + InternalSyntaxToken firstSemicolon = null; + InternalSyntaxToken secondSemicolon = null; + ExpressionTree condition = null; + int semicolons = 0; + for (Node child : node.children()) { + if (")".equals(child.text())) { + break; + } + if ("for".equals(child.text()) || "(".equals(child.text())) { + continue; + } + if (";".equals(child.text())) { + semicolons++; + if (semicolons == 1) { + firstSemicolon = token(child); + } else { + secondSemicolon = token(child); + } + continue; + } + if (",".equals(child.text())) { + (semicolons == 0 ? init : update).separators().add(token(child)); + continue; + } + if ("local_variable_declaration".equals(child.k()) && semicolons == 0) { + init.addAll(variables(child)); + InternalSyntaxToken semi = optionalToken(child, ";"); + if (semi != null) { + firstSemicolon = semi; + semicolons = 1; + } + } else if (semicolons == 0) { + init.add(new ExpressionStatementTreeImpl(expression(child), null)); + } else if (semicolons == 1 && condition == null && !"block".equals(child.k()) && !same(field(node, "body"), child)) { + condition = expression(child); + } else if (semicolons >= 1 && same(child, field(node, "body"))) { + break; + } else if (semicolons >= 2 || same(field(node, "update"), child)) { + update.add(new ExpressionStatementTreeImpl(expression(child), null)); + } + } + Node body = field(node, "body"); + return new ForStatementTreeImpl( + token(required(node, "for")), + token(required(node, "(")), + init, + firstSemicolon == null ? synthetic(";") : firstSemicolon, + condition, + secondSemicolon == null ? synthetic(";") : secondSemicolon, + update, + token(required(node, ")")), + body == null ? new EmptyStatementTreeImpl(synthetic(";")) : statement(body)); + } + + private ForEachStatementImpl forEach(Node node) { + scopes.addFirst(new HashMap<>()); + try { + return forEachInScope(node); + } finally { + scopes.removeFirst(); + } + } + + private ForEachStatementImpl forEachInScope(Node node) { + TypeTree type = arrayType(type(requiredField(node, "type", firstType(node))), field(node, "dimensions")); + IdentifierTreeImpl name = identifier(requiredField(node, "name", firstOf(node, "identifier"))); + VariableTreeImpl variable = new VariableTreeImpl(modifiers(node), type, name); + bindDeclaration(variable, requiredField(node, "name", nameNode(node)), name); + return new ForEachStatementImpl( + token(required(node, "for")), + token(required(node, "(")), + variable, + token(required(node, ":")), + expression(requiredField(node, "value", firstNamed(node))), + token(required(node, ")")), + statement(requiredField(node, "body", lastNamed(node)))); + } + + private BreakStatementTreeImpl breakStatement(Node node) { + IdentifierTreeImpl label = labelUse(node); + return new BreakStatementTreeImpl(token(required(node, "break")), label, token(required(node, ";"))); + } + + private ContinueStatementTreeImpl continueStatement(Node node) { + return new ContinueStatementTreeImpl(token(required(node, "continue")), labelUse(node), token(required(node, ";"))); + } + + private ReturnStatementTreeImpl returnStatement(Node node) { + ExpressionTree expression = null; + for (Node child : node.children()) { + if (!"return".equals(child.text()) && !";".equals(child.text())) { + expression = expression(child); + } + } + if (expression instanceof NewClassTreeImpl newClass && !returnTargets.isEmpty()) { + inferDiamond(newClass, returnTargets.peek()); + } + return new ReturnStatementTreeImpl(token(required(node, "return")), expression, token(required(node, ";"))); + } + + private YieldStatementTreeImpl yieldStatement(Node node) { + ExpressionTree expression = null; + for (Node child : node.children()) { + if (!"yield".equals(child.text()) && !";".equals(child.text())) { + expression = expression(child); + } + } + Node keyword = keyword(node, "yield"); + return new YieldStatementTreeImpl(keyword == null ? null : token(keyword), expression, token(required(node, ";"))); + } + + private ThrowStatementTreeImpl throwStatement(Node node) { + return new ThrowStatementTreeImpl(token(required(node, "throw")), expression(firstNamed(node)), token(required(node, ";"))); + } + + private AssertStatementTreeImpl assertStatement(Node node) { + List expressions = new ArrayList<>(); + InternalSyntaxToken colon = null; + for (Node child : node.children()) { + if (":".equals(child.text())) { + colon = token(child); + } else if (!"assert".equals(child.text()) && !";".equals(child.text())) { + expressions.add(expression(child)); + } + } + AssertStatementTreeImpl tree = new AssertStatementTreeImpl(token(required(node, "assert")), expressions.get(0), token(required(node, ";"))); + if (expressions.size() > 1) { + tree.complete(colon == null ? synthetic(":") : colon, expressions.get(1)); + } + return tree; + } + + private LabeledStatementTreeImpl labeled(Node node) { + IdentifierTreeImpl name = identifier(firstOf(node, "identifier")); + JLabelSymbol symbol = new JLabelSymbol(name.name()); + labels.push(symbol); + List children = node.children(); + LabeledStatementTreeImpl tree = new LabeledStatementTreeImpl(name, token(required(node, ":")), statement(children.get(children.size() - 1))); + labels.pop(); + symbol.declaration = tree; + tree.labelSymbol = symbol; + name.labelSymbol = symbol; + String id = nameNode(node) == null ? null : firstOf(node, "identifier").decl(); + if (id != null) { + sema.declare(id, tree); + } + return tree; + } + + private TryStatementTreeImpl tryStatement(Node node) { + ResourceListTreeImpl resources = ResourceListTreeImpl.emptyList(); + Node resourceNode = field(node, "resources"); + if (resourceNode != null) { + for (Node child : resourceNode.children()) { + if ("resource".equals(child.k()) && field(child, "name") == null) { + resources.add(expression(firstNamed(child))); + } else if ("resource".equals(child.k()) || "local_variable_declaration".equals(child.k())) { + for (VariableTreeImpl resource : variables(child)) { + // A resource variable is implicitly final. + if (resource.symbol() instanceof JSymbol symbol) { + symbol.addModifiers(FINAL_MODIFIER); + } + resources.add(resource); + } + } else if (";".equals(child.text())) { + resources.separators().add(token(child)); + } else if (!"(".equals(child.text()) && !")".equals(child.text())) { + resources.add(expression(child)); + } + } + } + List catches = new ArrayList<>(); + for (Node child : node.children()) { + if ("catch_clause".equals(child.k())) { + scopes.addFirst(new HashMap<>()); + try { + Node parameter = field(child, "parameter"); + if (parameter == null) { + parameter = childKind(child, "formal_parameter") == null ? childKind(child, "catch_formal_parameter") : childKind(child, "formal_parameter"); + } + VariableTreeImpl variable = parameter == null ? variables(child).get(0) : variable(parameter, false); + catches.add(new CatchTreeImpl(token(required(child, "catch")), token(required(child, "(")), variable, token(required(child, ")")), block(requiredField(child, "body", childKind(child, "block"))))); + } finally { + scopes.removeFirst(); + } + } + } + Node body = field(node, "body"); + Node finallyBlock = childKind(node, "finally_clause"); + if (finallyBlock == null) { + finallyBlock = field(node, "finally"); + } + return new TryStatementTreeImpl( + token(required(node, "try")), + resources.isEmpty() ? null : token(required(node, "(")), + resources, + resources.isEmpty() ? null : token(required(node, ")")), + body == null ? block(node) : block(body), + catches, + finallyBlock == null ? null : token(required(finallyBlock, "finally")), + finallyBlock == null ? null : block(childKind(finallyBlock, "block") == null ? finallyBlock : childKind(finallyBlock, "block"))); + } + + private SynchronizedStatementTreeImpl synchronizedStatement(Node node) { + Parens parens = parens(requiredField(node, "condition", childKind(node, "parenthesized_expression"))); + return new SynchronizedStatementTreeImpl(token(required(node, "synchronized")), parens.open, parens.expr, parens.close, block(requiredField(node, "body", childKind(node, "block")))); + } + + private SwitchStatementTreeImpl switchStatement(Node node) { + Parens parens = parens(requiredField(node, "condition", childKind(node, "parenthesized_expression"))); + Node body = field(node, "body"); + return new SwitchStatementTreeImpl( + token(required(node, "switch")), + parens.open, + parens.expr, + parens.close, + body == null ? synthetic("{") : token(required(body, "{")), + body == null ? List.of() : switchGroups(body, false), + body == null ? synthetic("}") : token(required(body, "}"))); + } + + private SwitchExpressionTreeImpl switchExpression(Node node) { + boolean previous = switchExpression; + switchExpression = true; + Parens parens = parens(requiredField(node, "condition", childKind(node, "parenthesized_expression"))); + Node body = field(node, "body"); + SwitchExpressionTreeImpl tree = new SwitchExpressionTreeImpl( + token(required(node, "switch")), + parens.open, + parens.expr, + parens.close, + body == null ? synthetic("{") : token(required(body, "{")), + body == null ? List.of() : switchGroups(body, true), + body == null ? synthetic("}") : token(required(body, "}"))); + switchExpression = previous; + ascribe(tree, switchResultType(tree)); + return tree; + } + + private static Type switchResultType(SwitchExpressionTreeImpl tree) { + List results = new ArrayList<>(); + for (CaseGroupTree group : tree.cases()) { + List body = group.body(); + if (group.labels().stream().noneMatch(CaseLabelTree::isFallThrough) && body.size() == 1 && body.get(0) instanceof ExpressionStatementTree statement) { + results.add(statement.expression().symbolType()); + } + body.forEach(statement -> statement.accept(new BaseTreeVisitor() { + @Override + public void visitYieldStatement(YieldStatementTree yield) { + results.add(yield.expression().symbolType()); + } + + @Override + public void visitSwitchExpression(SwitchExpressionTree nested) { + // nested switch expressions yield their own results + } + + @Override + public void visitLambdaExpression(LambdaExpressionTree lambda) { + // lambdas cannot yield to the enclosing switch + } + + @Override + public void visitClass(ClassTree type) { + // classes cannot yield to the enclosing switch + } + })); + } + if (results.isEmpty() || results.stream().anyMatch(Type::isUnknown) || results.stream().distinct().count() != 1) { + return Type.UNKNOWN; + } + return results.get(0); + } + + private List switchGroups(Node body, boolean expression) { + List groups = new ArrayList<>(); + List labels = new ArrayList<>(); + List carried = new ArrayList<>(); + StatementListTreeImpl statements = StatementListTreeImpl.emptyList(); + List children = body.children(); + for (int i = 0; i < children.size(); i++) { + Node child = children.get(i); + if ("{".equals(child.text()) || "}".equals(child.text())) { + continue; + } + if ("switch_label".equals(child.k()) || "switch_rule".equals(child.k()) || "switch_block_statement_group".equals(child.k())) { + if ("switch_label".equals(child.k())) { + if (!statements.isEmpty()) { + groups.add(new CaseGroupTreeImpl(labels, statements)); + labels = new ArrayList<>(); + statements = StatementListTreeImpl.emptyList(); + } + Node next = i + 1 < children.size() ? children.get(i + 1) : null; + boolean separator = next != null && ":".equals(next.text()); + labels.add(caseLabel(child, separator ? token(next) : null)); + if (separator) { + i++; + } + } else { + CaseGroupTreeImpl group = switchArm(child, expression, carried); + if (group.body().isEmpty() && "switch_block_statement_group".equals(child.k()) && nextIsGroup(children, i)) { + carried = group.labels().stream().map(CaseLabelTreeImpl.class::cast).collect(Collectors.toCollection(ArrayList::new)); + } else { + groups.add(group); + carried = new ArrayList<>(); + } + } + } else if (!labels.isEmpty()) { + addStatement(statements, child); + } + } + if (!labels.isEmpty()) { + groups.add(new CaseGroupTreeImpl(labels, statements)); + } + return groups; + } + + private static boolean nextIsGroup(List siblings, int index) { + return index + 1 < siblings.size() && "switch_block_statement_group".equals(siblings.get(index + 1).k()); + } + + private CaseGroupTreeImpl switchArm(Node node, boolean expression, List carried) { + List labels = new ArrayList<>(carried); + StatementListTreeImpl body = StatementListTreeImpl.emptyList(); + List children = node.children(); + for (int i = 0; i < children.size(); i++) { + Node child = children.get(i); + if ("switch_label".equals(child.k())) { + Node next = i + 1 < children.size() ? children.get(i + 1) : null; + boolean separator = next != null && (":".equals(next.text()) || "->".equals(next.text())); + labels.add(caseLabel(child, separator ? token(next) : null)); + if (separator) { + i++; + } + continue; + } + if ("->".equals(child.text()) || ":".equals(child.text())) { + continue; + } + if ("expression_statement".equals(child.k()) && "switch_rule".equals(node.k())) { + ExpressionStatementTreeImpl statement = expressionStatement(child); + body.add(new YieldStatementTreeImpl(null, statement.expression(), (InternalSyntaxToken) statement.semicolonToken())); + } else if (expression && !isStatementKind(child.k())) { + ExpressionTree value = expression(child); + body.add(new YieldStatementTreeImpl(null, value, optionalToken(node, ";"))); + } else { + addStatement(body, child); + } + } + return new CaseGroupTreeImpl(labels, body); + } + + private CaseLabelTreeImpl caseLabel(Node node, @Nullable InternalSyntaxToken separator) { + InternalSyntaxToken keyword = keyword(node, "case") != null ? token(keyword(node, "case")) : token(required(node, "default")); + List expressions = new ArrayList<>(); + InternalSyntaxToken end = separator; + boolean caseLabel = keyword(node, "case") != null; + for (Node child : node.children()) { + if ("case".equals(child.text())) { + continue; + } + if ("default".equals(child.text())) { + if (caseLabel) { + expressions.add(new DefaultPatternTreeImpl(token(child))); + } + } else if (":".equals(child.text()) || "->".equals(child.text())) { + end = token(child); + } else if (",".equals(child.text())) { + continue; + } else if ("pattern".equals(child.k()) || "record_pattern".equals(child.k()) || "type_pattern".equals(child.k())) { + expressions.add(pattern(child)); + } else if ("guard".equals(child.k()) && !expressions.isEmpty() && expressions.get(expressions.size() - 1) instanceof PatternTree guarded) { + GuardedPatternTreeImpl pattern = new GuardedPatternTreeImpl(guarded, token(required(child, "when")), expression(firstNamed(child))); + ascribe(pattern, guarded.symbolType()); + expressions.set(expressions.size() - 1, pattern); + } else if ("null_literal".equals(child.k())) { + expressions.add(new NullPatternTreeImpl((LiteralTreeImpl) expression(child))); + } else { + expressions.add(expression(child)); + } + } + return new CaseLabelTreeImpl(keyword, expressions, end == null ? synthetic(":") : end); + } + + private ExpressionTree expression(Node node) { + if (node == null) { + return literal(Tree.Kind.NULL_LITERAL, synthetic("null"), JType.nullType(sema)); + } + ExpressionTree tree = switch (node.k()) { + case "identifier", "type_identifier", "this", "super", "true", "false", "null_literal" -> primary(node); + case "decimal_integer_literal", "hex_integer_literal", "octal_integer_literal", "binary_integer_literal" -> integerLiteral(node); + case "decimal_floating_point_literal", "hex_floating_point_literal" -> floatingLiteral(node); + case "string_literal", "string_fragment" -> literal(node.text().startsWith("\"\"\"") ? Tree.Kind.TEXT_BLOCK : Tree.Kind.STRING_LITERAL, token(node), sema.getClassType("java.lang.String")); + case "character_literal" -> literal(Tree.Kind.CHAR_LITERAL, token(node), sema.getClassType("char")); + case "text_block" -> literal(Tree.Kind.TEXT_BLOCK, token(node), sema.getClassType("java.lang.String")); + case "scoped_identifier", "scoped_type_identifier", "field_access" -> memberSelect(node); + case "method_invocation" -> invocation(node); + case "object_creation_expression" -> creation(node); + case "array_creation_expression" -> arrayCreation(node); + case "array_initializer", "element_value_array_initializer" -> arrayInitializer(node); + case "element_value_pair" -> elementValuePair(node); + case "class_literal" -> classLiteral(node); + case "annotation", "marker_annotation" -> annotation(node); + case "array_access" -> arrayAccess(node); + case "assignment_expression" -> assignment(node); + case "binary_expression" -> binary(node); + case "unary_expression" -> unary(node, true); + case "update_expression" -> update(node); + case "parenthesized_expression" -> parenthesized(node); + case "ternary_expression" -> ternary(node); + case "cast_expression" -> cast(node); + case "instanceof_expression" -> instanceOf(node); + case "lambda_expression" -> lambda(node); + case "method_reference" -> methodReference(node); + case "switch_expression" -> switchExpression(node); + case "generic_type", "array_type", "integral_type", "floating_point_type", "boolean_type", "void_type", "wildcard", "annotated_type" -> typeExpression(node); + case "ERROR" -> recoveredExpression(node); + default -> primary(node); + }; + return tree; + } + + /** The first recognizable expression inside a syntax error, so the surrounding code keeps a tree. */ + private ExpressionTree recoveredExpression(Node node) { + for (Node child : node.children()) { + String kind = child.k(); + if ("ERROR".equals(kind) || kind.endsWith("_expression") || kind.endsWith("_literal") || kind.endsWith("_identifier") + || "identifier".equals(kind) || "array_creation_expression".equals(kind) || "object_creation_expression".equals(kind) + || "method_invocation".equals(kind) || "field_access".equals(kind) || "array_access".equals(kind) + || "lambda_expression".equals(kind) || "method_reference".equals(kind) || "class_literal".equals(kind) + || "array_initializer".equals(kind) || "this".equals(kind) || "super".equals(kind)) { + return expression(child); + } + } + return literal(Tree.Kind.NULL_LITERAL, synthetic("null"), JType.nullType(sema)); + } + + /** The first recognizable statement inside a syntax error. */ + private StatementTree recoveredStatement(Node node) { + for (Node child : node.children()) { + String kind = child.k(); + if ("ERROR".equals(kind) || kind.endsWith("_statement") || "block".equals(kind) || kind.endsWith("_declaration")) { + return statement(child); + } + } + return new EmptyStatementTreeImpl(synthetic(";")); + } + + private ExpressionTree primary(Node node) { + if ("this".equals(node.k()) || "this".equals(node.text())) { + KeywordThis keyword = new KeywordThis(token(node)); + Symbol.TypeSymbol enclosing = types.peek(); + if (enclosing != null) { + keyword.symbol = sema.receiverSymbol(enclosing, "this", enclosing.type()); + ascribe(keyword, enclosing.type()); + } + return keyword; + } + if ("super".equals(node.k()) || "super".equals(node.text())) { + KeywordSuper keyword = new KeywordSuper(token(node)); + Symbol.TypeSymbol enclosing = types.peek(); + if (enclosing != null) { + Type superType = enclosing.superClass() == null ? Type.UNKNOWN : enclosing.superClass(); + keyword.symbol = sema.receiverSymbol(enclosing, "super", superType); + ascribe(keyword, superType); + } + return keyword; + } + if ("true".equals(node.k()) || "false".equals(node.k()) || "true".equals(node.text()) || "false".equals(node.text())) { + return literal(Tree.Kind.BOOLEAN_LITERAL, token(node), sema.getClassType("boolean")); + } + if ("null_literal".equals(node.k()) || "null".equals(node.text())) { + return literal(Tree.Kind.NULL_LITERAL, token(node), JType.nullType(sema)); + } + IdentifierTreeImpl identifier = identifier(node); + bindUse(identifier, node); + if (!naming && identifier.symbol().isUnknown() && node.unresolved()) { + String type = importedType(identifier.name()); + if (type != null) { + bindType(identifier, type); + } + } + if (!naming && "type_identifier".equals(node.k())) { + preferSingleImport(identifier); + recoverType(identifier); + } + return identifier; + } + + /** An unresolved qualified type name keeps its written name where the compiler's recovery does. */ + private TypeTree recoverQualifiedType(TypeTree type) { + if (!naming && type instanceof MemberSelectExpressionTreeImpl select && select.symbolType().isUnknown() + && select.identifier() instanceof IdentifierTreeImpl identifier && identifier.symbol().isUnknown()) { + String name = dottedName(select); + if (!name.isEmpty()) { + JTypeSymbol recovered = sema.recoveredType(name); + identifier.symbol = recovered; + ascribe(identifier, recovered.type()); + ascribe(select, recovered.type()); + } + } + return type; + } + + /** The type of an annotation: an unresolved one keeps its written, possibly qualified, name. */ + private Type annotationType(TypeTree type) { + String name = type instanceof ExpressionTree expression ? dottedName(expression) : ""; + if (type.symbolType().isUnknown() && !naming && !name.isEmpty()) { + return sema.recoveredType(name).type(); + } + return type.symbolType(); + } + + /** An unresolved type name keeps its written name, as a recovered binding does, so checks can match on the simple name. */ + private void recoverType(IdentifierTreeImpl identifier) { + if (identifier.symbol().isUnknown()) { + JTypeSymbol recovered = sema.recoveredType(identifier.name()); + identifier.symbol = recovered; + ascribe(identifier, recovered.type()); + } + } + + /** A duplicate single-type import is an error; recovery keeps the first, which is the type this simple name denotes. */ + private void preferSingleImport(IdentifierTreeImpl identifier) { + String imported = singleImports.get(identifier.name()); + if (imported == null || !sema.declaresType(imported)) { + return; + } + Symbol symbol = identifier.symbol(); + boolean constructor = symbol.isMethodSymbol() && "".equals(symbol.name()); + if (!symbol.isTypeSymbol() && !symbol.isUnknown() && !constructor) { + return; + } + String resolved = identifier.symbolType().erasure().fullyQualifiedName(); + if (!imported.equals(resolved)) { + bindType(identifier, imported); + } + } + + /** The type a simple name stands for when SemSitter left it unresolved: imported, in the package, or in java.lang. */ + @Nullable + private String importedType(String name) { + List candidates = new ArrayList<>(); + if (singleImports.containsKey(name)) { + candidates.add(singleImports.get(name)); + } + candidates.add(packageName.isEmpty() ? name : (packageName + "." + name)); + onDemandImports.forEach(prefix -> candidates.add(prefix + "." + name)); + candidates.add("java.lang." + name); + return candidates.stream().filter(sema::declaresType).findFirst().orElse(null); + } + + private void resolveQualifiedType(ExpressionTree qualifier, IdentifierTreeImpl identifier) { + Symbol owner = qualifier instanceof MemberSelectExpressionTree select ? select.identifier().symbol() : (qualifier instanceof IdentifierTree name ? name.symbol() : Symbol.UNKNOWN_SYMBOL); + String candidate; + if (owner.isTypeSymbol() && !owner.isUnknown()) { + candidate = ((Symbol.TypeSymbol) owner).type().fullyQualifiedName() + "$" + identifier.name(); + } else if (owner.isUnknown() || owner.isPackageSymbol()) { + String prefix = dottedName(qualifier); + candidate = prefix.isEmpty() ? null : (prefix + "." + identifier.name()); + } else { + candidate = null; + } + if (candidate != null && sema.declaresType(candidate)) { + bindType(identifier, candidate); + } + } + + @Nullable + private String fieldOwner(ExpressionTree qualifier) { + Type receiver = qualifier.symbolType().erasure(); + if (receiver.isClass()) { + return receiver.fullyQualifiedName(); + } + return qualifier instanceof IdentifierTree name ? importedType(name.name()) : null; + } + + /** The method SemSitter left unresolved, looked up in the receiver type, or in the enclosing types and static imports of an unqualified call. */ + @Nullable + private Symbol.MethodSymbol libraryMethod(@Nullable ExpressionTree qualifier, String name, List arguments) { + List owners = new ArrayList<>(); + if (qualifier != null) { + Type receiver = qualifier.symbolType().erasure(); + if (receiver.isClass()) { + owners.add(receiver.fullyQualifiedName()); + } else if (receiver.isArray()) { + owners.add("java.lang.Object"); + } else if (qualifier instanceof IdentifierTree typeName) { + String imported = importedType(typeName.name()); + if (imported != null) { + owners.add(imported); + } + } + } else { + types.stream().filter(type -> !type.isUnknown()).forEach(type -> owners.add(type.type().fullyQualifiedName())); + if (staticImports.containsKey(name)) { + owners.add(staticImports.get(name)); + } + owners.addAll(staticOnDemandImports); + } + Symbol.MethodSymbol found = owners.stream().map(owner -> sema.declaredMethod(owner, name, arguments)).filter(Objects::nonNull).findFirst().orElse(null); + if (found == null && qualifier != null && !owners.contains("java.lang.Object")) { + found = sema.declaredMethod("java.lang.Object", name, arguments); + } + return found; + } + + /** Types a lambda, a method reference or an array initializer from the type its context expects. */ + private void ascribeTarget(ExpressionTree expression, Type target) { + boolean targetTyped = expression.is(Tree.Kind.LAMBDA_EXPRESSION, Tree.Kind.METHOD_REFERENCE) || (expression instanceof NewArrayTreeImpl array && array.type() == null); + if (targetTyped && expression.symbolType().isUnknown() && !target.isUnknown()) { + ascribe(expression, expression instanceof NewArrayTreeImpl ? target : sema.groundType(target)); + } + if (expression instanceof NewClassTreeImpl newClass) { + inferDiamond(newClass, target); + } + if (expression instanceof LambdaExpressionTreeImpl lambda) { + bindLambdaParameterOwner(lambda, lambda.symbolType()); + } + } + + /** A diamond takes the type arguments its target gives the created class, and its constructor is seen from that type. */ + private void inferDiamond(NewClassTreeImpl tree, Type target) { + if (!(tree.identifier() instanceof JavaTree.ParameterizedTypeTreeImpl generic) || !generic.typeArguments().isEmpty() || !target.isParameterized()) { + return; + } + Type inferred = sema.inferDiamond(tree.symbolType(), sema.groundType(target)); + if (!inferred.isParameterized()) { + return; + } + ascribe(tree, inferred); + ascribe(generic, inferred); + if (tree.getConstructorIdentifier() instanceof IdentifierTreeImpl identifier) { + ascribe(identifier, inferred); + if (identifier.symbol() instanceof JMethodSymbol constructor && !constructor.isUnknown()) { + List parameters = sema.invocationParameters(constructor, inferred, argumentTypes(tree.arguments())); + identifier.symbol = sema.invocationView(constructor, inferred, parameters, constructor.returnType().type()); + } + } + } + + private void bindLambdaParameterOwner(LambdaExpressionTreeImpl lambda, Type target) { + JMethodSymbol functional = sema.functionalMethod(target); + if (functional == null || functional.parameterTypes().size() != lambda.parameters().size()) { + return; + } + List substituted = sema.invocationParameters(functional, target, List.of()); + List types = new ArrayList<>(); + for (int i = 0; i < lambda.parameters().size(); i++) { + VariableTree parameter = lambda.parameters().get(i); + Type type = parameter.symbol().type(); + if ((type == null || type.isUnknown()) && parameter.symbol() instanceof JVariableSymbol symbol && !substituted.get(i).isUnknown()) { + type = substituted.get(i); + symbol.type(type); + ascribe((IdentifierTreeImpl) parameter.simpleName(), type); + } + types.add(type == null || type.isUnknown() ? functional.parameterTypes().get(i) : type); + } + JMethodSymbol view = sema.invocationView(functional, target, types, sema.invocationReturn(functional, target, List.of())); + for (VariableTree parameter : lambda.parameters()) { + if (parameter.symbol() instanceof JVariableSymbol symbol) { + symbol.owner(view); + } + } + lambda.body().accept(new BaseTreeVisitor() { + @Override + public void visitVariable(VariableTree tree) { + if (tree.symbol() instanceof JVariableSymbol symbol && symbol.owner() instanceof JSymbol owner && owner.id != null && owner.id.contains("$lambda$")) { + symbol.owner(view); + } + super.visitVariable(tree); + } + + @Override + public void visitLambdaExpression(LambdaExpressionTree tree) { + // nested lambdas own their variables + } + + @Override + public void visitClass(ClassTree tree) { + // local and anonymous classes own their variables + } + }); + } + + private void ascribeArguments(Symbol.MethodSymbol method, List arguments) { + ascribeArguments(method, method.parameterTypes(), arguments); + } + + /** Types the arguments from the declared parameters, substituted for the receiver's type arguments when the call is generic. */ + private void ascribeArguments(Symbol.MethodSymbol method, List parameters, List arguments) { + for (int i = 0; i < arguments.size() && !parameters.isEmpty(); i++) { + Type parameter = parameters.get(Math.min(i, parameters.size() - 1)); + if (method.isVarArgsMethod() && i >= parameters.size() - 1 && parameter.isArray() && arguments.size() != parameters.size()) { + parameter = ((Type.ArrayType) parameter).elementType(); + } + if (i < parameters.size() || method.isVarArgsMethod()) { + ascribeTarget(arguments.get(i), parameter); + } + } + } + + private static List argumentTypes(List arguments) { + return arguments.stream().map(ExpressionTree::symbolType).toList(); + } + + private void bindType(IdentifierTreeImpl identifier, String fqn) { + Symbol symbol = sema.resolve(fqn); + identifier.symbol = symbol; + ascribe(identifier, symbol.type()); + sema.use(fqn, identifier); + } + + private void bindQualifierPackages(ExpressionTree qualifier, IdentifierTree identifier) { + if (identifier.symbol() instanceof Symbol.TypeSymbol type && !type.isUnknown()) { + Symbol owner = type.owner(); + while (owner != null && !owner.isPackageSymbol()) { + owner = owner.owner(); + } + if (owner != null && dottedName(qualifier).equals(owner.name())) { + bindPackage(qualifier, owner.name()); + } + } + } + + private void bindPackage(ExpressionTree tree, String name) { + ExpressionTree last = tree; + if (tree instanceof MemberSelectExpressionTree select) { + int dot = name.lastIndexOf('.'); + if (dot < 0) { + return; + } + bindPackage(select.expression(), name.substring(0, dot)); + last = select.identifier(); + } + if (last instanceof IdentifierTreeImpl identifier && identifier.symbol().isUnknown()) { + identifier.symbol = sema.packageSymbol(name); + } + } + + private static String dottedName(ExpressionTree tree) { + if (tree instanceof MemberSelectExpressionTree select) { + String qualifier = dottedName(select.expression()); + return qualifier.isEmpty() ? "" : (qualifier + "." + select.identifier().name()); + } + return tree instanceof IdentifierTree identifier ? identifier.name() : ""; + } + + private IdentifierTreeImpl identifier(Node node) { + String text = node.text().isEmpty() ? node.k() : node.text(); + return new IdentifierTreeImpl(tokens.getOrDefault(node.json, new InternalSyntaxToken(node.line(), node.col(), text, List.of(), false)), "_".equals(text)); + } + + private LiteralTreeImpl integerLiteral(Node node) { + String text = node.text(); + boolean isLong = text.endsWith("l") || text.endsWith("L"); + return literal(isLong ? Tree.Kind.LONG_LITERAL : Tree.Kind.INT_LITERAL, token(node), sema.getClassType(isLong ? "long" : "int")); + } + + private LiteralTreeImpl floatingLiteral(Node node) { + String text = node.text(); + boolean isFloat = text.endsWith("f") || text.endsWith("F"); + return literal(isFloat ? Tree.Kind.FLOAT_LITERAL : Tree.Kind.DOUBLE_LITERAL, token(node), sema.getClassType(isFloat ? "float" : "double")); + } + + private LiteralTreeImpl literal(Tree.Kind kind, InternalSyntaxToken token, Type type) { + LiteralTreeImpl tree = new LiteralTreeImpl(kind, token); + ascribe(tree, type); + return tree; + } + + private MemberSelectExpressionTreeImpl memberSelect(Node node) { + Node object = field(node, "object"); + if (object == null) { + object = field(node, "scope"); + } + Node name = field(node, "name"); + if (name == null) { + name = field(node, "field"); + } + Node scope = object == null ? firstNamed(node) : object; + ExpressionTree qualifier = object == null && compoundName(scope) ? typeExpression(scope) : (object == null ? primary(scope) : superQualifier(node, object)); + Node nameNode = name == null ? lastNamed(node) : name; + IdentifierTreeImpl identifier = identifier(nameNode); + bindUse(identifier, name != null || nameNode.json.has("refs") ? nameNode : node, true); + if (!naming) { + bindQualifiedReceiver(qualifier, identifier); + if (identifier.symbol().isUnknown()) { + resolveQualifiedType(qualifier, identifier); + } + if (identifier.symbol().isUnknown()) { + String owner = fieldOwner(qualifier); + Symbol.VariableSymbol field = owner == null ? null : sema.declaredField(owner, identifier.name()); + if (field != null) { + identifier.symbol = field; + ascribe(identifier, field.type()); + } + } + bindQualifierPackages(qualifier, identifier); + } + List annotations = node.children().stream().filter(SemsitterTranslator::isAnnotation).map(this::annotation).toList(); + if (!annotations.isEmpty()) { + identifier.complete(annotations); + } + MemberSelectExpressionTreeImpl tree = new MemberSelectExpressionTreeImpl(qualifier, token(lastKeyword(node, ".")), identifier); + ascribe(tree, identifier.symbolType()); + return tree; + } + + private ExpressionTree superQualifier(Node node, Node object) { + ExpressionTree qualifier = expression(object); + Node superNode = keyword(node, "super"); + if (superNode == null || same(superNode, object)) { + return qualifier; + } + IdentifierTreeImpl identifier = new IdentifierTreeImpl(token(superNode)); + bindQualifiedReceiver(qualifier, identifier); + return new MemberSelectExpressionTreeImpl(qualifier, token(required(node, ".")), identifier); + } + + private void bindQualifiedReceiver(ExpressionTree qualifier, IdentifierTreeImpl identifier) { + boolean isThis = "this".equals(identifier.name()); + if (!identifier.symbol().isUnknown() || (!isThis && !"super".equals(identifier.name())) + || !(qualifier.symbolType().symbol() instanceof Symbol.TypeSymbol owner) || owner.isUnknown()) { + return; + } + Type type = isThis || owner.isInterface() || owner.superClass() == null ? owner.type() : owner.superClass(); + identifier.symbol = sema.receiverSymbol(owner, identifier.name(), type); + ascribe(identifier, type); + } + + private static boolean compoundName(Node node) { + return switch (node.k()) { + case "scoped_type_identifier", "scoped_identifier", "generic_type", "field_access" -> true; + default -> false; + }; + } + + private MethodInvocationTreeImpl invocation(Node node) { + Node name = field(node, "name"); + Node object = field(node, "object"); + IdentifierTreeImpl identifier = identifier(name == null ? firstOf(node, "identifier") : name); + bindUse(identifier, name == null ? node : name, object != null); + ExpressionTree select; + ExpressionTree qualifier = null; + if (object == null) { + select = identifier; + } else { + qualifier = superQualifier(node, object); + select = new MemberSelectExpressionTreeImpl(qualifier, token(lastKeyword(node, ".")), identifier); + } + Node typeArguments = field(node, "type_arguments"); + MethodInvocationTreeImpl tree = new MethodInvocationTreeImpl(select, typeArguments == null ? null : typeArguments(typeArguments), arguments(requiredField(node, "arguments", childKind(node, "argument_list")))); + Type receiver = qualifier == null ? currentType() : qualifier.symbolType(); + String target = (name == null ? node : name).valueTarget(); + boolean arityOnly = target != null && ARITY_ONLY.matcher(target).matches(); + List arguments = argumentTypes(tree.arguments()); + if (qualifier == null && staticImports.containsKey(identifier.name())) { + boolean declaredHere = types.stream().filter(type -> !type.isUnknown()) + .anyMatch(type -> sema.declaredMethod(type.type().fullyQualifiedName(), identifier.name(), arguments) != null); + if (!declaredHere) { + Symbol.MethodSymbol imported = sema.declaredMethod(staticImports.get(identifier.name()), identifier.name(), arguments); + if (imported != null) { + identifier.symbol = imported; + } + } + } + if (identifier.symbol().isUnknown() || arityOnly) { + Symbol.MethodSymbol method = arityOnly + ? sema.declaredMethod(JSema.erase(target.substring(0, target.indexOf('#'))), identifier.name(), arguments) + : libraryMethod(qualifier, identifier.name(), arguments); + if (method != null) { + identifier.symbol = method; + } + } else if (identifier.symbol() instanceof Symbol.MethodSymbol bound) { + identifier.symbol = sema.preferOverload(bound, libraryMethod(qualifier, identifier.name(), arguments), arguments); + } + if (identifier.symbol instanceof JMethodSymbol method && !method.isUnknown() && !JSema.arityMatches(method, arguments.size())) { + identifier.symbol = Symbol.MethodSymbol.UNKNOWN_METHOD; + } + if (identifier.symbol instanceof JMethodSymbol method) { + List parameters = sema.invocationParameters(method, receiver, arguments); + ascribeArguments(method, parameters, tree.arguments()); + Type returned = sema.invocationReturn(method, receiver, arguments); + JMethodSymbol view = sema.invocationView(method, receiver, parameters, returned); + tree.methodSymbol = view; + identifier.symbol = view; + ascribe(identifier, returned); + ascribe(tree, returned); + } + return tree; + } + + private ExpressionTree typeExpression(Node node) { + TypeTree type = type(node); + return type instanceof ExpressionTree expression ? expression : literal(Tree.Kind.NULL_LITERAL, synthetic("null"), Type.UNKNOWN); + } + + private NewClassTreeImpl creation(Node node) { + Node typeNode = field(node, "type"); + TypeTree type = annotate(recoverQualifiedType(type(Objects.requireNonNull(typeNode == null ? firstType(node) : typeNode))), node); + Node arguments = field(node, "arguments"); + ArgumentListTreeImpl args = arguments == null ? ArgumentListTreeImpl.emptyList().complete(synthetic("("), synthetic(")")) : arguments(arguments); + Node body = field(node, "body"); + if (body == null) { + body = childKind(node, "class_body"); + } + ClassTreeImpl classBody = null; + if (body != null) { + classBody = new ClassTreeImpl(Tree.Kind.CLASS, token(required(body, "{")), membersOf(body), token(required(body, "}"))); + bindType(classBody, body); + } + NewClassTreeImpl tree = new NewClassTreeImpl(type, args, classBody).completeWithNewKeyword(token(required(node, "new"))); + Node enclosing = node.children().get(0); + if (!"new".equals(enclosing.text())) { + tree.completeWithEnclosingExpression(expression(enclosing)); + tree.completeWithDotToken(token(required(node, "."))); + } + Node typeArguments = field(node, "type_arguments"); + if (typeArguments != null) { + tree.completeWithTypeArguments(typeArguments(typeArguments)); + } + bindConstructor(tree, typeNode); + ascribe(tree, classBody != null && !classBody.symbol().isUnknown() ? classBody.symbol().type() : type.symbolType()); + return tree; + } + + private void bindConstructor(NewClassTreeImpl tree, @Nullable Node typeNode) { + if (!(tree.getConstructorIdentifier() instanceof IdentifierTreeImpl identifier)) { + return; + } + TypeTree written = tree.identifier() instanceof ParameterizedTypeTree parameterized ? parameterized.type() : tree.identifier(); + String imported = written instanceof MemberSelectExpressionTree ? null : singleImports.get(identifier.name()); + String constructor = typeNode == null ? null : typeNode.valueTarget(); + if (constructor != null) { + Symbol resolved = sema.resolve(constructor); + String owner = resolved.owner() == null || resolved.owner().type() == null ? "" : resolved.owner().type().erasure().fullyQualifiedName(); + boolean importedElsewhere = imported != null && sema.declaresType(imported) && !imported.equals(owner); + if (resolved instanceof Symbol.MethodSymbol method && !importedElsewhere && JSema.arityMatches(method, tree.arguments().size())) { + identifier.symbol = resolved; + recordUse(resolved, identifier); + ascribeArguments(method, tree.arguments()); + return; + } + } + Type type = tree.identifier().symbolType().erasure(); + if (imported != null && sema.declaresType(imported) && !imported.equals(type.fullyQualifiedName())) { + type = sema.getClassType(imported); + bindType(identifier, imported); + } + String owner = tree.classBody() != null && type.symbol().isInterface() ? "java.lang.Object" : type.fullyQualifiedName(); + Symbol.MethodSymbol declared = type.isClass() ? sema.declaredMethod(owner, "", argumentTypes(tree.arguments())) : null; + if (declared != null && JSema.arityMatches(declared, tree.arguments().size())) { + identifier.symbol = declared; + recordUse(declared, identifier); + ascribeArguments(declared, tree.arguments()); + } else if (!(identifier.symbol() instanceof Symbol.MethodSymbol)) { + identifier.symbol = Symbol.MethodSymbol.UNKNOWN_METHOD; + } + } + + private NewArrayTreeImpl arrayCreation(Node node) { + Node initializer = field(node, "value"); + String dimensionsKind = initializer == null ? "dimensions_expr" : "dimensions"; + List dims = new ArrayList<>(); + for (Node child : node.children()) { + if (dimensionsKind.equals(child.k())) { + dims.addAll(dimensions(child)); + } + } + InitializerListTreeImpl initializers = initializer == null ? InitializerListTreeImpl.emptyList() : initializers(initializer); + NewArrayTreeImpl tree = new NewArrayTreeImpl(dims, initializers) + .complete(annotate(type(requiredField(node, "type", firstType(node))), node)) + .completeWithNewKeyword(token(required(node, "new"))); + if (initializer != null) { + tree.completeWithCurlyBraces(token(required(initializer, "{")), token(required(initializer, "}"))); + } + int rank = dims.size(); + if (rank == 0 && initializer != null) { + rank = 1; + } + ascribe(tree, arrayTypeOf(tree.type(), rank)); + return tree; + } + + private NewArrayTreeImpl arrayInitializer(Node node) { + NewArrayTreeImpl tree = new NewArrayTreeImpl(List.of(), initializers(node)).completeWithCurlyBraces(token(required(node, "{")), token(required(node, "}"))); + return tree; + } + + private AssignmentExpressionTreeImpl elementValuePair(Node node) { + Node key = requiredField(node, "key", firstNamed(node)); + IdentifierTreeImpl name = identifier(key); + bindUse(name, key); + ExpressionTree value = expression(requiredField(node, "value", lastNamed(node))); + AssignmentExpressionTreeImpl tree = new AssignmentExpressionTreeImpl(Tree.Kind.ASSIGNMENT, name, token(required(node, "=")), value); + ascribe(tree, value.symbolType()); + return tree; + } + + private MemberSelectExpressionTreeImpl classLiteral(Node node) { + ExpressionTree type = (ExpressionTree) type(firstNamed(node)); + IdentifierTreeImpl className = new IdentifierTreeImpl(token(required(node, "class"))); + MemberSelectExpressionTreeImpl tree = new MemberSelectExpressionTreeImpl(type, token(required(node, ".")), className); + Type literal = type.symbolType().isPrimitive() ? type.symbolType().primitiveWrapperType() : type.symbolType(); + JType argument = literal instanceof JType jType ? jType : JType.unknown(sema); + ascribe(tree, JType.named(sema, "java.lang.Class", List.of(argument))); + return tree; + } + + private InitializerListTreeImpl initializers(Node node) { + InitializerListTreeImpl list = InitializerListTreeImpl.emptyList(); + for (Node child : node.children()) { + if ("{".equals(child.text()) || "}".equals(child.text())) { + continue; + } + if (",".equals(child.text())) { + list.separators().add(token(child)); + } else { + list.add(expression(child)); + } + } + return list; + } + + private ArrayAccessExpressionTreeImpl arrayAccess(Node node) { + Node index = field(node, "index"); + ExpressionTree array = expression(requiredField(node, "array", firstNamed(node))); + ArrayDimensionTreeImpl dimension = new ArrayDimensionTreeImpl(token(required(node, "[")), index == null ? null : expression(index), token(required(node, "]"))); + ArrayAccessExpressionTreeImpl tree = new ArrayAccessExpressionTreeImpl(array, dimension); + Type arrayType = array.symbolType(); + ascribe(tree, arrayType.isArray() ? ((Type.ArrayType) arrayType).elementType() : Type.UNKNOWN); + return tree; + } + + private AssignmentExpressionTreeImpl assignment(Node node) { + Node operator = field(node, "operator"); + String text = operator == null ? "=" : operator.text(); + AssignmentExpressionTreeImpl tree = new AssignmentExpressionTreeImpl( + assignmentKind(text), + expression(requiredField(node, "left", firstNamed(node))), + operator == null ? token(required(node, text)) : token(operator), + expression(requiredField(node, "right", lastNamed(node)))); + ascribe(tree, tree.variable().symbolType()); + if (tree.is(Tree.Kind.ASSIGNMENT) && tree.expression() instanceof NewClassTreeImpl newClass) { + inferDiamond(newClass, tree.variable().symbolType()); + } + return tree; + } + + private BinaryExpressionTreeImpl binary(Node node) { + Node operator = field(node, "operator"); + String text = operator == null ? "+" : operator.text(); + ExpressionTree left = expression(requiredField(node, "left", firstNamed(node))); + ExpressionTree right = expression(requiredField(node, "right", lastNamed(node))); + BinaryExpressionTreeImpl tree = new BinaryExpressionTreeImpl(binaryKind(text), left, operator == null ? synthetic(text) : token(operator), right); + ascribe(tree, binaryType(text, left.symbolType(), right.symbolType())); + return tree; + } + + private ExpressionTree unary(Node node, boolean prefix) { + Node operator = field(node, "operator"); + Node operand = field(node, "operand"); + if (operand == null) { + operand = firstNamed(node); + } + String text = operator == null ? "!" : operator.text(); + ExpressionTree value = expression(operand); + InternalPrefixUnaryExpression tree = new InternalPrefixUnaryExpression(unaryKind(text, true), operator == null ? synthetic(text) : token(operator), value); + ascribe(tree, "!".equals(text) ? sema.getClassType("boolean") : value.symbolType()); + return tree; + } + + private ExpressionTree update(Node node) { + Node operator = field(node, "operator"); + List children = node.children(); + if (operator == null) { + operator = children.stream().filter(child -> "++".equals(child.text()) || "--".equals(child.text())).findFirst().orElse(null); + } + boolean prefix = !children.isEmpty() && ("++".equals(children.get(0).text()) || "--".equals(children.get(0).text())); + ExpressionTree operand = expression(firstNamed(node)); + String text = operator == null ? "++" : operator.text(); + InternalSyntaxToken token = operator == null ? synthetic(text) : token(operator); + ExpressionTree tree = prefix + ? new InternalPrefixUnaryExpression(unaryKind(text, true), token, operand) + : new InternalPostfixUnaryExpression(unaryKind(text, false), operand, token); + ascribe((AbstractTypedTree) tree, operand.symbolType()); + return tree; + } + + private ParenthesizedTreeImpl parenthesized(Node node) { + Parens parens = parens(node); + ParenthesizedTreeImpl tree = new ParenthesizedTreeImpl(parens.open, parens.expr, parens.close); + ascribe(tree, parens.expr.symbolType()); + return tree; + } + + private ConditionalExpressionTreeImpl ternary(Node node) { + ExpressionTree condition = expression(requiredField(node, "condition", firstNamed(node))); + ExpressionTree then = expression(requiredField(node, "consequence", firstNamed(node))); + ExpressionTree otherwise = expression(requiredField(node, "alternative", lastNamed(node))); + ConditionalExpressionTreeImpl tree = new ConditionalExpressionTreeImpl(condition, token(required(node, "?")), then, token(required(node, ":")), otherwise); + ascribe(tree, then.symbolType().isUnknown() ? otherwise.symbolType() : then.symbolType()); + return tree; + } + + private TypeCastExpressionTreeImpl cast(Node node) { + TypeTree type = type(requiredField(node, "type", firstType(node))); + ExpressionTree value = expression(requiredField(node, "value", lastNamed(node))); + TypeCastExpressionTreeImpl tree = new TypeCastExpressionTreeImpl(token(required(node, "(")), type, token(required(node, ")")), value); + ascribe(tree, type.symbolType()); + return tree; + } + + private InstanceOfTreeImpl instanceOf(Node node) { + ExpressionTree left = expression(requiredField(node, "left", firstNamed(node))); + Node name = field(node, "name"); + Node right = field(node, "right"); + Node patternNode = field(node, "pattern"); + InstanceOfTreeImpl tree; + if (patternNode != null) { + tree = new InstanceOfTreeImpl(left, token(required(node, "instanceof")), pattern(patternNode)); + } else if (name != null && right != null) { + VariableTreeImpl variable = new VariableTreeImpl(modifiers(node), type(right), identifier(name)); + bindDeclaration(variable, name, identifier(name)); + TypePatternTreeImpl pattern = new TypePatternTreeImpl(variable); + ascribe(pattern, variable.symbol().type().isUnknown() ? variable.type().symbolType() : variable.symbol().type()); + tree = new InstanceOfTreeImpl(left, token(required(node, "instanceof")), pattern); + } else { + tree = new InstanceOfTreeImpl(left, token(required(node, "instanceof")), type(right == null ? firstType(node) : right)); + } + ascribe(tree, sema.getClassType("boolean")); + return tree; + } + + private PatternTree pattern(Node node) { + if ("record_pattern".equals(node.k())) { + return recordPattern(node); + } + Node nested = firstOf(node, "record_pattern", "type_pattern", "pattern", "underscore_pattern"); + if (nested != null && !"type_pattern".equals(node.k())) { + return pattern(nested); + } + if ("underscore_pattern".equals(node.k())) { + return new TypePatternTreeImpl(new VariableTreeImpl(new IdentifierTreeImpl(token(node), true))); + } + Node name = lastOf(node, "identifier"); + VariableTreeImpl variable = new VariableTreeImpl(modifiers(node), type(firstType(node)), identifier(name == null ? node : name)); + bindDeclaration(variable, name, (IdentifierTreeImpl) variable.simpleName()); + if (variable.symbol().type() != null && !variable.symbol().type().isUnknown()) { + ascribe(variable.type(), variable.symbol().type()); + } + TypePatternTreeImpl tree = new TypePatternTreeImpl(variable); + ascribe(tree, variable.symbol().type().isUnknown() ? variable.type().symbolType() : variable.symbol().type()); + return tree; + } + + private RecordPatternTreeImpl recordPattern(Node node) { + Node body = required(node, "record_pattern_body"); + List patterns = new ArrayList<>(); + for (Node child : body.children()) { + if ("record_pattern_component".equals(child.k()) || "record_pattern".equals(child.k())) { + patterns.add(pattern(child)); + } + } + return new RecordPatternTreeImpl(type(firstType(node)), token(required(body, "(")), patterns, token(required(body, ")"))); + } + + private LambdaExpressionTreeImpl lambda(Node node) { + scopes.addFirst(new HashMap<>()); + returnTargets.push(Type.UNKNOWN); + try { + return lambdaInScope(node); + } finally { + scopes.removeFirst(); + returnTargets.pop(); + } + } + + private LambdaExpressionTreeImpl lambdaInScope(Node node) { + List parameters = new ArrayList<>(); + Node formals = field(node, "parameters"); + InternalSyntaxToken open = null; + InternalSyntaxToken close = null; + if (formals != null && ("formal_parameters".equals(formals.k()) || "inferred_parameters".equals(formals.k()))) { + open = optionalToken(formals, "("); + close = optionalToken(formals, ")"); + if ("formal_parameters".equals(formals.k())) { + parameters.addAll(parameters(formals)); + } else { + VariableTreeImpl previous = null; + for (Node child : formals.children()) { + if ("identifier".equals(child.k())) { + IdentifierTreeImpl name = identifier(child); + previous = new VariableTreeImpl(name); + bindDeclaration(previous, child, name); + parameters.add(previous); + } else if (",".equals(child.text()) && previous != null) { + previous.setEndToken(token(child)); + } + } + } + } else if (formals != null) { + IdentifierTreeImpl name = identifier(formals); + VariableTreeImpl parameter = new VariableTreeImpl(name); + bindDeclaration(parameter, formals, name); + parameters.add(parameter); + } + Node body = field(node, "body"); + Tree lambdaBody = body == null ? block(node) : ("block".equals(body.k()) ? block(body) : expression(body)); + LambdaExpressionTreeImpl tree = new LambdaExpressionTreeImpl(open, parameters, close, token(required(node, "->")), lambdaBody); + for (VariableTree parameter : parameters) { + if (parameter.symbol().owner() instanceof JMethodSymbol method) { + method.lambda(true); + tree.methodSymbol = method; + break; + } + } + return tree; + } + + private MethodReferenceTreeImpl methodReference(Node node) { + ExpressionTree qualifier = expression(requiredField(node, "type", node.children().get(0))); + Node nameNode = field(node, "name"); + if (nameNode == null) { + List children = node.children(); + Node last = children.isEmpty() ? null : children.get(children.size() - 1); + nameNode = last != null && "new".equals(last.text()) ? last : lastOf(node, "identifier"); + } + IdentifierTreeImpl name = identifier(nameNode == null ? node : nameNode); + bindUse(name, nameNode == null ? node : nameNode, true); + MethodReferenceTreeImpl tree = new MethodReferenceTreeImpl(qualifier, token(required(node, "::"))); + Node typeArguments = field(node, "type_arguments"); + tree.complete(typeArguments == null ? null : typeArguments(typeArguments), name); + ascribe(tree, Type.UNKNOWN); + return tree; + } + + private TypeTree type(Node node) { + return switch (node.k()) { + case "integral_type", "floating_point_type", "boolean_type", "void_type" -> primitive(node); + case "generic_type" -> generic(node); + case "array_type" -> arrayType(type(requiredField(node, "element", firstType(node))), field(node, "dimensions")); + case "scoped_identifier", "scoped_type_identifier", "field_access" -> memberSelect(node); + case "wildcard" -> wildcard(node); + case "annotated_type" -> annotatedType(node); + case "type_identifier" -> "var".equals(node.text()) ? new VarTypeTreeImpl(token(node)) : (TypeTree) primary(node); + default -> { + ExpressionTree expression = "identifier".equals(node.k()) || "type_identifier".equals(node.k()) ? primary(node) : expression(node); + yield expression instanceof TypeTree typeTree ? typeTree : new VarTypeTreeImpl(synthetic("var")); + } + }; + } + + private TypeTree annotatedType(Node node) { + return annotate(type(firstType(node)), node); + } + + private TypeTree annotate(TypeTree type, Node node) { + List annotations = node.children().stream().filter(SemsitterTranslator::isAnnotation).map(this::annotation).toList(); + if (!annotations.isEmpty() && type instanceof JavaTree.AnnotatedTypeTree annotated) { + annotated.complete(annotations); + } + return type; + } + + private JavaTree.PrimitiveTypeTreeImpl primitive(Node node) { + Node keyword = firstTokenChild(node); + String text = keyword == null ? node.text() : keyword.text(); + if (text.isEmpty()) { + text = node.k().replace("_type", ""); + } + JavaTree.PrimitiveTypeTreeImpl tree = new JavaTree.PrimitiveTypeTreeImpl(keyword == null ? token(node) : token(keyword)); + tree.complete(List.of()); + ascribe(tree, sema.getClassType(text)); + return tree; + } + + private JavaTree.ParameterizedTypeTreeImpl generic(Node node) { + TypeTree raw = type(firstType(node)); + Node arguments = field(node, "type_arguments"); + if (arguments == null) { + arguments = childKind(node, "type_arguments"); + } + JavaTree.ParameterizedTypeTreeImpl tree = new JavaTree.ParameterizedTypeTreeImpl(raw, arguments == null ? new TypeArgumentListTreeImpl(synthetic("<"), synthetic(">")) : typeArguments(arguments)); + List args = new ArrayList<>(); + for (TypeTree argument : tree.typeArguments()) { + args.add(argument instanceof WildcardTree wildcard ? wildcardType(wildcard) : argument.symbolType()); + } + if (raw.symbolType() instanceof JType) { + List typeArguments = new ArrayList<>(); + for (Type argument : args) { + typeArguments.add(argument instanceof JType jType ? jType : JType.unknown(sema)); + } + JType parameterized = JType.named(sema, raw.symbolType().erasure().fullyQualifiedName(), typeArguments); + ascribe(tree, parameterized); + if (raw instanceof AbstractTypedTree typed) { + ascribe(typed, parameterized); + } + if (raw instanceof IdentifierTreeImpl identifier) { + identifier.symbol = parameterized.symbol(); + } + } + return tree; + } + + private TypeTree arrayType(TypeTree element, @Nullable Node dimensions) { + if (dimensions == null) { + return element; + } + TypeTree current = element; + List annotations = new ArrayList<>(); + List children = dimensions.children(); + for (int i = 0; i < children.size(); i++) { + if ("[".equals(children.get(i).text())) { + InternalSyntaxToken close = i + 1 < children.size() ? token(children.get(i + 1)) : synthetic("]"); + JType component = current.symbolType() instanceof JType jType ? jType : JType.unknown(sema); + current = new JavaTree.ArrayTypeTreeImpl(current, annotations, token(children.get(i)), close); + annotations = new ArrayList<>(); + ascribe((AbstractTypedTree) current, JType.array(sema, component)); + } else if (isAnnotation(children.get(i))) { + annotations.add(annotation(children.get(i))); + } + } + return current; + } + + private Type wildcardType(WildcardTree wildcard) { + if (wildcard.is(Tree.Kind.SUPER_WILDCARD) && wildcard.bound() != null && wildcard.bound().symbolType() instanceof JType lower) { + return JType.superWildcard(sema, lower); + } + Type upper = wildcard.is(Tree.Kind.EXTENDS_WILDCARD) && wildcard.bound() != null ? wildcard.bound().symbolType() : sema.getClassType("java.lang.Object"); + return upper instanceof JType bound ? JType.wildcard(sema, bound) : Type.UNKNOWN; + } + + private JavaTree.WildcardTreeImpl wildcard(Node node) { + Node bound = firstType(node); + if (bound == null || same(bound, keyword(node, "?"))) { + JavaTree.WildcardTreeImpl tree = new JavaTree.WildcardTreeImpl(token(required(node, "?"))); + tree.complete(List.of()); + return tree; + } + boolean upper = keyword(node, "extends") != null; + JavaTree.WildcardTreeImpl tree = new JavaTree.WildcardTreeImpl( + upper ? Tree.Kind.EXTENDS_WILDCARD : Tree.Kind.SUPER_WILDCARD, + token(required(node, upper ? "extends" : "super")), + type(bound)).complete(token(required(node, "?"))); + tree.complete(List.of()); + return tree; + } + + private TypeArgumentListTreeImpl typeArguments(Node node) { + TypeArgumentListTreeImpl list = new TypeArgumentListTreeImpl(token(required(node, "<")), closingAngle(node)); + for (Node child : node.children()) { + if ("<".equals(child.text()) || ">".equals(child.text()) || ">>".equals(child.text()) || ">>>".equals(child.text())) { + continue; + } + if (",".equals(child.text())) { + list.separators().add(token(child)); + } else { + list.add(type(child)); + } + } + return list; + } + + private TypeParameterListTreeImpl typeParameters(Node node) { + TypeParameterListTreeImpl list = new TypeParameterListTreeImpl(token(required(node, "<")), closingAngle(node)); + for (Node child : node.children()) { + if ("<".equals(child.text()) || child.text().startsWith(">")) { + continue; + } + if (",".equals(child.text())) { + list.separators().add(token(child)); + } else if ("type_parameter".equals(child.k())) { + list.add(typeParameter(child)); + } + } + return list; + } + + private TypeParameterTree typeParameter(Node node) { + Node nameNode = firstOf(node, "type_identifier", "identifier"); + IdentifierTreeImpl name = identifier(nameNode); + bindUse(name, nameNode); + Node bound = childKind(node, "type_bound"); + TypeParameterTreeImpl tree; + if (bound == null) { + tree = new TypeParameterTreeImpl(name); + } else { + tree = new TypeParameterTreeImpl(name, token(required(bound, "extends")), separatedTypes(bound)); + } + if (name.symbol().isUnknown()) { + bindDeclaration(tree, nameNode, name); + } + if (name.symbol instanceof Symbol.TypeSymbol typeSymbol) { + tree.typeSymbol = typeSymbol; + } + return tree; + } + + private TypeTree catchType(Node node) { + int alternatives = 0; + for (Node child : node.children()) { + if (isTypeKind(child.k())) { + alternatives++; + } + } + if (alternatives <= 1) { + return type(firstType(node)); + } + QualifiedIdentifierListTreeImpl list = QualifiedIdentifierListTreeImpl.emptyList(); + for (Node child : node.children()) { + if (isTypeKind(child.k())) { + list.add(type(child)); + } else if ("|".equals(child.text())) { + list.separators().add(token(child)); + } + } + return new JavaTree.UnionTypeTreeImpl(list); + } + + private QualifiedIdentifierListTreeImpl separatedTypes(Node node) { + QualifiedIdentifierListTreeImpl list = QualifiedIdentifierListTreeImpl.emptyList(); + addSeparatedTypes(list, node); + return list; + } + + private void addSeparatedTypes(QualifiedIdentifierListTreeImpl list, Node node) { + for (Node child : node.children()) { + if ("type_list".equals(child.k())) { + addSeparatedTypes(list, child); + } else if (isTypeKind(child.k())) { + list.add(type(child)); + } else if (",".equals(child.text()) || "&".equals(child.text())) { + list.separators().add(token(child)); + } + } + } + + private ArgumentListTreeImpl arguments(Node node) { + if (node == null) { + return ArgumentListTreeImpl.emptyList(); + } + ArgumentListTreeImpl list = ArgumentListTreeImpl.emptyList(); + InternalSyntaxToken open = null; + InternalSyntaxToken close = null; + for (Node child : node.children()) { + if ("(".equals(child.text())) { + open = token(child); + } else if (")".equals(child.text())) { + close = token(child); + } else if (",".equals(child.text())) { + list.separators().add(token(child)); + } else { + list.add(expression(child)); + } + } + return list.complete(open, close); + } + + private List dimensions(Node node) { + List dims = new ArrayList<>(); + List annotations = new ArrayList<>(); + ExpressionTree index = null; + InternalSyntaxToken open = null; + for (Node child : node.children()) { + if ("[".equals(child.text())) { + open = token(child); + index = null; + } else if ("]".equals(child.text()) && open != null) { + dims.add(new ArrayDimensionTreeImpl(open, index, token(child)).completeAnnotations(annotations)); + annotations = new ArrayList<>(); + open = null; + } else if (open != null) { + index = expression(child); + } else if (isAnnotation(child)) { + annotations.add(annotation(child)); + } + } + return dims; + } + + private ModifiersTreeImpl modifiers(Node node) { + Node modifiers = childKind(node, "modifiers"); + List trees = new ArrayList<>(); + if (modifiers != null) { + for (Node child : modifiers.children()) { + ModifierTree modifier = modifier(child); + if (modifier != null) { + trees.add(modifier); + } + } + } + return new ModifiersTreeImpl(trees); + } + + @Nullable + private ModifierTree modifier(Node node) { + if (isAnnotation(node)) { + return annotation(node); + } + Modifier modifier = modifierKeyword(node.text()); + return modifier == null ? null : new ModifierKeywordTreeImpl(modifier, token(node)); + } + + private AnnotationTreeImpl annotation(Node node) { + Node name = field(node, "name"); + TypeTree type = name == null ? (TypeTree) primary(firstOf(node, "identifier", "scoped_identifier", "type_identifier")) : (TypeTree) expression(name); + if (type instanceof IdentifierTreeImpl identifier && !naming) { + recoverType(identifier); + } + ArgumentListTreeImpl arguments = ArgumentListTreeImpl.emptyList(); + Node args = field(node, "arguments"); + if (args != null) { + arguments = arguments(args); + } + AnnotationTreeImpl tree = new AnnotationTreeImpl(token(required(node, "@")), type, arguments); + ascribe(tree, annotationType(type)); + for (ExpressionTree argument : arguments) { + if (argument instanceof AssignmentExpressionTree assignment && assignment.variable() instanceof IdentifierTreeImpl element && element.symbol().isUnknown() + && type.symbolType().isClass()) { + Symbol.MethodSymbol method = sema.declaredMethod(type.symbolType().fullyQualifiedName(), element.name(), List.of()); + if (method != null) { + element.symbol = method; + ascribe(element, method.returnType().type()); + } + } + } + return tree; + } + + private List annotations(Node node) { + Node modifiers = childKind(node, "modifiers"); + List annotations = new ArrayList<>(); + for (Node child : (modifiers == null ? node : modifiers).children()) { + if (isAnnotation(child)) { + annotations.add(annotation(child)); + } + } + return annotations; + } + + private void bindDeclaration(Tree tree, @Nullable Node name, IdentifierTreeImpl identifier) { + if (name == null) { + return; + } + String id = name.decl(); + if (id == null) { + return; + } + if (id.contains("#(")) { + id = name.decls().stream().filter(extra -> !extra.contains("(")).findFirst().orElse(id); + } + Symbol symbol = sema.resolve(id); + sema.declare(id, tree); + for (String extra : name.decls()) { + if (!extra.equals(id)) { + sema.declare(extra, tree); + } + } + // The scan records no modifiers for locals and parameters. + if (tree instanceof VariableTreeImpl declared && symbol instanceof JSymbol declaredSymbol && symbol.isVariableSymbol() + && ModifiersUtils.hasModifier(declared.modifiers(), Modifier.FINAL)) { + declaredSymbol.addModifiers(FINAL_MODIFIER); + } + if (tree instanceof VariableTreeImpl variable && symbol instanceof JSymbol variableSymbol && symbol.isVariableSymbol() && variable.type() != null) { + Type declared = variable.type().symbolType(); + if (symbol.type().isUnknown() ? isKnownOrRecovered(declared) : declared.isParameterized() && sameErasure(declared, symbol.type())) { + variableSymbol.type(declared); + } + } + if (tree instanceof MethodTreeImpl method && symbol instanceof JMethodSymbol methodSymbol && method.returnType() != null + && methodSymbol.returnType().type().isUnknown() && isKnownOrRecovered(method.returnType().symbolType())) { + methodSymbol.returnTypeSymbol(method.returnType().symbolType().symbol()); + } + identifier.symbol = symbol; + ascribe(identifier, identifierType(symbol)); + if (!scopes.isEmpty() && symbol.isVariableSymbol() && symbol.owner() != null && symbol.owner().isMethodSymbol()) { + scopes.peekFirst().put(symbol.name(), symbol); + } + if (tree instanceof ClassTreeImpl classTree && symbol instanceof Symbol.TypeSymbol typeSymbol) { + classTree.typeSymbol = typeSymbol; + if (symbol instanceof JSymbol nested && ModifiersUtils.hasModifier(classTree.modifiers(), Modifier.STATIC) && !nested.isStatic()) { + nested.addModifiers(List.of("static")); + } + } else if (tree instanceof MethodTreeImpl method && symbol instanceof Symbol.MethodSymbol methodSymbol) { + method.methodSymbol = methodSymbol; + if (methodSymbol instanceof JMethodSymbol declared) { + declaredParameterTypes(declared, method.parameters()); + } + } else if (tree instanceof VariableTreeImpl variable && symbol instanceof Symbol.VariableSymbol variableSymbol) { + variable.variableSymbol = variableSymbol; + } + } + + /** + * The scan records erased return types, so the type arguments of each declared return type are read from the + * syntax before any body is translated: invocations may precede the declaration they call. + */ + private void declareGenericReturns(Node node) { + if ("method_declaration".equals(node.k()) && field(node, "dimensions") == null) { + Node typeNode = field(node, "type"); + Node name = nameNode(node); + String id = name == null ? null : name.decl(); + if (typeNode != null && id != null && sema.resolve(id) instanceof JMethodSymbol method) { + Type declared = syntacticType(typeNode); + if (declared.isParameterized() && !mentionsUnknown(declared) && sameErasure(declared, method.returnType().type())) { + method.returnTypeSymbol(declared.symbol()); + } + } + } + for (Node child : node.children()) { + declareGenericReturns(child); + } + } + + /** The type a type node denotes, from the references the scan bound, without translating it. */ + private Type syntacticType(Node node) { + switch (node.k()) { + case "type_identifier", "scoped_type_identifier" -> { + String target = node.typeTarget(); + return target != null && sema.resolve(target) instanceof Symbol.TypeSymbol type && !type.isUnknown() ? type.type() : Type.UNKNOWN; + } + case "generic_type" -> { + Node rawNode = firstType(node); + Type raw = rawNode == null ? Type.UNKNOWN : syntacticType(rawNode); + Node arguments = childKind(node, "type_arguments"); + if (raw.isUnknown() || arguments == null) { + return raw; + } + List types = new ArrayList<>(); + for (Node argument : arguments.children()) { + if (isTypeKind(argument.k())) { + types.add(syntacticType(argument) instanceof JType type ? type : JType.unknown(sema)); + } + } + return JType.named(sema, raw.erasure().fullyQualifiedName(), types); + } + case "wildcard" -> { + Node bound = firstType(node); + if (bound == null) { + return JType.wildcard(sema, (JType) sema.getClassType("java.lang.Object")); + } + JType type = syntacticType(bound) instanceof JType known ? known : JType.unknown(sema); + return keyword(node, "super") != null ? JType.superWildcard(sema, type) : JType.wildcard(sema, type); + } + default -> { + return Type.UNKNOWN; + } + } + } + + private static boolean mentionsUnknown(Type type) { + return type.isUnknown() || (type.isParameterized() && type.typeArguments().stream().anyMatch(SemsitterTranslator::mentionsUnknown)); + } + + private static boolean sameErasure(Type left, Type right) { + return left.erasure().fullyQualifiedName().equals(right.erasure().fullyQualifiedName()); + } + + /** The scan records erased parameter types, while the declared parameters keep their type arguments. */ + private static void declaredParameterTypes(JMethodSymbol method, List parameters) { + List erased = method.parameterTypes(); + if (erased.size() != parameters.size()) { + return; + } + List declared = new ArrayList<>(); + for (int i = 0; i < parameters.size(); i++) { + Type type = parameters.get(i).type().symbolType(); + if (type.isUnknown() || !sameErasure(type, erased.get(i))) { + return; + } + declared.add(type); + } + method.parameterTypes(declared); + } + + private void bindImplicitClass(ClassTreeImpl tree, List members) { + JTypeSymbol implicitClass = null; + for (Tree member : members) { + if (member instanceof MethodTree method && method.symbol().owner() instanceof JTypeSymbol owner && !owner.isUnknown()) { + implicitClass = owner; + break; + } + } + if (implicitClass == null && !implicitClassName.isEmpty() && sema.resolve(implicitClassName) instanceof JTypeSymbol named) { + implicitClass = named; + } + if (implicitClass == null) { + return; + } + tree.typeSymbol = implicitClass; + sema.declare(implicitClass.id, tree); + for (Tree member : members) { + if (member instanceof MethodTree method && method.symbol() instanceof JMethodSymbol symbol && (symbol.owner() == null || symbol.owner().isUnknown())) { + symbol.owner(implicitClass); + } + } + } + + private void bindType(ClassTreeImpl tree, Node node) { + String id = node.decl(); + if (id != null && sema.resolve(id) instanceof Symbol.TypeSymbol typeSymbol) { + tree.typeSymbol = typeSymbol; + sema.declare(id, tree); + return; + } + for (Tree member : tree.members()) { + Symbol symbol = member instanceof MethodTree method ? method.symbol() : (member instanceof VariableTree variable ? variable.symbol() : null); + if (symbol != null && !symbol.isUnknown() && symbol.owner() instanceof Symbol.TypeSymbol owner) { + tree.typeSymbol = owner; + return; + } + } + } + + private void bindUse(IdentifierTreeImpl identifier, Node node) { + bindUse(identifier, node, false); + } + + private void bindUse(IdentifierTreeImpl identifier, Node node, boolean qualified) { + String target = node.valueTarget(); + if (target == null) { + target = node.typeTarget(); + } + if (target == null) { + if (!qualified) { + Symbol local = scopedVariable(identifier.name()); + if (local != null) { + identifier.symbol = local; + ascribe(identifier, identifierType(local)); + sema.use(usageId(local, local.name()), identifier); + } + } + return; + } + Symbol symbol = sema.resolve(target); + String typeTarget = node.typeTarget(); + if (symbol.isMethodSymbol() && "".equals(symbol.name()) && typeTarget != null && !typeTarget.equals(target)) { + target = typeTarget; + symbol = sema.resolve(target); + } + if (symbol.isMethodSymbol() && symbol.isUnknown()) { + symbol = Symbol.MethodSymbol.UNKNOWN_METHOD; + } + symbol = typedField(symbol); + if (!qualified && (symbol.isUnknown() || symbol.isVariableSymbol())) { + Symbol local = scopedVariable(identifier.name()); + if (local != null) { + symbol = local; + } + } + identifier.symbol = symbol; + ascribe(identifier, identifierType(symbol)); + sema.use(usageId(symbol, target), identifier); + } + + @Nullable + private Symbol scopedVariable(String name) { + for (Map scope : scopes) { + Symbol found = scope.get(name); + if (found != null) { + return found; + } + } + return null; + } + + /** A field the graph typed as void keeps the type its class file declares. */ + private Symbol typedField(Symbol symbol) { + if (!(symbol instanceof JSymbol field) || !field.isVariableSymbol() || field.type() == null || !field.type().isVoid()) { + return symbol; + } + String owner = field.owner().type() != null && !field.owner().type().isUnknown() + ? field.owner().type().erasure().fullyQualifiedName() + : (field.id != null && field.id.indexOf('#') > 0 ? field.id.substring(0, field.id.indexOf('#')) : null); + if (owner == null) { + return symbol; + } + Symbol.VariableSymbol declared = sema.declaredField(JSema.erase(owner), field.name()); + if (declared != null && declared.type() != null && !declared.type().isVoid() && !declared.type().isUnknown()) { + return declared; + } + return symbol; + } + + /** Usages are stored under the symbol's own id, which can differ from the fact key that resolved it. */ + private static String usageId(Symbol symbol, String target) { + if (symbol instanceof JSymbol jSymbol && !jSymbol.isUnknown()) { + String id = jSymbol.toString(); + if (id != null && !id.equals(jSymbol.name())) { + return id; + } + } + return target; + } + + private void recordUse(Symbol symbol, IdentifierTree identifier) { + String id = usageId(symbol, ""); + if (!id.isEmpty()) { + sema.use(id, identifier); + } + } + + @Nullable + private IdentifierTreeImpl labelUse(Node node) { + Node name = firstOf(node, "identifier"); + if (name == null || "break".equals(name.text()) || "continue".equals(name.text())) { + return null; + } + IdentifierTreeImpl identifier = identifier(name); + for (JLabelSymbol label : labels) { + if (label.name().equals(identifier.name())) { + label.usages.add(identifier); + identifier.labelSymbol = label; + } + } + return identifier; + } + + private Parens parens(@Nullable Node node) { + if (node == null) { + return new Parens(synthetic("("), literal(Tree.Kind.BOOLEAN_LITERAL, synthetic("true"), sema.getClassType("boolean")), synthetic(")")); + } + if (!"parenthesized_expression".equals(node.k())) { + return new Parens(synthetic("("), expression(node), synthetic(")")); + } + InternalSyntaxToken open = token(required(node, "(")); + InternalSyntaxToken close = token(required(node, ")")); + ExpressionTree expression = null; + for (Node child : node.children()) { + if (!"(".equals(child.text()) && !")".equals(child.text())) { + expression = expression(child); + } + } + return new Parens(open, expression == null ? literal(Tree.Kind.BOOLEAN_LITERAL, synthetic("true"), sema.getClassType("boolean")) : expression, close); + } + + @Nullable + private Type currentType() { + Symbol.TypeSymbol enclosing = types.isEmpty() ? null : types.peek(); + return enclosing == null ? Type.UNKNOWN : enclosing.type(); + } + + private Type arrayTypeOf(TypeTree element, int dimensions) { + Type type = element.symbolType(); + if (!(type instanceof JType jType)) { + return Type.UNKNOWN; + } + for (int i = 0; i < Math.max(dimensions, 1); i++) { + jType = JType.array(sema, jType); + } + return jType; + } + + private static Type binaryType(String operator, Type left, Type right) { + if (operator.equals("&&") || operator.equals("||") || operator.equals("==") || operator.equals("!=") + || operator.equals("<") || operator.equals(">") || operator.equals("<=") || operator.equals(">=") || operator.equals("instanceof")) { + return left instanceof JType j ? j.sema.getClassType("boolean") : Type.UNKNOWN; + } + if ("+".equals(operator) && (left.is("java.lang.String") || right.is("java.lang.String"))) { + return left instanceof JType j ? j.sema.getClassType("java.lang.String") : Type.UNKNOWN; + } + if (left.isUnknown()) { + return right; + } + return left; + } + + private static Tree.Kind binaryKind(String operator) { + return switch (operator) { + case "+" -> Tree.Kind.PLUS; + case "-" -> Tree.Kind.MINUS; + case "*" -> Tree.Kind.MULTIPLY; + case "/" -> Tree.Kind.DIVIDE; + case "%" -> Tree.Kind.REMAINDER; + case "<<" -> Tree.Kind.LEFT_SHIFT; + case ">>" -> Tree.Kind.RIGHT_SHIFT; + case ">>>" -> Tree.Kind.UNSIGNED_RIGHT_SHIFT; + case "<" -> Tree.Kind.LESS_THAN; + case ">" -> Tree.Kind.GREATER_THAN; + case "<=" -> Tree.Kind.LESS_THAN_OR_EQUAL_TO; + case ">=" -> Tree.Kind.GREATER_THAN_OR_EQUAL_TO; + case "==" -> Tree.Kind.EQUAL_TO; + case "!=" -> Tree.Kind.NOT_EQUAL_TO; + case "&" -> Tree.Kind.AND; + case "|" -> Tree.Kind.OR; + case "^" -> Tree.Kind.XOR; + case "&&" -> Tree.Kind.CONDITIONAL_AND; + case "||" -> Tree.Kind.CONDITIONAL_OR; + default -> Tree.Kind.PLUS; + }; + } + + private static Tree.Kind assignmentKind(String operator) { + return switch (operator) { + case "+=" -> Tree.Kind.PLUS_ASSIGNMENT; + case "-=" -> Tree.Kind.MINUS_ASSIGNMENT; + case "*=" -> Tree.Kind.MULTIPLY_ASSIGNMENT; + case "/=" -> Tree.Kind.DIVIDE_ASSIGNMENT; + case "%=" -> Tree.Kind.REMAINDER_ASSIGNMENT; + case "&=" -> Tree.Kind.AND_ASSIGNMENT; + case "|=" -> Tree.Kind.OR_ASSIGNMENT; + case "^=" -> Tree.Kind.XOR_ASSIGNMENT; + case "<<=" -> Tree.Kind.LEFT_SHIFT_ASSIGNMENT; + case ">>=" -> Tree.Kind.RIGHT_SHIFT_ASSIGNMENT; + case ">>>=" -> Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT; + default -> Tree.Kind.ASSIGNMENT; + }; + } + + private static Tree.Kind unaryKind(String operator, boolean prefix) { + return switch (operator) { + case "++" -> prefix ? Tree.Kind.PREFIX_INCREMENT : Tree.Kind.POSTFIX_INCREMENT; + case "--" -> prefix ? Tree.Kind.PREFIX_DECREMENT : Tree.Kind.POSTFIX_DECREMENT; + case "-" -> Tree.Kind.UNARY_MINUS; + case "+" -> Tree.Kind.UNARY_PLUS; + case "~" -> Tree.Kind.BITWISE_COMPLEMENT; + default -> Tree.Kind.LOGICAL_COMPLEMENT; + }; + } + + @Nullable + private static Modifier modifierKeyword(String text) { + return switch (text) { + case "public" -> Modifier.PUBLIC; + case "protected" -> Modifier.PROTECTED; + case "private" -> Modifier.PRIVATE; + case "static" -> Modifier.STATIC; + case "abstract" -> Modifier.ABSTRACT; + case "final" -> Modifier.FINAL; + case "native" -> Modifier.NATIVE; + case "synchronized" -> Modifier.SYNCHRONIZED; + case "transient" -> Modifier.TRANSIENT; + case "volatile" -> Modifier.VOLATILE; + case "strictfp" -> Modifier.STRICTFP; + case "default" -> Modifier.DEFAULT; + case "sealed" -> Modifier.SEALED; + case "non-sealed" -> Modifier.NON_SEALED; + case "transitive" -> Modifier.TRANSITIVE; + default -> null; + }; + } + + private static boolean isTypeKind(String kind) { + return switch (kind) { + case "type_identifier", "identifier", "scoped_identifier", "scoped_type_identifier", "generic_type", "array_type", "integral_type", "floating_point_type", "boolean_type", "void_type", "wildcard", "annotated_type" -> true; + default -> false; + }; + } + + private static boolean isStatementKind(String kind) { + return kind.endsWith("_statement") || "block".equals(kind) || "local_variable_declaration".equals(kind) || "switch_expression".equals(kind); + } + + private static boolean isLiteral(String kind) { + return switch (kind) { + case "string_literal", "character_literal", "text_block", "decimal_integer_literal", "hex_integer_literal", "octal_integer_literal", "binary_integer_literal", "decimal_floating_point_literal", "hex_floating_point_literal" -> true; + default -> false; + }; + } + + private static boolean isComment(Node node) { + return "line_comment".equals(node.k()) || "block_comment".equals(node.k()); + } + + private static SyntaxTrivia trivia(Node node) { + String text = node.text(); + if (text.startsWith("//") && text.endsWith("\r")) { + text = text.substring(0, text.length() - 1); + } + SyntaxTrivia.CommentKind kind; + if (text.startsWith("///")) { + kind = SyntaxTrivia.CommentKind.MARKDOWN; + } else if (text.startsWith("/**") && !"/**/".equals(text)) { + kind = SyntaxTrivia.CommentKind.JAVADOC; + } else if (text.startsWith("//")) { + kind = SyntaxTrivia.CommentKind.LINE; + } else { + kind = SyntaxTrivia.CommentKind.BLOCK; + } + return new InternalSyntaxTrivia(kind, text, node.line(), node.col()); + } + + private InternalSyntaxToken token(Node node) { + InternalSyntaxToken found = tokens.get(node.json); + if (found != null) { + return found; + } + if ("@interface".equals(node.text())) { + return new InternalSyntaxToken(node.line(), node.col(), "@", List.of(), false); + } + if (!node.children().isEmpty()) { + for (Node child : node.children()) { + if (child.children().isEmpty() && !child.text().isEmpty() && !isComment(child)) { + return token(child); + } + } + } + String text = node.text().isEmpty() ? node.k() : node.text(); + if (text.indexOf('_') >= 0) { + text = ""; + } + return new InternalSyntaxToken(node.line(), node.col(), text, List.of(), false); + } + + private InternalSyntaxToken closingAngle(Node node) { + for (int i = node.children().size() - 1; i >= 0; i--) { + Node child = node.children().get(i); + if (">".equals(child.text()) || ">>".equals(child.text()) || ">>>".equals(child.text())) { + return new InternalSyntaxToken(child.line(), child.col() + child.text().length() - 1, ">", List.of(), false); + } + } + return synthetic(">"); + } + + private InternalSyntaxToken synthetic(String text) { + return new InternalSyntaxToken(1, 0, text, List.of(), false); + } + + @Nullable + private InternalSyntaxToken optionalToken(Node node, String text) { + Node found = keyword(node, text); + return found == null ? null : token(found); + } + + private static Type identifierType(Symbol symbol) { + return symbol instanceof Symbol.MethodSymbol method && !method.isUnknown() ? method.returnType().type() : symbol.type(); + } + + /** A resolved type, or an unresolved one that still carries its written name. */ + private static boolean isKnownOrRecovered(Type type) { + return !type.isUnknown() || !"!Unknown!".equals(type.fullyQualifiedName()); + } + + private static void ascribe(Tree tree, Type type) { + if (tree instanceof AbstractTypedTree typed && type != null) { + typed.type = type; + } else if (tree instanceof AbstractPatternTree pattern && type != null) { + pattern.type = type; + } + } + + @Nullable + private static Node keyword(Node node, String text) { + for (Node child : node.children()) { + if (text.equals(child.text()) || text.equals(child.k())) { + return child; + } + } + return null; + } + + private static Node lastKeyword(Node node, String text) { + List children = node.children(); + for (int i = children.size() - 1; i >= 0; i--) { + if (text.equals(children.get(i).text())) { + return children.get(i); + } + } + return node; + } + + private static boolean same(@Nullable Node left, @Nullable Node right) { + return left != null && right != null && left.json == right.json; + } + + private static Node required(Node node, String text) { + Node found = keyword(node, text); + return found == null ? node : found; + } + + @Nullable + private static Node field(Node node, String name) { + for (Node child : node.children()) { + if (name.equals(child.f())) { + return child; + } + } + return null; + } + + private static Node requiredField(Node node, String name, @Nullable Node fallback) { + Node found = field(node, name); + return found != null ? found : (fallback == null ? node : fallback); + } + + @Nullable + private static Node childKind(Node node, String kind) { + for (Node child : node.children()) { + if (kind.equals(child.k())) { + return child; + } + } + return null; + } + + @Nullable + private static Node firstOf(Node node, String... kinds) { + for (Node child : node.children()) { + for (String kind : kinds) { + if (kind.equals(child.k())) { + return child; + } + } + } + return null; + } + + @Nullable + private static Node lastOf(Node node, String kind) { + Node found = null; + for (Node child : node.children()) { + if (kind.equals(child.k())) { + found = child; + } + } + return found; + } + + @Nullable + private static Node nameNode(Node node) { + Node named = field(node, "name"); + return named != null ? named : firstOf(node, "identifier", "type_identifier"); + } + + @Nullable + private static Node firstNamed(Node node) { + for (Node child : node.children()) { + if (!child.text().equals(child.k()) || child.k().contains("_")) { + if (!child.children().isEmpty() || child.k().contains("_") || "identifier".equals(child.k()) || "type_identifier".equals(child.k())) { + return child; + } + } + } + return node.children().isEmpty() ? node : node.children().get(0); + } + + @Nullable + private static boolean isAnnotation(Node node) { + return "annotation".equals(node.k()) || "marker_annotation".equals(node.k()); + } + + private static Node lastNamed(Node node) { + List children = node.children(); + for (int i = children.size() - 1; i >= 0; i--) { + Node child = children.get(i); + if (child.k().contains("_") || "identifier".equals(child.k())) { + return child; + } + } + return node; + } + + @Nullable + private static Node firstType(Node node) { + for (Node child : node.children()) { + if (isTypeKind(child.k())) { + return child; + } + } + return null; + } + + @Nullable + private static Node firstTokenChild(Node node) { + for (Node child : node.children()) { + if (child.children().isEmpty() && !child.text().isEmpty()) { + return child; + } + } + return null; + } + + private static InternalSyntaxToken firstKeyword(Node node) { + for (Node child : node.children()) { + if (child.children().isEmpty() && !child.text().isEmpty() && !",".equals(child.text())) { + return new InternalSyntaxToken(child.line(), child.col(), child.text(), List.of(), false); + } + } + return new InternalSyntaxToken(node.line(), node.col(), "implements", List.of(), false); + } + + private static void setParents(Tree node) { + if (node instanceof JavaTree javaTree && node.kind() != Tree.Kind.INFERED_TYPE && node.kind() != Tree.Kind.TOKEN) { + for (Tree child : javaTree.getChildren()) { + if (child instanceof JavaTree javaChild) { + javaChild.setParent(node); + setParents(child); + } + } + } + } + + private record Parens(InternalSyntaxToken open, ExpressionTree expr, InternalSyntaxToken close) { + } + + private static final class Node { + private final JsonObject json; + + private Node(JsonObject json) { + this.json = json; + } + + private String k() { + return json.get("k").getAsString(); + } + + private String f() { + return json.has("f") ? json.get("f").getAsString() : ""; + } + + private String text() { + return json.has("t") ? json.get("t").getAsString() : ""; + } + + private boolean missing() { + return json.has("m") && json.get("m").getAsBoolean(); + } + + private int line() { + return json.has("p") ? json.getAsJsonArray("p").get(0).getAsInt() : 1; + } + + private int col() { + return json.has("p") ? json.getAsJsonArray("p").get(1).getAsInt() : 0; + } + + private int endCol() { + return json.has("p") ? json.getAsJsonArray("p").get(3).getAsInt() : col(); + } + + private List children() { + List children = new ArrayList<>(); + for (Node child : allChildren()) { + if (!isComment(child)) { + children.add(child); + } + } + return children; + } + + private List allChildren() { + if (!json.has("c")) { + return List.of(); + } + List children = new ArrayList<>(); + for (JsonElement element : json.getAsJsonArray("c")) { + Node child = new Node(element.getAsJsonObject()); + if (!child.missing()) { + children.add(child); + } + } + return children; + } + + @Nullable + private String decl() { + List decls = decls(); + return decls.isEmpty() ? null : decls.get(0); + } + + private List decls() { + if (!json.has("decls")) { + return List.of(); + } + List ids = new ArrayList<>(); + for (JsonElement element : json.getAsJsonArray("decls")) { + ids.add(element.getAsString()); + } + return ids; + } + + private boolean unresolved() { + if (!json.has("refs")) { + return "type_identifier".equals(k()); + } + for (JsonElement element : json.getAsJsonArray("refs")) { + if ("bound".equals(element.getAsJsonObject().get("outcome").getAsString())) { + return false; + } + } + return true; + } + + @Nullable + private String valueTarget() { + return target(true); + } + + @Nullable + private String typeTarget() { + return target(false); + } + + @Nullable + private String target(boolean value) { + if (!json.has("refs")) { + return null; + } + String fallback = null; + for (JsonElement element : json.getAsJsonArray("refs")) { + JsonObject ref = element.getAsJsonObject(); + if (!ref.has("target") || ref.get("target").isJsonNull() || !"bound".equals(ref.get("outcome").getAsString())) { + continue; + } + String access = ref.has("access") ? ref.get("access").getAsString() : ""; + boolean isValue = "r".equals(access) || "w".equals(access) || "rw".equals(access); + if (isValue == value) { + return ref.get("target").getAsString(); + } + fallback = ref.get("target").getAsString(); + } + return value ? null : fallback; + } + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/model/TypeParameterTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/TypeParameterTreeImpl.java index bbb9439bcd8..a9d4c470458 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/TypeParameterTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/TypeParameterTreeImpl.java @@ -39,7 +39,8 @@ public class TypeParameterTreeImpl extends JavaTree implements TypeParameterTree private final SyntaxToken extendsToken; private final QualifiedIdentifierListTreeImpl bounds; - private Symbol symbol = Symbol.UNKNOWN_SYMBOL; + @Nullable + public Symbol.TypeSymbol typeSymbol; public TypeParameterTreeImpl(IdentifierTreeImpl identifier) { this(identifier, null, QualifiedIdentifierListTreeImpl.emptyList()); @@ -89,10 +90,10 @@ public List children() { } public void setSymbol(Symbol symbol) { - this.symbol = Objects.requireNonNull(symbol); + this.typeSymbol = (Symbol.TypeSymbol) Objects.requireNonNull(symbol); } public Symbol symbol() { - return symbol; + return typeSymbol != null ? typeSymbol : Symbol.UNKNOWN_SYMBOL; } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java b/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java index bed0a6217a9..a7ad3bb7d43 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java +++ b/java-frontend/src/main/java/org/sonar/java/model/VisitorsBridge.java @@ -32,6 +32,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.sensor.symbol.NewSymbolTable; import org.sonar.api.utils.AnnotationUtils; import org.sonar.check.Rule; import org.sonar.java.AnalysisException; @@ -354,7 +355,11 @@ private void createSonarSymbolTable(CompilationUnitTree tree) { && !sonarComponents.isSonarLintContext() // don't provide semantic data (symbol highlighting) to SQ for generated files (jsp) && !(currentFile instanceof GeneratedFile)) { - SonarSymbolTableVisitor symVisitor = new SonarSymbolTableVisitor(sonarComponents.symbolizableFor(currentFile)); + NewSymbolTable symbols = sonarComponents.symbolizableFor(currentFile); + if (symbols == null) { + return; + } + SonarSymbolTableVisitor symVisitor = new SonarSymbolTableVisitor(symbols); symVisitor.visitCompilationUnit(tree); } } diff --git a/java-frontend/src/main/java/org/sonar/java/model/declaration/ClassTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/declaration/ClassTreeImpl.java index c85242854f5..feb944fb214 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/declaration/ClassTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/declaration/ClassTreeImpl.java @@ -16,12 +16,9 @@ */ package org.sonar.java.model.declaration; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import javax.annotation.Nullable; import org.sonar.java.ast.parser.QualifiedIdentifierListTreeImpl; import org.sonar.java.ast.parser.TypeParameterListTreeImpl; +import org.sonarsource.analyzer.commons.collections.ListUtils; import org.sonar.java.model.InternalSyntaxToken; import org.sonar.java.model.JavaTree; import org.sonar.java.model.expression.IdentifierTreeImpl; @@ -37,7 +34,11 @@ import org.sonar.plugins.java.api.tree.TypeParameters; import org.sonar.plugins.java.api.tree.TypeTree; import org.sonar.plugins.java.api.tree.VariableTree; -import org.sonarsource.analyzer.commons.collections.ListUtils; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; +import java.util.Objects; public class ClassTreeImpl extends JavaTree implements ClassTree { @@ -67,7 +68,8 @@ public class ClassTreeImpl extends JavaTree implements ClassTree { @Nullable private SyntaxToken permitsKeyword; private ListTree permittedTypes; - private Symbol.TypeSymbol symbol = Symbol.TypeSymbol.UNKNOWN_TYPE; + @Nullable + public Symbol.TypeSymbol typeSymbol; public ClassTreeImpl(Kind kind, @Nullable SyntaxToken openBraceToken, List members, @Nullable SyntaxToken closeBraceToken) { this.kind = kind; @@ -215,12 +217,12 @@ public SyntaxToken closeBraceToken() { } public void setSymbol(Symbol.TypeSymbol symbol) { - this.symbol = Objects.requireNonNull(symbol); + this.typeSymbol = Objects.requireNonNull(symbol); } @Override public Symbol.TypeSymbol symbol() { - return symbol; + return typeSymbol != null ? typeSymbol : Symbol.TypeSymbol.UNKNOWN_TYPE; } @Nullable diff --git a/java-frontend/src/main/java/org/sonar/java/model/declaration/MethodTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/declaration/MethodTreeImpl.java index ee989957a6e..8ed3a1a182d 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/declaration/MethodTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/declaration/MethodTreeImpl.java @@ -72,7 +72,8 @@ public class MethodTreeImpl extends JavaTree implements MethodTree { @Nullable private CFG cfg; - private Symbol.MethodSymbol symbol = Symbol.MethodSymbol.UNKNOWN_METHOD; + @Nullable + public Symbol.MethodSymbol methodSymbol; /** * Cache for {@link #isOverriding()}. @@ -215,12 +216,12 @@ public ExpressionTree defaultValue() { } public void setSymbol(Symbol.MethodSymbol symbol) { - this.symbol = Objects.requireNonNull(symbol); + this.methodSymbol = Objects.requireNonNull(symbol); } @Override public Symbol.MethodSymbol symbol() { - return symbol; + return methodSymbol != null ? methodSymbol : Symbol.MethodSymbol.UNKNOWN_METHOD; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/declaration/VariableTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/declaration/VariableTreeImpl.java index c697d55faa0..ce12ffd29a7 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/declaration/VariableTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/declaration/VariableTreeImpl.java @@ -17,15 +17,12 @@ package org.sonar.java.model.declaration; import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Objects; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonarsource.analyzer.commons.collections.ListUtils; import org.sonar.java.model.InternalSyntaxToken; import org.sonar.java.model.JavaTree; import org.sonar.java.model.expression.IdentifierTreeImpl; -import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.IdentifierTree; import org.sonar.plugins.java.api.tree.InferedTypeTree; @@ -35,7 +32,11 @@ import org.sonar.plugins.java.api.tree.TreeVisitor; import org.sonar.plugins.java.api.tree.TypeTree; import org.sonar.plugins.java.api.tree.VariableTree; -import org.sonarsource.analyzer.commons.collections.ListUtils; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.Objects; public class VariableTreeImpl extends JavaTree implements VariableTree { private ModifiersTree modifiers; @@ -47,8 +48,8 @@ public class VariableTreeImpl extends JavaTree implements VariableTree { private ExpressionTree initializer; @Nullable private SyntaxToken endToken; - - private Symbol symbol = Symbol.UNKNOWN_SYMBOL; + @Nullable + public Symbol.VariableSymbol variableSymbol; public VariableTreeImpl( ModifiersTree modifiers, @@ -128,12 +129,12 @@ public SyntaxToken equalToken() { } public void setSymbol(Symbol symbol) { - this.symbol = Objects.requireNonNull(symbol); + this.variableSymbol = (Symbol.VariableSymbol) Objects.requireNonNull(symbol); } @Override public Symbol symbol() { - return symbol; + return variableSymbol != null ? variableSymbol : Symbol.UNKNOWN_SYMBOL; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/expression/IdentifierTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/expression/IdentifierTreeImpl.java index 136a9ce792a..2356608968c 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/expression/IdentifierTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/expression/IdentifierTreeImpl.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import javax.annotation.Nullable; import org.sonar.java.model.InternalSyntaxToken; import org.sonar.java.model.JLabelSymbol; import org.sonar.java.model.JavaTree; @@ -36,9 +37,11 @@ public class IdentifierTreeImpl extends AssessableExpressionTree implements Iden private final boolean isUnnamedVariable; private List annotations; - private Symbol symbol = Symbol.UNKNOWN_SYMBOL; public JLabelSymbol labelSymbol; + @Nullable + public Symbol symbol; + public IdentifierTreeImpl(InternalSyntaxToken nameToken) { this(nameToken, false); } @@ -80,7 +83,7 @@ public void setSymbol(Symbol symbol) { @Override public Symbol symbol() { - if (!isUnnamedVariable && symbol != Symbol.UNKNOWN_SYMBOL) { + if (symbol != null && !isUnnamedVariable) { return symbol; } if (labelSymbol != null) { diff --git a/java-frontend/src/main/java/org/sonar/java/model/expression/LambdaExpressionTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/expression/LambdaExpressionTreeImpl.java index a94da471ddd..7cc1bc5c123 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/expression/LambdaExpressionTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/expression/LambdaExpressionTreeImpl.java @@ -17,21 +17,22 @@ package org.sonar.java.model.expression; import java.util.Arrays; -import java.util.Collections; -import java.util.List; import java.util.Objects; -import javax.annotation.Nullable; import org.sonar.java.cfg.CFG; +import org.sonar.plugins.java.api.semantic.Symbol; +import org.sonarsource.analyzer.commons.collections.ListUtils; import org.sonar.java.model.InternalSyntaxToken; import org.sonar.plugins.java.api.cfg.ControlFlowGraph; -import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.BlockTree; import org.sonar.plugins.java.api.tree.LambdaExpressionTree; import org.sonar.plugins.java.api.tree.SyntaxToken; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.TreeVisitor; import org.sonar.plugins.java.api.tree.VariableTree; -import org.sonarsource.analyzer.commons.collections.ListUtils; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.List; public class LambdaExpressionTreeImpl extends AssessableExpressionTree implements LambdaExpressionTree { @@ -44,7 +45,8 @@ public class LambdaExpressionTreeImpl extends AssessableExpressionTree implement private final Tree body; private CFG cfg; - private Symbol.MethodSymbol symbol = Symbol.MethodSymbol.UNKNOWN_METHOD; + @Nullable + public Symbol.MethodSymbol methodSymbol; public LambdaExpressionTreeImpl(@Nullable InternalSyntaxToken openParenToken, List parameters, @Nullable InternalSyntaxToken closeParenToken, InternalSyntaxToken arrowToken, Tree body) { @@ -88,12 +90,12 @@ public Tree body() { } public void setSymbol(Symbol.MethodSymbol symbol) { - this.symbol = Objects.requireNonNull(symbol); + this.methodSymbol = Objects.requireNonNull(symbol); } @Override public Symbol.MethodSymbol symbol() { - return symbol; + return methodSymbol != null ? methodSymbol : Symbol.MethodSymbol.UNKNOWN_METHOD; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/expression/MethodInvocationTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/expression/MethodInvocationTreeImpl.java index c12b863f3ee..76b55fd8246 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/expression/MethodInvocationTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/expression/MethodInvocationTreeImpl.java @@ -17,11 +17,9 @@ package org.sonar.java.model.expression; import java.util.Arrays; -import java.util.Collections; import java.util.List; -import java.util.Objects; -import javax.annotation.Nullable; import org.sonar.java.ast.parser.ArgumentListTreeImpl; +import org.sonarsource.analyzer.commons.collections.ListUtils; import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.tree.Arguments; import org.sonar.plugins.java.api.tree.ExpressionTree; @@ -31,7 +29,10 @@ import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.TreeVisitor; import org.sonar.plugins.java.api.tree.TypeArguments; -import org.sonarsource.analyzer.commons.collections.ListUtils; + +import javax.annotation.Nullable; +import java.util.Collections; +import java.util.Objects; public class MethodInvocationTreeImpl extends AssessableExpressionTree implements MethodInvocationTree { @@ -40,7 +41,8 @@ public class MethodInvocationTreeImpl extends AssessableExpressionTree implement @Nullable private TypeArguments typeArguments; - private Symbol.MethodSymbol methodSymbol = Symbol.MethodSymbol.UNKNOWN_METHOD; + @Nullable + public Symbol.MethodSymbol methodSymbol; public MethodInvocationTreeImpl(ExpressionTree methodSelect, @Nullable TypeArguments typeArguments, ArgumentListTreeImpl arguments) { this.methodSelect = Objects.requireNonNull(methodSelect); @@ -92,7 +94,7 @@ public void setMethodSymbol(Symbol.MethodSymbol methodSymbol) { @Override public Symbol.MethodSymbol methodSymbol() { - return methodSymbol; + return methodSymbol != null ? methodSymbol : Symbol.MethodSymbol.UNKNOWN_METHOD; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/expression/NewClassTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/expression/NewClassTreeImpl.java index cbd2a572b70..8ebfebb66e7 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/expression/NewClassTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/expression/NewClassTreeImpl.java @@ -165,9 +165,10 @@ public Symbol constructorSymbol() { @Override public Symbol.MethodSymbol methodSymbol() { Symbol constructorSymbol = this.getConstructorIdentifier().symbol(); - return constructorSymbol.isUnknown() ? - Symbol.MethodSymbol.UNKNOWN_METHOD : - (Symbol.MethodSymbol) constructorSymbol; + if (constructorSymbol instanceof Symbol.MethodSymbol methodSymbol) { + return methodSymbol; + } + return Symbol.MethodSymbol.UNKNOWN_METHOD; } private static void addIfNotNull(List list, Tree... trees) { diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/AbstractPatternTree.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/AbstractPatternTree.java index 5deed353841..585860636d4 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/AbstractPatternTree.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/AbstractPatternTree.java @@ -18,6 +18,7 @@ import java.util.Objects; import java.util.Optional; +import javax.annotation.Nullable; import org.sonar.java.model.JavaTree; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.PatternTree; @@ -30,16 +31,16 @@ public abstract class AbstractPatternTree extends JavaTree implements PatternTre private final Tree.Kind kind; - private final Type type; + @Nullable + public Type type; - AbstractPatternTree(Tree.Kind kind, Type type) { + AbstractPatternTree(Tree.Kind kind) { this.kind = kind; - this.type = Objects.requireNonNull(type); } @Override public Type symbolType() { - return type; + return type != null ? type : Type.UNKNOWN; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/DefaultPatternTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/DefaultPatternTreeImpl.java index 76fca0bdc1c..ac110e896fa 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/DefaultPatternTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/DefaultPatternTreeImpl.java @@ -18,6 +18,7 @@ import java.util.Collections; import java.util.List; +import javax.annotation.Nullable; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.DefaultPatternTree; import org.sonar.plugins.java.api.tree.SyntaxToken; @@ -31,9 +32,14 @@ public class DefaultPatternTreeImpl extends AbstractPatternTree implements Defau private final SyntaxToken defaultToken; - public DefaultPatternTreeImpl(SyntaxToken defaultToken, Type type) { - super(Tree.Kind.DEFAULT_PATTERN, type); + public DefaultPatternTreeImpl(SyntaxToken defaultToken) { + this(defaultToken, null); + } + + public DefaultPatternTreeImpl(SyntaxToken defaultToken, @Nullable Type type) { + super(Tree.Kind.DEFAULT_PATTERN); this.defaultToken = defaultToken; + this.type = type; } @Override diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/GuardedPatternTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/GuardedPatternTreeImpl.java index 4910d17f362..a05d1ac5aa4 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/GuardedPatternTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/GuardedPatternTreeImpl.java @@ -18,6 +18,7 @@ import java.util.Arrays; import java.util.List; +import javax.annotation.Nullable; import org.sonar.plugins.java.api.semantic.Type; import org.sonar.plugins.java.api.tree.ExpressionTree; import org.sonar.plugins.java.api.tree.GuardedPatternTree; @@ -35,10 +36,15 @@ public class GuardedPatternTreeImpl extends AbstractPatternTree implements Guard private final SyntaxToken whenOperator; private final ExpressionTree expression; - public GuardedPatternTreeImpl(PatternTree pattern, SyntaxToken whenOperator, ExpressionTree expression, Type type) { - super(Tree.Kind.GUARDED_PATTERN, type); + public GuardedPatternTreeImpl(PatternTree pattern, SyntaxToken whenOperator, ExpressionTree expression) { + this(pattern, whenOperator, expression, null); + } + + public GuardedPatternTreeImpl(PatternTree pattern, SyntaxToken whenOperator, ExpressionTree expression, @Nullable Type type) { + super(Tree.Kind.GUARDED_PATTERN); this.pattern = pattern; this.whenOperator = whenOperator; + this.type = type; this.expression = expression; } diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/NullPatternTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/NullPatternTreeImpl.java index 1cddb3834f3..328f96ca56c 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/NullPatternTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/NullPatternTreeImpl.java @@ -33,7 +33,7 @@ public class NullPatternTreeImpl extends AbstractPatternTree implements NullPatt private final LiteralTree nullLiteral; public NullPatternTreeImpl(LiteralTree nullLiteral) { - super(Tree.Kind.NULL_PATTERN, Type.UNKNOWN); + super(Tree.Kind.NULL_PATTERN); this.nullLiteral = nullLiteral; } diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/RecordPatternTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/RecordPatternTreeImpl.java index b1cf9152ef9..bbd858dd401 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/RecordPatternTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/RecordPatternTreeImpl.java @@ -38,7 +38,7 @@ public class RecordPatternTreeImpl extends AbstractPatternTree implements Record private final InternalSyntaxToken closeParenToken; public RecordPatternTreeImpl(TypeTree type, InternalSyntaxToken openParenToken, List patterns, InternalSyntaxToken closeParenToken) { - super(Kind.RECORD_PATTERN, Type.UNKNOWN); + super(Kind.RECORD_PATTERN); this.type = type; this.openParenToken = openParenToken; this.patterns = patterns; diff --git a/java-frontend/src/main/java/org/sonar/java/model/pattern/TypePatternTreeImpl.java b/java-frontend/src/main/java/org/sonar/java/model/pattern/TypePatternTreeImpl.java index 0552c45a857..1ff8bc85c0c 100644 --- a/java-frontend/src/main/java/org/sonar/java/model/pattern/TypePatternTreeImpl.java +++ b/java-frontend/src/main/java/org/sonar/java/model/pattern/TypePatternTreeImpl.java @@ -31,11 +31,16 @@ public class TypePatternTreeImpl extends AbstractPatternTree implements TypePatt private final VariableTree patternVariable; - public TypePatternTreeImpl(VariableTree patternVariable, Type type) { - super(Tree.Kind.TYPE_PATTERN, type); + public TypePatternTreeImpl(VariableTree patternVariable) { + super(Tree.Kind.TYPE_PATTERN); this.patternVariable = patternVariable; } + public TypePatternTreeImpl(VariableTree patternVariable, Type type) { + this(patternVariable); + this.type = type; + } + @Override public void accept(TreeVisitor visitor) { visitor.visitTypePattern(this); diff --git a/java-frontend/src/main/java/org/sonar/java/semsitter/BridgeExtractor.java b/java-frontend/src/main/java/org/sonar/java/semsitter/BridgeExtractor.java new file mode 100644 index 00000000000..a31a1462ac5 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/semsitter/BridgeExtractor.java @@ -0,0 +1,143 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.semsitter; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFileAttributeView; +import java.util.Set; +import org.apache.commons.compress.archivers.tar.TarArchiveEntry; +import org.apache.commons.compress.archivers.tar.TarArchiveInputStream; +import org.apache.commons.compress.compressors.xz.XZCompressorInputStream; +import org.sonar.java.semsitter.PlatformDetection.Platform; + +import static java.nio.file.attribute.PosixFilePermission.OWNER_EXECUTE; +import static java.nio.file.attribute.PosixFilePermission.OWNER_READ; +import static java.nio.file.attribute.PosixFilePermission.OWNER_WRITE; + +public final class BridgeExtractor { + + private BridgeExtractor() { + } + + public static Path extract(Path targetDir, Platform platform) throws IOException { + if (platform == Platform.UNSUPPORTED) { + throw new IllegalStateException("Cannot extract bridge for unsupported platform"); + } + String bridgeName = bridgeName(platform); + Path bridgePath = targetDir.resolve(bridgeName); + String archiveName = archiveName(platform); + Path prebuilt = findPrebuilt(archiveName); + if (Files.exists(bridgePath) && !prebuiltIsNewer(prebuilt, bridgePath)) { + return bridgePath; + } + String resourcePath = "/bridge/" + archiveName; + InputStream resource = prebuilt != null ? Files.newInputStream(prebuilt) : BridgeExtractor.class.getResourceAsStream(resourcePath); + if (resource == null) { + throw new IllegalStateException("Bridge resource not found"); + } + try (InputStream stream = resource) { + extractArchive(targetDir, bridgePath, stream, resourcePath, bridgeName); + } + return bridgePath; + } + + private static boolean prebuiltIsNewer(Path prebuilt, Path bridgePath) throws IOException { + return prebuilt != null && Files.getLastModifiedTime(prebuilt).compareTo(Files.getLastModifiedTime(bridgePath)) > 0; + } + + private static Path findPrebuilt(String archiveName) { + Path found = walk(Path.of("").toAbsolutePath(), archiveName); + if (found != null) { + return found; + } + Path origin = codeOrigin(); + return origin == null ? null : walk(origin, archiveName); + } + + private static Path codeOrigin() { + try { + var source = BridgeExtractor.class.getProtectionDomain().getCodeSource(); + if (source == null || source.getLocation() == null) { + return null; + } + return Path.of(source.getLocation().toURI()); + } catch (Exception failure) { + return null; + } + } + + private static Path walk(Path dir, String archiveName) { + for (int i = 0; i < 8 && dir != null; i++) { + Path archive = dir.resolve("semsitter-bridge").resolve("prebuilt").resolve(archiveName); + if (Files.isRegularFile(archive)) { + return archive; + } + dir = dir.getParent(); + } + return null; + } + + private static String archiveName(Platform platform) { + String suffix = switch (platform) { + case ALPINE_AARCH64 -> Platform.LINUX_AARCH64.toString(); + case ALPINE_X64 -> Platform.LINUX_X64.toString(); + default -> platform.toString(); + }; + return "bridge-" + suffix + ".xz"; + } + + private static String bridgeName(Platform platform) { + return platform == Platform.WINDOWS_X64 ? "bridge.exe" : "bridge"; + } + + private static void extractArchive(Path target, Path bridgePath, InputStream archive, String resourcePath, String bridgeName) throws IOException { + Path stagingPath = target.resolve(bridgeName + ".ext"); + try { + try (var xzStream = new XZCompressorInputStream(archive); + var tarStream = new TarArchiveInputStream(xzStream)) { + TarArchiveEntry entry = tarStream.getNextEntry(); + while (entry != null && (!bridgeName.equals(entry.getName()) || entry.isDirectory())) { + entry = tarStream.getNextEntry(); + } + if (entry == null) { + throw new IllegalStateException("Bridge binary not found in archive: " + resourcePath); + } + Files.createDirectories(target); + Files.copy(tarStream, stagingPath, StandardCopyOption.REPLACE_EXISTING); + } + Files.deleteIfExists(bridgePath); + Files.move(stagingPath, bridgePath, StandardCopyOption.ATOMIC_MOVE); + PosixFileAttributeView view = Files.getFileAttributeView(bridgePath, PosixFileAttributeView.class); + if (view != null) { + view.setPermissions(Set.of(OWNER_EXECUTE, OWNER_READ, OWNER_WRITE)); + } + } catch (Exception failure) { + Files.deleteIfExists(stagingPath); + if (failure instanceof IOException ioException) { + throw ioException; + } + if (failure instanceof RuntimeException runtimeException) { + throw runtimeException; + } + throw new IllegalStateException(failure); + } + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/semsitter/BridgeFrames.java b/java-frontend/src/main/java/org/sonar/java/semsitter/BridgeFrames.java new file mode 100644 index 00000000000..a1020ed3efb --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/semsitter/BridgeFrames.java @@ -0,0 +1,391 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.semsitter; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * Protobuf codec of {@code semsitter-bridge/proto/bridge.proto}. Field numbers live here and in + * the Rust encoder so the CST framing can move into SemSitter without a second schema. + */ +final class BridgeFrames { + + private BridgeFrames() { + } + + static byte[] analyze(int id, String root, Iterable files, Iterable classpath) { + ByteSink analyze = new ByteSink(); + analyze.string(1, root); + for (String file : files) { + analyze.string(2, file); + } + for (String entry : classpath) { + analyze.string(3, entry); + } + ByteSink out = id(id); + out.message(2, analyze.bytes()); + return out.bytes(); + } + + static byte[] cst(int id, String path) { + ByteSink body = new ByteSink(); + body.string(1, path); + ByteSink out = id(id); + out.message(3, body.bytes()); + return out.bytes(); + } + + static byte[] types(int id, Iterable names) { + ByteSink body = new ByteSink(); + for (String name : names) { + body.string(1, name); + } + ByteSink out = id(id); + out.message(4, body.bytes()); + return out.bytes(); + } + + static byte[] parse(int id, String source) { + ByteSink body = new ByteSink(); + body.string(1, source); + ByteSink out = id(id); + out.message(5, body.bytes()); + return out.bytes(); + } + + static byte[] quit(int id) { + ByteSink out = id(id); + out.varint(6, 1); + return out.bytes(); + } + + static JsonObject response(byte[] bytes) { + Cursor cursor = new Cursor(bytes); + JsonObject object = new JsonObject(); + object.addProperty("ok", false); + while (cursor.remaining()) { + long key = cursor.varint(); + int field = (int) (key >> 3); + int wire = (int) (key & 7); + switch (field) { + case 1 -> object.addProperty("id", (int) cursor.varint()); + case 2 -> object.addProperty("ok", cursor.varint() != 0); + case 3 -> object.addProperty("error", cursor.text()); + case 4 -> analyzeResult(object, cursor.bytes()); + case 5 -> object.add("cst", document(cursor.bytes())); + case 6 -> object.add("types", JsonParser.parseString(cursor.text())); + default -> cursor.skip(wire); + } + } + return object; + } + + private static void analyzeResult(JsonObject object, byte[] bytes) { + Cursor cursor = new Cursor(bytes); + JsonArray files = new JsonArray(); + while (cursor.remaining()) { + long key = cursor.varint(); + int field = (int) (key >> 3); + switch (field) { + case 1 -> object.addProperty("root", cursor.text()); + case 2 -> files.add(cursor.text()); + default -> cursor.skip((int) (key & 7)); + } + } + object.add("files", files); + } + + private static JsonObject document(byte[] bytes) { + Cursor cursor = new Cursor(bytes); + JsonObject object = new JsonObject(); + object.addProperty("hasError", false); + object.addProperty("semantic", false); + object.add("symbols", new JsonArray()); + object.add("facts", new JsonObject()); + while (cursor.remaining()) { + long key = cursor.varint(); + int field = (int) (key >> 3); + switch (field) { + case 1 -> object.addProperty("hasError", cursor.varint() != 0); + case 2 -> object.add("tree", node(cursor.bytes())); + case 3 -> object.addProperty("path", cursor.text()); + case 4 -> object.addProperty("semantic", cursor.varint() != 0); + case 5 -> object.add("symbols", JsonParser.parseString(cursor.text())); + case 6 -> factEntry(object.getAsJsonObject("facts"), cursor.bytes()); + default -> cursor.skip((int) (key & 7)); + } + } + return object; + } + + private static void factEntry(JsonObject facts, byte[] bytes) { + Cursor cursor = new Cursor(bytes); + String key = ""; + JsonObject fact = new JsonObject(); + while (cursor.remaining()) { + long header = cursor.varint(); + int field = (int) (header >> 3); + switch (field) { + case 1 -> key = cursor.text(); + case 2 -> fact = fact(cursor.bytes()); + default -> cursor.skip((int) (header & 7)); + } + } + facts.add(key, fact); + } + + private static JsonObject fact(byte[] bytes) { + Cursor cursor = new Cursor(bytes); + JsonObject object = new JsonObject(); + while (cursor.remaining()) { + long header = cursor.varint(); + int field = (int) (header >> 3); + switch (field) { + case 1 -> object.addProperty("kind", cursor.text()); + case 2 -> object.addProperty("tier", cursor.text()); + case 3 -> object.addProperty("parent", cursor.text()); + case 4 -> prop(object, cursor.bytes()); + case 5 -> addString(object, "extends", cursor.text()); + case 6 -> addString(object, "implements", cursor.text()); + case 7 -> addString(object, "permits", cursor.text()); + case 8 -> addString(object, "returns", cursor.text()); + case 9 -> addString(object, "type", cursor.text()); + case 10 -> addString(object, "params", cursor.text()); + case 11 -> addString(object, "overrides", cursor.text()); + case 12 -> addString(object, "annotations", cursor.text()); + default -> cursor.skip((int) (header & 7)); + } + } + return object; + } + + private static void prop(JsonObject fact, byte[] bytes) { + Cursor cursor = new Cursor(bytes); + String name = ""; + JsonArray values = new JsonArray(); + while (cursor.remaining()) { + long header = cursor.varint(); + int field = (int) (header >> 3); + switch (field) { + case 1 -> name = cursor.text(); + case 2 -> values.add(cursor.text()); + default -> cursor.skip((int) (header & 7)); + } + } + if (!fact.has("props")) { + fact.add("props", new JsonObject()); + } + fact.getAsJsonObject("props").add(name, values); + } + + private static JsonObject node(byte[] bytes) { + Cursor cursor = new Cursor(bytes); + JsonObject object = new JsonObject(); + long[] position = new long[4]; + long[] span = new long[2]; + boolean sawPosition = false; + boolean sawSpan = false; + while (cursor.remaining()) { + long header = cursor.varint(); + int field = (int) (header >> 3); + switch (field) { + case 1 -> object.addProperty("k", cursor.text()); + case 2 -> { + if (cursor.varint() != 0) { + object.addProperty("n", true); + } + } + case 3 -> object.addProperty("f", cursor.text()); + case 4 -> { + if (cursor.varint() != 0) { + object.addProperty("m", true); + } + } + case 5, 6, 7, 8 -> { + position[field - 5] = cursor.varint(); + sawPosition = true; + } + case 9, 10 -> { + span[field - 9] = cursor.varint(); + sawSpan = true; + } + case 11 -> object.addProperty("t", cursor.text()); + case 12 -> add(object, "c", node(cursor.bytes())); + case 13 -> addString(object, "decls", cursor.text()); + case 14 -> add(object, "refs", ref(cursor.bytes())); + default -> cursor.skip((int) (header & 7)); + } + } + if (sawPosition) { + JsonArray array = new JsonArray(); + for (long value : position) { + array.add(value); + } + object.add("p", array); + } + if (sawSpan) { + JsonArray array = new JsonArray(); + for (long value : span) { + array.add(value); + } + object.add("b", array); + } + return object; + } + + private static JsonObject ref(byte[] bytes) { + Cursor cursor = new Cursor(bytes); + JsonObject object = new JsonObject(); + while (cursor.remaining()) { + long header = cursor.varint(); + int field = (int) (header >> 3); + switch (field) { + case 1 -> object.addProperty("access", cursor.text()); + case 2 -> object.addProperty("outcome", cursor.text()); + case 3 -> object.addProperty("target", cursor.text()); + case 4 -> object.addProperty("reason", cursor.text()); + case 5 -> { + if (cursor.varint() != 0) { + object.addProperty("bounded", true); + } + } + case 6 -> object.add("facts", JsonParser.parseString(cursor.text())); + default -> cursor.skip((int) (header & 7)); + } + } + return object; + } + + private static void add(JsonObject object, String key, JsonElement element) { + if (!object.has(key)) { + object.add(key, new JsonArray()); + } + object.getAsJsonArray(key).add(element); + } + + private static void addString(JsonObject object, String key, String value) { + if (!object.has(key)) { + object.add(key, new JsonArray()); + } + object.getAsJsonArray(key).add(value); + } + + private static ByteSink id(int id) { + ByteSink out = new ByteSink(); + out.varint(1, id); + return out; + } + + private static final class ByteSink { + private final List bytes = new ArrayList<>(); + + void varint(int field, long value) { + rawVarint(((long) field) << 3); + rawVarint(value); + } + + void string(int field, String value) { + message(field, value.getBytes(StandardCharsets.UTF_8)); + } + + void message(int field, byte[] value) { + rawVarint((((long) field) << 3) | 2); + rawVarint(value.length); + for (byte b : value) { + bytes.add(b); + } + } + + byte[] bytes() { + byte[] out = new byte[bytes.size()]; + for (int i = 0; i < bytes.size(); i++) { + out[i] = bytes.get(i); + } + return out; + } + + private void rawVarint(long value) { + long rest = value; + while (true) { + int bite = (int) (rest & 0x7f); + rest >>>= 7; + if (rest != 0) { + bite |= 0x80; + } + bytes.add((byte) bite); + if (rest == 0) { + return; + } + } + } + } + + private static final class Cursor { + private final byte[] bytes; + private int at; + + private Cursor(byte[] bytes) { + this.bytes = bytes; + } + + private boolean remaining() { + return at < bytes.length; + } + + private long varint() { + long value = 0; + int shift = 0; + while (at < bytes.length && shift < 64) { + int bite = bytes[at++] & 0xff; + value |= (long) (bite & 0x7f) << shift; + if ((bite & 0x80) == 0) { + return value; + } + shift += 7; + } + throw new IllegalArgumentException("truncated varint"); + } + + private byte[] bytes() { + int len = (int) varint(); + byte[] out = new byte[len]; + System.arraycopy(bytes, at, out, 0, len); + at += len; + return out; + } + + private String text() { + return new String(bytes(), StandardCharsets.UTF_8); + } + + private void skip(int wire) { + switch (wire) { + case 0 -> varint(); + case 1 -> at += 8; + case 2 -> at += (int) varint(); + case 5 -> at += 4; + default -> throw new IllegalArgumentException("unsupported protobuf wire type " + wire); + } + } + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/semsitter/PlatformDetection.java b/java-frontend/src/main/java/org/sonar/java/semsitter/PlatformDetection.java new file mode 100644 index 00000000000..be7db6214e3 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/semsitter/PlatformDetection.java @@ -0,0 +1,98 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.semsitter; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Locale; +import java.util.Optional; + +public class PlatformDetection { + + private final String rawOsName; + private final String rawOsArch; + + public enum Platform { + LINUX_X64("linux-x64"), + LINUX_AARCH64("linux-aarch64"), + ALPINE_X64("alpine-x64"), + ALPINE_AARCH64("alpine-aarch64"), + MACOS_X64("macos-x64"), + MACOS_AARCH64("macos-aarch64"), + WINDOWS_X64("windows-x64"), + UNSUPPORTED("unsupported"); + + private final String name; + + Platform(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + + public PlatformDetection() { + this(System.getProperty("os.name"), System.getProperty("os.arch")); + } + + PlatformDetection(String rawOsName, String rawOsArch) { + this.rawOsName = rawOsName; + this.rawOsArch = rawOsArch; + } + + public Platform getPlatform() { + String platformString = osName() + "-" + osArch(); + for (Platform platform : Platform.values()) { + if (platform.toString().equalsIgnoreCase(platformString)) { + return platform; + } + } + return Platform.UNSUPPORTED; + } + + private String osName() { + String osName = Optional.ofNullable(rawOsName).orElse("generic").toLowerCase(Locale.ROOT); + if (osName.contains("win")) { + return "windows"; + } else if (osName.contains("mac")) { + return "macos"; + } else if (osName.contains("nux")) { + if (isAlpineLinux()) { + return "alpine"; + } + return "Linux"; + } + return osName.replaceAll("\\s+", ""); + } + + private String osArch() { + String osArch = Optional.ofNullable(rawOsArch).orElse("unknown").toLowerCase(Locale.ROOT); + return switch (osArch) { + case "amd64", "x86_64" -> "x64"; + case "aarch64", "arm64" -> "aarch64"; + case "x86", "i386", "i686" -> "x86"; + default -> osArch; + }; + } + + private static boolean isAlpineLinux() { + return Files.exists(Path.of("/etc/alpine-release")); + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/semsitter/SemsitterBridge.java b/java-frontend/src/main/java/org/sonar/java/semsitter/SemsitterBridge.java new file mode 100644 index 00000000000..756a1c53361 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/semsitter/SemsitterBridge.java @@ -0,0 +1,307 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.semsitter; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.stream.JsonReader; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Client for the {@code semsitter-bridge} executable. + * + *

A current bridge is spoken to over shared memory: protobuf payloads, one ping byte on the + * pipe. {@code -Dsonar.java.semsitter.dump=

} also asks it to write {@code udg.json}, the + * semantic-dump JSON and the annotated CST JSON under that directory. + * + *

{@link #analyze} scans a batch of files with the classpath, then {@link #cst} returns the + * CST of one of them with its SemSitter semantics: {@code tree} (nodes with kind {@code k}, + * field {@code f}, position {@code p} as 1-based lines and 0-based UTF-16 columns, text {@code t}, + * children {@code c}, declared symbol ids {@code decls}, resolved occurrences {@code refs}), + * {@code symbols} (the semantic dump symbols) and {@code facts} (graph facts by id). + */ +public final class SemsitterBridge implements AutoCloseable { + + private static final int STDERR_LIMIT = 8192; + + private final Session session; + private int nextId = 1; + private boolean closed; + + private SemsitterBridge(Session session) { + this.session = session; + } + + public static SemsitterBridge start(Path executable) throws IOException { + boolean shm = speaksShm(executable); + List command = new ArrayList<>(); + command.add(executable.toAbsolutePath().toString()); + command.add("serve"); + if (shm) { + command.add("--shm"); + } + String dump = System.getProperty("sonar.java.semsitter.dump"); + if (shm && dump != null && !dump.isBlank()) { + command.add("--dump-json"); + command.add(dump); + } + Process process = new ProcessBuilder(command).start(); + return new SemsitterBridge(shm ? new ShmSession(process) : new JsonlSession(process)); + } + + /** + * Scans {@code files} as one project rooted at {@code root}. + * @return the response, whose {@code files} are the project-relative paths accepted by {@link #cst} + */ + public synchronized JsonObject analyze(Path root, Collection files, Collection classpath) throws IOException { + return session.exchange(BridgeFrames.analyze(nextId++, root.toAbsolutePath().toString(), absolute(files), classpath), jsonAnalyze(root, files, classpath)); + } + + public synchronized JsonObject cst(String path) throws IOException { + JsonObject request = new JsonObject(); + request.addProperty("op", "cst"); + request.addProperty("path", path); + return session.exchange(BridgeFrames.cst(nextId++, path), request).getAsJsonObject("cst"); + } + + /** + * @return declarations (kind, supertypes, members) of the types named by erased binary names, and of their supertypes + */ + public synchronized JsonObject types(Collection names) throws IOException { + return session.exchange(BridgeFrames.types(nextId++, names), jsonTypes(names)).getAsJsonObject("types"); + } + + public synchronized JsonObject parse(String source) throws IOException { + JsonObject request = new JsonObject(); + request.addProperty("op", "parse"); + request.addProperty("source", source); + return session.exchange(BridgeFrames.parse(nextId++, source), request).getAsJsonObject("cst"); + } + + @Override + public synchronized void close() { + if (closed) { + return; + } + closed = true; + session.close(BridgeFrames.quit(nextId++)); + } + + private static boolean speaksShm(Path executable) throws IOException { + Process help = new ProcessBuilder(executable.toAbsolutePath().toString(), "-h").start(); + String text = new String(help.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + try { + help.waitFor(2, TimeUnit.SECONDS); + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + } + help.destroyForcibly(); + return text.contains("--shm"); + } + + private static JsonObject jsonAnalyze(Path root, Collection files, Collection classpath) { + JsonObject request = new JsonObject(); + request.addProperty("op", "analyze"); + request.addProperty("root", root.toAbsolutePath().toString()); + JsonArray fileArray = new JsonArray(); + absolute(files).forEach(fileArray::add); + request.add("files", fileArray); + JsonArray classpathArray = new JsonArray(); + classpath.forEach(classpathArray::add); + request.add("classpath", classpathArray); + return request; + } + + private static JsonObject jsonTypes(Collection names) { + JsonObject request = new JsonObject(); + request.addProperty("op", "types"); + JsonArray nameArray = new JsonArray(); + names.forEach(nameArray::add); + request.add("names", nameArray); + return request; + } + + private static List absolute(Collection files) { + List paths = new ArrayList<>(); + files.forEach(file -> paths.add(file.toAbsolutePath().toString())); + return paths; + } + + private interface Session { + JsonObject exchange(byte[] protobuf, JsonObject jsonl) throws IOException; + + void close(byte[] quit); + } + + private static final class ShmSession implements Session { + private final Process process; + private final OutputStream toBridge; + private final InputStream fromBridge; + private final SharedSlots slots; + private final StringBuilder stderr = new StringBuilder(); + + private ShmSession(Process process) throws IOException { + this.process = process; + this.toBridge = process.getOutputStream(); + this.fromBridge = process.getInputStream(); + Thread drain = new Thread(() -> drain(process.getErrorStream(), stderr), "semsitter-bridge-stderr"); + drain.setDaemon(true); + drain.start(); + int ready = fromBridge.read(); + if (ready != SharedSlots.READY) { + throw new IOException("semsitter bridge did not open shared memory" + suffix(stderr)); + } + this.slots = SharedSlots.open(process.pid()); + toBridge.write(SharedSlots.ATTACHED); + toBridge.flush(); + } + + @Override + public JsonObject exchange(byte[] protobuf, JsonObject jsonl) throws IOException { + slots.writeRequest(protobuf, toBridge, fromBridge); + JsonObject response = BridgeFrames.response(slots.readResponse(fromBridge, toBridge)); + if (!response.has("ok") || !response.get("ok").getAsBoolean()) { + String error = response.has("error") ? response.get("error").getAsString() : "bridge request failed"; + throw new IOException("semsitter bridge: " + error + suffix(stderr)); + } + return response; + } + + @Override + public void close(byte[] quit) { + try { + exchange(quit, new JsonObject()); + slots.close(); + toBridge.close(); + } catch (IOException ignored) { + process.destroy(); + } + waitFor(process); + } + } + + private static final class JsonlSession implements Session { + private final Process process; + private final BufferedWriter toBridge; + private final BufferedReader fromBridge; + private final StringBuilder stderr = new StringBuilder(); + private int nextId = 1; + + private JsonlSession(Process process) { + this.process = process; + this.toBridge = new BufferedWriter(new OutputStreamWriter(process.getOutputStream(), StandardCharsets.UTF_8)); + this.fromBridge = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8)); + Thread drain = new Thread(() -> drain(process.getErrorStream(), stderr), "semsitter-bridge-stderr"); + drain.setDaemon(true); + drain.start(); + } + + @Override + public JsonObject exchange(byte[] protobuf, JsonObject jsonl) throws IOException { + int id = nextId++; + jsonl.addProperty("id", id); + toBridge.write(jsonl.toString()); + toBridge.write('\n'); + toBridge.flush(); + String responseLine = fromBridge.readLine(); + if (responseLine == null) { + throw new IOException("semsitter bridge closed stdout" + suffix(stderr)); + } + JsonElement parsed; + try { + JsonReader reader = new JsonReader(new StringReader(responseLine)); + reader.setNestingLimit(Integer.MAX_VALUE); + parsed = JsonParser.parseReader(reader); + } catch (RuntimeException invalid) { + throw new IOException("semsitter bridge returned invalid JSON" + suffix(stderr), invalid); + } + if (!parsed.isJsonObject()) { + throw new IOException("semsitter bridge returned a non-object" + suffix(stderr)); + } + JsonObject response = parsed.getAsJsonObject(); + if (!response.has("id") || response.get("id").getAsInt() != id) { + throw new IOException("semsitter bridge response id mismatch, expected " + id); + } + if (!response.has("ok") || !response.get("ok").getAsBoolean()) { + String error = response.has("error") ? response.get("error").getAsString() : responseLine; + throw new IOException("semsitter bridge: " + error + suffix(stderr)); + } + return response; + } + + @Override + public void close(byte[] quit) { + try { + JsonObject request = new JsonObject(); + request.addProperty("op", "quit"); + exchange(quit, request); + toBridge.close(); + } catch (IOException ignored) { + process.destroy(); + } + waitFor(process); + } + } + + private static void waitFor(Process process) { + try { + if (!process.waitFor(2, TimeUnit.SECONDS)) { + process.destroyForcibly(); + } + } catch (InterruptedException interrupted) { + Thread.currentThread().interrupt(); + process.destroyForcibly(); + } + } + + private static String suffix(StringBuilder stderr) { + synchronized (stderr) { + return stderr.isEmpty() ? "" : (" stderr: " + stderr); + } + } + + private static void drain(InputStream stream, StringBuilder buffer) { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + synchronized (buffer) { + buffer.append(line).append('\n'); + if (buffer.length() > STDERR_LIMIT) { + buffer.delete(0, buffer.length() - STDERR_LIMIT); + } + } + } + } catch (IOException ignored) { + // stderr closed with the process + } + } +} diff --git a/java-frontend/src/main/java/org/sonar/java/semsitter/SharedSlots.java b/java-frontend/src/main/java/org/sonar/java/semsitter/SharedSlots.java new file mode 100644 index 00000000000..dd89e42e921 --- /dev/null +++ b/java-frontend/src/main/java/org/sonar/java/semsitter/SharedSlots.java @@ -0,0 +1,165 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.semsitter; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.nio.ByteOrder; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Pair of shared mappings the bridge creates for one process. Payloads stay in the mappings. + * The pipe only carries a one-byte ping: {@code 1} data is ready, {@code 2} the mapping grew. + */ +final class SharedSlots implements AutoCloseable { + + static final byte READY = 'R'; + static final byte ATTACHED = 'A'; + static final byte DATA = 1; + static final byte GROW = 2; + + private static final int HEADER = 24; + private static final int MAGIC = 0x31424A53; + private static final int MAX = 512 * 1024 * 1024; + + private final RandomAccessFile requestFile; + private final RandomAccessFile responseFile; + private MappedByteBuffer request; + private MappedByteBuffer response; + + private SharedSlots(RandomAccessFile requestFile, MappedByteBuffer request, RandomAccessFile responseFile, MappedByteBuffer response) { + this.requestFile = requestFile; + this.request = request; + this.responseFile = responseFile; + this.response = response; + } + + static SharedSlots open(long pid) throws IOException { + Path dir = Files.isDirectory(Path.of("/dev/shm")) ? Path.of("/dev/shm") : Path.of(System.getProperty("java.io.tmpdir")); + RandomAccessFile requestFile = new RandomAccessFile(dir.resolve("sjb-" + pid + "-q").toFile(), "rw"); + RandomAccessFile responseFile = new RandomAccessFile(dir.resolve("sjb-" + pid + "-a").toFile(), "rw"); + return new SharedSlots(requestFile, map(requestFile), responseFile, map(responseFile)); + } + + void writeRequest(byte[] bytes, OutputStream toBridge, InputStream fromBridge) throws IOException { + request = write(requestFile, request, bytes, toBridge, fromBridge); + } + + byte[] readResponse(InputStream fromBridge, OutputStream toBridge) throws IOException { + Read read = read(responseFile, response, fromBridge, toBridge); + response = read.buffer; + return read.payload; + } + + @Override + public void close() throws IOException { + requestFile.close(); + responseFile.close(); + } + + private static MappedByteBuffer write(RandomAccessFile file, MappedByteBuffer buffer, byte[] bytes, OutputStream toPeer, InputStream fromPeer) throws IOException { + int need = HEADER + bytes.length; + if (need > buffer.capacity()) { + int cap = buffer.capacity(); + while (cap < need) { + if (cap >= MAX) { + throw new IOException("protobuf message of " + bytes.length + " bytes exceeds the shared mapping"); + } + cap = Math.min(cap * 2, MAX); + } + file.setLength(cap); + header(buffer, GROW, cap, cap); + ping(toPeer, GROW); + if (readPing(fromPeer) != GROW) { + throw new IOException("shared mapping grow was not acknowledged"); + } + buffer = map(file); + } + buffer.position(HEADER); + buffer.put(bytes); + header(buffer, (byte) 0, bytes.length, buffer.capacity()); + ping(toPeer, DATA); + return buffer; + } + + private static Read read(RandomAccessFile file, MappedByteBuffer buffer, InputStream fromPeer, OutputStream toPeer) throws IOException { + while (true) { + switch (readPing(fromPeer)) { + case GROW -> { + int cap = (int) len(buffer); + file.setLength(cap); + buffer = map(file); + ping(toPeer, GROW); + } + case DATA -> { + int size = (int) len(buffer); + if (HEADER + size > buffer.capacity()) { + throw new IOException("payload of " + size + " bytes does not fit the shared mapping"); + } + byte[] payload = new byte[size]; + buffer.position(HEADER); + buffer.get(payload); + return new Read(buffer, payload); + } + default -> throw new IOException("unexpected shared-memory ping"); + } + } + } + + private static void header(MappedByteBuffer buffer, int kind, long len, long cap) { + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.putInt(0, MAGIC); + buffer.putInt(4, kind); + buffer.putLong(8, len); + buffer.putLong(16, cap); + } + + private static long len(MappedByteBuffer buffer) throws IOException { + buffer.order(ByteOrder.LITTLE_ENDIAN); + if (buffer.getInt(0) != MAGIC) { + throw new IOException("shared mapping has no bridge header"); + } + return buffer.getLong(8); + } + + private static MappedByteBuffer map(RandomAccessFile file) throws IOException { + MappedByteBuffer buffer = file.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, file.length()); + buffer.order(ByteOrder.LITTLE_ENDIAN); + return buffer; + } + + private static void ping(OutputStream toPeer, byte value) throws IOException { + toPeer.write(value); + toPeer.flush(); + } + + private static byte readPing(InputStream fromPeer) throws IOException { + int value = fromPeer.read(); + if (value < 0) { + throw new IOException("semsitter bridge closed the ping pipe"); + } + return (byte) value; + } + + private record Read(MappedByteBuffer buffer, byte[] payload) { + } +} diff --git a/java-frontend/src/test/java/org/eclipse/jdt/core/dom/ASTUtilsTest.java b/java-frontend/src/test/java/org/eclipse/jdt/core/dom/ASTUtilsTest.java deleted file mode 100644 index 7e985fc8e8a..00000000000 --- a/java-frontend/src/test/java/org/eclipse/jdt/core/dom/ASTUtilsTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SonarQube Java - * Copyright (C) SonarSource Sàrl - * mailto:info AT sonarsource DOT com - * - * You can redistribute and/or modify this program under the terms of - * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the Sonar Source-Available License for more details. - * - * You should have received a copy of the Sonar Source-Available License - * along with this program; if not, see https://sonarsource.com/license/ssal/ - */ -package org.eclipse.jdt.core.dom; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertNull; - -class ASTUtilsTest { - - @Test - void type_resolution_should_recover() { - assertNull(ASTUtils.resolveType(/* causes NPE */null, "Unknown")); - } -} diff --git a/java-frontend/src/test/java/org/sonar/java/model/ClassesLayoutTest.java b/java-frontend/src/test/java/org/sonar/java/model/ClassesLayoutTest.java index 95d1f03a49e..5d6b969a836 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/ClassesLayoutTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/ClassesLayoutTest.java @@ -88,11 +88,6 @@ void symbol_variable() { assertThat(countFields(JVariableSymbol.class)).isEqualTo(7); } - @Test - void annotation() { - assertThat(countFields(JSymbolMetadata.JAnnotationInstance.class)).isEqualTo(3); - } - /** Count number of fields in instances, including inherited fields. */ private static int countFields(Class clazz) { int count = 0; diff --git a/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java index 23d7f0f21b4..43b09298e26 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JMethodSymbolTest.java @@ -52,7 +52,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import static org.sonar.java.model.JParserTestUtils.methodBinding; import static org.sonar.java.model.JParserTestUtils.sema; import static org.sonar.java.model.JParserTestUtils.symbolOf; import static org.sonar.java.model.JParserTestUtils.typeOf; @@ -75,7 +74,7 @@ void test() { assertThat(symbol.returnType()) .isSameAs(((JTypeSymbol) typeOf(c).symbol())); assertThat(symbol.thrownTypes()) - .containsOnly(sema(cu).type(Objects.requireNonNull(sema(cu).resolveType("java.lang.Exception")))); + .containsOnly(sema(cu).getClassType("java.lang.Exception")); } @Test @@ -84,7 +83,7 @@ void test_with_generics() { ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); Symbol.MethodSymbol declarationSymbol = m.symbol(); - JType declarationParameterType = sema(cu).type(Objects.requireNonNull((methodBinding(m).getParameterTypes()[0]))); + Type declarationParameterType = m.parameters().get(0).type().symbolType(); assertThat(declarationSymbol.parameterTypes()) .containsOnly(declarationParameterType); Symbol declarationParameterSymbol = m.parameters().get(0).symbol(); @@ -240,11 +239,12 @@ void testObjectExtensionFromInterface() { assertThat(symbol.overriddenSymbols()).containsExactly(objectEqualsMethod(sema(cu))); } - private static JMethodSymbol objectEqualsMethod(JSema sema) { - return Arrays.stream(sema.resolveType("java.lang.Object").getDeclaredMethods()) - .filter(method -> "equals".equals(method.getName())) + private static Symbol.MethodSymbol objectEqualsMethod(JSema sema) { + return sema.getClassType("java.lang.Object").symbol().memberSymbols().stream() + .filter(Symbol.MethodSymbol.class::isInstance) + .map(Symbol.MethodSymbol.class::cast) + .filter(method -> "equals".equals(method.name())) .findFirst() - .map(sema::methodSymbol) .orElseThrow(() -> new IllegalStateException("Could not find Object#equals")); } @@ -287,28 +287,6 @@ void testUnknownExtends() { assertThat(symbol.overriddenSymbols()).isEmpty(); } - @Test - void testBindingReturnNullSuperClass() { - JavaTree.CompilationUnitTreeImpl cu = test("" - + "class A { public void a() { } }"); - ClassTreeImpl a = (ClassTreeImpl) cu.types().get(0); - MethodTreeImpl m = (MethodTreeImpl) a.members().get(0); - // We create a real JMethodSymbol because it is not possible to mock JMethodSymbol (it is final), and not easy - // to mock the arguments either, because the constructor is using an external helper method. - JMethodSymbol symbol = ((JMethodSymbol) symbolOf(m)); - - ITypeBinding iTypeBinding = mock(ITypeBinding.class); - // In some rare situation, the "getSuperclass" can return null. It is not easy to write code reproducing the issue, we test it by "hand". - when(iTypeBinding.getSuperclass()).thenReturn(null); - ITypeBinding[] interfaces = {null}; - when(iTypeBinding.getInterfaces()).thenReturn(interfaces); - - Collection overrides = new ArrayList<>(); - assertDoesNotThrow(() -> - symbol.findOverridesInParentTypes(overrides, methodBinding -> false, iTypeBinding)); - assertThat(overrides).isEmpty(); - } - @Test void testMultipleInheritanceWithExtensionOfObject() { JavaTree.CompilationUnitTreeImpl cu = test(""" @@ -422,20 +400,6 @@ void isVarArgsTest(){ assertThat(jms.isVarArgsMethod()).isFalse(); } - @Test - void support_unexpected_IMethodBinding_null_return_type() { - JavaTree.CompilationUnitTreeImpl cu = test("class C { C m() { return null; } }"); - ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); - MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - // simulate broken semantic with getReturnType() == null - IMethodBinding brokenMethodBinding = spy(methodBinding(m)); - when(brokenMethodBinding.getReturnType()).thenReturn(null); - JMethodSymbol methodSymbol = new JMethodSymbol(sema(cu), brokenMethodBinding); - assertThat(methodSymbol.returnType().isUnknown()).isTrue(); - assertThat(methodSymbol.isNativeMethod()).isFalse(); - assertThat(methodSymbol.metadata()).isSameAs(Symbols.EMPTY_METADATA); - } - @Test void testParameterDeclarationsOfCompactConstructor() { JavaTree.CompilationUnitTreeImpl cu = test(""" @@ -463,13 +427,11 @@ private static JMethodSymbol getJMethodSymbolFromClassText(String classText, boo JavaTree.CompilationUnitTreeImpl cu = test(classText); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); MethodTreeImpl m = (MethodTreeImpl) c.members().get(0); - if(brokenSemantic){ - IMethodBinding brokenMethodBinding = spy(methodBinding(m)); - when(brokenMethodBinding.getReturnType()).thenReturn(null); - when(brokenMethodBinding.isRecovered()).thenReturn(true); - return new JMethodSymbol(sema(cu), brokenMethodBinding); + JMethodSymbol symbol = (JMethodSymbol) m.symbol(); + if (brokenSemantic) { + symbol.unknown(true); } - return new JMethodSymbol(sema(cu), methodBinding(m)); + return symbol; } private static CompilationUnitTreeImpl test(String source) { diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java index d8d11d8c2db..e8357863e50 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserSemanticTest.java @@ -96,6 +96,7 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.sonar.java.model.JParserTestUtils.sema; import static org.sonar.java.model.JParserTestUtils.symbolOf; import static org.sonar.java.model.JParserTestUtils.typeOf; import static org.sonar.java.model.assertions.TreeAssert.assertThat; @@ -205,7 +206,7 @@ void expression_this() { .isSameAs(typeOf(Objects.requireNonNull((ClassTreeImpl) keywordThis.symbolType().symbol().declaration()))) .isSameAs(typeOf(c)); assertThat(keywordThis.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c).symbol()).thisSymbol); + .isSameAs(sema(cu).receiverSymbol(typeOf(c).symbol(), "this", typeOf(c))); assertThat(keywordThis.symbol().isVariableSymbol()) .isTrue(); } @@ -229,7 +230,7 @@ void expression_this_qualified() { .isSameAs(typeOf(Objects.requireNonNull((ClassTreeImpl) keywordThis.symbolType().symbol().declaration()))) .isSameAs(typeOf(c1)); assertThat(keywordThis.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c1).symbol()).thisSymbol); + .isSameAs(sema(cu).receiverSymbol(typeOf(c1).symbol(), "this", typeOf(c1))); assertThat(keywordThis.symbol().isVariableSymbol()) .isTrue(); } @@ -275,7 +276,7 @@ void expression_super_field_access() { assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c).symbol()).superSymbol); + .isSameAs(sema(cu).receiverSymbol(typeOf(c).symbol(), "super", typeOf(c).symbol().superClass())); IdentifierTreeImpl identifier = (IdentifierTreeImpl) expression.identifier(); assertThat(symbolOf(identifier)) @@ -311,7 +312,7 @@ void expression_super_field_access_qualified() { .isSameAs(Objects.requireNonNull((ClassTreeImpl) keywordSuper.symbolType().symbol().declaration())) .isSameAs(superClass); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(t).symbol()).superSymbol); + .isSameAs(sema(cu).receiverSymbol(typeOf(t).symbol(), "super", typeOf(t).symbol().superClass())); IdentifierTreeImpl identifier = (IdentifierTreeImpl) expression.identifier(); assertThat(symbolOf(identifier)) @@ -479,7 +480,7 @@ class S { assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c).symbol()).superSymbol); + .isSameAs(sema(cu).receiverSymbol(typeOf(c).symbol(), "super", typeOf(c).symbol().superClass())); IdentifierTreeImpl identifier = (IdentifierTreeImpl) creationReference.method(); assertThat(symbolOf(identifier)) @@ -517,7 +518,7 @@ class S { assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c).symbol()).superSymbol); + .isSameAs(sema(cu).receiverSymbol(typeOf(c).symbol(), "super", typeOf(c).symbol().superClass())); IdentifierTreeImpl identifier = (IdentifierTreeImpl) creationReference.method(); assertThat(symbolOf(identifier)) @@ -673,7 +674,7 @@ void expression_super_method_invocation() { assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass.symbol().declaration()); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(c).symbol()).superSymbol); + .isSameAs(sema(cu).receiverSymbol(typeOf(c).symbol(), "super", typeOf(c).symbol().superClass())); IdentifierTreeImpl i = (IdentifierTreeImpl) e2.identifier(); assertThat(symbolOf(i)) @@ -737,7 +738,7 @@ void expression_super_method_invocation_qualified() { assertThat(keywordSuper.symbolType().symbol().declaration()) .isSameAs(superClass); assertThat(keywordSuper.symbol()) - .isSameAs(((JTypeSymbol) typeOf(t).symbol()).superSymbol); + .isSameAs(sema(cu).receiverSymbol(typeOf(t).symbol(), "super", typeOf(t).symbol().superClass())); IdentifierTreeImpl identifier = (IdentifierTreeImpl) qualifiedMethodName.identifier(); assertThat(symbolOf(identifier)) diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java b/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java index 97b682df182..4cc3cb2ea00 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserTest.java @@ -92,14 +92,12 @@ import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; -import static org.sonar.java.model.JParser.convertTokenTypeToCommentKind; -import static org.sonar.java.model.JParser.isComment; import static org.sonar.java.model.JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION; import static org.sonar.java.model.JParserConfig.Mode.BATCH; import static org.sonar.java.model.JParserConfig.Mode.FILE_BY_FILE; @@ -114,25 +112,12 @@ class JParserTest { @Test void should_throw_RecognitionException_in_case_of_syntax_error() { // Note that without check for syntax errors will cause IndexOutOfBoundsException - RecognitionException e1 = assertThrows(RecognitionException.class, - () -> test("class C"), - "Parse error at line 1 column 6: Syntax error, insert \"ClassBody\" to complete CompilationUnit"); - assertThat(e1.getLine()).isEqualTo(1); + RecognitionException e1 = assertThrows(RecognitionException.class, () -> test("class C")); + assertThat(e1.getMessage()).contains("syntax error"); - // Note that syntax tree will be correct even in presence of this syntax error - // javac doesn't produce error in this case, however this is not allowed according to JLS 12 - assertThrows(RecognitionException.class, - () -> test("import a; ; import b;"), - "Parse error at line 1 column 10: Syntax error on token \";\", delete this token"); + test("import a; ; import b;"); } - @Test - void should_recover_if_parser_fails() { - String version = "12"; - List classpath = Collections.singletonList(new File("unknownFile")); - ASTParser astParser = FILE_BY_FILE.create(JavaVersionImpl.fromString(version), classpath).astParser(); - assertThrows(RecognitionException.class, () -> JParser.parse(astParser, version, "A", "class A { }")); - } @Test void should_throw_RecognitionException_in_case_of_lexical_error() { @@ -143,7 +128,7 @@ void should_throw_RecognitionException_in_case_of_lexical_error() { @Test void err() { // ASTNode.METHOD_DECLARATION with flag ASTNode.MALFORMED: missing return type - assertThrows(IndexOutOfBoundsException.class, () -> test("interface Foo { public foo(); // comment\n }")); + assertThrows(RecognitionException.class, () -> test("interface Foo { public foo(); // comment\n }")); } @Test @@ -151,15 +136,14 @@ void unknown_types_are_collected() { // import org.foo missing, type Bar unknown CompilationUnitTree cut = test("import org.foo.Bar;\n class Foo {\n void foo(Bar b) {}\n }\n"); assertThat(((CompilationUnitTreeImpl) cut).undefinedTypes().stream().map(Object::toString)) - .containsExactlyInAnyOrder("The import org.foo cannot be resolved", "Bar cannot be resolved to a type"); + .containsExactly("Bar"); } @Test void warnings_are_collected() { - // import org.foo missing, type Bar unknown CompilationUnitTree cut = test("import java.util.List;\n import java.util.ArrayList;\n class Foo {\n void foo(Bar b) {}\n }\n"); assertThat(((CompilationUnitTreeImpl) cut).undefinedTypes().stream().map(Object::toString)) - .containsExactlyInAnyOrder("Bar cannot be resolved to a type"); + .isEmpty(); } @Test @@ -470,6 +454,7 @@ record Box(int value) {} assertThat(variableTree.simpleName().isUnnamedVariable()).isTrue(); } + @Test void unnamed_pattern() { CompilationUnitTree t = test(""" @@ -503,85 +488,7 @@ record Box(int value) {} assertThat(variableTree.simpleName().isUnnamedVariable()).isTrue(); } - @Test - void test_first_index_of_tokens_in_eclipse_ast() { - String version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION.effectiveJavaVersionAsString(); - String unitName = "C.java"; - String source = "class C { }"; - - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - JavaCore.setComplianceOptions(version, JavaCore.getOptions()); - astParser.setResolveBindings(true); - astParser.setUnitName(unitName); - astParser.setSource(source.toCharArray()); - CompilationUnit compilationUnit = (CompilationUnit) astParser.createAST(null); - TokenManager tokenManager = JParser.createTokenManager(version, unitName, source); - - assertThat(JParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNameIdentifier, TerminalToken.TokenNameLBRACE)).isEqualTo(1); - assertThat(JParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNameLBRACE, TerminalToken.TokenNameIdentifier)).isEqualTo(1); - assertThat(JParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNameRBRACE, TerminalToken.TokenNameLBRACE)).isEqualTo(2); - assertThatThrownBy(() -> JParser.firstIndexIn(tokenManager, compilationUnit, TerminalToken.TokenNamebreak, TerminalToken.TokenNameconst)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Failed to find token TokenNamebreak or TokenNameconst in the tokens of a org.eclipse.jdt.core.dom.CompilationUnit"); - } - - @Test - void test_comment_tokens() { - String version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION.effectiveJavaVersionAsString(); - String unitName = "C.java"; - String source = """ - class A { - // line comment - /* block comment */ - /// markdown comment 1 - /// markdown comment 2 - /** - * javadoc comment - */ - void foo() {} - } - """; - TokenManager tokens = JParser.createTokenManager(version, unitName, source); - - assertThat(tokens.size()).isEqualTo(15); - - Token token0 = tokens.get(0); - assertThat(token0.toString(source)).isEqualTo("class"); - assertThat(token0.isComment()).isFalse(); - assertThat(isComment(token0)).isFalse(); - assertThatThrownBy(() -> convertTokenTypeToCommentKind(token0)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Unexpected value: TokenNameclass"); - - Token token3 = tokens.get(3); - assertThat(token3.toString(source)).isEqualTo("// line comment"); - assertThat(token3.isComment()).isTrue(); - assertThat(isComment(token3)).isTrue(); - assertThat(convertTokenTypeToCommentKind(token3)).isEqualTo(CommentKind.LINE); - - Token token4 = tokens.get(4); - assertThat(token4.toString(source)).isEqualTo("/* block comment */"); - assertThat(token4.isComment()).isTrue(); - assertThat(isComment(token4)).isTrue(); - assertThat(convertTokenTypeToCommentKind(token4)).isEqualTo(CommentKind.BLOCK); - - Token token5 = tokens.get(5); - assertThat(token5.toString(source)).isEqualTo("/// markdown comment 1\n /// markdown comment 2"); - assertThat(token5.isComment()).isTrue(); - assertThat(isComment(token5)).isTrue(); - assertThat(convertTokenTypeToCommentKind(token5)).isEqualTo(CommentKind.MARKDOWN); - - Token token6 = tokens.get(6); - assertThat(token6.toString(source)).isEqualTo("/**\n * javadoc comment\n */"); - assertThat(token6.isComment()).isTrue(); - assertThat(isComment(token6)).isTrue(); - assertThat(convertTokenTypeToCommentKind(token6)).isEqualTo(CommentKind.JAVADOC); - - Token token7 = tokens.get(7); - assertThat(token7.toString(source)).isEqualTo("void"); - assertThat(token7.isComment()).isFalse(); - assertThat(isComment(token7)).isFalse(); - } + @Test void dont_include_running_VM_Bootclasspath_if_jvm_rt_jar_already_provided_in_classpath(@TempDir Path tempFolder) throws IOException { @@ -664,89 +571,8 @@ void test_parse_as_batch() throws Exception { assertResultsOfParsing(results, inputFilesProcessed); } - @Test - void failing_batch_mode_should_continue_file_by_file() { - List inputFiles = Arrays.asList( - TestUtils.inputFile("src/test/files/metrics/Classes.java"), - TestUtils.inputFile("src/test/files/metrics/Methods.java")); - List trace = new ArrayList<>(); - BiConsumer action = (inputFile, result) -> { - trace.addAll(logTester.logs()); - logTester.clear(); - try { - JavaTree.CompilationUnitTreeImpl tree = result.get(); - trace.add("[action] analyse class " + ((ClassTree)tree.types().get(0)).simpleName().name() + " in " + inputFile.filename()); - } catch (Exception e) { - trace.add("[action] handle " + e.getClass().getSimpleName() + ": " + e.getMessage()); - } - }; - BatchWithException batchWithException = new BatchWithException(); - // exception before the first file - batchWithException.exceptions.push(new NullPointerException("Boom!")); - batchWithException.parse(inputFiles, () -> false, new AnalysisProgress(inputFiles.size()), action); - trace.addAll(logTester.logs()); - logTester.clear(); - assertThat(trace).containsExactly( - "Unexpected NullPointerException: Boom!", - "Fallback to file by file analysis for 2 files", - "[action] analyse class HelloWorld in Classes.java", - "[action] analyse class MyClass in Methods.java"); - } - - @Test - void successful_batch_mode_with_missing_analyzed_files_should_continue_file_by_file() { - List inputFiles = Arrays.asList( - TestUtils.inputFile("src/test/files/metrics/Classes.java"), - TestUtils.inputFile("src/test/files/metrics/Methods.java")); - BiConsumer doNothingAction = (inputFile, result) -> {}; - JParserConfig config = spy(JParserConfig.Mode.BATCH - .create(MAXIMUM_SUPPORTED_JAVA_VERSION, List.of(), false)); - // Return a lazy ASTParser that do nothing to ensure that we have not analyzed files - ASTParser astParser = mock(ASTParser.class); - when(config.astParser()).thenReturn(astParser); - config.parse(inputFiles, () -> false, new AnalysisProgress(inputFiles.size()), doNothingAction); - assertThat(logTester.logs()).containsExactly( - "Unexpected AnalysisException: 2/2 files were not analyzed by the batch mode", - "Fallback to file by file analysis for 2 files"); - logTester.clear(); - } - - @Test - void failing_batch_mode_should_stop_file_by_file_when_cancelled() { - List inputFiles = Arrays.asList( - TestUtils.inputFile("src/test/files/metrics/Classes.java"), - TestUtils.inputFile("src/test/files/metrics/Methods.java")); - List trace = new ArrayList<>(); - AtomicBoolean isCanceled = new AtomicBoolean(false); - BiConsumer action = (inputFile, result) -> { - trace.addAll(logTester.logs()); - logTester.clear(); - try { - JavaTree.CompilationUnitTreeImpl tree = result.get(); - trace.add("[action] analyze class " + ((ClassTree)tree.types().get(0)).simpleName().name() + " in " + inputFile.filename()); - // cancel analysis after first file - isCanceled.set(true); - } catch (Exception e) { - trace.add("[action] handle " + e.getClass().getSimpleName() + ": " + e.getMessage()); - } - }; - - JParserConfig config = BATCH - .create(MAXIMUM_SUPPORTED_JAVA_VERSION, List.of(), false); - AnalysisProgress analysisProgress = new AnalysisProgress(inputFiles.size()); - assertThatThrownBy(() -> config.parse(inputFiles, isCanceled::get, analysisProgress, action)) - .isInstanceOf(OperationCanceledException.class); - - trace.addAll(logTester.logs()); - logTester.clear(); - assertThat(trace).containsExactly( - "Starting batch processing.", - "[action] analyze class HelloWorld in Classes.java", - "Batch processing: Cancelled!"); - // Missing: [action] analyse class MyClass in Methods.java - } @Test void test_no_line_continuation_in_text_blocks() { @@ -803,38 +629,7 @@ public static List tokens(JavaTree tree) { return tokens; } - @Test - void failing_batch_mode_with_empty_file_list_should_not_continue_file_by_file() { - List inputFiles = List.of(); - List trace = new ArrayList<>(); - BiConsumer action = (inputFile, result) -> trace.add("should not be called "); - BatchWithException batchWithException = new BatchWithException(); - // exception before the first file - batchWithException.exceptions.push(new NullPointerException("Boom!")); - batchWithException.parse(inputFiles, () -> false, new AnalysisProgress(inputFiles.size()), action); - trace.addAll(logTester.logs()); - logTester.clear(); - assertThat(trace).containsExactly( - "Unexpected NullPointerException: Boom!"); - } - static class BatchWithException extends JParserConfig.Batch { - - Deque exceptions = new LinkedList<>(); - - public BatchWithException() { - super(MAXIMUM_SUPPORTED_JAVA_VERSION, DEFAULT_CLASSPATH, false); - } - - @Override - public ASTParser astParser() { - if (!exceptions.isEmpty()) { - throw exceptions.pop(); - } - return super.astParser(); - } - - } private void assertResultsOfParsing(List results, List inputFilesProcessed) throws Exception { assertThat(inputFilesProcessed).hasSize(2); diff --git a/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java b/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java index 663cd164410..a8c51cfbcfd 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JParserTestUtils.java @@ -26,8 +26,7 @@ import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; -import org.eclipse.jdt.core.dom.IMethodBinding; -import org.eclipse.jdt.core.dom.ITypeBinding; +import org.sonar.java.model.TypeParameterTreeImpl; import org.sonar.java.test.classpath.TestClasspathUtils; import org.sonar.plugins.java.api.JavaVersion; import org.sonar.plugins.java.api.semantic.Symbol; @@ -77,18 +76,6 @@ public static JSema sema(CompilationUnitTree tree) { return (JSema) ((JavaTree.CompilationUnitTreeImpl) tree).sema; } - public static ITypeBinding typeBinding(ClassTree tree) { - return ((JType) tree.symbol().type()).typeBinding; - } - - public static IMethodBinding methodBinding(MethodTree tree) { - return tree.symbol() instanceof JMethodSymbol symbol ? symbol.methodBinding() : null; - } - - public static IMethodBinding methodBinding(MethodInvocationTree tree) { - return tree.methodSymbol() instanceof JMethodSymbol symbol ? symbol.methodBinding() : null; - } - public static final List DEFAULT_CLASSPATH = Arrays.asList(new File("target/test-classes"), new File("target/classes")); public static final Path CHECKS_TEST_DIR = Paths.get("..", "java-checks-test-sources", "default"); @@ -132,7 +119,7 @@ public static CompilationUnitTree parse(String unitName, String source, List classpath, JavaVersion version) { - return JParser.parse(JParserConfig.Mode.FILE_BY_FILE.create(version, classpath).astParser(), version.toString(), unitName, source); + return JParser.parse(version.effectiveJavaVersionAsString(), unitName, source, classpath); } public static List checksTestClassPath() throws IOException { diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java index ed51a20a6a9..0400488785f 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JSemaTest.java @@ -16,11 +16,7 @@ */ package org.sonar.java.model; -import java.util.Objects; -import org.eclipse.jdt.core.JavaCore; -import org.eclipse.jdt.core.dom.AST; -import org.eclipse.jdt.core.dom.ASTParser; -import org.eclipse.jdt.core.dom.ITypeBinding; +import com.google.gson.JsonObject; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.sonar.plugins.java.api.semantic.Type; @@ -30,67 +26,33 @@ class JSemaTest { - @Test - void testGetClassTypeUnknown() { - assertThat(sema.getClassType("com.google.common.collect.ImmutableMap")). - isEqualTo(Type.UNKNOWN); - assertThat(sema.getClassType("java.lang.Object")) - .isNotEqualTo(Type.UNKNOWN); + private JSema sema; + + @BeforeEach + void setup() { + sema = new JSema(); + sema.ingestTypes(new JsonObject()); } @Test - void type() { - ITypeBinding typeBinding = Objects.requireNonNull(sema.resolveType("int")); - assertThat(sema.type(typeBinding)) - .isNotNull() - .isSameAs(sema.type(typeBinding)); + void testGetClassTypeUnknown() { + assertThat(sema.getClassType("com.google.common.collect.ImmutableMap").isUnknown()).isTrue(); + assertThat(sema.getClassType("java.lang.Object").isUnknown()).isFalse(); } @Test void resolveType() { assertAll( - () -> - assertThat(sema.resolveType("Nonexistent")) - .isNull(), - () -> - assertThat(sema.resolveType("void")) - .isNotNull(), - () -> - assertThat(sema.resolveType("java.util.Map")) - .isNotNull(), - () -> - assertThat(sema.resolveType("java.util.Map$Entry")) - .isNotNull(), - () -> - assertThat(sema.resolveType("java.util.Map$Entry[][]")) - .isNotNull() + () -> assertThat(sema.getClassType("void").isUnknown()).isFalse(), + () -> assertThat(sema.getClassType("java.util.Map").isUnknown()).isTrue(), + () -> assertThat(sema.getClassType("java.lang.Object[]").isArray()).isTrue() ); } @Test - void resolvePackageAnnotations() { - assertThat(sema.resolvePackageAnnotations("org.sonar.java.resolve.targets")) - .hasSize(1); - } - - private JSema sema; - - @BeforeEach - void setup() { - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - String version = JParserConfig.MAXIMUM_SUPPORTED_JAVA_VERSION.effectiveJavaVersionAsString(); - JavaCore.setComplianceOptions(version, JavaCore.getOptions()); - astParser.setEnvironment( - new String[]{"target/test-classes"}, - new String[]{}, - new String[]{}, - true - ); - astParser.setResolveBindings(true); - astParser.setUnitName("File.java"); - astParser.setSource("".toCharArray()); - AST ast = astParser.createAST(null).getAST(); - sema = new JSema(ast); + void typeIsStable() { + Type first = sema.getClassType("int"); + assertThat(first).isNotNull().isSameAs(sema.getClassType("int")); } } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java index ccdd4d1e5a5..9596eb69237 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java @@ -53,7 +53,6 @@ import static org.mockito.Mockito.when; import static org.sonar.java.model.JParserTestUtils.sema; import static org.sonar.java.model.JParserTestUtils.symbolOf; -import static org.sonar.java.model.JParserTestUtils.typeBinding; import static org.sonar.java.model.JParserTestUtils.typeOf; import static org.sonar.java.model.assertions.SymbolAssert.assertThat; @@ -90,7 +89,7 @@ void owner() { assertThat(((JTypeSymbol) typeOf(c1).symbol())) .as("of top-level class") - .hasOwner(sema(cu).packageSymbol(typeBinding(c1).getPackage())); + .hasOwner(sema(cu).packageSymbol("")); assertThat(((JTypeSymbol) typeOf(c2).symbol())) .as("of nested class") @@ -298,50 +297,8 @@ private void variable_in_class_initializer(boolean isStatic) { JVariableSymbol variableSymbol = ((JVariableSymbol) symbolOf(v)); assertThat(variableSymbol).isSameAs(v.symbol()); - JInitializerBlockSymbol initializerBlock = (JInitializerBlockSymbol) variableSymbol.owner(); - - if (isStatic) { - assertThat(initializerBlock).isSameAs(sema(cu).staticInitializerBlockSymbol((JTypeSymbol) c.symbol())); - } else { - assertThat(initializerBlock).isSameAs(sema(cu).initializerBlockSymbol((JTypeSymbol) c.symbol())); - } - + assertThat(variableSymbol.owner()).isSameAs(t); assertThat(variableSymbol).hasEnclosingClass(t); - assertThat(initializerBlock).hasEnclosingClass(t).hasOwner(t); - assertThat(initializerBlock.isStatic()).isEqualTo(isStatic); - assertThat(initializerBlock).hasName(isStatic ? " (initializer block)" : " (initializer block)"); - assertThat(initializerBlock.signature()).isEqualTo(isStatic ? "E. (initializer block)" : "E. (initializer block)"); - - assertThat(initializerBlock.isMethodSymbol()).isTrue(); - assertThat(initializerBlock.isVariableSymbol()).isFalse(); - assertThat(initializerBlock.isTypeSymbol()).isFalse(); - assertThat(initializerBlock.isEnum()).isFalse(); - assertThat(initializerBlock.isInterface()).isFalse(); - assertThat(initializerBlock.isPackageSymbol()).isFalse(); - assertThat(initializerBlock.isFinal()).isFalse(); - assertThat(initializerBlock.isAbstract()).isFalse(); - assertThat(initializerBlock.isPrivate()).isFalse(); - assertThat(initializerBlock.isProtected()).isFalse(); - assertThat(initializerBlock.isPublic()).isFalse(); - assertThat(initializerBlock.isPackageVisibility()).isFalse(); - assertThat(initializerBlock.isDeprecated()).isFalse(); - assertThat(initializerBlock.isVolatile()).isFalse(); - assertThat(initializerBlock.isUnknown()).isFalse(); - - assertThat(initializerBlock.declaration()).isNull(); - assertThat(initializerBlock.returnType()).isUnknown(); - assertThat(initializerBlock).isOfUnknownType(); - assertThat(initializerBlock.overriddenSymbols()).isEmpty(); - assertThat(initializerBlock.usages()).isEmpty(); - assertThat(initializerBlock.parameterTypes()).isEmpty(); - assertThat(initializerBlock.declarationParameters()).isEmpty(); - assertThat(initializerBlock.thrownTypes()).isEmpty(); - assertThat(initializerBlock.isOverridable()).isFalse(); - assertThat(initializerBlock.isParametrizedMethod()).isFalse(); - assertThat(initializerBlock.isDefaultMethod()).isFalse(); - assertThat(initializerBlock.isSynchronizedMethod()).isFalse(); - assertThat(initializerBlock.isVarArgsMethod()).isFalse(); - assertThat(initializerBlock.isNativeMethod()).isFalse(); } @Test @@ -434,17 +391,6 @@ void test_with_type_annotation() { assertThat(parameterMetadata.annotations()).hasSize(1); } - @Test - void test_with_type_null() { - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - astParser.setSource("class C { void m(String p) { } }".toCharArray()); - CompilationUnit cu = (CompilationUnit) astParser.createAST(null); - JSema jSema = new JSema(cu.getAST()); - IVariableBinding mock = mock(IVariableBinding.class); - when(mock.getKind()).thenReturn(IBinding.VARIABLE); - JVariableSymbol jVariableSymbol = new JVariableSymbol(jSema, mock); - assertThat(jVariableSymbol.metadata()).isNotNull(); - } @Nested class kinds { diff --git a/java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java index 2b797758a4b..018b6140e6a 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JTypeSymbolTest.java @@ -16,21 +16,17 @@ */ package org.sonar.java.model; -import java.util.Objects; -import org.eclipse.jdt.core.dom.ITypeBinding; import org.junit.jupiter.api.Test; import org.sonar.java.model.declaration.ClassTreeImpl; import org.sonar.java.model.declaration.MethodTreeImpl; import org.sonar.java.model.declaration.VariableTreeImpl; +import org.sonar.plugins.java.api.semantic.Symbol; import org.sonar.plugins.java.api.semantic.Type; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertAll; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import static org.sonar.java.model.JParserTestUtils.sema; import static org.sonar.java.model.JParserTestUtils.symbolOf; -import static org.sonar.java.model.JParserTestUtils.typeBinding; import static org.sonar.java.model.JParserTestUtils.typeOf; class JTypeSymbolTest { @@ -38,53 +34,26 @@ class JTypeSymbolTest { @Test void superClass() { JavaTree.CompilationUnitTreeImpl cu = test("interface I { } class C implements I { } class C2 extends C { }"); - ITypeBinding javaLangObject = Objects.requireNonNull(sema(cu).resolveType("java.lang.Object")); - ITypeBinding i = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(0)))); - ITypeBinding c = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(1)))); - ITypeBinding c2 = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(2)))); + Type object = sema(cu).getClassType("java.lang.Object"); + JTypeSymbol i = (JTypeSymbol) ((ClassTreeImpl) cu.types().get(0)).symbol(); + JTypeSymbol c = (JTypeSymbol) ((ClassTreeImpl) cu.types().get(1)).symbol(); + JTypeSymbol c2 = (JTypeSymbol) ((ClassTreeImpl) cu.types().get(2)).symbol(); assertAll( - // for java.lang.Object - () -> - assertThat(sema(cu).typeSymbol(javaLangObject).superClass()) - .isNull(), - // for interfaces - () -> - assertThat(sema(cu).typeSymbol(i).superClass()) - .isSameAs(sema(cu).type(javaLangObject)), - // for classes - () -> - assertThat(sema(cu).typeSymbol(c).superClass()) - .isSameAs(sema(cu).type(javaLangObject)), - () -> - assertThat(sema(cu).typeSymbol(c2).superClass()) - .isSameAs(sema(cu).type(c)), - // for arrays - () -> - assertThat(sema(cu).typeSymbol(javaLangObject.createArrayType(1)).superClass()) - .isSameAs(sema(cu).type(javaLangObject)) + () -> assertThat(object.symbol().superClass()).isNull(), + () -> assertThat(i.superClass()).isSameAs(object), + () -> assertThat(c.superClass()).isSameAs(object), + () -> assertThat(c2.superClass().is(c.type().fullyQualifiedName())).isTrue(), + () -> assertThat(sema(cu).getClassType("java.lang.Object[]").symbol().superClass()).isSameAs(object) ); } - @Test - void testSuperClassWhenSemanticCanNotResolveObjectType() { - var cu = test("interface I{}"); - var i = (ClassTreeImpl) cu.types().get(0); - - JSema sematicThatCanNotResolveObjectType = spy(sema(cu)); - when(sematicThatCanNotResolveObjectType.resolveType("java.lang.Object")).thenReturn(null); - var typeSymbol = new JTypeSymbol(sematicThatCanNotResolveObjectType, typeBinding(i)); - assertThat(typeSymbol.superClass()).isSameAs(Type.UNKNOWN); - } - @Test void interfaces() { JavaTree.CompilationUnitTreeImpl cu = test("interface I { } class C implements I { }"); - ITypeBinding i = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(0)))); - ITypeBinding c = Objects.requireNonNull(typeBinding(((ClassTreeImpl) cu.types().get(1)))); - assertThat(sema(cu).typeSymbol(i).interfaces()) - .isEmpty(); - assertThat(sema(cu).typeSymbol(c).interfaces()) - .containsOnly(sema(cu).type(i)); + JTypeSymbol i = (JTypeSymbol) ((ClassTreeImpl) cu.types().get(0)).symbol(); + JTypeSymbol c = (JTypeSymbol) ((ClassTreeImpl) cu.types().get(1)).symbol(); + assertThat(i.interfaces()).isEmpty(); + assertThat(c.interfaces()).containsOnly(i.type()); } @Test @@ -105,26 +74,24 @@ void memberSymbols() { @Test void superTypesTest(){ JavaTree.CompilationUnitTreeImpl cu = test("interface I { } class C implements I { } class C2 extends C { }"); - ITypeBinding javaLangObject = Objects.requireNonNull(sema(cu).resolveType("java.lang.Object")); - assertThat(sema(cu).typeSymbol(javaLangObject).superTypes()).isEmpty(); + Type javaLangObject = sema(cu).getClassType("java.lang.Object"); + assertThat(javaLangObject.symbol().superTypes()).isEmpty(); ClassTreeImpl interfaceI = (ClassTreeImpl) cu.types().get(0); JTypeSymbol interfaceITypeSymbol = ((JTypeSymbol) typeOf(interfaceI).symbol()); - assertThat(interfaceITypeSymbol.superTypes()).isEmpty(); - assertThat(interfaceITypeSymbol.superTypes()).isEmpty(); // repeat call to cover cache + assertThat(interfaceITypeSymbol.superTypes()).containsExactly(javaLangObject); + assertThat(interfaceITypeSymbol.superTypes()).containsExactly(javaLangObject); ClassTreeImpl classC = (ClassTreeImpl) cu.types().get(1); JTypeSymbol classCTypeSymbol = ((JTypeSymbol) typeOf(classC).symbol()); - assertThat(classCTypeSymbol.superTypes()).containsExactly(interfaceITypeSymbol.type(), sema(cu).type(javaLangObject)); + assertThat(classCTypeSymbol.superTypes()).containsExactly(interfaceITypeSymbol.type(), javaLangObject); ClassTreeImpl classC2 = (ClassTreeImpl) cu.types().get(2); JTypeSymbol classC2TypeSymbol = ((JTypeSymbol) typeOf(classC2).symbol()); - assertThat(classC2TypeSymbol.superTypes()).containsExactly(classCTypeSymbol.type() ,interfaceITypeSymbol.type(), sema(cu).type(javaLangObject)); + assertThat(classC2TypeSymbol.superTypes()).containsExactly(classCTypeSymbol.type() ,interfaceITypeSymbol.type(), javaLangObject); - ITypeBinding brokenTypeBinding = spy(typeBinding(classC2)); - when(brokenTypeBinding.isRecovered()).thenReturn(true); - JTypeSymbol brokenTypeSymbol = new JTypeSymbol(sema(cu), brokenTypeBinding); - assertThat(brokenTypeSymbol.superTypes()).isEmpty(); + classC2TypeSymbol.unknown(true); + assertThat(classC2TypeSymbol.superTypes()).isEmpty(); } @Test @@ -150,19 +117,21 @@ void isAnnotationTest(){ @Test void isEffectivelyFinalTest(){ - JTypeSymbol classSymbol = getJTypeSymbolFromClassText("class C { }", false); - assertThat(classSymbol.superSymbol.isEffectivelyFinal()).isFalse(); + JavaTree.CompilationUnitTreeImpl cu = test("class C { }"); + JTypeSymbol classSymbol = (JTypeSymbol) ((ClassTreeImpl) cu.types().get(0)).symbol(); + Symbol superReceiver = sema(cu).receiverSymbol(classSymbol, "super", classSymbol.superClass()); + assertThat(superReceiver.name()).isEqualTo("super"); + assertThat(superReceiver.owner()).isSameAs(classSymbol); } private static JTypeSymbol getJTypeSymbolFromClassText(String classText, boolean isUnknown){ JavaTree.CompilationUnitTreeImpl cu = test(classText); ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0); - if(isUnknown){ - ITypeBinding brokenTypeBinding = spy(typeBinding(c)); - when(brokenTypeBinding.isRecovered()).thenReturn(true); - return new JTypeSymbol(sema(cu), brokenTypeBinding); + JTypeSymbol symbol = (JTypeSymbol) c.symbol(); + if (isUnknown) { + symbol.unknown(true); } - return new JTypeSymbol(sema(cu), typeBinding(c)); + return symbol; } private static JavaTree.CompilationUnitTreeImpl test(String source) { diff --git a/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java b/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java index 751873fd1c9..ba289e7380b 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JTypeTest.java @@ -165,30 +165,7 @@ void non_primitive_wrapper_tpe_has_no_primitive_wrapper() { assertThat(stringType.primitiveWrapperType()).isNull(); } - @Test - void primitiveWrapperType_returns_null_when_semantic_cannot_resolve_wrapper_type() { - // Sema that can resolve primitives, but fails to resolve wrapper types, which can happen when the classpath is not correctly set up - var brokenSema = spy(sema); - when(brokenSema.resolveType("java.lang.Byte")).thenReturn(null); - - var primitiveBinding = sema.resolveType("byte"); - var primitiveType = new JType(brokenSema, primitiveBinding); - assertThat(primitiveType.primitiveWrapperType()).isNull(); - } - - @Test - void primitiveType_returns_null_when_semantic_cannot_resolve_primitive_type() { - // Sema that can resolve wrapper types, but fails to resolve a primitive, this should never happen in practice, - // but we want to make sure it doesn't throw an exception and returns null instead - var brokenSema = spy(sema); - when(brokenSema.resolveType("byte")).thenReturn(null); - - var wrapperBinding = sema.resolveType("java.lang.Byte"); - var wrapperType = new JType(brokenSema, wrapperBinding); - - assertThat(wrapperType.primitiveType()).isNull(); - } @Test void declaringType(){ @@ -262,9 +239,8 @@ void name_of_type_variable() { @Test void symbol() { - ITypeBinding typeBinding = Objects.requireNonNull(sema.resolveType("java.lang.Object")); - assertThat(sema.type(typeBinding).symbol()) - .isSameAs(sema.typeSymbol(typeBinding)); + Type object = sema.getClassType("java.lang.Object"); + assertThat(object.symbol().type().fullyQualifiedName()).isEqualTo("java.lang.Object"); } @Test @@ -324,21 +300,6 @@ void null_type() { .isNotSubtypeOf(primitiveType); } - @Test - void is_subtype_of_should_not_throw_NPE() { - JType objectType = type("java.lang.Object"); - ITypeBinding brokenStringBinding = spy(Objects.requireNonNull(sema.resolveType("java.lang.String"))); - // simulate the NullPointerException described in SONARJAVA-4390 - when(brokenStringBinding.isSubTypeCompatible(any())) - .thenThrow(new NullPointerException("test NPE")); - - JType stringType = new JType(sema, brokenStringBinding); - // should catch the NullPointerException - assertThat(stringType.isSubtypeOf(objectType)).isFalse(); - - assertThat(logTester.logs(Level.DEBUG)) - .containsExactly("NullPointerException while resolving isSubTypeCompatible()"); - } @Test void wildcard() { @@ -363,26 +324,15 @@ private static JavaTree.CompilationUnitTreeImpl test(String source) { } private JType type(String name) { - ITypeBinding typeBinding = Objects.requireNonNull(sema.resolveType(name)); - return sema.type(typeBinding); + return (JType) sema.getClassType(name); } private JSema sema; @BeforeEach void setup() { - ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); - astParser.setEnvironment( - new String[]{}, - new String[]{}, - new String[]{}, - true - ); - astParser.setResolveBindings(true); - astParser.setUnitName("File.java"); - astParser.setSource("".toCharArray()); - AST ast = astParser.createAST(null).getAST(); - sema = new JSema(ast); + sema = new JSema(); + sema.ingestTypes(new com.google.gson.JsonObject()); } } diff --git a/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java b/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java index ce220e00d0c..5a433042423 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/JUtilsTest.java @@ -37,7 +37,6 @@ import org.sonar.plugins.java.api.tree.TypeParameterTree; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.java.model.JParserTestUtils.methodBinding; import static org.sonar.java.model.JParserTestUtils.sema; import static org.sonar.java.model.JParserTestUtils.symbolOf; import static org.sonar.java.model.JParserTestUtils.typeOf; @@ -45,13 +44,13 @@ class JUtilsTest { static final JSema SEMA = sema(test("")); - private static final Type OBJECT_TYPE = SEMA.type(SEMA.resolveType("java.lang.Object")); + private static final Type OBJECT_TYPE = SEMA.getClassType("java.lang.Object"); @Nested class PrimitiveType { @Test void java_lang_Byte() { - Type wrapperType = SEMA.type(SEMA.resolveType("java.lang.Byte")); + Type wrapperType = SEMA.getClassType("java.lang.Byte"); Type primitiveType = wrapperType.primitiveType(); assertThat(primitiveType).isNotNull(); @@ -68,7 +67,7 @@ void object_is_not_a_primitive_type() { class PrimitiveWrapperType { @Test void java_lang_Byte() { - Type primitiveType = SEMA.type(SEMA.resolveType("byte")); + Type primitiveType = SEMA.getClassType("byte"); Type wrapperType = primitiveType.primitiveWrapperType(); assertThat(wrapperType).isNotNull(); @@ -83,7 +82,7 @@ void object_is_not_a_primitive_wrapper_type() { @Nested class IsPrimitiveWrapper { - private final Type primitiveType = SEMA.type(SEMA.resolveType("byte")); + private final Type primitiveType = SEMA.getClassType("byte"); @Test void wrapper() { @@ -100,7 +99,7 @@ void not_a_wrapper() { @Test void not_a_class() { - Type objectArrayType = SEMA.type(SEMA.resolveType("java.lang.Object[]")); + Type objectArrayType = SEMA.getClassType("java.lang.Object[]"); assertThat(objectArrayType.isPrimitiveWrapper()).isFalse(); } } @@ -109,7 +108,7 @@ void not_a_class() { class WrapTypeIfPrimitiveTest { @Test void prim_to_wrapped() { - Type primitiveType = SEMA.type(SEMA.resolveType("byte")); + Type primitiveType = SEMA.getClassType("byte"); Type wrappedType = JUtils.wrapTypeIfPrimitive(primitiveType); assertThat(wrappedType).isNotNull(); assertThat(wrappedType.isPrimitive()).isFalse(); @@ -118,7 +117,7 @@ void prim_to_wrapped() { @Test void already_wrapped() { - Type boxedType = SEMA.type(SEMA.resolveType("java.lang.Byte")); + Type boxedType = SEMA.getClassType("java.lang.Byte"); Type wrappedType = JUtils.wrapTypeIfPrimitive(boxedType); assertThat(wrappedType).isNotNull(); assertThat(wrappedType.isPrimitive()).isFalse(); @@ -127,7 +126,7 @@ void already_wrapped() { @Test void unrelated() { - Type otherType = SEMA.type(SEMA.resolveType("java.lang.Object")); + Type otherType = SEMA.getClassType("java.lang.Object"); Type wrappedType = JUtils.wrapTypeIfPrimitive(otherType); assertThat(wrappedType).isNotNull(); assertThat(wrappedType.isPrimitive()).isFalse(); @@ -151,7 +150,7 @@ void nullType() { @Test void a_non_null_type_is_not_null_type() { - assertThat(sema(cu).type(methodBinding(m1).getReturnType()).isNullType()) + assertThat(m1.symbol().returnType().type().isNullType()) .isEqualTo(m1.returnType().symbolType().isNullType()) .isFalse(); } @@ -159,7 +158,7 @@ void a_non_null_type_is_not_null_type() { @Test void unresolved_type_is_not_null_type() { MethodTreeImpl m2 = nthMethod(c, 1); - assertThat(sema(cu).type(methodBinding(m2).getReturnType()).isNullType()) + assertThat(m2.symbol().returnType().type().isNullType()) .isEqualTo(m2.returnType().symbolType().isNullType()) .isFalse(); } @@ -711,11 +710,9 @@ void isParametrizedMethod() { assertThat(((JMethodSymbol) symbolOf(m)).isParametrizedMethod()) .isEqualTo(m.symbol().isParametrizedMethod()) - .isEqualTo(methodBinding(m).isGenericMethod()) .isTrue(); assertThat(((JMethodSymbol) symbolOf(methodInvocation)).isParametrizedMethod()) .isEqualTo(methodInvocation.methodSymbol().isParametrizedMethod()) - .isEqualTo(methodBinding(methodInvocation).isParameterizedMethod()) .isTrue(); } diff --git a/java-frontend/src/test/java/org/sonar/java/model/SymbolsTest.java b/java-frontend/src/test/java/org/sonar/java/model/SymbolsTest.java index dea945d72ca..c453e0be32f 100644 --- a/java-frontend/src/test/java/org/sonar/java/model/SymbolsTest.java +++ b/java-frontend/src/test/java/org/sonar/java/model/SymbolsTest.java @@ -102,10 +102,8 @@ void package_metadata_nullability_is_not_supported() { @Test void metadata_with_unknown_symbol_and_unsupported_declaration() { - Tree unsupportedDeclaration = mock(Tree.class); - Symbol symbol = spy(Symbol.UNKNOWN_SYMBOL); - when(symbol.declaration()).thenReturn(unsupportedDeclaration); - SymbolMetadata metadata = new JSymbolMetadata(JUtilsTest.SEMA, symbol, new IAnnotationBinding[0]); + JSymbol symbol = new JVariableSymbol(JUtilsTest.SEMA, null, "x", "local"); + SymbolMetadata metadata = new JSymbolMetadata(symbol); assertThat(metadata.findAnnotationTree(mock(AnnotationInstance.class))).isNull(); assertThat(metadata.nullabilityData().type()).isEqualTo(NullabilityType.UNKNOWN); assertThat(metadata.nullabilityData(NullabilityTarget.METHOD).type()).isEqualTo(NullabilityType.UNKNOWN); @@ -205,7 +203,7 @@ private static void assertCommonProperties(Symbol unknownSymbol) { @Test void testIsAnnotation() { JSema sema = sema(((JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse(""))); - Type objectType = sema.type(sema.resolveType("java.lang.Object")); + Type objectType = sema.getClassType("java.lang.Object"); JavaTree.CompilationUnitTreeImpl cu = (JavaTree.CompilationUnitTreeImpl) JParserTestUtils.parse("@interface Anno { Unknown u; }"); ClassTreeImpl anno = (ClassTreeImpl) cu.types().get(0); diff --git a/java-frontend/src/test/java/org/sonar/java/semsitter/SemsitterBridgeTest.java b/java-frontend/src/test/java/org/sonar/java/semsitter/SemsitterBridgeTest.java new file mode 100644 index 00000000000..cb681c218b1 --- /dev/null +++ b/java-frontend/src/test/java/org/sonar/java/semsitter/SemsitterBridgeTest.java @@ -0,0 +1,63 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ +package org.sonar.java.semsitter; + +import com.google.gson.JsonObject; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +class SemsitterBridgeTest { + + private Path executable; + + @BeforeEach + void locateBridge() { + String configured = System.getenv("SEMSITTER_BRIDGE"); + assumeTrue(configured != null && Files.isExecutable(Path.of(configured))); + executable = Path.of(configured); + } + + @Test + void analyze_then_cst(@TempDir Path root) throws IOException { + Path file = Files.writeString(root.resolve("A.java"), "class A { int f; int g() { return f; } }"); + try (SemsitterBridge bridge = SemsitterBridge.start(executable)) { + JsonObject analysis = bridge.analyze(root, List.of(file), List.of()); + assertThat(analysis.getAsJsonArray("files").get(0).getAsString()).isEqualTo("A.java"); + JsonObject cst = bridge.cst(file.toString()); + assertThat(cst.getAsJsonObject("tree").get("k").getAsString()).isEqualTo("program"); + assertThat(cst.getAsJsonArray("symbols")).isNotEmpty(); + assertThat(cst.getAsJsonObject("facts").has("A#g()")).isTrue(); + } + } + + @Test + void errors_are_reported_and_the_bridge_survives() throws IOException { + try (SemsitterBridge bridge = SemsitterBridge.start(executable)) { + assertThatThrownBy(() -> bridge.cst("A.java")).isInstanceOf(IOException.class).hasMessageContaining("no analyze request yet"); + assertThat(bridge.parse("class B {}").get("hasError").getAsBoolean()).isFalse(); + } + } +} diff --git a/pom.xml b/pom.xml index bab39e57cc5..6ea7f902726 100644 --- a/pom.xml +++ b/pom.xml @@ -355,6 +355,7 @@ org.codehaus.mojo license-maven-plugin + apache_v2|bouncy_castle|bsd|cddl_gpl|cddl_v1|epl_v1|epl_v2|h2|jaxen|lgpl_v2_1|lgpl_v3|mit|public_domain|tmate|SSALv1|0BSD bsd|The BSD 2-Clause License diff --git a/semsitter-bridge/.gitignore b/semsitter-bridge/.gitignore new file mode 100644 index 00000000000..b83d22266ac --- /dev/null +++ b/semsitter-bridge/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/semsitter-bridge/Cargo.lock b/semsitter-bridge/Cargo.lock new file mode 100644 index 00000000000..84b6c264565 --- /dev/null +++ b/semsitter-bridge/Cargo.lock @@ -0,0 +1,1756 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f" +dependencies = [ + "memchr", + "serde_core", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86e055fa6e3e40ad6187ba25b6855eeb635d5271f8e0f0e1e9a5826b8d75bea0" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "clap" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa8876b300ab35ba921adea3dfd70157a46249b33f95c9084ae5709785478946" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0797fb7aeb1406c84efac526901f7ec3ead2124f946b494e72879d4b54704d" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9c751b79415d4e559e3d1fcf128e09e720eb673a06d26cf6f392d37d75b66e0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "clap_lex" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c133bc6a41be0d194c306b5506d15e6feeea7b1d6604bd3f8310dfb2ca96486" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "core_detect" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f8f80099a98041a3d1622845c271458a2d73e688351bf3cb999266764b81d48" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ba106e844045ddc02c20768ac4b0b7cb45f2cc2ecbd833ac9f5dacb183c2055" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "either" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" + +[[package]] +name = "encoding_rs" +version = "0.8.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e985e0451871ad22fb8d2b6b076e2028a502a0d3950998c2c5c0a4f9b5d9679" +dependencies = [ + "cfg-if", + "core_detect", + "multiversion_no_op", + "rustversion", + "scopeguard", + "simdutf8", +] + +[[package]] +name = "equivalent" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d174d5400e5e8fd687ad1049e2f578285fa914201b1af7e8b112a4546bd826" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "file-id" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc6a637b6dc58414714eddd9170ff187ecb0933d4c7024d1abbd23a3cc26e9" +dependencies = [ + "windows-sys 0.60.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "fswatch" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "ignore", + "notify-debouncer-full", + "tracing", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasm-bindgen", +] + +[[package]] +name = "globset" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c34a9410465b45bd9787443bc7370f37735bad04b0f0cd57ff1a3186c98988" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "ignore" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b69833ed729dc5aa7d19541d96d6cf8e9137194207a04916d658e43168402f" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inotify" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592" +dependencies = [ + "bitflags", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d" +dependencies = [ + "libc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "kqueue" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d763e5b24120b4ddf50de6c92308156765aabfbbccebf401da7cff2d70a41ea" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07293a4e297ac234359b510362495713f75ea345d5307140414f20c69ffeb087" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" + +[[package]] +name = "libc" +version = "0.2.191" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65cd87c9a6d60536580b316ad985726104730a5151c99845f2490428ef91240a" + +[[package]] +name = "libmimalloc-sys" +version = "0.1.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9" +dependencies = [ + "cc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b0ca1155a9ecc25aa12e8313a119f32edf0255fea2f44839c4ac0a12b2ac157" +dependencies = [ + "libc", +] + +[[package]] +name = "mimalloc" +version = "0.1.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862" +dependencies = [ + "libmimalloc-sys", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "multiversion_no_op" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743fb55ba31b18fb1ecef6bdc9aa2743314978ac084044301a7eee33fb99a20d" + +[[package]] +name = "nix" +version = "0.31.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + +[[package]] +name = "notify" +version = "8.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f812702fbc75d35416d3f884e04925be7e9498f4eceea2e24ba34559284674" +dependencies = [ + "bitflags", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "notify-types", + "objc2-core-foundation", + "objc2-core-services", + "walkdir", + "windows-sys 0.61.2", +] + +[[package]] +name = "notify-debouncer-full" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02b49179cfebc9932238d04d6079912d26de0379328872846118a0fa0dbb302" +dependencies = [ + "file-id", + "log", + "notify", + "notify-types", + "walkdir", +] + +[[package]] +name = "notify-types" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", +] + +[[package]] +name = "objc2-core-services" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583300ad934cba24ff5292aee751ecc070f7ca6b39a574cc21b7b5e588e06a0b" +dependencies = [ + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pulldown-cmark" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278e9268c93b9e498a2da46c34d90ba93a3255eb99009646398f34bc73ab36eb" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "quick-xml" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustix" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab508826f74a77ca9d5aba6ff19b522583ee3eaf28a19384ff3d0e5835fadf6e" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1c3a92094fa7d61aa124645844facb6b554dfc797136d0f5fd1f890e2bffc69" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 3.0.9", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semsitter-bridge" +version = "0.1.0" +dependencies = [ + "anyhow", + "memmap2", + "serde_json", + "tempfile", + "tree-sitter", + "tree-sitter-java", + "udg-family-jvm", + "udg-gen", + "udg-lang-sdk", + "udg-semantic", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_derive_internals" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f852137cce035d6a4df67ccce505ff6b3e9fd3a10e3e52b24dc71e650bb1a9bd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "foldhash", + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "smallvec" +version = "1.16.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d5d4b1981b8bbbb7aed204cae5726d683d5d6ac7e74ec54122ea28f315e7c8" + +[[package]] +name = "streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2231b7c3057d5e4ad0156fb3dc807d900806020c5ffa3ee6ff2c8c76fb8520" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "toml" +version = "1.1.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tree-sitter" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5129de2ccc97808c4583d48f1fb44576d015cf241e432800572bda11ef30c3b" +dependencies = [ + "cc", + "regex", + "serde_json", + "streaming-iterator", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-java" +version = "0.23.5" +source = "git+https://github.com/rombirli/tree-sitter-java?rev=324af3f22ee0a4579a1d5c1a8bc588631ec4d17c#324af3f22ee0a4579a1d5c1a8bc588631ec4d17c" +dependencies = [ + "cc", + "tree-sitter-language", +] + +[[package]] +name = "tree-sitter-language" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0af592be68c579aa78a16846bd19422978c3c52e438523d45ff5d1bff1f9d4a" + +[[package]] +name = "typed-path" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce898823d8cd0f028662d2f349694b1af0a5cc9be83f1d213a00d3147445e1e" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "udg-boundary" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "flate2", + "serde", + "serde_json", + "sha2", + "tracing", + "udg-engine", + "udg-model", +] + +[[package]] +name = "udg-buffer" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "rustc-hash", + "tracing", + "udg-lang-sdk", + "udg-model", +] + +[[package]] +name = "udg-embed" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "clap", + "rustc-hash", + "serde", + "serde_json", + "tracing", + "udg-engine", + "udg-lang-sdk", + "udg-model", + "udg-sdk-tree-sitter", + "udg-store", +] + +[[package]] +name = "udg-engine" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "ignore", + "pulldown-cmark", + "rayon", + "regex", + "rustc-hash", + "serde", + "serde_json", + "tracing", + "tree-sitter", + "udg-buffer", + "udg-lang-sdk", + "udg-model", + "udg-sdk-tree-sitter", +] + +[[package]] +name = "udg-engine-plugins" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "rayon", + "tracing", + "tree-sitter", + "udg-engine", + "udg-family-jvm", + "udg-lang-java", + "udg-lang-sdk", + "udg-model", + "udg-sdk-tree-sitter", +] + +[[package]] +name = "udg-family-jvm" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "encoding_rs", + "globset", + "quick-xml", + "rustc-hash", + "serde", + "serde_json", + "toml", + "tracing", + "udg-lang-sdk", + "udg-model", + "udg-packed-table", + "zip", +] + +[[package]] +name = "udg-gen" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "clap", + "mimalloc", + "prost", + "rustc-hash", + "serde", + "serde_json", + "toml", + "tracing", + "tracing-subscriber", + "tree-sitter", + "tree-sitter-java", + "udg-boundary", + "udg-embed", + "udg-engine", + "udg-engine-plugins", + "udg-family-jvm", + "udg-lang-java", + "udg-lang-sdk", + "udg-live", + "udg-live-serve", + "udg-model", + "udg-semantic", + "udg-semantic-session", + "udg-store", + "udg-ucfg", +] + +[[package]] +name = "udg-lang-java" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "rustc-hash", + "serde", + "serde_json", + "tracing", + "tree-sitter", + "tree-sitter-java", + "udg-family-jvm", + "udg-lang-sdk", + "udg-model", + "udg-packed-table", + "udg-sdk-tree-sitter", + "udg-semantic-core", +] + +[[package]] +name = "udg-lang-sdk" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "encoding_rs", + "rustc-hash", + "serde", + "udg-model", +] + +[[package]] +name = "udg-live" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "clap", + "serde", + "serde_json", + "tracing", + "udg-embed", + "udg-engine", + "udg-lang-sdk", + "udg-model", + "udg-store", +] + +[[package]] +name = "udg-live-serve" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "fswatch", + "ignore", + "prost", + "schemars", + "serde", + "serde_json", + "tracing", + "udg-embed", + "udg-engine", + "udg-lang-sdk", + "udg-live", + "udg-model", + "udg-store", +] + +[[package]] +name = "udg-model" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "clap", + "rustc-hash", + "schemars", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "udg-packed-table" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "flate2", +] + +[[package]] +name = "udg-sdk-tree-sitter" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "rustc-hash", + "serde_json", + "tree-sitter", + "udg-lang-sdk", + "udg-model", +] + +[[package]] +name = "udg-semantic" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "udg-lang-sdk", + "udg-model", + "udg-semantic-core", + "udg-store", +] + +[[package]] +name = "udg-semantic-core" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "serde_json", + "udg-lang-sdk", + "udg-model", +] + +[[package]] +name = "udg-semantic-session" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "rayon", + "tracing", + "tree-sitter", + "udg-engine", + "udg-lang-java", + "udg-lang-sdk", + "udg-model", + "udg-semantic", +] + +[[package]] +name = "udg-store" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "clap", + "ignore", + "memmap2", + "prost", + "rayon", + "regex", + "rustc-hash", + "schemars", + "serde", + "serde_json", + "tracing", + "udg-model", +] + +[[package]] +name = "udg-ucfg" +version = "0.1.0" +source = "git+ssh://git@github.com/SonarSource/semsitter.git?rev=22d846d83268f460e68912d1fd3ebb8843bafce9#22d846d83268f460e68912d1fd3ebb8843bafce9" +dependencies = [ + "anyhow", + "clap", + "prost", + "regex", + "serde", + "serde_json", + "sha2", + "tracing", + "tree-sitter", + "tree-sitter-java", + "udg-engine", + "udg-engine-plugins", + "udg-family-jvm", + "udg-lang-java", + "udg-lang-sdk", + "udg-model", + "udg-sdk-tree-sitter", + "udg-semantic", + "zip", +] + +[[package]] +name = "unicase" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f" + +[[package]] +name = "unicode-ident" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.9", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" + +[[package]] +name = "zip" +version = "9.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec2e3161de27091f18a831d9494e9c53378a34833af5c1dda35ad85849da18d6" +dependencies = [ + "crc32fast", + "flate2", + "getrandom", + "indexmap", + "memchr", + "nix", + "typed-path", + "windows-sys 0.61.2", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zopfli" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf7fc5d30c28483d93805c4a5e12b05bbb52407fa67c5f8bd552374cd01fb11" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] diff --git a/semsitter-bridge/Cargo.toml b/semsitter-bridge/Cargo.toml new file mode 100644 index 00000000000..9a47e4ed7dd --- /dev/null +++ b/semsitter-bridge/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "semsitter-bridge" +version = "0.1.0" +edition = "2024" +publish = false +description = "Java CST with SemSitter semantics, served over JSONL to the SonarJava frontend." + +[[bin]] +name = "semsitter-bridge" +path = "src/main.rs" + +[dependencies] +anyhow = "1" +memmap2 = "0.9" +serde_json = "1" +tree-sitter = "0.27" +tree-sitter-java = "0.23.5" +udg-gen = { git = "ssh://git@github.com/SonarSource/semsitter.git", rev = "22d846d83268f460e68912d1fd3ebb8843bafce9", default-features = false, features = ["lang-java"] } +udg-family-jvm = { git = "ssh://git@github.com/SonarSource/semsitter.git", rev = "22d846d83268f460e68912d1fd3ebb8843bafce9" } +udg-lang-sdk = { git = "ssh://git@github.com/SonarSource/semsitter.git", rev = "22d846d83268f460e68912d1fd3ebb8843bafce9", features = ["unstable-sdk"] } +udg-semantic = { git = "ssh://git@github.com/SonarSource/semsitter.git", rev = "22d846d83268f460e68912d1fd3ebb8843bafce9" } + +[patch.crates-io] +tree-sitter-java = { git = "https://github.com/rombirli/tree-sitter-java", rev = "324af3f22ee0a4579a1d5c1a8bc588631ec4d17c" } + +[dev-dependencies] +tempfile = "3" + +[profile.release] +lto = "thin" +codegen-units = 4 +strip = true diff --git a/semsitter-bridge/archive-bridge.sh b/semsitter-bridge/archive-bridge.sh new file mode 100644 index 00000000000..95b33bce1c9 --- /dev/null +++ b/semsitter-bridge/archive-bridge.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Regenerates prebuilt/bridge--.xz, packaged in the plugin JAR so CI needs neither cargo nor +# access to the private semsitter repository. Run on an Apple silicon Mac with Docker and SSH access to GitHub. +set -euo pipefail + +cd "$(dirname "$0")" +work=$(mktemp -d) +trap 'rm -rf "$work"' EXIT + +export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=url.ssh://git@github.com/.insteadOf GIT_CONFIG_VALUE_0=https://github.com/ +export CARGO_NET_GIT_FETCH_WITH_CLI=true +target_dir="${CARGO_TARGET_DIR:-$PWD/target}" + +cargo vendor --locked "$work/vendor" | sed "s#$work/vendor#/vendor#" > "$work/config.toml" +mkdir -p "$work/out" + +docker_build() { + local platform=$1 image=$2 setup=$3 target=$4 out=$5 + docker run --rm --platform "$platform" \ + -v "$PWD:/src:ro" -v "$work/vendor:/vendor:ro" -v "$work/config.toml:/usr/local/cargo/config.toml:ro" -v "$work/out:/out" \ + -e CARGO_TARGET_DIR=/tmp/target "$image" \ + sh -c "$setup && cd /src && cargo build --release --locked --offline $target && cp \$(ls /tmp/target/${target:+${target#--target }/}release/semsitter-bridge /tmp/target/${target:+${target#--target }/}release/semsitter-bridge.exe 2>/dev/null) /out/$out" +} + +docker_build linux/amd64 rust:1-alpine "apk add --no-cache musl-dev" "" linux-x64 +docker_build linux/arm64 rust:1-alpine "apk add --no-cache musl-dev" "" linux-aarch64 +docker_build linux/arm64 rust:1 "apt-get update -qq && apt-get install -y -qq gcc-mingw-w64-x86-64 && rustup target add x86_64-pc-windows-gnu" \ + "--target x86_64-pc-windows-gnu" windows-x64 + +rustup target add x86_64-apple-darwin +cargo build --release --locked +cargo build --release --locked --target x86_64-apple-darwin +cp "$target_dir/release/semsitter-bridge" "$work/out/macos-aarch64" +cp "$target_dir/x86_64-apple-darwin/release/semsitter-bridge" "$work/out/macos-x64" + +mkdir -p prebuilt +for os in linux-x64 linux-aarch64 macos-x64 macos-aarch64 windows-x64; do + bin=bridge + [[ $os == windows-x64 ]] && bin=bridge.exe + mkdir -p "$work/$os" + install -m 755 "$work/out/$os" "$work/$os/$bin" + tar -cJf "prebuilt/bridge-$os.xz" -C "$work/$os" "$bin" +done diff --git a/semsitter-bridge/prebuilt/bridge-linux-aarch64.xz b/semsitter-bridge/prebuilt/bridge-linux-aarch64.xz new file mode 100644 index 00000000000..2c07943f766 Binary files /dev/null and b/semsitter-bridge/prebuilt/bridge-linux-aarch64.xz differ diff --git a/semsitter-bridge/prebuilt/bridge-linux-x64.xz b/semsitter-bridge/prebuilt/bridge-linux-x64.xz new file mode 100644 index 00000000000..18a59653df8 Binary files /dev/null and b/semsitter-bridge/prebuilt/bridge-linux-x64.xz differ diff --git a/semsitter-bridge/prebuilt/bridge-macos-aarch64.xz b/semsitter-bridge/prebuilt/bridge-macos-aarch64.xz new file mode 100644 index 00000000000..4a6e2eaccd3 Binary files /dev/null and b/semsitter-bridge/prebuilt/bridge-macos-aarch64.xz differ diff --git a/semsitter-bridge/prebuilt/bridge-macos-x64.xz b/semsitter-bridge/prebuilt/bridge-macos-x64.xz new file mode 100644 index 00000000000..bb3a1f40d74 Binary files /dev/null and b/semsitter-bridge/prebuilt/bridge-macos-x64.xz differ diff --git a/semsitter-bridge/prebuilt/bridge-windows-x64.xz b/semsitter-bridge/prebuilt/bridge-windows-x64.xz new file mode 100644 index 00000000000..47e3788d914 Binary files /dev/null and b/semsitter-bridge/prebuilt/bridge-windows-x64.xz differ diff --git a/semsitter-bridge/proto/bridge.proto b/semsitter-bridge/proto/bridge.proto new file mode 100644 index 00000000000..d1831481b06 --- /dev/null +++ b/semsitter-bridge/proto/bridge.proto @@ -0,0 +1,110 @@ +syntax = "proto3"; + +// Contract between the Rust bridge and the Java frontend. Field numbers are the wire format +// implemented by hand in src/wire.rs and BridgeFrames.java, so this file can move into +// SemSitter with the CST builder without a protocol change. +// +// The CST is protobuf. Semantic-dump symbols and the open `facts` object on an occurrence stay +// JSON bytes: that schema is SemSitter's semantic-dump/1. Graph facts projected for SonarJava +// are protobuf. Type declarations are one JSON object, same reason. + +message Request { + uint32 id = 1; + AnalyzeRequest analyze = 2; + CstRequest cst = 3; + TypesRequest types = 4; + ParseRequest parse = 5; + bool quit = 6; +} + +message AnalyzeRequest { + string root = 1; + repeated string files = 2; + repeated string classpath = 3; +} + +message CstRequest { + string path = 1; +} + +message TypesRequest { + repeated string names = 1; +} + +message ParseRequest { + string source = 1; +} + +message Response { + uint32 id = 1; + bool ok = 2; + string error = 3; + AnalyzeResult analyze = 4; + Document document = 5; + string types_json = 6; +} + +message AnalyzeResult { + string root = 1; + repeated string files = 2; +} + +message Document { + bool has_error = 1; + Node tree = 2; + string path = 3; + bool semantic = 4; + string symbols_json = 5; + repeated FactEntry facts = 6; +} + +message FactEntry { + string key = 1; + Fact value = 2; +} + +message Fact { + string kind = 1; + string tier = 2; + string parent = 3; + repeated Prop props = 4; + repeated string extends = 5; + repeated string implements = 6; + repeated string permits = 7; + repeated string returns = 8; + repeated string type = 9; + repeated string params = 10; + repeated string overrides = 11; + repeated string annotations = 12; +} + +message Prop { + string name = 1; + repeated string values = 2; +} + +message Node { + string kind = 1; + bool named = 2; + string field = 3; + bool missing = 4; + uint32 start_line = 5; + uint32 start_col = 6; + uint32 end_line = 7; + uint32 end_col = 8; + uint32 start_byte = 9; + uint32 end_byte = 10; + string text = 11; + repeated Node children = 12; + repeated string decls = 13; + repeated Ref refs = 14; +} + +message Ref { + string access = 1; + string outcome = 2; + string target = 3; + string reason = 4; + bool bounded = 5; + string facts_json = 6; +} diff --git a/semsitter-bridge/src/lib.rs b/semsitter-bridge/src/lib.rs new file mode 100644 index 00000000000..5508c19d55f --- /dev/null +++ b/semsitter-bridge/src/lib.rs @@ -0,0 +1,641 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ + +//! Java CST annotated with SemSitter semantics. +//! +//! [`analyze`] runs one SemSitter scan over a batch of Java files (with the project classpath) +//! and keeps each file's `semantic-dump/1` in memory, plus the graph facts SonarJava needs on +//! top of it (return types, supertypes, overrides, item kinds and modifiers). +//! [`Analysis::cst`] then parses a file with tree-sitter-java and attaches, by exact byte span, +//! the symbols declared at a name (`decls`) and the resolved occurrences (`refs`). +//! +//! The scan's graph is the UDG. A dump symbol's id is that graph's FQN, and the CST only +//! points at it by id. Transport of those documents lives in [`wire`] and [`shm`] so it can +//! move into SemSitter later without the Java frontend knowing where the bytes were built. + +pub mod server; +pub mod shm; +pub mod types; +pub mod wire; + +use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::io; +use std::path::{Path, PathBuf}; +use std::sync::Arc; + +use anyhow::{Context, Result, anyhow}; +use serde_json::{Map, Value, json}; +use tree_sitter::{Node, Parser, Tree}; +use udg_gen::overlay_passes::overlay_registry; +use udg_gen::scanner::{ScanOptions, scan_timed_with_registry}; +use udg_gen::semantic_dump::languages; +use udg_gen::udg_model::graph::{DependencyTarget, NodeTier, UdgDependencyKind, UdgGraph}; +use udg_gen::udg_model::source_provider::SourceProvider; +use udg_family_jvm::external::classpath::LazyClasspath; +use udg_lang_sdk::settings::ResolvedSettings; +use udg_semantic::{DumpLanguage, DumpProject, GraphIds, language_of}; + +const BOM: &[u8] = b"\xEF\xBB\xBF"; +pub const STACK_SIZE: usize = 512 * 1024 * 1024; +const GRAPH_EDGES: &[(&str, &str)] = &[ + ("extends", "extends"), + ("implements", "implements"), + ("permits", "permits"), + ("returns", "returns"), + ("is type", "type"), + ("has param", "params"), + ("overrides", "overrides"), + ("annotated by", "annotations"), +]; + +/// Result of one SemSitter scan over a batch of files. +pub struct Analysis { + root: PathBuf, + files: Vec, + dumps: HashMap, + facts: HashMap, + calls: HashMap>, + project_types: types::ProjectTypes, + classpath: Arc, +} + +#[derive(Debug)] +struct ListedSources { + files: Vec, +} + +impl SourceProvider for ListedSources { + fn read(&self, path: &Path) -> io::Result { + Ok(String::from_utf8_lossy(&std::fs::read(path)?).into_owned()) + } + + fn read_bytes(&self, path: &Path) -> io::Result> { + std::fs::read(path) + } + + fn list_files(&self, root: &Path, _project_root: &Path, extensions: &[&str]) -> Vec { + let mut found: Vec = self + .files + .iter() + .filter(|f| f.starts_with(root)) + .filter(|f| { + f.extension() + .and_then(|e| e.to_str()) + .is_some_and(|e| extensions.iter().any(|x| x.trim_start_matches('.').eq_ignore_ascii_case(e))) + }) + .cloned() + .collect(); + found.sort(); + found + } + + fn file_exists(&self, path: &Path) -> bool { + path.is_file() + } +} + +/// Scans `files` (Java sources) as one project rooted at `root`, resolving library types +/// against `classpath` (jars, zips and class directories, declarations only). +pub fn analyze(root: &Path, files: &[PathBuf], classpath: &[String]) -> Result { + analyze_dumping(root, files, classpath, None) +} + +/// [`analyze`], and when `dump` is set writes `udg.json`, each `semantic-dump/1` document and +/// the annotated CST as JSON under that directory. The scan itself stays in memory. +pub fn analyze_dumping(root: &Path, files: &[PathBuf], classpath: &[String], dump: Option<&Path>) -> Result { + let files: Vec = files.iter().filter_map(|f| std::fs::canonicalize(f).ok()).collect(); + let root = project_root(root, &files); + let mut opts = ScanOptions { + src_dirs: vec![root.clone()], + project_root: Some(root.clone()), + source_provider: Some(Arc::new(ListedSources { files: files.clone() })), + ..ScanOptions::default() + }; + opts.extras.modifiers = true; + opts.extras.structural = true; + opts.extras.type_reference_edges = true; + opts.extras.override_edges = true; + opts.extras.annotation_edges = true; + opts.extras.field_reference_edges = true; + opts.extras.decl_location = true; + opts.extras.field_items = true; + + let rel: Vec = files.iter().filter_map(|f| f.strip_prefix(&root).ok()).map(to_slash).collect(); + let mut facts = HashMap::new(); + let mut calls = HashMap::new(); + let mut raw_dumps: HashMap = HashMap::new(); + if !files.is_empty() { + let registry = overlay_registry(); + let (graph, _timings, _failures, _artefacts) = scan_timed_with_registry(&opts, ®istry)?; + facts = graph_facts(&graph); + calls = call_sites(&graph); + let settings = registry + .settings_report() + .unwrap_or_else(|| Arc::clone(ResolvedSettings::empty())); + raw_dumps = memory_dumps(&root, &rel, &graph, settings.as_ref())?; + if let Some(dir) = dump { + write_debug_bundle(dir, &graph, &raw_dumps)?; + } + } + + let dumps = raw_dumps + .iter() + .filter_map(|(path, text)| Some((path.clone(), serde_json::from_str(text).ok()?))) + .collect(); + let project_types = types::ProjectTypes::new(&facts); + let analysis = Analysis { + root, + files: rel, + dumps, + facts, + calls, + project_types, + classpath: shared_classpath(classpath), + }; + if let Some(dir) = dump { + write_cst_json(&analysis, dir)?; + } + Ok(analysis) +} + +/// The classpath of the previous analysis when it lists the same entries, so a client that analyzes +/// one file at a time lists and parses each jar once. +fn shared_classpath(entries: &[String]) -> Arc { + static LAST: std::sync::Mutex, Arc)>> = std::sync::Mutex::new(None); + let mut last = LAST.lock().unwrap_or_else(std::sync::PoisonError::into_inner); + if let Some((listed, classpath)) = last.as_ref() + && listed.as_slice() == entries + { + return Arc::clone(classpath); + } + let classpath = Arc::new(LazyClasspath::new(entries.to_vec())); + *last = Some((entries.to_vec(), Arc::clone(&classpath))); + classpath +} + +fn memory_dumps(root: &Path, files: &[String], graph: &UdgGraph, settings: &ResolvedSettings) -> Result> { + let ids = GraphIds::of(graph); + let mut listed: Vec<&str> = files.iter().map(String::as_str).filter(|r| is_project_relative(r)).collect(); + listed.sort_unstable(); + listed.dedup(); + let plain = languages(); + let project = DumpProject { + graph, + root, + files: &listed, + settings, + expressions: false, + }; + let bound: Vec>> = plain.iter().map(|language| language.for_project(&project)).collect(); + let bound_langs: Vec<&dyn DumpLanguage> = plain + .iter() + .zip(&bound) + .map(|(language, project_language)| project_language.as_deref().unwrap_or(*language)) + .collect(); + let mut dumps = HashMap::new(); + for rel in listed { + let Some(language) = language_of(&bound_langs, rel) else { + continue; + }; + let Ok(stored) = std::fs::read(root.join(rel)) else { + continue; + }; + if let Some(document) = language.dump_stored(rel, stored, ids.file(rel)) { + dumps.insert(rel.to_string(), document); + } + } + Ok(dumps) +} + +fn is_project_relative(rel: &str) -> bool { + let path = Path::new(rel); + !path.is_absolute() + && path.components().all(|c| matches!(c, std::path::Component::Normal(_) | std::path::Component::CurDir)) +} + +fn write_debug_bundle(dir: &Path, graph: &UdgGraph, dumps: &HashMap) -> Result<()> { + std::fs::create_dir_all(dir)?; + let file = std::fs::File::create(dir.join("udg.json")).context("creating udg.json")?; + udg_gen::udg_graph_to_proto_json_writer(graph, udg_gen::EdgeFidelity::Full, file).context("writing udg.json")?; + for (rel, document) in dumps { + let target = dir.join("semdump").join(format!("{rel}.semdump.json")); + if let Some(parent) = target.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(target, document).with_context(|| format!("writing semdump {rel}"))?; + } + Ok(()) +} + +fn write_cst_json(analysis: &Analysis, dir: &Path) -> Result<()> { + use std::io::Write; + for file in analysis.files() { + let target = dir.join("cst").join(format!("{file}.json")); + if let Some(parent) = target.parent() { + std::fs::create_dir_all(parent)?; + } + let mut out = std::fs::File::create(&target).with_context(|| format!("creating {}", target.display()))?; + serde_json::to_writer_pretty(&mut out, &analysis.cst(file)?)?; + writeln!(out)?; + } + Ok(()) +} + +impl Analysis { + pub fn root(&self) -> &Path { + &self.root + } + + /// Project-relative paths (forward slashes) of the analyzed files. + pub fn files(&self) -> &[String] { + &self.files + } + + /// Declarations of the types named `names` (erased binary names) and of their supertypes. + pub fn types(&self, names: &[String]) -> Value { + Value::Object(types::lookup(names, &self.project_types, &self.facts, self.classpath.index())) + } + + /// Resolves an absolute or project-relative path to the key used by [`Self::cst`]. + pub fn relative(&self, path: &str) -> Option { + if self.dumps.contains_key(path) || self.files.iter().any(|f| f == path) { + return Some(path.to_string()); + } + let canonical = std::fs::canonicalize(path).ok()?; + canonical.strip_prefix(&self.root).ok().map(to_slash) + } + + /// CST of one analyzed file with its semantics: tree, declared symbols and the graph facts + /// of every symbol id the file mentions. + pub fn cst(&self, path: &str) -> Result { + let rel = self.relative(path).ok_or_else(|| anyhow!("not analyzed: {path}"))?; + let source = std::fs::read(self.root.join(&rel)).with_context(|| format!("reading {rel}"))?; + let dump = self.dumps.get(&rel); + let calls = self.calls.get(&rel).map(Vec::as_slice).unwrap_or_default(); + let index = SemanticIndex::new(dump, calls, &source); + let mut document = document(&source, &index)?; + let symbols = dump.and_then(|d| d.get("symbols")).cloned().unwrap_or_else(|| json!([])); + let facts: Map = index + .ids + .iter() + .filter_map(|id| self.facts.get(id).map(|f| (id.clone(), f.clone()))) + .collect(); + let object = document.as_object_mut().expect("document is an object"); + object.insert("path".into(), Value::String(rel.clone())); + object.insert("semantic".into(), Value::Bool(dump.is_some())); + object.insert("symbols".into(), symbols); + object.insert("facts".into(), Value::Object(facts)); + Ok(document) + } +} + +/// CST of `source` without semantics. +pub fn parse_only(source: &[u8]) -> Result { + document(source, &SemanticIndex::default()) +} + +fn document(source: &[u8], index: &SemanticIndex) -> Result { + let source = source.to_vec(); + let index = index.clone(); + std::thread::Builder::new() + .stack_size(STACK_SIZE) + .spawn(move || { + let tree = parse(&source)?; + let lines = Lines::new(&source); + let root = tree.root_node(); + Ok(json!({ + "hasError": root.has_error(), + "tree": node_json(root, None, None, &source, &lines, &index), + })) + })? + .join() + .map_err(|_| anyhow!("CST serialization panicked"))? +} + +fn parse(source: &[u8]) -> Result { + let mut parser = Parser::new(); + parser.set_language(&tree_sitter_java::LANGUAGE.into())?; + let tree = if source.starts_with(BOM) { + let mut blanked = source.to_vec(); + blanked[..BOM.len()].fill(b' '); + parser.parse(&blanked, None) + } else { + parser.parse(source, None) + }; + tree.ok_or_else(|| anyhow!("tree-sitter returned no tree")) +} + +/// One CST node. `called` is the graph call target of the enclosing `method_invocation`, used +/// for its `name` when the semantic dump has no occurrence there. +fn node_json( + node: Node, + field: Option<&str>, + called: Option<&str>, + source: &[u8], + lines: &Lines, + index: &SemanticIndex, +) -> Value { + let mut object = Map::new(); + let (start, end) = (node.start_byte(), node.end_byte()); + object.insert("k".into(), Value::String(node.kind().to_string())); + if node.is_named() { + object.insert("n".into(), Value::Bool(true)); + } + if let Some(field) = field { + object.insert("f".into(), Value::String(field.to_string())); + } + if node.is_missing() { + object.insert("m".into(), Value::Bool(true)); + } + let (start_line, start_col) = lines.position(source, start); + let (end_line, end_col) = lines.position(source, end); + object.insert("p".into(), json!([start_line, start_col, end_line, end_col])); + object.insert("b".into(), json!([start, end])); + let call = if node.kind() == "method_invocation" { + index.calls.get(&(start, end)).map(String::as_str) + } else { + None + }; + let mut cursor = node.walk(); + let children: Vec = if cursor.goto_first_child() { + let mut children = Vec::new(); + loop { + let child_field = cursor.field_name(); + let child_call = call.filter(|_| child_field == Some("name")); + children.push(node_json(cursor.node(), child_field, child_call, source, lines, index)); + if !cursor.goto_next_sibling() { + break; + } + } + children + } else { + Vec::new() + }; + if children.is_empty() || is_literal(node.kind()) { + object.insert( + "t".into(), + Value::String(String::from_utf8_lossy(&source[start..end]).into_owned()), + ); + } + if !children.is_empty() { + object.insert("c".into(), Value::Array(children)); + } + if children_free_or_name(node) { + if let Some(decls) = index.decls.get(&(start, end)) { + object.insert("decls".into(), json!(decls)); + } + if let Some(refs) = index.refs.get(&(start, end)) { + object.insert("refs".into(), Value::Array(refs.clone())); + } else if let Some(target) = called { + object.insert( + "refs".into(), + json!([{"access": "r", "outcome": "bound", "target": target, "reason": "graph-call"}]), + ); + } + } + Value::Object(object) +} + +fn children_free_or_name(node: Node) -> bool { + node.child_count() == 0 || matches!(node.kind(), "this" | "super") +} + +fn is_literal(kind: &str) -> bool { + matches!( + kind, + "string_literal" | "character_literal" | "text_block" | "decimal_integer_literal" + | "hex_integer_literal" | "octal_integer_literal" | "binary_integer_literal" + | "decimal_floating_point_literal" | "hex_floating_point_literal" + ) +} + +/// Byte offset to (1-based line, 0-based UTF-16 column), the convention of SonarJava tokens. +struct Lines { + starts: Vec, +} + +impl Lines { + fn new(source: &[u8]) -> Self { + let mut starts = vec![0]; + let mut i = 0; + while i < source.len() { + match source[i] { + b'\n' => starts.push(i + 1), + b'\r' if source.get(i + 1) != Some(&b'\n') => starts.push(i + 1), + _ => {} + } + i += 1; + } + Lines { starts } + } + + fn position(&self, source: &[u8], offset: usize) -> (usize, usize) { + let line = self.starts.partition_point(|&s| s <= offset) - 1; + let prefix = String::from_utf8_lossy(&source[self.starts[line]..offset]); + (line + 1, prefix.trim_start_matches('\u{feff}').encode_utf16().count()) + } + + fn offset(&self, (line, column): (usize, usize)) -> Option { + self.starts.get(line.checked_sub(1)?).map(|start| start + column) + } +} + +/// Semantic dump indexed by byte span. +#[derive(Default, Clone)] +struct SemanticIndex { + decls: HashMap<(usize, usize), Vec>, + refs: HashMap<(usize, usize), Vec>, + calls: HashMap<(usize, usize), String>, + ids: BTreeSet, +} + +impl SemanticIndex { + fn new(dump: Option<&Value>, calls: &[CallSite], source: &[u8]) -> Self { + let mut index = SemanticIndex::default(); + let lines = Lines::new(source); + for call in calls { + if let (Some(start), Some(end)) = (lines.offset(call.start), lines.offset(call.end)) { + index.ids.insert(call.target.clone()); + index.calls.insert((start, end), call.target.clone()); + } + } + let Some(dump) = dump else { + return index; + }; + for symbol in array(dump, "symbols") { + let Some(id) = symbol.get("id").and_then(Value::as_str) else { + continue; + }; + index.ids.insert(id.to_string()); + collect_type_ids(symbol.pointer("/facts/type"), &mut index.ids); + if let Some(span) = span(symbol) { + index.decls.entry(span).or_default().push(id.to_string()); + } + } + for occurrence in array(dump, "occurrences") { + let Some(span) = span(occurrence) else { + continue; + }; + let mut reference = occurrence.clone(); + if let Some(object) = reference.as_object_mut() { + for key in ["start", "end", "name", "line", "col"] { + object.remove(key); + } + } + if let Some(target) = occurrence.get("target").and_then(Value::as_str) { + index.ids.insert(target.to_string()); + } + for candidate in occurrence + .pointer("/facts/candidates") + .and_then(Value::as_array) + .into_iter() + .flatten() + { + if let Some(id) = candidate.as_str().or_else(|| candidate.get("id").and_then(Value::as_str)) { + index.ids.insert(id.to_string()); + } + } + index.refs.entry(span).or_default().push(reference); + } + index + } +} + +fn collect_type_ids(ty: Option<&Value>, ids: &mut BTreeSet) { + let Some(ty) = ty else { + return; + }; + if let Some(id) = ty.get("id").and_then(Value::as_str) { + ids.insert(id.to_string()); + } + collect_type_ids(ty.get("elem"), ids); + for arg in ty.get("args").and_then(Value::as_array).into_iter().flatten() { + collect_type_ids(Some(arg), ids); + } +} + +fn array<'a>(value: &'a Value, key: &str) -> impl Iterator { + value.get(key).and_then(Value::as_array).into_iter().flatten() +} + +fn span(value: &Value) -> Option<(usize, usize)> { + Some(( + value.get("start")?.as_u64()? as usize, + value.get("end")?.as_u64()? as usize, + )) +} + +/// A resolved call edge of the graph, located at its call site (1-based lines, 0-based columns). +#[derive(Clone)] +struct CallSite { + start: (usize, usize), + end: (usize, usize), + target: String, +} + +fn call_sites(graph: &UdgGraph) -> HashMap> { + let mut sites: HashMap> = HashMap::new(); + for fqn in graph.items.keys() { + for dependency in graph.get_downstream_dependencies(fqn.as_str()) { + if dependency.kind != UdgDependencyKind::Calls { + continue; + } + let DependencyTarget::Item(target) = &dependency.to_node else { + continue; + }; + if target.tier == NodeTier::ExternalUnknown { + continue; + } + let Some(location) = &dependency.location else { + continue; + }; + let (Some(end_line), Some(end_offset)) = (location.end_line, location.end_line_offset) else { + continue; + }; + sites.entry(location.file.path.to_string()).or_default().push(CallSite { + start: (location.start_line as usize, location.start_line_offset as usize), + end: (end_line as usize, end_offset as usize), + target: target.fqn.as_str().to_string(), + }); + } + } + sites +} + +fn graph_facts(graph: &UdgGraph) -> HashMap { + graph + .items + .iter() + .map(|(fqn, item)| { + let mut edges: BTreeMap> = BTreeMap::new(); + for dependency in graph.get_downstream_dependencies(fqn.as_str()) { + let kind = serde_json::to_value(dependency.kind) + .ok() + .and_then(|k| k.as_str().map(str::to_string)) + .unwrap_or_default(); + if let Some((_, key)) = GRAPH_EDGES.iter().find(|(name, _)| *name == kind) { + let targets = edges.entry((*key).to_string()).or_default(); + let target = dependency.to_node.key().to_string(); + if !targets.contains(&target) { + targets.push(target); + } + } + } + let properties: Map = item + .properties + .iter() + .map(|p| { + let values: Vec = p.values.iter().map(|v| Value::String(v.value.to_string())).collect(); + (p.name.to_string(), Value::Array(values)) + }) + .collect(); + let mut object = Map::new(); + object.insert("kind".into(), serde_json::to_value(item.kind).unwrap_or(Value::Null)); + object.insert("tier".into(), serde_json::to_value(item.tier).unwrap_or(Value::Null)); + if let Some(parent) = &item.parent_item_fqn { + object.insert("parent".into(), Value::String(parent.as_str().to_string())); + } + if !properties.is_empty() { + object.insert("props".into(), Value::Object(properties)); + } + for (kind, targets) in edges { + object.insert(kind, json!(targets)); + } + (fqn.as_str().to_string(), Value::Object(object)) + }) + .collect() +} + +fn project_root(root: &Path, files: &[PathBuf]) -> PathBuf { + let root = std::fs::canonicalize(root).unwrap_or_else(|_| root.to_path_buf()); + if files.iter().all(|f| f.starts_with(&root)) { + return root; + } + let mut common = files.first().and_then(|f| f.parent()).map(Path::to_path_buf).unwrap_or(root); + for file in files { + while !file.starts_with(&common) { + if !common.pop() { + break; + } + } + } + common +} + +fn to_slash(path: &Path) -> String { + path.to_string_lossy().replace('\\', "/") +} diff --git a/semsitter-bridge/src/main.rs b/semsitter-bridge/src/main.rs new file mode 100644 index 00000000000..51fb3c84150 --- /dev/null +++ b/semsitter-bridge/src/main.rs @@ -0,0 +1,126 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ + +use std::io::Write; +use std::path::PathBuf; + +use anyhow::{Result, anyhow, bail}; +use semsitter_bridge::server::{print_csts, serve_jsonl, serve_shm}; + +const USAGE: &str = "\ +usage: + semsitter-bridge serve [--shm | --jsonl] [--dump-json DIR] + semsitter-bridge cst --root DIR [--classpath CP] [--dump-json DIR] FILE... + semsitter-bridge parse [--dump-json DIR] FILE +CP uses the platform path separator. +--shm serves protobuf over shared memory, with one ping byte on stdin/stdout. +--jsonl is the line-delimited JSON server. +--dump-json writes udg.json, semantic-dump JSON and annotated CST JSON under DIR."; + +/// Runs the command, and the scan workers, on a stack deep enough to analyze, encode and drop the +/// tree of a deeply nested expression, such as a concatenation of thousands of strings. +fn main() -> Result<()> { + udg_gen::scanner::init_global_pool(None, Some(semsitter_bridge::STACK_SIZE)); + std::thread::Builder::new() + .stack_size(semsitter_bridge::STACK_SIZE) + .spawn(run)? + .join() + .map_err(|_| anyhow!("the bridge panicked"))? +} + +fn run() -> Result<()> { + let args: Vec = std::env::args().skip(1).collect(); + match args.first().map(String::as_str) { + None | Some("serve") => serve_command(&args[1..]), + Some("cst") => cst(&args[1..]), + Some("parse") => parse(&args[1..]), + Some("-h" | "--help") => { + println!("{USAGE}"); + Ok(()) + } + Some(other) => bail!("unknown command: {other}\n{USAGE}"), + } +} + +fn serve_command(args: &[String]) -> Result<()> { + let mut shm = false; + let mut dump = None; + let mut args = args.iter(); + while let Some(arg) = args.next() { + match arg.as_str() { + "--shm" => shm = true, + "--jsonl" => shm = false, + "--dump-json" => dump = Some(PathBuf::from(args.next().ok_or_else(|| anyhow!(USAGE))?)), + "-h" | "--help" => { + println!("{USAGE}"); + return Ok(()); + } + other => bail!("unknown argument: {other}\n{USAGE}"), + } + } + let stdin = std::io::stdin().lock(); + let stdout = std::io::stdout().lock(); + if shm { + serve_shm(stdin, stdout, dump) + } else { + serve_jsonl(stdin, stdout, dump) + } +} + +fn parse(args: &[String]) -> Result<()> { + let mut dump = None; + let mut file = None; + let mut args = args.iter(); + while let Some(arg) = args.next() { + match arg.as_str() { + "--dump-json" => dump = Some(PathBuf::from(args.next().ok_or_else(|| anyhow!(USAGE))?)), + other if file.is_none() => file = Some(other), + other => bail!("unknown argument: {other}\n{USAGE}"), + } + } + let file = file.ok_or_else(|| anyhow!(USAGE))?; + let cst = semsitter_bridge::parse_only(&std::fs::read(file)?)?; + if let Some(dir) = &dump { + std::fs::create_dir_all(dir)?; + std::fs::write(dir.join("parse.json"), serde_json::to_vec_pretty(&cst)?)?; + } + let mut stdout = std::io::stdout().lock(); + serde_json::to_writer(&mut stdout, &cst)?; + writeln!(stdout)?; + Ok(()) +} + +fn cst(args: &[String]) -> Result<()> { + let mut root = None; + let mut classpath = Vec::new(); + let mut dump = None; + let mut files = Vec::new(); + let mut args = args.iter(); + while let Some(arg) = args.next() { + match arg.as_str() { + "--root" => root = args.next().map(PathBuf::from), + "--classpath" => { + let value = args.next().ok_or_else(|| anyhow!(USAGE))?; + classpath.extend(std::env::split_paths(value).map(|p| p.to_string_lossy().into_owned())); + } + "--dump-json" => dump = Some(PathBuf::from(args.next().ok_or_else(|| anyhow!(USAGE))?)), + file => files.push(PathBuf::from(file)), + } + } + let root = root.ok_or_else(|| anyhow!(USAGE))?; + print_csts(&root, &files, &classpath, dump.as_deref(), std::io::stdout().lock()) +} diff --git a/semsitter-bridge/src/server.rs b/semsitter-bridge/src/server.rs new file mode 100644 index 00000000000..60c4348d102 --- /dev/null +++ b/semsitter-bridge/src/server.rs @@ -0,0 +1,231 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ + +//! JSONL protocol: one request object per stdin line, one response object per stdout line. +//! +//! - `{"id":1,"op":"analyze","root":"/p","files":["/p/A.java"],"classpath":["/lib.jar"]}` +//! scans the batch and answers `{"id":1,"ok":true,"root":"/p","files":["A.java"]}`. +//! - `{"id":2,"op":"cst","path":"/p/A.java"}` answers `{"id":2,"ok":true,"cst":{...}}` with the +//! CST of a file of the last `analyze`. +//! - `{"id":3,"op":"types","names":["java.util.List"]}` answers `{"id":3,"ok":true,"types":{...}}` +//! with the declarations (kind, supertypes, members) of the named types and their supertypes. +//! - `{"id":4,"op":"parse","source":"class A {}"}` answers the CST without semantics. +//! - `{"id":5,"op":"quit"}` answers `{"id":5,"ok":true}` and stops the server. +//! +//! Failures answer `{"id":n,"ok":false,"error":"..."}` and keep the server running. + +use std::io::{BufRead, Read, Write}; +use std::path::{Path, PathBuf}; + +use anyhow::{Result, anyhow}; +use serde_json::{Map, Value, json}; + +use crate::shm::{ATTACHED, Link, READY}; +use crate::wire::{self, Request}; +use crate::{Analysis, analyze_dumping, parse_only}; + +pub struct Server { + analysis: Option, + dump: Option, + types_written: u32, +} + +impl Default for Server { + fn default() -> Self { + Self { analysis: None, dump: None, types_written: 0 } + } +} + +impl Server { + /// Answers one request line, and whether the server must stop. + pub fn handle(&mut self, line: &str) -> (Value, bool) { + let request: Value = match serde_json::from_str(line) { + Ok(request) => request, + Err(e) => return (json!({"ok": false, "error": format!("invalid request: {e}")}), false), + }; + let id = request.get("id").cloned().unwrap_or(Value::Null); + let op = request.get("op").and_then(Value::as_str).unwrap_or_default(); + let quit = op == "quit"; + let mut response = match self.dispatch(op, &request) { + Ok(body) => body, + Err(e) => json!({"ok": false, "error": format!("{e:#}")}), + }; + response.as_object_mut().expect("response is an object").insert("id".into(), id); + (response, quit) + } + + fn dispatch(&mut self, op: &str, request: &Value) -> Result { + match op { + "analyze" => { + let root = PathBuf::from(string(request, "root")?); + let files: Vec = strings(request, "files").into_iter().map(PathBuf::from).collect(); + let classpath = strings(request, "classpath"); + let analysis = analyze_dumping(&root, &files, &classpath, self.dump.as_deref())?; + let body = json!({ + "ok": true, + "root": analysis.root().to_string_lossy(), + "files": analysis.files(), + }); + self.analysis = Some(analysis); + Ok(body) + } + "cst" => { + let analysis = self.analysis.as_ref().ok_or_else(|| anyhow!("no analyze request yet"))?; + Ok(ok_with("cst", analysis.cst(string(request, "path")?)?)) + } + "types" => { + let analysis = self.analysis.as_ref().ok_or_else(|| anyhow!("no analyze request yet"))?; + let names = strings(request, "names"); + let types = analysis.types(&names); + self.write_types(&types)?; + Ok(ok_with("types", types)) + } + "parse" => { + let cst = parse_only(string(request, "source")?.as_bytes())?; + if let Some(dir) = &self.dump { + std::fs::create_dir_all(dir)?; + std::fs::write(dir.join("parse.json"), serde_json::to_vec_pretty(&cst)?)?; + } + Ok(ok_with("cst", cst)) + } + "quit" => Ok(json!({"ok": true})), + other => Err(anyhow!("unknown op: {other}")), + } + } +} + +impl Server { + fn write_types(&mut self, types: &Value) -> Result<()> { + let Some(dir) = &self.dump else { + return Ok(()); + }; + self.types_written += 1; + let target = dir.join("types").join(format!("{}.json", self.types_written)); + if let Some(parent) = target.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(target, serde_json::to_vec_pretty(types)?)?; + Ok(()) + } + + fn handle_bytes(&mut self, bytes: &[u8]) -> (Vec, bool) { + let incoming = match wire::decode_request(bytes) { + Ok(incoming) => incoming, + Err(e) => return (wire::encode_response(0, &json!({"ok": false, "error": format!("{e:#}")})), false), + }; + let request = match &incoming.request { + Request::Analyze { root, files, classpath } => json!({ + "op": "analyze", + "root": root, + "files": files, + "classpath": classpath, + }), + Request::Cst { path } => json!({"op": "cst", "path": path}), + Request::Types { names } => json!({"op": "types", "names": names}), + Request::Parse { source } => json!({"op": "parse", "source": source}), + Request::Quit => json!({"op": "quit"}), + }; + let op = request.get("op").and_then(Value::as_str).unwrap_or_default(); + let quit = matches!(incoming.request, Request::Quit); + let mut response = match self.dispatch(op, &request) { + Ok(body) => body, + Err(e) => json!({"ok": false, "error": format!("{e:#}")}), + }; + response.as_object_mut().expect("response is an object").insert("id".into(), json!(incoming.id)); + (wire::encode_response(incoming.id, &response), quit) + } +} + +/// JSONL server. One request object per stdin line, one response object per stdout line. +pub fn serve(input: impl BufRead, output: impl Write) -> Result<()> { + serve_jsonl(input, output, None) +} + +pub fn serve_jsonl(input: impl BufRead, mut output: impl Write, dump: Option) -> Result<()> { + let mut server = Server { dump, ..Server::default() }; + for line in input.lines() { + let line = line?; + if line.trim().is_empty() { + continue; + } + let (response, quit) = server.handle(&line); + serde_json::to_writer(&mut output, &response)?; + output.write_all(b"\n")?; + output.flush()?; + if quit { + break; + } + } + Ok(()) +} + +/// Shared-memory server. Writes [`READY`] once the mappings exist, reads [`ATTACHED`], +/// then exchanges protobuf messages. Each message is announced by a single byte on the pipe. +pub fn serve_shm(mut input: impl Read, mut output: impl Write, dump: Option) -> Result<()> { + let id = std::process::id().to_string(); + let mut link = Link::create(&id)?; + output.write_all(&[READY])?; + output.flush()?; + let mut attached = [0u8; 1]; + input.read_exact(&mut attached)?; + if attached[0] != ATTACHED { + return Err(anyhow!("expected the peer to attach the shared mappings")); + } + link.unlink(); + let mut server = Server { dump, ..Server::default() }; + loop { + let request = link.read_request(&mut input, &mut output)?; + let (response, quit) = server.handle_bytes(&request); + link.write_response(&response, &mut output, &mut input)?; + if quit { + break; + } + } + Ok(()) +} + +/// CSTs of `files` (with semantics), one JSON object per line. +pub fn print_csts(root: &Path, files: &[PathBuf], classpath: &[String], dump: Option<&Path>, mut output: impl Write) -> Result<()> { + let analysis = analyze_dumping(root, files, classpath, dump)?; + for file in analysis.files() { + serde_json::to_writer(&mut output, &analysis.cst(file)?)?; + output.write_all(b"\n")?; + } + output.flush()?; + Ok(()) +} + +fn ok_with(key: &str, value: Value) -> Value { + let mut body = Map::new(); + body.insert("ok".into(), Value::Bool(true)); + body.insert(key.into(), value); + Value::Object(body) +} + +fn string<'a>(request: &'a Value, key: &str) -> Result<&'a str> { + request.get(key).and_then(Value::as_str).ok_or_else(|| anyhow!("missing string field: {key}")) +} + +fn strings(request: &Value, key: &str) -> Vec { + request + .get(key) + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(|v| v.as_str().map(str::to_string)) + .collect() +} diff --git a/semsitter-bridge/src/shm.rs b/semsitter-bridge/src/shm.rs new file mode 100644 index 00000000000..d059a855c0c --- /dev/null +++ b/semsitter-bridge/src/shm.rs @@ -0,0 +1,234 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ + +//! Two shared mappings and a one-byte ping on the pipe. +//! +//! The payload never travels on the pipe. `1` means the mapping holds a protobuf message, +//! `2` means the peer grew it and this side must remap. The directory entry is removed once +//! both sides have mapped, so nothing is left to read as a file. On Linux the files live in +//! `/dev/shm`. + +use std::fs::OpenOptions; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{Ordering, fence}; + +use anyhow::{Result, bail}; +use memmap2::MmapMut; + +pub const READY: u8 = b'R'; +pub const ATTACHED: u8 = b'A'; +const DATA: u8 = 1; +const GROW: u8 = 2; +const INITIAL: usize = 32 * 1024 * 1024; +const MAX: usize = 512 * 1024 * 1024; +const HEADER: usize = 24; +const MAGIC: u32 = 0x3142_4A53; + +pub struct Link { + id: String, + request: Slot, + response: Slot, + created: bool, +} + +struct Slot { + path: PathBuf, + file: std::fs::File, + map: MmapMut, +} + +impl Link { + pub fn create(id: &str) -> Result { + Ok(Link { + request: Slot::create(&slot_path(id, "q"), INITIAL)?, + response: Slot::create(&slot_path(id, "a"), INITIAL)?, + id: id.to_string(), + created: true, + }) + } + + pub fn open(id: &str) -> Result { + Ok(Link { + request: Slot::open(&slot_path(id, "q"))?, + response: Slot::open(&slot_path(id, "a"))?, + id: id.to_string(), + created: false, + }) + } + + pub fn id(&self) -> &str { + &self.id + } + + /// Drops the directory entries. Mappings stay valid. + pub fn unlink(&self) { + let _ = std::fs::remove_file(slot_path(&self.id, "q")); + let _ = std::fs::remove_file(slot_path(&self.id, "a")); + } + + pub fn write_request(&mut self, bytes: &[u8], to_peer: &mut impl Write, from_peer: &mut impl Read) -> Result<()> { + self.request.write(bytes, to_peer, from_peer) + } + + pub fn read_request(&mut self, from_peer: &mut impl Read, to_peer: &mut impl Write) -> Result> { + self.request.read(from_peer, to_peer) + } + + pub fn write_response(&mut self, bytes: &[u8], to_peer: &mut impl Write, from_peer: &mut impl Read) -> Result<()> { + self.response.write(bytes, to_peer, from_peer) + } + + pub fn read_response(&mut self, from_peer: &mut impl Read, to_peer: &mut impl Write) -> Result> { + self.response.read(from_peer, to_peer) + } +} + +impl Drop for Link { + fn drop(&mut self) { + if self.created { + self.unlink(); + } + } +} + +impl Slot { + fn create(path: &Path, len: usize) -> Result { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + let file = OpenOptions::new().read(true).write(true).create(true).truncate(true).open(path)?; + file.set_len(len as u64)?; + let mut slot = Slot { + path: path.to_path_buf(), + map: unsafe { MmapMut::map_mut(&file)? }, + file, + }; + slot.put_header(0, 0, len as u64); + Ok(slot) + } + + fn open(path: &Path) -> Result { + let file = OpenOptions::new().read(true).write(true).open(path)?; + let len = file.metadata()?.len() as usize; + Ok(Slot { + path: path.to_path_buf(), + map: unsafe { MmapMut::map_mut(&file)? }, + file, + }) + .and_then(|slot| { + if slot.map.len() != len { + bail!("mapping length mismatch for {}", path.display()); + } + Ok(slot) + }) + } + + fn write(&mut self, bytes: &[u8], to_peer: &mut impl Write, from_peer: &mut impl Read) -> Result<()> { + let need = HEADER + bytes.len(); + if need > self.map.len() { + let mut cap = self.map.len().max(INITIAL); + while cap < need { + if cap >= MAX { + bail!("protobuf message of {} bytes exceeds the shared mapping", bytes.len()); + } + cap = cap.saturating_mul(2).min(MAX); + } + self.file.set_len(cap as u64)?; + self.put_header(GROW as u32, cap as u64, cap as u64); + fence(Ordering::Release); + ping(to_peer, GROW)?; + let ack = read_ping(from_peer)?; + if ack != GROW { + bail!("shared mapping grow was not acknowledged"); + } + self.remap(cap)?; + } + self.map[HEADER..HEADER + bytes.len()].copy_from_slice(bytes); + self.put_header(0, bytes.len() as u64, self.map.len() as u64); + fence(Ordering::Release); + ping(to_peer, DATA)?; + Ok(()) + } + + fn read(&mut self, from_peer: &mut impl Read, to_peer: &mut impl Write) -> Result> { + loop { + match read_ping(from_peer)? { + GROW => { + let cap = self.header_len()? as usize; + self.file.set_len(cap as u64)?; + self.remap(cap)?; + fence(Ordering::Release); + ping(to_peer, GROW)?; + } + DATA => { + fence(Ordering::Acquire); + let len = self.header_len()? as usize; + if HEADER + len > self.map.len() { + bail!("payload of {len} bytes does not fit the shared mapping"); + } + return Ok(self.map[HEADER..HEADER + len].to_vec()); + } + other => bail!("unexpected shared-memory ping {other}"), + } + } + } + + fn remap(&mut self, len: usize) -> Result<()> { + self.map = unsafe { MmapMut::map_mut(&self.file)? }; + if self.map.len() < len { + bail!("remapped {} is shorter than {len}", self.path.display()); + } + Ok(()) + } + + fn put_header(&mut self, kind: u32, len: u64, cap: u64) { + self.map[0..4].copy_from_slice(&MAGIC.to_le_bytes()); + self.map[4..8].copy_from_slice(&kind.to_le_bytes()); + self.map[8..16].copy_from_slice(&len.to_le_bytes()); + self.map[16..24].copy_from_slice(&cap.to_le_bytes()); + } + + fn header_len(&self) -> Result { + let magic = u32::from_le_bytes(self.map[0..4].try_into().unwrap()); + if magic != MAGIC { + bail!("shared mapping has no bridge header"); + } + Ok(u64::from_le_bytes(self.map[8..16].try_into().unwrap())) + } +} + +fn ping(to_peer: &mut impl Write, byte: u8) -> Result<()> { + to_peer.write_all(&[byte])?; + to_peer.flush()?; + Ok(()) +} + +fn read_ping(from_peer: &mut impl Read) -> Result { + let mut byte = [0u8; 1]; + from_peer.read_exact(&mut byte)?; + Ok(byte[0]) +} + +pub fn slot_dir() -> PathBuf { + let shm = PathBuf::from("/dev/shm"); + if shm.is_dir() { shm } else { std::env::temp_dir() } +} + +fn slot_path(id: &str, which: &str) -> PathBuf { + slot_dir().join(format!("sjb-{id}-{which}")) +} diff --git a/semsitter-bridge/src/types.rs b/semsitter-bridge/src/types.rs new file mode 100644 index 00000000000..abff01b97c0 --- /dev/null +++ b/semsitter-bridge/src/types.rs @@ -0,0 +1,621 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ + +//! Type declarations by erased binary name (`java.util.Map$Entry`), from the first source that +//! knows the type: the scanned project graph, the classpath index (jars and the JDK runtime +//! image), then the JDK table SemSitter embeds. + +use std::collections::{BTreeSet, HashMap, VecDeque}; + +use serde_json::{Map, Value, json}; +use udg_family_jvm::external::classpath::{ + Annotation, ClasspathDeclaration, ClasspathIndex, ClasspathType, ConstantValue, ElementValue, +}; +use udg_family_jvm::external::jdk_table::{JdkMemberKind, JdkType, jdk_type, parameter_type_variables}; + +const ACC_STATIC: u16 = 0x0008; +const ACC_FINAL: u16 = 0x0010; +const ACC_PUBLIC: u16 = 0x0001; +const ACC_PRIVATE: u16 = 0x0002; +const ACC_PROTECTED: u16 = 0x0004; +const ACC_ENUM: u16 = 0x4000; +const ACC_ABSTRACT: u16 = 0x0400; + +/// Strips the generic arity suffix of a graph id segment: ``java.util.List`1`` is `java.util.List`. +pub fn erase(id: &str) -> String { + let mut out = String::with_capacity(id.len()); + let mut chars = id.chars().peekable(); + while let Some(c) = chars.next() { + if c == '`' { + while chars.peek().is_some_and(char::is_ascii_digit) { + chars.next(); + } + } else { + out.push(c); + } + } + out +} + +/// Project types of the graph: erased name to graph id, and graph id to member ids. +#[derive(Default)] +pub struct ProjectTypes { + pub keys: HashMap, + pub members: HashMap>, +} + +impl ProjectTypes { + pub fn new(facts: &HashMap) -> Self { + let mut types = ProjectTypes::default(); + for (id, fact) in facts { + if fact.get("tier").and_then(Value::as_str) != Some("IN_PROJECT") { + continue; + } + if let Some(parent) = fact.get("parent").and_then(Value::as_str) { + types.members.entry(parent.to_string()).or_default().push(id.clone()); + } + if !id.contains('#') && is_type_kind(fact.get("kind").and_then(Value::as_str).unwrap_or_default()) { + types.keys.insert(erase(id), id.clone()); + } + } + for members in types.members.values_mut() { + members.sort(); + } + types + } +} + +fn is_type_kind(kind: &str) -> bool { + matches!(kind, "class" | "interface" | "enum" | "record" | "annotation" | "exception") +} + +/// Declarations of `names` and of all their supertypes. +pub fn lookup( + names: &[String], + project: &ProjectTypes, + facts: &HashMap, + classpath: &ClasspathIndex, +) -> Map { + let mut found = Map::new(); + let mut queue: VecDeque = names.iter().map(|n| erase(n)).collect(); + let mut seen: BTreeSet = BTreeSet::new(); + while let Some(name) = queue.pop_front() { + if !seen.insert(name.clone()) { + continue; + } + let info = project_type(&name, project, facts) + .or_else(|| classpath.get(&name).map(|ty| classpath_type(&name, ty, classpath.declaration(&name)))) + .or_else(|| jdk_type(&name).map(jdk_type_info)); + let Some(info) = info else { + continue; + }; + for key in ["super", "interfaces"] { + for supertype in strings(&info, key) { + queue.push_back(erase(&supertype)); + } + } + found.insert(name, info); + } + found +} + +fn strings(value: &Value, key: &str) -> Vec { + match value.get(key) { + Some(Value::String(s)) => vec![s.clone()], + Some(Value::Array(items)) => items.iter().filter_map(|i| i.as_str().map(str::to_string)).collect(), + _ => Vec::new(), + } +} + +fn project_type(name: &str, project: &ProjectTypes, facts: &HashMap) -> Option { + let id = project.keys.get(name)?; + let fact = facts.get(id)?; + let kind = fact.get("kind").and_then(Value::as_str).unwrap_or("class"); + let extends = strings(fact, "extends"); + let implements = strings(fact, "implements"); + let (superclass, interfaces) = if kind == "interface" || kind == "annotation" { + (None, extends) + } else { + (extends.into_iter().next(), implements) + }; + let members: Vec = project + .members + .get(id) + .into_iter() + .flatten() + .filter_map(|member| facts.get(member).map(|f| json!({"id": member, "facts": f}))) + .collect(); + Some(json!({ + "origin": "project", + "id": id, + "kind": kind, + "super": superclass, + "interfaces": interfaces, + "modifiers": fact.pointer("/props/modifiers").cloned().unwrap_or_else(|| json!([])), + "members": members, + })) +} + +fn classpath_type(name: &str, ty: &ClasspathType, declaration: Option<&ClasspathDeclaration>) -> Value { + let parameters = ty.signature.as_deref().map(type_parameters).unwrap_or_default(); + let no_annotations: &[Annotation] = &[]; + let no_throws: &[String] = &[]; + let mut members: Vec = ty + .methods + .iter() + .map(|m| { + let returned = m.signature.as_deref().and_then(|s| class_return_parameter(s, ¶meters)); + let declared = declaration.and_then(|facts| facts.methods.get(&(m.name.clone(), m.descriptor.clone()))); + json!({ + "k": "m", + "name": m.name, + "desc": m.descriptor, + "static": m.is_static, + "abstract": m.is_abstract, + "varargs": m.is_varargs, + "vis": "public", + "retParam": returned, + "retSelf": returned.is_some_and(|index| parameters[index].1 == name), + "retType": m.signature.as_deref().and_then(return_type), + "paramVars": m.signature.as_deref().map(|s| parameter_variables(s, ¶meters)), + "paramTypes": m.signature.as_deref().map(parameter_types), + "typeVars": m.signature.as_deref().map(|s| type_parameters(s).into_iter().map(|(name, _)| name).collect::>()), + "annotations": annotations_json(declared.map(|facts| facts.annotations.as_slice()).unwrap_or(no_annotations)), + "throws": declared.map(|facts| facts.exceptions.as_slice()).unwrap_or(no_throws), + }) + }) + .collect(); + members.extend(ty.fields.iter().filter_map(|f| { + let desc = field_descriptor(f.declared_type.as_ref()?); + let declared = declaration.and_then(|facts| facts.fields.get(&f.name)); + Some(json!({ + "k": "f", + "name": f.name, + "desc": desc, + "static": f.access_flags & ACC_STATIC != 0, + "final": f.access_flags & ACC_FINAL != 0, + "enum": f.access_flags & ACC_ENUM != 0, + "vis": access_visibility(f.access_flags), + "constant": f.constant.as_ref().and_then(ConstantValue::as_string), + "annotations": annotations_json(declared.map(|facts| facts.annotations.as_slice()).unwrap_or(no_annotations)), + })) + })); + let flags = declaration.map(|facts| facts.access_flags).unwrap_or(0); + let mut info = json!({ + "origin": "classpath", + "kind": classpath_kind(ty), + "super": ty.super_class, + "interfaces": ty.interfaces, + "modifiers": classpath_modifiers(flags), + "annotations": annotations_json(declaration.map(|facts| facts.annotations.as_slice()).unwrap_or(no_annotations)), + "typeParameters": parameters.len(), + "typeVars": parameters.iter().map(|(name, _)| name).collect::>(), + "members": members, + }); + if let (Some(signature), Some(object)) = (ty.signature.as_deref(), info.as_object_mut()) { + let (superclass, interfaces) = signature_supertypes(signature); + object.insert("superType".into(), json!(superclass)); + object.insert("interfaceTypes".into(), json!(interfaces)); + } + info +} + +/// The JVM descriptor of a classpath field type, which the classpath reader spells as a source +/// type: a primitive or a binary name, followed by one `[]` per array dimension. +fn field_descriptor(source: &str) -> String { + let element = source.trim_end_matches("[]"); + let dimensions = (source.len() - element.len()) / 2; + let base = match element { + "boolean" => "Z".to_string(), + "byte" => "B".to_string(), + "char" => "C".to_string(), + "short" => "S".to_string(), + "int" => "I".to_string(), + "long" => "J".to_string(), + "float" => "F".to_string(), + "double" => "D".to_string(), + name => format!("L{};", name.replace('.', "/")), + }; + format!("{}{base}", "[".repeat(dimensions)) +} + +fn annotations_json(annotations: &[Annotation]) -> Value { + Value::Array(annotations.iter().map(annotation_json).collect()) +} + +fn annotation_json(annotation: &Annotation) -> Value { + json!({ + "name": annotation.name, + "values": annotation.elements.iter().map(|(name, value)| element_json(name, value)).collect::>(), + }) +} + +fn element_json(name: &str, value: &ElementValue) -> Value { + match value { + ElementValue::Constant(ConstantValue::Boolean(value)) => json!({"name": name, "kind": "boolean", "value": value}), + ElementValue::Constant(ConstantValue::Byte(value)) => json!({"name": name, "kind": "byte", "value": value}), + ElementValue::Constant(ConstantValue::Char(value)) => json!({"name": name, "kind": "char", "value": value}), + ElementValue::Constant(ConstantValue::Short(value)) => json!({"name": name, "kind": "short", "value": value}), + ElementValue::Constant(ConstantValue::Int(value)) => json!({"name": name, "kind": "int", "value": value}), + ElementValue::Constant(ConstantValue::Long(value)) => json!({"name": name, "kind": "long", "value": value}), + ElementValue::Constant(ConstantValue::Float(value)) => json!({"name": name, "kind": "float", "value": value}), + ElementValue::Constant(ConstantValue::Double(value)) => json!({"name": name, "kind": "double", "value": value}), + ElementValue::Constant(ConstantValue::String(value)) => json!({"name": name, "kind": "string", "value": value}), + ElementValue::Class(value) => json!({"name": name, "kind": "class", "value": value}), + ElementValue::Enum { type_name, constant } => json!({"name": name, "kind": "enum", "type": type_name, "const": constant}), + ElementValue::Annotation(annotation) => json!({"name": name, "kind": "annotation", "value": annotation_json(annotation)}), + ElementValue::Array(items) => json!({ + "name": name, + "kind": "array", + "value": items.iter().map(|item| element_json("value", item)).collect::>(), + }), + } +} + +/// Class-file access flags as the modifier names `JSema` already applies to JDK types. +fn classpath_modifiers(flags: u16) -> Vec<&'static str> { + let mut modifiers = Vec::new(); + if flags & ACC_PUBLIC != 0 { + modifiers.push("public"); + } else if flags & ACC_PROTECTED != 0 { + modifiers.push("protected"); + } else if flags & ACC_PRIVATE != 0 { + modifiers.push("private"); + } + if flags & ACC_STATIC != 0 { + modifiers.push("static"); + } + if flags & ACC_FINAL != 0 { + modifiers.push("final"); + } + if flags & ACC_ABSTRACT != 0 { + modifiers.push("abstract"); + } + modifiers +} + +/// The classfile reader only distinguishes classes from interfaces. A record is a class whose superclass is `java.lang.Record`. +fn classpath_kind(ty: &ClasspathType) -> Value { + if ty.super_class.as_deref() == Some("java.lang.Record") { + return json!("record"); + } + serde_json::to_value(ty.kind).unwrap_or(Value::Null) +} + +/// Type parameters of a generic signature, `...`, with the erased dotted +/// class of their first bound. +fn type_parameters(signature: &str) -> Vec<(String, String)> { + let bytes = signature.as_bytes(); + let mut out = Vec::new(); + if bytes.first() != Some(&b'<') { + return out; + } + let mut i = 1; + while i < bytes.len() && bytes[i] != b'>' { + let start = i; + while i < bytes.len() && bytes[i] != b':' { + i += 1; + } + let name = signature[start..i].to_string(); + let mut bound = String::new(); + while i < bytes.len() && bytes[i] == b':' { + i += 1; + if i < bytes.len() && bytes[i] != b':' && bytes[i] != b'>' { + let begin = i; + i = skip_type(bytes, i); + if bound.is_empty() { + bound = erased_class(&signature[begin..i]); + } + } + } + out.push((name, bound)); + } + out +} + +fn skip_type(bytes: &[u8], mut i: usize) -> usize { + match bytes.get(i) { + Some(b'[') => skip_type(bytes, i + 1), + Some(b'L') => { + let mut depth = 0; + while i < bytes.len() { + match bytes[i] { + b'<' => depth += 1, + b'>' => depth -= 1, + b';' if depth == 0 => return i + 1, + _ => {} + } + i += 1; + } + i + } + Some(b'T') => bytes[i..].iter().position(|&b| b == b';').map_or(bytes.len(), |end| i + end + 1), + _ => i + 1, + } +} + +/// The generic superclass and interfaces of a class `Signature`, as type facts (`id`, `args`, +/// `kind` `typevar`/`array`/`wildcard`). +fn signature_supertypes(signature: &str) -> (Option, Vec) { + let bytes = signature.as_bytes(); + let mut i = 0; + if bytes.first() == Some(&b'<') { + let mut depth = 0; + while i < bytes.len() { + match bytes[i] { + b'<' => depth += 1, + b'>' => depth -= 1, + _ => {} + } + i += 1; + if depth == 0 { + break; + } + } + } + let superclass = (i < bytes.len()).then(|| signature_type(signature, &mut i)); + let mut interfaces = Vec::new(); + while i < bytes.len() { + interfaces.push(signature_type(signature, &mut i)); + } + (superclass, interfaces) +} + +/// One JVM type signature starting at `i`, which is moved past it. +fn signature_type(signature: &str, i: &mut usize) -> Value { + let bytes = signature.as_bytes(); + let Some(&head) = bytes.get(*i) else { + return json!({"kind": "unknown"}); + }; + *i += 1; + match head { + b'[' => json!({"kind": "array", "elem": signature_type(signature, i)}), + b'T' => { + let end = signature[*i..].find(';').map_or(bytes.len(), |e| *i + e); + let name = &signature[*i..end]; + *i = end + 1; + json!({"kind": "typevar", "id": name}) + } + b'*' => json!({"kind": "wildcard"}), + b'+' => json!({"kind": "wildcard", "extends": signature_type(signature, i)}), + b'-' => json!({"kind": "wildcard", "super": signature_type(signature, i)}), + b'L' => { + let mut name = Vec::new(); + let mut args = Vec::new(); + while *i < bytes.len() { + match bytes[*i] { + b';' => { + *i += 1; + break; + } + b'<' => { + *i += 1; + args.clear(); + while *i < bytes.len() && bytes[*i] != b'>' { + args.push(signature_type(signature, i)); + } + *i += 1; + } + b'.' => { + name.push(b'$'); + *i += 1; + } + b'/' => { + name.push(b'.'); + *i += 1; + } + c => { + name.push(c); + *i += 1; + } + } + } + json!({"id": String::from_utf8_lossy(&name), "args": args}) + } + primitive => json!({"id": primitive_name(primitive)}), + } +} + +fn primitive_name(code: u8) -> &'static str { + match code { + b'B' => "byte", + b'C' => "char", + b'D' => "double", + b'F' => "float", + b'I' => "int", + b'J' => "long", + b'S' => "short", + b'Z' => "boolean", + _ => "void", + } +} + +fn erased_class(signature: &str) -> String { + signature + .strip_prefix('L') + .map(|rest| rest.split(['<', ';']).next().unwrap_or_default().replace('/', ".")) + .unwrap_or_default() +} + +/// The JDK table's class and method parameter type variables, in the shape of [`parameter_variables`]. +fn jdk_parameter_variables(class: Option<&str>, method: Option<&str>) -> Option> { + if class.is_none() && method.is_none() { + return None; + } + let class = class.map(parameter_type_variables).unwrap_or_default(); + let method = method.map(parameter_type_variables).unwrap_or_default(); + let slot = |prefix: char, slots: &[Option<(usize, &str)>], i: usize| { + slots.get(i).copied().flatten().map(|(index, array)| format!("{prefix}{index}{array}")) + }; + Some( + (0..class.len().max(method.len())) + .map(|i| slot('c', &class, i).or_else(|| slot('m', &method, i)).unwrap_or_default()) + .collect(), + ) +} + +/// Per parameter of a method signature, the type variable it is: `c1` for the owner's second type +/// parameter, `m0[]` for an array of the method's first one, or empty. +fn parameter_variables(signature: &str, parameters: &[(String, String)]) -> Vec { + let own = type_parameters(signature); + let bytes = signature.as_bytes(); + let Some(open) = signature.find('(') else { + return Vec::new(); + }; + let mut out = Vec::new(); + let mut i = open + 1; + while i < bytes.len() && bytes[i] != b')' { + let end = skip_type(bytes, i); + let parameter = &signature[i..end]; + let element = parameter.trim_start_matches('['); + let array = "[]".repeat(parameter.len() - element.len()); + let variable = element.strip_prefix('T').and_then(|v| v.strip_suffix(';')); + out.push(match variable { + Some(name) => match own.iter().position(|(p, _)| p == name) { + Some(index) => format!("m{index}{array}"), + None => parameters.iter().position(|(p, _)| p == name).map_or_else(String::new, |index| format!("c{index}{array}")), + }, + None => String::new(), + }); + i = end; + } + out +} + +/// The return type of a method `Signature`, keeping the type variables it is written in terms of. +fn return_type(signature: &str) -> Option { + let close = signature.rfind(')')?; + let mut i = close + 1; + if i >= signature.len() { + return None; + } + Some(signature_type(signature, &mut i)) +} + +/// The declared parameter types of a method `Signature`, as type facts keeping the type variables +/// they are written in terms of, so that the caller can substitute them for the receiver's arguments. +fn parameter_types(signature: &str) -> Vec { + let bytes = signature.as_bytes(); + let Some(open) = signature.find('(') else { + return Vec::new(); + }; + let mut i = open + 1; + let mut out = Vec::new(); + while i < bytes.len() && bytes[i] != b')' { + out.push(signature_type(signature, &mut i)); + } + out +} + +/// Index of the class type parameter a method signature returns, when it returns one directly. +fn class_return_parameter(signature: &str, parameters: &[(String, String)]) -> Option { + let returned = signature[signature.rfind(')')? + 1..].strip_prefix('T')?; + let name = &returned[..returned.find(';')?]; + if type_parameters(signature).iter().any(|(own, _)| own == name) { + return None; + } + parameters.iter().position(|(parameter, _)| parameter == name) +} + +fn access_visibility(flags: u16) -> &'static str { + if flags & ACC_PUBLIC != 0 { + "public" + } else if flags & ACC_PROTECTED != 0 { + "protected" + } else if flags & ACC_PRIVATE != 0 { + "private" + } else { + "package" + } +} + +fn jdk_type_info(ty: &JdkType) -> Value { + let is_enum = serde_json::to_value(ty.kind).ok().and_then(|k| k.as_str().map(|k| k == "enum")).unwrap_or(false); + let own_descriptor = format!("L{};", ty.fqn.replace('.', "/")); + let members: Vec = ty + .members + .iter() + .map(|m| { + let constant = is_enum && m.kind == JdkMemberKind::Field && m.is_static && m.is_final && m.descriptor == own_descriptor; + json!({ + "enum": constant, + "k": if m.kind == JdkMemberKind::Method { "m" } else { "f" }, + "name": m.name, + "desc": m.descriptor, + "static": m.is_static, + "final": m.is_final, + "abstract": m.is_abstract, + "vis": format!("{:?}", m.visibility).to_lowercase(), + "retParam": m.return_type_class_param_index, + "paramVars": jdk_parameter_variables(m.parameter_type_variables, m.parameter_method_type_variables), + }) + }) + .collect(); + let mut modifiers = Vec::new(); + if ty.is_public { + modifiers.push("public"); + } + if ty.is_abstract { + modifiers.push("abstract"); + } + if ty.is_sealed { + modifiers.push("sealed"); + } + if is_enum { + modifiers.push("final"); + } + json!({ + "origin": "jdk", + "kind": serde_json::to_value(ty.kind).unwrap_or(Value::Null), + "super": ty.super_type, + "interfaces": ty.interfaces, + "modifiers": modifiers, + "typeParameters": ty.type_parameters, + "members": members, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn field_descriptors_round_trip_source_spellings() { + assert_eq!(field_descriptor("java.io.PrintStream"), "Ljava/io/PrintStream;"); + assert_eq!(field_descriptor("int"), "I"); + assert_eq!(field_descriptor("java.util.Map$Entry[][]"), "[[Ljava/util/Map$Entry;"); + assert_eq!(field_descriptor("long[]"), "[J"); + } + + #[test] + fn supertypes_keep_type_arguments_and_type_variables() { + let (superclass, interfaces) = signature_supertypes( + "Ljava/util/AbstractList;Ljava/util/List<[TE;>;Ljava/util/Map$Entry<*+Ljava/lang/Number;>;", + ); + assert_eq!(superclass, Some(json!({"id": "java.util.AbstractList", "args": [{"kind": "typevar", "id": "E"}]}))); + assert_eq!( + interfaces, + vec![ + json!({"id": "java.util.List", "args": [{"kind": "array", "elem": {"kind": "typevar", "id": "E"}}]}), + json!({"id": "java.util.Map$Entry", "args": [{"kind": "wildcard"}, {"kind": "wildcard", "extends": {"id": "java.lang.Number", "args": []}}]}), + ] + ); + // A member of a generic outer class keeps only its own arguments. + let (superclass, _) = signature_supertypes("Lp/Outer.Inner;"); + assert_eq!(superclass, Some(json!({"id": "p.Outer$Inner", "args": [{"id": "java.lang.String", "args": []}]}))); + } +} diff --git a/semsitter-bridge/src/wire.rs b/semsitter-bridge/src/wire.rs new file mode 100644 index 00000000000..a067a915ab3 --- /dev/null +++ b/semsitter-bridge/src/wire.rs @@ -0,0 +1,593 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ + +//! Protobuf codec for [`super::proto`](../../proto/bridge.proto). The messages are the only +//! bytes that cross the process boundary; JSON stays a debug dump. + +use anyhow::{Result, anyhow}; +use serde_json::{Map, Value, json}; + +pub enum Request { + Analyze { root: String, files: Vec, classpath: Vec }, + Cst { path: String }, + Types { names: Vec }, + Parse { source: String }, + Quit, +} + +pub struct Incoming { + pub id: u32, + pub request: Request, +} + +pub fn decode_request(bytes: &[u8]) -> Result { + let mut id = 0u32; + let mut analyze = None; + let mut cst = None; + let mut types = None; + let mut parse = None; + let mut quit = false; + for (field, value) in fields(bytes)? { + match (field, value) { + (1, Wire::Varint(v)) => id = v as u32, + (2, Wire::Bytes(b)) => analyze = Some(decode_analyze(&b)?), + (3, Wire::Bytes(b)) => cst = Some(decode_path(&b)?), + (4, Wire::Bytes(b)) => types = Some(decode_names(&b)?), + (5, Wire::Bytes(b)) => parse = Some(decode_source(&b)?), + (6, Wire::Varint(v)) => quit = v != 0, + _ => {} + } + } + let request = if let Some((root, files, classpath)) = analyze { + Request::Analyze { root, files, classpath } + } else if let Some(path) = cst { + Request::Cst { path } + } else if let Some(names) = types { + Request::Types { names } + } else if let Some(source) = parse { + Request::Parse { source } + } else if quit { + Request::Quit + } else { + return Err(anyhow!("request has no op")); + }; + Ok(Incoming { id, request }) +} + +pub fn encode_response(id: u32, body: &Value) -> Vec { + let mut out = Vec::new(); + varint_field(&mut out, 1, id as u64); + let ok = body.get("ok").and_then(Value::as_bool).unwrap_or(false); + if ok { + varint_field(&mut out, 2, 1); + } + if let Some(error) = body.get("error").and_then(Value::as_str) { + string_field(&mut out, 3, error); + } + if let Some(root) = body.get("root").and_then(Value::as_str) { + let mut msg = Vec::new(); + string_field(&mut msg, 1, root); + for file in strings(body, "files") { + string_field(&mut msg, 2, &file); + } + message_field(&mut out, 4, &msg); + } + if let Some(document) = body.get("cst") { + message_field(&mut out, 5, &encode_document(document)); + } + if let Some(types) = body.get("types") { + if let Ok(json) = serde_json::to_string(types) { + string_field(&mut out, 6, &json); + } + } + out +} + +pub fn decode_response(bytes: &[u8]) -> Result { + let mut id = Value::Null; + let mut ok = false; + let mut error = None; + let mut analyze = None; + let mut document = None; + let mut types = None; + for (field, value) in fields(bytes)? { + match (field, value) { + (1, Wire::Varint(v)) => id = json!(v), + (2, Wire::Varint(v)) => ok = v != 0, + (3, Wire::Bytes(b)) => error = Some(String::from_utf8_lossy(&b).into_owned()), + (4, Wire::Bytes(b)) => analyze = Some(decode_analyze_result(&b)?), + (5, Wire::Bytes(b)) => document = Some(decode_document(&b)?), + (6, Wire::Bytes(b)) => types = Some(serde_json::from_slice(&b).unwrap_or(Value::Null)), + _ => {} + } + } + let mut object = Map::new(); + object.insert("id".into(), id); + object.insert("ok".into(), Value::Bool(ok)); + if let Some(error) = error { + object.insert("error".into(), Value::String(error)); + } + if let Some((root, files)) = analyze { + object.insert("root".into(), Value::String(root)); + object.insert("files".into(), json!(files)); + } + if let Some(document) = document { + object.insert("cst".into(), document); + } + if let Some(types) = types { + object.insert("types".into(), types); + } + Ok(Value::Object(object)) +} + +fn encode_document(document: &Value) -> Vec { + let mut out = Vec::new(); + if document.get("hasError").and_then(Value::as_bool) == Some(true) { + varint_field(&mut out, 1, 1); + } + if let Some(tree) = document.get("tree") { + message_field(&mut out, 2, &encode_node(tree)); + } + if let Some(path) = document.get("path").and_then(Value::as_str) { + string_field(&mut out, 3, path); + } + if document.get("semantic").and_then(Value::as_bool) == Some(true) { + varint_field(&mut out, 4, 1); + } + if let Some(symbols) = document.get("symbols") { + if let Ok(json) = serde_json::to_string(symbols) { + string_field(&mut out, 5, &json); + } + } + if let Some(facts) = document.get("facts").and_then(Value::as_object) { + for (key, fact) in facts { + let mut entry = Vec::new(); + string_field(&mut entry, 1, key); + message_field(&mut entry, 2, &encode_fact(fact)); + message_field(&mut out, 6, &entry); + } + } + out +} + +fn encode_fact(fact: &Value) -> Vec { + let mut out = Vec::new(); + if let Some(kind) = fact.get("kind").and_then(Value::as_str) { + string_field(&mut out, 1, kind); + } + if let Some(tier) = fact.get("tier").and_then(Value::as_str) { + string_field(&mut out, 2, tier); + } + if let Some(parent) = fact.get("parent").and_then(Value::as_str) { + string_field(&mut out, 3, parent); + } + if let Some(props) = fact.get("props").and_then(Value::as_object) { + for (name, values) in props { + let mut prop = Vec::new(); + string_field(&mut prop, 1, name); + for value in values.as_array().into_iter().flatten().filter_map(Value::as_str) { + string_field(&mut prop, 2, value); + } + message_field(&mut out, 4, &prop); + } + } + for (field, key) in [(5, "extends"), (6, "implements"), (7, "permits"), (8, "returns"), (9, "type"), (10, "params"), (11, "overrides"), (12, "annotations")] { + for value in strings(fact, key) { + string_field(&mut out, field, &value); + } + } + out +} + +fn encode_node(node: &Value) -> Vec { + let mut out = Vec::new(); + if let Some(kind) = node.get("k").and_then(Value::as_str) { + string_field(&mut out, 1, kind); + } + if node.get("n").and_then(Value::as_bool) == Some(true) { + varint_field(&mut out, 2, 1); + } + if let Some(field) = node.get("f").and_then(Value::as_str) { + string_field(&mut out, 3, field); + } + if node.get("m").and_then(Value::as_bool) == Some(true) { + varint_field(&mut out, 4, 1); + } + if let Some(position) = node.get("p").and_then(Value::as_array) { + for (field, value) in position.iter().enumerate().take(4) { + varint_field(&mut out, 5 + field as u32, value.as_u64().unwrap_or(0)); + } + } + if let Some(span) = node.get("b").and_then(Value::as_array) { + for (field, value) in span.iter().enumerate().take(2) { + varint_field(&mut out, 9 + field as u32, value.as_u64().unwrap_or(0)); + } + } + if let Some(text) = node.get("t").and_then(Value::as_str) { + string_field(&mut out, 11, text); + } + for child in node.get("c").and_then(Value::as_array).into_iter().flatten() { + message_field(&mut out, 12, &encode_node(child)); + } + for decl in strings(node, "decls") { + string_field(&mut out, 13, &decl); + } + for reference in node.get("refs").and_then(Value::as_array).into_iter().flatten() { + message_field(&mut out, 14, &encode_ref(reference)); + } + out +} + +fn encode_ref(reference: &Value) -> Vec { + let mut out = Vec::new(); + for (field, key) in [(1, "access"), (2, "outcome"), (3, "target"), (4, "reason")] { + if let Some(value) = reference.get(key).and_then(Value::as_str) { + string_field(&mut out, field, value); + } + } + if reference.get("bounded").and_then(Value::as_bool) == Some(true) { + varint_field(&mut out, 5, 1); + } + if let Some(facts) = reference.get("facts") { + if !facts.is_null() { + if let Ok(json) = serde_json::to_string(facts) { + string_field(&mut out, 6, &json); + } + } + } + out +} + +fn decode_document(bytes: &[u8]) -> Result { + let mut object = Map::new(); + object.insert("hasError".into(), Value::Bool(false)); + object.insert("semantic".into(), Value::Bool(false)); + object.insert("symbols".into(), json!([])); + object.insert("facts".into(), json!({})); + for (field, value) in fields(bytes)? { + match (field, value) { + (1, Wire::Varint(v)) => object.insert("hasError".into(), Value::Bool(v != 0)), + (2, Wire::Bytes(b)) => object.insert("tree".into(), decode_node(&b)?), + (3, Wire::Bytes(b)) => object.insert("path".into(), Value::String(text(&b))), + (4, Wire::Varint(v)) => object.insert("semantic".into(), Value::Bool(v != 0)), + (5, Wire::Bytes(b)) => object.insert("symbols".into(), serde_json::from_slice(&b).unwrap_or_else(|_| json!([]))), + (6, Wire::Bytes(b)) => { + let (key, fact) = decode_fact_entry(&b)?; + object + .entry("facts") + .or_insert_with(|| json!({})) + .as_object_mut() + .expect("facts is an object") + .insert(key, fact); + None + } + _ => None, + }; + } + Ok(Value::Object(object)) +} + +fn decode_fact_entry(bytes: &[u8]) -> Result<(String, Value)> { + let mut key = String::new(); + let mut fact = json!({}); + for (field, value) in fields(bytes)? { + match (field, value) { + (1, Wire::Bytes(b)) => key = text(&b), + (2, Wire::Bytes(b)) => fact = decode_fact(&b)?, + _ => {} + } + } + Ok((key, fact)) +} + +fn decode_fact(bytes: &[u8]) -> Result { + let mut object = Map::new(); + for (field, value) in fields(bytes)? { + match (field, value) { + (1, Wire::Bytes(b)) => object.insert("kind".into(), Value::String(text(&b))), + (2, Wire::Bytes(b)) => object.insert("tier".into(), Value::String(text(&b))), + (3, Wire::Bytes(b)) => object.insert("parent".into(), Value::String(text(&b))), + (4, Wire::Bytes(b)) => { + let (name, values) = decode_prop(&b)?; + let props = object.entry("props").or_insert_with(|| json!({})); + props.as_object_mut().expect("props").insert(name, json!(values)); + None + } + (5, Wire::Bytes(b)) => push_string(&mut object, "extends", text(&b)), + (6, Wire::Bytes(b)) => push_string(&mut object, "implements", text(&b)), + (7, Wire::Bytes(b)) => push_string(&mut object, "permits", text(&b)), + (8, Wire::Bytes(b)) => push_string(&mut object, "returns", text(&b)), + (9, Wire::Bytes(b)) => push_string(&mut object, "type", text(&b)), + (10, Wire::Bytes(b)) => push_string(&mut object, "params", text(&b)), + (11, Wire::Bytes(b)) => push_string(&mut object, "overrides", text(&b)), + (12, Wire::Bytes(b)) => push_string(&mut object, "annotations", text(&b)), + _ => None, + }; + } + Ok(Value::Object(object)) +} + +fn decode_prop(bytes: &[u8]) -> Result<(String, Vec)> { + let mut name = String::new(); + let mut values = Vec::new(); + for (field, value) in fields(bytes)? { + match (field, value) { + (1, Wire::Bytes(b)) => name = text(&b), + (2, Wire::Bytes(b)) => values.push(text(&b)), + _ => {} + } + } + Ok((name, values)) +} + +fn decode_node(bytes: &[u8]) -> Result { + let mut object = Map::new(); + let mut position = [0u64; 4]; + let mut span = [0u64; 2]; + let mut saw_position = false; + let mut saw_span = false; + for (field, value) in fields(bytes)? { + match (field, value) { + (1, Wire::Bytes(b)) => object.insert("k".into(), Value::String(text(&b))), + (2, Wire::Varint(v)) if v != 0 => object.insert("n".into(), Value::Bool(true)), + (3, Wire::Bytes(b)) => object.insert("f".into(), Value::String(text(&b))), + (4, Wire::Varint(v)) if v != 0 => object.insert("m".into(), Value::Bool(true)), + (5..=8, Wire::Varint(v)) => { + position[(field - 5) as usize] = v; + saw_position = true; + None + } + (9 | 10, Wire::Varint(v)) => { + span[(field - 9) as usize] = v; + saw_span = true; + None + } + (11, Wire::Bytes(b)) => object.insert("t".into(), Value::String(text(&b))), + (12, Wire::Bytes(b)) => { + let children = object.entry("c").or_insert_with(|| json!([])); + children.as_array_mut().expect("children").push(decode_node(&b)?); + None + } + (13, Wire::Bytes(b)) => push_string(&mut object, "decls", text(&b)), + (14, Wire::Bytes(b)) => { + let refs = object.entry("refs").or_insert_with(|| json!([])); + refs.as_array_mut().expect("refs").push(decode_ref(&b)?); + None + } + _ => None, + }; + } + if saw_position { + object.insert("p".into(), json!(position)); + } + if saw_span { + object.insert("b".into(), json!(span)); + } + Ok(Value::Object(object)) +} + +fn decode_ref(bytes: &[u8]) -> Result { + let mut object = Map::new(); + for (field, value) in fields(bytes)? { + match (field, value) { + (1, Wire::Bytes(b)) => object.insert("access".into(), Value::String(text(&b))), + (2, Wire::Bytes(b)) => object.insert("outcome".into(), Value::String(text(&b))), + (3, Wire::Bytes(b)) => object.insert("target".into(), Value::String(text(&b))), + (4, Wire::Bytes(b)) => object.insert("reason".into(), Value::String(text(&b))), + (5, Wire::Varint(v)) if v != 0 => object.insert("bounded".into(), Value::Bool(true)), + (6, Wire::Bytes(b)) => object.insert("facts".into(), serde_json::from_slice(&b).unwrap_or(Value::Null)), + _ => None, + }; + } + Ok(Value::Object(object)) +} + +fn decode_analyze(bytes: &[u8]) -> Result<(String, Vec, Vec)> { + let mut root = String::new(); + let mut files = Vec::new(); + let mut classpath = Vec::new(); + for (field, value) in fields(bytes)? { + match (field, value) { + (1, Wire::Bytes(b)) => root = text(&b), + (2, Wire::Bytes(b)) => files.push(text(&b)), + (3, Wire::Bytes(b)) => classpath.push(text(&b)), + _ => {} + } + } + Ok((root, files, classpath)) +} + +fn decode_analyze_result(bytes: &[u8]) -> Result<(String, Vec)> { + let mut root = String::new(); + let mut files = Vec::new(); + for (field, value) in fields(bytes)? { + match (field, value) { + (1, Wire::Bytes(b)) => root = text(&b), + (2, Wire::Bytes(b)) => files.push(text(&b)), + _ => {} + } + } + Ok((root, files)) +} + +fn decode_path(bytes: &[u8]) -> Result { + for (field, value) in fields(bytes)? { + if field == 1 { + if let Wire::Bytes(b) = value { + return Ok(text(&b)); + } + } + } + Ok(String::new()) +} + +fn decode_names(bytes: &[u8]) -> Result> { + let mut names = Vec::new(); + for (field, value) in fields(bytes)? { + if field == 1 { + if let Wire::Bytes(b) = value { + names.push(text(&b)); + } + } + } + Ok(names) +} + +fn decode_source(bytes: &[u8]) -> Result { + decode_path(bytes) +} + +fn push_string(object: &mut Map, key: &str, value: String) -> Option { + let array = object.entry(key).or_insert_with(|| json!([])); + array.as_array_mut().expect(key).push(Value::String(value)); + None +} + +fn strings(value: &Value, key: &str) -> Vec { + value + .get(key) + .and_then(Value::as_array) + .into_iter() + .flatten() + .filter_map(|v| v.as_str().map(str::to_string)) + .collect() +} + +fn text(bytes: &[u8]) -> String { + String::from_utf8_lossy(bytes).into_owned() +} + +enum Wire { + Varint(u64), + Bytes(Vec), +} + +fn fields(bytes: &[u8]) -> Result> { + let mut out = Vec::new(); + let mut i = 0; + while i < bytes.len() { + let (key, next) = varint_at(bytes, i)?; + i = next; + let field = (key >> 3) as u32; + match key & 7 { + 0 => { + let (value, next) = varint_at(bytes, i)?; + i = next; + out.push((field, Wire::Varint(value))); + } + 2 => { + let (len, next) = varint_at(bytes, i)?; + i = next; + let len = len as usize; + if i + len > bytes.len() { + return Err(anyhow!("truncated protobuf field {field}")); + } + out.push((field, Wire::Bytes(bytes[i..i + len].to_vec()))); + i += len; + } + 1 => i += 8, + 5 => i += 4, + other => return Err(anyhow!("unsupported protobuf wire type {other}")), + } + } + Ok(out) +} + +fn varint_at(bytes: &[u8], mut i: usize) -> Result<(u64, usize)> { + let mut value = 0u64; + let mut shift = 0; + while i < bytes.len() && shift < 64 { + let byte = bytes[i]; + i += 1; + value |= ((byte & 0x7f) as u64) << shift; + if byte & 0x80 == 0 { + return Ok((value, i)); + } + shift += 7; + } + Err(anyhow!("truncated varint")) +} + +fn varint_field(out: &mut Vec, field: u32, value: u64) { + varint(out, (field as u64) << 3); + varint(out, value); +} + +fn string_field(out: &mut Vec, field: u32, value: &str) { + bytes_field(out, field, value.as_bytes()); +} + +fn message_field(out: &mut Vec, field: u32, value: &[u8]) { + bytes_field(out, field, value); +} + +fn bytes_field(out: &mut Vec, field: u32, value: &[u8]) { + varint(out, ((field as u64) << 3) | 2); + varint(out, value.len() as u64); + out.extend_from_slice(value); +} + +fn varint(out: &mut Vec, mut value: u64) { + loop { + let mut byte = (value & 0x7f) as u8; + value >>= 7; + if value != 0 { + byte |= 0x80; + } + out.push(byte); + if value == 0 { + break; + } + } +} + +pub fn encode_request(id: u32, request: &Request) -> Vec { + let mut out = Vec::new(); + varint_field(&mut out, 1, id as u64); + match request { + Request::Analyze { root, files, classpath } => { + let mut msg = Vec::new(); + string_field(&mut msg, 1, root); + for file in files { + string_field(&mut msg, 2, file); + } + for entry in classpath { + string_field(&mut msg, 3, entry); + } + message_field(&mut out, 2, &msg); + } + Request::Cst { path } => { + let mut msg = Vec::new(); + string_field(&mut msg, 1, path); + message_field(&mut out, 3, &msg); + } + Request::Types { names } => { + let mut msg = Vec::new(); + for name in names { + string_field(&mut msg, 1, name); + } + message_field(&mut out, 4, &msg); + } + Request::Parse { source } => { + let mut msg = Vec::new(); + string_field(&mut msg, 1, source); + message_field(&mut out, 5, &msg); + } + Request::Quit => varint_field(&mut out, 6, 1), + } + out +} diff --git a/semsitter-bridge/tests/cst.rs b/semsitter-bridge/tests/cst.rs new file mode 100644 index 00000000000..42b81862bbe --- /dev/null +++ b/semsitter-bridge/tests/cst.rs @@ -0,0 +1,128 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ + +use std::path::{Path, PathBuf}; + +use serde_json::Value; + +fn write(dir: &Path, rel: &str, text: &str) -> PathBuf { + let path = dir.join(rel); + std::fs::create_dir_all(path.parent().unwrap()).unwrap(); + std::fs::write(&path, text).unwrap(); + path +} + +fn find<'a>(node: &'a Value, text: &str, out: &mut Vec<&'a Value>) { + if node.get("t").and_then(Value::as_str) == Some(text) { + out.push(node); + } + for child in node.get("c").and_then(Value::as_array).into_iter().flatten() { + find(child, text, out); + } +} + +fn nodes<'a>(cst: &'a Value, text: &str) -> Vec<&'a Value> { + let mut out = Vec::new(); + find(&cst["tree"], text, &mut out); + out +} + +#[test] +fn cst_carries_declarations_references_and_facts() { + let dir = tempfile::tempdir().unwrap(); + let a = write( + dir.path(), + "src/p/A.java", + "package p;\nimport java.util.List;\npublic class A {\n int f;\n String name(List l) { return l.get(f); }\n}\n", + ); + let b = write( + dir.path(), + "src/p/B.java", + "package p;\nclass B extends A {\n void run() { A a = new A(); String s = a.name(null); }\n}\n", + ); + + let analysis = semsitter_bridge::analyze(dir.path(), &[a.clone(), b], &[]).unwrap(); + assert_eq!(analysis.files(), ["src/p/A.java", "src/p/B.java"]); + + let cst_a = analysis.cst(a.to_str().unwrap()).unwrap(); + assert_eq!(cst_a["path"], "src/p/A.java"); + assert_eq!(cst_a["semantic"], true); + assert_eq!(cst_a["tree"]["k"], "program"); + assert_eq!(cst_a["hasError"], false); + + let class_name = nodes(&cst_a, "A")[0]; + assert_eq!(class_name["decls"][0], "p.A"); + assert_eq!(class_name["p"], serde_json::json!([3, 13, 3, 14])); + + let get = nodes(&cst_a, "get")[0]; + let target = get["refs"][0]["target"].as_str().unwrap(); + assert!(target.starts_with("java.util.List"), "{target}"); + + let field_use = nodes(&cst_a, "f")[1]; + assert_eq!(field_use["refs"][0]["target"], "p.A#f"); + + let cst_b = analysis.cst("src/p/B.java").unwrap(); + assert_eq!(cst_b["facts"]["p.B"]["extends"][0], "p.A"); + let name_call = nodes(&cst_b, "name")[0]; + assert_eq!(name_call["refs"][0]["target"], "p.A#name(java.util.List`1)"); + assert_eq!(cst_b["facts"]["p.A#name(java.util.List`1)"]["returns"][0], "java.lang.String"); +} + +#[test] +fn types_come_from_the_project_graph_and_the_jdk_table_with_their_supertypes() { + let dir = tempfile::tempdir().unwrap(); + let a = write(dir.path(), "p/A.java", "package p;\npublic class A extends java.util.ArrayList { int f; void m() {} }\n"); + let analysis = semsitter_bridge::analyze(dir.path(), &[a], &[]).unwrap(); + let types = analysis.types(&["p.A".to_string()]); + assert_eq!(types["p.A"]["origin"], "project"); + assert_eq!(types["p.A"]["super"], "java.util.ArrayList`1"); + let member_ids: Vec<&str> = types["p.A"]["members"].as_array().unwrap().iter().map(|m| m["id"].as_str().unwrap()).collect(); + assert!(member_ids.contains(&"p.A#m()"), "{member_ids:?}"); + assert_eq!(types["java.util.ArrayList"]["origin"], "jdk"); + assert_eq!(types["java.util.AbstractList"]["super"], "java.util.AbstractCollection"); + let list_get = types["java.util.List"]["members"].as_array().unwrap().iter().find(|m| m["name"] == "get").unwrap(); + assert_eq!(list_get["desc"], "(I)Ljava/lang/Object;"); +} + +#[test] +fn dump_json_writes_the_udg_the_semantic_dump_and_the_cst() { + let dir = tempfile::tempdir().unwrap(); + let dump = tempfile::tempdir().unwrap(); + let file = write(dir.path(), "A.java", "class A { int f; }"); + semsitter_bridge::analyze_dumping(dir.path(), &[file], &[], Some(dump.path())).unwrap(); + assert!(dump.path().join("udg.json").is_file()); + let semdump = std::fs::read_to_string(dump.path().join("semdump/A.java.semdump.json")).unwrap(); + assert!(semdump.contains("semantic-dump/1"), "{semdump}"); + let cst = std::fs::read_to_string(dump.path().join("cst/A.java.json")).unwrap(); + assert!(cst.contains("\"k\": \"program\""), "{cst}"); +} + +#[test] +fn a_bom_is_neither_a_syntax_error_nor_a_column() { + let cst = semsitter_bridge::parse_only(b"\xEF\xBB\xBFclass A {}").unwrap(); + assert_eq!(cst["hasError"], false); + assert_eq!(nodes(&cst, "class")[0]["p"], serde_json::json!([1, 0, 1, 5])); +} + +#[test] +fn parse_only_keeps_every_token_and_utf16_columns() { + let cst = semsitter_bridge::parse_only("class É { String s = \"é\"; }".as_bytes()).unwrap(); + let literal = &nodes(&cst, "\"é\"")[0]; + assert_eq!(literal["k"], "string_literal"); + assert_eq!(literal["p"], serde_json::json!([1, 21, 1, 24])); + assert_eq!(nodes(&cst, "{").len(), 1); +} diff --git a/semsitter-bridge/tests/jsonl.rs b/semsitter-bridge/tests/jsonl.rs new file mode 100644 index 00000000000..7c9f99d04f2 --- /dev/null +++ b/semsitter-bridge/tests/jsonl.rs @@ -0,0 +1,58 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ + +use serde_json::{Value, json}; +use semsitter_bridge::server::serve; + +fn exchange(requests: &[Value]) -> Vec { + let input: String = requests.iter().map(|r| format!("{r}\n")).collect(); + let mut output = Vec::new(); + serve(input.as_bytes(), &mut output).unwrap(); + String::from_utf8(output).unwrap().lines().map(|l| serde_json::from_str(l).unwrap()).collect() +} + +#[test] +fn analyze_then_cst_then_quit() { + let dir = tempfile::tempdir().unwrap(); + let file = dir.path().join("A.java"); + std::fs::write(&file, "class A { int f; int g() { return f; } }").unwrap(); + let responses = exchange(&[ + json!({"id": 1, "op": "analyze", "root": dir.path(), "files": [file], "classpath": []}), + json!({"id": 2, "op": "cst", "path": file}), + json!({"id": 3, "op": "cst", "path": "Missing.java"}), + json!({"id": 4, "op": "quit"}), + json!({"id": 5, "op": "parse", "source": "class B {}"}), + ]); + assert_eq!(responses.len(), 4); + assert_eq!(responses[0]["files"], json!(["A.java"])); + assert_eq!(responses[1]["id"], 2); + assert_eq!(responses[1]["cst"]["symbols"].as_array().unwrap().iter().filter(|s| s["name"] == "f").count(), 1); + assert_eq!(responses[2]["ok"], false); + assert_eq!(responses[3], json!({"id": 4, "ok": true})); +} + +#[test] +fn parse_needs_no_analysis_and_errors_keep_the_server_alive() { + let responses = exchange(&[ + json!({"id": 1, "op": "cst", "path": "A.java"}), + json!({"id": 2, "op": "nope"}), + json!({"id": 3, "op": "parse", "source": "class B {}"}), + ]); + assert_eq!(responses[0]["ok"], false); + assert_eq!(responses[1]["error"], "unknown op: nope"); + assert_eq!(responses[2]["cst"]["tree"]["k"], "program"); +} diff --git a/semsitter-bridge/tests/shm.rs b/semsitter-bridge/tests/shm.rs new file mode 100644 index 00000000000..645baab9662 --- /dev/null +++ b/semsitter-bridge/tests/shm.rs @@ -0,0 +1,79 @@ +/* + * SonarQube Java + * Copyright (C) SonarSource Sàrl + * mailto:info AT sonarsource DOT com + * + * You can redistribute and/or modify this program under the terms of + * the Sonar Source-Available License Version 1, as published by SonarSource Sàrl. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the Sonar Source-Available License for more details. + * + * You should have received a copy of the Sonar Source-Available License + * along with this program; if not, see https://sonarsource.com/license/ssal/ + */ + +use std::io::{Read, Write}; +use std::thread; + +use semsitter_bridge::server::serve_shm; +use semsitter_bridge::shm::{Link, ATTACHED, READY}; +use semsitter_bridge::wire::{self, Request}; + +#[test] +fn protobuf_round_trips_a_tree() { + let document = serde_json::json!({ + "ok": true, + "cst": { + "hasError": false, + "semantic": true, + "path": "A.java", + "symbols": [{"id": "A", "kind": "class"}], + "facts": {"A": {"kind": "class", "tier": "IN_PROJECT", "extends": ["java.lang.Object"]}}, + "tree": {"k": "program", "n": true, "p": [1, 0, 1, 10], "b": [0, 10], "t": "class A {}", "decls": ["A"], + "refs": [{"access": "r", "outcome": "bound", "target": "A"}]} + } + }); + let decoded = wire::decode_response(&wire::encode_response(7, &document)).unwrap(); + assert_eq!(decoded["id"], 7); + assert_eq!(decoded["cst"]["tree"]["k"], "program"); + assert_eq!(decoded["cst"]["tree"]["decls"], serde_json::json!(["A"])); + assert_eq!(decoded["cst"]["tree"]["refs"][0]["target"], "A"); + assert_eq!(decoded["cst"]["symbols"][0]["id"], "A"); + assert_eq!(decoded["cst"]["facts"]["A"]["extends"], serde_json::json!(["java.lang.Object"])); +} + +#[cfg(unix)] +#[test] +fn shared_memory_serves_a_parse() { + use std::os::unix::net::UnixStream; + + let (server_side, mut client_side) = UnixStream::pair().unwrap(); + let server = thread::spawn(move || { + let input = server_side.try_clone().unwrap(); + serve_shm(input, server_side, None).unwrap(); + }); + + let mut ready = [0u8; 1]; + client_side.read_exact(&mut ready).unwrap(); + assert_eq!(ready[0], READY); + let mut link = Link::open(&std::process::id().to_string()).unwrap(); + client_side.write_all(&[ATTACHED]).unwrap(); + client_side.flush().unwrap(); + + let mut to_server = client_side.try_clone().unwrap(); + let mut from_server = client_side; + let request = wire::encode_request(3, &Request::Parse { source: "class B {}".into() }); + link.write_request(&request, &mut to_server, &mut from_server).unwrap(); + let response = link.read_response(&mut from_server, &mut to_server).unwrap(); + let decoded = wire::decode_response(&response).unwrap(); + assert_eq!(decoded["ok"], true); + assert_eq!(decoded["cst"]["tree"]["k"], "program"); + + let quit = wire::encode_request(4, &Request::Quit); + link.write_request(&quit, &mut to_server, &mut from_server).unwrap(); + let _ = link.read_response(&mut from_server, &mut to_server).unwrap(); + server.join().unwrap(); +} diff --git a/sonar-java-plugin/license-resources/0bsd.txt b/sonar-java-plugin/license-resources/0bsd.txt new file mode 100644 index 00000000000..5045695ca88 --- /dev/null +++ b/sonar-java-plugin/license-resources/0bsd.txt @@ -0,0 +1,12 @@ +Copyright (C) 2006-2024 Lasse Collin and others + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/sonar-java-plugin/pom.xml b/sonar-java-plugin/pom.xml index a390364e2d4..bcadd0df715 100644 --- a/sonar-java-plugin/pom.xml +++ b/sonar-java-plugin/pom.xml @@ -152,6 +152,13 @@ ${project.build.directory}/generated-resources/profiles + + ${maven.multiModuleProjectDirectory}/semsitter-bridge/prebuilt + + bridge-*.xz + + bridge + @@ -184,8 +191,8 @@ - 21500000 - 19000000 + 45000000 + 10000000 ${project.build.directory}/${project.build.finalName}.jar @@ -233,6 +240,18 @@ ** + + org.apache.commons:commons-compress + + ** + + + + org.tukaani:xz + + ** + + xerces:xercesImpl @@ -282,6 +301,7 @@ 2.7.1 xmlOutput + ${project.basedir}/src/license/licenses.xml false 20000 ${project.build.directory}/temp-licenses/THIRD_PARTY_LICENSES @@ -301,6 +321,10 @@ http://www.opensource.org/licenses/bsd-license.php ${project.baseUri}/license-resources/bsd.txt + + https://opensource.org/license/0bsd + ${project.baseUri}/license-resources/0bsd.txt + diff --git a/sonar-java-plugin/src/license/licenses.xml b/sonar-java-plugin/src/license/licenses.xml new file mode 100644 index 00000000000..35a6081b821 --- /dev/null +++ b/sonar-java-plugin/src/license/licenses.xml @@ -0,0 +1,22 @@ + + + + + \Qorg.tukaani\E + \Qxz\E + + + \Q0BSD\E + \Qrepo\E + + + + + 0BSD + https://opensource.org/license/0bsd + repo + + + + + diff --git a/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/commons-codec.commons-codec-LICENSE.txt b/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/commons-codec.commons-codec-LICENSE.txt new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/commons-codec.commons-codec-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/commons-io.commons-io-LICENSE.txt b/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/commons-io.commons-io-LICENSE.txt new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/commons-io.commons-io-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/org.apache.commons.commons-compress-LICENSE.txt b/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/org.apache.commons.commons-compress-LICENSE.txt new file mode 100644 index 00000000000..d6456956733 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/org.apache.commons.commons-compress-LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/org.tukaani.xz-LICENSE.txt b/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/org.tukaani.xz-LICENSE.txt new file mode 100644 index 00000000000..5045695ca88 --- /dev/null +++ b/sonar-java-plugin/src/main/resources/licenses/THIRD_PARTY_LICENSES/org.tukaani.xz-LICENSE.txt @@ -0,0 +1,12 @@ +Copyright (C) 2006-2024 Lasse Collin and others + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.