Skip to content

Commit afa2e18

Browse files
author
owen-jones-diffblue
authored
Merge pull request diffblue#421 from diffblue/owen-jones-diffblue/CSVSA-tolerate-unreachable-function-calls
CSVSA: tolerate unreachable function calls
2 parents 848ff4b + 8a2294f commit afa2e18

10 files changed

+141
-5
lines changed
551 Bytes
Binary file not shown.

regression/CSVSA/TestBasic/Test.class

513 Bytes
Binary file not shown.

regression/CSVSA/TestBasic/Test.java

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package CSVSA.TestBasic;
2+
3+
public class Test {
4+
class A {
5+
public void f() {
6+
}
7+
8+
public void g() {
9+
}
10+
}
11+
12+
public void tolerate_unreachable_functions() {
13+
A a = null;
14+
a.f();
15+
a.g();
16+
}
17+
}

regression/CSVSA/TestBasic/__init__.py

Whitespace-only changes.
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from regression.CSVSA.csvsa_driver import CsvsaDriver
2+
3+
4+
folder_name = 'TestBasic'
5+
6+
7+
def test_tolerate_unreachable_functions():
8+
csvsa_driver = CsvsaDriver(folder_name).with_test_function('tolerate_unreachable_functions')
9+
with csvsa_driver.run() as csvsa_expectation:
10+
csvsa_expectation.check_successful_run()
11+
csvsa_expectation.check_does_not_match("a . f()")
12+
csvsa_expectation.check_does_not_match("a . g()")

regression/CSVSA/__init__.py

Whitespace-only changes.

regression/CSVSA/csvsa_driver.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import os
2+
3+
from regression.executable_runner import ExecutableRunner
4+
from regression.regex_expectation import RegexExpectation
5+
6+
7+
8+
class CsvsaExpectation(RegexExpectation):
9+
"""Encapsulate the output of CsvsaDriver"""
10+
11+
def __init__(self, goto_functions, return_code, cmdline):
12+
"""Get the state at the end of test_function"""
13+
RegexExpectation.__init__(self, goto_functions, return_code, compatibility_mode=False,
14+
check_verification=False, cmdline=cmdline)
15+
16+
17+
class CsvsaDriver:
18+
"""Run security scanner with CSVSA"""
19+
20+
def __init__(self, folder_name):
21+
"""Set the class path and temporary directory"""
22+
self.folder_name = folder_name
23+
self.with_test_class_name('Test')
24+
self.with_test_function('f')
25+
26+
def with_test_class_name(self, test_class_name):
27+
self.class_name = test_class_name
28+
self.class_filename = test_class_name + '.class'
29+
return self
30+
31+
def with_test_function(self, test_function_name):
32+
self.function_name = test_function_name
33+
return self
34+
35+
def run(self):
36+
"""Run CSVSA and parse the output before returning it"""
37+
fq_class_name = '.'.join(['CSVSA', self.folder_name, self.class_name])
38+
fq_function_name = fq_class_name + '.' + self.function_name
39+
executable_path = os.path.join(os.environ['SECURITY_SCANNER_HOME'], 'bin', 'security-analyzer')
40+
class_path = os.path.join('CSVSA', self.folder_name, self.class_filename)
41+
cmd = [executable_path, '--function', fq_function_name, '--lazy-methods-context-sensitive',
42+
'--show-goto-functions', class_path]
43+
44+
executable_runner = ExecutableRunner(cmd)
45+
(stdout, _, return_code) = executable_runner.run(pipe_stderr_to_stdout=True)
46+
47+
return CsvsaExpectation(stdout, return_code, " ".join(cmd))

regression/executable_runner.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ def __init__(self, cmd, timeout=600, encoding='utf-8'):
1313
self.timeout = timeout
1414
self.encoding = encoding
1515

16-
def run(self):
16+
def run(self, pipe_stderr_to_stdout=False):
1717
"""Run the executable and capture the output
1818
1919
If the process exceeds the timeout then we assert false so that the test fails.
@@ -22,13 +22,14 @@ def run(self):
2222
"""
2323
# We limit ourselves to the API available in python 3.3 to make life easier on Travis. There are API
2424
# improvements in python 3.5 and 3.6 which would make this code simpler.
25-
proc = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
25+
stderr = subprocess.STDOUT if pipe_stderr_to_stdout else subprocess.PIPE
26+
proc = subprocess.Popen(self.cmd, stdout=subprocess.PIPE, stderr=stderr)
2627
try:
2728
(stdout_data, stderr_data) = proc.communicate(timeout=self.timeout)
2829
except subprocess.TimeoutExpired:
2930
proc.kill()
3031
print('The executable ran for longer than {} seconds'.format(self.timeout))
3132
assert False
32-
return (stdout_data.decode(self.encoding),
33-
stderr_data.decode(self.encoding),
33+
return (stdout_data.decode(self.encoding) if stdout_data else None,
34+
stderr_data.decode(self.encoding) if stderr_data else None,
3435
proc.returncode)

regression/regex_expectation.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import re
2+
import sys
3+
4+
5+
class RegexExpectation:
6+
"""Encapsulate the output of CsvsaDriver"""
7+
8+
def __init__(self, text, return_code, compatibility_mode=True, check_verification=False, cmdline=""):
9+
"""Get the state at the end of test_function"""
10+
if return_code < 0:
11+
self.exit_code = 0
12+
self.signal = -return_code
13+
else:
14+
self.exit_code = return_code
15+
self.signal = 0
16+
if compatibility_mode:
17+
# This makes is easier to use this code with test.desc files written for test.pl
18+
text += "\nEXIT={}\nSIGNAL={}\n".format(self.exit_code, self.signal)
19+
self.text = text
20+
self.split_text = text.splitlines()
21+
# check_verification indicates whether a successful run should include "VERIFICATION SUCCESSFUL" in it
22+
self.check_verification = check_verification
23+
self.cmdline = cmdline
24+
25+
def check_successful_run(self):
26+
self.check_does_not_match_regex("^warning: ignoring")
27+
if self.check_verification:
28+
self.check_matches_regex("^VERIFICATION SUCCESSFUL$")
29+
assert self.exit_code == 0
30+
assert self.signal == 0
31+
32+
def check_matches(self, x):
33+
assert any(line.find(x) != -1 for line in self.split_text)
34+
35+
def check_does_not_match(self, x):
36+
assert all(line.find(x) == -1 for line in self.split_text)
37+
38+
def check_matches_regex(self, x):
39+
assert any(re.search(x, line) for line in self.split_text)
40+
41+
def check_does_not_match_regex(self, x):
42+
assert all(not re.search(x, line) for line in self.split_text)
43+
44+
def __enter__(self):
45+
return self
46+
47+
def __exit__(self, exc_type, exc_value, traceback):
48+
if exc_value is not None:
49+
print("Failure may relate to command: ", self.cmdline, file=sys.stderr)

src/driver/csvsa_specializer.cpp

+11-1
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,17 @@ goto_programt::targett csvsa_specializert::specialize_instruction(
438438
const auto &callee_map = context.get_callee_map();
439439

440440
// Replace virtual function calls with specific dispatch tables:
441+
auto child_nodes_it = callee_map.find(it->location_number);
442+
if(child_nodes_it == callee_map.end())
443+
{
444+
// This instruction was never examined by CSVSA -- it must be
445+
// unreachable.
446+
it->make_assumption(false_exprt());
447+
return it;
448+
}
449+
450+
const auto &child_nodes = child_nodes_it->second;
451+
441452
auto &function = to_code_function_call(instruction.code).function();
442453
if(function.id() == ID_virtual_function)
443454
{
@@ -487,7 +498,6 @@ goto_programt::targett csvsa_specializert::specialize_instruction(
487498
{
488499
// Static function call -- simply replace by the target specialization.
489500
auto &function_symbol = expr_dynamic_cast<symbol_exprt>(function);
490-
const auto &child_nodes = callee_map.at(it->location_number);
491501
INVARIANT(
492502
child_nodes.size() <= 1, "Non-virtual calls should have a single target");
493503
// If there are no children, this is a stub -- leave the call alone.

0 commit comments

Comments
 (0)