A Survey of Machine Learning for Big Code and Naturalness
Miltiadis Allamanis, Earl T. Barr, Premkumar Devanbu, Charles Sutton
Introduction
Software is ubiquitous in modern society. Almost every aspect of life, including healthcare, energy, transportation, public safety, and even entertainment, depends on the reliable operation of high-quality software. Unfortunately, developing software is a costly process: software engineers need to tackle the inherent complexity of software while avoiding bugs, and still delivering highly functional software products on time. There is therefore an ongoing demand for innovations in software tools that help make software more reliable and maintainable. New methods are constantly sought, to reduce the complexity of software and help engineers construct better software.
Research in this area has been dominated by the formal, or logico-deductive, approach. Practitioners of this approach hold that, since software is constructed in mathematically well-defined programming languages, software tools can be conceived in purely formal terms. The design of software tools is to be approached using formal methods of definition, abstraction, and deduction. Properties of tools thus built should be proven using rigorous proof techniques such as induction over discrete structures. This logico-deductive approach has tremendous appeal in programming languages research, as it holds the promise of proving facts and properties of the program. Many elegant and powerful abstractions, definitions, algorithms, and proof techniques have been developed, which have led to important practical tools for program verification, bug finding, and refactoring (Bessey et al., 2010; Cousot et al., 2005; Clarke et al., 2003). It should be emphasized that these are theory-first approaches. Software constructions are viewed primarily as mathematical objects, and when evaluating software tools built using this approach, the elegance and rigor of definitions, abstractions, and formal proofs-of-properties are of dominant concern. The actual varieties of use of software constructs, in practice, become relevant later, in case studies, that typically accompany presentations in this line of work.
Of late, another valuable resource has arisen: the large and growing body of successful, widely used, open-source software systems. Open-source software systems such as Linux, MySQL, Django, Ant, and OpenEJB have become ubiquitous. These systems publicly expose not just source code, but also meta-data concerning authorship, bug-fixes, and review processes. The scale of available data is massive: billions of tokens of code and millions of instances of meta-data, such as changes, bug-fixes, and code reviews (“big code”). The availability of “big code” suggests a new, data-driven approach to developing software tools: why not let the statistical distributional properties, estimated over large and representative software corpora, also influence the design of development tools? Thus rather than performing well in the worst case, or in case studies, our tools can perform well in most cases, thus delivering greater advantages in expectation. The appeal of this approach echoes that of earlier work in computer architecture: Amdahl’s law (Amdahl, 1967), for example, tells us to focus on the common case. This motivates a similar hope for development tools, that tools for software development and program analysis can be improved by focusing on the common cases using a fine-grained estimate of the statistical distribution of code. Essentially, the hope is that analyzing the text of thousands of well-written software projects can uncover patterns that partially characterize software that is reliable, easy to read, and easy to maintain.
The promise and power of machine learning rests on its ability to generalize from examples and handle noise. To date, software engineering (SE) and programming languages (PL) research has largely focused on using machine learning (ML) techniques as black boxes to replace heuristics and find features, sometimes without appreciating the subtleties of the assumptions these techniques make. A key contribution of this survey is to elucidate these assumptions and their consequences. Just as natural language processing (NLP) research changed focus from brittle rule-based expert systems that could not handle the diversity of real-life data to statistical methods (Jurafsky, 2000), SE/PL should make the same transition, augmenting traditional methods that consider only the formal structure of programs with information about the statistical properties of code.
First, in Section 2, we discuss the basis of this area, which we call the “naturalness hypothesis”. We then review recent work on machine learning methods for analyzing source code, focusing on probabilistic models, such as -gram language models and deep learning methods.It may be worth pointing out that deep learning and probabilistic modeling are not mutually exclusive. Indeed, many of the currently most effective methods for language modeling, for example, are based on deep learning. We also touch on other types of machine learning-based source code models, aiming to give a broad overview of the area, to explain the core methods and techniques, and to discuss applications in programming languages and software engineering. We focus on work that goes beyond a “bag of words” representation of code, modeling code using sequences, trees, and continuous representations. We describe a wide range of emerging applications, ranging from recommender systems, debugging, program analysis, and program synthesis. The large body of work on semantic parsing (Neubig, 2016), is not the focus of this survey but we include some methods that output code in general-purpose programming languages (rather than carefully crafted domain-specific languages). This review is structured as follows. We first discuss the different characteristics of natural language and source code to motivate the design decisions involved in machine learning models of code (Section 3). We then introduce a taxonomy of probabilistic models of source code (Section 4). Then we describe the software engineering and programming language applications of probabilistic source code models (Section 5). Finally, we mention a few overlapping research areas (Section 7), and we discuss challenges and interesting future directions (Section 6).
There have been short reviews summarizing the progress and the vision of the research area, from both software engineering (Devanbu, 2015) and programming languages perspectives (Bielik et al., 2015; Yahav, 2015). However, none of these articles can be considered extensive literature reviews, which is the purpose of this work. Ernst (2017) discusses promising areas of applying natural language processing to software development, including error messages, variable names, code comments, and user questions. Some resources, datasets and code can be found at http://learnbigcode.github.io/. An online version of the work reviewed here — which we will keep up-to-date by accepting external contributions — can be found at https://ml4code.github.io.
The Naturalness Hypothesis
Many aspects of code, such as names, formatting, the lexical order of methods, etc. have no impact on program semantics. This is precisely why we abstract them in most program analyses. But then, why should statistical properties of code matter at all? To explain this, we recently suggested a hypothesis, called the naturalness hypothesis. The inspiration for the naturalness hypothesis can be traced back to the “literate programming” concept of D. Knuth, which draws from the insight that programming is a form of human communication: “Let us change our traditional attitude to the construction of programs: Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do…” (Knuth, 1984) The naturalness hypothesis, then, holds that
The naturalness hypothesis. Software is a form of human communication; software corpora have similar statistical properties to natural language corpora; and these properties can be exploited to build better software engineering tools.
The exploitation of the statistics of human communication is a mature and effective technology, with numerous applications (Jurafsky, 2000). Large corpora of human communication, viz. natural language corpora, have been extensively studied, and highly refined statistical models of these corpora have been used to great effect in speech recognition, translation, error-correction, etc..
The naturalness hypothesis holds that, because coding is an act of communication, one might expect large code corpora to have rich patterns, similar to natural language, thus allowing software engineering tools to exploit probabilistic ML models. The first empirical evidence of this hypothesis, showing that models originally developed for natural language were surprisingly effective for source code, was presented by Hindle et al. (2012, 2016). More evidence and numerous applications of this approach have followed, which are the subject of this review.
The naturalness hypothesis, then, inspires the goal to apply machine learning approaches to create probabilistic source code models that learn how developers naturally write and use code. These models can be used to augment existing tools with statistical information and enable new machine learning-based software engineering tools, such as recommender systems and program analyses. At a high level, statistical methods allow a system to make hypotheses, along with probabilistic confidence values, of what a developer might want to do next or what formal properties might be true of a chunk of code. Probabilistic methods also provide natural ways of learning correspondences between code and other types of documents, such as requirements, blog posts, comments, etc.— such correspondences will always be uncertain, because natural language is ambiguous, and so the quantitative measure of confidence provided by probabilities is especially natural. As we discuss in Section 5, one could go so far as to claim that almost every area of software engineering and programming language research has potential opportunities for exploiting statistical properties.
Although the “naturalness hypothesis” may not seem surprising, one should appreciate the root cause of “naturalness”. Naturalness of code seems to have a strong connection with the fact that developers prefer to write (Allamanis et al., 2014) and read (Hellendoorn et al., 2015) code that is conventional, idiomatic, and familiar because it helps understanding and maintaining software systems. Code that takes familiar forms is more transparent, in that its meaning is more readily apparent to an experienced reader. Thus, the naturalness hypothesis leads seamlessly to a “code predictability” notion, suggesting that code artifacts — from simple token sequences to formal verification statements — contain useful recurring and predictable patterns that can be exploited. “Naturalness” and “big code” should be viewed as instances of a more general concept that there is exploitable regularity across human-written code that can be “absorbed” and generalized by a learning component that can transfer its knowledge and probabilistically reason about new code.
This article reviews the emerging area of machine learning and statistical natural language processing methods applied to source code. We focus on probabilistic models of code, that is, methods that estimate a distribution over all possible source files. Machine learning in probabilistic models has seen wide application throughout artificial intelligence, including natural language processing, robotics, and computer vision, because of its ability to handle uncertainty and to learn in the face of noisy data. One might reasonably ask why it is necessary to handle uncertainty and noise in software development tools, when in many cases the program to be analyzed is known (there is no uncertainty about what the programmer has written) and is deterministic. In fact, there are several interesting motivations for incorporating probabilistic modeling into machine learning methods for software development. First, probabilistic methods offer a principled method for handling uncertainty and fusing multiple, possibly ambiguous, sources of information. Second, probabilistic models provide a natural framework for connecting prior knowledge to data — providing a natural framework to design methods based on abstractions of statistical properties of code corpora. In particular, we often wish to infer relationships between source code and natural language text, such as comments, bug reports, requirements documents, documentation, search queries, and so on. Because natural language text is ambiguous, it is useful to quantify uncertainty in the correspondence between code and text. Finally, when predicting program properties, probabilities provide a way to relax strict requirements on soundness: we can seek unsound methods that predict program properties based on statistical patterns in the code, using probabilities as a way to quantify the method’s confidence in its predictions.
Text, Code and Machine Learning
Programming languages narrow the gap between computers and the human mind: they construct palatable abstractions out of a multitude of minute state transitions. Source code has two audiences and is inherently bimodal: it communicates along two channels: one with humans, and one with computers. Humans must understand code to read and write it; computers must be able to execute it. The bimodality of code drives the similarities and differences between it and text. Below, we discuss these similarities and differences with forward pointers to how they have been exploited, handled, or remain open. Although code and text are similar, code written in a general-purpose programming languages, is a relatively new problem domain for existing ML and NLP techniques. Hindle et al. (2016) not only showed that exploitable similarity exists between the two via an -gram language model, but that code is even less surprising than text. Although it may seem manifestly obvious that code and text have many differences, it is useful to enumerate these differences carefully, as this allows us to gain insight into when techniques from NLP need to be modified to deal with code. Perhaps the most obvious difference is that code is executable and has formal syntax and semantics. We close by discussing how source code bimodality manifests itself as synchronization points between the algorithmic and explanatory channels.
All code is executable; text often is not. So code is often semantically brittle — small changes (e.g. swapping function arguments) can drastically change the meaning of code; whereas natural language is more robust in that readers can often understand text even if it contains mistakes. Despite the bimodal nature of code and its human-oriented modality, the sensitivity of code semantics to “noise” necessitates the combination of probabilistic and formal methods. For example, existing work builds probabilistic models then applies strict formal constraints to filter their output (Section 4.1) or uses them to guide formal methods (Section 5.8). Nevertheless, further research on bridging formal and probabilistic methods is needed (Section 6.1).
Whether it is possible to translate between natural languages in a way that completely preserves meaning is a matter of debate. Programming languages, on the other hand, can be translated between each other exactly, as all mainstream programming languages are Turing-complete. (That said, porting real-world programs to new languages and platforms remains challenging in practice (Brown, 1979)). ML techniques have not yet comprehensively tackled such problems and are currently limited to solely translating among languages with very similar characteristics, e.g. Java and C# (Section 6.1). Programming languages differ in their expressivity and intelligibility, ranging from Haskell to Malbolgehttps://en.wikipedia.org/wiki/Malbolge, with some especially tailored for certain problem domains; in contrast, natural languages are typically used to communicate across a wide variety of domains. Executability of code induces control and data flows within programs, which have only weak analogs in text. Finally, executability gives rise to additional modalities of code — its static and dynamic views (e.g. execution traces), which are not present in text. Learning over traces or flows are promising directions (Section 6.1).
Programming languages are formal languages, whereas formal languages are only mathematical models of natural language. As a consequence, programming languages are designed top-down by a few designers for many users. Natural languages, in contrast, emerge, bottom up, “through social dynamics” (Croft, 2008). Natural languages change gradually, while programming languages exhibit punctuated change: new releases, like Python 3, sometimes break backward compatibility. Formatting can also be meaningful in code: Python’s whitespace sensitivity is the canonical example. Text has a robust environmental dependence, whereas code suffers from bit rot — the deterioration of software’s functionality through time because of changes in its environment (e.g. dependencies) — because all its explicit environmental interactions must be specified upfront and execution environments evolve much more quickly than natural languages.
Source code’s formality facilities reuse. Solving a problem algorithmically is cognitively expensive, so developers actively try to reuse code (Hunt and Thomas, 2000), moving common functionality into libraries to facilitate reuse. As a result, usually functions are semantically unique within a project. Coding competitions or undergraduate projects are obvious exceptions. In contrast, one can find thousands of news articles describing an important global event. On the other hand, Gabel and Su (2010) have found that locally, code is more pattern dense than text (Section 4.1). This has led to important performance improvements on some applications, such as code completion (Section 5.1).
Because programming languages are automatically translated into machine code, they must be syntactically, even to a first approximation semantically, unambiguousExceptions exist, like http://po-ru.com/diary/ruby-parsing-ambiguities/ and https://stackoverflow.com/questions/38449606/ambiguity-in-multiple-inheritance, but these rarely matter in practice.. In contrast to NLP models, which must always account for textual ambiguity, probabilistic models of code can and do take advantage of the rich and unambiguous code structure. Although it is less pervasive, ambiguity remains a problem in the analysis of code, because of issues like polymorphism and aliasing. Section 4.2 and Section 5.4 discuss particularly notable approaches to handling them. Co-reference ambiguities can arise when viewing code statically, especially in dynamically typed languages (Section 6.1). The undefined behavior that some programming languages permit can cause semantic ambiguity and, in the field, syntactic problems can arise due to nonstandard compilers (Bessey et al., 2010); however, the dominance of a handful of compilers/interpreters for most languages ameliorates both problems.
Code’s two channels, the algorithmic and the explanatory channels, interact through their semantic units, but mapping code units to textual units remains an open problem. Natural semantic units in code are identifiers, statements, blocks, and functions. None of these universally maps to textual semantic units. For example, identifiers, even verbose function names that seek to describe their function, carry less information than words like “Christmas” or “set”. In general, statements in code and sentences in text differ in how much background knowledge the reader needs in order to understand them in isolation; an arbitrary statement is far more likely to use domain-specific, even project-specific, names or neologisms than an arbitrary sentence is. Blocks vary greatly in length and semantics richness and often lack clear boundaries to a human reader. Functions are clearly delimited and semantically rich, but long. In text, a sentence is the natural multiword semantic unit and usually contains fewer than 50 words (tokens). Unfortunately, one cannot, however, easily equate them. A function differs from a sentence or a sequence of sentences, i.e. a paragraph, in that it is named and called, while, in general settings, sentences or paragraphs rarely have names or are referred to elsewhere in a text. Further, a single function acts on the world, so it is like a single action sentence, but is usually much longer, often containing hundreds of tokens, and usually performs multiple actions, making a function closer to a sequence of sentences, or a paragraph, but paragraphs are rarely solely composed of action sentences.
Additionally, parse trees of sentences in text tend to be diverse, short, and shallow compared to abstract syntax trees of functions, which are usually much deeper with repetitive internal structure. Code bases are multilingual (i.e. contain code in more than one programming language, e.g. Java and SQL) with different tasks described in different languages, more frequently than text corpora; this can drastically change the shape and frequency of its semantic units. Code has a higher neologism rate than text. Almost 70% of all characters are identifiers and a developer must choose a name for each one (Deißenböck and Pizka, 2006); when writing text, an author rarely names new things but usually chooses an existing word to use. Existing work handles code’s neologism rate by introducing cache mechanisms or decomposing identifiers at a subtoken level (Section 4.1).
Determining which semantic code unit is most useful for which task is an open question. Consider the problem of automatically generating comments that describe code, which can be formalized as a machine translation problem from code to text. Statistical machine translation approaches learn from an aligned corpus. Statement-granular alignment yields redundant comments, while function granular alignment has saliency issues (Section 5.5). As another example, consider code search, where search engines must map queries into semantic code units. Perhaps the answer will be in maps from code to text whose units vary by granularity or context (Section 6.1).
Probabilistic Models of Code
In this section, we turn our attention to probabilistic machine learning models of source code. A probabilistic model of source code is a probability distribution over code artifacts. As all models do, probabilistic machine learning models make simplifying assumptions about the modeled domain. These assumptions make the models tractable to learn and use, but introduce error. Since each model makes different assumptions, each model has its own strengths and weaknesses and is more suitable for some applications. In this section, we group existing work into families of models and discuss their assumptions. To group these family of models in terms of shared design choices, we separate these models into three categories, based on the form of the equation of the modeled probability distribution and their inputs and outputs, with the caveat that some models fall into multiple categories. We also discuss how and why these models differ from their natural language counterparts. In Section 5, we discuss applications of these models in software engineering and programming languages.
define a probability distribution over code by stochastically modeling the generation of smaller and simpler parts of code, e.g. tokens or AST nodes.
take an abstract representation In the machine learning literature, representation, applied to code, is roughly equivalent to abstraction in programming language research: a lossy encoding that preserves a semantic property of interest. of code as input. Example representations include token contexts or data flow. The resulting model yields a conditional probability distribution over code element properties, like the types of variables, and can predict them.
infer, without supervision, a likely latent structure within code. These models are an instantiation of clustering in the code domain; they can find reusable and human-interpretable patterns.
Since code-generating models predict the complex structure of code, they make simplifying assumptions about the generative process and iteratively predict elements of the code to generate a full code unit, e.g. code file or method. Because of code’s structural complexity and the simplifying assumptions these models make to cope with it, none of the existing models in the literature generate code that always parses, compiles, and executes. Some of the models do, however, impose constraints that take code structure into account to remove some inconsistencies; for instance, (Maddison and Tarlow, 2014) only generate variables declared within each scope.
We structure the discussion about code-generating models of code as follows. We first discuss how models in this category generate code and then we show how the three different types of models (language, transducer and multimodal models) differ.
Probabilistic models for generating structured objects are widely in use in machine learning and natural language processing with a wide range of applications. Machine learning research is considering a wide range of structures from natural language sentences to chemical structures and images. Within the source code domain, we can broadly find three categories of models based on the way they generate code’s structure: token-level models that generate code as a sequence of tokens, syntactic models that generate code as a tree and semantic models that generate graph structures. Note that this distinction is about the generative process and not about the information used within this process. For example Nguyen et al. (2013) uses syntactic context, but is classified as a token-level model that generates tokens.
In contrast to text, code tends to be more verbose (Hindle et al., 2012) and much information is lost within the tokens of the context. To tackle this problem, Nguyen et al. (2013) extended the standard -gram model by annotating the code tokens with parse information that can be extracted from the currently generated sequence. This increases the available context information allowing the -gram model to achieve better predictive performance. Following this trend, but using concrete and abstract semantics of code, Raychev et al. (2014) create a token-level model that treats code generation as a combined synthesis and probabilistic modeling task.
Tu et al. (2014) and later, Hellendoorn and Devanbu (2017) noticed that code has a high degree of localness, where identifiers (e.g. variable names) are repeated often within close distance. In their work, they adapted work in speech and natural language processing (Kuhn and De Mori, 1990) adding a cache mechanism that assigns higher probability to tokens that have been observed most recently, achieving significantly better performance compared to other -gram models. Modeling identifiers in code is challenging (Allamanis and Sutton, 2013; Maddison and Tarlow, 2014; Allamanis et al., 2015; Bielik et al., 2016). The agglutinations of multiple subtokens (e.g. in getFinalResults) when creating identifiers is one reason. Following recent NLP work that models subword structure (e.g. morphology) (Sennrich et al., 2016), explicitly modeling subtoken in identifiers may improve the performance of generative models. Existing token-level code-generating models do not produce syntactically valid code. Raychev et al. (2014) added additional context in the form of constraints — derived from program analysis — to avoid generating some incorrect code.
More recently, sequence-based code models have turned to deep recurrent neural network (RNN) models to outperform -grams. These models predict each token sequentially, but loosen the fixed-context-size assumption, instead representing the context using a distributed vector representation (Section 4.2). Following this trend, Karpathy et al. (2015) and Cummins et al. (2017b) use character-level LSTMs (Hochreiter and Schmidhuber, 1997). Similarly, White et al. (2015) and Dam et al. (2016) use token-level RNNs. Recently, Bhoopchand et al. (2016) used a token sparse pointer-based neural model of Python that learns to copy recently declared identifiers to capture very long-range dependencies of identifiers, outperforming standard LSTM modelsThis work differs from the rest from the fact that it anonymizes/normalizes identifiers, creating a less sparse problem. Because of the anonymization, the results are not directly comparable with other models..
Although neural models usually have superior predictive performance, training them is significantly more costly compared to -gram models usually requiring orders of magnitude more data. Intuitively, there are two reasons why deep learning methods have proven successful for language models. First, the hidden state in an RNN can encode longer-range dependencies of variable-length beyond the short context of -gram models. Second, RNN language models can learn a much richer notion of similarity across contexts. For example, consider an -gram model over code, in which we are trying to estimate the distribution following the context for(int i=N; i>=0; i--). In a corpus, few examples of this pattern may exist because such long contexts occur rarely. A simple -gram model cannot exploit the fact that this context is very similar to for(int j=M; j>=0; j--). But a neural network can exploit it, by learning to assign these two sequences similar vectors.
Syntactic (or structural) code-generating models model code at the level of abstract syntax trees (ASTs). Thus, in contrast to sequence-based models, they describe a stochastic process of generating tree structures. Such models make simplifying assumptions about how a tree is generated, usually following generative NLP models of syntactic trees: they start from a root node, then sequentially generate children top-to-bottom and left-to-right. Syntactic models generate a tree node conditioned on context defined as the forest of subtrees generated so far. In contrast to sequence models, these models — by construction — generate syntactically correct code. In general, learning models that generate tree structures is harder compared to generating sequences: it is relatively computationally expensive, especially for neural models, given the variable shape and size of the trees that inhibit efficient batching. In contrast to their wide application in NLP, probabilistic context free grammars (PCFG) have been found to be unsuitable as language models of code (Maddison and Tarlow, 2014; Raychev et al., 2016). This may seem surprising, because most parsers assume that programming languages are context-free. But the problem is that the PCFGs are not a good model of statistical dependencies between code tokens, because nearby tokens may be far away in the AST. So it is not that PCFGs do not capture long-range dependencies (-gram-based models do not either), but that they do not even capture close-range dependencies that matter (Bielik et al., 2016). Further, ASTs tend to be deeper and wider than text parse trees due to the highly compositional nature of code.
Maddison and Tarlow (2014) and Allamanis et al. (2015) increase the size of the context considered by creating a non-context-free log-bilinear neural network grammar, using a distributed vector representation for the context. Additionally, Maddison and Tarlow (2014) restricts the generation to generate variables that have been declared. To achieve this, they use the deterministically-known information and filter out invalid output. This simple process always produces correct code, even when the network does not learn to produce it. In contrast, Amodio et al. (2017) create a significantly more complex model that aims to learn to enforce deterministic constraints of the code generation, rather than enforcing them on the directly on the output. We further discuss the issue of embedding constraints and problem structure in models vs. learning the constraints in Section 6.
Raychev et al. (2016); Bielik et al. (2016) increase the context by annotating PCFGs with a learned program that uses features from the code. Although the programs can, in principle, be arbitrary, they limit themselves to synthesizing decision tree programs. Similarly, Wang et al. (2016c); Yin and Neubig (2017) use an LSTM over AST nodes to achieve the same goal. Allamanis and Sutton (2014) also create a syntactic model learning Bayesian TSGs (Cohn et al., 2010; Post and Gildea, 2009) (see Section 4.3).
Semantic code-generating models view code as a graph. Graphs are a natural representation of source code that require little abstraction or projection. Therefore, graph model can be thought as generalizations of sequence and tree models. However, generating complex graphs is hard, since there is no natural “starting” point or generative process, as reflected by the limited number of graphs models in the literature. We refer the interested reader to the related work section of Johnson (2016) for a discussion of recent models in the machine learning literature. To our knowledge, there are no generative models that directly generate graph representations of realistic code (e.g. data-flow graphs). Nguyen and Nguyen (2015) propose a generative model, related to graph generative models in NLP, that suggests application programming interface (API) completions. They train their model over API usages. However, they predict entire graphs as completions and perform no smoothing, so their model will assign zero probability to unseen graphs. In this way, their model differs from graph generating models in NLP, which can generate arbitrary graphs.
1.2. Types of Code Generating Models
Code LMs are evaluated like LMs in NLP, using perplexity (or equivalently cross-entropy) and word error rate. Cross-entropy is the most common measure. Language models — as most predictive machine learning models — can be seen as compression algorithms where the model predicts the full output (i.e. decompresses) using extra information. Cross-entropy measures the average number of extra bits of information per token of code that a model needs to decompress the correct output using a perfect code (in the information-theoretic sense)
Transducer models can be evaluated with SMT evaluation measures, such as BLEU (Papineni et al., 2002) — commonly used in machine translation as an approximate measure of translation quality — or programming and logic-related measures (e.g. “Does the translated code parse/compile?” and “Are the two snippets equivalent?”).
2. Representational Models of Source Code
Allamanis et al. (2015) learn distributed vector representations for variable and methods usage contexts and use them to predict a probability distribution over their names. Such distributed representations are quite similar to those produced by word2vec (Mikolov et al., 2013); the authors found that the distributed vector representations of variables and methods learn common semantic properties, implying that some form of the distributional hypothesis in NLP also holds for code.
Gu et al. (2016) use a sequence-to-sequence deep neural network (Sutskever et al., 2014), originally introduced for SMT, to learn intermediate distributed vector representations of natural language queries which they use to predict relevant API sequences. Mou et al. (2016) learn distributed vector representations using custom convolutional neural networks to represent features of snippets of code, then they assume that student solutions to various coursework problems have been intermixed and seek to recover the solution-to-problem mapping via classification.
Li et al. (2016) learn distributed vector representations for the nodes of a memory heap and use the learned representations to synthesize candidate formal specifications for the code that produced the heap. Li et al. (2016) exploit heap structure to define graph neural networks, a new machine learning model based on gated recurrent units (GRU, a type of RNN (Cho et al., 2014)) to directly learn from heap graphs. Piech et al. (2015) and Parisotto et al. (2017) learn distributed representations of source code input/output pairs and use them to assess and review student assignments or to guide program synthesis from examples.
Neural code-generative models of code also use distributed representations to capture context, a common practice in NLP. For example, the work of Maddison and Tarlow (2014) and other neural language models (e.g. LSTMs in Dam et al. (2016)) describe context distributed representations while sequentially generating code. Ling et al. (2016) and Allamanis et al. (2015) combine the code-context distributed representation with a distributed representations of other modalities (e.g. natural language) to synthesize code. While all of these representations can, in principle, encode unbounded context, handling all code dependencies of arbitrary length is an unsolved problem. Some neural architectures, such as LSTMs (Hochreiter and Schmidhuber, 1997), GRUs (Cho et al., 2014) and their variants, have made progress on this problem and can handle moderately long-range dependencies.
2.2. Structured Prediction
Structured prediction is the problem of predicting a set of interdependent variables, given a vector of input features. Essentially, structured prediction generalizes standard classification to multiple output variables. A simple example of structured prediction is to predict a part-of-speech tag for each word in a sentence. Often the practitioner defines a dependency structure among the outputs, e.g., via a graph, as part of the model definition. Structured prediction has been widely studied within machine learning and NLP, and are omnipresent in code. Indeed, structured prediction is particularly well-suited to code, because it can exploit the semantic and syntactic structure of code to define the model. Structured prediction is a general framework to which deep learning methods have been applied. For example, the celebrated sequence-to-sequence (seq2seq) learning models (Sutskever et al., 2014; Bahdanau et al., 2015) are general methods for tackling the related structured prediction problem. In short, structured prediction and distributed representations are not mutually exclusive.
One of the most well-known applications of structured prediction to source code is Raychev et al. (2015), who represent code as a variable dependency network, represent each JavaScript variable as a single node, and model their pairwise interactions as a conditional random field (CRF). They train the CRF to jointly predict the types and names of all variables within a snippet of code. Proksch et al. (2015) use a directed graphical model to represent the context of an (incomplete) usage of an object to suggest a method invocation (viz. constructor) autocompletion in Java.
Structured prediction, such as predicting a sequence of elements, can be combined with distributed representations. For example, Allamanis et al. (2016, 2015) use distributed representations to predict sequences of identifier sub-tokens to build a single token and Gu et al. (2016) predict the sequence of API calls. Li et al. (2016) learn distributed representations for the nodes of a fixed heap graph by considering its structure and the interdependencies among the nodes. Kremenek et al. (2007) use a factor graph to learn and enforce API protocols, like the resource usage specification of the POSIX file API, as do Livshits et al. (2009) for information flow problems. Allamanis et al. (2018) predict the data flow graph of code by learning to paste snippets of code into existing code and adapting the variables used.
3. Pattern Mining Models of Source Code
Pattern mining models aim to discover a finite set of human-interpretable patterns from source code, without annotation or supervision, and present the mined patterns to software engineers. Broadly, these models cluster source code into a finite set of groups. Probabilistic pattern mining models of code infer the likely latent structure of a probability distribution
where is a deterministic function that returns a (possibly partial, e.g. API calls only) view of the code and represents a set of latent variables that the model introduces and aims to infer. Applications of such models are common in the mining software repositories community and include documentation (e.g. API patterns), summarization, and anomaly detection. Table 3 lists this work. Unsupervised learning is one of the most challenging areas in machine learning. This hardness stems from the need to automatically distinguish important patterns in the code from spurious patterns that may appear to be significant because of limited and noisy data. When designing unsupervised models, the core assumption lies in the objective function being used and often we resort to using a principle from statistics, information theory or a proxy supervised task. Like all machine learning models, they require assumptions about how the data is represented. An important issue with unsupervised methods is the hardness of evaluating the output, since the quality of the output is rarely quantifiable. A vast literature on non-probabilistic methods exploits data mining methods, such as frequent pattern mining and anomaly detection (Witten et al., 2016). We do not discuss these models here, since they are not probabilistic models of code. Classic probabilistic topic models (Blei, 2012), which usually views code (or other software engineering artifacts) as a bag-of-words, have also been heavily investigated. Since these models and their strengths and limitations are well-understood, we omit them here.
Allamanis and Sutton (2014) learn a tree substitution grammar (TSG) using Bayesian nonparametrics, a technique originally devised for natural language grammars. TSGs learn to group commonly co-appearing grammar productions (tree fragments). Although TSGs have been used in NLP to improve parsing performance (which is ambiguous in text), Allamanis and Sutton (2014) observe that the inferred fragments represent common code usage conventions and name them idioms. Later, Allamanis et al. (2016) extend this technique to mine semantic code idioms by modifying the input code representation and adapting the inference method.
In a similar fashion, Fowkes and Sutton (2015) learn the latent variables of a graphical model to infer common API usage patterns. Their method automatically infers the most probable grouping of API elements. This is in stark contrast to frequency-based methods (Xie and Pei, 2006) that suffer from finding frequent but not necessarily “interesting” patterns. Finally, Movshovitz-Attias and Cohen (2015) infer the latent variables of a graphical model that models a software ontology.
As in NLP and machine learning in general, evaluating pattern mining models is hard, since the quality of the discovered latent structure is subjective. Thus, researchers often resort to extrinsic, application-specific measures. For example, Fowkes et al. (2017) run a user study to directly assess the quality of their summarization method.
Applications
Probabilistic models of source code have found a wide range of applications in software engineering and programming language research. These models enable the principled use of probabilistic reasoning to handle uncertainty. Common sources of uncertainty are underspecified or inherently ambiguous data (such as natural language text). In some domains, probabilistic source code models also simplify or accelerate analysis tasks that would otherwise be too computationally costly to execute. In this section, our goal is to explain the use of probabilistic models in each area, not review them in detail. We describe each area’s goals and key problems, then explain how they can benefit from probabilistic, machine learning-based methods, and how the methods are evaluated.
Software engineering recommender systems (Robillard et al., 2010; Robillard et al., 2014) make recommendations to assist software engineering tasks, such as code autocompletion and recommending likely code reviewers for a given code change. Many of these systems employ data mining and machine learning approaches on various software engineering artifacts. Probabilistic models of code find application in source code-based recommender systems (Mens and Lozano, 2014), such as those that aid developers write or maintain code.
Modeling developer intent is a challenge: even if there were an agreed upon way to formalize intent, developers are reluctant to formalize their intent separately from their code itself. Probabilistic reasoning is well-suited for inferring intent, since it allows us to quantify the uncertainty that is inherent to inferring any latent variable. Probabilistic recommender systems extract information from the context of (partial) code and use it to probabilistically reason about developer intent.
The most prominent recommender system and a feature commonly used in integrated development environment (IDEs) is code completion. All widely used IDEs, such as Eclipse, IntelliJ and Visual Studio, have some code completion features. According to Amann et al. (2016), code completion is the most used IDE feature. However, code completion tools typically return suggestions in alphabetic order, rather than in relative order of predicted relevance to the context. Statistical code completion aims to improve suggestion accuracy by learning probabilities over the suggestions and providing to the users a ranked list. Some systems focus on automatically completing specific constructs (e.g. method calls and parameters); others try to complete all code tokens. In all cases, probabilistic code completion systems use existing code as their training set.
Statistical code completion was first studied by Bruch et al. (2009) who extracted features from code context to suggest completions for method invocations and constructors. Later, Proksch et al. (2015) used Bayesian graphical models (structured prediction) to improve accuracy. This context-based model captures all usages of an object and models the probability distribution for the next call. A version of this research is integrated into the Eclipse IDE under Eclipse Recommenders (Foundation, ).
Source code language models have implicitly and explicitly been used for code completion. Hindle et al. (2012) were the first to use a token-level -gram LM for this purpose, using the previous tokens to represent the completion context at each location. Later, Tu et al. (2014); Franks et al. (2015) used a cache -gram LM and further improved the completion performance, showing that a local cache acts as a domain adapted -gram. Nguyen et al. (2013) augment the completion context with semantic information, improving the code completion accuracy of the -gram LM. Raychev et al. (2014) exploit formal properties of the code in context to limit incorrect (but statistically probable) API call suggestions. Their method is the first to depart from simple statistical token completion towards statistical program synthesis of single statements. Apart from token-level language models for code completion, Bielik et al. (2016) and Maddison and Tarlow (2014) create AST-level LMs that can be used for suggestion.
In contrast to work that predicts source code, Movshovitz-Attias and Cohen (2013) create a recommender system to assist comment completion given a source code snippet, using a topic-like graphical model to model context information. Similarly, the work of Allamanis et al. (2014, 2015, 2016) can be seen as a recommender systems for suggesting names for variables, methods, and classes by using relevant code tokens as the context.
2. Inferring Coding Conventions
Coding conventions are syntactic constraints on code beyond those imposed by the grammar of a programming language. They govern choices like formatting (brace and newline placement) or Hungarian notation vs. CamelCase naming. They seek to prevent some classes of bugs and make code easier to comprehend, navigate, and maintain (Allamanis et al., 2014). In massive, open, online courses, coding conventions help teachers identify and understand common student errors (Glassman et al., 2015). Enforcing coding conventions is tedious. Worse, it is sometimes difficult to achieve consensus on what they should be, a prerequisite for their codification in rule-based systems. Inferring coding conventions with machine learning solves this problem by learning emergent conventions directly from a codebase. This can help software teams to determine the coding conventions a codebase uses without the need to define rules rule upfront or configure existing convention enforcing tools.
Machine learning models of source code that look at the surface structure (e.g. tokens, syntax) are inherently well-suited for this task. Using the source code as data, they can infer the emergent conventions while quantifying uncertainty over those decisions. An important challenge in this application domain is the sparsity of the code constructs, caused by the diverse and non-repeatable form of source code within projects and domains. Allamanis et al. (2014, 2015, 2016); Bavishi et al. (2017) exploit the statistical similarities of code’s surface structure to learn and suggest variable, method, and class naming conventions, while Allamanis and Sutton (2014) and Allamanis et al. (2016) mine conventional syntactic and semantic patterns of code constructs that they call idioms. They show that these idioms are useful for documentation and can help software engineering tool designers achieve better coverage of their tools. To handle code formatting conventions, Parr and Vinju (2016) learn a source code formatter from data by using a set of hand-crafted features from the AST and a -NN classifier.
3. Code Defects
Probabilistic models of source code assign high probability to code that appears often in practice, i.e. is natural. Therefore, code considered very improbable may be buggy. This is analogous to anomaly detection using machine learning (Chandola et al., 2009). Finding defects is a core problem in software engineering and programming language research. The challenge in this domain rests in correctly characterizing source code that contains a defects with high precision and recall. This is especially difficult because of the rarity of defects and the extreme diversity of (correct) source code.
Preliminary work suggests that the probability assigned by language models can indicate code defects. Allamanis and Sutton (2013) suggest that -gram LMs can be seen as complexity measures and Ray et al. (2016) present evidence that buggy code tends to have lower probability (is less “natural”) than correct code and show that LMs find defects as well as popular tools like FindBugs.
Wang et al. (2016b) use deep belief networks to automatically learn token-level source code features that predict code defects. Fast et al. (2014) and Hsiao et al. (2014) learn statistics from large numbers of code to detect potentially erroneous code and perform program analyses while Wang et al. (2016a) learn coarse-grained -gram language models to detect uncommon usages of code. These models implicitly assume that a simple set of statistics or an LM can capture anomalous/unusual contexts. Recently, Murali et al. (2017b) use a combination of topic models to bias a recurrent neural network that models the sequences of API calls in a learned probabilistic automaton. They use the model to detect highly improbable sequences of API calls detecting real-world bugs in Android code. Allamanis et al. (2018); Pradel and Sen (2017) use various elements from code context to detect specific kinds of bugs, such as variable and operator misuses.
Because of the sparsity of source code, work on detecting code defects uses different abstraction levels of source code. For example, Wang et al. (2016a) create coarse-grained -grams, while Murali et al. (2017b) focus on possible paths (that remove control flow dependencies) over API calls. Therefore, each model captures a limited family of defects, determined by the model designers’ choice of abstraction to represent. Pu et al. (2016) and Gupta et al. (2017) create models for detecting and fixing defects but only for student submissions where data sparsity is not a problem. Other data-mining based methods (e.g. Wasylkowski et al. (2007)) also exist, but are out-of-scope from this review since they do not employ probabilistic methods.
Also related is the work of Campbell et al. (2014) and Bhatia and Singh (2018). These researchers use source code LMs to identify and correct syntax errors. Detecting syntax errors is an easier and more well defined task. The goal of these models is not to detect the existence of such an error (that can be deterministically found) but to efficiently localize the error and suggest a fix.
The earlier work of Liblit et al. (2005), Zheng et al. (2006) use traces for statistical bug isolation. Kremenek et al. (2007) learn factor graphs (structured prediction) to model resource-specific bugs by modeling resource usage specifications. These models use an efficient representation to capture bugs, but can fail on interprocedural code that requires more complex graph representations. Finally, Patra and Pradel (2016) use an LM of source code to generate input for fuzz testing browsers.
Not all anomalous behavior is a bug (it may simply be rare behavior), but anomalous behavior in often executed code almost certainly is (Engler et al., 2001). Thus, probabilistic models of source code seem a natural fit for finding defective code. They have not, however, seen much industrial uptake. One possible cause is their imprecision. The vast diversity of code constructs entails sparsity, from which all anomaly detection methods suffer. Methods based on probabilistic models are no exception: they tend to consider rare, but correct, code anomalous.
4. Code Translation, Copying, and Clones
The success of statistical machine translation (SMT) among (natural) languages has inspired researchers to use machine learning to translate code from one source language (e.g. Java) to another (e.g. C#). Although rule-based rewriting systems can be (and have been) used, it is tedious to create and maintain these rules, in the face of language evolution. SMT models are well suited for this task, although they tend to produce invalid code. To reduce these errors during translation Karaivanov et al. (2014) and Nguyen et al. (2015) add semantic constraints to the translation process.
Existing research has applied widely used SMT models for text. Although these models learn mappings between different language constructs such as APIs, they have only been used for translating between programming languages of similar paradigms and structure (C# and Java are both object-oriented languages with managed memory). This is an important limitation; machine learning innovations are required to translate between languages of different types (e.g. Java to Haskell or assembly to C) or even languages with different memory management (e.g. Java to C). Existing per-statement SMT from Java to C does not track memory allocations and therefore fails to emit memory de-allocations that C’s lack of garbage collection requires. Similarly, translating object-oriented code to functional languages will require learning the conceptual differences of the two paradigms while preserving semantics, such as learning to translate a loop to a map-reduce functional. Researchers evaluate translation models by scoring exact matches, measuring the syntactic or semantic correctness of the translated code, or using BLEU (Papineni et al., 2002). We discuss this and other measure-related issues in Section 6.
Developers often copy code during development. This practice requires fixups to rename variables and handle name collisions; it can also create code clones, similar code snippets in different locations of a code base (Koschke, 2007). Allamanis and Brockschmidt (2017) automate naming cleanups after copying; they use structured prediction and distributed representations to adapt/port a pasted snippet’s variables into the target context. Their method probabilistically represents semantic information about variable use to predict the correct name adaptations without external information (e.g. tests). Clones may indicate refactoring opportunities (that allow reusing the cloned code). White et al. (2016) use autoencoders and recurrent neural networks (Goodfellow et al., 2016) to find clones as code snippets that share similar distributed representations. Using distributed vector representations allows them to learn a continuous similarity metric between code locations, rather than using edit distance.
5. Code to Text and Text to Code
Linking natural language text to source code has many useful applications, such as program synthesis, traceability, search and documentation. However, the diversity of both text and code, the ambiguity of text, the compositional nature of code and the layered abstractions in software make interconnecting text and code a hard problem. Probabilistic machine learning models provide a principled method for modeling and resolving ambiguities in text and in code.
Generating natural language from source code, i.e. code-to-text, has applications to code documentation and readability. For example, Oda et al. (2015) translate Python code to pseudocode (in natural language) using machine translation techniques, with the goal of producing a more readable generation of the code. Iyer et al. (2016) design a neural attention model that summarizes code as text. Movshovitz-Attias and Cohen (2013) generate comments from code using -gram models and topic models.
The reverse direction, text-to-code, aims to help people, both developers and end users, write programs more easily. This area is closely related to semantic parsing in NLP. Semantic parsing is the task of converting a natural language utterance into a representation of its meaning, often database or logical queries that could subsequently be used for question answering (Jurafsky, 2000). We do not have space to fully describe the large body of work that has been done in semantic parsing in NLP, but instead will focus on text-to-code methods that output code in languages that are used by human software developers. This area has attracted growing interest, with applications such as converting natural language to Excel macros (Gulwani and Marron, 2014), Java expressions (Gvero and Kuncak, 2015), shell commands (Lin et al., 2017; Lin et al., 2018), simple if-then programs (Quirk et al., 2015), regular expressions (Kushman and Barzilay, 2013) and to SQL queries (Zhong et al., 2017). Finally, Yin and Neubig (2017) have recently presented a neural architecture for general-purpose code generation. For more details, Neubig (2016) provides an informal survey of code generation methods.
6. Documentation, Traceability and Information Retrieval
Improving documentation and code search are central questions in software engineering. Probabilistic models are particularly natural here because, as we have seen, they allow integrating information between NL text and code. Although the more general code-to-text and text-to-code models from the previous sections could clearly be applied here, researchers have often found, as in the NL domain, that more specialized solutions are currently effective for these problems.
Code search — a common activity for software engineers (Sadowski et al., 2015; Amann et al., 2016) — can employ natural language queries. Software engineering researchers have focused on the code search problem using information retrieval (IR) methods (Gallardo-Valencia and Elliott Sim, 2009; Holmes et al., 2005; Thummalapenta and Xie, 2007; Mcmillan et al., 2013). Niu et al. (2016) has used learning-to-rank methods but with manually extracted features. Within the area of statistical models of source code, Gu et al. (2016) train a sequence-to-sequence (seq2seq) neural network to map natural language into API sequences. Allamanis et al. (2015) learn a bimodal, generative model of code, conditioned on natural language text and use it to rank code search results. All of these methods use rank-based measures (e.g. mean reciprocal rank) to evaluate their performance.
Documentation is text that captures requirements, specifications, and descriptions of code. Engineers turn to it to prioritize new features and to understand code during maintenance. Searching, formalizing, reasoning about, and interlinking code to (i.e. the traceability problem of Gotel et al. (2012)) documentation are seminal software engineering problems. Mining common API patterns is a recurring theme and there is a large literature of non-probabilistic methods (e.g. frequency-based) for mining and synthesizing API patterns (Buse and Weimer, 2012; Xie and Pei, 2006), which are out-of-scope of this review. Also out-of-scope is work that combines natural language information with APIs. For example, Treude and Robillard (2016) extract phrases from StackOverflow using heuristics (manually selected regular expressions) and use off-the-self classifiers on a set of hand-crafted features. We refer the reader to Robillard et al. (2010) for all probabilistic and non-probabilistic recommender systems. Within this domain, there are a few probabilistic code models that mine API sequences. Gu et al. (2016) map natural language text to commonly used API sequences, Allamanis and Sutton (2014) learn fine-grained source code idioms, that may include APIs. Fowkes and Sutton (2015) uses a graphical model to mine interesting API sequences.
Documentation is also related to information extraction from (potentially unstructured) documents. Cerulo et al. (2015) use a language model to detect code “islands” in free text. Sharma et al. (2015) use a language model over tweets to identify software-relevant tweets.
7. Program Synthesis
Program synthesis is concerned with generating full or partial programs from a specification (Gulwani et al., 2017). Traditionally, a specification is a formal statement in an appropriate logic. More recently, researchers have considered partial or incomplete specifications, such as input/output pairs (Lau, 2001) or a natural language description. Program synthesis generates full or partial programs from a specification. When the specification is a natural language description, this is the semantic parsing task (see Section 5.5). Program synthesis (e.g. from examples or a specification) has received a great deal of attention in programming language research. The core challenge is searching the vast space of possible programs to find one that complies with the specification. Probabilistic machine learning models help guiding the search process to more probable programs.
Research on programming by example (PBE) leverages machine learning methods to synthesize code. Liang et al. (2010) use a graphical model to learn commonalities of programs across similar tasks with the aim to improve program synthesis search. Menon et al. (2013) use features from the input/output examples to learn a parameterized PCFG to speed up synthesis. Singh and Gulwani (2015) extract features from the synthesized program to learn a supervised classifier that can predict the correct program and use it to re-rank synthesis suggestions. The recent work of Balog et al. (2017) and Parisotto et al. (2017) combine ideas from existing enumerative search techniques with learned heuristics to learn to efficiently synthesize code, usually written within a DSL. Neelakantan et al. (2015) and Reed and de Freitas (2016) introduce neural differentiable architectures for program induction. This is an interesting emerging area of research (Kant, 2018), but does not yet scale to the types of problems considered by the programming language and software engineering community (Gaunt et al., 2016; Feser et al., 2017); also see Gaunt et al. (2016); Devlin et al. (2017) for a comparison of neural program induction and program synthesis methods. Finally, the code completion work of Raychev et al. (2014) can be seen as a limited program synthesis of method invocations at specific locations.
Although program synthesis is usually referred in the context of generating a program that complies to some form of specification, probabilistic models have been used to synthesize random — but functioning — programs for benchmarks and compiler fuzzing. Cummins et al. (2017b) synthesize automatically a large number of OpenCL benchmarks by learning a character-level LSTM over valid OpenCL code. Their goal is to generate reasonable-looking code, rather than synthesize a program that complies with a specification. To ease their task, they normalize the code by consistently alpha-renaming variables and method names. Finally, they filter invalid intermediate output so they, in the end, generate only valid programs. In a similar manner, Patra and Pradel (2016) synthesize JavaScript programs for fuzz testing JavaScript interpreters.
8. Program Analysis
Program analysis is an important area that seeks to soundly extract semantic properties, like correctness, from programs. Sound analyses, especially those that scale, can be imprecise and return unacceptably large numbers of false positives. Probabilistic models of code use probabilistic reasoning to alleviate these problems. Three distinct program analysis approaches have exploited probabilistic models of source code.
First, a family of models relaxes the soundness requirement, yielding probabilistic results instead. Raychev et al. (2015) use a graphical model to predict the probability distribution of JavaScript variable types by learning statistical patterns of how code is used. Oh et al. (2015) and Mangal et al. (2015) use machine learning models to statistically parameterize program analyses to reduce false positive ratio while maintaining high precision. Chae et al. (2017) reduce automatically (without machine learning) a program to a set of data-flow graphs, manually extract features from them. Using these features they then learn the appropriate parametrization of a static analysis, using a traditional classifier. Koc et al. (2017) train a classifier, using LSTMs, to predict if a static analysis warning is a false positive. The neural network learns common patterns that can discriminate between false and true positives. The second paradigm that has been explored is to use machine learning to create models that produce plausible hypotheses of formal verification statements that can be proved. Brockschmidt et al. (2017) and Li et al. (2016) propose a set of models that generate separation logic expressions from the heap graph of a program, suitable for formally verifying its correctness. The third paradigm — although not yet applied directly to source code but to other forms of formal reasoning — learns heuristics to speed-up the search for a formal automated proof. The goal of such methods is to replace hard-coded heuristics with a learnable and adaptive module that can prioritize search tactics per-problem without human intervention. Alemi et al. (2016) and Loos et al. (2017) take a first step towards this direction by learning heuristic for automated theorem proving for mathematical expressions.
Challenges and Future Directions
The development and analysis of code must contend with uncertainty, in many forms: “What is the purpose of this code?”, “What does functionality does this unit test test?”, “From this program, can we infer any of its specification?”, “What is this program’s intended (or likely) input domain?” or “Why did this program crash here?”. This contrasts with traditional program analysis which is conservative: it deems that a program has a property, like a bug, if that property is possible, independent of likelihood. In contrast, machine learning studies robust inference under uncertainty and noise. Thus, the application of machine learning to code and its development, is an emerging research topic with the potential to influence programming language and software engineering research. Here, we list topics where principled probabilistic reasoning promises new advances, focusing on probabilistic models of code. We also list open challenges, some quite longstanding like long-range dependency, and speculate about directions for making progress on their resolution.
For each open problem, machine learning is introduced to handle uncertainty, ambiguity, or avoid hard-coded heuristics. Probabilistic learning systems model these as noise which they handle robustly by training statistical principled models. This in turn has allowed the creation of new previously impossible systems (e.g. text-to-code systems) or replaced existing, hard-coded heuristics with machine learning systems that promise to be more robust and generalize better. This course of direction highly resembles that of NLP, where hard-coded “expert” systems have been successfully replaced by sophisticated machine learning methods.
The first wave of machine learning for source code applied off-the-shelf machine learning tools with hand-extracted features. The second wave, reviewed here, avoids manual feature extraction and uses the source code itself within machine learning heavily drawing inspiration from existing machine learning methods in NLP and elsewhere. The third wave promises new machine learning models informed by programming language semantics. What form will it take?
At the time of this writing, machine learning, and deep learning in particular, is enjoying rock-star status among research fields. Despite its current (perhaps ephemeral) popularity, it is not a panacea. In some cases, a machine learning model may not be required (e.g. when the problem is deterministic) and, in other cases, simple models can outperform advanced, off-the-self deep learning methods, designed for non-code domains (Fu and Menzies, 2017; Hellendoorn and Devanbu, 2017). Furthermore, over-engineering or under-engineering machine learning models usually leads to suboptimal results. Selecting a machine learning model for a specific problem necessitates questioning if a specific model is fit for the target application. Strong baseline methods are needed to estimate if a performance improvement justifies the added complexity of a model. In short, the right tools should be used for the right job, and machine learning is no exception.
Programming language research and practice use a well-defined and widely useful set of representations, usually in a symbolic form. In contrast, machine learning research customarily works with continuous representations. Bridging the gap between these representations by allowing machine learning systems to reason with programming language representations, is an important challenge. Handling unambiguous code has already led to a combination of probabilistic methods with “formal” constraints, that limit the probabilistic model to valid code. For example, Maddison and Tarlow (2014) limit their model to generate syntactically correct and scope-respecting code while Raychev et al. (2015) easily create a highly-structured model of a program taking advantage of its unambiguous form. Introducing better representations that bridge the gap between machine learning and source code will allow the probabilistic models of code to reason about the rich structure and semantics of code, without resorting to proxies. The core problem in this area is the lack of understanding of machine learning from the programming language community and vice-versa. At the same time, new machine learning methods that can handle programming language structures in its full complexity at scale need to be researched.
A major obstacle here is engineering systems that efficiently and effectively combine the probabilistic world of machine learning and the formal, logic-based world of code analysis. One approach, taken by several authors (Allamanis et al., 2017; Raychev et al., 2015; Rocktäschel and Riedel, 2017; Mangal et al., 2015), is to relax formal systems into probabilistic. Such systems, however, lack the guarantees formal systems (e.g. soundness) often provide. Alemi et al. (2016), Balog et al. (2017) and Loos et al. (2017) follow a second approach that maintains soundness: they learn input and context-specific heuristics that efficiently guide search-based methods, such as theorem proving and program synthesis.
All of approaches to source code modeling must decide whether to explicitly model the structure and constraints of source code or to rely on general methods with adequate capacity. One one hand, using well-known, domain-generic machine learning methods has the advantage that the models are well-understood and rarely require significant effort or expertise to apply. On the other hand, designing models with built-in inductive biases for the problem domain usually performs better with less data, at the cost of manually designing and debugging domain (even problem-specific) networks. One such promising direction is modular neural network architectures. Such architectures decompose the network into components that are combined based on the problem instance. For source code models, such architectures can derive its structure through static analyses. These architectures have been useful for visual question answering in NLP. Andreas et al. (2016) create neural networks by composing them from “neural modules”, based on the input query structure. Similarly, we believe that such architectures will be useful within probabilistic models of source code. An early example is the work of Allamanis et al. (2018) who design a neural network based on the output of data flow analysis. Such architectures should not only be effective for bridging representations among communities but — as we will discuss next — can combat issues with compositionality, sparsity and generalization. Nevertheless, issues that arise in static analyses, such as path explosion, will still need to be addressed.
The principle of reusability in software engineering creates a form of sparsity in the data, where it is rare to find multiple source code elements that perform exactly the same tasks. For example, it is rare to find hundreds of database systems, whereas one can easily find thousands of news articles on a popular piece of news. The exceptions, like programming competitions and student solutions to programming assignments, are quite different from industrial code. This suggests that there are many opportunities for researching machine learning models and inference methods that can handle and generalize from the highly-structured, sparse and composable nature of source code data. Do we believe in the unreasonable effectiveness of data (Halevy et al., 2009)? Yes, but we do not have sufficient data.
Although code and text are both intrinsically extensible, code pushes the limit of existing machine learning methods in terms of representing composition. This is because most natural language methods rarely define novel, previously unseen, terms, with the possible exception of legal and scientific texts. In contrast, source code is inherently extensible, with developers constantly creating new terms (e.g. by defining new functions and classes) and combining them in still higher-level concepts. Compositionality refers to the idea that the meaning of some element can be understood by composing the meaning of its constituent parts. Recent work (Hill et al., 2016) has shown that deep learning architecture can learn some aspects of compositionality in text. Machine learning for highly compositional objects remains challenging, because it has proven hard to capture relations between objects, especially across abstraction levels. Such challenges arise even when considering simple code-like expressions (Allamanis et al., 2017). However, if sufficient progress is to be made, representing source code artifacts in machine learning will improve significantly, positively affecting other downstream tasks. For example, learning composable models that can combine meaningful representations of variables into meaningful representations of expressions and functions will lead to much stronger generalization performance.
Data sparsity is still an important and unsolved problem. Although finding a reasonably large amount of source code is relatively easy, it is increasingly hard to retrieve some representations of source code. Indeed, it is infeasible even to compile all of the projects in a corpus of thousands of projects, because compiling a project requires understanding how the project handles external dependencies, which can sometimes be idiosyncratic. Furthermore, computing or acquiring semantic properties of existing, real-world code (e.g. purity of a function (Finifter et al., 2008) or pre-/post- conditions (Hoare, 1969)) is hard to do, especially at scale. Scalability also hampers harvesting run-time data from real-world programs: it is challenging to acquire substantial run-time data even for a single project. Exploring ways to synthesize or transform “natural” programs that perform the same task in different ways is a possible way ahead. Another promising direction to tackle this issue is by learning to extrapolate from run-time data (e.g. collected via instrumentation of a test-suite) to static properties of the code (Allamanis et al., 2016). Although this is inherently a noisy process, achieving high accuracy may be sufficient, thanks to the inherent ability of machine learning to handle small amounts of noise.
Strong generalization also manifests as a deployability problem. Machine learning models, especially when they have become effective, are often so large that they are too large for a developer’s machine, but using the cloud raises privacyhttps://www.theregister.co.uk/2017/07/25/kite_flies_into_a_fork/ concerns and prevents offline coding. When under development and tooling is needed, code evolves quickly, subjecting models to constant concept drift and necessitating frequent retraining which can be extremely slow and costly. Addressing this deployability concern is an open problem and requires advances in machine learning areas such as transfer learning and one-shot learning. For example, say a program uses libraries and , which have been shipped with the models and . Could we save time training a model for by transferring knowledge from and ?
Finally, source code representations are multifaceted. For example, the token-level “view” of source code is quite different from a data flow view of code. Learning to exploit multiple views simultaneously can help machine learning models generalize and tackle issues with data sparsity. Multi-view (Xu et al., 2013) and multi-modal learning (e.g. Gella et al. (2016)), areas actively explored in machine learning, aim to achieve exactly this. By combining multiple representations of data, they aim to improve upon the performance on various tasks, learning to generalize using multiple input signals. We believe that this is a promising future direction that may allow us to combine probabilistic representations of code to achieve better generalization.
To train and evaluate machine learning models, we need to easily measure their performance. These measures allow the direct comparison of models and have already lead to improvements in multiple areas, such as code completion (Section 5.1). Nonetheless, these measures are imprecise. For instance, probabilistic recommender systems define a probability density over suggestions whose cross-entropy can be computed against the empirical distribution in test data. Although cross-entropy is correlated with suggestion accuracy and confidence, small improvements in cross entropy may not improve accuracy. Sometimes the imprecision is due to unrealistic use case assumptions. For example, the measures for LM-based code completion tend to assume that code is written sequentially, from the first token to the last one. However, developers rarely write code in such a simple and consistent way (Proksch et al., 2016). Context-based approaches assume that the available context (e.g. other object usages in the context) is abundant, which is not true in a real editing scenarios. Researchers reporting keystrokes saved have usually assumed that code completion suggestions are continuously presented to the user as she is typing. When the top suggestion is the target token, the user presses a single key (e.g. return) to complete the rest of the target.
Furthermore, some metrics that are widely used in NLP are not suited for source code. For example, BLEU score is not suitable for measuring the quality of output source code (e.g. in transducer models) because it fails to account for the fact that the syntax is known in all programming languages, so the BLEU score may be artificially “inflated” for predicting deterministic syntax. Second, the granularity over which BLEU is computed (e.g. per-statement vs. per-token) is controversial. Finally, syntactically diverse answers may be semantically equivalent, yielding a low BLEU score while being correct. Finding new widely-accepted measures for various tasks will allow the community to build reliable models with well-understood performance characteristics.
2. New Domains
Here, we visit a number of domains to which machine learning has not yet been systematically applied and yet suffer from uncertainty problems that machine learning is particularly well suited to address, promising new advances.
Debugging is a common task for software engineers (Wong et al., 2016). Debugging is like trying to find a needle in the haystack; a developer has to recognize relevant information from the deluge of available information. A multitude of tools exist in this area whose main goal is to visualize a program’s state during its execution. Probabilistic models of source code could help developers, such as by filtering highly-improbable program states. Statistical debugging models, such as the work of Zheng et al. (2003, 2006) and Liblit et al. (2005) are indicative of the possibilities within this area. Further adding learning within debugging models may allow further advances in statistical debugging. However, progress in this area is impeded by the combination of lack of data at a large scale and the inherent difficulty of pattern recognition in very high-dimensional spaces. Defects4J (Just et al., 2014) — a curated corpus of bugs — could further prove useful within machine learning for fault prediction. Furthermore, collecting and filtering execution traces to aid debugging is another challenge for which machine learning is well-suited. Collection requires expensive instrumentation, which can introduce Heisenbugs, bugs masked by the overhead of the instrumentation added to localize them. Here the question is “Can machine learning identify probe points or reconstruct more complete traces from partial traces?” Concerning filtering traces, machine learning may be able to find interesting locations, like the root cause of bugs. Future methods should also be able to generalize across different programs, or even different revisions of the same program, a difficult task for existing machine learning methods.
Traceability is the study of links among software engineering artifacts. Examples include links that connect code to its specification, the specification to requirements, and fixes to bug reports. Developers can exploit these links to better understand and maintain their code. Usually, these links must be recovered. Information retrieval has dominated link recovery. The work of Guo et al. (2017) and Le et al. (2015) suggests that learning better (semantic) representations of artifacts can successfully, automatically solve important traceability problems.
Two major obstacles impede progress: lack of data and a focus on generic text. Tracing discussions in email threads, online chat rooms (e.g. Slack), documents and source code would be extremely useful, but no publicly available and annotated data exists. Additionally, to date, NLP research has mostly focused on modeling generic text (e.g. from newspapers); technical text in conversational environments (e.g. chatbots) has only begun to be researched. StackOverflow presents one such interesting target. Although there are hundreds of studies that extract useful artifacts (e.g. documentation) from StackOverflow, NLP methods — such as dependency parsing, co-reference analysis and other linguistic phenomena — have not been explored.
Code completion and synthesis using machine learning are two heavily researched and interrelated areas. Despite this fact, to our knowledge, there has been no full scale comparison between LM-based (Hindle et al., 2016; Nguyen et al., 2013; Raychev et al., 2014) and structured prediction-based autocompletion models (Bruch et al., 2009; Proksch et al., 2015). Although both types of systems target the same task, the lack of a well-accepted benchmark, evaluation methodology and metrics has lead to the absence of a quantitative comparison that highlights the strengths and weaknesses of each approach. This highlights the necessity of widely accepted, high-quality benchmarks, shared tasks, and evaluation metrics that can lead to comparable and measurable improvements to tasks of interest. NLP and computer vision follow such a paradigm with great successSee https://qz.com/1034972/ for a popular account of the effect of large-scale datasets in computer vision..
Omar et al. (2017) discuss the challenges that arise from the fact that program editors usually deal with incomplete, partial programs. Although they discuss how formal semantics can extend to these cases, inherently any reasoning about partial code requires reasoning about the programmer’s intent. Lu et al. (2017) used information-retrieval methods for synthesizing code completions showing that simply retrieving snippets from “big code” can be useful when reasoning about code completion, even without a learnable probabilistic component. This suggests a fruitful area for probabilistic models of code that can assist editing tools when reasoning about incomplete code’s semantics, by modeling how code could be completed.
Software engineering education is one area that is already starting to be affected by this field. The work of Campbell et al. (2014) and Bhatia and Singh (2018) already provide an automated method for fixing syntax errors in student code, whereas Wang et al. (2017); Piech et al. (2015) suggest advancements towards giving richer feedback to students. Achieving reasonable automation can help provide high-quality computer science education to many more students than is feasible today. However, there are important challenges associated with this area. This includes the availability of highly-granular data where machine learning systems can be trained, difficulty with embedding semantic features of code into machine learning methods and the hardness of creating models that can generalize to multiple and new tasks. Student coursework submissions are potentially a ripe application area for machine learning, because here we have available many programs, from different students, which are meant to perform the same tasks. An especially large amount of such data is available in Massive Open Online Courses (MOOCs). This opens exciting possibilities, such as providing granular and detailed feedback, curriculum customization and other intelligent tutoring systems can significantly change computer science education.
Probabilistic models have allowed computer systems to handle noisy inputs such as speech, and handwritten text input. In the future, probabilistic models of source code may enable novel assistive IDEs, creating inclusive tools that improve upon conventional methods of developer-computer interaction and provide inclusive coding experiences.
Related Research Areas
A variety of related research areas within software engineering and programming languages overlap with the area of statistical modeling of code. One of the most closely related research areas is mining software repositories (MSR) and big code. MSR is a well-established, vibrant and active field; the idea is to mine the large amounts of source code data and meta-data available in open-source (and commercial) repositories to gain valuable information, and use this information to enhance both tools and processes. The eponymous flagship conference is now in its 15th iteration. “Big Code” is a synonymous neologism, created by DARPA’s MUSE programhttp://science.dodlive.mil/2014/03/21/darpas-muse-mining-big-code/ to borrow some branding shine from the well-marketed term “Big Data”. The MSR field’s early successes date back to work by Zimmermann et al. (2005), Williams and Hollingsworth (2005), Gabel and Su (2008) and Acharya et al. (2007) on mining API protocols from source code bases. These approaches used pragmatic counting techniques, such as frequent item-set mining. Mining software repositories is in one sense a broader field than statistical models of code, as rather than focusing on code alone, MSR considers the full spectrum of software engineering data that can be derived from the software engineering process, such as process measures, requirement traceability, commit logs. Additionally, research in malware detection is related to probabilistic models of code (Arp et al., 2014; Cen et al., 2015).
Another active area at the intersection machine learning and programming language research is probabilistic programming (Gordon et al., 2014). This might appear to be related to statistical models of code, but in fact there is a fundamental difference; essentially, probabilistic programming works in the reverse direction. Probabilistic programming seeks to deploy programming language concepts to make it easier for developers to write new machine learning algorithms. Statistical code models seek to apply machine learning concepts to make it easier for developers to write new programs. In some sense, the two areas are dual to each other. That being said, one can certainly imagine completing the cycle and attempting to develop statistical code models for probabilistic programming languages. This could be a fascinating endeavor as probabilistic programming grows in popularity to the extent that large corpora of probabilistic programs become available.
In software engineering, the term modeling often refers to formal specifications of program behavior, which are clearly a very different kinds of models than those described here. Combining formal models of semantics with statistical models of source code described in this review would be an interesting area for future research. There is also some work on probabilistic models of code that do not have a learning component, such as Liblit et al. (2005). Within machine learning, there has been an interesting recent line of work on neural abstract machines (Graves et al., 2014; Reed and de Freitas, 2016; Riedel et al., 2017), which extend deterministic automata from computer science, such as pushdown automata and Turing machines, to represent differentiable functions, so that the functions can be estimated by techniques from machine learning. To the best of our knowledge, this intriguing line of work does not yet consider source code, unlike the work described in this review. Finally, semantic parsing is a vibrant research area of NLP that is closely related to the idea of program synthesis from natural language; see Section 5.5 for more discussion.
Conclusions
Probabilistic models of source code have exciting potential to support new tools in almost every area of program analysis and software engineering. We reviewed existing work in the area, presenting a taxonomy of probabilistic machine learning source code models and their applications. The reader may appreciate that most of the research contained in this review was conducted within the past few years, indicating a growth of interest in this area among the machine learning, programming languages and software engineering communities. Probabilistic models of source code raise the exciting opportunity of learning from existing code, probabilistically reasoning about new source code artifacts and transferring knowledge between developers and projects.
Appendix A Supplementary materials
LABEL:tbl:datasets presents datasets that have been specifically created for this area of research.