|
1 | 1 | """ |
2 | 2 | Multi-language sandbox execution server. |
3 | 3 |
|
4 | | -Supports: Python, JavaScript/TypeScript, Go, Java, Rust, C/C++, Bash/Shell |
| 4 | +Supports: Python, JavaScript/TypeScript, Go, Java, Kotlin, Rust, C/C++, Bash/Shell |
5 | 5 | Provides isolated code execution with resource limits and structured error reporting. |
6 | 6 |
|
7 | 7 | Security / trust model (load-bearing — read before "fixing" CodeQL alerts): |
@@ -118,6 +118,7 @@ async def _correlation_id(request, call_next): |
118 | 118 | "json", |
119 | 119 | "yaml", "yml", |
120 | 120 | "java", |
| 121 | + "kotlin", "kt", "kts", |
121 | 122 | } |
122 | 123 |
|
123 | 124 | def normalize_language(lang: str) -> str: |
@@ -148,6 +149,8 @@ def normalize_language(lang: str) -> str: |
148 | 149 | return "yaml" |
149 | 150 | if lang in ("java",): |
150 | 151 | return "java" |
| 152 | + if lang in ("kotlin", "kt", "kts",): |
| 153 | + return "kotlin" |
151 | 154 | return lang |
152 | 155 |
|
153 | 156 |
|
@@ -198,6 +201,7 @@ def list_languages(): |
198 | 201 | "typescript": ["tsc", "--version"], |
199 | 202 | "go": ["go", "version"], |
200 | 203 | "java": ["javac", "--version"], |
| 204 | + "kotlin": ["kotlinc", "-version"], |
201 | 205 | "rust": ["rustc", "--version"], |
202 | 206 | "c": ["gcc", "--version"], |
203 | 207 | "cpp": ["g++", "--version"], |
@@ -759,10 +763,10 @@ def execute_code(request: ExecuteRequest): |
759 | 763 | """Execute code in isolated environment.""" |
760 | 764 | lang = normalize_language(request.language) |
761 | 765 |
|
762 | | - if lang not in ("python", "javascript", "typescript", "go", "java", "rust", "c", "cpp", "bash"): |
| 766 | + if lang not in ("python", "javascript", "typescript", "go", "java", "kotlin", "rust", "c", "cpp", "bash"): |
763 | 767 | raise HTTPException( |
764 | 768 | status_code=400, |
765 | | - detail=f"Language '{request.language}' not supported. Supported: python, javascript, typescript, go, java, rust, c, cpp, bash" |
| 769 | + detail=f"Language '{request.language}' not supported. Supported: python, javascript, typescript, go, java, kotlin, rust, c, cpp, bash" |
766 | 770 | ) |
767 | 771 |
|
768 | 772 | workspace = tempfile.mkdtemp(dir=WORKSPACE_BASE) |
@@ -967,6 +971,31 @@ def _syntax_check_impl(lang: str, code: str, workspace: Path, filename: Optional |
967 | 971 | if not errors and stderr.strip(): |
968 | 972 | errors.append(stderr.strip().split("\n")[-1]) |
969 | 973 |
|
| 974 | + elif lang == "kotlin": |
| 975 | + fpath = workspace / (filename or "Source.kt") |
| 976 | + fpath.parent.mkdir(parents=True, exist_ok=True) |
| 977 | + fpath.write_text(code) |
| 978 | + classes_dir = workspace / "synccheck_out" |
| 979 | + classes_dir.mkdir(exist_ok=True) |
| 980 | + |
| 981 | + # No syntax-only check in kotlinc. Full compile to temp |
| 982 | + # output dir is the check, same approach as Java |
| 983 | + result = _run_cmd( |
| 984 | + ["kotlinc", "-d", str(classes_dir), str(fpath)], |
| 985 | + timeout=30, cwd=workspace |
| 986 | + ) |
| 987 | + if result["returncode"] != 0: |
| 988 | + stderr = result.get("stderr", "") |
| 989 | + for line in stderr.splitlines(): |
| 990 | + # kotlinc emits errors as either an `e:`-prefixed line or a |
| 991 | + # `file.kt:L:C: error:` line. Match both — but NOT a bare |
| 992 | + # "error" substring, which also hits `w:` warning lines that |
| 993 | + # merely mention the word (false-positive syntax failures). |
| 994 | + if line.strip().startswith("e:") or ": error:" in line: |
| 995 | + errors.append(line.strip()) |
| 996 | + if not errors and stderr.strip(): |
| 997 | + errors.append(stderr.strip().split("\n")[-1]) |
| 998 | + |
970 | 999 | elif lang == "rust": |
971 | 1000 | fpath = workspace / (filename or "check.rs") |
972 | 1001 | fpath.write_text(code) |
@@ -1348,6 +1377,40 @@ def execute_java(code, test_code, workspace, timeout, stdin=None, **_): |
1348 | 1377 | execution_time_ms=int((time.time() - start) * 1000), |
1349 | 1378 | ) |
1350 | 1379 |
|
| 1380 | +# --- Kotlin --- |
| 1381 | + |
| 1382 | +def execute_kotlin(code, test_code, workspace, timeout, stdin=None, **_): |
| 1383 | + start = time.time() |
| 1384 | + fpath = workspace / "Source.kt" |
| 1385 | + fpath.write_text(code) |
| 1386 | + jar_path = workspace / "output.jar" |
| 1387 | + |
| 1388 | + # Compile |
| 1389 | + r = _run_cmd( |
| 1390 | + ["kotlinc", "-include-runtime", "-d", str(jar_path), str(fpath)], |
| 1391 | + 60, cwd=workspace |
| 1392 | + ) |
| 1393 | + |
| 1394 | + if not r["success"]: |
| 1395 | + return ExecuteResponse( |
| 1396 | + success=False, compile_success=False, |
| 1397 | + tests_run=0, tests_passed=0, |
| 1398 | + stdout="", stderr=r["stderr"], |
| 1399 | + error_type="CompileError", error_message=r["stderr"][:500], |
| 1400 | + execution_time_ms=int((time.time() - start) * 1000), |
| 1401 | + ) |
| 1402 | + |
| 1403 | + # Run |
| 1404 | + r = _run_cmd(["java", "-jar", str(jar_path)], timeout, cwd=workspace, stdin=stdin) |
| 1405 | + return ExecuteResponse( |
| 1406 | + success=r["success"], compile_success=True, |
| 1407 | + tests_run=1, tests_passed=1 if r["success"] else 0, |
| 1408 | + stdout=r["stdout"], stderr=r["stderr"], |
| 1409 | + error_type=_classify_error(r["stderr"]) if not r["success"] else None, |
| 1410 | + error_message=r["stderr"][:500] if not r["success"] else None, |
| 1411 | + execution_time_ms=int((time.time() - start) * 1000), |
| 1412 | + ) |
| 1413 | + |
1351 | 1414 | # --- Rust --- |
1352 | 1415 |
|
1353 | 1416 | def execute_rust(code, test_code, workspace, timeout, stdin=None, **_): |
@@ -1483,6 +1546,7 @@ def execute_bash(code, test_code, workspace, timeout, stdin=None, **_): |
1483 | 1546 | "cpp": execute_cpp, |
1484 | 1547 | "bash": execute_bash, |
1485 | 1548 | "java": execute_java, |
| 1549 | + "kotlin": execute_kotlin, |
1486 | 1550 | } |
1487 | 1551 |
|
1488 | 1552 |
|
|
0 commit comments