Skip to content

Commit

Permalink
merge with updated master
Browse files Browse the repository at this point in the history
  • Loading branch information
kelloggm committed Jan 5, 2023
2 parents 65fac19 + 18df446 commit e722240
Show file tree
Hide file tree
Showing 25 changed files with 326 additions and 257 deletions.
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.
24 changes: 12 additions & 12 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 @@ -25,17 +25,11 @@ Dependencies
============

* Python 3

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

Of course, you will also need to have installed:

* 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
2 changes: 1 addition & 1 deletion dljc
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

import do_like_javac

do_like_javac.run()
do_like_javac.run()
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
1 change: 0 additions & 1 deletion do_like_javac/capture/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from . import ant, gradle, javac, mvn
import itertools

capture_modules = [ant, gradle, javac, mvn]

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
14 changes: 7 additions & 7 deletions do_like_javac/command.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import json
import os
import pprint
from . import arg
from . import log
from . import tools
from . import cache
import os,json,sys
import sys

from . import arg, cache, log, tools

def output_json(filename, obj):
with open(filename, 'w') as f:
Expand All @@ -24,8 +24,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)

TOOLS = {
# 'soot' : soot,
Expand All @@ -19,7 +9,6 @@
'inference' : infer,
'print' : jprint,
'randoop' : randoop,
'randoop_old': randoop_old,
'bixie' : bixie,
'graphtool' : graphtools,
'chicory' : chicory,
Expand All @@ -34,7 +23,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
3 changes: 2 additions & 1 deletion do_like_javac/tools/bixie.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import copy
import os

from . import common
import copy

argparser = None

Expand Down
Loading

0 comments on commit e722240

Please sign in to comment.