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