Skip to content

Commit

Permalink
Merge branch 'master' of github.com:ChrisTimperley/Kaskara
Browse files Browse the repository at this point in the history
  • Loading branch information
ChrisTimperley committed Jun 9, 2020
2 parents 3b475ce + 8ddfb99 commit 128d100
Show file tree
Hide file tree
Showing 12 changed files with 517 additions and 74 deletions.
94 changes: 88 additions & 6 deletions lib/kaskara/spoon/analyser.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
# -*- coding: utf-8 -*-
__all__ = ('SpoonAnalyser',)

from typing import Iterator
from typing import Any, Iterator, Mapping, Sequence
import contextlib
import json
import os
import shlex
import subprocess

from dockerblade import DockerDaemon as DockerBladeDockerDaemon
from loguru import logger
import attr

from .analysis import SpoonFunction, SpoonStatement
from .post_install import IMAGE_NAME as SPOON_IMAGE_NAME
from ..analyser import Analyser
from ..analysis import Analysis
from ..container import ProjectContainer
from ..core import FileLocationRange
from ..functions import ProgramFunctions
from ..loops import ProgramLoops
from ..project import Project
from ..statements import ProgramStatements


@attr.s
Expand All @@ -22,15 +31,29 @@ class SpoonAnalyser(Analyser):
@contextlib.contextmanager
def _container(self, project: Project) -> Iterator[ProjectContainer]:
"""Provisions an ephemeral container for a given project."""
create = self._dockerblade.client.containers.create
launch = self._dockerblade.client.containers.run
with contextlib.ExitStack() as stack:
docker_project = create(project.image)
stack.callback(docker_project.remove, force=True)
# create a temporary volume from the project image
volume_name = 'kaskaraspoon'
cmd_create_volume = (f'docker run --rm -v {volume_name}:'
f'{shlex.quote(project.directory)} '
f'{project.image} /bin/true')
cmd_kill_volume = f'docker volume rm {volume_name}'
logger.debug(f'created temporary volume [{volume_name}] '
f'from project image [{project.image}] '
f'via command: {cmd_create_volume}')
subprocess.check_output(cmd_create_volume, shell=True)
stack.callback(subprocess.call, cmd_kill_volume,
shell=True,
stderr=subprocess.DEVNULL,
stdout=subprocess.DEVNULL,
stdin=subprocess.DEVNULL)

docker_analyser = launch(SPOON_IMAGE_NAME, '/bin/sh',
stdin_open=True,
volumes_from=[docker_project.id],
volumes={volume_name: {
'bind': '/workspace',
'mode': 'ro'}},
detach=True)
stack.callback(docker_analyser.remove, force=True)

Expand All @@ -43,4 +66,63 @@ def analyse(self, project: Project) -> Analysis:
return self._analyse_container(container)

def _analyse_container(self, container: ProjectContainer) -> Analysis:
raise NotImplementedError
dir_source = '/workspace'
dir_output = '/output'
container.shell.check_output(f'kaskara {dir_source} -o {dir_output}')

# load statements
filename_statements = os.path.join(dir_output, 'statements.json')
statements_dict = json.loads(container.files.read(filename_statements))
statements = self._load_statements_from_dict(statements_dict)

# load functions
filename_functions = os.path.join(dir_output, 'functions.json')
functions_dict = json.loads(container.files.read(filename_functions))
functions = self._load_functions_from_dict(functions_dict)

# load loops
filename_loops = os.path.join(dir_output, 'loops.json')
loops_dict = json.loads(container.files.read(filename_loops))
loops = self._load_loops_from_dict(loops_dict)

# find insertion points
insertions = statements.insertions()

return Analysis(project=container.project,
loops=loops,
functions=functions,
statements=statements,
insertions=insertions)

def _load_statements_from_dict(self,
dict_: Sequence[Mapping[str, Any]]
) -> ProgramStatements:
"""Loads the statement database from a given dictionary."""
logger.debug('parsing statements database')
statements = \
ProgramStatements([SpoonStatement.from_dict(d) for d in dict_])
logger.debug(f'parsed {len(statements)} statements')
return statements

def _load_functions_from_dict(self,
dict_: Sequence[Mapping[str, Any]]
) -> ProgramFunctions:
"""Loads the function database from a given dictionary."""
logger.debug('parsing function database')
functions = \
ProgramFunctions([SpoonFunction.from_dict(d) for d in dict_])
logger.debug(f'parsed {len(functions)} functions')
return functions

def _load_loops_from_dict(self,
dict_: Sequence[Mapping[str, Any]]
) -> ProgramFunctions:
"""Loads the loops database from a given dictionary."""
logger.debug('parsing loop database')
loop_bodies: List[FileLocationRange] = []
for loop_info in dict_:
loc = FileLocationRange.from_string(loop_info['body'])
loop_bodies.append(loc)
loops = ProgramLoops.from_body_location_ranges(loop_bodies)
logger.debug(f'parsed loops')
return loops
46 changes: 46 additions & 0 deletions lib/kaskara/spoon/analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
__all__ = ('SpoonStatement',)

from typing import Any, FrozenSet, Mapping, Optional

import attr

from ..core import FileLocationRange
from ..functions import Function
from ..statements import Statement


@attr.s(frozen=True, slots=True, auto_attribs=True)
class SpoonFunction(Function):
name: str
location: FileLocationRange
body_location: FileLocationRange
return_type: str

@staticmethod
def from_dict(dict_: Mapping[str, Any]) -> 'SpoonFunction':
name: str = dict_['name']
location = FileLocationRange.from_string(dict_['location'])
body_location = FileLocationRange.from_string(dict_['body'])
return_type = dict_['return-type']
return SpoonFunction(name, location, body_location, return_type)


@attr.s(frozen=True, auto_attribs=True, slots=True)
class SpoonStatement(Statement):
kind: str
content: str
canonical: str
location: FileLocationRange

@staticmethod
def from_dict(dict_: Mapping[str, Any]) -> 'SpoonStatement':
kind: str = dict_['kind']
content: str = dict_['source']
canonical: str = dict_['canonical']
location = FileLocationRange.from_string(dict_['location'])
return SpoonStatement(kind, content, canonical, location)

@property
def visible(self) -> Optional[FrozenSet[str]]:
return None
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package christimperley.kaskara;

import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.reference.CtTypeReference;

/**
* Describes a function within a given project.
*/
public class Function {
@JsonProperty("name")
private final String name;
@JsonSerialize(converter = SourcePositionSerializer.class)
@JsonProperty("location")
private final SourcePosition location;
@JsonSerialize(converter = SourcePositionSerializer.class)
@JsonProperty("body")
private final SourcePosition bodyLocation;
private final CtTypeReference<?> returnType;

/**
* Constructs a function description for a given Clang AST method element.
* @param element The AST element for the method.
* @return A description of the given AST element.
*/
public static Function forSpoonMethod(CtMethod<?> element) {
var name = element.getSimpleName();
var location = element.getPosition();
var body = element.getBody();
var bodyLocation = body.getPosition();
var returnType = element.getType();
return new Function(name, location, bodyLocation, returnType);
}

/**
* Constructs a function description.
* @param name The name of the function.
* @param location The location of the function definition.
* @param bodyLocation The location of the body of the function definition.
* @param returnType The return type of the function.
*/
public Function(String name,
SourcePosition location,
SourcePosition bodyLocation,
CtTypeReference<?> returnType) {
this.name = name;
this.location = location;
this.bodyLocation = bodyLocation;
this.returnType = returnType;
}

@JsonGetter("return-type")
public String getReturnType() {
return this.returnType.getQualifiedName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package christimperley.kaskara;

import java.util.ArrayList;
import java.util.List;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.visitor.filter.AbstractFilter;

/**
* Provides an interface for finding all function definitions within a given project.
*/
public class FunctionFinder {
private final Project project;

public static FunctionFinder forProject(Project project) {
return new FunctionFinder(project);
}

protected FunctionFinder(Project project) {
this.project = project;
}

/**
* Finds all function declarations within the associated project.
* @return A list of all functions within the associated project.
*/
public List<Function> find() {
var elements = this.project.getModel().getElements(new AbstractFilter<CtMethod>() {
@Override
public boolean matches(CtMethod element) {
// function must have body
if (element.getBody() == null) {
return false;
}
// function must appear in file
return element.getPosition().isValidPosition();
}
});

List<Function> functions = new ArrayList<>();
for (var element : elements) {
var function = Function.forSpoonMethod(element);
functions.add(function);
}
return functions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package christimperley.kaskara;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import spoon.reflect.code.CtLoop;
import spoon.reflect.cu.SourcePosition;

/**
* Describes a control-flow loop within a given project.
*/
public class Loop {
@JsonSerialize(converter = SourcePositionSerializer.class)
@JsonProperty("body")
private final SourcePosition bodyLocation;

/**
* Constructs a description for a given Clang AST loop element.
* @param element The AST element for the loop.
* @return A description of the given AST element.
*/
public static Loop forSpoonLoop(CtLoop element) {
var bodyLocation = element.getBody().getPosition();
return new Loop(bodyLocation);
}

protected Loop(SourcePosition bodyLocation) {
this.bodyLocation = bodyLocation;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package christimperley.kaskara;

import java.util.ArrayList;
import java.util.List;
import spoon.reflect.code.CtLoop;
import spoon.reflect.visitor.filter.AbstractFilter;

/**
* Provides an interface for finding all loops within a given project.
*/
public class LoopFinder {
private final Project project;

public static LoopFinder forProject(Project project) {
return new LoopFinder(project);
}

protected LoopFinder(Project project) {
this.project = project;
}

/**
* Finds all loops within the associated project.
* @return A list of all loops within the associated project.
*/
public List<Loop> find() {
var elements = this.project.getModel().getElements(new AbstractFilter<CtLoop>() {
@Override
public boolean matches(CtLoop element) {
// loop must have body
if (element.getBody() == null) {
return false;
}
// loop must appear in file
return element.getPosition().isValidPosition();
}
});

List<Loop> loops = new ArrayList<>();
for (var element : elements) {
loops.add(Loop.forSpoonLoop(element));
}
return loops;
}
}
Loading

0 comments on commit 128d100

Please sign in to comment.