Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge SRI Commits #22

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
96 changes: 96 additions & 0 deletions Extending.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
Extending DLJC
==================

## Supporting a new build system

Create a new Python file in `do_like_javac/capture/` named after the build system you
want to support. Start with this template:

```python
from . import generic

supported_commands = [...]

class MyCapture(generic.GenericCapture):
def __init__(self, cmd, args):
super(MyCapture, self).__init__(cmd, args)
self.build_cmd = [...]

def get_javac_commands(self, verbose_output):
return []

get_target_jars(self, verbose_output):
return []

def gen_instance(cmd, args):
return MyCapture(cmd, args)
```

Where supported\_commands is a list of commands that might be used to invoke the
build system (e.g. "gradle", "gradlew", "ant", "maven").

For the constructor, cmd is a sys.argv style list containing the build command
the user supplied at the command line. For example, if they wrote

dljc -t print -- mybuildsystem -arg1 -arg2 -arg3

then cmd would contain `['mybuildsystem', '-arg1', '-arg2', '-arg3']`. You
should set self.build_cmd to be a version of that command that invokes the build
system with flags that give you the verbose output you'll need to parse the
javac commands from.

`args` is the argparse structure that dljc stores its own arguments in. You can
find more details in `do_like_javac/arg.py`.

Your job now is to implement `get_javac_commands` and optionally
`get_target_jars`. verbose\_output contains a list of lines of output from
running the build system. If your build system outputs entire javac commands
on a single line, or if you can reconstitute the commands in their entirety,
then there's a convenience method called `javac_parse` defined in the
superclass, which expects a list of words in the command and produces the
output you need.

If not, the ouput format is a list of javac command dicts, which are of the
form:

```python
{
'java_files': ['Foo.java', 'Bar.java', ...],
'javac_switches': {
# Switches have the - prefix removed.
# Value is either the argument given to the switch,
# or True for no-argument flags.
'classpath': ...,
...
}
}
```

`get_target_jars` returns a list of paths to jar files output by the build.

Finally, add your new module to the `capture_modules` list at the top of
`do_like_javac/capture/__init__.py`.

## Adding a new analysis tool

Create a new Python file in `do_like_javac/tools/` named after the tool you
want to add. Use this template:

```python
argparser = None

def run(args, javac_commands, jars):
print("My tool results.")
```

`args` is the argparse structure created by DLJC. `argparser` is where you can
plug in additional command line options you'd like to be available for your
tool. You can see an example in `do_like_javac/tools/infer.py`.

`javac_commands` and `jars` are the structures discussed above, consisting of
the javac command used to build the project and the jar files generated (if
that information is available).

After augmenting `run()` to do what you want, add your tool to the `TOOLS`
dictionary at the top of `do_like_javac/tools/__init__.py`, along with a
`from . import mytoolname` statement.
10 changes: 0 additions & 10 deletions Makefile

This file was deleted.

28 changes: 14 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ analysis tools, including:

* [Randoop](https://randoop.github.io/randoop/)
* [Bixie](http://sri-csl.github.io/bixie/)
* [Checker Framework](http://types.cs.washington.edu/checker-framework/)
* the [Checker Framework](https://checkerframework.org/)

`do-like-javac` supports projects built with:

Expand All @@ -24,18 +24,12 @@ project, selecting "Export" and choosing the "Ant Buildfiles" option.
Dependencies
============

* Python 3.5+

That's it. No other external dependencies for the core `do-like-javac` scripts.

Of course, you will also need to have installed:

* Python 3
* The analysis tool(s) you want to run.
* Any build dependencies of the project you're analyzing.

`do-like-javac` was built and tested on Mac OS X and GNU/Linux. It probably also
works on Microsoft Windows, but the method of invocation is probably different and
we provide no support.
`do-like-javac` was built and tested on Mac OS X and GNU/Linux. It may also
work on Microsoft Windows, but we provide no support.

Installation
============
Expand Down Expand Up @@ -69,15 +63,21 @@ If you're running checking tools, there are a couple more flags that may be
helpful. `--quiet` suppresses output from tools, and `--timeout <seconds>`
kills any tool subcommand that runs longer than `<seconds>`.

Extending
===========

Instructions for adding support for new tools or build systems to DLJC can be
found in [Extending.md](./Extending.md).

Caching
=======

`do-like-javac` can only extract data from a full compile of a project. That means
if you want to re-run it with new arguments or different analysis tools, you will
have to clean and fully re-compile your project. To save time and shortcut this
have to clean and fully re-compile the project. To save time and shortcut this
process, we save a cache of the results in the output directory. If you want `dljc`
to use this cache, simply add the `--cache` flag and the cache (if available) will
be used instead of recompiling your project.
be used instead of recompiling the project.

**IMPORTANT NOTE**: We don't do any sort of cache invalidation or freshness checking.
If you add new files to your project and want `dljc` to pick up on them, you will have
Expand All @@ -101,7 +101,7 @@ The Bixie tool will run your project through [Bixie](http://sri-csl.github.io/bi
Dyntrace
---------

The Dyntrace tool will run your project through Randoop to generate tests, then run those tests with Daikon/Chicory to generate invariants. You will need to provide a library directory with the following jars using the `--lib` option:
The Dyntrace tool will run your project through Randoop to generate tests, then run those tests with Daikon/Chicory to generate likely invariants. You will need to provide a library directory with the following jars using the `--lib` option:

* randoop.jar
* junit-4.12.jar
Expand Down Expand Up @@ -133,7 +133,7 @@ mechanism; from the javac documentation:
This tool also supports some other tool-specific optional arguments:
* `--stubs /path/to/stubs` tells the checker to run with the specified stub files.
* `--ajava /path/to/ajava` tells the checker to run with the specified ajava files.
* `--jdkVersion 8/11` tells the Checker Framework to run using JDK8 or JDK11.
* `--jdkVersion 8/11/17` tells the Checker Framework to run using JDK8, JDK11, or JDK17..
* `--quals /path/to/qual.jar` tells the Checker Framework where to find qualifiers (annotations) to put on the classpath.
* `--extraJavacArgs='-AcustomArg1 -AcustomArg2'` passes the given arguments to invocations of `javac` that run
a Checker Framework checker as an annotation processor (i.e., the arguments are NOT passed to the `javac` used while
Expand Down
Empty file removed delete-me
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this adding an empty file? Why?

Empty file.
9 changes: 6 additions & 3 deletions do_like_javac/arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
import os
import sys

from . import tools
from . import capture
from . import capture, tools

DEFAULT_OUTPUT_DIRECTORY = os.path.join(os.getcwd(), 'dljc-out')

Expand All @@ -30,8 +29,10 @@ def __call__(self, parser, namespace, values, option_string=None):
default=DEFAULT_OUTPUT_DIRECTORY, dest='output_directory',
action=AbsolutePathAction,
help='The directory to log results.')

base_group.add_argument('--log_to_stderr', action='store_true',
help='''Redirect log messages to stderr instead of log file''')

base_group.add_argument('-t', '--tool', metavar='<tool>',
action='store',default=None,
help='A comma separated list of tools to run. Valid tools: ' + ', '.join(tools.TOOLS))
Expand All @@ -51,7 +52,9 @@ def __call__(self, parser, namespace, values, option_string=None):
help='''Use the dljc cache (if available)''')

base_group.add_argument('-c', '--checker', metavar='<checker>',
action='store',
action='store',
# do not run the NullnessChecker by default
# default='NullnessChecker',
help='A checker to check (for checker/inference tools)')

base_group.add_argument('--stubs', metavar='<stubs>',
Expand Down
1 change: 1 addition & 0 deletions do_like_javac/cache.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
import pickle


def retrieve(cmd, args, capturer):
cache_file = os.path.join(args.output_directory, "dljc.cache")

Expand Down
22 changes: 17 additions & 5 deletions do_like_javac/capture/generic.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,28 @@
import os
import zipfile
import timeit
import zipfile

import do_like_javac.tools.common as cmdtools


def is_switch(s):
return s != None and s.startswith('-')
def is_switch_first_part(s):
return s != None and s.startswith('-') and ("=" not in s)

## brought this from github.com/kelloggm/do-like-javac
def is_switch_first_part(s):
return s != None and s.startswith('-') and ("=" not in s)

def get_entry_point(jar):
class_pattern = "Main-Class:"

with zipfile.ZipFile(jar, 'r') as zip:
metadata = str.splitlines(zip.read("META-INF/MANIFEST.MF").decode("utf-8"))
metadata = []
try:
metadata = str.splitlines(zip.read("META-INF/MANIFEST.MF").decode("utf-8"))
except TypeError as e:
print(f"ERROR: unable to read META-INF/MANIFEST.MF. See: {e}")
for line in metadata:
if class_pattern in line:
content = line[len(class_pattern):].strip()
Expand Down Expand Up @@ -61,10 +71,12 @@ def capture(self):
stats = {}

start_time = timeit.default_timer()
result = cmdtools.run_cmd(self.build_cmd)
stats['build_time'] = result['time']
result = cmdtools.run_cmd(self.build_cmd, self.args)
# stats['build_time'] = result['time']
stats['build_time'] = timeit.default_timer() - start_time

with open(os.path.join(self.args.output_directory, 'build_output.txt'), 'w') as f:
build_out_file = os.path.join(self.args.output_directory, 'build_output.txt')
with open(build_out_file, 'w') as f:
f.write(result['output'])

if result['return_code'] != 0:
Expand Down
3 changes: 2 additions & 1 deletion do_like_javac/capture/gradle.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
# additional grant of patent rights can be found in the PATENTS_Facebook file
# in the same directory.

from . import generic
import os

from . import generic

supported_commands = ['gradle', 'gradlew']

def gen_instance(cmd, args):
Expand Down
1 change: 1 addition & 0 deletions do_like_javac/capture/mvn.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import os
import re

from . import generic

supported_commands = ['mvn', 'mvnw']
Expand Down
11 changes: 4 additions & 7 deletions do_like_javac/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
import pprint
import sys

from . import arg
from . import cache
from . import log
from . import tools
from . import arg, cache, log, tools


def output_json(filename, obj):
with open(filename, 'w') as f:
Expand All @@ -27,9 +25,8 @@ def main():
sys.exit(1)

javac_commands, jars, stats = result

if len(javac_commands) == 0:
raise Exception("command.main: no javac commands found by capturer:\n cmd = {}\n args = {}".format(cmd, args))
if not javac_commands or len(javac_commands) == 0:
raise ValueError(f"no javac commands found by capturer:\n\tcmd = {cmd}\n\targs = {args}")

log.info('Results: %s', pprint.pformat(javac_commands))
output_json(os.path.join(args.output_directory, 'javac.json'), javac_commands)
Expand Down
5 changes: 2 additions & 3 deletions do_like_javac/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@
# additional grant of patent rights can be found in the PATENTS_Facebook file
# in the same directory.

import logging
import os
import sys
import platform
import shutil
import logging
import sys

FORMAT = '[%(levelname)s] %(message)s'
LOG_FILE = 'toplevel.log'
Expand Down
17 changes: 3 additions & 14 deletions do_like_javac/tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
from . import jprint
from . import randoop
from . import randoop_old
from . import bixie
from . import graphtools
from . import chicory
from . import dyntrace
from . import dyntracecounts

# import soot
from . import check
from . import infer
from . import wpi
from . import (bixie, check, chicory, dyntrace, dyntracecounts, graphtools,
infer, jprint, randoop, wpi)

from . import testminimizer

Expand All @@ -21,7 +11,6 @@
'inference' : infer,
'print' : jprint,
'randoop' : randoop,
'randoop_old': randoop_old,
'bixie' : bixie,
'graphtool' : graphtools,
'chicory' : chicory,
Expand All @@ -37,7 +26,7 @@ def check_tool(tool):
if tool in TOOLS:
return tool
else:
print("ERROR: Could not find tool {}".format(tool))
print(f"ERROR: Could not find tool {tool}")
return None

def parse_tools(tools):
Expand Down
2 changes: 1 addition & 1 deletion do_like_javac/tools/bixie.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
import copy
import os

from . import common
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is repeated again below.


Expand Down
Loading