Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Directory Processing and Patch Output Generation #140

Merged
merged 18 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,8 @@ disable=raw-checker-failed,
deprecated-pragma,
use-symbolic-message-instead,
use-implicit-booleaness-not-comparison-to-string,
use-implicit-booleaness-not-comparison-to-zero
use-implicit-booleaness-not-comparison-to-zero,
unspecified-encoding

# Enable the message, report, category or checker with the given id(s). You can
# either give multiple identifier separated by comma (,) or put this option
Expand Down
12 changes: 6 additions & 6 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"configurations": [
{
"name": "Launch ESBMC-AI on Open File",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "esbmc_ai",
"justMyCode": true,
Expand All @@ -17,7 +17,7 @@
},
{
"name": "Fix Code on Open File",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "esbmc_ai",
"justMyCode": true,
Expand All @@ -31,7 +31,7 @@
},
{
"name": "Optimize Code on Open File",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "esbmc_ai",
"justMyCode": true,
Expand All @@ -44,15 +44,15 @@
},
{
"name": "Python: Current File",
"type": "python",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"justMyCode": true
},
{
"name": "Run tests",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"justMyCode": true,
Expand All @@ -63,7 +63,7 @@
},
{
"name": "Run coverage tests",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"justMyCode": true,
Expand Down
1 change: 1 addition & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ clang = "*"
langchain = "*"
langchain-openai = "*"
langchain-community = "*"
lizard = "*"

[dev-packages]
pylint = "*"
Expand Down
11 changes: 8 additions & 3 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
]
},
"generate_solution": {
"max_attempts": 4,
"max_attempts": 5,
"temperature": 0.0,
"message_history": "normal",
"scenarios": {
Expand All @@ -87,8 +87,13 @@
]
}
},
"system": [],
"initial": "From now on, act as an Automated Code Repair Tool that repairs AI C code. You will be shown AI C code, along with ESBMC output. Pay close attention to the ESBMC output, which contains a stack trace along with the type of error that occurred and its location. Provide the repaired C code as output, as would an Automated Code Repair Tool. Aside from the corrected source code, do not output any other text. The ESBMC output is {esbmc_output} The source code is {source_code}"
"system": [
{
"role": "System",
"content": "From now on, act as an Automated Code Repair Tool that repairs AI C code. You will be shown AI C code, along with ESBMC output. Pay close attention to the ESBMC output, which contains a stack trace along with the type of error that occurred and its location. "
}
],
"initial": "Provide the repaired C code as output, as would an Automated Code Repair Tool. Aside from the corrected source code, do not output any other text. The ESBMC output is {esbmc_output} The source code is {source_code}"
}
}
}
190 changes: 117 additions & 73 deletions esbmc_ai/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,25 @@
# Author: Yiannis Charalambous 2023

import os
from pathlib import Path
import re
import sys

# Enables arrow key functionality for input(). Do not remove import.
import readline
from typing import Optional

from esbmc_ai.commands.fix_code_command import FixCodeCommandResult

_ = readline

import argparse
from langchain.base_language import BaseLanguageModel


import esbmc_ai.config as config
from esbmc_ai import __author__, __version__
from esbmc_ai.frontend.solution import (
SourceFile,
get_main_source_file,
set_main_source_file,
get_main_source_file_path,
)
from esbmc_ai.solution import SourceFile, Solution, get_solution

from esbmc_ai.commands import (
ChatCommand,
Expand All @@ -30,7 +31,7 @@
)

from esbmc_ai.loading_widget import LoadingWidget, create_loading_widget
from esbmc_ai.user_chat import UserChat
from esbmc_ai.chats import UserChat
from esbmc_ai.logging import print_horizontal_line, printv, printvv
from esbmc_ai.esbmc_util import esbmc
from esbmc_ai.chat_response import FinishReason, ChatResponse
Expand Down Expand Up @@ -123,7 +124,34 @@ def init_commands_list() -> None:


def update_solution(source_code: str) -> None:
set_main_source_file(SourceFile(get_main_source_file_path(), source_code))
get_solution().files[0].update_content(content=source_code, reset_changes=True)


def _run_esbmc(source_file: SourceFile, anim: Optional[LoadingWidget] = None) -> str:
assert source_file.file_path

if anim:
anim.start("ESBMC is processing... Please Wait")
exit_code, esbmc_output = esbmc(
path=source_file.file_path,
esbmc_params=config.esbmc_params,
timeout=config.verifier_timeout,
)
if anim:
anim.stop()

# ESBMC will output 0 for verification success and 1 for verification
# failed, if anything else gets thrown, it's an ESBMC error.
if not config.allow_successful and exit_code == 0:
printv("Success!")
print(esbmc_output)
sys.exit(0)
elif exit_code != 0 and exit_code != 1:
printv(f"ESBMC exit code: {exit_code}")
printv(f"ESBMC Output:\n\n{esbmc_output}")
sys.exit(1)

return esbmc_output


def init_commands() -> None:
Expand All @@ -137,25 +165,30 @@ def init_commands() -> None:
fix_code_command.on_solution_signal.add_listener(update_solution)


def _run_command_mode(
command: ChatCommand,
args: list[str],
esbmc_output: str,
source_code: str,
) -> None:
def _run_command_mode(command: ChatCommand, args: argparse.Namespace) -> None:
path_arg: Path = Path(args.filename)

solution: Solution = get_solution()
if path_arg.is_dir():
for path in path_arg.glob("**/*"):
if path.is_file() and path.name:
solution.add_source_file(path, None)
else:
solution.add_source_file(path_arg, None)

match command.command_name:
case fix_code_command.command_name:
error, solution = fix_code_command.execute(
file_name=get_main_source_file_path(),
source_code=source_code,
esbmc_output=esbmc_output,
)

if error:
print("Failed all attempts...")
sys.exit(1)
else:
print(solution)
for source_file in solution.files:
# Run ESBMC first round
esbmc_output: str = _run_esbmc(source_file)
source_file.assign_verifier_output(esbmc_output)

result: FixCodeCommandResult = fix_code_command.execute(
source_file=source_file,
generate_patches=config.generate_patches,
)

print(result)
case _:
command.execute()
sys.exit(0)
Expand Down Expand Up @@ -249,7 +282,15 @@ def main() -> None:
+ "}",
)

args = parser.parse_args()
parser.add_argument(
"-p",
"--generate-patches",
action="store_true",
default=False,
help="Generate patch files and place them in the same folder as the source files.",
)

args: argparse.Namespace = parser.parse_args()

print(f"ESBMC-AI {__version__}")
print(f"Made by {__author__}")
Expand All @@ -270,54 +311,59 @@ def main() -> None:
printv("Reading source code...")
print(f"Running ESBMC with {config.esbmc_params}\n")

# Cast to string (for language servers)
args.filename = str(args.filename)

# Read source code
with open(args.filename, mode="r") as file:
# Add the main source file to the solution explorer.
set_main_source_file(SourceFile(args.filename, file.read()))
assert isinstance(args.filename, str)

anim.start("ESBMC is processing... Please Wait")
exit_code, esbmc_output = esbmc(
path=get_main_source_file_path(),
esbmc_params=config.esbmc_params,
timeout=config.verifier_timeout,
)
anim.stop()

# Print verbose lvl 2
print_horizontal_line(2)
printvv(esbmc_output)
print_horizontal_line(2)

# ESBMC will output 0 for verification success and 1 for verification
# failed, if anything else gets thrown, it's an ESBMC error.
if not config.allow_successful and exit_code == 0:
print("Success!")
print(esbmc_output)
sys.exit(0)
elif exit_code != 0 and exit_code != 1:
print(f"ESBMC exit code: {exit_code}")
print(f"ESBMC Output:\n\n{esbmc_output}")
sys.exit(1)

# Command mode: Check if command is called and call it.
# ===========================================
# Command mode
# ===========================================
# Check if command is called and call it.
# If not, then continue to user mode.
if args.command != None:
command = args.command
if command in command_names:
print("Running Command:", command)
for idx, command_name in enumerate(command_names):
if command == command_name:
_run_command_mode(
command=commands[idx],
args=[], # NOTE: Currently not supported...
source_code=get_main_source_file().content,
esbmc_output=esbmc_output,
)
_run_command_mode(command=commands[idx], args=args)
sys.exit(0)

# ===========================================
# User Mode (Supports only 1 file)
# ===========================================

# Init Solution
solution: Solution
# Determine if we are processing one file versus multiple files
path_arg: Path = Path(args.filename)
if path_arg.is_dir():
# Load only files.
print(
"Processing multiple files is not supported in User Mode."
"Call a command using -c to process directories"
)
sys.exit(1)
else:
# Add the main source file to the solution explorer.
solution: Solution = get_solution()
solution.add_source_file(path_arg, None)
del path_arg

# Assert that we have one file and one filepath
assert len(solution.files) == 1

source_file: SourceFile = solution.files[0]
assert source_file.file_path

esbmc_output: str = _run_esbmc(source_file, anim)

# Print verbose lvl 2
print_horizontal_line(2)
printvv(esbmc_output)
print_horizontal_line(2)

source_file.assign_verifier_output(esbmc_output)
del esbmc_output

printv(f"Initializing the LLM: {config.ai_model.name}\n")
chat_llm: BaseLanguageModel = config.ai_model.create_llm(
api_keys=config.api_keys,
Expand All @@ -332,8 +378,8 @@ def main() -> None:
ai_model_agent=config.chat_prompt_user_mode,
ai_model=config.ai_model,
llm=chat_llm,
source_code=get_main_source_file().content,
esbmc_output=esbmc_output,
source_code=source_file.latest_content,
esbmc_output=source_file.latest_verifier_output,
set_solution_messages=config.chat_prompt_user_mode.scenarios["set_solution"],
)

Expand All @@ -345,7 +391,6 @@ def main() -> None:
if len(config.chat_prompt_user_mode.initial_prompt) > 0:
printv("Using initial prompt from file...\n")
anim.start("Model is parsing ESBMC output... Please Wait")
# TODO Make protected
response = chat.send_message(
message=config.chat_prompt_user_mode.initial_prompt,
)
Expand Down Expand Up @@ -375,17 +420,16 @@ def main() -> None:
print()
print("ESBMC-AI will generate a fix for the code...")

error, solution = fix_code_command.execute(
file_name=get_main_source_file_path(),
source_code=get_main_source_file().content,
esbmc_output=esbmc_output,
result: FixCodeCommandResult = fix_code_command.execute(
source_file=source_file,
generate_patches=config.generate_patches,
)

if not error:
if result.successful:
print(
"\n\nESBMC-AI: Here is the corrected code, verified with ESBMC:"
)
print(f"```\n{solution}\n```")
print(f"```\n{result.repaired_source}\n```")
continue
else:
# Commands without parameters or returns are handled automatically.
Expand Down
13 changes: 13 additions & 0 deletions esbmc_ai/chats/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Author: Yiannis Charalambous

from .base_chat_interface import BaseChatInterface
from .latest_state_solution_generator import LatestStateSolutionGenerator
from .solution_generator import SolutionGenerator
from .user_chat import UserChat

__all__ = [
"BaseChatInterface",
"LatestStateSolutionGenerator",
"SolutionGenerator",
"UserChat",
]
Loading
Loading