Skip to content

Commit

Permalink
Set proper File Origins for bundled plugins (#1121)
Browse files Browse the repository at this point in the history
* Extract plugin class logic to separate function
* Introduce a base class for plugin details
* Introduce file origin provider: Allows to override file origin resolved by JARs and 'lib' directories
* Use private modifier
* Reorganize IDE File origin classes. Some origins might be aware about the IDE. Introduce a separate subclass to handle such origins.
* Track bundled plugin metadata with origin
* Allow fully finding plugin classes in explicit locations
* Discover bundled plugin classes with properly set origins
* Classes selector can be provided with explicit locations
* Introduce default plugin class resolver with support for bundled plugins
* Use functional style when mapping dependencies to resolvers
* Use same plugin usage filter rules
* Use exact origin in comparison
  • Loading branch information
novotnyr authored Jul 17, 2024
1 parent dede69c commit fcae030
Show file tree
Hide file tree
Showing 21 changed files with 314 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,18 @@ package com.jetbrains.plugin.structure.ide.classes

import com.jetbrains.plugin.structure.classes.resolvers.FileOrigin
import com.jetbrains.plugin.structure.ide.Ide
import com.jetbrains.plugin.structure.intellij.plugin.IdePlugin
import java.nio.file.Path

sealed class IdeFileOrigin : FileOrigin {
override val parent: FileOrigin? = null

abstract val ide: Ide
data class IdeLibDirectory(override val ide: Ide) : IdeAwareFileOrigin(ide)
data class RepositoryLibrary(override val ide: Ide) : IdeAwareFileOrigin(ide)
data class SourceLibDirectory(override val ide: Ide) : IdeAwareFileOrigin(ide)
data class CompiledModule(override val ide: Ide, val moduleName: String) : IdeAwareFileOrigin(ide)

data class IdeLibDirectory(override val ide: Ide) : IdeFileOrigin()
data class RepositoryLibrary(override val ide: Ide) : IdeFileOrigin()
data class SourceLibDirectory(override val ide: Ide) : IdeFileOrigin()
data class CompiledModule(override val ide: Ide, val moduleName: String) : IdeFileOrigin()
class BundledPlugin(val pluginFile: Path, val idePlugin: IdePlugin) : IdeFileOrigin()

abstract class IdeAwareFileOrigin(open val ide: Ide): IdeFileOrigin()
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
dependencies {
api(project(":structure-intellij"))
api(project(":structure-classes"))
api(project(":structure-ide-classes"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.jetbrains.plugin.structure.intellij.classes.locator

import com.jetbrains.plugin.structure.classes.resolvers.FileOrigin
import com.jetbrains.plugin.structure.intellij.plugin.IdePlugin
import java.nio.file.Path

fun interface FileOriginProvider {
fun getFileOrigin(idePlugin: IdePlugin, pluginFile: Path): FileOrigin
}

Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import java.nio.file.Path
/**
* Locates plugin classes located in a single JAR file.
*/
class JarPluginLocator(private val readMode: Resolver.ReadMode) : ClassesLocator {
class JarPluginLocator(
private val readMode: Resolver.ReadMode,
private val fileOriginProvider: FileOriginProvider = SingleJarFileOriginProvider
) : ClassesLocator {
override val locationKey: LocationKey = JarPluginKey

/**
Expand All @@ -22,7 +25,7 @@ class JarPluginLocator(private val readMode: Resolver.ReadMode) : ClassesLocator
*/
override fun findClasses(idePlugin: IdePlugin, pluginFile: Path): List<Resolver> {
if (pluginFile.isJar()) {
return listOf(JarFileResolver(pluginFile, readMode, PluginFileOrigin.SingleJar(idePlugin)))
return listOf(JarFileResolver(pluginFile, readMode, fileOriginProvider.getFileOrigin(idePlugin, pluginFile)))
}
return emptyList()
}
Expand All @@ -32,4 +35,8 @@ object JarPluginKey : LocationKey {
override val name: String = "jar"

override fun getLocator(readMode: Resolver.ReadMode) = JarPluginLocator(readMode)
}

object SingleJarFileOriginProvider: FileOriginProvider {
override fun getFileOrigin(idePlugin: IdePlugin, pluginFile: Path) = PluginFileOrigin.SingleJar(idePlugin)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,28 @@

package com.jetbrains.plugin.structure.intellij.classes.locator

import com.jetbrains.plugin.structure.base.utils.*
import com.jetbrains.plugin.structure.base.utils.closeOnException
import com.jetbrains.plugin.structure.base.utils.isDirectory
import com.jetbrains.plugin.structure.base.utils.isJar
import com.jetbrains.plugin.structure.base.utils.isZip
import com.jetbrains.plugin.structure.base.utils.listFiles
import com.jetbrains.plugin.structure.classes.resolvers.Resolver
import com.jetbrains.plugin.structure.classes.resolvers.buildDirectoriesResolvers
import com.jetbrains.plugin.structure.classes.resolvers.buildJarOrZipFileResolvers
import com.jetbrains.plugin.structure.intellij.plugin.IdePlugin
import java.nio.file.Path

class LibDirectoryLocator(private val readMode: Resolver.ReadMode) : ClassesLocator {
class LibDirectoryLocator(
private val readMode: Resolver.ReadMode,
private val fileOriginProvider: FileOriginProvider = LibDirectoryOriginProvider
) : ClassesLocator {
override val locationKey = LibDirectoryKey

override fun findClasses(idePlugin: IdePlugin, pluginFile: Path): List<Resolver> {
val pluginLib = pluginFile.resolve("lib")
val resolvers = arrayListOf<Resolver>()
if (pluginLib.isDirectory) {
val libDirectoryOrigin = PluginFileOrigin.LibDirectory(idePlugin)
val libDirectoryOrigin = fileOriginProvider.getFileOrigin(idePlugin, pluginFile)
val jarsOrZips = pluginLib.listFiles().filter { file -> file.isJar() || file.isZip() }
val directories = pluginLib.listFiles().filter { file -> file.isDirectory }
resolvers.closeOnException {
Expand All @@ -28,11 +35,14 @@ class LibDirectoryLocator(private val readMode: Resolver.ReadMode) : ClassesLoca
}
return resolvers
}

}

object LibDirectoryKey : LocationKey {
override val name: String = "lib directory"

override fun getLocator(readMode: Resolver.ReadMode) = LibDirectoryLocator(readMode)
}

object LibDirectoryOriginProvider: FileOriginProvider {
override fun getFileOrigin(idePlugin: IdePlugin, pluginFile: Path) = PluginFileOrigin.LibDirectory(idePlugin)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.jetbrains.plugin.structure.intellij.classes.plugin

import com.jetbrains.plugin.structure.classes.resolvers.FileOrigin
import com.jetbrains.plugin.structure.classes.resolvers.Resolver
import com.jetbrains.plugin.structure.ide.classes.IdeFileOrigin
import com.jetbrains.plugin.structure.intellij.classes.locator.FileOriginProvider
import com.jetbrains.plugin.structure.intellij.classes.locator.JarPluginLocator
import com.jetbrains.plugin.structure.intellij.classes.locator.LibDirectoryLocator
import com.jetbrains.plugin.structure.intellij.classes.locator.LocationKey
import com.jetbrains.plugin.structure.intellij.plugin.IdePlugin
import java.nio.file.Path

class BundledPluginClassesFinder {
companion object {
val LOCATION_KEYS = listOf(BundledPluginJarKey, BundledPluginDirectoryKey)

fun findPluginClasses(
idePlugin: IdePlugin,
additionalKeys: List<LocationKey> = emptyList()
): IdePluginClassesLocations {
return IdePluginClassesFinder.fullyFindPluginClassesInExplicitLocations(idePlugin, LOCATION_KEYS + additionalKeys)
}
}

object BundledPluginJarKey : LocationKey {
override val name: String = "Bundled Plugin JAR"
override fun getLocator(readMode: Resolver.ReadMode) = JarPluginLocator(readMode, BundledPluginOriginator)
}

object BundledPluginDirectoryKey : LocationKey {
override val name: String = "Bundled Plugin Directory"
override fun getLocator(readMode: Resolver.ReadMode) = LibDirectoryLocator(readMode, BundledPluginOriginator)
}

object BundledPluginOriginator : FileOriginProvider {
override fun getFileOrigin(idePlugin: IdePlugin, pluginFile: Path): FileOrigin {
return IdeFileOrigin.BundledPlugin(pluginFile, idePlugin)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,14 @@
package com.jetbrains.plugin.structure.intellij.classes.plugin

import com.jetbrains.plugin.structure.base.plugin.Settings
import com.jetbrains.plugin.structure.base.utils.*
import com.jetbrains.plugin.structure.base.utils.checkIfInterrupted
import com.jetbrains.plugin.structure.base.utils.closeLogged
import com.jetbrains.plugin.structure.base.utils.closeOnException
import com.jetbrains.plugin.structure.base.utils.createDir
import com.jetbrains.plugin.structure.base.utils.exists
import com.jetbrains.plugin.structure.base.utils.isDirectory
import com.jetbrains.plugin.structure.base.utils.isJar
import com.jetbrains.plugin.structure.base.utils.isZip
import com.jetbrains.plugin.structure.classes.resolvers.Resolver
import com.jetbrains.plugin.structure.intellij.classes.locator.ClassesDirectoryKey
import com.jetbrains.plugin.structure.intellij.classes.locator.JarPluginKey
Expand All @@ -16,7 +23,6 @@ import com.jetbrains.plugin.structure.intellij.extractor.PluginExtractor
import com.jetbrains.plugin.structure.intellij.plugin.IdePlugin
import java.io.Closeable
import java.io.IOException
import java.nio.file.Files
import java.nio.file.Path

/**
Expand Down Expand Up @@ -102,7 +108,15 @@ class IdePluginClassesFinder private constructor(
return findPluginClasses(idePlugin, extractDirectory, readMode, additionalKeys)
}

fun findPluginClasses(
fun fullyFindPluginClassesInExplicitLocations(idePlugin: IdePlugin, locations: List<LocationKey>): IdePluginClassesLocations =
IdePluginClassesFinder(
idePlugin,
extractDirectory = Settings.EXTRACT_DIRECTORY.getAsPath().createDir(),
Resolver.ReadMode.FULL,
locations
).findPluginClasses()

private fun findPluginClasses(
idePlugin: IdePlugin,
extractDirectory: Path,
readMode: Resolver.ReadMode = Resolver.ReadMode.FULL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import com.jetbrains.pluginverifier.PluginVerifierMain.main
import com.jetbrains.pluginverifier.options.CmdOpts
import com.jetbrains.pluginverifier.options.OptionsParser
import com.jetbrains.pluginverifier.output.OutputOptions
import com.jetbrains.pluginverifier.plugin.PluginDetailsProviderImpl
import com.jetbrains.pluginverifier.plugin.DefaultPluginDetailsProvider
import com.jetbrains.pluginverifier.plugin.PluginFilesBank
import com.jetbrains.pluginverifier.plugin.SizeLimitedPluginDetailsCache
import com.jetbrains.pluginverifier.reporting.DirectoryBasedPluginVerificationReportage
Expand Down Expand Up @@ -116,7 +116,7 @@ object PluginVerifierMain {

val pluginDownloadDirDiskSpaceSetting = getDiskSpaceSetting("plugin.verifier.cache.dir.max.space", 5L * 1024)
val pluginFilesBank = PluginFilesBank.create(pluginRepository, downloadDirectory, pluginDownloadDirDiskSpaceSetting)
val pluginDetailsProvider = PluginDetailsProviderImpl(getPluginsExtractDirectory())
val pluginDetailsProvider = DefaultPluginDetailsProvider(getPluginsExtractDirectory())

val reportageAggregator = LoggingPluginVerificationReportageAggregator()
DirectoryBasedPluginVerificationReportage(reportageAggregator) { outputOptions.getTargetReportDirectory(it) }.use { reportage ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ class PluginVerifier(
* Selectors of classes that constitute the plugin
* class loader and of classes that should be verified.
*/
private val classesSelectors = listOf(MainClassesSelector(), ExternalBuildClassesSelector())
private val classesSelectors = listOf(MainClassesSelector.forPlugin(), ExternalBuildClassesSelector())

fun IdePluginClassesLocations.createPluginResolver() =
CompositeResolver.create(classesSelectors.flatMap { it.getClassLoader(this) })
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
package com.jetbrains.pluginverifier.filtering

import com.jetbrains.plugin.structure.classes.resolvers.Resolver
import com.jetbrains.plugin.structure.intellij.classes.locator.LocationKey
import com.jetbrains.plugin.structure.intellij.classes.plugin.BundledPluginClassesFinder
import com.jetbrains.plugin.structure.intellij.classes.plugin.IdePluginClassesFinder
import com.jetbrains.plugin.structure.intellij.classes.plugin.IdePluginClassesLocations
import com.jetbrains.plugin.structure.intellij.plugin.IdePlugin
Expand All @@ -21,13 +23,22 @@ import com.jetbrains.plugin.structure.intellij.plugin.PluginXmlUtil
* a) they typically don't contain IntelliJ API usages
* b) the verification may produce false warnings since some libraries optionally depend on missing libraries.
*/
class MainClassesSelector : ClassesSelector {
class MainClassesSelector private constructor(private val locationKeys: List<LocationKey>) : ClassesSelector {

companion object {
fun forPlugin(): MainClassesSelector {
return MainClassesSelector(IdePluginClassesFinder.MAIN_CLASSES_KEYS)
}
fun forBundledPlugin(): MainClassesSelector {
return MainClassesSelector(BundledPluginClassesFinder.LOCATION_KEYS)
}
}

/**
* Selects the plugin's classes that can be referenced by the plugin and its dependencies.
*/
override fun getClassLoader(classesLocations: IdePluginClassesLocations): List<Resolver> =
IdePluginClassesFinder.MAIN_CLASSES_KEYS.flatMap { classesLocations.getResolvers(it) }
locationKeys.flatMap { classesLocations.getResolvers(it) }

/**
* Determines plugin's classes that must be verified.
Expand All @@ -40,7 +51,7 @@ class MainClassesSelector : ClassesSelector {
* we predict the .jar-files that correspond to the plugin itself (not the secondary bundled libraries).
*/
override fun getClassesForCheck(classesLocations: IdePluginClassesLocations): Set<String> {
val resolvers = IdePluginClassesFinder.MAIN_CLASSES_KEYS.flatMap { classesLocations.getResolvers(it) }
val resolvers = locationKeys.flatMap { classesLocations.getResolvers(it) }

val allClassesReferencedFromXml = getAllClassesReferencedFromXml(classesLocations.idePlugin)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.jetbrains.pluginverifier.resolution

import com.jetbrains.plugin.structure.classes.resolvers.CompositeResolver
import com.jetbrains.plugin.structure.classes.resolvers.Resolver
import com.jetbrains.pluginverifier.filtering.ExternalBuildClassesSelector
import com.jetbrains.pluginverifier.filtering.MainClassesSelector
import com.jetbrains.pluginverifier.plugin.PluginDetails

class BundledPluginClassResolverProvider {
private val bundledClassesSelectors = listOf(MainClassesSelector.forBundledPlugin(), ExternalBuildClassesSelector())

fun getResolver(pluginDetails: PluginDetails): Resolver {
val classLocations = pluginDetails.pluginClassesLocations
return bundledClassesSelectors.flatMap { it.getClassLoader(classLocations) }
.let { CompositeResolver.create(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.jetbrains.pluginverifier.dependencies.resolution.DependencyFinder
import com.jetbrains.pluginverifier.ide.IdeDescriptor
import com.jetbrains.pluginverifier.plugin.PluginDetails
import com.jetbrains.pluginverifier.plugin.PluginDetailsCache
import com.jetbrains.pluginverifier.repository.repositories.bundled.BundledPluginInfo
import com.jetbrains.pluginverifier.verifiers.packages.PackageFilter
import com.jetbrains.pluginverifier.verifiers.resolution.caching
import java.io.Closeable
Expand All @@ -24,6 +25,8 @@ class DefaultClassResolverProvider(
private val externalClassesPackageFilter: PackageFilter
) : ClassResolverProvider {

private val bundledPluginClassResolverProvider = BundledPluginClassResolverProvider()

override fun provide(checkedPluginDetails: PluginDetails): ClassResolverProvider.Result {
val closeableResources = arrayListOf<Closeable>()
closeableResources.closeOnException {
Expand All @@ -34,7 +37,7 @@ class DefaultClassResolverProvider(

closeableResources += dependenciesResults

val dependenciesClassResolver = createDependenciesResolver(dependenciesResults)
val dependenciesClassResolver = createDependenciesClassResolver(checkedPluginDetails, dependenciesResults)

val resolver = CompositeResolver.create(
pluginResolver,
Expand All @@ -48,24 +51,34 @@ class DefaultClassResolverProvider(

override fun provideExternalClassesPackageFilter() = externalClassesPackageFilter

private fun createDependenciesResolver(results: List<DependencyFinder.Result>): Resolver {
val resolvers = arrayListOf<Resolver>()
private fun createPluginResolver(pluginDependency: PluginDetails): Resolver =
when (pluginDependency.pluginInfo) {
is BundledPluginInfo -> bundledPluginClassResolverProvider.getResolver(pluginDependency)
else -> pluginDependency.pluginClassesLocations.createPluginResolver()
}

private fun createDependenciesClassResolver(checkedPluginDetails: PluginDetails, dependencies: List<DependencyFinder.Result>): Resolver {
val resolvers = mutableListOf<Resolver>()
resolvers.closeOnException {
for (result in results) {
if (result is DependencyFinder.Result.DetailsProvided) {
val cacheResult = result.pluginDetailsCacheResult
if (cacheResult is PluginDetailsCache.Result.Provided) {
val resolver = try {
cacheResult.pluginDetails.pluginClassesLocations.createPluginResolver()
} catch (e: Exception) {
e.rethrowIfInterrupted()
continue
}
resolvers.add(resolver)
}
}
val pluginDetails = dependencies
.filterIsInstance<DependencyFinder.Result.DetailsProvided>()
.map { it.pluginDetailsCacheResult }
.filterIsInstance<PluginDetailsCache.Result.Provided>()
.map { it.pluginDetails }

resolvers += pluginDetails.mapNotNullInterruptible { createPluginResolver(it) }
}
return CompositeResolver.create(resolvers)
}

private inline fun <T, R> Iterable<T>.mapNotNullInterruptible(transform: (T) -> R): List<R> {
return mapNotNull {
try {
transform(it)
} catch (e: Exception) {
e.rethrowIfInterrupted()
null
}
return CompositeResolver.create(resolvers)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package com.jetbrains.pluginverifier.usages

import com.jetbrains.plugin.structure.classes.resolvers.FileOrigin
import com.jetbrains.pluginverifier.results.location.ClassLocation

class SameOriginApiUsageFilter : ClassLocationApiUsageFilter() {
object SameOriginApiUsageFilter : ClassLocationApiUsageFilter() {
override fun allow(usageLocation: ClassLocation, apiLocation: ClassLocation): Boolean {
val usageOrigin = usageLocation.classFileOrigin
val apiHostOrigin = apiLocation.classFileOrigin

return usageOrigin == apiHostOrigin
return invoke(usageOrigin, apiHostOrigin)
}
}

operator fun invoke(usageOrigin: FileOrigin, apiHostOrigin: FileOrigin): Boolean = usageOrigin == apiHostOrigin
}
Loading

0 comments on commit fcae030

Please sign in to comment.