Static analysis tools

❤️

pure FP

Jeroen Engels

@jfmengels

Elm Radio

Static Analysis Tools

elm-review

Elm

Pure FP

statically type-checked 

compiled

// linter-disable rule
// linter-disable rule
// linter-disable rule
// linter-disable rule
// linter-disable rule
// linter-disable rule
// linter-disable rule

false positives

// linter-disable rule

linters

Developers

🤬

False positive False negative
Linter / compiler Unnecessary blocking Missed
problem
False positive False negative
Linter / compiler Unnecessary blocking Missed
problem
Code optimizer Optimization bug, potential crash Missed opportunity
False positive
Linter / compiler Unnecessary blocking

Missing information

Presumptions

"acceptance of something as true   on the basis of probability​"

Incorrect presumption

array.map(function(item) {
    console.log(item);
});
const array = new NotReallyAnArray();

(array-callback-return) Array.prototype.map() expects a return value from function

Report when it

looks like an array

Report when we know it's an array

Presumptions

👎

Guarantees

👍

What guarantees do we have when analyzing source code?

FpJmybnS3yy8Ny#1kF4D6X?zgoN&TCN5H5CrM9kr
MrAfxo5L6F?KQ583fFcpp$n5z$3T#!g6e6tsjGEr
gNix&i69gQneYe#8Go@gkHSfaDd!@&XY@qHG!kL$
BftTcRJC88z!3Szqq&NChsx7D9C4LJLE&hftKksA
EoTFoxJ$nSis79zbEQGM?5ff7QCsnjoxj7pspGBo
rN6rXdxngDeDm@tlGQasFAC@g5JQ?jBHAEqK9&g!
#5?GiBXhSrfYXX@jC8SGooamhdn&Ag5Cm8$nFGj4

Abstract Syntax Trees

(a + b) / 2
Integer 2
Binary expression
    using "+"
Reference to "a"
Binary expression
    using "/"
Reference to "b"
Parenthesized

Guarantee

Analyzed code is syntactically correct

Pattern matching on the AST = Rule

a / 0
Integer 0
Reference to "a"
Binary expression
    using "/"

Rule: No division by 0

If I see a binary expression using "/" where on the right side there's an integer 0 , then report a problem.

someVariable + 1

Has the variable been defined somewhere?

const a = 1; const a = 2;
fn(tooFewOrTooManyArguments)
import "unknown-module"

Compilers

Compilers provide guarantees

👍

What if X has no compiler?

Enabled by default

*But ignorable

*

Unnecessary ESLint rules for Elm

92% (56/61) of the recommended rules

87% (228/263) of all the rules

People enable rules they don't agree with

It's hard to configure so many rules

Large use of premade configurations

someVariable + 1

Does this represent something that can be added to?

Static type checkers

(often as part of the compiler)

array.map(function(item) {
    console.log(item);
});
Array.map (\item -> {- ... -}) array

Explicit call to the target function

Guaranteed to be an Array

Rule authors

🤬

side-effects

someFunction argument =
    let
        a = func1(argument)
        
        b = func2(argument)
        
        c = func3(a, b)
    in
    func4(c)
someFunction argument =
    let
        a = func1(argument)
        
        b = func2(argument)
        
        c = func3(a, b)
    in
    func4(c)
func1 argument =
    func5(
      GLOBAL_VAR++,
      argument
    )


func2 argument =
    GLOBAL_VAR + argument

???

???

References indicate explicit dependencies on values

Side-effects create hidden dependencies on operations

Presumptions

👎

FP

Guarantees

👍

Pure FP

Functional programming

Programming without side-effects

New opportunities

Moving things around

someFunction n =
  if needToCompute(n) then
+   let
+     value =
+       expensiveComputation(n)
+   in
    use(value)

  else
    0
someFunction n =
- let
-   value =
-     expensiveComputation(n)
- in
  if needToCompute(n) then
    use(value)

  else
    0
someFunction n =
  let
    value =
      expensiveComputation(n)
  in
  if needToCompute(n) then
    use(value)

  else
    0

Pure functions

someFunction n =
  if f(n) == f(n) then
    1

  else
    0
someFunction n =
  if True then
    1

  else
    0
someFunction n =
  1
sayHello name =
  let
    neverUsed = toUpperCase(name)
  in
  "Hello " ++ format(name)
sayHello name =
  let
-   neverUsed = toUpperCase(name)
+   toUpperCase(name)
  in
  "Hello " ++ format(name)
sayHello name =
- let
-   neverUsed = toUpperCase(name)
- in
  "Hello " ++ format(name)

Dead code elimination ☠️

sayHello name =
  let
-   neverUsed = toUpperCase(name)
+   toUpperCase(name)
  in
  "Hello " ++ format(name)

Dynamic references

fnName = "unusedFn"

global[fnName](10)
unusedFn n =
  n + 1

Post-analysis

code manipulation

-- Injected at
-- compilation time
unusedFn(10)

Troublesome features

Arbitrary code execution

eval("unusedFn(10)")

Useful features vs code guarantees

Summary

Missing information

Presumptions

False positives/negatives

Distrust of the tool

Parsing guarantee correct syntax

Compilers and type checkers

remove surprises

Recreate guarantees with

a lot of linter rules

Requires premade and

opinionated configurations

Frustration

Great dead

code elimination

Code

simplifications

Pure FP greatly simplifies

different kinds of analysis

with a lot less false positives

Thank you!

 

https://slides.com/jfmengels/static-analysis-tools-love-pure-fp

Jeroen Engels

@jfmengels

Elm Radio

someFunction arg =
  let
    array = []
    
    if someCondition(arg) then
      array.push(arg)

    else if otherCond(arg) then
      array.push(0)

    else
      array.push(1)
  in
  array
someFunction arg =
  if someCondition(arg) then
    [ arg ]

  else if otherCond(arg) then
    [ 0 ]
  
  else
    [ 1 ]

With mutability

Without mutability

Static Analysis Tools Love Pure FP

By Jeroen Engels

Static Analysis Tools Love Pure FP

Talk for Lambda Days 2022 (https://www.lambdadays.org/lambdadays2022/jeroen-engels)

  • 741