Self-Supervised Bug Detection and Repair
Year: 2022

Abstract:
Machine learning-based program analyses have recently shown the promise of integrating formal and probabilistic reasoning towards aiding software development. However, in the absence of large annotated corpora, training these analyses is challenging. Towards addressing this, we present BUGLAB, an approach for selfsupervised learning of bug detection and repair. BUGLAB co-trains two models: (1) a detector model that learns to detect and repair bugs in code, (2) a selector model that learns to create buggy code for the detector to use as training data. A Python implementation of BUGLAB improves by up to 30% upon baseline methods on a test dataset of 2374 real-life bugs and finds 19 previously unknown bugs in open-source software.

heading: 1 Introduction
text: Detecting and repairing bugs in source code requires strong reasoning skills over formal structures (e.g. data and control flow) and ambiguous information (e.g. identifier names, coding idioms, and comments). Traditional program analyses are able to detect critical bugs through formal reasoning and combinatorial search, but need to be manually coded by experts. That is a lengthy and costly process, which misses the opportunity to use ambiguous information pervasive within code.
Towards broadening the applicability of such methods, and utilizing ambiguous information, deep learning-based bug detection methods are being investigated [22, 3, 13]. These methods have the potential to further improve the engineering of software we rely on every day. However, many challenges in the area remain open, such as creating robust bug detection and repair methods that cover a wide range of common bugs in the absence of large supervised training corpora. Existing work focuses on randomly inserted bugs [22, 13], Cloze test proxy tasks [3], corpora of small code edits that may contain bugs [9] or build errors [28]. All these approaches rely on datasets of very limited size or ones known not to be representative of the characteristics of bugs found in real code.
In this work, we propose BUGLAB, a self-supervised approach that trains robust bug detectors by co-training a bug selector that learns to create hard-to-detect bugs (Sec. 2). For example, for a given code snippet with two well-named variables, a variable misuse bug may be easy to detect and repair, whereas an incorrect comparison operator might be significantly harder to identify. We propose a neural architecture for BUGLAB (Sec. 3) and implement it for Python (Sec. 4). Our implementation considers four broad classes of seemingly simple, yet hard-to-detect bugs and shows improved performance over training with randomly-inserted bugs on PYPIBUGS, a new, manually curated test set of 2374 real-life bugs (Sec. 5). Furthermore, we tested our trained models on popular open-source Python packages and identified 19 previously unreported bugs, though false positive rates of ∼ 98% remain impractical. We hope that creating machine learning methods that can detect these bugs early and assist developers will speed up software development and allow engineers to deliver more robust software. We release PyPIBugs and our code at https://github.com/ microsoft/neurips21-self-supervised-bug-detection-and-repair.
∗Work done while at Microsoft Research.
35th Conference on Neural Information Processing Systems (NeurIPS 2021).

heading: 2 Self-Supervised Bug Detection
text: In this section, we first introduce the concept of code rewriting, and then use it to define BUGLAB as a framework for self-supervised learning of bug detection and repair.
Code Rewriting Rewriting is common within compilers and their optimizations, test-driven searchbased bug repair tools, mutation testing, and refactoring tools. Rewrites can be semantics-preserving (e.g. renamings of local variables), or semantics-altering (e.g. replacing >= by !=).
Let S denote the set of all syntax trees (not necessarily rooted in the start symbol of the language grammar). Syntax tree locations ` ∈ { } ∪ N∗ in a syntax tree s ∈ S are recursively defined, where s| = s and s|` for ` = `′◦i is the i-th child of s|`′ (i.e. s|(2,3) denotes the third child of the second child of s). We define a rewrite rule ρ = (mρ, tρ) as a pair of a matching function mρ : S → {true, false} and a transformation function tρ : S → S. The matching function mρ(s) yields true iff the rule ρ is applicable at the root of a subtree s. The transformation function can be applied to obtain a transformed syntax tree. For convenience, we define tρ(s) = s iff mρ(s) = false. We then write ρ(s) to indicate the modification of a syntax tree s using ρ when possible, and otherwise the identity function. For reversible rewrite rules ρ, we denote the inverse rule as ρ−1 such that ρ−1(ρ(s)) = s holds. We discuss concrete rewrite rules ρ in Sec. 4. Given a set of rewrite rules R we define the set of “potential rewrites” in a syntax tree s as RRs ={ 〈`, ρ〉 | ρ ∈ R, ` location in s,mρ(s|`) = true } . For each tuple 〈`, ρ〉 ∈ RRs , we use s′ = s[ρ]` to denote the new syntax tree obtained by applying ρ at location ` of s. In BUGLAB, we train models that use rewrites from RRs to insert and repair bugs. We will discuss such neural models in Sec. 3.
BUGLAB In BUGLAB, we are interested in selfsupervised training of a robust bug detector model Dθ with parameters θ on an unannotated codebase C. Let R be a set of rewrite rules2 that allows to insert and repair bugs. We train Dθ to be able to recognize the “hardest” possible rewrites that could be applied on our codebase C. For this, we consider the loss LDθ of Dθ on a rewritten code snippet s[ρ]`, for which the model needs to predict the repairing rewrite 〈 `, ρ−1 〉 . Formally, we want to minimize the objective
Es∼C [ max 〈`,ρ〉∈RRs LDθ ( s[ρ]`, 〈 `, ρ−1 〉)] .
However, for any useful detector the set of rewrites RRs is commonly very large or unbounded and computing the maximum over all 〈`, ρ〉 ∈ RRs is practically intractable. To address this, BUGLAB introduces a bug selector model Sφ (with parameters φ), whose goal is to approximate the
intractable max〈`,ρ〉∈RRs LDθ (·). We can then sample rewrites from Sφ instead of computing the maximum. We denote this as 〈`, ρ〉 ∼ Sφ(s) and the overall BUGLAB training objective can be written as a min-max optimization problem:
max φ min θ Es∼C
[ E〈`,ρ〉∼Sφ(s) [ LDθ ( s[ρ]`, 〈 `, ρ−1 〉)]] . (1)
The two models S and D in BUGLAB are “symmetric” in the sense that they both predict rewrites on code snippets, and only differ in their objectives — one aiming to introduce bugs and one aiming to repair them. In practice, we can and do use the same architecture to model both S and D, which we will discuss in the next section. At test time, we discard S and only use the trained detector D to locate and repair bugs.
2In this work we assume that R contains a special “identity” rewrite rule ρ∅ that does not change the code.

heading: 3 Neural Models
text: In this section, we discuss how we represent code in BUGLAB and the neural models we use to learn how to rewrite code in the selector and detector models.
Code Representation We consider source code as a set of entities vi ∈ V which relate to each other with a set of typed relations ek ∈ E, where a relation ek = (vi, r, vj) denotes a relationship between entities vi and vj with type r. The entities and relations can be thought as a heterogeneous graph G = (V,E). The choice of code entities and their relationships is a form of high-level feature extraction. We discuss concrete entities and relationships for Python in Sec. 4. We also define a projection function Ptok that accepts V and E and returns a sequence Vtok of the token entities in V with the nodes appearing in relations in E deterministically mapped to elements of Vtok, i.e. Etok = {(p(vi), r, p(vj))}, where p maps the entities in V to Vtok. Ptok will be used for relational transformer models.
To learn a neural representation of the code entities vi, first we define an embedding function e(vi) which maps the content of each entity to an initial D-dimensional representation. Throughout this work — similar to Allamanis et al. [4] and other previous work — we deterministically split the string representation of each node into subtokens (e.g., fooBar is split into foo and bar), embed them through a learned embedding matrix, and use max pooling to get a single vector. We then “contextualize” the entity representations within G using one of two models: a MLP-based GNN model with max message aggregation and the GREAT relational transformer of Hellendoorn et al. [13] over the token sequence and relations Vtok, Etok = Ptok(V,E). GREAT uses both positional encodings and the projected relations in Etok. See Appx. A for detailed architecture descriptions. Other models to compute entity representations can be used, but were not explored in this work.
We use r` to denote the computed vector representation of the entity at location `, independent of the model used to produce it. We use these representations to define our code rewriting models.
Probabilistic Code Rewriting Models Both bug selection and bug detection require to model the probability of applying a specific rewrite at a location in a code snippet s, either to introduce or repair a bug. For this, we factorize this task into localization and rewrite-given-location models, i.e.
p ( 〈`, ρ〉 | s, RRs ) = ploc ( ` | s, RRs ) prew ( ρ | `, s, RRs ) . (2)
We model ploc as a probability distribution over the relevant locations {` | 〈`, ρ〉 ∈ RRs } ∪ {NoBug}, where NoBug is a special location used to indicate that the code is not buggy. In practice, we implement this similar to a pointer net [19] using the representations r` (see Appx. A for details).
To select rewrites, we use rewrite type-specific learnable rule score functions wρ (r`,Mρ(s, `)). This function maps a vector representation of an entity r` and potential additional metadata onto a scalar score. The rule-specific metadata Mρ(s, `) is defined for some rewrites, e.g. containing representations of other entities that could be used in the location `. We will discuss three concrete rule score functions in Sec. 4. The rewrite probability distribution prew is then modeled by a softmax over the scores of all applicable rewrites at a target location `, i.e.
prew ( ρ | `, s, RRs ) = softmax 〈`,ρ′〉∈RRs (wρ′ (r`,Mρ′(s, `))) .

heading: 4 A Python Implementation
text: This section presents an implementation of BUGLAB for Python called PYBUGLAB. PYBUGLAB currently tackles a large subset of “stupid simple bugs” [16]. Fixing these bugs requires small changes to the code, but commonly has significant impact on code correctness. Such bugs may be thought as a form of a typographical mistake or a copy-paste error, and are often relatively hard to locate by humans but obvious after the fact. They are also quite common, as observed in the empirical statistics of Karampatsis and Sutton [16] and Just et al. [14]. Future work may focus on a broader set of rewrite rules or even learnable rewrites, but as we will observe in Sec. 5 more work is needed towards this. Almost all ideas in PYBUGLAB transfer straightforwardly to other programming languages other than Python, but would require some engineering effort to implement.
PYBUGLAB Code Entities and Relations In this work, we follow related literature (see Sec. 6 for more) and extract entities and relationships that are readily available by tokenizers, parsers,
existing simple program analyses, or other Python-specific program analysis tools. The complete list of entities and relationships can be found in Appx. B and include syntactic entities and relations, relations about the intraprocedural data and control flow, types, and documentation. Some notable entities include SyntaxNodes, Tokens, and Symbols (references to variables and functions). Fig. 4 in Appx. B shows a graph of the entities and relationships of the snippet in Fig. 2.

heading: 4.1 Bug-Inducing PYBUGLAB Rewrite Rules
text: PYBUGLAB focuses on four common kinds of bugs. Fig. 2 shows a code snippet and the rewrites allowed for each location, which number 63 even for this small example.
Variable Misuse Originally defined by Allamanis et al. [3] as a Cloze test for source code, Vasic et al. [30] and Hellendoorn et al. [13] reformulated the task to localizing a variable misuse bug (if any) within a snippet and repairing it. PYBUGLAB uses the latter representation. Variable misuse bugs are common, with 12.8-14.8% found in the ManySStuBs4J corpus [16] and about 6% of them caught during Java compilation in the Google build system [28]. To insert and repair variable misuse bugs, PYBUGLAB supports variable-swapping rewrites, such as in locations l1, l3 and l4 (amongst others) in Fig. 2. To score a variable-swapping rewrite, we use the representation of the rewrite location r` along with the representation rσ of a variable Symbol σ that could replace the current variable, i.e. is in-scope and has been defined before `. The rule score function wρ for replacing the variable at ` with the symbol σ is then computed as the inner product r>` rσ .
Argument Swapping (or Argument Selection) First coined by Rice et al. [26], it refers to swapping the arguments of a function invocation, e.g. in l6 of Fig. 2. Rice et al. [26] and DeepBugs [22] tackled this problem when all arguments are single identifiers. PYBUGLAB extends this to swapping arbitrary argument expressions. The rule score function wρ for an argument swapping rewrite is a two-layer MLP applied to the concatenation of the output representations of the representation of the parameter and the to-be-swapped arguments arg1, and arg2: MLP ( [rparams, rarg1, rarg2] ) .
Wrong Operator Corrupting operators has a long history in mutation testing [14]. Detecting incorrect operators with deep learning was first tackled by DeepBugs [22] by using learnable embeddings of operators, operands and literals for arithmetic and comparison operators. DeepBugs focused only on binary operators. In PYBUGLAB we tackle all binary operators, including Boolean, arithmetic and comparison operators and two unary operators: logical and arithmetic negation. Locations l11, l14, l16, and l20 in Fig. 2 are rewrites related to wrong operators. The rule score function wρ for an operator rewrite again uses an inner product, r>` rop, where rop is a learned embedding for operator op. Note that we rewrite operators only to compatible operators (e.g. < to > but not +).
Wrong Literal Corrupting operands, and specifically, literals appearing in the source code, is also a common strategy in mutation testing. As in mutation testing, PYBUGLAB handles a limited number of commonly used literals, allowing rewrites to replace integer literals within the set of -2, -1, 0, 1, 2 and swapping the Boolean literal True with False and vice versa. The scoring function is identical to the operator rewrite, using a learnable embedding rlit for each literal lit.

heading: 4.2 PYBUGLAB Rewrite Rules for Data Augmentation
text: We additionally consider more rewrite rules that are not meant to change the program semantics, using them as a form of data augmentation. This is in spirit similar to ideas in computer vision where images are transformed (e.g. rotated, cropped) but maintain their original content. Such rewrites
Algorithm 1 Sequential Training Procedure for Selector and Detector models
Require: Code dataset C, initial detector/selector model parameters θ(0), φ(0) 1: for meta-epoch i = 0 to I do 2: // Create dataset of buggy programs: 3: C(i)D ← {( s[ρ]`, 〈 `, ρ−1 〉) | s ∈ C, k samples 〈`, ρ〉 ∼ Sφ(i)(s)
} 4: θ(i+1) ← update θ(i) by training D on C(i)D 5: // Create dataset of hard-to-detect bugs: 6: C(i)S ← {( s, argmax〈`,ρ〉∈RRs ( LD θ(i+1) ( s[ρ]`, 〈 `, ρ−1 〉))) | s ∈ C
} 7: φ(i+1) ← update φ(i) by training S on C(i)S
have been shown to yield adversarially robust models of code [23]. Although our goal is not to provide adversarial robustness, we believe that such rewrites can help generalization. PYBUGLAB implements the following rewrites for this purpose:
• Variable Renaming renames a local variable to a random name not already in scope. • Comment Deletion removes code comments, including docstrings and inline comments. Such
comments commonly contain natural language information that is useful for code comprehension, but usually do not affect program semantics.
• Comparison Expression Mirroring swaps the two sides of a comparison operator and changes it appropriately. For example, a<b is transformed to b>a. Note that in cases such as foo() < bar(), this will change the order of execution of foo and bar, possibly altering program semantics.
• If-Else Branch Swapping negates the test condition of an if-else statement or a ternary expressions using DeMorgan’s law and swaps the then body with the else body.

heading: 4.3 Implementation Details
text: To make the training computationally tractable we approximate Eq. 1. A simplified, sequential version of our training procedure is shown in Alg. 1. Intuitively, we alternate between training the two models, as the (discrete) sampling of rewrite rules in the selector models precludes direct endto-end training. We first use the current state of the selector model to generate “hard” samples and train the detector model on these samples (we always include the unmodified (i.e., NoBug case) as a sample). Then, we use the loss of the detector model to identify those generated samples that were hardest to detect and train the selector model to produce such samples.
In practice, we implemented the training procedure as a system of asynchronously communicating processes, and all of the described steps happen in parallel. We do not use “generations” C
(0) D/S , C (1) D/S , . . . of datasets, but instead use two constantly updated “pools” of training data, one for the detector and one for the selector. Each training step samples a minibatch from the current state of the corresponding data pool. We remove samples from the data pool once they have been sampled ν times for use in training, in spirit similar to replay buffers in reinforcement learning. In our experiments, ν was set to 4. We regularly (in separate, concurrent processes) take snapshots of the the current state of the D and S models to generate new elements that are updated to the data pools, matching the procedure described in Alg. 1. We approximate the argmax in line 6 by only considering the k samples chosen in line 3 for each input program. During training of S, we then mask out the unobserved choices before computing the loss.

heading: 5 Evaluation
text: We now discuss our new dataset and evaluate PYBUGLAB. We . . . . . . . . . .highlight. . . . .key. . . . . . . .results.
Datasets To train PYBUGLAB we retrieve the 4k most downloaded packages in the Python package index (PyPI) and take 3.4k of them as training packages, using the rest for test purposes. During training, PYBUGLAB installs each package along with all its dependencies. Installing all the dependencies is important for extracting the entities and the relations beyond local syntactic ones (e.g. type
inference, method resolution). For each file, PYBUGLAB checks if it is a duplicate of a file that has already been seen in the training following the method of Allamanis [1] and runs all the relevant program analyses to extract the entities and relationships in each function. When we use additional rewrites for data augmentation, these are applied at the input of the PYBUGLAB pipeline as a form of pre-processing. Following Alg. 1, the bug selector S selects k = 5 bugs to introduce, rewrites the source code text, and then the program analyses extract the new entities and relationships for the rewritten code snippets. The initial and rewritten code snippets are then used to create the training data for the detector and selector models.
We use two testsets to measure performance. First, we create RANDOMBUGS, a testset of 761 445 snippets derived from functions from the 600 PyPI test packages (not seen during training). For each function we find within these packages we add it to the dataset along with 9 rewritten functions with a randomly inserted bug. On average graphs have 260 nodes, 601 edges, 25 rewrite locations, and 130 possible rewrites. We also collect a testset of real bugs. Although we conjecture that, in practice, the vast majority of bugs like those discussed in Sec. 4.1 are fixed when developers locally test their software, a few of those slip and then are fixed across different revisions checked into a version control systems. We have crawled the accessible repositories of all 285k packages in the Python Package Index (PyPI), collected and manually filtered bugs captured by the rewrites from Sec. 4.1. . . . . .This. . . . . .new . . . . . . . . .dataset, . . . . . . . . . . . . . . .PYPIBUGS,. . . . . . . . . .contains. . . . . . .2374 . . . . . . . . . . . . .real-world,. . . . . . .small . . . . . . .bugs. We describe the data collection process in detail in Appx. D. In addition, we consider PYPIBUGS-PostFix: the examples from PYPIBUGS after a bug was fixed - we believe these samples are very likely to not contain any bugs anymore. We publish the dataset at https://www.microsoft.com/en-us/download/103554 and include it in the supplementary material.

heading: 5.1 Quantitative Evaluation
text: Our first experiment aims to evaluate whether the BUGLAB training framework yields more precise bug detectors. We consider two model architectures, using either GNNs or the GREAT transformer to compute embeddings of code entities (architecture details and hyperparameter choices can be found in Appx. A). We use four different training strategies: “supervised” is training only a bug detector on a fixed dataset of 1 million functions from the 3.4k training packages with randomly inserted bugs. “Random Selector” refers to a variant of PYBUGLAB using a bug selector model that uniformly at random picks a rewrite to insert bugs. Finally, PYBUGLAB and PYBUGLAB +Aug use our framework from Sec. 2, with the latter also using additional rewrites to augment our code corpus. For the fully supervised model, we train with early stopping over a validation set; the other models are trained for a fixed number of 300 epochs (with 200k training samples per epoch) for the bug detector3 and the last detector model is used for evaluation.
Effectiveness of BUGLAB Training We first consider the performance of different models on the synthetic RANDOMBUGS dataset. Tbl. 1 shows the accuracy of predicting a full bug repair correctly (“Joint”) and analogous to Eq. 2 break this up into a localization accuracy (“Loc”) of predicting the correct location (or NoBug for correct examples) and a repair accuracy
(“Repair”) for selecting the correct rewrite given the buggy location.
We observe that . . . . . . . . . . . . . . . . . . . . . . . .PYBUGLAB-training. . . . . . .leads. . . .to . . . . . . .more. . . . . . . .robust. . . . . .bug . . . . . . . . . . .detectors. . . . . . . . . . . . .compared. . .to. . . . . . . .other . . . . . . . . . .methods for both GNNs and GREAT. Random selector models — a form of data augmentation — improve performance over supervised methods but mostly on in-distribution RANDOMBUGS samples. As expected, . . . . . . . . . . . . .augmenting. . . . .the . . . . . .code. . . . . . . . .dataset. . . . . . .helps. . . . . . . . . . . . . . . . .generalization, but does not make a substantial difference. Expanding the kinds of rewrites used to augment the data and learning to select them may improve performance in the future.
3This amounts to about 1.5 weeks for the GNN models and about 1 week for the GREAT models on a single P100 GPU.
Furthermore, . . . .bug. . . . . . . . . . . . . . .localization . . .is . . . . . . .much. . . . . . . .harder. . . . . .than. . . . . . . .repair . . .at . .a . . . . . . .given. . . . . . . . . .location. This is somewhat expected: there are many more candidate locations compared to potential repairs at a given location. However, this suggests that research should focus on the localization problem rather than repair.
We now turn to the results on PYPIBUGS, shown in Tbl. 2, which also includes the accuracy of choosing the special NoBug location on the PYPIBUGS-PostFix dataset, as well as the area under the precision recall curve for the results on both PYPIBUGS and PYPIBUGS-PostFix.
We find that . . . . . . . . . .detecting. . . . .and. . . . . . . . . . . .repairing . . . . . . . . .real-life. . . . . . .bugs . .is. . . . . . . . . . . . . . .significantly. . . . . . . .harder. . . . . .than. . . . . . . . . . .handling. . . . . . . . . . . .randomly . . . . . . . . .inserted. . . . . .bugs. As PYBUGLAB models trained using a learned bug selector outperform those using a “Random Selector”, we speculate that the learned selector avoids generating easy-to-detect bugs, focusing the detector model on recognizing deeper semantic patterns. Despite this, improvements in RANDOMBUGS often correlate with improvements in PYPIBUGS. This is encouraging: collecting PYPIBUGS-like datasets is costly; corpora with random bugs can help measure relative improvements to some extent. Finally, we find that . . . . . . . . . . . . . .recognizing . . . . . . . . . . . . .non-buggy. . . . . . . . . .samples. . .is . . . . . .very . . . . . .hard, and in particular, does not always profit from training in PYBUGLAB.
In our qualitative analysis (Sec. 5.2), we observed that the models raised some confident but incorrect warnings at very “odd” locations. However, these warnings were different across models. We have tested an ensembling strategy averaging the output probabilities of five separately trained GNN models. This results in localization and repair accuracies of 83.0% and 85.4% on RANDOMBUGS (vs. 81.1% and 84.5%) and 34.4% and 72.2% on PYPIBUGS (vs. 33.5% and 72.0%). As we discuss in Sec. 5.2 finding the cause of the “spurious” warnings is important future work.
Per-Bug Evaluation To better understand which bugs are hard to detect, we break down the results the best-performing PYBUGLAB +Aug models on RANDOMBUGS by type of bug in Tbl. 3. We observe that incorrect literals are some of the hardest bugs to detect. Incorrect assignment operators (e.g. = and +=) are easy to detect in RANDOMBUGS, but significantly harder in PYPIBUGS. This may be attributed to class imbalance, with simple assignment (=) being the majority class. . . . . . . . . . . .Detecting. . .if .a. . . . . . . . .snippet. . . . .has . .a . . . . .bug . . .or . . . .not. . . . . . . .seems . . .to . . .be. . . .the. . . . . . . . .hardest. . . . . .task:. . . .no. . . . . . . .model. . . . . . . . . .achieves. . . . . . . . . . .accuracy. . . . . . . . .beyond. . . . . .63%.
We note that in our experiments, GNNs-based models seem to often outperform GREAT, somewhat contradicting the results of Hellendoorn et al. [13]. We have performed substantial additional experiments to investigate and verify these results, cf. Sec. A.2. This may have to do with the performance of these models on long sequences or that the GNN has access to more fine-grained information, instead of relations over the projected token sequences. For example, this could be attributed to the lack of syntax and symbol nodes in the representation used in GREAT. Nevertheless, GREAT
is noticeably better (62.5% vs. 53.8%) at detecting NoBug and locating wrong binary operators in PYPIBUGS.
Table 4: Development of Performance on Bug Selector Samples
Training Time Joint Loc Repair
0.5 days 64.2 83.8 72.1 1.5 days 62.5 80.7 72.9 2.5 days 62.0 83.0 69.8 3.5 days 61.7 82.5 69.8 4.5 days 61.9 83.0 69.5 5.5 days 61.1 83.0 68.6 6.5 days 60.5 78.7 72.4
Bug Selector Performance To understand how training of the bug selector proceeds, we perform two experiments. In our first experiment, we take a snapshot of the selector model during training of the PYBUGLAB +Aug (GNN) model every 24 hours, after an initial burn-in phase of 12 hours. We then generate 10000 buggy samples using each of these snapshots and then test a fixed model on each of these snapshots. The results of this are shown in Tbl. 4, using a fully trained PYBUGLAB +Aug (GNN) model from another training run as a fixed model. We conclude that
. . . . . . . . . . . . . . . . . . . . . . . . .PYBUGLAB succeeds. . . .in . . . . . . . . . .learning . . .to . . . . . . . . . .generate. . . . . . . .harder. . .to . . . .find. . . . . .bugs, though we can observe the selector model trading off “harder-to-localize” and “harder-tofix” properties. Tests on other models show similar trends, confirming the robustness of this result.
In a second experiment, we compare the distribution of different bug kinds in PYPIBUGS and RANDOMBUGS with the distribution of bugs sampled from the final snapshot of our selector model from above. The results are shown in Tbl. 5, where we can see that a number of bugs (argument swapping, use of wrong literals and of assignment operators) are substantially over-represented, whereas mistakes in comparison operators and variable misuse are under-represented. This indicates that . . . . . . . . . . . . . . . . . . . . . . . . . .PYBUGLAB generates. . . . . .hard. . .to . . . . . .find, . . . .but. . . .not. . . . . . . . . . . . . .necessarily . . . . . . . . . .realistic . . . . . .bugs.
Comparison to CuBERT Finally, we compare our models to CuBERT [15], which uses a masked language modeling objective to pre-train a BERT-like model and then learns bug detectors specific to a class of bugs (e.g., wrong binary operators) on top of this pre-trained model. Note that CuBERT detects if a bug exists but does not localize it. For the comparison, we create two sub-datasets of PYPIBUGS: PYPIBUGS-WrongOp contains the 501 samples that involve the binary operators supported by CuBERT, and PYPIBUGS-VarMisuse, which contains the 1278 bugs that involve variable misuses. We complete both of these datasets with 501 (resp. 1278) random NoBug code samples from our RANDOMBUGS, to match the 1:1 buggy/non-buggy distribution used in CuBERT’s training. Since CuBERT classification models focus on a single bug type, to compare to PYBUGLAB we mask out all code locations that do not correspond to a bug that could be detected by the corresponding CuBERT model. We then treat the prediction of the NoBug location as a “non-buggy” prediction and all other locations as a “buggy” prediction. For example, for the snippet in Fig. 2, only the locations l2, l11, l14, l16, and l20 and their corresponding rewrites are considered by PYBUGLAB for the comparison on PYPIBUGS-WrongOp.
Tbl. 6 shows the results of comparing the released CuBERT snapshots with the PYBUGLAB +Aug GNN model. We observe that
. . . .the . . . . . . . . . . . . . . . . . . . . . . . . .PYBUGLAB models . . . . .have. . . . . . . . . . . . . . . .substantially . . . . . . .better. . . . . . .recall. . . . . .than. . . . . . . . . . . . . . . . . . .CuBERT-based . . . . . . . . .models, even though they were trained to detect more bug types. When calibrating the CuBERT models to have a recall equal to PYBUGLAB, their precision drops substantially. In particular, on PYPIBUGS-WrongOp, it is reduced to 0.609, and
1 def make_id(name): 2 r = get_rand_string(12) 3 if len(name) <= 22: 4 name = name[:22] 5 return name + "-" + r
(a) A wrong comparison operator bug (red box) in PYPIBUGS detected and repaired by the GNN PYBUGLAB +Aug models.
1 def update(self, roomId, 2 title, **request_params): 3 check_type(roomId, basestring) 4 check_type(roomId, basestring) 5 [...]
(b) A variable misuse (red box) caught in an open-source project. GNN PYBUGLAB +Aug suggests to rewrite roomId to title. The fixing pull request is found here.
Figure 3: Bugs found by PYBUGLAB. Snippets reformatted and abbreviated to fit figure.
on PYPIBUGS-VarMisuse, it is reduced to 0.613; in both cases, PYBUGLAB outperforms CuBERT substantially.

heading: 5.2 Qualitative Inspection of Raised Warnings
text: We now take a qualitative look at the raised warnings raised by PYBUGLAB. As example, Fig. 3a shows a sample of PYPIBUGS where the developer used an incorrect comparison operator. Once pointed to it, it is clear to a human that the truncation statement in line 4 has no effect (under the reasonable assumption that name is a string), and that a different comparison operator (>) is necessary.
To gain an understanding of the performance of PYBUGLAB on realistic data, we performed an indepth analysis of the cases flagged as bugs by our best-performing model on the code found within the 4k top PyPI packages. We observed a mixture of false positives with few previously unseen real-life bugs, matching the quantitative results in Tbl. 3. First, we find that the majority of the false positives are “incorrect literal” detections. This suggests that learning to detect such bugs is a hard problem. Furthermore, many literals serve as default “configurations” (e.g. the number of retries for a network request) and different values are not bugs. We posit that a large percentage of literal replacements the selector learns to make fall in this category.
We also found that some repairs suggested by the model actually produce semantically equivalent code. For example, the model lacks knowledge that two variables refer to the same object in memory (aliasing), and so attempts to “repair” variable misuse bugs by switching between these. Other examples includes checking the return values of standard functions such as Python’s str.find, which returns -1 if the query string is not found. In such cases, PYBUGLAB often suggested to rewrite an if x.find(y) <= -1 to if x.find(y) == -1, which makes no difference in practice. These false negatives can be attributed to the fact that the bug selector model considers such changes as introducing bugs, even though they are not actually changing behavior. This suggests that for better results, the rewrite rules need to ensure that the rewrites are not semantics-preserving and represent bugs.
Finally, some reported issues were sufficiently complex that it took us (the human authors) a couple of minutes of thought to conclude that a warning is spurious. Simultaneously, there are some warnings that are “obviously” incorrect to us, but the reasons why the neural models raise them is unclear. This highlights the importance of research on explainability techniques along with better ways to calibrate model confidence. The fact that selectors may introduce spurious “bugs” may also be affecting how the detector model learns. Ideas that have appeared in reinforcement learning, such as the one of Dennis et al. [8], may allow models to improve their performance in spite of spurious bugs.
Overall, . . . . .only. . . .19. . .of. . . . .the . . . . . .1000. . . . . . . . . . .reported . . . . . . . . . . .warnings . . . . . .were. . . . . . . .found . . .to . . .be. . . . . . . . . .real-life. . . . . . .bugs. Of these 19, we reported 11 on GitHub (6 already merged, 5 pending approval). See Appx. G for details. 3 other bugs had already been fixed between the version PYBUGLAB processed and the current version or the project was deprecated, whereas another 5 bugs are minor and we decided not to report them. One of the detected bugs is shown in Fig. 3b. Overall, most of the detected bugs appear within unit tests, logging, or exception handling, possibly because bugs there do not impact the core functionality of a project. However, given the number of such bugs we collected in PYPIBUGS, we believe that such bugs arise equally often in other code, but that they are detected and fixed more quickly.
Although our analysis only forms a lower bound on the precision of PYBUGLAB and related methods, it suggests that there is still ample room for future improvements towards making machine learningbased bug detection and repair practically useful.

heading: 6 Related Work
text: Detecting bugs in source code has been researched since the early days of computing. Traditionally, bug detection is tackled as a formal task, where any code that cannot be proved to satisfy some correctness property may contain a bug. This is essential for security- and safety-critical bugs, but not for other — equally common — bugs. In the last decade, software engineering and programming language research have increasingly realized ambiguous information within code (e.g. variable names, comments) contains valuable information and using this information can yield valuable results [2]. The main premise is that patterns in source code, such as patterns in names, control, and data flow can be informative. This information can also be exploited to detect some bugs. For example, Ray et al. [24] noted that even simple language models tend to assign lower probability to buggy code.
Multiple static analysis methods have been researched that combine some form of data-oriented bug detection. This ranges from language model-based tools, such as the early work of Wang et al. [32] to specification-mining tools such as the work of Eberhardt et al. [10]. BUGLAB is related to DeepBugs [22] which uses an MLP over a limited window of code tokens and train separate models to detect wrong operators, operands, and argument swappings. BUGLAB opts for a more structured representation of code and a single model. Allamanis et al. [3], Vasic et al. [30], Hellendoorn et al. [13] tackle variable misuse bugs (one of the kinds of bugs included in PYBUGLAB) but either by randomly introducing the bugs in code or using a Cloze-like test. Instead, BUGLAB opts for a selfsupervised approach and tackles a broader range of bugs. Concurrently to this work, Patra and Pradel [21] showed an alternative method for learning to generate realistic bugs. Dinella et al. [9] learn a supervised sequential model that performs graph transformations that replicate small edits in code (refactoring, introducing functionality, bug fixing, etc.). Their model — Hoppity — could serve as a learnable rewrite operation in BUGLAB in future work. Dynamic analysis methods have also been researched with promising results [31], but collecting representative dynamic traces over a diverse set of programs at scale (e.g. from the top Python packages used in this work) is practically impossible.
BUGLAB is related to ideas around self-supervised learning recently explored in deep learning, computer vision, and NLP. In our case, we aim to train a bug detection model without using training data from real-life bugs. BUGLAB resembles ELECTRA [6], with the important difference that the rewrites to the input code go beyond single token replacement that need to respect strict constraints of programming languages (syntax, variable scopes) and the model is directly used for bug detection, rather than for pre-training. The main BUGLAB objective Eq. 1 also resembles GANs [12] with the exception that the objective is non-differentiable (introducing a bug alters the discrete data representation), the selector is a structured probabilistic code rewriting model, and that we are mainly interested in the bug detector (analogous to the discriminator) rather than the selector.

heading: 7 Discussion and Conclusions
text: Learned program analyses offer the promise to improve how we develop software. They also offer a great opportunity to study machine learning models that combine formal and probabilistic reasoning. Towards achieving these we presented BUGLAB, a self-supervised approach for learning program analyses, that improves upon baseline methods and detects bugs in real-life code. We also empirically show the limitations of existing bug-detecting machine learning methods, which suffer from impractical false-positive rates. Importantly, we show the large gap of performance of existing methods on corpora of randomly inserted bugs — commonly used in prior work — and real-life bugs.

heading: Acknowledgements
text: We want to thank Sebastian Nowozin and Marwin Segler for helpful discussions, Marwin Segler for comments on a draft of this work, and the anonymous reviewers for useful questions and suggestions. Finally, we would like to thank the contributors to the following open-source tools used: PyTorch [20], PyDriller [27], MessagePack, LibCST, Jedi, Kubernetes, Helm.

Review summaries:
This paper proposes a self-supervised approach that trains bug detectors by co-training a bug selector that learns to create bugs. A high-level framework is formulated, and then a python implementation using graph neural networks and relational transformers is provided. Experiments on a manually curated bug dataset demonstrate its effectiveness.

This work tackles programming bug detection and repair, introducing BugLab, which uses two models trained in an adversarial fashion together. One model is a bug detector that learns to identify and repair bugs, while the other is a bug selector that introduces buggy mutations in code that are difficult for the detector to identify. When trained together, the detector model is able to see more training data for bugs that it is worse at detecting, leading to better performance after sufficient training in this fashion. BugLab also uses semantics-preserving code modifications as a form of data augmentation. The experiments show that the BugLab training procedure leads to improved performance in bug localization and repair when applied to prior models (GNN and GREAT), compared to baselines of supervised learning and selecting buggy mutations at random instead of sampling from the trained selector model.

This paper describes an adversarial approach towards detecting and repairing local bugs in human-written software. The approach involves training in tandem two neural networks, the detector, which discovers and repairs bugs in a given piece of code, and the selector, which produces bugs that the detector is unable to detect. At test time, only the detector is used.  The architecture used for each of the networks is similar: first the program is represented by a graph with labeled edges, then each node is embedded using a language-like model (to take into account semantic information in variable names), then embeds the graph using either a GREAT relational transformer or a GNN. The code rewriting model then predicts a distribution over locations to rewrite using a pointer network, and a distribution of rewrites at that given location using a softmax over potential rewrites.  The types of bugs considered are local and shallow bugs such as variable misnamings or use of the wrong operator or literal in an expression. The authors create a corpus of code by crawling pypi and create a corpus of known shallow bugs by looking for version updates that include edits that fit their definition of a shallow bug. They additionally include a corpus of random generated bugs. In general, their approach works better on the random bug corpus than on the real bug corpus, and also works much better at repairing bugs given localization than it does at localization. They are able to achieve better performance than the special purpose CuBERT model using a F1 metric. Additionally, they were able to detect 19 bugs in open source projects, but with a TPR of 0.19% this is probably not yet a productionizable tool in this context.

This paper describes a self-supervised ML-based bug fixer that uses a combination of a bug localization and repair model, with a bug injection model. The two models are identical in architecture: they select a program location to mutate, and then one of a few possible mutations and its parameters (e.g., swapping a binary operator with another). Training is done concurrently from a dynamic corpus of buggy and bug-free examples. The bug fixer is trained on detecting bug-free examples, or fixing buggy ones. The bug injector is trained on selecting the location and type of bug to inject to maximize the bug fixer's loss.  The initial corpus is also augmented via a number of "semantics preserving" rewrites of examples (e.g., variable renaming).  The approach is shown to outperform bug fixers that don't have the benefit of a bug injector, as well as those that don't use data augmentation. Furthermore, the approach was shown to find previously-undiscovered bugs in open-source code, as well as perform better than previous SOTA models on a manually curated test dataset of bugs.