Skip to content

Conversation

@srivatsan0611
Copy link

Summary

Adds complete C++ language support to ClankerLoop, enabling users to solve problems in C++.

Implementation Details

  • Added C++ as a third supported language alongside TypeScript and Python
  • Created CppGenerator class following the existing pattern of TypeScriptGenerator and PythonGenerator
  • Implemented dynamic runner generation - each problem gets a custom C++ main() function generated based on its function signature schema (unlike TS/Python which use static runner templates)
  • Two-phase execution: Compile user code + generated runner into a binary (30s timeout), then run that binary against test cases in parallel (10s timeout each)
  • Type mapping system: Automatically converts schema types to C++ equivalents (e.g., int → long long, array → std::vector, map → std::unordered_map<K,V>)
  • Comprehensive error handling: Detects and explains segfaults (exit 139), aborts (exit 134), floating point exceptions (exit 136), and compilation errors
  • Docker environment setup: Installed g++ compiler and nlohmann/json library in Cloudflare Sandbox container
  • Updated all API schemas and frontend to support "cpp" as a language option
  • Maintains consistency: Reuses existing concurrency controls, sandbox infrastructure, and test execution patterns

Copilot AI review requested due to automatic review settings December 7, 2025 15:38
@vercel
Copy link

vercel bot commented Dec 7, 2025

@srivatsan0611 is attempting to deploy a commit to the anirudhkamath's projects Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds comprehensive C++ language support to ClankerLoop, enabling users to solve coding problems in C++ alongside the existing TypeScript and Python options. The implementation follows a similar pattern to the existing language generators but introduces dynamic runner generation and a two-phase execution model (compilation followed by test execution).

Key Changes:

  • Added C++ as a third supported language with complete end-to-end integration from frontend to backend
  • Implemented CppGenerator class with dynamic runner code generation based on function signatures
  • Created two-phase execution model: compile once (30s timeout), then run tests in parallel (10s timeout each)

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
packages/api-types/src/schemas/problems.ts Updated all language enum schemas to include "cpp" option
apps/web/app/problem/[problemId]/components/problem-render.tsx Added C++ as a selectable language in the UI dropdown
apps/web/actions/run-user-solution.ts Extended CodeGenLanguage type to include "cpp"
apps/backend/src/problem-actions/src/types.ts Added "cpp" to SupportedLanguage type definition
apps/backend/src/problem-actions/src/runners.ts Added C++ language config and runner template (note: template is unused)
apps/backend/src/problem-actions/src/run-user-solution.ts Implemented C++ compilation logic, C++-specific error handling, and dynamic runner generation
apps/backend/src/problem-actions/src/code-generator/types.ts Extended code generator schema to support "cpp"
apps/backend/src/problem-actions/src/code-generator/index.ts Added C++ generator to factory function and exports
apps/backend/src/problem-actions/src/code-generator/cpp-generator.ts Complete C++ code generator with type mapping, struct generation, and dynamic runner creation
apps/backend/Dockerfile Installed g++ compiler and nlohmann/json library for C++ compilation

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +131 to +144
// Provide C++-specific error messages for common exit codes
let errorMessage =
"Execution failed. Please abide by the given function signature and structure.";
if (language === "cpp") {
if (result.exitCode === 139) {
errorMessage = "Segmentation Fault (exit code 139): Your program tried to access invalid memory. Check for null pointer dereferences, array out of bounds, or stack overflow.";
} else if (result.exitCode === 134) {
errorMessage = "Aborted (exit code 134): Your program was terminated, possibly due to an assertion failure or abort() call.";
} else if (result.exitCode === 136) {
errorMessage = "Floating Point Exception (exit code 136): Division by zero or invalid arithmetic operation.";
} else if (result.stderr) {
errorMessage = `Runtime Error:\n${result.stderr}`;
}
}
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C++ error handling logic (lines 131-144) is duplicated in two places: once in runUserSolution and again in runUserSolutionWithCustomInputs (lines 425-438). Consider extracting this into a shared helper function to improve maintainability and ensure consistency.

function getCppErrorMessage(exitCode: number, stderr: string): string {
  if (exitCode === 139) {
    return "Segmentation Fault (exit code 139): Your program tried to access invalid memory. Check for null pointer dereferences, array out of bounds, or stack overflow.";
  } else if (exitCode === 134) {
    return "Aborted (exit code 134): Your program was terminated, possibly due to an assertion failure or abort() call.";
  } else if (exitCode === 136) {
    return "Floating Point Exception (exit code 136): Division by zero or invalid arithmetic operation.";
  } else if (stderr) {
    return `Runtime Error:\n${stderr}`;
  }
  return "Execution failed. Please abide by the given function signature and structure.";
}

Copilot uses AI. Check for mistakes.
# Install nlohmann/json library (header-only JSON library for C++)
RUN mkdir -p /usr/local/include && \
cd /tmp && \
git clone --depth 1 https://github.com/nlohmann/json.git && \
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nlohmann/json library is installed from the latest commit on the main branch without a version pin. This could lead to unexpected behavior if the library's API changes. Consider pinning to a specific tag/release for reproducibility.

git clone --depth 1 --branch v3.11.3 https://github.com/nlohmann/json.git
Suggested change
git clone --depth 1 https://github.com/nlohmann/json.git && \
git clone --depth 1 --branch v3.11.3 https://github.com/nlohmann/json.git && \

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +117
generateRunnerCode(schema: FunctionSignatureSchema): string {
// Generate parameter deserialization code
const paramDeserializations = schema.parameters
.map(
(p, index) =>
` auto ${p.name} = input[${index}].get<${this.typeToString(p.type)}>();`
)
.join("\n");

const paramNames = schema.parameters.map((p) => p.name).join(", ");
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The generateRunnerCode method doesn't handle the case where a function has no parameters. When schema.parameters is empty, paramDeserializations will be an empty string and paramNames will be an empty string, but the generated code would still have comments and structure that suggests parameters. While this should still compile correctly, consider adding a comment or adjusting the code generation for clarity in the zero-parameter case.

Copilot uses AI. Check for mistakes.
boolean: "bool",
null: "std::nullptr_t",
};
return map[typeDef.type];
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typeToString method doesn't handle the case where typeDef.type doesn't match any key in the map. This would return undefined, which could cause issues downstream. Consider adding a default case or throwing an error for unsupported primitive types.

const mapped = map[typeDef.type];
if (!mapped) {
  throw new Error(`Unsupported primitive type: ${typeDef.type}`);
}
return mapped;
Suggested change
return map[typeDef.type];
const mapped = map[typeDef.type];
if (!mapped) {
throw new Error(`Unsupported primitive type: ${typeDef.type}`);
}
return mapped;

Copilot uses AI. Check for mistakes.
Comment on lines +131 to +196
// C++ runner template
// This template will be combined with user solution and compiled
// Args: ./runner <input_path> <output_path>
export const CPP_RUNNER = `
#include <fstream>
#include <sstream>
#include <nlohmann/json.hpp>
using json = nlohmann::json;
int main(int argc, char* argv[]) {
if (argc < 3) {
std::cerr << "Usage: " << argv[0] << " <input.json> <output.json>" << std::endl;
return 1;
}
std::string inputPath = argv[1];
std::string outputPath = argv[2];
try {
// Read input JSON
std::ifstream inputFile(inputPath);
if (!inputFile.is_open()) {
throw std::runtime_error("Failed to open input file");
}
json input = json::parse(inputFile);
inputFile.close();
// Capture stdout
std::stringstream stdoutCapture;
std::streambuf* oldCout = std::cout.rdbuf(stdoutCapture.rdbuf());
// Call user's solution - this will be customized per function signature
auto result = runSolution(input);
// Restore stdout
std::cout.rdbuf(oldCout);
// Write output JSON
json output;
output["success"] = true;
output["result"] = result;
output["stdout"] = stdoutCapture.str();
std::ofstream outputFile(outputPath);
outputFile << output.dump();
outputFile.close();
} catch (const std::exception& e) {
json output;
output["success"] = false;
output["error"] = e.what();
output["trace"] = "";
output["stdout"] = "";
std::ofstream outputFile(outputPath);
outputFile << output.dump();
outputFile.close();
// Exit with code 0 so main code can read output.json and handle the error
return 0;
}
return 0;
}
`.trim();
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CPP_RUNNER template exported here is never actually used in the codebase. The C++ implementation dynamically generates runner code via CppGenerator.generateRunnerCode() instead of using this static template. Consider removing this unused export to avoid confusion about which runner template is actually used for C++.

Copilot uses AI. Check for mistakes.
Comment on lines +71 to +99
// For C++, compile the code first
if (language === "cpp") {
// Combine solution and runner into single file for compilation
const combinedCode = `${preparedCode}\n\n${runnerTemplate}`;
const combinedPath = `${WORK_DIR}/combined.cpp`;
await sandbox.uploadFile(Buffer.from(combinedCode, "utf-8"), combinedPath);

// Compile C++ code with 30 second timeout
const compileCommand = `g++ -std=c++17 -O2 -Wall -Wextra -I/usr/local/include ${combinedPath} -o ${WORK_DIR}/runner`;
const compileResult = await sandbox.executeCommand(
compileCommand,
WORK_DIR,
30000, // 30 second timeout for compilation
);

// Check for compilation errors
if (compileResult.exitCode !== 0) {
const compilationError = compileResult.stderr || "Compilation failed";
// Return compilation error for all test cases
results = testCases.map((testCase) => ({
testCase,
status: "error" as const,
actual: null,
expected: testCase.expected,
error: `Compilation Error:\n${compilationError}`,
}));
return results;
}
}
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C++ compilation logic (lines 71-99) is duplicated in two places: once in runUserSolution and again in runUserSolutionWithCustomInputs (lines 356-383). Consider extracting this into a shared helper function to improve maintainability and ensure consistency.

async function compileCppCode(
  sandbox: Sandbox,
  preparedCode: string,
  runnerTemplate: string,
  workDir: string
): Promise<{ success: boolean; error?: string }> {
  const combinedCode = `${preparedCode}\n\n${runnerTemplate}`;
  const combinedPath = `${workDir}/combined.cpp`;
  await sandbox.uploadFile(Buffer.from(combinedCode, "utf-8"), combinedPath);

  const compileCommand = `g++ -std=c++17 -O2 -Wall -Wextra -I/usr/local/include ${combinedPath} -o ${workDir}/runner`;
  const compileResult = await sandbox.executeCommand(
    compileCommand,
    workDir,
    30000,
  );

  if (compileResult.exitCode !== 0) {
    return {
      success: false,
      error: compileResult.stderr || "Compilation failed"
    };
  }
  return { success: true };
}

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +14
RUN mkdir -p /usr/local/include && \
cd /tmp && \
git clone --depth 1 https://github.com/nlohmann/json.git && \
cp json/single_include/nlohmann/json.hpp /usr/local/include/ && \
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The installation of nlohmann/json only copies the single header file to /usr/local/include/ directly, but the compile command uses -I/usr/local/include and includes <nlohmann/json.hpp> (note the nlohmann/ subdirectory). This path mismatch will cause compilation failures.

Fix this by creating the proper directory structure:

RUN mkdir -p /usr/local/include/nlohmann && \
    cd /tmp && \
    git clone --depth 1 https://github.com/nlohmann/json.git && \
    cp json/single_include/nlohmann/json.hpp /usr/local/include/nlohmann/ && \
    rm -rf /tmp/json
Suggested change
RUN mkdir -p /usr/local/include && \
cd /tmp && \
git clone --depth 1 https://github.com/nlohmann/json.git && \
cp json/single_include/nlohmann/json.hpp /usr/local/include/ && \
RUN mkdir -p /usr/local/include/nlohmann && \
cd /tmp && \
git clone --depth 1 https://github.com/nlohmann/json.git && \
cp json/single_include/nlohmann/json.hpp /usr/local/include/nlohmann/ && \

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +62
if (nt.definition.kind === "object") {
const props = Object.entries(nt.definition.properties)
.map(([k, v]) => ` ${this.typeToString(v)} ${k};`)
.join("\n");
// Generate constructor
const constructorParams = Object.entries(nt.definition.properties)
.map(([k, v]) => `${this.typeToString(v)} _${k}`)
.join(", ");
const constructorInits = Object.keys(nt.definition.properties)
.map((k) => `${k}(_${k})`)
.join(", ");

return `struct ${nt.name} {\n${props}\n ${nt.name}(${constructorParams}) : ${constructorInits} {}\n};`;
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generateTypeDefinitions method generates constructors for struct definitions, but it doesn't handle the case where an object has no properties (empty properties object). This would result in invalid C++ syntax with trailing colons in the constructor. Consider adding a check:

if (nt.definition.kind === "object") {
  const props = Object.entries(nt.definition.properties)
    .map(([k, v]) => `    ${this.typeToString(v)} ${k};`)
    .join("\n");
  
  // Only generate constructor if there are properties
  if (Object.keys(nt.definition.properties).length === 0) {
    return `struct ${nt.name} {\n${props}\n};`;
  }
  
  const constructorParams = Object.entries(nt.definition.properties)
    .map(([k, v]) => `${this.typeToString(v)} _${k}`)
    .join(", ");
  const constructorInits = Object.keys(nt.definition.properties)
    .map((k) => `${k}(_${k})`)
    .join(", ");

  return `struct ${nt.name} {\n${props}\n    ${nt.name}(${constructorParams}) : ${constructorInits} {}\n};`;
}

Copilot uses AI. Check for mistakes.
generateScaffold(schema: FunctionSignatureSchema): string {
const params = schema.parameters
.map((p) => {
const typeStr = this.typeToString(p.type);
Copy link

Copilot AI Dec 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The C++ generator doesn't handle optional parameters (unlike TypeScript and Python generators which check p.optional). If the schema includes optional parameters, they won't be properly represented in C++. Consider adding support for optional parameters using std::optional<T>:

const params = schema.parameters
  .map((p) => {
    const typeStr = this.typeToString(p.type);
    if (p.optional) {
      this.includes.add("optional");
      return `std::optional<${typeStr}> ${p.name}`;
    }
    return `${typeStr} ${p.name}`;
  })
  .join(", ");
Suggested change
const typeStr = this.typeToString(p.type);
const typeStr = this.typeToString(p.type);
if (p.optional) {
this.includes.add("optional");
return `std::optional<${typeStr}> ${p.name}`;
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant