You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
289 lines
12 KiB
289 lines
12 KiB
4 months ago
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||
|
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||
|
<head>
|
||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||
|
<link rel="stylesheet" href="resources/doc.css" charset="UTF-8" type="text/css" />
|
||
|
<link rel="stylesheet" href="../coverage/jacoco-resources/prettify.css" charset="UTF-8" type="text/css" />
|
||
|
<link rel="shortcut icon" href="resources/report.gif" type="image/gif" />
|
||
|
<script type="text/javascript" src="../coverage/jacoco-resources/prettify.js"></script>
|
||
|
<title>JaCoCo - Implementation Design</title>
|
||
|
</head>
|
||
|
<body onload="prettyPrint()">
|
||
|
|
||
|
<div class="breadcrumb">
|
||
|
<a href="../index.html" class="el_report">JaCoCo</a> >
|
||
|
<a href="index.html" class="el_group">Documentation</a> >
|
||
|
<span class="el_source">Implementation Design</span>
|
||
|
</div>
|
||
|
<div id="content">
|
||
|
|
||
|
<h1>Implementation Design</h1>
|
||
|
|
||
|
<p>
|
||
|
This is a unordered list of implementation design decisions. Each topic tries
|
||
|
to follow this structure:
|
||
|
</p>
|
||
|
|
||
|
<ul>
|
||
|
<li>Problem statement</li>
|
||
|
<li>Proposed Solution</li>
|
||
|
<li>Alternatives and Discussion</li>
|
||
|
</ul>
|
||
|
|
||
|
|
||
|
<h2>Coverage Analysis Mechanism</h2>
|
||
|
|
||
|
<p class="intro">
|
||
|
Coverage information has to be collected at runtime. For this purpose JaCoCo
|
||
|
creates instrumented versions of the original class definitions. The
|
||
|
instrumentation process happens on-the-fly during class loading using so
|
||
|
called Java agents.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
There are several different approaches to collect coverage information. For
|
||
|
each approach different implementation techniques are known. The following
|
||
|
diagram gives an overview with the techniques used by JaCoCo highlighted:
|
||
|
</p>
|
||
|
|
||
|
<img src="resources/implementation.png" alt="Coverage Implementation Techniques"/>
|
||
|
|
||
|
<p>
|
||
|
Byte code instrumentation is very fast, can be implemented in pure Java and
|
||
|
works with every Java VM. On-the-fly instrumentation with the Java agent
|
||
|
hook can be added to the JVM without any modification of the target
|
||
|
application.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
The Java agent hook requires at least 1.5 JVMs. Class files compiled with
|
||
|
debug information (line numbers) allow for source code highlighting. Unluckily
|
||
|
some Java language constructs get compiled to byte code that produces
|
||
|
unexpected highlighting results, especially in case of implicitly generated
|
||
|
code like default constructors or control structures for finally statements.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<h2>Coverage Agent Isolation</h2>
|
||
|
|
||
|
<p class="intro">
|
||
|
The Java agent is loaded by the application class loader. Therefore the
|
||
|
classes of the agent live in the same name space like the application classes
|
||
|
which can result in clashes especially with the third party library ASM. The
|
||
|
JoCoCo build therefore moves all agent classes into a unique package.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
The JaCoCo build renames all classes contained in the
|
||
|
<code>jacocoagent.jar</code> into classes with a
|
||
|
<code>org.jacoco.agent.rt_<randomid></code> prefix, including the
|
||
|
required ASM library classes. The identifier is created from a random number.
|
||
|
As the agent does not provide any API, no one should be affected by this
|
||
|
renaming. This trick also allows that JaCoCo tests can be verified with
|
||
|
JaCoCo.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<h2>Minimal Java Version</h2>
|
||
|
|
||
|
<p class="intro">
|
||
|
JaCoCo requires Java 1.5.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
The Java agent mechanism used for on-the-fly instrumentation became available
|
||
|
with Java 1.5 VMs. Coding and testing with Java 1.5 language level is more
|
||
|
efficient, less error-prone – and more fun than with older versions.
|
||
|
JaCoCo will still allow to run against Java code compiled for these.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<h2>Byte Code Manipulation</h2>
|
||
|
|
||
|
<p class="intro">
|
||
|
Instrumentation requires mechanisms to modify and generate Java byte code.
|
||
|
JaCoCo uses the ASM library for this purpose internally.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
Implementing the Java byte code specification would be an extensive and
|
||
|
error-prone task. Therefore an existing library should be used. The
|
||
|
<a href="http://asm.objectweb.org/">ASM</a> library is lightweight, easy to
|
||
|
use and very efficient in terms of memory and CPU usage. It is actively
|
||
|
maintained and includes as huge regression test suite. Its simplified BSD
|
||
|
license is approved by the Eclipse Foundation for usage with EPL products.
|
||
|
</p>
|
||
|
|
||
|
<h2>Java Class Identity</h2>
|
||
|
|
||
|
<p class="intro">
|
||
|
Each class loaded at runtime needs a unique identity to associate coverage data with.
|
||
|
JaCoCo creates such identities by a CRC64 hash code of the raw class definition.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
In multi-classloader environments the plain name of a class does not
|
||
|
unambiguously identify a class. For example OSGi allows to use different
|
||
|
versions of the same class to be loaded within the same VM. In complex
|
||
|
deployment scenarios the actual version of the test target might be different
|
||
|
from current development version. A code coverage report should guarantee that
|
||
|
the presented figures are extracted from a valid test target. A hash code of
|
||
|
the class definitions allows to differentiate between classes and versions of
|
||
|
classes. The CRC64 hash computation is simple and fast resulting in a small 64
|
||
|
bit identifier.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
The same class definition might be loaded by class loaders which will result
|
||
|
in different classes for the Java runtime system. For coverage analysis this
|
||
|
distinction should be irrelevant. Class definitions might be altered by other
|
||
|
instrumentation based technologies (e.g. AspectJ). In this case the hash code
|
||
|
will change and identity gets lost. On the other hand code coverage analysis
|
||
|
based on classes that have been somehow altered will produce unexpected
|
||
|
results. The CRC64 code might produce so called <i>collisions</i>, i.e.
|
||
|
creating the same hash code for two different classes. Although CRC64 is not
|
||
|
cryptographically strong and collision examples can be easily computed, for
|
||
|
regular class files the collision probability is very low.
|
||
|
</p>
|
||
|
|
||
|
<h2>Coverage Runtime Dependency</h2>
|
||
|
|
||
|
<p class="intro">
|
||
|
Instrumented code typically gets a dependency to a coverage runtime which is
|
||
|
responsible for collecting and storing execution data. JaCoCo uses JRE types
|
||
|
only in generated instrumentation code.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
Making a runtime library available to all instrumented classes can be a
|
||
|
painful or impossible task in frameworks that use their own class loading
|
||
|
mechanisms. Since Java 1.6 <code>java.lang.instrument.Instrumentation</code>
|
||
|
has an API to extends the bootsstrap loader. As our minimum target is Java 1.5
|
||
|
JaCoCo decouples the instrumented classes and the coverage runtime through
|
||
|
official JRE API types only. The instrumented classes communicate through the
|
||
|
<code>Object.equals(Object)</code> method with the runtime. A instrumented
|
||
|
class can retrieve its probe array instance with the following code. Note
|
||
|
that only JRE APIs are used:
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<pre class="source lang-java linenums">
|
||
|
Object access = ... // Retrieve instance
|
||
|
|
||
|
Object[] args = new Object[3];
|
||
|
args[0] = Long.valueOf(8060044182221863588); // class id
|
||
|
args[1] = "com/example/MyClass"; // class name
|
||
|
args[2] = Integer.valueOf(24); // probe count
|
||
|
|
||
|
access.equals(args);
|
||
|
|
||
|
boolean[] probes = (boolean[]) args[0];
|
||
|
</pre>
|
||
|
|
||
|
<p>
|
||
|
The most tricky part takes place in line 1 and is not shown in the snippet
|
||
|
above. The object instance providing access to the coverage runtime through
|
||
|
its <code>equals()</code> method has to be obtained. Different approaches have
|
||
|
been implemented and tested so far:
|
||
|
</p>
|
||
|
|
||
|
<ul>
|
||
|
<li><b><code>SystemPropertiesRuntime</code></b>: This approach stores the
|
||
|
object instance under a system property. This solution breaks the contract
|
||
|
that system properties must only contain <code>java.lang.String</code>
|
||
|
values and therefore causes trouble in applications that rely on this
|
||
|
definition (e.g. Ant).</li>
|
||
|
<li><b><code>LoggerRuntime</code></b>: Here we use a shared
|
||
|
<code>java.util.logging.Logger</code> and communicate through the logging
|
||
|
parameter array instead of a <code>equals()</code> method. The coverage
|
||
|
runtime registers a custom <code>Handler</code> to receive the parameter
|
||
|
array. This approach might break environments that install their own log
|
||
|
managers (e.g. Glassfish).</li>
|
||
|
<li><b><code>URLStreamHandlerRuntime</code></b>: This runtime registers a
|
||
|
<code>URLStreamHandler</code> for a "jacoco-xxxxx" protocol. Instrumented
|
||
|
classes open a connection on this protocol. The returned connection object
|
||
|
is the one that provides access to the coverage runtime through its
|
||
|
<code>equals()</code> method. However to register the protocol the runtime
|
||
|
needs to access internal members of the <code>java.net.URL</code> class.</li>
|
||
|
<li><b><code>ModifiedSystemClassRuntime</code></b>: This approach adds a
|
||
|
public static field to an existing JRE class through instrumentation. Unlike
|
||
|
the other methods above this is only possible for environments where a Java
|
||
|
agent is active.</li>
|
||
|
<li><b><code>InjectedClassRuntime</code></b>: This approach defines a new class
|
||
|
using <code>java.lang.invoke.MethodHandles.Lookup.defineClass</code>
|
||
|
introduced in Java 9.</li>
|
||
|
</ul>
|
||
|
|
||
|
<p>
|
||
|
Starting from version 0.8.3 JaCoCo Java agent implementation uses the
|
||
|
<code>InjectedClassRuntime</code> to define new class in bootstrap class
|
||
|
loader when running on JRE 9 and higher, otherwise uses
|
||
|
<code>ModifiedSystemClassRuntime</code> to add field to an existing JRE class.
|
||
|
Starting from version 0.8.0 field is added to the class
|
||
|
<code>java.lang.UnknownError</code>, versions 0.5.0 - 0.7.9 were adding field
|
||
|
to the class <code>java.util.UUID</code>, having bigger chance of conflict
|
||
|
with other agents.
|
||
|
</p>
|
||
|
|
||
|
|
||
|
<h2>Memory Usage</h2>
|
||
|
|
||
|
<p class="intro">
|
||
|
Coverage analysis for huge projects with several thousand classes or hundred
|
||
|
thousand lines of code should be possible. To allow this with reasonable
|
||
|
memory usage the coverage analysis is based on streaming patterns and
|
||
|
"depth first" traversals.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
The complete data tree of a huge coverage report is too big to fit into a
|
||
|
reasonable heap memory configuration. Therefore the coverage analysis and
|
||
|
report generation is implemented as "depth first" traversals. Which means that
|
||
|
at any point in time only the following data has to be held in working memory:
|
||
|
</p>
|
||
|
|
||
|
<ul>
|
||
|
<li>A single class which is currently processed.</li>
|
||
|
<li>The summary information of all parents of this class (package, groups).</li>
|
||
|
</ul>
|
||
|
|
||
|
<h2>Java Element Identifiers</h2>
|
||
|
|
||
|
<p class="intro">
|
||
|
The Java language and the Java VM use different String representation formats
|
||
|
for Java elements. For example while a type reference in Java reads like
|
||
|
<code>java.lang.Object</code>, the VM references the same type as
|
||
|
<code>Ljava/lang/Object;</code>. The JaCoCo API is based on VM identifiers only.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
Using VM identifiers directly does not cause any transformation overhead at
|
||
|
runtime. There are several programming languages based on the Java VM that
|
||
|
might use different notations. Specific transformations should therefore only
|
||
|
happen at the user interface level, for example during report generation.
|
||
|
</p>
|
||
|
|
||
|
<h2>Modularization of the JaCoCo implementation</h2>
|
||
|
|
||
|
<p class="intro">
|
||
|
JaCoCo is implemented in several modules providing different functionality.
|
||
|
These modules are provided as OSGi bundles with proper manifest files. But
|
||
|
there are no dependencies on OSGi itself.
|
||
|
</p>
|
||
|
|
||
|
<p>
|
||
|
Using OSGi bundles allows well defined dependencies at development time and
|
||
|
at runtime in OSGi containers. As there are no dependencies on OSGi, the
|
||
|
bundles can also be used like regular JAR files.
|
||
|
</p>
|
||
|
|
||
|
</div>
|
||
|
<div class="footer">
|
||
|
<span class="right"><a href="@jacoco.home.url@">JaCoCo</a> @qualified.bundle.version@</span>
|
||
|
<a href="license.html">Copyright</a> © @copyright.years@ Mountainminds GmbH & Co. KG and Contributors
|
||
|
</div>
|
||
|
|
||
|
</body>
|
||
|
</html>
|