File size: 7,772 Bytes
01dee4e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 | import os
import re
import json
import shutil
import argparse
import subprocess
from tqdm import tqdm
from multiprocessing import Process, current_process
import pydot
import networkx as nx
from networkx.readwrite import json_graph
from utils.json_utils import save_jsonl, load_jsonl
# Global configuration for file paths and directories
PROJECT_ROOT = os.getcwd()
OUTPUT_BASE_DIR = f"{PROJECT_ROOT}/datasets/processed/primevul"
OUTPUT_CHUNKS_DIR = f"{OUTPUT_BASE_DIR}/data_chunks/cpg"
OUTPUT_GRAPHS_DIR = f"{OUTPUT_BASE_DIR}/data_graphs/cpg"
# Joern tool paths
JOERN_PARSE_PATH = os.path.join(PROJECT_ROOT, "utils/joern-cli/joern-parse")
JOERN_EXPORT_PATH = os.path.join(PROJECT_ROOT, "utils/joern-cli/joern-export")
def process_data(chunk_index):
# Get the process ID for naming the output files and temporary directories
process_id = current_process().name
# Read the data samples
data_samples = []
with open(input_jsonl_path, "r") as file:
for i, line in enumerate(file):
if i < chunk_index[0]: # Skip lines before the start index
continue
if i >= chunk_index[1]: # Stop reading after the end index
break
try: # Load the JSON object within chunk_index
data_samples.append(json.loads(line.strip()))
except json.JSONDecodeError as e:
print(f"Error decoding JSON: {e}")
# Create a list to store the graphs
raw_graphs = []
processed_graphs = []
# Initialize the temporary directories and create them if they do not exist
temp_dir = f"{OUTPUT_BASE_DIR}/temporary/{process_id}" # temp_dir is the directory to store temporary files (including temp.c, cpg.bin, and temp folder)
os.makedirs(temp_dir, exist_ok=True)
# Set the current working directory to the temp_dir so joern-parse and joern-export can put the temp files in temp_dir
os.chdir(temp_dir)
# Define the variable used by joern-parse to process and save the output into temp_dir
temp_file = os.path.join(temp_dir, "temp.c") # define the path to the directory of temp.c file
graph_type = "cpg" # the type of graph to export, can be "cpg", "cfg", or "ast"
representation = f"--repr={graph_type}"
output_dir = f"--out=temp"
output_format = "--format=dot"
for data_sample in tqdm(data_samples, desc=f"processing chunk {process_id}"):
# Write the C code to a temp.c file for each data sample
with open(temp_file, "w") as file:
file.write(data_sample["func"])
# Run the command to parse the input file into cpg.bin under the home directory
parse_result = subprocess.run([JOERN_PARSE_PATH, temp_file], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# Run the command to export the parsed graph into a .dot file
export_result = subprocess.run([JOERN_EXPORT_PATH, representation, output_dir, output_format], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# Load the .dot file using pydot
(graph,) = pydot.graph_from_dot_file(os.path.join(temp_dir, "temp/temp.c/_global_.dot"))
# Convert to a NetworkX graph
nx_graph = nx.nx_pydot.from_pydot(graph)
# Save the raw graph (with all attributes) to the list of raw graphs
raw_graphs.append(json_graph.node_link_data(nx_graph))
# Iterate over each node in the graph and keep only the "CODE" and "label" attributes
for node in nx_graph.nodes:
# Get the attributes of the current node
attributes = nx_graph.nodes[node]
# Only keep "CODE" and "label", remove others
for attr in list(attributes): # Convert to list to avoid changing dict size during iteration
if attr not in ["CODE", "label"]:
del nx_graph.nodes[node][attr]
# Iterate over all edges and keep only the "label" attribute
for u, v, attr in nx_graph.edges(data=True):
label_value = attr.get("label") # Get the "label" attribute if it exists
# Clear all attributes and re-add the "label" if it exists
attr.clear()
if label_value is not None:
attr["label"] = label_value
# Append the processed graph to the list of processed graphs
processed_graphs.append(json_graph.node_link_data(nx_graph))
# Delete the temp.c file, cpg.bin, and temp folder under the temp_dir
os.remove(temp_file)
os.remove(os.path.join(temp_dir, "cpg.bin"))
shutil.rmtree(os.path.join(temp_dir, "temp"))
# Save the results
os.makedirs(os.path.join(OUTPUT_CHUNKS_DIR, "raw"), exist_ok=True)
os.makedirs(os.path.join(OUTPUT_CHUNKS_DIR, "processed"), exist_ok=True)
save_jsonl(os.path.join(OUTPUT_CHUNKS_DIR, f"raw/{process_id}.jsonl"), raw_graphs)
save_jsonl(os.path.join(OUTPUT_CHUNKS_DIR, f"processed/{process_id}.jsonl"), processed_graphs)
if __name__ == "__main__":
# Create an argument parser
parser = argparse.ArgumentParser(description="Run different sets of experiments")
parser.add_argument(
"--file_name", type=str,
choices=["primevul_test.jsonl", "primevul_train.jsonl", "primevul_valid.jsonl"],
help="The file name of the dataset to process.",
)
# Parse the arguments
args = parser.parse_args()
# Define the input and output file paths based on the provided file name
input_jsonl_path = os.path.join(PROJECT_ROOT, f"datasets/raw/primevul/{args.file_name}")
# Calculate the number of data samples
with open(input_jsonl_path, "r") as file:
number_of_data_samples = sum(1 for _ in file)
# Split the data_samples into chunks
num_chunks = os.cpu_count()
chunk_size = number_of_data_samples // num_chunks
data_chunks_index = [[i, min(i + chunk_size, number_of_data_samples)] for i in range(0, number_of_data_samples, chunk_size)]
# Create and start processes for each chunk
processes = []
for i, chunk_index in enumerate(data_chunks_index):
p = Process(target=process_data, args=(chunk_index,))
processes.append(p)
p.start()
# Wait for all processes to finish
for p in processes:
p.join()
print("Processing completed!")
# Merge the data chunks into memory lists, then save to avoid overwriting
for graph_type in ["raw", "processed"]:
# Initialize an empty list to hold the merged data from all chunks
merged_data = []
chunk_dir = os.path.join(OUTPUT_CHUNKS_DIR, graph_type)
chunk_files = [f for f in os.listdir(chunk_dir) if f.endswith('.jsonl')]
chunk_files.sort(key=lambda x: int(re.search(r'\d+', x).group()) if re.search(r'\d+', x) else 0)
# Load each chunk file and extend the merged_data list
for chunk_file in tqdm(chunk_files, desc=f"Loading {graph_type} chunks"):
chunk_file_path = os.path.join(chunk_dir, chunk_file)
merged_data.extend(load_jsonl(chunk_file_path))
# Save the fully extended list to distinct files
output_jsonl_path = os.path.join(OUTPUT_GRAPHS_DIR, f"{graph_type}/{args.file_name}")
os.makedirs(os.path.dirname(output_jsonl_path), exist_ok=True)
save_jsonl(output_jsonl_path, merged_data)
print(f"Merged {graph_type} saved to: {output_jsonl_path}")
# Delete the temporary directory after merging
if os.path.exists(OUTPUT_CHUNKS_DIR):
shutil.rmtree(OUTPUT_CHUNKS_DIR)
shutil.rmtree(os.path.join(OUTPUT_BASE_DIR, "temporary"))
print(f"Deleted temporary directory.")
|