2021-07-07 00:48:01 +09:00
|
|
|
import logging
|
|
|
|
|
import os
|
2022-04-26 03:46:08 -04:00
|
|
|
import re
|
2021-07-07 00:48:01 +09:00
|
|
|
import subprocess
|
2021-10-11 16:59:33 +01:00
|
|
|
from dataclasses import dataclass
|
2022-10-28 22:17:07 -04:00
|
|
|
import time
|
2022-07-03 10:43:59 -04:00
|
|
|
|
2023-09-30 07:54:40 +02:00
|
|
|
from typing import (
|
|
|
|
|
Any,
|
|
|
|
|
Callable,
|
|
|
|
|
Dict,
|
|
|
|
|
Optional,
|
|
|
|
|
Tuple,
|
|
|
|
|
TYPE_CHECKING,
|
|
|
|
|
TypeVar,
|
|
|
|
|
Sequence,
|
|
|
|
|
)
|
2022-03-13 13:33:19 -04:00
|
|
|
|
|
|
|
|
from django.conf import settings
|
|
|
|
|
|
|
|
|
|
from coreapp import compilers, platforms
|
|
|
|
|
from coreapp.compilers import Compiler
|
|
|
|
|
|
2025-01-24 11:47:44 +00:00
|
|
|
from coreapp.flags import Language
|
2022-03-13 13:33:19 -04:00
|
|
|
from coreapp.platforms import Platform
|
2023-01-14 16:28:48 +00:00
|
|
|
import coreapp.util as util
|
2021-07-07 00:48:01 +09:00
|
|
|
|
2022-02-17 18:47:04 +00:00
|
|
|
from .error import AssemblyError, CompilationError
|
2023-10-27 23:40:59 +09:00
|
|
|
from .libraries import Library
|
2022-02-17 18:47:04 +00:00
|
|
|
from .models.scratch import Asm, Assembly
|
|
|
|
|
from .sandbox import Sandbox
|
|
|
|
|
|
2022-06-12 08:03:24 -04:00
|
|
|
# Thanks to Guido van Rossum for the following fix
|
|
|
|
|
# https://github.com/python/mypy/issues/5107#issuecomment-529372406
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
F = TypeVar("F")
|
|
|
|
|
|
|
|
|
|
def lru_cache(maxsize: int = 128, typed: bool = False) -> Callable[[F], F]:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
from functools import lru_cache
|
|
|
|
|
|
2021-07-07 00:48:01 +09:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
2021-08-26 14:49:33 -04:00
|
|
|
PATH: str
|
2021-08-26 13:56:05 +01:00
|
|
|
if settings.USE_SANDBOX_JAIL:
|
2021-08-26 14:49:33 -04:00
|
|
|
PATH = "/bin:/usr/bin"
|
2021-08-26 13:56:05 +01:00
|
|
|
else:
|
2021-08-26 14:49:33 -04:00
|
|
|
PATH = os.environ["PATH"]
|
2021-08-26 13:52:12 +01:00
|
|
|
|
2023-10-27 23:40:59 +09:00
|
|
|
WINE = "wine"
|
|
|
|
|
WIBO = "wibo"
|
2022-08-09 06:46:19 +09:00
|
|
|
|
2022-02-20 09:21:38 -05:00
|
|
|
|
2023-06-03 16:37:03 +01:00
|
|
|
@dataclass
|
|
|
|
|
class DiffResult:
|
2024-08-22 23:43:26 -06:00
|
|
|
result: Optional[Dict[str, Any]] = None
|
|
|
|
|
errors: Optional[str] = None
|
2023-06-03 16:37:03 +01:00
|
|
|
|
|
|
|
|
|
2021-11-19 13:51:11 -05:00
|
|
|
@dataclass
|
|
|
|
|
class CompilationResult:
|
|
|
|
|
elf_object: bytes
|
|
|
|
|
errors: str
|
|
|
|
|
|
2022-02-20 09:21:38 -05:00
|
|
|
|
2021-08-26 14:49:33 -04:00
|
|
|
def _check_assembly_cache(*args: str) -> Tuple[Optional[Assembly], str]:
|
2021-08-08 23:58:01 +09:00
|
|
|
hash = util.gen_hash(args)
|
2021-08-09 22:57:26 +09:00
|
|
|
return Assembly.objects.filter(hash=hash).first(), hash
|
2021-08-08 23:58:01 +09:00
|
|
|
|
2021-08-25 13:23:50 +02:00
|
|
|
|
2021-07-07 00:48:01 +09:00
|
|
|
class CompilerWrapper:
|
2021-08-26 14:49:33 -04:00
|
|
|
@staticmethod
|
2022-03-13 13:33:19 -04:00
|
|
|
def filter_compiler_flags(compiler_flags: str) -> str:
|
2021-08-25 13:23:50 +02:00
|
|
|
# Remove irrelevant flags that are part of the base compiler configs or
|
|
|
|
|
# don't affect matching, but clutter the compiler settings field.
|
2021-08-25 02:02:31 +02:00
|
|
|
# TODO: use cfg for this?
|
2021-08-25 13:23:50 +02:00
|
|
|
skip_flags_with_args = {
|
|
|
|
|
"-B",
|
|
|
|
|
"-I",
|
|
|
|
|
"-U",
|
2021-08-25 02:02:31 +02:00
|
|
|
}
|
2021-08-25 13:23:50 +02:00
|
|
|
skip_flags = {
|
|
|
|
|
"-ffreestanding",
|
2022-02-03 11:35:37 -05:00
|
|
|
"-non_shared",
|
2021-08-25 13:23:50 +02:00
|
|
|
"-Xcpluscomm",
|
|
|
|
|
"-Wab,-r4300_mul",
|
|
|
|
|
"-c",
|
|
|
|
|
}
|
2022-02-03 12:21:26 -05:00
|
|
|
|
2021-08-25 13:23:50 +02:00
|
|
|
skip_next = False
|
|
|
|
|
flags = []
|
2021-10-26 08:14:48 -04:00
|
|
|
for flag in compiler_flags.split():
|
2021-08-25 13:23:50 +02:00
|
|
|
if skip_next:
|
|
|
|
|
skip_next = False
|
|
|
|
|
continue
|
|
|
|
|
if flag in skip_flags:
|
|
|
|
|
continue
|
|
|
|
|
if flag in skip_flags_with_args:
|
|
|
|
|
skip_next = True
|
|
|
|
|
continue
|
|
|
|
|
if any(flag.startswith(f) for f in skip_flags_with_args):
|
|
|
|
|
continue
|
|
|
|
|
flags.append(flag)
|
|
|
|
|
return " ".join(flags)
|
2021-08-25 02:02:31 +02:00
|
|
|
|
2022-02-18 14:27:12 -05:00
|
|
|
@staticmethod
|
|
|
|
|
def filter_compile_errors(input: str) -> str:
|
2022-04-26 03:46:08 -04:00
|
|
|
filter_strings = [
|
2022-07-29 11:41:20 -04:00
|
|
|
r"wine: could not load .*\.dll.*\n?",
|
|
|
|
|
r"wineserver: could not save registry .*\n?",
|
2022-08-09 06:46:19 +09:00
|
|
|
r"### .*\.exe Driver Error:.*\n?",
|
|
|
|
|
r"# Cannot find my executable .*\n?",
|
|
|
|
|
r"### MWCPPC\.exe Driver Error:.*\n?",
|
2025-02-10 10:01:41 +00:00
|
|
|
r"Fontconfig error:.*\n?",
|
2022-04-26 03:46:08 -04:00
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for str in filter_strings:
|
|
|
|
|
input = re.sub(str, "", input)
|
|
|
|
|
|
|
|
|
|
return input.strip()
|
2022-02-18 14:27:12 -05:00
|
|
|
|
2021-07-07 00:48:01 +09:00
|
|
|
@staticmethod
|
2022-06-12 08:03:24 -04:00
|
|
|
@lru_cache(maxsize=settings.COMPILATION_CACHE_SIZE)
|
2022-02-20 09:21:38 -05:00
|
|
|
def compile_code(
|
2022-05-22 10:11:59 -06:00
|
|
|
compiler: Compiler,
|
|
|
|
|
compiler_flags: str,
|
|
|
|
|
code: str,
|
|
|
|
|
context: str,
|
|
|
|
|
function: str = "",
|
2023-09-30 07:54:40 +02:00
|
|
|
libraries: Sequence[Library] = (),
|
2022-02-20 09:21:38 -05:00
|
|
|
) -> CompilationResult:
|
2022-03-13 13:33:19 -04:00
|
|
|
if compiler == compilers.DUMMY:
|
2021-12-26 06:30:21 -05:00
|
|
|
return CompilationResult(f"compiled({context}\n{code}".encode("UTF-8"), "")
|
|
|
|
|
|
2021-09-06 07:21:51 +09:00
|
|
|
code = code.replace("\r\n", "\n")
|
|
|
|
|
context = context.replace("\r\n", "\n")
|
2021-08-09 23:35:34 +09:00
|
|
|
|
2021-08-11 13:37:24 -04:00
|
|
|
with Sandbox() as sandbox:
|
2022-10-27 03:47:45 +01:00
|
|
|
ext = compiler.language.get_file_extension()
|
|
|
|
|
code_file = f"code.{ext}"
|
2023-08-30 18:27:06 +01:00
|
|
|
src_file = f"src.{ext}"
|
2022-10-27 03:47:45 +01:00
|
|
|
ctx_file = f"ctx.{ext}"
|
|
|
|
|
|
|
|
|
|
code_path = sandbox.path / code_file
|
2021-08-10 22:44:39 -04:00
|
|
|
object_path = sandbox.path / "object.o"
|
2025-01-24 11:47:44 +00:00
|
|
|
skip_line_directive = (
|
|
|
|
|
compiler.is_ido and compiler.language == Language.PASCAL
|
|
|
|
|
)
|
2021-08-09 12:52:15 -04:00
|
|
|
with code_path.open("w") as f:
|
2025-01-24 11:47:44 +00:00
|
|
|
if not skip_line_directive:
|
|
|
|
|
f.write(f'#line 1 "{ctx_file}"\n')
|
2021-08-09 12:52:15 -04:00
|
|
|
f.write(context)
|
2022-02-20 09:21:38 -05:00
|
|
|
f.write("\n")
|
2021-07-07 01:54:41 +09:00
|
|
|
|
2025-01-24 11:47:44 +00:00
|
|
|
if not skip_line_directive:
|
|
|
|
|
f.write(f'#line 1 "{src_file}"\n')
|
2021-08-09 12:52:15 -04:00
|
|
|
f.write(code)
|
2022-02-20 09:21:38 -05:00
|
|
|
f.write("\n")
|
2021-08-02 17:30:27 +01:00
|
|
|
|
2022-03-13 13:33:19 -04:00
|
|
|
cc_cmd = compiler.cc
|
2022-02-03 12:21:26 -05:00
|
|
|
|
2023-08-30 18:27:06 +01:00
|
|
|
# MWCC requires the file to exist for DWARF line numbers,
|
|
|
|
|
# and requires the file contents for error messages
|
2022-07-15 14:47:13 +01:00
|
|
|
if compiler.is_mwcc:
|
2022-10-27 03:47:45 +01:00
|
|
|
ctx_path = sandbox.path / ctx_file
|
2022-07-15 14:47:13 +01:00
|
|
|
ctx_path.touch()
|
2023-08-30 18:27:06 +01:00
|
|
|
with ctx_path.open("w") as f:
|
|
|
|
|
f.write(context)
|
|
|
|
|
f.write("\n")
|
|
|
|
|
|
|
|
|
|
src_path = sandbox.path / src_file
|
|
|
|
|
src_path.touch()
|
|
|
|
|
with src_path.open("w") as f:
|
|
|
|
|
f.write(code)
|
|
|
|
|
f.write("\n")
|
2022-07-15 14:47:13 +01:00
|
|
|
|
2022-02-03 12:21:26 -05:00
|
|
|
# IDO hack to support -KPIC
|
2022-03-13 13:33:19 -04:00
|
|
|
if compiler.is_ido and "-KPIC" in compiler_flags:
|
2022-02-03 12:21:26 -05:00
|
|
|
cc_cmd = cc_cmd.replace("-non_shared", "")
|
|
|
|
|
|
2023-09-21 01:06:35 +01:00
|
|
|
if compiler.platform != platforms.DUMMY and not compiler.path.exists():
|
|
|
|
|
logging.warning("%s does not exist, creating it!", compiler.path)
|
|
|
|
|
compiler.path.mkdir(parents=True)
|
|
|
|
|
|
2021-08-09 12:52:15 -04:00
|
|
|
# Run compiler
|
2021-08-10 22:44:39 -04:00
|
|
|
try:
|
2022-10-28 22:17:07 -04:00
|
|
|
st = round(time.time() * 1000)
|
2023-09-30 07:54:40 +02:00
|
|
|
libraries_compiler_flags = " ".join(
|
|
|
|
|
(
|
2024-04-10 21:02:03 +01:00
|
|
|
compiler.library_include_flag
|
|
|
|
|
+ str(lib.get_include_path(compiler.platform.id))
|
2023-09-30 07:54:40 +02:00
|
|
|
for lib in libraries
|
|
|
|
|
)
|
|
|
|
|
)
|
2023-01-20 18:04:27 +00:00
|
|
|
compile_proc = sandbox.run_subprocess(
|
2022-02-03 12:21:26 -05:00
|
|
|
cc_cmd,
|
2023-01-20 18:04:27 +00:00
|
|
|
mounts=(
|
|
|
|
|
[compiler.path] if compiler.platform != platforms.DUMMY else []
|
|
|
|
|
),
|
2021-08-10 22:44:39 -04:00
|
|
|
shell=True,
|
|
|
|
|
env={
|
2022-02-20 09:21:38 -05:00
|
|
|
"PATH": PATH,
|
2022-07-29 11:41:20 -04:00
|
|
|
"WINE": WINE,
|
2022-08-09 06:46:19 +09:00
|
|
|
"WIBO": WIBO,
|
2022-02-20 09:21:38 -05:00
|
|
|
"INPUT": sandbox.rewrite_path(code_path),
|
|
|
|
|
"OUTPUT": sandbox.rewrite_path(object_path),
|
2022-03-13 13:33:19 -04:00
|
|
|
"COMPILER_DIR": sandbox.rewrite_path(compiler.path),
|
2023-09-30 07:54:40 +02:00
|
|
|
"COMPILER_FLAGS": sandbox.quote_options(
|
|
|
|
|
compiler_flags + " " + libraries_compiler_flags
|
|
|
|
|
),
|
2022-05-22 10:11:59 -06:00
|
|
|
"FUNCTION": function,
|
2022-02-20 09:21:38 -05:00
|
|
|
"MWCIncludes": "/tmp",
|
2022-07-29 07:44:16 +01:00
|
|
|
"TMPDIR": "/tmp",
|
2022-02-20 09:21:38 -05:00
|
|
|
},
|
2023-01-20 18:04:27 +00:00
|
|
|
timeout=settings.COMPILATION_TIMEOUT_SECONDS,
|
2022-02-20 09:21:38 -05:00
|
|
|
)
|
2022-10-28 22:17:07 -04:00
|
|
|
et = round(time.time() * 1000)
|
|
|
|
|
logging.debug(f"Compilation finished in: {et - st} ms")
|
2021-08-10 22:44:39 -04:00
|
|
|
except subprocess.CalledProcessError as e:
|
|
|
|
|
# Compilation failed
|
2022-08-11 01:19:38 -07:00
|
|
|
msg = e.stdout
|
2022-02-21 17:19:54 +00:00
|
|
|
|
|
|
|
|
logging.debug("Compilation failed: %s", msg)
|
2022-08-09 06:46:19 +09:00
|
|
|
raise CompilationError(CompilerWrapper.filter_compile_errors(msg))
|
2022-04-26 03:46:08 -04:00
|
|
|
except ValueError as e:
|
|
|
|
|
# Shlex issue?
|
|
|
|
|
logging.debug("Compilation failed: %s", e)
|
|
|
|
|
raise CompilationError(str(e))
|
2023-01-20 18:04:27 +00:00
|
|
|
except subprocess.TimeoutExpired as e:
|
|
|
|
|
raise CompilationError("Compilation failed: timeout expired")
|
2021-07-07 00:48:01 +09:00
|
|
|
|
2021-08-10 22:44:39 -04:00
|
|
|
if not object_path.exists():
|
2022-08-11 01:19:38 -07:00
|
|
|
error_msg = (
|
|
|
|
|
"Compiler did not create an object file: %s" % compile_proc.stdout
|
|
|
|
|
)
|
2022-07-30 11:37:10 +01:00
|
|
|
logging.debug(error_msg)
|
|
|
|
|
raise CompilationError(error_msg)
|
2021-12-26 06:30:21 -05:00
|
|
|
|
|
|
|
|
object_bytes = object_path.read_bytes()
|
|
|
|
|
|
|
|
|
|
if not object_bytes:
|
|
|
|
|
raise CompilationError("Compiler created an empty object file")
|
2021-08-09 12:52:15 -04:00
|
|
|
|
2022-08-11 01:19:38 -07:00
|
|
|
compile_errors = CompilerWrapper.filter_compile_errors(compile_proc.stdout)
|
2022-02-18 14:27:12 -05:00
|
|
|
|
2024-02-07 19:46:44 +01:00
|
|
|
return CompilationResult(object_bytes, compile_errors)
|
2021-07-29 23:03:04 +09:00
|
|
|
|
|
|
|
|
@staticmethod
|
2022-03-13 13:33:19 -04:00
|
|
|
def assemble_asm(platform: Platform, asm: Asm) -> Assembly:
|
|
|
|
|
if not platform.assemble_cmd:
|
|
|
|
|
raise AssemblyError(
|
|
|
|
|
f"Assemble command for platform {platform.id} not found"
|
|
|
|
|
)
|
2021-10-11 16:59:33 +01:00
|
|
|
|
2022-03-13 13:33:19 -04:00
|
|
|
cached_assembly, hash = _check_assembly_cache(platform.id, asm.hash)
|
2022-02-17 13:11:21 -05:00
|
|
|
if cached_assembly:
|
|
|
|
|
logger.debug(f"Assembly cache hit! hash: {hash}")
|
|
|
|
|
return cached_assembly
|
2021-08-08 23:58:01 +09:00
|
|
|
|
2022-03-13 13:33:19 -04:00
|
|
|
if platform == platforms.DUMMY:
|
2021-12-26 06:30:21 -05:00
|
|
|
assembly = Assembly(
|
|
|
|
|
hash=hash,
|
2022-03-13 13:33:19 -04:00
|
|
|
arch=platform.arch,
|
2021-12-26 06:30:21 -05:00
|
|
|
source_asm=asm,
|
|
|
|
|
)
|
|
|
|
|
assembly.save()
|
|
|
|
|
return assembly
|
|
|
|
|
|
2021-08-11 13:37:24 -04:00
|
|
|
with Sandbox() as sandbox:
|
2024-04-08 19:20:15 +01:00
|
|
|
asm_prelude_path = sandbox.path / "prelude.s"
|
|
|
|
|
asm_prelude_path.write_text(platform.asm_prelude)
|
|
|
|
|
|
2021-08-10 22:44:39 -04:00
|
|
|
asm_path = sandbox.path / "asm.s"
|
2022-11-15 22:46:15 +00:00
|
|
|
data = asm.data.replace(".section .late_rodata", ".late_rodata")
|
2024-04-08 19:20:15 +01:00
|
|
|
asm_path.write_text(data + "\n")
|
2021-07-29 23:03:04 +09:00
|
|
|
|
2021-08-10 22:44:39 -04:00
|
|
|
object_path = sandbox.path / "object.o"
|
2021-08-10 00:00:02 +09:00
|
|
|
|
2021-08-10 22:44:39 -04:00
|
|
|
# Run assembler
|
|
|
|
|
try:
|
2023-01-20 18:04:27 +00:00
|
|
|
assemble_proc = sandbox.run_subprocess(
|
2022-03-13 13:33:19 -04:00
|
|
|
platform.assemble_cmd,
|
2021-08-23 20:58:18 +09:00
|
|
|
mounts=[],
|
2021-08-10 22:44:39 -04:00
|
|
|
shell=True,
|
|
|
|
|
env={
|
2022-02-20 09:21:38 -05:00
|
|
|
"PATH": PATH,
|
2024-04-08 19:20:15 +01:00
|
|
|
"PRELUDE": sandbox.rewrite_path(asm_prelude_path),
|
2022-02-20 09:21:38 -05:00
|
|
|
"INPUT": sandbox.rewrite_path(asm_path),
|
|
|
|
|
"OUTPUT": sandbox.rewrite_path(object_path),
|
2023-07-03 13:51:58 +01:00
|
|
|
"COMPILER_BASE_PATH": sandbox.rewrite_path(
|
|
|
|
|
settings.COMPILER_BASE_PATH
|
|
|
|
|
),
|
2022-02-20 09:21:38 -05:00
|
|
|
},
|
2023-01-20 18:04:27 +00:00
|
|
|
timeout=settings.ASSEMBLY_TIMEOUT_SECONDS,
|
2022-02-20 09:21:38 -05:00
|
|
|
)
|
2021-08-10 22:44:39 -04:00
|
|
|
except subprocess.CalledProcessError as e:
|
2021-12-26 06:30:21 -05:00
|
|
|
raise AssemblyError.from_process_error(e)
|
2023-01-20 18:04:27 +00:00
|
|
|
except subprocess.TimeoutExpired as e:
|
|
|
|
|
raise AssemblyError("Timeout expired")
|
2021-07-29 23:03:04 +09:00
|
|
|
|
2021-08-09 02:38:22 -04:00
|
|
|
# Assembly failed
|
2021-08-10 22:44:39 -04:00
|
|
|
if assemble_proc.returncode != 0:
|
2022-02-20 09:21:38 -05:00
|
|
|
raise AssemblyError(
|
|
|
|
|
f"Assembler failed with error code {assemble_proc.returncode}"
|
|
|
|
|
)
|
2021-08-01 02:29:21 +09:00
|
|
|
|
2021-08-10 22:44:39 -04:00
|
|
|
if not object_path.exists():
|
2021-12-26 06:30:21 -05:00
|
|
|
raise AssemblyError("Assembler did not create an object file")
|
2021-08-10 01:08:33 +09:00
|
|
|
|
2022-02-17 13:11:21 -05:00
|
|
|
assembly = Assembly(
|
|
|
|
|
hash=hash,
|
2022-03-13 13:33:19 -04:00
|
|
|
arch=platform.arch,
|
2022-02-17 13:11:21 -05:00
|
|
|
source_asm=asm,
|
|
|
|
|
elf_object=object_path.read_bytes(),
|
|
|
|
|
)
|
2021-08-09 02:38:22 -04:00
|
|
|
assembly.save()
|
2021-12-26 06:30:21 -05:00
|
|
|
return assembly
|