| Proposal |
Summary
Eclipse embodies a component/modularity model. It allows plug-ins to express what they need of others (requiring plug-ins and importing packages) as well as what they contribute to others (providing/exporting packages). Ideally plug-ins would export/provide only that code which they consider to be API. Historically however the Eclipse plug-ins (and likely much of the community) have exported all of the code they contain. This is helpful to consumers living on the edge or pushing the envelope but it hinders both security as well as static API analysis done for security or performance optimization. Here we propose a path forward which allows plug-in producers to express their API and thus the runtime to enforce API-only consumption but still enables wild-men developers to access non-API on a selective basis.Last Modified: December 16, 2004
Currently the Eclipse plug-ins expose all their code. The typical plugin.xml looks something like
<plugin ...>
<runtime>
<library name="resources.jar">
<export name="*"/>
</library>
</runtime>
</plugin>
The use of * in the export filter means that all classes in the related JAR are exposed to consumers. Typically these JARs contain both API and implementation classes.
Beyond the appalling aesthetics of exposing the non-API classes, there are a number of pragmatic reasons why this is bad. In particular, this practice inhibits runtime security and static analysis.
Key to the Java security model is understanding and limiting the entry points to a piece of function. The security mechanisms are costly both at execution and development time. It is simply infeasible to secure every part of a large chunk of code like Eclipse if we have to consider all visible methods on all visible classes as entry points. Instead, we look to the API to provide a choke point through which all external access must go. If we secure the API then the implementation code need not be secured (or at least requires less work to secure). Of course, if you take this approach, the runtime must gurarantee that only the API types are visible to outside parties. To do this, there must be a clear, machine-readable description of these types in a plug-in.
Static code analysis is increasingly interesting. It enables both security analysis and verification as well as performance optimizations such as code reduction and inlining. These analysis techniques begin by identifying the entry points in a body of code and then exhaustively traversing all execution paths. In the case of security analysis, the algorithm is looking for unsafe operations and places where permissions are required. For performance analysis the tools look for dead code (code which is not traversed), inlining opportunities and classes, methods and fields which can be obfuscated. Without an explicit and complete specification of the API, these analyses are not possible.
Historically Eclipse has not limited access to the internal classes of a plug-in. We define the API in Javadoc and warn programmers not to stray. This approach is weak and leads to component interaction via non-API routes. Enforcing API-only visibility eliminates this weakness.
Despite the pitfalls, some developers find it convenient or necessary to use internal classes. Some Eclipse plug-ins have been known to do this! In addition, various test suites rely on access to internal types to implement white-box tests. Simply cutting off this access is not friendly.
PDE builds development time classpaths from the exported packages it finds in the plug-ins it manages. Because Eclipse plug-ins typically export all their code, dependent plug-ins have all internal classes on their classpath at development time. This can lead to unintended use of internals through the convenience of code completion. Developers know there is a type called FooBar so type Foo<ctrl><space> and choose from the results (or use Organize Imports). Because they find a type and things seem to work they are happy. Unfortunately, it is entirely possible that the FooBar used is not API since PDE has no way of telling the difference.
Plug-in producers are in the best position to define their API. They clearly know the bounds of what they want to expose/support. Actually defining the API is not particularly challenging. The plugin.xml <export name=""/> syntax and manifest.mf Export-Package: header both allow export specification. The specifications are given using an obvious and simple format. For example, the plugin.xml and manifest.mf markup (only one is needed) to expose the API for the Resource plug-in is shown below.
<plugin ...>
<runtime>
<library name="resources.jar">
<export name="org.eclipse.core.resources, org.eclipse.core.resources.refresh,
org.eclipse.core.resources.team"/>
</library>
</runtime>
</plugin>
Export-Package: org.eclipse.core.resources, org.eclipse.core.resources.refresh, org.eclipse.core.resources.team
The task of defining the exports is relatively straightforward. For most plug-ins it amounts to creating entries similar to those above. Notice that packages which are not to be exposed are simply not listed. Since Eclipse already uses package naming conventions for API packages, this process should take about 5 minutes per Eclipse plug-in. Simply open the jar for a plug-in and note all of the API packages. List these packages in your choice of manifest (plugin.xml or manifest.mf). The effort required for non-Eclipse plug-ins which may not follow any package naming conventions may be slightly greater but is still quite minimal.
Note that classes used in executable extensions and Bundle.loadClass() do not need to be listed in the exports as they are loaded by asking the plug-in itself to load the class rather than going through the normal classloader delegation mechanism.
Of course, if you are using non-API in someone else's plug-in you will have to either clean up your code or negotiate to have the required types exposed by the prerequisite plug-in.
Notice also that there is a subtle difference between exposing a type and it being API. A type is not API simply because it is exported by a plug-in's manifest. plug-ins may be willing to expose types which are provisional API or specific types which are made available for targetted users (e.g, so-called SPI). The definition of API remains under this model. That is, the Javadoc is still the final source.
[Note: We need a better name here. "strict mode" is hard to negate (what is the opposite?). "test mode" is ok but has a wider connotation. Strict mode may in fact be one aspect of a supposed test mode but it likely does not cover all aspects.]
As mentioned in the problem definition, various people expect access to a wider range of classes. To support this we introduce a new (non-standard) OSGi manifest.mf header Export-InternalPackage and a osgi.strictClassLoading property for the framework. The Export-InternalPackage header has exactly the same syntax as Export-Package.
When the framework is in strict mode (i.e., osgi.strictClassLoading = true), only types listed by the Export-Package headers are considered. When the framework is in non-strict mode (i.e., osgi.strictClassLoading = false), the elements on the Export-Package and Export-InternalPackage headers are merged (in that order) and considered by the runtime.
We do not propose any changes to the plugin.xml syntax. Developers using plugin.xml can specify their exports as outlined above and the manifest generator will automatically generate Export-InternalPackage header entries for all packages found in the plug-in but not included in the generated Export-Package header. Note however that the manifest.mf format offers additional power in filtering types from a package. Developers wishing to be more precise about their exports (sub-package control) can acheive this using the manifest.mf file.
By default Eclipse will default to non-strict mode (osgi.strictClassLoading = false) so as to increase compatibility.
As a bare minimum the Eclipse RCP plug-ins must operate correctly in strict mode. Optimally the entire Eclipse SDK will operate correctly in strict mode.
The OSGi code needs to support the strict mode flag. This impacts the following areas:
PDE UI will need to expose the new export header in the appropriate way (e.g., in the package related editor pages). There are several things to note here
PDE should offer the developer the option to use strict mode development-time classpaths. The semantics are the same here as putting the runtime in strict mode. That is, in strict mode only the Export-Package header is used where as in non-strict mode all exports are used. There may be an additional/related ability to add the Export-InternalPackage entries to the secondary classpath for a plug-in project.
In any event, PDE should use the new JDT classpath includes/excludes capabilities to match the compiler classpath to the actual runtime classpath as close as possible.
PDE build likely needs some switches similar to those described above to manage the classpath computations.
As mentioned above, the manifest generator will be updated to generate Export-InternalPackage: entries for every package /type that is not listed in the Export-Package header.
All RCP plug-ins are expected to run correctly in strict mode. This means that their Export-Package list must include all API as well as any packages/classes which are known to be required by friendly plugins. Again, the Export-Package list is NOT a declaration of API. It is a visibility statement much like the public keyword in Java.
The exact syntax of the package lists is open to change. The use of OSGi directives on the standard Export-Package header (for example) has been suggested. The various approaches have benefits and drawbacks and will be considered as an implementation detail. Here we note that the final syntax may not be as described above.
This proposal details the set of changes required to correctly and completely specify the public face (including API) of a plug-in. These changes are needed to allow Eclipse to run in a secure mode as well as facilitating various analysis techniques for security and performance optimization. The changes are quite modest and can be completed by related teams with limited effort.