# Compilation of Commit Changes within Java Source Code Repositories

Stefan Schott  Paderborn University
Paderborn, Germany
<EMAIL_ADDRESS>Wolfram Fischer  SAP Security Research
Mougins, France
<EMAIL_ADDRESS>Serena Elisa Ponta  SAP Security Research
Mougins, France
<EMAIL_ADDRESS>Jonas Klauke  Paderborn University
Paderborn, Germany
<EMAIL_ADDRESS>Eric Bodden  Paderborn University & Fraunhofer IEM
Paderborn, Germany
<EMAIL_ADDRESS>
###### Abstract

Java applications include third-party dependencies as bytecode. To keep these
applications secure, researchers have proposed tools to re-identify
dependencies that contain known vulnerabilities. Yet, to allow such re-
identification, one must obtain, for each vulnerability patch, the bytecode
fixing the respective vulnerability at first. Such patches for dependencies
are curated in databases in the form of fix-commits. But fix-commits are in
source code, and automatically compiling whole Java projects to bytecode is
notoriously hard, particularly for non-current versions of the code.

In this paper, we thus propose Jess, an approach that largely avoids this
problem by compiling solely the relevant code that was modified within a given
commit. Jess reduces the code, retaining only those parts that the committed
change references. To avoid name-resolution errors, Jess automatically infers
stubs for references to entities that are unavailable to the compiler. A
challenge is here that, to facilitate the above mentioned re-identification,
Jess must seek to produce bytecode that is almost identical to the bytecode
which one would obtain by a successful compilation of the full project.

An evaluation on 347 GitHub projects shows that Jess is able to compile, in
isolation, 72% of methods and constructors, of which 89% have bytecode equal
to the original one. Furthermore, on the _Project KB_ database of fix-commits,
in which only 8% of files modified within the commits can be compiled with the
provided build scripts, Jess is able to compile 73% of all files that these
commits modify.

###### Index Terms:

MSR, Commits, Git, Java

##  I Introduction

Java projects on average comprise 71% of code from open-source libraries [19],
many of which contain known, exploitable vulnerabilities. Under the name of
software composition analysis, commercially available tools thus try to re-
identify such known-to-be-vulnerable code when it gets included into a given
Java project. Yet, as Dann et al. showed [6], detection is commonly performed
by checking only the metadata associated with the included dependency. This
metadata is then compared against a database, which contains information about
vulnerable versions of certain dependencies. However, as Ponta et al. [31]
have shown, because metadata is often unavailable or inaccurate, these tools
frequently perform a misclassification. To improve over this, one must seek to
unambiguously re-identify the actually vulnerable code within the
application’s dependencies. Existing vulnerability databases such as Project
KB [32] already map known vulnerabilities to commits in the related source
code repositories. These existing vulnerability databases contain the exact
source code repository snapshot of the applied fix. But Java projects
typically include dependencies as bytecode, not source code. Further, as Dann
et al. [6] showed, many open-source dependencies are rebundled or repackaged,
i.e., their JAR files contain bytecode they do not declare to contain. To
unambiguously determine whether a given piece of vulnerable code does or does
not exist in a given bytecode-level dependency, one thus must be able to
reason about this vulnerability in terms of its bytecode. To this end few
approaches like BScout [5] and PPT4J [30] have been developed that try to
address this problem by trying to compare the included bytecode to the source
code of the respective patches. However, they acknowledge this source-to-
bytecode comparison to be specifically challenging, which causes imprecisions
in the detection.

Now one might think it to be easy to just compile the history version of the
project that implemented the given vulnerability’s patch to avoid the source-
to-bytecode comparison. But unfortunately it is notoriously hard to compile
Java projects obtained from source code repositories [14, 34, 35]. As
mentioned above, existing vulnerability databases only contain the repository
snapshot, but not the release version, where the patch is applied, and being
able to obtain the release version corresponding to a snapshot is highly
dependent on the employed development practices within the repository (e.g.
use of proper tags, semantic versioning, etc.). Therefore one needs to be able
to directly compile the snapshot representing the vulnerability patch.
However, compiling a historic snapshot from a source code repository is one
particularly hard problem. As Tufano et al. [35] have shown, across 100
investigated Java projects from the Apache Software Foundation, which is known
for an often outstanding maintenance of projects, only 38% of the change
history was compilable.

Yet, luckily, when the task is to re-identify known-to-be-vulnerable code, one
does not require the full code of the snapshot, but only the code of the
security-relevant methods that a given fix-commit actually patches [5, 30,
31]. To this end, we propose Jess, an approach that reduces the code to be
compiled by retaining only the code that a given fix-commit modifies. Jess
receives source code of a Java project as input and slices away all
information from the code base that is not required to compile the specific
areas of interest. If done naïvely, this can easily cause the compiler to
yield name-resolution errors. To avoid these, Jess infers the types that are
required for the compilation to succeed, yet which are not available within
the code base, and generates type stubs that correspond to these inferred
types. A challenge is here that, to facilitate the re-identification of
vulnerabilities in the resulting bytecode, Jess must seek to produce bytecode
that is not just similar but actually identical to that which one would obtain
also by a successful compilation of the full project. Using Jess one can
easily obtain the bytecode corresponding to a vulnerability patch, which can
subsequently be used by software composition analysis approaches without the
need to employ a challenging source-to-bytecode comparison.

We evaluated Jess on 347 of the most popular Java projects on GitHub. Our
evaluation showed that of the 32,970 methods and constructors that we had
randomly sampled from the projects, Jess was able to compile more than 72% in
isolation. The evaluation further showed that the bytecode obtained via Jess
is highly similar to the original bytecode. It only differs by 0.66% on
average, and in 89% of the cases it even equals the bytecode obtained by full
compilation. Further, our evaluation on a real-world dataset of fix-commits
[32], in which only 8% of files modified in the commits can be compiled with
the provided build scripts, shows that Jess is nonetheless even here able to
compile 73% of all files that these commits modify.

To summarize, the original contributions of this paper are:

  * •

An approach to compiling areas of Java code, modified within a commit, based
on slicing, type inference and stubbing, with the goal to produce bytecode
identical to that of the original compilation.

  * •

An implementation in an open-source
tool111https://github.com/stschott/jess222https://doi.org/10.5281/zenodo.12804723.

  * •

A large-scale evaluation on 347 of the most popular Java projects on GitHub
and the Project KB [32] fix-commit dataset.

##  II Compiling Commit Changes with Jess

Figure 1: Overview of Jess

Figure 1 shows our approach behind compiling commit changes with Jess. The
goal is to obtain bytecode identical or at least very close to the one that
would be generated by invoking the original build scripts with every single
dependency fully resolved. Jess takes as input the signatures of the methods
to be compiled (referred to as target in the following), the Java source code
files containing them, all other Java source code files available in the
hosting code base and all dependencies that are still retrievable in form of
JAR-files. For the sake of brevity we describe the approach using a single
method as target, however Jess supports handling of single or multiple members
(e.g., methods, fields, constructors, and initializers) of classes and inner
classes.

Jess follows a mark-and-sweep approach [22] for its processing. Initially,
Jess parses all available files and marks the target with the annotation Keep-
All to denote that it must not be altered (e.g., the body of methods must not
be sliced or modified).

In the resolution step, Jess identifies the parts of the code base that are
relevant for compiling the target. It inspects all references in the target,
e.g., field accesses, method invocations, or object creations, and tries to
find the corresponding declaration. Such references may be part of the target
file itself, a different file within the code base, or external files.
Referenced members are marked with the annotation Keep to denote that only the
signature of the member should be kept. All newly marked members are
recursively resolved until no further references are found or the found
references are not part of the available code base. While references in the
signatures are always resolved, those in the body of methods and constructors
only need to be resolved when annotated as Keep-All. For fields, the type
definition is always resolved, whereas the field initializer is resolved only
in case of Keep-All.

In the slicing step, Jess removes all class members that are not needed for
the compilation of the target: class members that have not been marked, bodies
of methods and constructors marked as Keep only, and the initializers of
fields marked as Keep only. Additionally, Jess removes classes and inner
classes without any marked member, now unused import declarations, and JavaDoc
as it is not reflected within the bytecode. Exceptions that need to be
considered while slicing are described in Section IV. Slicing the body of
methods invoked from the target method, while keeping its signature, allows to
minimize the amount of transitively referenced members, while obtaining
compilable code. After slicing, Jess outputs the sliced target file, which
contains the target method and the class members it references, and a set of
sliced support files, which contain class members that are either referenced
directly or transitively from the target method.

Due to slicing away code unrelated to the target methods compilation, it may
enable a successful compilation of the target method, even when the snapshot
contains broken source code (e.g. due to syntax errors), as long as the the
broken code is not contained within the target method itself. After slicing,
the compilation may still fail due to references to classes outside of the
available code base.

Finally, the stub generation step infers the signature of unresolved
references based on the symbol’s usage context and synthesizes a type stub,
i.e., definitions of classes and class members with full signatures, yet no
content. Similar to interfaces, which describe the structure of a class
without describing the specific implementations, Jess generates classes that
contain the full member signatures without any actual functional
implementation. The stub generation is described in detail in Section V. Note
that after applying the slicing and stub generation steps, even though it
might affect the resulting bytecode of other classes and methods that are not
declared as target, Jess does not perform another backward impact analysis.
Current software composition analysis approaches perform a syntactic matching
of only the fix code [5, 30, 31]. Thus, Jess only aims at producing (equal)
bytecode for the member declared as target, while the dependent files are only
used to enable a compilation.

Finally, a Java compiler can then compile the target method with the sliced
target file, sliced support files, generated stub files and available JAR-
files.

##  III Jess Compilation Example

⬇

1import org.example.X;

2

3public class A {

4 @Keep // 2

5 int num = D.getNumber();

6

7 int otherNum = 12;

8

9 @KeepAll // 1

10 public int visit() {

11 B b = new B();

12 b.inc(num);

13

14 X x = new X();

15 String s = "five";

16 int val = x.getVal(s);

17 boolean con = X.conv;

18

19 return val;

20 }

21}

(a) Target file

⬇

1public class B {

2 @Keep // 2

3 public C inc(int a){...}

4 public E dec(int a){...}

5

6 @Keep // 3

7 class C {...}

8}

(b) Support file

⬇

1// Generated Stub

2package org.example;

3

4public class X {

5 static boolean conv = true;

6

7 public int getVal

8 (String arg) {return 0;}

9}

(c) Generated stub file

Listing 2: Jess compilation example

Listings 2, 2 and 2 illustrate an example of how Jess performs the processing
for compiling commit changes. Listing 2 shows the target file with the visit
method being the target method (i.e. modified within the commit). Only the
classes A and B (cf. Listings 2, 2) are assumed to be within the available
code base. After parsing, in the first iteration of the resolution step, Jess
marks the target method as Keep-All (e.g. method changed in the commit) to
state that the method body should be kept intact. The numbers next to the
markings within the listings indicate the resolution iteration in which the
corresponding member has been marked. In the next iteration, Jess resolves all
references within the visit method. In line 11 (Listing 2), a new object of
class B is created and in line 12, the inc(int a) method of class B is
invoked. Due to this invocation, Jess marks the method declaration (line 3 in
Listing 2) as Keep, which indicates that the method signature needs to be kept
intact, while the body can be sliced away. This method invocation receives the
field num as argument and therefore Jess marks the field declaration in line 5
of Listing 2 also with a Keep-Marking. The remaining references within the
target method’s body or signature all reference a class X, which is assumed to
not be part of the code base. Now Jess looks at all members that have been
marked in the previous step. As all added markings are Keep-Markings, only the
member signatures are of interest, since their content will be sliced away in
the next step. Thus, the only additional reference is in line 3 of Listing 2
where the inc method specifies a return type of C. Because of this, a Keep-
Marking is added to the inner class C (cf. line 6 of Listing 2). After all
relevant references for the compilation of the target method have been
considered, Jess removes all non-marked members within the classes and the
contents of the members marked with a Keep-Marking. In class A Jess will
remove the declaration of the field otherNum and the initialization of the
field num. In class B Jess will remove the declarations of the method dec, as
well as the body of method inc (and add a corresponding dummy return
statement, which will be explained in detail in Section IV). Finally, in inner
class C, Jess will remove all class members. In doing so, Jess eliminates
references to the classes D and E, which might be originating from an external
library that might not be available any longer and would cause a normal
compilation of the full snapshot to fail.

After slicing, not all references to classes outside of the available code
base could be removed. As the target method contains multiple references to
members of the unavailable class X, Jess now generates a stub file, which can
be seen in Listing 2. When Jess processes the creation of the non resolvable
object (line 14 in Listing 2), it tries to infer all necessary information to
generate an appropriate stub file from its usage context. In this case, since
no argument is given in the object creation, Jess does not need to generate
any constructor. To generate the correct bytecode, however, the simple name of
the referenced class is insufficient—the fully qualified name (FQN) is
required instead. To determine the FQN, Jess considers the import declaration
within the target file (line 1 in Listing 2) and sets the package definition
of the generated stub file to the determined package path (org.example in this
example). The next unresolvable reference can be seen in line 16 of Listing 2,
where the method getVal is invoked. Whenever Jess processes a method
invocation, it needs to determine four properties:

  1. 1.

the object or class that the method is being invoked on,

  2. 2.

the return type of the method,

  3. 3.

the parameter types of the method,

  4. 4.

whether the method is static or non-static.

In the usage context within Listing 2, it is possible to determine all four
properties. The method is invoked on an object with the name x, which can be
traced back to the object instantiation in line 14. The return value of the
method is assigned to a variable of type int. As its only argument it receives
the variable s, which can be traced back to its definition site in line 15,
where it is defined as type String. Since the method is invoked on the
previously instantiated object x, it is likely that the method is non-static.
Due to this information, Jess generates the corresponding method stub in lines
7–8 in Listing 2. Due to syntactic ambiguities (which will be described in
detail in Section V-A) it is not possible to infer with certainty the definite
return and parameter types and whether the method is static or non-static.
However, the generated stub will still enable a successful compilation.
Finally, the reference within the target method can be observed in line 17 in
Listing 2. Here a field of class X is accessed and assigned to the boolean
variable con. Due to the usage context, Jess is able to generate the field
stub in line 5 of Listing 2, by applying the same inference procedure as for
the previous method invocation. When the sliced target file, sliced support
files, and the generated stub file are handed to a Java compiler, the target
method can be successfully compiled.

##  IV Slicing Exceptions

In the following we describe some exceptions to be considered during the
slicing step.

Abstract interface / superclass methods: Whenever a class implements an
interface or extends an abstract class, every abstract method definition needs
to be implemented in concrete subtypes, otherwise the compilation will fail.
Because of this, Jess can only slice away methods within classes that are
implementing an abstract method if it also slices away the corresponding
abstract method definition from within the interface or superclass. However,
if the interface or superclass is originating from an already-compiled class
file or the JDK, e.g. the Collection interface, Jess cannot slice away any
such method definitions. In such cases, Jess only slices away the body of such
methods.

Abstract functional interface methods: A functional interface in Java is an
interface that defines just one abstract method [27]. Whenever this is the
case, a lambda or method reference expression can be used to create instances
of the corresponding interface. In such cases, although the abstract method is
typically not referenced explicitly, its definition still needs to be kept
such that its defining interface is considered as functional by the Java
compiler. Thus, Jess does not slice away the abstract method from within
referenced functional interfaces, even when the method is not referenced
explicitly. If the interface is not referenced at all, Jess will slice away
the whole interface.

There are also exceptions that apply to slicing the bodies and initializers of
methods, constructors and fields:

Method return types: Whenever a method signature specifies a return type that
is not void, the corresponding method body needs to contain a return
statement, which returns a value that adheres to the specified type. When this
is the case, Jess removes the whole method body and replaces it with a single
return statement that returns a dummy-value (e.g. return 0 or return
‘‘dummy’’). If the specified return type is a reference type (except for
String types), Jess inserts a return null statement.

Super constructor invocation: Whenever a class extends another class, the Java
compiler requires the extending class to (transitively) call a constructor of
the super class (via the super statement) [29]. If the super class contains a
default constructor (which does not require parameters) this call is
automatically generated by the Java compiler. However, when the super class
only provides constructors that expect arguments, a super constructor needs to
be called explicitly from within a constructor belonging to the extending
class. Thus, Jess keeps such super constructor invocations intact and only
replaces their arguments with dummy-values.

Final fields: Whenever a field is declared as final, it needs to be assigned a
value during instance initialization. Thus slicing away the initialization of
a final field would result in the Java compiler not being able to compile the
respective class. Furthermore, final fields, which are initialized with a
literal or an arithmetic expression (e.g. final int x = y + 1), are treated as
constants by the Java compiler, with the constant being propagated directly to
the method referencing it. Therefore, it is not sufficient to replace the
initialization with a dummy value, as this will modify the resulting bytecode.
Due to this, if the field is initialized with a literal or arithmetic
expression, Jess keeps the original initialization intact.

Type-import-on-demand: Whenever there is a type-import-on-demand declaration
(import with asterisk), Jess keeps the import declaration as it might contain
important information for the stub generation step. After stub generation, to
avoid a compilation fail due to the package path not existing, Jess generates
the directory structures described by these import declarations.

##  V Stub Generation

In the following we describe the generation of type stubs, characterized by a
full signature and no content.

For generating fitting type stubs, Jess applies a greedy algorithm. First, it
scans the code parts that remain after slicing for references that could not
be resolved within the available code base. For each unresolvable reference,
based on the usage context, Jess then applies a set of inference techniques to
determine corresponding types. The determined types are required to create
type stubs, which will not only satisfy the Java compiler, but result in
bytecode that is as close as possible to the original one. Finally, once all
unresolvable references have been considered and the appropriate type
information has been inferred, Jess generates the respective stub files
containing the type stubs. Jess applies the following techniques to infer
appropriate stubs for references which are not resolvable within the available
code base:

Literal inference: The easiest case for Jess to infer the needed types for,
e.g., a method signature, is if the method has been invoked with a literal.
The Java language specification clearly describes the types of literals [26].
For a method invocation, e.g., foo("abc"), which is invoked with the literal
"abc", the Java specification defines the type of the literal to be String.

Definition inference: During definition inference, Jess tries to locate the
definition-site of a variable and infers its type from there. This definition-
site can either be a variable, field or parameter definition. Here it is
important to consider the scopes of variables. At first Jess checks the block
in which the variable has been used and then incrementally checks outer
blocks, where the scope still applies to the variable usage, until it reaches
the method definition. At this point Jess checks the method parameters. If the
definition-site still has not been found, Jess incrementally increases its
scanning towards field definitions within the same class, potential outer
classes and extended superclasses.

Conditional inference: Whenever an expression is used in a statement that
requires a condition, e.g. if-, while- or do-statement, it has to resolve to a
boolean type [25]. This means that if e.g. a method’s return type is used as
the condition of an if-statement, its return type has to be boolean or
Boolean.

Return statement inference: When an expression is used in a return statement,
the type of the expression has to be the same (or more specific) as the return
type defined in the method signature.

Static / Non-static inference: To check if a method or field declaration is
static, Jess checks if it finds a definition-site for the object/class the
method or field is accessed on. If it finds a definition-site, the
field/method is declared as non-static, otherwise as static.

FQN inference: To infer the fully qualified name (FQN) of a class, Jess checks
all direct import statements within a file to find one where the last part of
the import matches the used class. Sometimes classes are imported via asterisk
imports. If none of the direct import statements matched and there is only a
single asterisk import statement in the file, Jess infers the FQN from the
asterisk import.

Superclass / Interface inference: There are some scenarios in which the
compilation will fail if a certain class is not extended or a certain
interface is not implemented.

  * •

Exceptions: When objects are used in throw-statements or caught in try-catch
blocks, the Java compiler requires the corresponding classes to extend the
respective Throwable class. If Jess observes such usages, it adds the
corresponding class extension to the generated stubs.

  * •

Iterable: If an object is used in a for-each loop, the Java compiler requires
the corresponding class to implement the Iterable interface. Jess detects such
occurrences and adds the appropriate Iterable interface implementation.

  * •

AutoCloseable: When an object is used as resource within a try-with-resources
statement, the Java compiler requires the corresponding class to implement the
AutoCloseable interface. Jess adds such an interface implementation to the
generated stub class if needed.

  * •

Typecasts: Whenever an explicit typecast is performed on an object, its
corresponding class must inherit or implement the type defined within the
typecast. Jess either adds the corresponding interface implementation or the
respective extend class statement to the generated class.

Generics inference: Sometimes, when objects are created, generic type
arguments are supplied to the created object. However, in order to be able to
pass generic type arguments, the respective class needs to specify
corresponding generic type parameters. When Jess observes such occurrences, it
generates the stub class in a way, that allows for supplying generic type
arguments to it.

###  V-A Type Inference Ambiguities

A precise inference of the corresponding signature is not always possible, due
to ambiguities that arise if some information is missing. Dagenais and Hendren
[4] already described some of the ambiguities that arise in the syntax of
partial Java programs. They already describe that precisely inferring the FQN
is not possible, when there is no direct import of a class, but multiple
asterisk imports are present within the target file. Furthermore, they
describe that e.g. it is not always possible to distinguish package names from
class names (in case of inner classes). However, the list presented is not
exhaustive as we found other ambiguous syntax constructs:

Method invocations without return value usage: When a method is invoked, but
its return value is not used, it is unclear what return type has been
originally specified in the method declaration. While it is likely that the
method does not return any value (i.e. void return type), it is still possible
that a return type is specified, but the return value is simply dropped after
its invocation. Listing 3 shows an example of such an occurrence. The return
value of the find method, which is invoked in line 4, is not used. However,
the possible method stubs in line 10 and line 12 are both valid, even though
one returns a value of type String, while the other does not. In each scenario
the Java compiler generates different bytecode.

Method invocations with literal arguments: When a method is invoked with a
literal as argument it is often not possible to unambiguously infer the
specified parameter type within the method declaration. If a method is invoked
with the integer literal 5 as argument, the original method declaration might
e.g. specify int, long, float or double as parameter type. If a method is
invoked with a null literal, it can only be inferred that the specified type
is not primitive. One example of this can be again observed in Listing 3, as
the invoked find method can either specify int or long as parameter type and
still be valid.

Static vs. non-static: As static methods or fields can either be accessed on a
class itself or object instances of a class, it is not possible to precisely
distinguish static class members. While it is likely that members accessed on
an object instance are non-static, there is no guarantee. An example of this
can be seen in Listing 3. It is unclear whether the in line 4 invoked find
method is declared as static or non-static, since both possibilities are
valid.

Generic class methods: In some scenarios, when a method belonging to a generic
class is invoked, it is unclear whether the method specifies its parameters
explicitly or as a generic type. Listing 3 illustrates such an example. In
line 3 an object of type W is created with String supplied as the generic type
argument. Then in line 4 the method add is invoked on the generic object, with
a string literal as argument. Now, if we look at the stub, it is possible that
the method add specifies a generic type parameter (see line 10) or explicitly
specifies String as its parameter type (see lines 12 & 13). Each scenario
results in different bytecode being generated.

Interface vs. Class: Without having additional context, it is not always
possible to distinguish whether a specified type is originating from a class
or an interface. When a method is called on an object of such a type, in the
bytecode it is either invoked via an invokevirtual instruction, in case of
originating from a class, or via an invokeinterface instruction, in case of
originating from an interface.

No explicit return type usage: It may be the case that e.g. a method outside
of the available code base is invoked and its return value is assigned to a
variable that is declared using the var keyword, which does not explicitly
assign a type to the variable. In this case is is not possible to precisely
determine the return type of the respective method. The same applies to nested
method invocations where two methods are outside of the available code base
and the return value of one method is directly used as argument for the other
method, without being assigned to a variable beforehand (e.g. foo(bar())). In
this case it is not possible to precisely infer the return type of one method
and the specific parameter type of the other method.

⬇

1public class G {

2 void process() {

3 V v = new V();

4 v.find(5);

5 }

6}

7

8// Stub

9public class V {

10 public void find(int i) {}

11 // or

12 public static String find

13 (long i) {return "abc";}

14}

(a) Ambiguous example 1

⬇

1public class H {

2 void verify() {

3 W<String> w = new W<>();

4 w.add("value");

5 }

6}

7

8// Stub

9public class W<T> {

10 public void add(T t) {}

11 // or

12 public void add

13 (String s) {}

14}

(b) Ambiguous example 2

Listing 3: Example of ambiguous syntax constructs

Due to the above mentioned syntactic ambiguities there are limits towards how
similar the bytecode obtained via Jess can be to the original bytecode. This
limit highly depends on how much of the code base is actually available and
how much has to be supplemented via stub generation. Jess was designed to
generate bytecode that is identical or at least very close to the original
one. However, since this is not always possible, Jess offers the option to
mark the locations where ambiguities may have caused imprecision. In case of
an unknown FQN, Jess sets the package of the generated stub to a special
unknown package. By doing so, the unknown package within the bytecode can be
treated as a wildcard in a potential comparison and therefore still be
compared against the original bytecode. Whenever a type (e.g. return or
parameter type of a method) cannot be precisely inferred, Jess sets the type
within the generated stub to a special Unknown type. Whenever an ambiguity is
detected, Jess can optionally perform this unknown type assignment.

##  VI Evaluation

In the following we evaluate the effectiveness of Jess’s compilation. To do
so, we implemented our approach in a tool. We answer the following research
questions.

  1. RQ1:

How does Jess’s compilation perform on popular and current Java projects?

  2. RQ2:

How similar is the bytecode obtained via Jess to the original bytecode?

  3. RQ3:

To which degree can Jess enable the compilation of commit changes?

The first two research questions focus on evaluating the performance of Jess
in general. They aim at showing how effectively it can be used on partial code
bases and how similar the generated bytecode is to the one obtained by a
successful compilation of the full project. The third research question
focuses on the use case of commit compilation, to evaluate its effectiveness
to compile code changed in commits fixing known vulnerabilities.

###  VI-A RQ1: How does Jess’s compilation perform on popular and current Java
projects?

In this research question we aim at investigating how Jess performs on the
latest state of Java projects hosted in source code repositories. Figure 4
illustrates our experimental setup for answering RQ1 and RQ2. Based on stars,
we selected the 1,000 most popular Java projects on GitHub. From these 1,000
projects we selected the Maven projects [9], which resulted in 347 total
projects considered in our evaluation. We cloned each of the repositories at
their most current state and, for each repository, we randomly sampled methods
as individual target for compilation via Jess. To have a diverse but
manageable dataset, we randomly selected 100 distinct methods or constructors
from each repository. We use “methods” to refer to both, methods and
constructors, from here on. To omit trivial methods like getters and setters,
we only considered methods containing at least three lines of code in their
body. The code base contained within the cloned repository and the signatures
of the sampled target methods are input to Jess. On the same set of target
methods, we performed four different compilation procedures. To establish a
baseline, we directly provided the target file to the Java compiler for
compilation, without processing it with Jess first. We then used Jess to
perform three different compilation experiments. In the first experiment, we
only used the Java source code within the cloned repository as available code
base and solely performed slicing without generating any stub files. In the
second experiment, we used the same code base, but complemented the slicing
with stub generation. In the final experiment, we added a simple Maven plugin,
which (transitively) downloads the available dependencies configured within
the project’s pom.xml files at the version configured within the project. Note
that the custom plugin does not perform any dependency conflict resolution nor
does it guarantee that all configured dependencies are available. We use this
custom plugin as we cannot use the default Maven Dependency Plugin [10] for
our experiment, as it employs a fail early strategy and stops the dependency
resolution process as soon as a single artifact resolution fails. Then, before
running Jess, the downloaded JAR files are added to the available code base.
In the next step, the target source code generated by Jess (i.e., sliced
target file, sliced support files, and optional stub files) is handed to the
Java compiler for compilation. To evaluate the similarity of the target
bytecode obtained via Jess to the original one (cf. RQ2 in Section VI-B), we
invoke the default mvn install command on each of the cloned projects to
compile it via the provided build scripts wherever possible. We then compare
the target bytecode to the original bytecode. As previously reported (see
Section I), automatic compilation via invoking the provided build scripts
often results in failure, even though the selected projects are at their most
current state. Thus, we are only able to perform a comparison for the projects
where the default build scripts yielded a successful compilation, which are
134 of 347 projects. We report on the results of the bytecode comparison in
Section VI-B. Our experiments were executed inside a Docker container running
Alpine Linux with Eclipse Temurin JDK 17 and Maven 3.9.3. The container was
executed on a Debian 10 machine, configured to use four cores of an Intel Xeon
E5-2695 v3 (2.30 GHz) CPU and 32GB of main memory.

Fig. 4: Overview of our evaluation setup for RQ1 and RQ2

Table I shows the results of our compilation experiments (RQ1). In total we
sampled 32,970 distinct methods originating from 21,856 different files. The
number of methods listed in Table I is slightly lower than 100 per project,
since a few small projects did not contain enough methods with at least three
lines of code. The first row of the table shows our baseline direct
compilation. When providing the file containing the target method directly to
the Java compiler the success rate was very low at 6.79%, since most files
contain references to other files. The compilation success rate increases to
38.43% by slicing the file via Jess first. In fact, references to files
outside of the available code base, but not required for compilation, are
removed from the respective target and support files. This result shows that a
considerable amount of methods within a project can be compiled solely relying
on the source code within its repository, without the need to resolve external
dependencies at all. However, if the target method refereces an external
dependency, which is not available within the provided code base, compilation
fails without stub generation. Applying stub generation after slicing, the
success rate of Jess significantly increases to 62.30%. In our final
experiment we run our custom dependency download plugin beforehand, to
increase the available code base as much as possible. By increasing the code
base available to Jess through the downloaded JAR files, the compilation
success rate increases to 72.02%, indicating that Jess allows for a targeted
compilation of most methods within the investigated projects.

We investigated the cases in which Jess failed (even when using the dependency
plugin) and found the main reasons to be: no longer available dependencies,
unresolved dependency conflicts, and limitations to the implementation of Jess
itself. Our implementation of Jess relies on the symbol-solving capabilities
of the popular Javaparser [18] library, thus it inherits Javaparser’s
limitations, e.g., lack of support for all up-to-date Java features and
problems in resolving language constructs like method references (::
operator), lambda expressions, and variable arguments (Varargs). These
limitations to Javaparser especially apply in partial code bases.

We additionally measured the time that Jess and the subsequent compilation of
the target method require (see Table I). As expected, since no resolution,
slicing, or stub generation needs to be performed, a direct compilation of the
target file is very fast. However, the success rate of a direct compilation is
low. Only applying slicing, the average processing time of Jess (including
compilation) takes 400ms. As expected the required time slightly increases to
464ms when Jess additionally applies stub generation. Furthermore, as the code
base that needs to be considered for resolution increases when using the
dependency plugin, the required processing and compilation time also increases
to 831ms (not including dependency download time). These results show that
even when a compilation of the full project is possible, compilation via Jess
may be preferable if the area of interest within the code is limited due to
its fast compilation times.

Jess was able to compile up to 72% of randomly sampled methods from popular
Java projects, in isolation, only taking 831.3ms per method on average.

| Methods | Compilable |  | Avg. Time
---
(ms)
Direct Compilation | 32,970 | 2,238 (6.79%) | 125.7
Jess (slicing) | 32,970 | 12,670 (38.43%) | 400.9
Jess (+ stub gen.) | 32,970 | 20,539 (62.30%) | 464.5
Jess (+ dep. plugin) | 32,970 | 23,744 (72.02%) | 831.3
TABLE I: Compilation success rates using Jess

###  VI-B RQ2: How similar is the bytecode obtained via Jess to the original
bytecode?

In this research question we investigate the similarity of the bytecode
obtained via Jess to the original bytecode, when the source code is compiled
the intended way via provided build scripts. For this investigation we used
the same experimental setup as previously described in Section VI-A. Wherever
we were able to successfully compile a target method via Jess and by invoking
the provided Maven build scripts, we compared the bytecode of the target
method originating from Jess and from the default build. We used ASM 9.5 [3]
to extract the textual representations of the bytecode corresponding to the
respective target methods (based on the signature) and performed a textual
head-to-head comparison where the method’s bytecodes are considered different
as soon as there is a single textual difference between the compared
bytecodes. Note that, since bytecode is a binary format, the extracted textual
representation depends on the ASM framework’s interpretation, which is based
on the Oracle JVM specification [28]. To see how similar the resulting
bytecodes are, if they are not equal, we computed the normalized Levenshtein
Distance (NLD) [36] on their textual representations. The NLD is a commonly
used measure that can be used to calculate the similarity of two text
sequences. The Levenshtein Distance represents the number of characters that
need to be inserted, deleted or substituted to transform one text sequence
into the other. The normalized Levenshtein Distance in addition also considers
the lengths of the text sequences to calculate a percentage value of
similarity. An NLD of 100% indicates that every single character needs to be
changed, while a value of 0% indicates that both sequences are identical. The
lower the NLD is, the more similar are the compared text sequences.

Table II shows the results of our comparison. The first row of the table shows
the comparison results when only slicing is applied by Jess. The number of
compared methods is smaller than investigated in Section VI-A, as we were only
able to compile 134 of the 347 projects using their provided build scripts.
Out of the compared 7,722 methods, 92.75% yielded the exact same bytecode as
obtained through the intended build. Furthermore, the compared methods were
highly similar to each other, as the average NLD is 0.29%, indicating a very
high degree of similarity between the respective method’s bytecodes. The
second row of Table II shows the results when stub generation is applied in
addition. As expected, when stubs need to be generated to enable a successful
compilation, the degree of similarity decreases through the ambiguities that
arise. Out of the 11,519 compared methods, 76.17% of the methods yielded
identical bytecode. The average NLD increased to 1.85%. Though higher than in
the case of pure slicing, the bytecode obtained via Jess is still very similar
to the original bytecode, while increasing the number of compilable methods by
more than 62%, indicating the added value of stub generation. Finally, the
last row of the table shows the comparison results when the code base
available to Jess is augmented with resolvable external dependencies. Out of
the 13,630333One source code method may expand into multiple bytecode methods
after compilation, e.g. due to anonymous classes compared methods, 12,192
(89.45%) yielded the exact same bytecode. Furthermore, the average NLD
decreased to 0.66%. As expected, due to the availability of external JAR
files, the similarity increases compared to the experiment with slicing + stub
generation as certain references become resolvable and thus fewer stubs are
required for compilation.

We investigated the methods that differ from their original bytecode after
purely applying slicing via Jess. While it is clear that, due to ambiguities,
the bytecode obtained through compilation supplemented with stubs may contain
differences, it is not obvious when only slicing is applied. Our investigation
revealed the main causes of the remaining differences to be:

  * •

Properties that are reflected into the bytecode when compiling using Maven
[11].

  * •

Bytecode that is automatically generated by frameworks (e.g. non-null checks),
which are configured in the provided build scripts and invoked by Maven, e.g.
Project Lombok [2] or Checker Framework [1].

  * •

Anonymous classes and synthetic methods generated by the Java compiler are
named with an incrementing numeric suffix (e.g. id$0). This numeric suffix is
based on the location within the source code file. Since Jess’s slicing
removes some of these constructs if not required for compilation, the naming
of these constructs may differ. However, this usually only results in a single
digit that differs from the original bytecode, making the bytecode still
highly similar to the original one.

Depending on the extent of the available code base, the bytecode obtained via
Jess on average only differs by 0.29–1.85% from the original bytecode with
76–93% of all compared methods even yielding identical bytecode.

| Methods | Equal | Avg. NLD
---|---|---|---
Jess (slicing) | 7,722 | 7,162 (92.75%) | 0.29%
Jess (+ stub gen.) | 11,519 | 8,774 (76.17%) | 1.85%
Jess (+ dep. plugin) | 13,630 | 12,192 (89.45%) | 0.66%
TABLE II: Similarity of the bytecode obtained via Jess to the original
bytecode

###  VI-C RQ3: To which degree can Jess enable the compilation of commit
changes?

Fig. 5: Overview of our evaluation setup for RQ3

In this research question we evaluate the primary use case Jess has been
designed for: to compile commit changes within repository snapshots. Figure 5
shows an overview of our experimental setup. We considered SAP’s Project KB
[32] as dataset for our evaluation. Project KB is a manually curated database,
which contains vulnerabilities affecting industry-relevant open source
projects from the years 2005–2023. In total, it contains 1,297 vulnerability
statements, which map a Common Vulnerability Enumeration (CVE) [24] to the
commits within a project’s source code repository that fix the corresponding
vulnerability. Such commits are referred to as fix-commits. As described
previously, Project KB only contains the snapshot version with the patch
applied, but not the corresponding release version. In this experiment we thus
assess Jess’s performance on directly compiling these snapshots. Each commit
in Project KB changes classes and methods that are related for some security
vulnerability, making all of them compilation targets for vulnerability
detection tasks. In our evaluation, we only consider statements that affect
Java projects and contain corresponding fix-commits, which leaves us with 693
unique vulnerability statements. Each vulnerability statement consists of one
or more vulnerability fixes, each containing a set of fix-commits that fix a
certain CVE. In our case, the 693 considered statements consist of 920
vulnerability fixes. We cloned the source code repository belonging to each
vulnerability fix, checked out the most recent commit out of the set of fix-
commits, which we continue to refer to as snapshot, and applied one of two
subsequent processing steps to the snapshot. At first we are interested in
establishing a baseline of how many of these snapshots are able to be compiled
by using the build scripts provided within each project. We used the same
procedure as Hassan et al. [14] to automatically locate the appropriate build
scripts and invoke them in a systematic way. We considered the three popular
Java build tools with the respective build commands that provided the highest
chance of success as reported by Hassan et al.: Maven [9] (mvn compile),
Gradle [17] (./gradlew and gradle build) and Ant [8] (ant build). Moreover,
since Ant does not define a default build command like Maven and Gradle do, we
added the command ant compile to our evaluation after manually inspecting the
respective build scripts to boost the compilation success rate. In total 907
of the 920 repositories within our dataset contain build scripts and are thus
theoretically compilable via invoking build scripts. Further, we provided the
same repository snapshots to Jess. Additionally, we performed a commit diff
extraction in which we extracted the signatures of classes, methods,
constructors and fields that have been modified or added within the respective
set of fix-commits. We then separately handed each file that has been modified
within the fix-commits to Jess as target file. Furthermore, we specified all
modified class members, as determined by the commit diff extraction, as target
members. Like in our previous experiments, we executed Jess with three
different configurations (see Section VI-A) and provided the repository as
available code base. The outcome of invoking a build script is typically
binary, either the compilation is successful and one obtains the bytecode of
all files within the snapshot or the compilation is not successful and one
obtains no bytecode at all. However, when compiling commit changes via Jess
the outcome is more fine-grained. When all files that have been modified
within the set of fix-commits were successfully compiled, we consider it as a
full success. If no file could be successfully compiled we consider it as no
success. However, in case Jess was able to compile a subset of the targets, we
consider it a partial success.

Table III shows the results when invoking the provided build scripts for
compilation. The results are split up for each individual build tool and show
how many snapshots could successfully be compiled by invoking the provided
build scripts and how many of the files, modified in the fix-commits, could
thus be compiled. One can immediately see that the rate of successful
compilations is rather low, with projects using Ant as build tool having by
far the highest success rate and project’s using Gradle having the lowest. An
investigation of the success rate for Ant projects revealed that almost all of
the successful compilations (27) can be attributed to snapshots of the Apache
Tomcat [12] repository, which is exceptionally well maintained. In total,
fewer than 15% of the snapshots could be compiled using the provided build
scripts, only yielding the bytecode of 8.8% of files changed in the commits,
which is a noticeably lower success rate than reported in other studies [34,
14, 35]. However, Project KB contains entries dating back to 2005, which means
that many of the fix-commits are old. According to Sulír and Porubän [34] the
age of the project heavily influences the probability of successful
compilation, which could explain the low success rates.

Build Tool | Compilation Succ. | Compiled Files
---|---|---
Ant | 29 / 84 (34.5%) | 55 / 239 (23.0%)
Maven | 94 / 657 (14.3%) | 174 / 2,133 (8.2%)
Gradle | 12 / 166 (7.2%) | 27 / 542 (5.0%)
| 135 / 907 (14.9%) | 256 / 2,914 (8.8%)
TABLE III: Compilation success rates when invoking build scripts provided by
the projects

Table IV shows the compilation results using Jess. Again, each row represents
the different configurations used for Jess. One can immediately see that Jess
performs significantly better than the provided build scripts. Even when only
slicing is applied, Jess was able to fully compile all commit changes within
23.6% of snapshots. For 53% of snapshots it was able to compile it at minimum
partially, resulting in at least some successfully compiled files. When
comparing the amount of files Jess was able to compile to the number of files
compilable via provided build scripts there is an even higher difference. This
difference only increase further when Jess additionally applies stub
generation or uses the dependency plugin to download available JAR files. Note
that the dependency plugin only works for Maven projects, so for the Ant and
Gradle projects no dependencies are downloaded at all. When using Jess with
slicing, stub generation and the custom dependency plugin, it is able to fully
compile all commit changes within 53% of the snapshots, while being at least
able to compile parts of 80.4% of all snapshots. A partial compilation of the
fix would allow for a subsequent matching for at least part of the patch. In
total Jess is able to compile 73.6% of all files modified within the fix-
commits for Java projects listed within Project KB. Note that due to only
being able to obtain the bytecode of at most 174 files modified within fix-
commits (less than 6% of files), using Jess and the provided build scripts, we
did not perform another bytecode comparison experiment. The results from the
previous experiment (see Section VI-B) should also apply here.

In theory, the success rate of Jess can be increased even further. As long as
all constraints imposed by the compiler are satisfied, the compilation will
succeed, which can be achieved by e.g. altering the source code designated for
compilation. However, as constraints are not unambiguous in partial code bases
(see Section V-A), this always comes with a trade-off between success rate and
degree of similarity of the resulting bytecode to the original one. As Jess
was primarily designed for producing bytecode that is suited for a subsequent
comparison, we privilege bytecode similarity over success rate. Jess is able
to compile 73.6% of fix-commits contained in Project KB, with the resulting
bytecode having a high degree of similarity to the original one (see Section
VI-B).

Relying on the provided build scripts only allows for a compilation of 8.8% of
files affected by fix-commits within Project KB. Using Jess we were able to
obtain the bytecode of the fixes within 73.6% of all files. For 80.4% of
vulnerability fixes Jess was able to compile at least parts of the fix,
allowing for a subsequent matching of the patch.

| Full Succ. | Partial Succ. | Compiled Files
---|---|---|---
Slicing | 214 / 907 (23.6%) | 481 / 907 (53.0%) | 1,258 / 2,914 (43.2%)
\+ SG | 412 / 907 (45.4%) | 705 / 907 (77.7%) | 1,939 / 2,914 (66.5%)
\+ DP | 481 / 907 (53.0%) | 729 / 907 (80.4%) | 2,146 / 2,914 (73.6%)
SG = Stub Generation, DP = Dependency Plugin

TABLE IV: Compilation success rates via Jess

##  VII Related Work

Different approaches have been developed, which aim at increasing the rate of
successful compilations of Java projects by fixing the default build process
or enabling a compilation of partial code bases. Stubber [33] aims at
compiling partial source code bases via providing predefined stub classes. In
contrast to Jess, Stubber does not specifically adjust the stub classes to the
target file, but modifies the existing source code to fit a previously created
stub class, thus changing the resulting bytecode of the target file. Jcoffee
[13] leverages the error messages reported by the Java compiler during
compilation. Based on the received error, Jcoffee modifies the source code or
generates needed declaration code to supplement the compilation. Stubber and
Jcoffee modify the source code of the compilation target within the partial
program and therefore alter the resulting bytecode. Dagenais and Hendren [4]
propose a technique that enables static analysis of partial Java programs by
applying a similar type inference approach as Jess does during its stub
generation step. Their approach supports up to Java 5 compliant source code,
parses the program into a Jimple based abstract syntax tree and adds missing
type information directly to the resulting AST. These approaches use similar
strategies as Jess, however, they do not aim at producing bytecode identical
to the original compilation. Thus, these approaches are not applicable to the
use case Jess has been designed for. Melo et al. [23] propose a similar
concept as Dagenais and Hendren for inferring types of only partially
available C programs. SnR [7] also applies type inference strategies to
compile incomplete Java code snippets. However, in contrast to other
techniques that generate supplemental stub files, SnR leverages a pre-built
knowledge base, which contains the type signatures of various libraries.
Hassan and Wang [15] leverage natural language processing techniques to mine
readme files and Wiki pages of software repositories to extract build commands
that deviate from default build commands. By doing so they are able to
increase the build success rate of projects where all build errors can be
solved by running a different set of build commands. HireBuild [16] uses a
history-driven repair approach to fix failing Gradle build scripts. It uses
revisions from a build fix database that have previously been applied to build
scripts in order to fix them and automatically creates new build fix templates
from the successful fixes. These templates are then automatically applied to
the failing build script if a similar failure mode as in the past is causing
the build failure. Lou et al. [20] investigated HireBuild and found multiple
shortcomings. Inspired by the findings they propose a new technique called
HoBuFF that does not rely on historical information but generates patches
based on resources available within the present project. BuildMedic [21] tries
to fix dependency-related build issues in Maven projects by implementing an
approach that automatically updates or deletes included dependencies.

Hassan et al. [14] investigate the feasibility and challenges that arise for
the automatic building of Java projects in software repositories. Their study
shows that automatic building is a challenging task, which often results in
failure. Tufano et al. [35] specifically investigate the compilability of
snapshots within Java software repositories. Their study shows that almost all
of the investigated projects contain long periods of snapshots that are not
compilable using the provided build scripts. Sulír and Porubän [34] study
automatic build invocation on more than 7,000 Java projects. Their
investigation reveals that projects using Gradle or Ant as build tools have
higher build failure rates than projects using Maven. Furthermore, they show
that dependency resolution issues are by far the most common issue, which is
causing build failures.

##  VIII Threats to Validity

Our evaluation of Jess’s performance on compiling commit changes relies on the
vulnerability database Project KB. Project KB contains fix-commits affecting
industry relevant open source projects. This means that the projects used
within our evaluation are typically well maintained. An evaluation on projects
that are not industry-relevant, may have yielded different results.
Furthermore, we only used default build commands (except for Ant) for invoking
builds, as Hassan et al. [14] reported them to have the highest success rate
for compilation. However, in our specific dataset there may still be different
build commands, which could have caused a higher compilation success rate when
invoking the provided build scripts. Moreover, we only used Maven projects for
our first two evaluation experiments, as we only created a simple dependency
download plugin for Maven. While such a plugin can also be created for Gradle
and Ant, the performance of dependency resolution may be different for other
build tools.

##  IX Conclusion

In this paper we presented our approach Jess, which enables the compilation of
commit changes within Java source code repositories. By resolving all
references made from the source code parts changed in the commit, Jess slices
away the parts of the code base which are not needed for a successful
compilation. If references are not available in the provided code base, Jess
can generate stub files which contain type stubs that are inferred from their
usage context. By combining these steps, our approach can compile exactly the
areas of interest which have been changed in a commit.

We evaluated Jess on 347 of the most popular Java GitHub projects. Of the
32,970 randomly sampled methods and constructors, Jess was able to compile
more than 72% of which the resulting bytecode on average only differed by
0.66% from the bytecode obtained when running the build scripts provided in
the code bases. In almost 90% of cases Jess produced equal bytecode. In a
real-world experiment using the Project KB vulnerability database, the
provided build scripts only achieved successful compilation of 8% of all files
modified within the provided fix-commits. Jess allowed for the compilation of
73% of all files without the need to invoke any build scripts. These results
show that Jess can effectively compile commit changes of which the resulting
bytecode closely resembles the original one, whereas the provided build
scripts often do not allow for a compilation at all.

## Acknowledgment

This work was partially supported by the German Research Foundation (DFG)
within the Collaborative Research Centre ”On-The-Fly Computing“ (GZ: SFB
901/3) under the project number 160364472.

## References

  * [1] Checker Framework Authors.  The Checker Framework, 2023.  Accessed 2023-11-08.  URL: https://checkerframework.org.
  * [2] Project Lombok Authors.  Project Lombok, 2023.  Accessed 2023-11-08.  URL: https://projectlombok.org.
  * [3] OW2 Consortium.  ASM: Java bytecode manipulation and analysis framework, 2023.  Accessed 2023-10-16.  URL: https://asm.ow2.io.
  * [4] Barthélémy Dagenais and Laurie Hendren.  Enabling static analysis for partial java programs.  In Proceedings of the 23rd ACM SIGPLAN conference on Object-oriented programming systems languages and applications, pages 313–328, 2008.
  * [5] Jiarun Dai, Yuan Zhang, Zheyue Jiang, Yingtian Zhou, Junyan Chen, Xinyu Xing, Xiaohan Zhang, Xin Tan, Min Yang, and Zhemin Yang.  $\\{$BScout$\\}$: Direct whole patch presence test for java executables.  In 29th USENIX Security Symposium (USENIX Security 20), pages 1147–1164, 2020.
  * [6] Andreas Dann, Henrik Plate, Ben Hermann, Serena Elisa Ponta, and Eric Bodden.  Identifying challenges for oss vulnerability scanners-a study & test suite.  IEEE Transactions on Software Engineering, 48(9):3613–3625, 2021\.
  * [7] Yiwen Dong, Tianxiao Gu, Yongqiang Tian, and Chengnian Sun.  Snr: constraint-based type inference for incomplete java code snippets.  In Proceedings of the 44th International Conference on Software Engineering, pages 1982–1993, 2022.
  * [8] Apache Software Foundation.  Apache Ant Project, 2023.  Accessed 2023-10-17.  URL: https://ant.apache.org.
  * [9] Apache Software Foundation.  Apache Maven Project, 2023.  Accessed 2023-10-17.  URL: https://maven.apache.org.
  * [10] The Apache Software Foundation.  Apache Maven Dependency Plugin, 2023.  Accessed 2023-11-10.  URL: https://maven.apache.org/plugins/maven-dependency-plugin.
  * [11] The Apache Software Foundation.  Apache Maven Resourcees Plugin - Filtering, 2023.  Accessed 2024-02-19.  URL: https://maven.apache.org/plugins/maven-resources-plugin/examples/filter.html.
  * [12] The Apache Software Foundation.  Apache Tomcat, 2023.  Accessed 2023-11-08.  URL: https://tomcat.apache.org.
  * [13] Piyush Gupta, Nikita Mehrotra, and Rahul Purandare.  Jcoffee: Using compiler feedback to make partial code snippets compilable.  In 2020 IEEE International Conference on Software Maintenance and Evolution (ICSME), pages 810–813. IEEE, 2020.
  * [14] Foyzul Hassan, Shaikh Mostafa, Edmund SL Lam, and Xiaoyin Wang.  Automatic building of java projects in software repositories: A study on feasibility and challenges.  In 2017 ACM/IEEE International Symposium on Empirical Software Engineering and Measurement (ESEM), pages 38–47. IEEE, 2017.
  * [15] Foyzul Hassan and Xiaoyin Wang.  Mining readme files to support automatic building of java projects in software repositories.  In 2017 IEEE/ACM 39th International Conference on Software Engineering Companion (ICSE-C), pages 277–279. IEEE, 2017.
  * [16] Foyzul Hassan and Xiaoyin Wang.  Hirebuild: An automatic approach to history-driven repair of build scripts.  In Proceedings of the 40th international conference on software engineering, pages 1078–1089, 2018.
  * [17] Gradle Inc.  Gradle Build Tool, 2023.  Accessed 2023-10-17.  URL: https://gradle.org.
  * [18] JavaParser.org.  About JavaParser, 2019.  Accessed 2023-09-15.  URL: https://javaparser.org/about.html.
  * [19] Henrik Plate (Endor Labs).  State of Dependency Management 2023, 2023.  Accessed 2024-03-25.  URL: https://www.endorlabs.com/learn/state-of-dependency-management-2023.
  * [20] Yiling Lou, Junjie Chen, Lingming Zhang, Dan Hao, and Lu Zhang.  History-driven build failure fixing: how far are we?  In Proceedings of the 28th ACM SIGSOFT International Symposium on Software Testing and Analysis, pages 43–54, 2019.
  * [21] Christian Macho, Shane McIntosh, and Martin Pinzger.  Automatically repairing dependency-related build breakage.  In 2018 IEEE 25th International Conference on Software Analysis, Evolution and Reengineering (SANER), pages 106–117. IEEE, 2018.
  * [22] John McCarthy.  Recursive functions of symbolic expressions and their computation by machine, part i.  Communications of the ACM, 3(4):184–195, 1960.
  * [23] Leandro TC Melo, Rodrigo G Ribeiro, Breno CF Guimarães, and Fernando Magno Quintão Pereira.  Type inference for c: Applications to the static analysis of incomplete programs.  ACM Transactions on Programming Languages and Systems (TOPLAS), 42(3):1–71, 2020.
  * [24] National Institute of Standards and Technology.  National Vulnerability Database, 2023.  Accessed 2023-10-17.  URL: https://nvd.nist.gov.
  * [25] Oracle.  Java Language Specification: Chapter 14. Blocks, Statements, and Patterns, 2021.  Accessed 2023-09-27.  URL: https://docs.oracle.com/javase/specs/jls/se17/html/jls-14.html.
  * [26] Oracle.  Java Language Specification: Chapter 15. Expressions, 2021.  Accessed 2023-09-27.  URL: https://docs.oracle.com/javase/specs/jls/se17/html/jls-15.html.
  * [27] Oracle.  Java Language Specification: Chapter 9. Interfaces, 2021.  Accessed 2023-09-19.  URL: https://docs.oracle.com/javase/specs/jls/se17/html/jls-9.html.
  * [28] Oracle.  JVM Specification: Chapter 4. The class File Format, 2021.  Accessed 2024-07-04.  URL: https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-4.html.
  * [29] Oracle.  Using the Keyword super, 2021.  Accessed 2023-11-07.  URL: https://docs.oracle.com/javase/tutorial/java/IandI/super.html.
  * [30] Zhiyuan Pan, Xing Hu, Xin Xia, Xian Zhan, David Lo, and Xiaohu Yang.  Ppt4j: Patch presence test for java binaries.  In Proceedings of the IEEE/ACM 46th International Conference on Software Engineering, pages 1–12, 2024.
  * [31] Serena Elisa Ponta, Henrik Plate, and Antonino Sabetta.  Beyond metadata: Code-centric and usage-based analysis of known vulnerabilities in open-source software.  In 2018 IEEE International Conference on Software Maintenance and Evolution (ICSME), pages 449–460. IEEE, 2018.
  * [32] Serena Elisa Ponta, Henrik Plate, Antonino Sabetta, Michele Bezzi, and Cédric Dangremont.  A manually-curated dataset of fixes to vulnerabilities of open-source software.  In 2019 IEEE/ACM 16th International Conference on Mining Software Repositories (MSR), pages 383–387. IEEE, 2019.
  * [33] André Schäfer, Wolfram Amme, and Thomas S Heinze.  Stubber: Compiling source code into bytecode without dependencies for java code clone detection.  In 2021 IEEE 15th International Workshop on Software Clones (IWSC), pages 29–35. IEEE, 2021.
  * [34] Matúš Sulír and Jaroslav Porubän.  A quantitative study of java software buildability.  In Proceedings of the 7th International Workshop on Evaluation and Usability of Programming Languages and Tools, pages 17–25, 2016.
  * [35] Michele Tufano, Fabio Palomba, Gabriele Bavota, Massimiliano Di Penta, Rocco Oliveto, Andrea De Lucia, and Denys Poshyvanyk.  There and back again: Can you compile that snapshot?  Journal of Software: Evolution and Process, 29(4):e1838, 2017.
  * [36] Li Yujian and Liu Bo.  A normalized levenshtein distance metric.  IEEE transactions on pattern analysis and machine intelligence, 29(6):1091–1095, 2007.