Authors Alan Schmitt Arthur Charguéraud Thomas Wood
License CC-BY-4.0
JSExplain: a Double Debugger for JavaScript Arthur Charguéraud Alan Schmitt Thomas Wood Inria & Université de Strasbourg, Inria & Univ Rennes, CNRS, IRISA Imperial College London CNRS, ICube UMR 7357 alan.schmitt@inria.fr thomas.wood09@imperial.ac.uk arthur.chargueraud@inria.fr ABSTRACT Indeed, in JS, the evaluation of any sub-expression, of any type We present JSExplain, a reference interpreter for JavaScript that conversion, and of most internal operations from the specification closely follows the specification and that produces execution traces. may result in the execution of user code, hence the raising of an These traces may be interactively investigated in a browser, with exception, interrupting the normal control flow. Throught its suc- an interface that displays not only the code and the state of the in- cessive editions, the ECMA standard progressively introduced a terpreter, but also the code and the state of the interpreted program. notation akin to an exception monad (§1.2). This notation is natu- Conditional breakpoints may be expressed with respect to both the rally translated into real code by a proper monadic bind operator interpreter and the interpreted program. In that respect, JSExplain of the exception monad. is a double-debugger for the specification of JavaScript. Regarding the state, the standard assumes a global state. A refer- ence interpreter could either assume a global state, modified with ACM Reference Format: side-effects, or thread the state explicitly in purely-functional style. Arthur Charguéraud, Alan Schmitt, and Thomas Wood. 2018. JSExplain: a We chose the latter approach for three reasons. First, we already Double Debugger for JavaScript. In WWW ’18 Companion: The 2018 Web Conference Companion, April 23–27, 2018, Lyon, France. ACM, New York, NY, need a monad for exceptions, so we may easily extend this monad USA, 9 pages. https://doi.org/10.1145/3184558.3185969 to also account for the state. Second, starting from code with an explicit state would make it easier to generate a corresponding inductive definition in a formal logic (e.g., Coq), which we would 1 INTRODUCTION like to investigate in the future. Third, to ease the reading, one may 1.1 A reference interpreter for JS easily hide a state that is explicitly threaded; the converse, materi- JavaScript (JS) has a complex semantics. As of 2017, its specifica- alizing a state that is implicit, would be much more challenging. tion by ECMA consists of 885 pages of English prose [6] (details We thus write our reference interpreter in a purely-functional in §1.2). Unsurprisingly, this prose-based presentation of the spec- language extended with syntactic sugar for the monadic notation ification does not meet the needs of the JavaScript designers and to account for the state and the propagation of abrupt termination implementers. In particular, the JavaScript standardization commit- (§2). For historical reasons, we chose a subset of OCaml as source tee (TC39) has repeatedly expressed the need for better tools for syntax, but other languages could be used. In fact, we implemented describing and interacting with the semantics (§1.3). a translator from our subset of OCaml to a subset of JS (a subset Prior work on the formalization of JS semantics, notably JSCert [3] involving no side effects and no type conversions). We thereby and KJS [11], has made some progress, yet falls short of delivering obtain a JS interpreter that is able to execute JS programs inside a several of the features needed by TC39 (§1.4). In this work, we JS virtual machine—JS fans should be delighted. To further improve aim at addressing these requests: we revisit the JSCert semantics accessibility to JS programmers, we also translate the source code by giving it a presentation more accessible to the JavaScript com- of our interpreter into a human-readable JS-style syntax, which we munity. Our presentation aims to be well-suited for writing and call pseudo-JS, and that essentially consists of JS syntax augmented reading the specifications, executing test cases, checking coverage, with a monadic notation and with basic pattern matching. and interatively debugging the specification (§1.5). Our reference semantics for JS is inherently executable. We may The JS specification is essentially describing a reference inter- thus execute our interpreter on test suites, either by compiling and preter. Although it consists of English prose, the ECMA standard executing the OCaml code, or by executing the JS translation of reads almost like pseudo-code. Most ambiguities and unclear para- that code. It is indeed useful to be able to check that the evaluation graphs that were present in ECMA3 and ECMA5 were progressively of examples from the JS test suites against our reference semantics resolved in subsequent editions. Thus, turning ECMA pseudo-code produces the desired output. into real code, i.e., code expressed in a real programming language, Even more interesting is the possibility to investigate, step by is not so hard. Yet, there are two nontrivial aspects: dealing with the step, the evaluation of the interpreter on a given test case. Such representation of the state, and dealing with abrupt termination, investigation allows to understand why the evaluation of a partic- such as exceptions, return, break, and continue statements. ular test case produces a particular output—given the complexity of JS, even an expert may easily get puzzled by the output value This paper is published under the Creative Commons Attribution 4.0 International of a particular piece of code. Furthermore, interactive execution (CC BY 4.0) license. Authors reserve their rights to disseminate the work on their makes it easier for the contributor of a new JS feature to add new personal and corporate Web sites with the appropriate attribution. WWW’18 Companion, April 23–27, 2018, Lyons, France test cases and to check that these tests trigger the new features and © 2018 IW3C2 (International World Wide Web Conference Committee), published correctly interact with existing features. under Creative Commons CC BY 4.0 License. In this paper, we present a tool, called JSExplain, for investigating ACM ISBN 978-1-4503-5640-4/18/04. https://doi.org/10.1145/3184558.3185969 JS executions. This tool can be thought as a double debugger, which WWW’18 Companion, April 23–27, 2018, Lyons, France Arthur Charguéraud, Alan Schmitt, and Thomas Wood displays both the state of the interpreted program and that of the Evaluation of: AdditiveExpression : AdditiveExpression + interpreter program. In particular, our tool supports conditional MultiplicativeExpression breakpoints expressed simultaneously on the interpreter program 1. Let lref be the result of evaluating AdditiveExpression. and the interpreted program. To implement this tool, we generate 2. Let lval be GetValue(lref). a version of our interpreter that is instrumented for producing 3. Let rref be the result of evaluating MultiplicativeExpression. execution traces (§3), and we provide a web-based tool to navigate 4. Let rval be GetValue(rref). through such traces (§4). As far as we know, our tool is the first such 5. Let lprim be ToPrimitive(lval). double debugger, i.e., debugger with specific additions for dealing 6. Let rprim be ToPrimitive(rval). with programs that interpret other programs (§5). 7. If Type(lprim) is String or Type(rprim) is String, then – Return the String that is the result of concatenating 1.2 English Specification of JS ToString(lprim) followed by ToString(rprim). To illustrate the style in which the JavaScript standard (ECMA) [6] 8. Return the result of applying the addition operation to is written, consider the description of addition, which will be our ToNumber(lprim) and ToNumber(rprim). running example throughout the paper. In JS, the addition operator Figure 1: ECMA5 specification of addition. casts its arguments to integers and computes their sum, except if one of the two arguments is a string, in which case the other argument is cast to a string and the two strings are concatenated. Evaluation of: AdditiveExpression : AdditiveExpression + The ECMA5 presentation (prior to June 2016) appears in Fig- MultiplicativeExpression ure 1. First, observe that the presentation describes both the parsing 1. Let lref be the result of evaluating AdditiveExpression. rule for addition and its evaluation rule. Presumably for improved 2. Let lval be GetValue(lref). accessibility, the JS standard does not make explicit the notion of 3. ReturnIfAbrupt(lval). an abstract syntax tree (AST). The semantics of addition goes as 4. Let rref be the result of evaluating MultiplicativeExpression. follows: first evaluate the left branch to a value, then evaluate the 5. Let rval be GetValue(rref). right branch to a value, then converts both values (which might 6. ReturnIfAbrupt(rval). be objects) into primitive values (e.g., string, number, ...), then test 7. Let lprim be ToPrimitive(lval). whether one of the two arguments is a string. If so, cast both ar- 8. ReturnIfAbrupt(lprim). guments to strings and return their concatenation; otherwise cast 9. Let rprim be ToPrimitive(rval). both arguments to numbers and return their sum. 10. ReturnIfAbrupt(rprim). This presentation style used in ECMA5 gives no details about 11. If Type(lprim) is String or Type(rprim) is String, then the propagation of exceptions. While the treatment of exceptions a. let lstr be ToString(lprim). is explicit for statements, it is left implicit for expressions. For b. ReturnIfAbrupt(lstr). example, if the evaluation of the left branch raises an exception, the c. let rstr be ToString(rprim). right branch should not be evaluated. It appeared that leaving the d. ReturnIfAbrupt(rstr). treatment of exceptions implicit could lead to ambiguities at what e. Return the String that is the result of concatenating lstr exactly should or should not be evaluated when an exception gets and rstr. triggered. The ECMA committee hates such ambiguities, because it 12. let lnum be ToNumber(lprim). could (and typically does) result in different browsers exhibiting 13. ReturnIfAbrupt(lnum). different behaviors—the nightmare of web-developers. 14. let rnum be ToNumber(rprim). In ECMA6, such ambiguities were resolved by making the prop- 15. ReturnIfAbrupt(rnum). agation of exceptions explicit. Figure 2 shows the updated specifi- 16. Return the result of applying the addition operation to lnum cation for the addition operator. There are two main changes. First, and rnum. each piece of evaluation is described on its own line, thereby mak- ing the evaluation order crystal clear. Second, the meta-operation Figure 2: ECMA6 specification of addition. ReturnIfAbrupt is invoked on every intermediate result. This meta- operation essentially corresponds to an exception monad. The ECMA6 standards, which aims to be accessible to a large audience, specification. As detailed in Figure 4, they define the question mark avoids the introduction of the word “monad”. Instead, it specifies symbol to be a lightweight shorthand for ReturnIfAbrupt steps. The ReturnIfAbrupt as a “macro”, as shown in Figure 3. Essentially, ev- specification of addition in that new style is shown in Figure 5. ery result consists of a “completion record”, which corresponds to The presentation of ECMA7 and ECMA8 (Figure 5) is both more a sum type distinguishing normal results from exceptions. concise than that of ECMA6 (Figure 2) and more formal than that of In all constructs except try-catch blocks, exceptions interrupt ECMA5 (Figure 1). The use of question marks is to be compared in the normal flow of the evaluation. As a result, ECMA6 specification §2 with the monadic notation that we use for our formal semantics. is scattered with about 1100 occurences of ReturnIfAbrupt opera- tions. Realizing the impracticability of that style of specification, 1.3 Requests from the JS Committee the standardization committee decided to introduce in ECMA7 an The JavaScript standardization body, part of ECMA and known additional layer of syntactic sugar in subsequent versions of the as TC39, includes representatives from browser vendors, major JSExplain: a Double Debugger for JavaScript WWW’18 Companion, April 23–27, 2018, Lyons, France Evaluation of: ReturnIfAbrupt Evaluation of: AdditiveExpression : AdditiveExpression + Algorithms steps that say MultiplicativeExpression 1. Let lref be the result of evaluating AdditiveExpression. 1. ReturnIfAbrupt(argument). 2. Let lval be ? GetValue(lref). mean the same thing as: 3. Let rref be the result of evaluating MultiplicativeExpression. 4. Let rval be ? GetValue(rref). 1. If argument is an abrupt completion, return argument. 5. Let lprim be ? ToPrimitive(lval). 2. Else if argument is a Completion Record, let argument be 6. Let rprim be ? ToPrimitive(rval). argument.[[value]]. 7. If Type(lprim) is String or Type(rprim) is String, then a. let lstr be ? ToString(lprim). Figure 3: ECMA6 interpretation of ReturnIfAbrupt. b. let rstr be ? ToString(rprim). c. Return the String that is the result of concatenating lstr Algorithms steps that say or are otherwise equivalent to: and rstr. 1. ReturnIfAbrupt(AbstractOperation()). 8. let lnum be ? ToNumber(lprim). 9. let rnum be ? ToNumber(rprim). mean the same thing as: 10. Return the result of applying the addition operation to lnum 1. Let hygienicTemp be AbstractOperation(). and rnum. 2. If hygienicTemp is an abrupt completion, return hygienicTemp. 3. Else if hygienicTemp is a Completion Record, let Figure 5: ECMA7 and ECMA8 specification of addition. hygienicTemp be hygienicTemp.[[Value]]. Where hygienicTemp is ephemeral and visible only in the steps pertaining to ReturnIfAbrupt. • a tool able to tell which lines from the specification are not covered by any test from the main test suite (test262 [15]); Invocations of abstract operations and syntax-directed operations • a tool able to execute step-by-step the specification on con- that are prefixed by ? indicate that ReturnIfAbrupt should be crete JS programs, and to inspect the value of the variables. applied to the resulting Completion Record. For example, the step: In particular, step-by-step execution is critically needed to eval- 1. ? OperationName(). uate new features. When the committee decides that a feature pro- posal is worth integrating, it typically does not accept the proposal is equivalent to the following step: as is, but instead modifies the proposal in a way that is amenable 1. ReturnIfAbrupt(OperationName()). to a simple, clear specification without corner cases, carefully try- ing to avoid harmful interactions with other existing features (or Figure 4: ECMA7 and ECMA8 addition for ReturnIfAbrupt. planned features). During this process, at some point the commit- tee members have in their hand a draft of the extended semantics as well as a collection of test cases illustrating the new behaviors actors of the web, and academics. It aims at defining a common that should be introduced. Naturally, they would like to check that semantics that all browsers should implement. TC39 faces major their formalization of the extended semantics assigns the expected challenges. On the one hand, it must ensure full backward com- behavior to each of the test cases. patibility, to avoid “breaking the web”. In particular, no feature One might argue that such a task could be performed by mod- used in the wild ever gets removed from the specification. On the ifying one of the mainstream browsers. Yet, existing JS runtimes other hand, the committee imposes the rule that no feature may are built with efficiency in mind, with huge code bases involving be added to the standard before it has been implemented, shipped, numerous optimizations. As such, modifying the code in any way and tested at scale in at least two distinct major browsers. Any is too costly for committee members to investigate variations on a member of the committee may propose new features, hence there feature request. Even if they could invest the effort, the distance are many proposals being actively developed, at different stages of between the English prose specification and the implementation formalization [14]. would be too large to have any confidence that the two match, i.e., The rapid evolution of the standard stresses the need for appro- that the behavior implemented in the code matches the behavior priate tools to assist in the rewriting, testing, and debugging of the described by the prose. semantics. In particular, several members with whom we have had An alternative approach to testing a new feature is to develop interactions expressed their need for several basic tools, such as: an elaboration (local translation) of that feature into plain JS. This • a tool for knowing whether all variables that occur in the can take the form of syntactic sugar adding a missing API, namely specification are properly defined (bound) somewhere; a polyfill, or the form of a source to source translation, namely • a tool to perform basic type-checking of the meta-functions a transpiler. For instance, one might translate so-called “template and of the variables involved in the specification; literals” into simple string concatenation. • a tool for checking that effectful operations go on a line of /* new feature */ /* plain JavaScript */ their own, to avoid ambiguity in the order of evaluation; var name = "me"; var name = "me"; • a tool for checking that the behavior is specified in all cases; `hello ${name}`; "hello " + name; WWW’18 Companion, April 23–27, 2018, Lyons, France Arthur Charguéraud, Alan Schmitt, and Thomas Wood While polyfills and transpilers are a simple approach to testing new 1.5 Contribution features, they have two major limitations. First, the encoding might In this paper, we present a tool, called JSExplain, which aims at be very invasive. For instance, the 2015 version of ECMAScript providing a formal semantics for JS that addresses the aforemen- added proxies, and as a consequence significantly changed the inter- tioned limitations of prior work. Our contribution is two-fold. First, nal methods of the language; the Babel [1] transpiler for proxies [2] we present a specification for JS expressed in a language that, we is able to simulate this feature in prior version of JS, but at the cost argue, JS programmers can easily read and write (§2). Second, we of replacing all field access operations with calls to wrapper func- present an interactive tool that supports step-by-step execution tions. Second, the interaction of several new features implemented of the specification on an input JS program (§3 and §4). Our tool using these approaches is very difficult to anticipate. mimics the features of a debugger, such as navigation controls, state and variable visualization, and conditional breakpoints, but does 1.4 Formal Specifications of JS so for both the interpreter program and the interpreted program. The language in which we display the specification consists of In recent years, two projects, JSCert [3] and KJS [11] have proposed a subset of JS extended with syntactic sugar for monads and basic a formal specification for a significant subset of JS. JSCert provides pattern matching. This language, which we call pseudo-JS, could a big-step inductive definition for ECMA5, (technically, a pretty-big- be the source syntax for our specification. However, for historical step specification [5]), formalized in the Coq proof assistant [16]. and technical reasons, we use as input syntax a subset of OCaml, JSCert comes along with a reference interpreter, called JSRef, that which is processed using the OCaml type-checker. Our current tool is proved correct with respect to the inductive definition. JSRef automatically converts the OCaml AST into pseudo-JS code. In the may be extracted into executable OCaml code for executing tests. future, we might as well have our reference interpreter be directly KJS describes a small-step semantics for JS as a set of rewriting in pseudo-JS syntax, and we could typecheck that code either by rules, using the K framework [13]. This framework has been used converting it to OCaml or by reimplementing a basic ML type- to formalize the semantics of several other real-world languages. It checker. A third alternative would be to use the Reason syntax [12], provides in particular tool support for executing (syntax-directed) a JS-like syntax for OCaml programs. The only difference between transition rules on a concrete input program. the approaches is whether TC39 committee members would prefer At first sight, it might seem that a formal specification addresses to write OCaml style or JS style code. all the requests from the committee. Definitions are thoroughly type-checked; in particular, all variables must be properly bound. Definitions, being defined in a formal language, are ambiguity- 2 SPECIFICATION LANGUAGE free; in particular, the order of evaluation and the propagation of 2.1 Constructs of the Language exceptions is precisely specified. The semantics can be executed on concrete input programs; moreover, with some extra tooling, one The input syntax in which we write and display the specification is may execute a set of programs and report on the coverage of the a purely-functional language that includes the following constructs: specification by the tests. variables, constants, sequence, conditional, let-binding, function Given all the nice features of formal semantics, why wouldn’t definition, function application (with support for prefix and infix the standardization committee TC39 consider one of these formal functions), data constructors, records (including record projections, semantics as the reference for the language? After discussing with and the “record-with” construct to build a copy of a record with senior members from TC39, we understand that there are (at least) a number of fields updated), tuples (i.e., anonymous records), and three main reasons why there is no chance for a formal semantics simple pattern matching (only with non-nested patterns, restricted such as JSCert or KJS to be adopted as reference semantics. to data constructors, constants, variables, and wildcards). For con- venience, let-bindings and functions may bind patterns (as opposed (1) Formal specifications in Coq or in K use syntax and concepts to only variables). that are not easily accessible to JS programmers. Yet, the We purposely aim for a specification language with a limited specification is meant to be read by a wide audience. number of constructs and a very standard semantics, to minimize (2) These formal languages have a cost of entry that is too high the cost of entry into that language. Note that the input code is type- for committee members to reach the level of proficiency checked in ML. (Polymorphism is used mainly for type-checking required for contributing new definitions all by themselves. options and lists, and operations on them.) (3) JSCert and KJS come with specifications that can be executed, As explained earlier (§1.2), the semantics involves the propa- yet provide no debugger-style support for interactively nav- gation of exceptions and other abrupt behaviors (break, continue, igating through an execution and for visualizing the state and return). Their propagation can be described within our small and the values of the variables, and thus do not help so much language, using functions and pattern matching. Nevertheless, in- in tuning the description of new features. troducing a little bit of syntactic sugar greatly improves read- ability. For example, we write “let%run x = e1 in e2” to mean In the present work, we temporarily leave aside the motivation “if_run e1 (fun x -> e2)”, where if_run is a function that imple- of giving a formal semantics to JS that one could use to formalize ments our monad. properties of the language (e.g., security properties), and rather The monadic operator if_run admits a polymorphic type, hence focus on trying to provide a formal semantics that meets better the functions from the specification may return objects of various types. day-to-day needs of the TC39 committee. Nevertheless, in practice most functions from the ECMA standard JSExplain: a Double Debugger for JavaScript WWW’18 Companion, April 23–27, 2018, Lyons, France and run_binary_op s c op v1 v2 = match op with | C_binary_op_add -> run_binary_op_add s c v1 v2 ... and run_binary_op_add s0 c v1 v2 = let%prim (s1, w1) = to_primitive_def s0 c v1 in let%prim (s2, w2) = to_primitive_def s1 c v2 in if (type_compare (type_of (Coq_value_prim w1)) Coq_type_string) || (type_compare (type_of (Coq_value_prim w2)) Coq_type_string) then let%string (s3, str1) = to_string s2 c (Coq_value_prim w1) in let%string (s4, str2) = to_string s3 c (Coq_value_prim w2) in res_out (Coq_out_ter (s4, (res_val (Coq_value_prim (Coq_prim_string (strappend str1 str2)))))) else let%number (s3, n1) = to_number s2 c (Coq_value_prim w1) in let%number (s4, n2) = to_number s3 c (Coq_value_prim w2) in res_out (Coq_out_ter (s4, (res_val (Coq_value_prim (Coq_prim_number (n1 +. n2)))))) Figure 6: Current input syntax of our specification language: a subset of pure OCaml, extended with monadic notation. are described as returning a “completion triple”, which either de- var run_binary_op = function (op, v1, v2) { scribe abrupt termination or describe a value. In a number of cases, switch (op) { case C_binary_op_add: the value is in fact constrained to be of a particular type. For exam- return (run_binary_op_add(v1, v2)); ple, if to_number produces a value, then this value is necessarily a ... } number. The standard exploits this invariant implicitly in formula- }; tion such as “let n be the number produced by calling to_number”. var run_binary_op_add = function (v1, v2) { In constrast, our code needs to explicitly project the number from var%prim w1 = to_primitive_def(v1); the value returned. To that end, we introduce specialized monads var%prim w2 = to_primitive_def(v2); such as if_number, written in practice “let%number n = e1 in e2”. if ((type_compare(type_of(w1), Type_string) (An alternative approach would be to assign polymorphic types || type_compare(type_of(w2), Type_string))) { to completion triples, however following this route would require var%string str1 = to_string(w1); diverging slightly from ECMA’s specification in a number of places.) var%string str2 = to_string(w2); return strappend(str1, str2); Figure 6 shows the specification of addition in our reference } else { interpreter, in OCaml syntax extended with the monadic notation. var%number n1 = to_number(w1); This code implements its informal equivalent from Figure 2. In var%number n2 = to_number(w2); that code, s denotes the state, c denotes the environment (variable return (n1 + n2); and lexical environment, in JS terminology), op corresponds to the } operator (here, the constructor C_binary_op_add corresponds to the }; AST token describing the operator +), v1 and v2 corresponds to the arguments, and w1 and w2 to their primitive values. The func- Figure 7: Generated code for the interpreter in pseudo-JS tion strappend denote string concatenation, whereas “+.” denotes syntax, with implicit environments, state, and casts. addition on floating pointer numbers (i.e., JS’s numbers). First observe that, as explained earlier (§1.1), the state is threaded throughout the code. We show in the next section how to hide the state variables (§2.2). Observe also that the code also relies on a Figure 6) is well-suited as a non-ambiguous input language. Note few auxiliary functions. The function type_compare implements that this code may be compiled using OCaml’s compiler in order to comparison over JS types—to keep our language small and explicit, run test cases; the current version of our interpreter passes more we do not want to assume a generic comparison function with than 5000 test cases from the official test suite (test262). nontrivial specification. The functions to_primitive_def, to_string, and to_number are internal functions from the specification that 2.2 Translation into Pseudo-JS Syntax implement conversions. These operations might end up evaluating Although we believe that it is a desirable feature to have a source arbitrary user code, and thus could perform side-effects or raise langage fully explicit, there is also virtue in pretty-printing the exceptions, hence the need to wrap them in monadic let-bindings. source code of our interpreter in a more concise syntax. The “noise” One important feature of this source language is that it does that appears in the formal specification (e.g. Figure 6) comes from not involve any “implicit” mechanism. All type conversions are three main sources: explicit in the code, so it is always perfectly clear what is meant. In particular, there is no need to type-check the code to figure out (1) every function takes as argument the environment; its semantics. In summary, the OCaml code of the interpreter (e.g., (2) every function takes as argument and returns a description of the mutable state (a.k.a. heap); WWW’18 Companion, April 23–27, 2018, Lyons, France Arthur Charguéraud, Alan Schmitt, and Thomas Wood (3) values are typically built using numerous constructors, e.g. Figure 8 illustrates the output of translating from our OCaml C_value_prim, which lifts a number (an OCaml value of type subset towards JS. Note that this code is not meant for human float) into a JS value (an OCaml value of type value). consumption. We implement monadic operators as function calls, Fortunately, we can easily eliminate these three sources of noises. introduce the return keyword where necessary, encode sum types First, the environment is almost always passed unchanged. It as object literals with a tag field, encode tuples as arrays (encoding may be modified only during the scope of a function call, a with tuples as object literals would work too), turn constructor appli- construct, or a block. When it is modified, new bindings are simply cations into functions calls, implement pattern matching by first pushed into the environment (which behaves like a stack), and switching on the tag field then binding fresh variables to denote subsequently popped. Thus, we may assume, like the ECMA speci- the arguments of constructors. fication does, that the environment is stored in a global state. This We thus obtain an executable JS interpreter in JS which, like our saves the need to pass an argument called “c” around. JS interpreter in OCaml, may be used for executing test cases. One Second, the description of the mutable state is threaded through interest of the JS version is that it may be easily executed inside the code. The “current state” is passed as argument to every function a browser, a set up that might be more convenient for a number that might perform side-effects, and, symmetrically, the “updated of users. One limitation, however, is that the number of steps that state” is returned to the caller, which binds a fresh name for it. can be simulated may be limited on JS virtual machines that do Considering that there is only one version of the state at any given not optimize tail-recursive function calls. Indeed, the execution point of an execution, we may assume, like the ECMA specification of monadic code involves repeated calls to continuations, whose does, that state to be stored in a global variable. This saves the need (tail-call) invocation unnecessarily grows the call stack. to pass values called “s1”, “s2”... around. To set up our interactive debugger, we produce, from our OCaml Third, the presence of many constructors is due to the need for source code, an instrumented version of the JS translation. This casts. Many of these casts could, however, be viewed as “implicit instrumented code produces execution traces as a result of inter- casts” (or “coercions”, in Coq’s terminology). For a carefully chosen preting an input JS program. These traces store information about set of casts, defined once and for all, and for a well-typed program all the states that the interpreter goes through. In particular, each with implicit casts, there exists a unique (non-ambiguous) way to event in the trace provides information about the code pointer and insert casts in order to make the program type-check. Although we the instantiations of local variables from the interpreter code. have previously argued that explicit casts are useful, as they allow More precisely, we log events at every entry point of a function, giving a semantics that does not depend on type-checking, we now every exit point, and on every variable binding. Each event captures argue that it may also be useful to pretty-print the code assuming the state, the stack, and the values of all local variables in scope of implicit casts, in order to improve readability. the interpreter code at the point where the event gets triggered. To In summary, we propose to the reader of the specification a reduce noise in the trace, we only log events in the core code of the version that features implicit state, implicit context, and implicit interpreter, and not the code from the auxiliary libraries. Overall, an casts. Given that we are playing the game of pretty-printing syntax, execution of the instrumented interpreter on some input JS program we take the opportunity to switch along the way to a JS-friendly produces an array of events. This array can then be investigated syntax, using brackets and semicolons. This target language, called using our double debugger (§4). pseudo-JS syntax, consists of a subset of the JS syntax, extended Figure 9 shows an example snippet of code, giving an idea of the with monadic notation, and an extended switch construct that is mechanisms at play. Note, again, that this code is not meant for hu- able to bind variables (like OCaml’s pattern matching, but restricted man consumption. The function log_event augments the trace. Con- to non-nested patterns for simplicity). sider for instance log_event("Main.js", 4033, ctx_747, "enter"). The pretty-printing of the addition operator in pseudo-JS syntax The first two arguments identify the position in the source file, as a appears in Figure 7. To illustrate our extended pattern matching file name and a unique token used to recover the line numbers. The syntax feature of pseudo-JS, we show below an excerpt from the third argument is a context describing values of the local variables, main switch that interprets an expression. and the fourth argument describes the type of event. When investigating the trace, we need to be able to highlight the switch (t) { corresponding line of the interpreter code. We wish to be able to do case Coq_expr_identifier(x): so for the three versions of the interpreter code: the OCaml version, var%run r = identifier_resolution(x); return (r); the pseudo-JS version, and the plain JS version. To implement this case Coq_expr_binary_op(e1, op, e2): ... feature, our generator, when processing the OCaml source code, also produces a table that maps, for each version and for each file 3 TRACE-PRODUCING EXECUTIONS of the interpreter, event tokens to line numbers. JSExplain is a tool for interactively investigating execution traces The contexts stored in events are extended each time a function is of our JS interpreter executing example JS programs. The interface entered, a new variable is declared, or the function returns (so as to consists of a web page [8] that embeds a JS parser and a trace- capture the returned value). Contexts are represented as a purely- producing version of our interpreter implemented in standard JS. functional linked list of mappings between variable names and So far, we have shown how to translate the OCaml source into values. This representation maximizes sharing and thus minimize pseudo-JS syntax (§2.2). In this section, we explain how to translate the memory footprint of the generated trace. The length of the trace the OCaml source into proper JS syntax, and then how to instrument grows linearly with the number of execution steps performed. For the JS code in a systematic way for producing execution traces. example, the simple program “var i = 0; while (i < N) { i++ }” JSExplain: a Double Debugger for JavaScript WWW’18 Companion, April 23–27, 2018, Lyons, France var run_binary_op_add = function (s0, c, v1, v2) { return (if_prim(to_primitive_def(s0, c, v1), function(s1, w1) { return (if_prim(to_primitive_def(s1, c, v2), function(s2, w2) { if ((type_compare(type_of(Coq_value_prim(w1)), Coq_type_string()) || type_compare(type_of(Coq_value_prim(w2)), Coq_type_string()))) { return (if_string(to_string(s2, c, Coq_value_prim(w1)), function(s3, str1) { return (if_string(to_string(s3, c, Coq_value_prim(w2)), function(s4, str2) { return (res_out(Coq_out_ter(s4, res_val( Coq_value_prim(Coq_prim_string(strappend(str1, str2))))))); }));})); } else { ... }})); })); }; Figure 8: Snippet of generated code for the interpreter in standard JS syntax, without trace instrumentation. var run_binary_op_add = function (s0, c, v1, v2) { var ctx_747 = ctx_push(ctx_empty, [{key: "s0", val: s0}, {key: "c", val: c}, {key: "v1", val: v1}, {key: "v2", val: v2}]); log_event("JsInterpreter.js", 4033, ctx_747, "enter"); var _return_1719 = if_prim((function () { log_event("JsInterpreter.js", 3985, ctx_747, "call"); var _return_1700 = to_primitive_def(s0, c, v1); log_event("JsInterpreter.js", 3984, ctx_push(ctx_747, [{key: "#RETURN_VALUE#", val: _return_1700}]), "return"); return (_return_1700); }()), function(s1, w1) { ... }); log_event("JsInterpreter.js", 4028, ctx_push(ctx_748, [{key: "#RETURN_VALUE#", val: _return_1718}]), "return"); return (_return_1718); }); log_event("JsInterpreter.js", 4032, ctx_push(ctx_747, [{key: "#RETURN_VALUE#", val: _return_1719}]), "return"); return (_return_1719); }; Figure 9: Snippet of generated code for the interpreter in standard JS syntax, with trace instrumentation. AST of JS. We instrument the JS code to produce a trace of events. This Esprima interpreted interpreted compilation is done ahead of time and depicted by solid arrows. program program When hitting the run button, the flow depicted by the dotted arrows occurs. The web page parses the code from the text area, compiler using the Esprima library [7]. This parser produces an AST, with web page Libraries nodes annotated with locations. This AST is then provided as input Interpreter (JS) to the instrumented interpreter, which generates a trace of events. and libraries trace This trace may then be inspected and navigated interactively. Interpreter (OCaml) (anno- For a given event from the execution trace, our interface high- tated JS) lights the corresponding piece of code from the interpreter, and annotating shows the values of the local variables, as illustrated in Figure 11. It compiler also highlights the corresponding piece of code in the interpreted program, as illustrated at the top of Figure 13, and displays the state Figure 10: Architecture of JSExplain. and the environment of the program at that point of the execution, as illustrated in Figure 12. generates a trace of size 2 990 for N = 1, of size 14 166 for N = 10, Recovering the information about the interpreted code is not of size 126 126 for N = 100, and of size 1 245 726 for N = 1000. completely straightforward. For example, to recover the fragment The fact that these numbers are large reflects the fact that the of code to highlight, we find in the trace the closest previous event reference interpreter is inherently vastly inefficient, as it follows that contains a call to function with an argument named _term_. the specification faithfully, without any optimization. Due to our This argument corresponds to the AST of a subexpression, and use of functional data structures, the memory footprint of the trace this AST is decorated (by the parser) with locations. Note that, for should be linear in the length of the trace. We have not observed efficiency reasons, we associate to each event from the trace its the memory footprint to be a limit, but if it were we could more corresponding _term_ argument during a single pass, performed carefully select which events should be stored. immediately after the trace is produced. Similarly, we are able to recover the state and environment asso- 4 JSEXPLAIN: A DOUBLE DEBUGGER FOR JS ciated with the event. The state of the interpreted program consists The global architecture of JSExplain is depicted in Figure 10. Starting of four fields: the strictness flag, the value of the this keyword, from our JS interpreter in OCaml, we generate a JS interpreter in WWW’18 Companion, April 23–27, 2018, Lyons, France Arthur Charguéraud, Alan Schmitt, and Thomas Wood Figure 11: Display of the variables from the interpreter code Figure 13: Example of a conditional breakpoint, constrain- ing the state of both the interpreter and the interpreted code. a change in the location on the subexpression evaluated in the interpreted code, and source cursor finds the last event in the trace for which the associated subexpression contains the active cursor in the “source program” text area. The aforementioned tools are sufficient for simple explorations of the trace, yet we have found that it is sometimes useful to reach events at which specific conditions occur, such as being at a specific line in the interpreter, in the interpreted code, with variables from the interpreter or interpreted code having specific values. We thus provide a text box to enter arbitrary breakpoint conditions to be evaluated on events from the trace. For example, the condition in Figure 13 reaches the next occurrence of a call to run_binary_op_add in a context where the source variable j has value 1. The break point condition may be any JS expression using the following API: I_line() returns the current line of the interpreter, S_line() returns Figure 12: Display of the state and environment of the inter- the current line of the source, I('x') returns the value of x in the preted code. The environment includes the local variables. interpreter, S_raw('x') returns the value of x in the source (e.g. the JS object {tag: "value_number", arg: 5}), and S('x') returns the the lexical environment, and the variable environment. We imple- JS interpretation of the value of x in the source (e.g. the JS value 5). mented a custom display for these elements, and also for values of the languages, in particular for objects: one may click on an object 5 RELATED WORK to reveal its contents and recursively explore it. There are many formal semantics of JavaScript, from pen-and-paper We provide several ways to navigate the trace. First, we provide ones [10], to the aforementioned JSCert [3] and KJS [11]. As de- buttons for reaching the beginning or end of the execution, and but- scribed in §1.4, these semantics are admirable but lack crucial fea- tons for stepping one event at a time. Second, we provide, similarly tures to be actively used in the standardization effort. to debuggers, next and previous buttons for skipping function calls, To our knowledge, the closest work to the double-debugger as well as a finish button to reach the end of the current function. approach is the multi-level debugging approach of Kruck et al. [9]. These features are implemented by navigating the trace, keeping They present a debugger for an interpreter for domain-specific track of the number of enter and return events. Third, we provide languages that lets developers choose the level of abstraction at buttons for navigation based not on steps related to the interpreter which they debug their program. An abstraction is a way to display program but instead based on steps of the interpreted program: some values (encoded in the host language, or as present in the DSL) source previous and source next find the closest event which induces as well as showing only stack frames that represent computation JSExplain: a Double Debugger for JavaScript WWW’18 Companion, April 23–27, 2018, Lyons, France at the DSL level. Our technique is more general as it does not focus the possibility of automatically generating pretty-big-step [5] defini- on domain-specific languages. tions from the reference semantics expressed in our small language, In fact, our double-debugger approach could be easily adapted possibly using some amount of annotation to guide the process. to interpreter for other languages than JS. To that end, it suffices to (4) To close even further the gap between a formal language and implemented an interpreter for the desired language in the subset the English prose, we could also investigate the possibility of auto- of OCaml that we support, and to provide code for extracting and matically generating English sentences from the code. Indeed, the displaying the term and state associated with a given event. We prose from the ECMAScript standard is written in such a systematic have recently followed that approach and adapted our framework manner that this should be doable, at least to some extent. to derive a double-debugger for (a significant subset of) the OCaml programming language. ACKNOWLEDGMENTS Regarding the translation from OCaml to JS that we imple- We acknowledge funding from the ANR project AJACS ANR-14- ment, one might consider using an existing, general-purpose tool. CE28-0008 and the CominLabs project SecCloud. Js_of_ocaml [17] converts OCaml bytecode into efficient JS code. Presumably, we could implement the logging instrumentation as an REFERENCES OCaml source-to-source translation and then invoke Js_of_ocaml. [1] 2017. Babel. (October 25, 2017). https://babeljs.io/. Yet, with that approach, we would need to convert the representa- [2] 2017. Babel proxy plugin. (October 25, 2017). https://www. tion of trace events from the encoding of these values performed npmjs.com/package/babel-plugin-proxy. by Js_of_ocaml into proper JS objects that we can display in the [3] Martin Bodin, Arthur Charguéraud, Daniele Filaretti, Philippa interactive interface. This conversion is nontrivial, as some infor- Gardner, Sergio Maffeis, Daiva Naudziuniene, Alan Schmitt, mation, such as the name of constructors, is lost in the process. As and Gareth Smith. 2014. A trusted mechanised javascript we already implemented a translator from OCaml to pseudo-JS, it specification. In Proceedings of POPL 2014, 87–100. was simpler to implement a translator from OCaml to plain JS. [4] 2017. Bucklescript. (October 27, 2017). https://bucklescript. Another translator from OCaml to JS is Bucklescript [4], which github.io/bucklescript/. was released after we started our work. Similarly to our transla- [5] Arthur Charguéraud. 2013. Pretty-big-step semantics. In Pro- tor, Bucklescript converts OCaml code into JS code advertised as ceedings of ESOP 2013 (LNCS). Volume 7792. Springer, 41– readable. Bucklescript also has the limitation that the names of con- 60. structors are lost, although presumably this could be easily fixed. [6] ECMA. 2017. Ecmascript 2017 language specification (ecma- Besides, if we wanted to revisit our implementation to base it on 262, 8th edition). (June 2017). https://www.ecma-international. Bucklescript, for trace generation we would need to either modify org/ecma-262/8.0/index.html. Bucklescript, which is quite complex as it covers the full OCaml [7] 2017. ECMAScript parsing infrastructure for multipurpose language, or to reimplement trace instrumentation at the OCaml analysis. (October 26, 2017). http://esprima.org/. level, which should be doable yet would involve a bit more work [8] 2017. JSExplain. (October 26, 2017). https://jscert.github.io/ than at the level of untyped JS code. jsexplain/branch/master/driver.html. [9] Bastian Kruck, Stefan Lehmann, Christoph Keßler, Jakob 6 CONCLUSION AND FUTURE WORK Reschke, Tim Felgentreff, Jens Lincke, and Robert Hirschfeld. We presented JSExplain to TC391 and the committee expressed 2016. Multi-level debugging for interpreter developers. In strong interest. They would like us to extend our specification to MODULARITY (Companion). ACM, 91–93. cover all of the specification. We have almost finished the formaliza- [10] Sergio Maffeis, John C. Mitchell, and Ankur Taly. 2008. An tion of proxies, which are a challenging addition to the language as operational semantics for javascript. In Proceedings of APLAS they change many internal methods. Although all members seem 2008 (LNCS). Volume 5356. Springer, 307–325. to agree that the current toolset for developing the specification is [11] Daejun Park, Andrei Stefanescu, and Grigore Rosu. 2015. KJS: inappropriate, it requires a strong leadership and a consensus to a complete formal semantics of javascript. In Proceedings of commit to a new toolchain. Our goal is to cover the current version PLDI 2015. ACM, 346–356. of ECMAScript, we currently cover ECMAScript 5, and to help [12] 2017. Reason. (October 27, 2017). https://reasonml.github.io/. committee members use it to formalize new additions to JavaScript. [13] Grigore Rosu and Traian-Florin Serbanuta. 2010. An overview There are numerous directions for future work. (1) We plan to set of the K semantic framework. Journal of Logic and Algebraic up a modular mechanism for describing unspecified behaviors (e.g. Programming, 79, 6, 397–434. “for-in” enumeration order) as well as browser-specific behaviors [14] 2017. TC39 proposals. (October 26, 2017). https://github.com/ (sometimes browsers deviate from the specification, for historical tc39/proposals. reasons). (2) We could investigate the possibility of extending the [15] 2017. Test262. (October 26, 2017). https://github.com/tc39/ formalization of the standard by also covering the parsing rules of test262. JS; currently, our semantics is expressed with respect to the AST of [16] The Coq development team. 2014. The Coq proof assistant the input program. (3) To re-establish a link with the original JSCert reference manual. Version 8.4. http://coq.inria.fr. inductive definition, which is useful for conducting formal proofs [17] Jérôme Vouillon and Vincent Balat. 2014. From bytecode to about the metatheory of the langage, we would like to investigate javascript: the js_of_ocaml compiler. Software: Practice and 1 https://tc39.github.io/tc39-notes/2016-05_may-25.html#jsexplain-as--tw Exprerience, 44, 8, 951–972.