Skip to content

Commit

Permalink
Merge branch 'main' into semantics-preserving-code-coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesLee-Jones committed Jul 16, 2024
2 parents a08de50 + 6914b01 commit 585bfd9
Show file tree
Hide file tree
Showing 357 changed files with 6,286 additions and 692 deletions.
7 changes: 2 additions & 5 deletions .github/workflows/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ case "$(uname)" in
# The C++ code generated by Dredd may require C++20.
export DREDD_EXTRA_CXX_ARGS="-std=c++20"
export DREDD_EXTRA_C_ARGS="-std=c17"
DREDD_SKIP_COPY_EXECUTABLE=1 ./scripts/check_single_file_tests.sh
;;

"Darwin"*)
Expand Down Expand Up @@ -179,11 +178,10 @@ case "$(uname)" in
rm test/single_file/structured_binding.cc
rm test/single_file/structured_binding.cc.expected
rm test/single_file/structured_binding.cc.noopt.expected

# The C++ code generated by Dredd may require recent C/C++ support.
export DREDD_EXTRA_CXX_ARGS="-std=c++20"
export DREDD_EXTRA_C_ARGS="-std=c17"
DREDD_SKIP_COPY_EXECUTABLE=1 ./scripts/check_single_file_tests.sh
;;

"MINGW"*|"MSYS_NT"*)
Expand Down Expand Up @@ -239,8 +237,6 @@ case "$(uname)" in
# The C code generated by Dredd may require recent C/C++ support.
export DREDD_EXTRA_C_ARGS="/std:c17"
export DREDD_EXTRA_CXX_ARGS="/std:c++20"

DREDD_SKIP_COPY_EXECUTABLE=1 ./scripts/check_single_file_tests.sh
;;

*)
Expand All @@ -249,4 +245,5 @@ case "$(uname)" in
;;
esac

DREDD_SKIP_COPY_EXECUTABLE=1 ./scripts/check_single_file_tests.sh
DREDD_SKIP_COPY_EXECUTABLE=1 ./scripts/check_execute_tests.py
4 changes: 2 additions & 2 deletions .github/workflows/c_apps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ pushd curl
FILES+=("${f}")
done

"${DREDD_EXECUTABLE}" $DREDD_SEMANTICS_PRESERVING --mutation-info-file temp.json -p "build/compile_commands.json" "${FILES[@]}"
"${DREDD_EXECUTABLE}" $DREDD_SEMANTICS_PRESERVING --mutation-info-file temp.json -p "build" "${FILES[@]}"
pushd build
ninja
# TODO: run some tests
Expand Down Expand Up @@ -133,7 +133,7 @@ pushd zstd
do
FILES+=("${f}")
done
"${DREDD_EXECUTABLE}" $DREDD_SEMANTICS_PRESERVING --mutation-info-file temp.json -p "temp/compile_commands.json" "${FILES[@]}"
"${DREDD_EXECUTABLE}" $DREDD_SEMANTICS_PRESERVING --mutation-info-file temp.json -p "temp" "${FILES[@]}"
# Build mutated zstd
make clean
CFLAGS=-O0 make zstd-release
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/cxx_apps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ pushd examples/math
cp -r math math-original
cmake -S . -B build -G Ninja -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
cmake --build build
${DREDD_EXECUTABLE} $DREDD_SEMANTICS_PRESERVING -p build/compile_commands.json --mutation-info-file mutation-info.json math/src/*.cc
${DREDD_EXECUTABLE} $DREDD_SEMANTICS_PRESERVING -p build --mutation-info-file mutation-info.json math/src/*.cc
./build/mathtest/mathtest
NUM_MUTANTS=`python3 ${DREDD_ROOT}/scripts/query_mutant_info.py mutation-info.json --largest-mutant-id`
EXPECTED_NUM_MUTANTS=1132
Expand Down Expand Up @@ -125,7 +125,7 @@ pushd examples/threaded
[[ -e "$f" ]] || break
FILES+=("${DREDD_ROOT}/examples/threaded/${f}")
done
${DREDD_EXECUTABLE} $DREDD_SEMANTICS_PRESERVING --mutation-info-file temp.json -p "${DREDD_ROOT}/examples/threaded/build/compile_commands.json" "${FILES[@]}"
${DREDD_EXECUTABLE} $DREDD_SEMANTICS_PRESERVING --mutation-info-file temp.json -p "${DREDD_ROOT}/examples/threaded/build" "${FILES[@]}"
cmake --build build
# Check that the application runs correctly and that there are no data races.
TSAN_OPTIONS=halt_on_error=1 ./build/threaded > threaded_output.txt
Expand Down Expand Up @@ -155,14 +155,14 @@ pushd SPIRV-Tools
[[ -e "$f" ]] || break
FILES+=("${DREDD_ROOT}/SPIRV-Tools/${f}")
done
${DREDD_EXECUTABLE} $DREDD_SEMANTICS_PRESERVING --mutation-info-file mutation-info.json -p "${DREDD_ROOT}/SPIRV-Tools/build/compile_commands.json" "${FILES[@]}"
${DREDD_EXECUTABLE} $DREDD_SEMANTICS_PRESERVING --mutation-info-file mutation-info.json -p "${DREDD_ROOT}/SPIRV-Tools/build" "${FILES[@]}"
cmake --build build --target test_val_abcde test_val_capability test_val_fghijklmnop test_val_limits test_val_rstuvw
./build/test/val/test_val_abcde
./build/test/val/test_val_capability
./build/test/val/test_val_fghijklmnop
./build/test/val/test_val_rstuvw
NUM_MUTANTS=`python3 ${DREDD_ROOT}/scripts/query_mutant_info.py mutation-info.json --largest-mutant-id`
EXPECTED_NUM_MUTANTS=67998
EXPECTED_NUM_MUTANTS=68841
if [ ${NUM_MUTANTS} -ne ${EXPECTED_NUM_MUTANTS} ]
then
echo "Found ${NUM_MUTANTS} mutants when mutating the SPIR-V validator source code. Expected ${EXPECTED_NUM_MUTANTS}. If Dredd changed recently, the expected value may just need to be updated, if it still looks sensible. Otherwise, there is likely a problem."
Expand All @@ -185,10 +185,10 @@ pushd llvm-project
[[ -e "$f" ]] || break
FILES+=("${DREDD_ROOT}/llvm-project/${f}")
done
${DREDD_EXECUTABLE} $DREDD_SEMANTICS_PRESERVING --mutation-info-file mutation-info.json -p "${DREDD_ROOT}/llvm-project/build/compile_commands.json" "${FILES[@]}"
${DREDD_EXECUTABLE} $DREDD_SEMANTICS_PRESERVING --mutation-info-file mutation-info.json -p "${DREDD_ROOT}/llvm-project/build" "${FILES[@]}"
cmake --build build --target LLVMInstCombine
NUM_MUTANTS=`python3 ${DREDD_ROOT}/scripts/query_mutant_info.py mutation-info.json --largest-mutant-id`
EXPECTED_NUM_MUTANTS=97675
EXPECTED_NUM_MUTANTS=98042
if [ ${NUM_MUTANTS} -ne ${EXPECTED_NUM_MUTANTS} ]
then
echo "Found ${NUM_MUTANTS} mutants when mutating the LLVM source code. Expected ${EXPECTED_NUM_MUTANTS}. If Dredd changed recently, the expected value may just need to be updated, if it still looks sensible. Otherwise, there is likely a problem."
Expand Down
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ You can also enable multiple mutants by setting the environment variable to a co
To clean up and restore the file `pi.cc` to it's initial state, run
```
rm examples/simple/pi
rm temp.json
git checkout HEAD examples/simple/pi.cc
```

Expand Down Expand Up @@ -131,12 +130,16 @@ All tests should pass.
To mutate all of the `.cc` files in the library use the following command:

```
${DREDD_EXECUTABLE} -p build/compile_commands.json math/src/*.cc --mutation-info-file mutant-info.json
${DREDD_EXECUTABLE} -p build math/src/*.cc --mutation-info-file mutant-info.json
```

The `-p` option allows the compilation database generated by CMake above to be passed to Dredd.
The `-p` option allows the compilation database generated by CMake above to be passed to Dredd: the `compile_commands.json` file in `build` will be used as a compilation database.
This is so that Dredd knows the correct compiler options to use when processing each source file.

The optional `--mutation-info-file` argument is used to specify a JSON file to which Dredd will output
machine-readable information about the mutations it applied. We explain how the `mutant-info.json` file created
via the `--mutation-info-file` argument can be used to query the mutants that Dredd has introduced.

You can run `git status` to see which files have changed, and `git diff` to see
the effect that Dredd has had on these files. These changes will be hard to understand as they are not intended to be
readable by humans.
Expand Down Expand Up @@ -210,7 +213,7 @@ rm -rf examples/math-original

## Building Dredd from source

The following instructions have been tested on Ubuntu 20.04.
The following instructions have been tested on Ubuntu 22.04.

### Prerequisites

Expand Down
9 changes: 8 additions & 1 deletion scripts/check_one_execute_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,29 @@
dredd_result = subprocess.run(cmd)
if dredd_result.returncode != 0:
print("Dredd failed.")
print(dredd_result.stdout.decode('utf-8'))
print(dredd_result.stderr.decode('utf-8'))
sys.exit(1)

# Compile the mutated program.
COMPILER = os.environ['CXX'] if test_is_cxx else os.environ['CC']
cmd = [COMPILER, f'harness.{extension}', f'tomutate.{extension}', '-o', 'test_executable']
EXTRA_COMPILER_ARGS = (os.environ.get('DREDD_EXTRA_CXX_ARGS', '') if test_is_cxx else os.environ.get('DREDD_EXTRA_C_ARGS', '')).split()
cmd = [COMPILER] + EXTRA_COMPILER_ARGS + [f'harness.{extension}', f'tomutate.{extension}', '-o', 'test_executable']
compile_result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if compile_result.returncode != 0:
print("Error compiling mutated file.")
print(compile_result.stdout.decode('utf-8'))
print(compile_result.stderr.decode('utf-8'))
print("Content of mutated file:")
print(open(f'tomutate.{extension}', 'r').read())
sys.exit(2)

# Run the compiled mutated program with no mutants enabled, and check the result is as expected.
cmd = [os.getcwd() + os.sep + 'test_executable']
original_result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if original_result.returncode != 0:
print("Error running non-mutated executable")
print(original_result.stdout.decode('utf-8'))
print(original_result.stderr.decode('utf-8'))
sys.exit(3)
actual_original_output = original_result.stdout.decode('utf-8').strip()
Expand All @@ -88,6 +94,7 @@
largest_mutant_id_result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
if largest_mutant_id_result.returncode != 0:
print("Error finding largest mutant id.")
print(largest_mutant_id_result.stdout.decode('utf-8'))
print(largest_mutant_id_result.stderr.decode('utf-8'))
sys.exit(5)
largest_mutant_id = int(largest_mutant_id_result.stdout.decode('utf-8'))
Expand Down
31 changes: 14 additions & 17 deletions scripts/query_mutant_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,19 @@ class MutantInfo:
instance: Optional[Dict]


def build_mapping_for_node(mutation_tree_node: Dict, file_info: Dict, result: Dict[int, MutantInfo]) -> None:
for mutation_group in mutation_tree_node["mutationGroups"]:
assert len(mutation_group) == 1
key: str = next(iter(mutation_group))
if key in ["replaceExpr", "replaceUnaryOperator", "replaceBinaryOperator"]:
for instance in mutation_group[key]["instances"]:
result[instance["mutationId"]] = MutantInfo(key, file_info, mutation_group, instance)
else:
assert key == "removeStmt"
result[mutation_group[key]["mutationId"]] = MutantInfo(key, file_info, mutation_group, None)
for child in mutation_tree_node["children"]:
build_mapping_for_node(child, file_info, result)


def build_mutant_to_node_mapping(json_info: Dict) -> Dict[int, MutantInfo]:
result: Dict[int, MutantInfo] = {}
for file_info in json_info["infoForFiles"]:
build_mapping_for_node(file_info["mutationTreeRoot"], file_info, result)
for mutation_tree_node in file_info["mutationTree"]:
for mutation_group in mutation_tree_node["mutationGroups"]:
assert len(mutation_group) == 1
key: str = next(iter(mutation_group))
if key in ["replaceExpr", "replaceUnaryOperator", "replaceBinaryOperator"]:
for instance in mutation_group[key]["instances"]:
result[instance["mutationId"]] = MutantInfo(key, file_info, mutation_group, instance)
else:
assert key == "removeStmt"
result[mutation_group[key]["mutationId"]] = MutantInfo(key, file_info, mutation_group, None)
return result


Expand Down Expand Up @@ -311,7 +306,8 @@ def main() -> int:
type=Path)
parser.add_argument("--largest-mutant-id",
help="Show the largest id among all mutants that are present. Mutants are normally numbered "
"contiguously starting from 0, so this is related to the total number of mutants.",
"contiguously starting from 0, so this is related to the total number of mutants. "
"Prints -1 if there are no mutants. ",
action='store_true')
parser.add_argument("--show-info-for-mutant",
help="Show information about a given mutant",
Expand All @@ -323,7 +319,8 @@ def main() -> int:
mapping: Dict[int, MutantInfo] = build_mutant_to_node_mapping(json_info)

if args.largest_mutant_id:
print(max(list(mapping)))
mutant_ids: List[int] = list(mapping.keys())
print(-1 if not mutant_ids else max(mutant_ids))
return 0

if args.show_info_for_mutant is not None:
Expand Down
10 changes: 8 additions & 2 deletions src/libdredd/include/libdredd/protobufs/dredd.proto
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,18 @@ message MutationInfo { repeated MutationInfoForFile info_for_files = 1; }

message MutationInfoForFile {
string filename = 1;
MutationTreeNode mutation_tree_root = 2;

// A mutation tree is represented in a flat manner as a list of nodes.
// Children of a given node are identified via indices into this list.
repeated MutationTreeNode mutation_tree = 2;
}

message MutationTreeNode {
repeated MutationGroup mutation_groups = 1;
repeated MutationTreeNode children = 2;

// The children of a node are identified via indices into the flat list
// of nodes that represents the mutation tree.
repeated uint32 children = 2;
}

message MutationGroup {
Expand Down
20 changes: 20 additions & 0 deletions src/libdredd/include/libdredd/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

#include "clang/AST/ASTContext.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ParentMapContext.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/Type.h"
#include "clang/Basic/SourceLocation.h"
#include "clang/Basic/SourceManager.h"
Expand Down Expand Up @@ -146,6 +148,24 @@ std::string TypeToUpperLimit(const clang::BuiltinType* type,

std::string TypeToLowerLimit(const clang::BuiltinType* type,
const clang::ASTContext& ast_context);

// It is often necessary to ask whether a given statement (which includes
// expressions) has a parent of a given type. This helper returns nullptr if
// the given statement has no parent of the template parameter type, and
// otherwise returns the first parent that does have the template parameter
// type.
template <typename RequiredParentT>
const RequiredParentT* GetFirstParentOfType(const clang::Stmt& stmt,
clang::ASTContext& ast_context) {
for (const auto& parent : ast_context.getParents(stmt)) {
const auto* candidate_result = parent.template get<RequiredParentT>();
if (candidate_result != nullptr) {
return candidate_result;
}
}
return nullptr;
}

} // namespace dredd

#endif // LIBDREDD_UTIL_H
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,11 @@ class MutateAstConsumer : public clang::ASTConsumer {
[[nodiscard]] std::string GetMutantTrackingDreddPreludeC(
int initial_mutation_id) const;

std::optional<protobufs::MutationTreeNode> ApplyMutations(
const MutationTreeNode& mutation_tree_node, int initial_mutation_id,
void ApplyMutations(
const MutationTreeNode& dredd_mutation_tree_node, int initial_mutation_id,
clang::ASTContext& context,
protobufs::MutationInfoForFile& protobufs_mutation_info_for_file,
protobufs::MutationTreeNode& protobufs_mutation_tree_node,
std::unordered_set<std::string>& dredd_declarations,
std::unordered_set<std::string>& dredd_macros, bool build_tree);

Expand Down
22 changes: 15 additions & 7 deletions src/libdredd/include_private/include/libdredd/mutate_visitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,13 @@ class MutateVisitor : public clang::RecursiveASTVisitor<MutateVisitor> {
return start_location_of_first_function_in_source_file_;
}

// Yields the C++ constant-sized arrays, whose size expressions need to be
// rewritten.
[[nodiscard]] const std::vector<const clang::VarDecl*>&
GetConstantSizedArraysToRewrite() const {
return constant_sized_arrays_to_rewrite_;
}

private:
// Helper class that uses the RAII pattern to support pushing a new mutation
// tree node on to the stack of mutation tree nodes used during visitation,
Expand Down Expand Up @@ -169,13 +176,10 @@ class MutateVisitor : public clang::RecursiveASTVisitor<MutateVisitor> {

void UpdateStartLocationOfFirstFunctionInSourceFile();

// It is often necessary to ask whether a given statement (which includes
// expressions) has a parent of a given type. This helper returns nullptr if
// the given statement has no parent of the template parameter type, and
// otherwise returns the first parent that does have the template parameter
// type.
template <typename RequiredParentT>
const RequiredParentT* GetFirstParentOfType(const clang::Stmt& stmt) const;
// Mutating an enum constant can be problematic when the enum constant is used
// to implicitly construct a C++ object. This helper method allows detecting
// this special case, so that it can be ignored.
bool IsConversionOfEnumToConstructor(const clang::Expr& expr) const;

const clang::CompilerInstance* compiler_instance_;
bool optimise_mutations_;
Expand Down Expand Up @@ -222,6 +226,10 @@ class MutateVisitor : public clang::RecursiveASTVisitor<MutateVisitor> {
// tracked, and mutations are not applied to expression nodes whose start
// location is one of these locations.
std::set<clang::SourceLocation> var_decl_source_locations_;

// This records C++ constant-sized array declarations, so that size
// expressions can be rewritten with the integers to which they evaluate.
std::vector<const clang::VarDecl*> constant_sized_arrays_to_rewrite_;
};

} // namespace dredd
Expand Down
Loading

0 comments on commit 585bfd9

Please sign in to comment.