import gc
import os
import re
import time
cache_dir = os.path.abspath('../transformers_cache')
os.makedirs(cache_dir, exist_ok=True)
os.environ['TRANSFORMERS_CACHE'] = cache_dir
os.environ['HF_HOME'] = cache_dir
print(f"Transformers cache directory set to: {cache_dir}")
import nltk.data
from nltk import word_tokenize
from nltk.corpus import stopwords
import numpy as np
import pandas as pd
from tqdm import tqdm
import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
from transformers import RobertaConfig, RobertaTokenizer, AutoModel
os.environ['CUDA_VISIBLE_DEVICES'] = '0'
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
tokenizer = nltk.data.load('tokenizers/punkt/english.pickle')
stemmer = nltk.stem.SnowballStemmer('english')
stop_words = set(stopwords.words('english'))
tqdm.pandas()
def RemoveGit(str):
gitPattern = '[Gg]it-svn-id'
return re.sub(gitPattern, ' ', str)
def clean_en_text(text):
text = text.lower()
text = re.sub(r'[^a-zA-Z0-9 ]', ' ', text)
text = ' '.join(text.split())
return text
def textProcess(text):
final = []
text = RemoveGit(text)
sentences = tokenizer.tokenize(text)
for sentence in sentences:
sentence = clean_en_text(sentence)
word_tokens = word_tokenize(sentence)
word_tokens = [word for word in word_tokens if word not in stop_words]
for word in word_tokens:
final.append(str(stemmer.stem(word)))
if len(final) == 0:
text = ' '
else:
text = ' '.join(final)
return text
def convert_examples_to_features(desc, mess, tokenizer, max_seq_length):
desc_token = tokenizer.tokenize(desc)
mess_token = tokenizer.tokenize(mess)
if len(desc_token) + len(mess_token) > max_seq_length - 3:
if len(desc_token) > (max_seq_length - 3) / 2 and len(mess_token) > (max_seq_length - 3) / 2:
desc_token = desc_token[:int((max_seq_length - 3) / 2)]
mess_token = mess_token[:max_seq_length - 3 - len(desc_token)]
elif len(desc_token) > (max_seq_length - 3) / 2:
desc_token = desc_token[:max_seq_length - 3 - len(mess_token)]
elif len(mess_token) > (max_seq_length - 3) / 2:
mess_token = mess_token[:max_seq_length - 3 - len(desc_token)]
combined_token = [tokenizer.cls_token] + desc_token + [tokenizer.sep_token] + mess_token + [tokenizer.sep_token]
input_ids_text = tokenizer.convert_tokens_to_ids(combined_token)
if len(input_ids_text) < max_seq_length:
padding_length = max_seq_length - len(input_ids_text)
input_ids_text += [tokenizer.pad_token_id] * padding_length
input_ids_text = torch.tensor(input_ids_text)
assert len(input_ids_text) == max_seq_length, 'Length of input_ids_text is error!'
attention_mask_text = input_ids_text.ne(tokenizer.pad_token_id).to(torch.int64)
return input_ids_text, attention_mask_text
class NewPairDataset(Dataset):
def __init__(self, feature_file, cve_name=None):
if cve_name:
feature_file = f'../cache/{cve_name}_interrelationship_features.csv'
model_path = '../pretrained_model/roberta-large'
if not os.path.exists(model_path):
print(f"Local RoBERTa model not found at {model_path}, downloading from Hugging Face...")
model_path = 'roberta-large'
if not os.path.exists(feature_file):
raise FileNotFoundError(f"Feature file not found: {feature_file}")
print(f"Loading features from: {feature_file}")
df_feature = pd.read_csv(feature_file)
df_feature['msg_text1'] = df_feature['msg_text1'].fillna('') + ' ' + df_feature['deepseek_text1'].fillna('')
df_feature['msg_text2'] = df_feature['msg_text2'].fillna('') + ' ' + df_feature['deepseek_text2'].fillna('')
print(f'Dataset size: {len(df_feature)} rows')
self.cve = df_feature['cve']
self.commit1 = df_feature['commit1']
self.commit2 = df_feature['commit2']
self.msg_text1 = df_feature['msg_text1']
self.msg_text2 = df_feature['msg_text2']
self.label1 = df_feature.get('label1', pd.Series([0] * len(df_feature)))
self.label2 = df_feature.get('label2', pd.Series([0] * len(df_feature)))
self.label = df_feature.get('label', pd.Series([0] * len(df_feature)))
handcrafted_columns = ['cve_match', 'cve_num1', 'cve_num2', 'bug_match', 'bug_num1', 'bug_num2', 'issue_match',
'issue_num1', 'issue_num2', 'id_match', 'author_match', 'time_interval',
'same_func_used_num', 'same_func_used_ratio', 'opposite_ratio', 'opposite_num',
'same_ratio', 'same_num', 'same_function_num',
'same_function_ratio', 'same_file_num', 'same_file_ratio', 'same_msg_token_num',
'same_msg_token_ratio', 'same_code_token_num', 'same_code_token_ratio',
'same_deepseek_text_token_num', 'same_deepseek_text_token_ratio',
'commit_pair_deepseek_tfidf', 'patch_score1', 'patch_score2']
handcrafted_feature = df_feature[handcrafted_columns]
handcrafted_feature = handcrafted_feature.fillna(0)
self.handcrafted = handcrafted_feature.to_numpy()
self.hc_dim = handcrafted_feature.shape[1]
print(f"Using {self.hc_dim} handcrafted features")
self.text_tokenizer = RobertaTokenizer.from_pretrained(model_path, cache_dir=cache_dir)
del df_feature
gc.collect()
def __len__(self):
return len(self.label)
def __getitem__(self, index):
max_seq_length = 512
msg_text1 = self.msg_text1[index] if isinstance(self.msg_text1[index], str) else ''
msg_text2 = self.msg_text2[index] if isinstance(self.msg_text2[index], str) else ''
input_ids_text, attention_mask_text = convert_examples_to_features(msg_text1, msg_text2, self.text_tokenizer,
max_seq_length)
sample = (input_ids_text, attention_mask_text, torch.tensor(self.handcrafted[index]),
torch.tensor(self.label[index]), self.cve[index], self.commit1[index], self.commit2[index],
self.label1[index], self.label2[index])
return sample
class NewPairModel(nn.Module):
def __init__(self, hc_dim=32, model_path='../pretrained_model/roberta-large'):
super(NewPairModel, self).__init__()
self.hc_dim = hc_dim
self.s_dim = 32
if not os.path.exists(model_path):
print(f"Local RoBERTa model not found at {model_path}, downloading from Hugging Face...")
model_path = 'roberta-large'
config = RobertaConfig.from_pretrained(model_path, cache_dir=cache_dir)
self.textEncoder = AutoModel.from_pretrained(model_path, config=config, cache_dir=cache_dir)
self.fc1 = nn.Linear(self.textEncoder.config.hidden_size, self.s_dim)
self.fc2 = nn.Linear(self.hc_dim, self.hc_dim)
self.mlp = nn.Sequential(
nn.Linear(self.s_dim + self.hc_dim, (self.s_dim + self.hc_dim) // 2),
nn.ReLU(),
nn.Linear((self.s_dim + self.hc_dim) // 2, 2)
)
self.criterion = nn.CrossEntropyLoss()
for param in self.textEncoder.parameters():
param.requires_grad = False
def forward(self, input_ids_text, attention_mask_text, handcrafted, label=None):
text_output = self.textEncoder(input_ids=input_ids_text, attention_mask=attention_mask_text)[1]
text_features = self.fc1(text_output)
hc_features = self.fc2(handcrafted)
combined_features = torch.cat([text_features, hc_features], dim=1)
logits = self.mlp(combined_features)
prob = torch.softmax(logits, -1)
if label is not None:
loss = self.criterion(logits, label)
return loss, prob
else:
return prob
def predict_relevance_scores(cve_name, batch_size=16):
model_path = "../checkpoint_Phase2_model.bin"
if not os.path.exists(model_path):
raise FileNotFoundError(f"Trained model not found: {model_path}")
feature_file = f'../cache/{cve_name}_interrelationship_features.csv'
if not os.path.exists(feature_file):
raise FileNotFoundError(f"Feature file not found: {feature_file}")
print(f"Loading trained model from: {model_path}")
dataset = NewPairDataset(feature_file, cve_name)
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=False)
model = NewPairModel(hc_dim=dataset.hc_dim)
if not os.path.exists(model_path):
print(f"Warning: Model file not found at {model_path}")
print("Creating dummy model for testing...")
checkpoint = None
else:
checkpoint = torch.load(model_path, map_location=device)
if checkpoint is not None:
if isinstance(checkpoint, torch.nn.DataParallel):
state_dict = checkpoint.module.state_dict()
elif isinstance(checkpoint, dict) and 'module.' in list(checkpoint.keys())[0]:
state_dict = {k.replace('module.', ''): v for k, v in checkpoint.items()}
else:
state_dict = checkpoint
try:
model.load_state_dict(state_dict)
print("Model loaded successfully")
except Exception as e:
print(f"Warning: Could not load model weights: {e}")
print("Using randomly initialized model")
else:
print("Using randomly initialized model for testing")
model = model.to(device)
if device.type == 'cuda' and torch.cuda.device_count() > 1:
model = torch.nn.DataParallel(model)
print(f"Using DataParallel with {torch.cuda.device_count()} GPUs")
else:
print(f"Using single device: {device}")
prob_list = []
label_list = []
cve_list = []
commit1_list = []
commit2_list = []
label1_list = []
label2_list = []
model.eval()
print(f"Starting prediction for {len(dataset)} commit pairs...")
bar = tqdm(dataloader, total=len(dataloader))
for step, batch in enumerate(bar):
input_ids_text = batch[0].to(device)
attention_mask_text = batch[1].to(device)
handcrafted = batch[2].float().to(device)
label = batch[3]
cve = batch[4]
commit1 = batch[5]
commit2 = batch[6]
label1 = batch[7]
label2 = batch[8]
with torch.no_grad():
prob = model(input_ids_text, attention_mask_text, handcrafted)
prob_list.append(prob.cpu().numpy())
label_list.append(list(label))
cve_list.append(list(cve))
commit1_list.append(list(commit1))
commit2_list.append(list(commit2))
label1_list.append(list(label1))
label2_list.append(list(label2))
torch.cuda.empty_cache()
cve_list = np.concatenate(cve_list, 0)
prob_list = np.concatenate(prob_list, 0)
relevance_scores = prob_list[:, 1]
label_list = np.concatenate(label_list, 0)
commit1_list = np.concatenate(commit1_list, 0)
commit2_list = np.concatenate(commit2_list, 0)
label1_list = np.concatenate(label1_list, 0)
label2_list = np.concatenate(label2_list, 0)
result_data = {
'cve': cve_list,
'commit1': commit1_list,
'commit2': commit2_list,
'relevance_score': relevance_scores,
'label': label_list,
'label1': label1_list,
'label2': label2_list
}
result_df = pd.DataFrame(result_data)
output_file = f'../cache/{cve_name}_relevance_scores.csv'
result_df.to_csv(output_file, index=False)
print(f"Results saved to: {output_file}")
return result_df
if __name__ == '__main__':
cve_name = "CVE-2018-6596"
print(f'Predicting relevance scores for {cve_name} at', time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
try:
result_df = predict_relevance_scores(cve_name)
top_pairs = result_df.nlargest(5, 'relevance_score')
print("\nTop 5 most relevant commit pairs:")
for idx, row in top_pairs.iterrows():
print(f" {row['commit1'][:8]} <-> {row['commit2'][:8]}: {row['relevance_score']:.4f}")
except Exception as e:
print(f"Error: {e}")