<<

DLint: Dynamically Checking Bad Coding Practices in JavaScript

Liang Gong1, Michael Pradel2, Manu Sridharan3 and Koushik Sen1

1 UC Berkeley 2 TU Darmstadt 3 Samsung Research America Why JavaScript?

• The RedMonk Rankings (1st) – Based on GitHub and StackOverflow • Web assembly language • Web applications, DSL, Desktop App, Mobile App

2 … Problematic JavaScript

• Designed and Implemented in 10 days  • Not all decisions were well thought  • Problematic language features – Error prone – Inefficient code – Security loophole • Problematic features are retained – backward compatibility

3 Problematic JavaScript

4 What is coding practice? • Good coding practices • informal rules • improve quality

• Better quality means: • Less correctness issues • Better performance • Better usability • Better maintainability • Less security loopholes • Less surprises • …

5 Rule: avoid using for..in over arrays

var sum = 0, value; var array = [11, 22, 33]; for (value in array) { sum += value; } > sum ?

6 Rule: avoid using for..in over arrays

var sum = 0, value; var array = [11, 22, 33]; for (value in array) { sum += value; } > sum ?

array index 11 + 22 + 33 => 66 (not array value) 0 + 1 + 2 => 3 array index : string 0+"0"+"1"+"2" => "0012"

7 Rule: avoid using for..in over arrays

var sum = 0, value; var array = [11, 22, 33]; for (value in array) { sum += value; } > sum ?

array index 11 + 22 + 33 => 66 (not array value) 0 + 1 + 2 => 3 array index : string 0+"0"+"1"+"2" => "0012"

• Cross-browser issues > "0012indexOftoString..." • Result depends on the Array prototype object 8 Rule: avoid using for..in over arrays

var sum = 0, value; var array = [11, 22, 33]; for (value in array) { sum += value; } > sum ?

for (i=0; i < array.length; i++) { sum += array[i]; }

function addup(element, index, array) { sum += element; } array.forEach(addup); 9 Rule: avoid using for..in over arrays

var sum = 0, value; var array = [11, 22, 33]; for (value in array) { sum += value; } > sum ?

for (i=0; i < array.length; i++) { sum += array[i]; }

function addup(element, index, array) { sum += element; } array.forEach(addup); 10 Coding Practices and Lint Tools

• Existing Lint-like checkers – Inspect – Rule-based checking – Detect common mistakes – Enforce • Limitations: – Approximates behavior – Unknown aliases – Lint tools favor precision over soundness • Difficulty: Precise static

11 DLint

• Dynamic Linter checking code quality rules for JS • Open-source, robust and extensible framework • Formalized and implemented 28 rules – Counterparts of static rules – Additional rules • Empirical study – Compare static and dynamic checking

12 Jalangi: A Dynamic Analysis Framework for JavaScript Koushik Sen, Swaroop Kalasapur, Tasneem Brutch, and Simon Gibbs

a.f = b.g PutField(Read("a", a), "f", GetField(Read("b", b), "g"))

if (a.f()) ... if (Branch(Method(Read("a", a), "f")()) ...

x = y + 1 x = Write("x", Binary(‘+’,Read("y", y), Literal(1))

analysis.Literal(c) analysis.Branch(c) analysis.Read(n, x) analysis.Write(n, x) analysis.PutField(b, f, v) analysis.Binary(op, x, y) analysis.Function(f, isConstructor) analysis.GetField(b,f) analysis.Method(b, f, isConstructor) analysis.Unary(op, x) ...

13 Runtime Patterns • Single-event: Stateless checking • Multi-event: Stateful checking

14 Language Misuse

Avoid setting properties of primitives, which has no effect.

var fact = 42; fact.isTheAnswer = true; console.log(fact.isTheAnswer);

> undefined

DLint Checker Predicate: propWrite(base,*,*) Λ isPrim(base)

15 Uncommon Values

Avoid producing NaN (Not a Number).

var x = 23 – "five";

> NaN

DLint Checker Predicate: unOp(*, val, NaN) Λ val ≠ NaN binOp(*, left, right, NaN) Λ left ≠ NaN Λ right ≠ NaN call(*, *, args,NaN, *) Λ NaN ≠ args

16 Uncommon Values Avoid concatenating undefined to string.

var value; ... var str = "price: "; ... var result = str + value;

> "price: undefined"

DLint Checker Predicate: binOp(+, left, right, res) Λ (left = undefined ∨ right = undefined) Λ isString(res) 17 API Misuse

Beware that all wrapped primitives coerce to true.

var b = false; if (new Boolean(b)) { console.log("true"); }

> true

DLint Checker Predicate: cond(val) Λ isWrappedBoolean(val) Λ val.valueOf() = false

18 19 20 21 DLint Overview Jalangi

Instrumented JS program JS program DLint Checkers

Behavior Final Report Information

• Instrument SpiderMonkey to intercept JavaScript files • Transpile JavaScript code with Jalangi [Sen et al. FSE 2013] • DLint checks runtime states and find issues • Report reason and code location

22 Evaluation

Research Questions • DLint warning vs. JSHint warning? • Additional warnings from DLint? • Coding convention vs. page popularity?

Experimental Setup • 200 web sites (top 50 + others) • Comparison to JSHint

23 % of Warnings: DLint vs. JSHint

JSHint Unique Common DLint Unique

Websites

0% 20% 40% 60% 80% 100% % of warnings on each site • Some sites: One approach finds all • Most sites: Better together 24 Additional Warnings Reported by DLint

# 84 Checker shares warning with JSHint 43 Checker shares no warnings with JSHint

average. average. 22

/ site /(base site 2) 11

00 warning warning Logarithmic I5 T6 L3 T5 A2 V2 L4 A5 T1 L2 L1 A6 A8 A3 T2 A4 I1 I4 V3 L5 I2 T4 L6 A7 T3 DLint checkers

• 53 warnings per page • 49 are missed by JSHint

25 Coding Convention vs. Page Popularity Quartile 1-3 Quartile 1-3 Mean Mean

0.16 0.02

0.14 . Op. 0.0175 0.12 0.015 0.1 Cov 0.0125 0.08 0.01 Warning / LOC Warning 0.06 0.0075 Warn. / # Warn. 0.04 0.005 0.0025 JSHint 0.02 # 0 DLint 0 #

Websites traffic ranking Websites traffic ranking

Correlation between Alexa popularity and number of DLint warnings: 0.6 26 27 Liang Gong, Electric Engineering & Computer Science, University of California, Berkeley. 28 Liang Gong, Electric Engineering & Computer Science, University of California, Berkeley. 29 30 Liang Gong, Electric Engineering & Computer Science, University of California, Berkeley. Rule: avoid setting field on primitive values

From Google Octane Game Boy Emulator :

var decode64 = ""; if (dataLength > 3 && dataLength % 4 == 0) { while (index < dataLength) { decode64 += String.fromCharCode(...); } if (sixbits[3] >= 0x40) { decode64.length -= 1; } }

31 Rule: avoid setting field on primitive values

From Google Octane Game Boy Emulator benchmark:

var decode64 = ""; if (dataLength > 3 && dataLength % 4 == 0) { while (index < dataLength) { decode64 += String.fromCharCode(...); } if (sixbits[3] >= 0x40) { decode64.length -= 1; } }

No effect because decode64 is a primitive string.

32 Rule: avoid no effect operations

window.onbeforeunload= "Twitch.player.getPlayer().pauseVideo();"

window.onunload= "Twitch.player.getPlayer().pauseVideo();"

33 Rule: avoid no effect operations

window.onbeforeunload= "Twitch.player.getPlayer().pauseVideo();"

window.onunload= "Twitch.player.getPlayer().pauseVideo();"

window.onbeforeunload = function () { Twitch.player.getPlayer().pauseVideo(); }

34 Takeaways

Dynamic lint-like checking for JavaScript • Static checkers are not sufficient, DLint complements • DLint is a open-source, robust and extensible tool – Works on real-world websites – Found 19 clear bugs on most popular websites

More information: • Paper: “DLint: Dynamically Checking Bad Coding Practices in JavaScript” • Source Code: https://github.com/Berkeley-Correctness-Group/DLint • Google “DLint Berkeley”

35 Takeaways

Dynamic lint-like checking for JavaScript • Static checkers are not sufficient, DLint complements • DLint is a open-source, robust and extensible tool – Works on real-world websites – Found 19 clear bugs on most popular websites

More information: • Paper: “DLint: Dynamically Checking Bad Coding Practices in JavaScript” • Source Code: https://github.com/Berkeley-Correctness-Group/DLint • Google “DLint Berkeley” Thanks! 36 Formalization: declarative specification

1. Predicates over runtime events

• propWrite(base, name, val) • propRead(base, name, val) • cond(val) • unOp(op, val, res) • binOp(op, left, right, res) • call(base, f, args, ret, isConstr)

Example: propWrite(*, "myObject", val) | isPrim(val)

37 Missing 'new' prefix when invoking a constructor. 4933 8 eval can be harmful. 2335 26 Implied eval (string instead of function as argument). 202 15 Do not override built-in variables. 62 6 document.write can be a form of eval. 1401 167 The array literal notation [] is preferable. 416 125 The object literal notation {} is preferable. 191 62 The Function constructor is a form of eval. 359 205 Do not use Number, Boolean, String as a constructor. 74 79

0% 10% 20% 30% 40% 50% 60% 70% 80% 90% 100% Fount by JSHint Only Common with DLint

38 T5 ConstructorFunctions 833 8

L1 ArgumentsVariable 74 6

DoubleEvaluation A1 181 413

L4 Literals 77 187

WrappedPrimitives A8 31 79

0% 20% 40% 60% 80% 100% Found by Dlint Only Common with JSHint

E.g., 181 calls of eval(), Function(), etc. missed by JSHint

39 40 Liang Gong, Electric Engineering & Computer Science, University of California, Berkeley. 41 Liang Gong, Electric Engineering & Computer Science, University of California, Berkeley. 42 43 NaN case study for IKEA

$9.90 XML: Empty Element

previousPrice = JS: undefined getElementValue("pricePrevious" + suffix, normal);

parseFloat(previousPrice).toFixed(2).replace(...) .replace('.00', '')); JS: NaN

44 Type Related Checker

Avoid accessing the undefined property.

var x; // undefined var y = {}; y[x] = 23; // { undefined: 23 }

propWrite(*, "undefined",*) propRead(*, "undefined", *)

45 Chained Analysis

a.f = b.g

PutField(Read("a", a), "f", GetField(Read("b", b), "g"))

Checker-1 Chained Analysis functions functions PutField Checker-2 Read PutField functions … Read PutField Checker-n Read … … functions … PutField Read

… 46 Rule: avoid using for..in on arrays

www.google.com/chrome, included code from Modernizr:

for (i in props) { // props is an array prop = props[i]; before = mStyle.style[prop]; ... }

https://github.com/Modernizr/Modernizr/pull/1419

47 API Misuse

eval is evil, do not use eval.

var fun = eval; ... fun("var a = 1;");

call(builtin, eval, , , ) call(builtin, Function, , , ) call(builtin, setTimeout∗ ∗ ∗, args, , ) | isString(args[0]) ∗ ∗ ∗ call(builtin, setInterval, args, ∗, ∗) | isString(args[0]) call(document, write, , , ) ∗ ∗ 48 ∗ ∗ ∗ 49