Skip to content

bamless/pulsar

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pulsar: a static analyzer for the J* language

Pulsar is a static analyzer and linter for the J* language written in J*.
It can be used either as a standalone application, or through Visual Studio Code via the extension.

Supported features

The analysis of source files is broken down into multiple passes each reporting a specific class of errors. Each pass can be disabled at will, and the behaviour of some of them can be changed by passing extra command line options. To list all available options simply pass the -h flag to pulsar.

Syntax analysis pass

Or more simply, the parser. This reports syntax errors and some more semantic ones like assignment to a non-lvalue expression. This pass does exactly the same things that the J* runtime parser already does, and its main use is to obtain a correctly formed parse tree on which to apply subsequent passses. If this pass fails with an error, no other pass will be executed, as we do not support partial parsing of source code. Obviously, this pass can't be disabled.

Semantic checking pass

This pass performs the same checks that the J* runtime compiler already does, such as unpacking declarations/assignments consistency, usage of break/continue inside loops and checking that return statements are inside functions. This pass can't be disabled, as source files that don't pass these checks are not considered valid J* source files.

Variable resolution pass

This pass is responsible for checking variable declarations and their use. It makes sure that variables respect scoping rules and that are declared before usage. It also checks for possibly uninitialized variables. A variable is considered uninitialized if its declaration doesn't have an initializer and its not assigned a value in all execution paths prior to its usage. Another check that is performed is the checking of declaration before usage of static global variables.
This pass can be completely disabled by passing the -v, --no-variable-resolution option.
By passing the -g, --no-redefined-globals option, this pass will not check for redefinition of global variables, as they are technically allowed by the language.
Examples:

resolve.jsr:

var a

if x
    a = 49
else
    print("Don't initialize `a`")
end

print(a)

fun foo()
    print(bar)
end

static var bar = "bar"

output:

Analyzing resolve.jsr...
File resolve.jsr [line 3]:

if x
   ^
Cannot resolve name 'x'
File resolve.jsr [line 9]:

print(a)
      ^
Variable 'a' (declared at line 1) may be uninitialized
File resolve.jsr [line 12]:

    print(bar)
          ^~~
Static name 'bar' referenced before declaration (line 15)

Unused variables pass

This pass checks that all declared local variables (or static global ones) are actually used in the program. Global variables are not checked as they form the API of the module and are accessible from the outside. To make pulsar ignore a specific variable and not report an error, append a trailing underscore to its name.
This pass can be disabled by passing the -u, --no-unused command line option.
By passing the -a, --no-unused-args option, the pass will not warn about unused function arguments.
Examples:

unused.jsr:

fun test(arg1, arg2)
    var foo = "bar"
    print(arg1)
end

// pulsar will not warn for the unused `arg_` as its name ends with a trailing underscore
fun stub(arg_)
    raise NotImplementedException()
end

output:

Analyzing unused.jsr...
File unused.jsr [line 1]:
fun test(arg1, arg2)
               ^~~~
Name 'arg2' declared but not used
File unused.jsr [line 2]:

    var foo = "bar"
        ^~~
Name 'foo' declared but not used

Return check pass

This pass reports non-void functions that don't return a result on all possible execution paths. A function is considered non-void if it has at least one non-bare return (a return statement with an expression).
This pass can be disabled by passing the -r, --no-check-returns option.
Examples:

return.jsr:

fun foo(x)
    if !x
        return false
    end
    print(x)
end

output:

Analyzing return.jsr...
File return.jsr [line 1]:
fun foo(x)
    ^~~
Non-void function 'foo' doesn't return a value on all execution paths

Unreachable code pass

This pass checks for unreachable statements, for example statements that follow a return or a raise that is always executed.
This pass can be disabled by passing the -U, --no-unreachable option.
Example:

unreachable.jsr

fun foo(x)
    return x
    print(x)
end

fun bar(y)
    try
        y += 2
    ensure
        raise Exception()
    end
    return y
end

output:

Analyzing unreachable.jsr...
File unreachable.jsr [line 3]:

    print(x)
    ^~~~~
Unreachable statement. Previous statement breaks control unconditionally
File unreachable.jsr [line 12]:

    return y
    ^~~~~~
Unreachable statement. Previous statement breaks control unconditionally

Access checking pass

This pass checks for and enforces naming conventions for private attributes/methods and constant variables. Specifically, an attribute/method starting with an underscore (_) is considered private to the current class and can't be accessed by anything but this. Variables are instead considered constants if their names are composed only of capital letters and underscores. For constant attribute variables, we do not warn if they are assigned to in the constructor, as otherwise there won't be a way to initialize them.
This pass can be disabled by passing the -A, --no-access-check option.
Example:

access.jsr

var PI = 3.141592

class Foo
    fun new(bar)
        this._bar = bar
    end

    fun getBar()
        return this._bar
    end
end

var foo = Foo("bar")

print(foo._bar)
print(foo.getBar())

PI = 3.15
print(PI)

output:

Analyzing access.jsr...
File access.jsr [line 15]:
print(foo._bar)
          ^~~~
Accessing supposedly private attribute '_bar'
File access.jsr [line 18]:
PI = 3.15
^~
Assigning to supposedly constant name PI

How to use

Pulsar doesn't have any dependency (aside from the obvious one of J*) so using it is as simple as cloning the repository:

git clone https://github.com/bamless/pulsar.git

moving in its directory:

cd pulsar

and executing pulsar.jsr with a list of J* files to check:

jstar pulsar.jsr file1.jsr file2.jsr file3.jsr

# Or more simply on unix-like systems
./pulsar.jsr file1.jsr file2.jsr file3.jsr