FlowResampler / utils /cpg_utils.py
Android12138's picture
Upload folder using huggingface_hub (part 2)
01dee4e verified
Raw History Blame Contribute Delete
7.77 kB
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.")