codecollab-io / cc-compiler

CodeCollab Compiler
https://codecollab.io
13 stars 4 forks source link

cc-compiler

The CodeCollab compiler used for running untrusted code. Currently supports 18 different languages.

This package allows you to run untrusted code in a collaborative environment. Multiple users can run code together simply by attaching themselves to an already running docker container. You can start this docker container using the {Compiler} included in this package or simply attach to an already existing docker container using the {Attacher}.

NOTE: This package uses node-pty to create a pseudo-tty. As such, escape sequences are not removed from the program output. It is highly recommended you use this library with xterm.js or any other terminal emulator that is able to process escape sequences.

Prerequisites

  1. Docker
  2. Docker daemon running that doesn't require sudo access.

Install

Using NPM:

$ npm install @portatolova/cc-compiler

This package requires that you build the included docker image. Navigate to the package directory and type the following command in the "setup" directory to install the docker image.

$ ./setup.sh

It is recommend that u build this image on any computer running on x86 architecture. There is no guarantees the image will work as intended on ARM devices.

Usage

This package comes with a Compiler and an Attacher:

const Compiler = require("@portatolova/cc-compiler").Compiler;
const Attacher = require("@portatolova/cc-compiler").Attacher;

Compiler

The compiler class is used to initialise and run the untrusted user code.

// Initialises the compiler
let compiler = new Compiler(options);

compiler.exec();                    // Starts the compiler
compiler.push(data);                // Sends data into the running program
compiler.stop();                    // Stops the compiler

Options

compiler.exec() Starts compilation.

compiler.push(data: string) Pipes data into the program's STDIN stream.

compiler.resize(size: { cols: number, rows: number }) Resizes the underlying pseudo-tty.

compiler.stop() Stops compilation.

options.langNum:

Each language has an index or "langNum" used to identify it. Here are the langNums and their associated languages. (NOTE: There is no index 7)

* 0 - Python 3.7
* 1 - NodeJS
* 2 - CoffeeScript
* 3 - Typescript
* 4 - C++
* 5 - C
* 6 - C#
* 8 - Python 2.7
* 9 - Bash Shell
* 10 - Java
* 11 - Swift
* 12 - Golang
* 13 - PHP
* 14 - Ruby
* 15 - Perl
* 16 - Kotlin
* 17 - Elixir
* 18 - Scala

options.pathToFiles:

Starting from v4.0.0, you do not need to have a specific folder structure. Simply set options.pathToFiles to where your code is stored and it will be mounted into the docker container as options.folderName. If options.folderName is not specified, code is used instead.

Attacher

The attacher class is used to attach to an existing docker container.

// Initialises the attacher
let attacher = new Attacher(options);

attacher.attach();                  // Attaches to the compiler
attacher.push(data);                // Sends data into the running program
attacher.stop();                    // Stops the compiler

Options

attacher.attach() Attach to an existing compiler.

attacher.push() Pipes data into the program's STDIN stream.

attacher.resize(size: { cols: number, rows: number }) Resizes the underlying pseudo-tty.

attacher.stop() Stops compilation.

Events

Both the Compiler and Attach can emit 3 events.

The Compiler also has an additional event

Example

This example runs code stored in /Users/carlvoller/test using Python 3.9. The compiler is listening for a "launched" event in which afterwards, it will attach the attacher to the compiler. The attacher then listens for "inc" and "done" events for program output and errors as well as for when compilation finishes. It then prints all output to the console.

const Compiler = require("../index").Compiler;
const Attacher = require("../index").Attacher;

// Initialise Compiler
let comp = new Compiler({
    langNum: 0,
    mainFile: "main.py",
    pathToFiles: "/Users/carlvoller/test",
    containerName: "Test1",
    folderName: "My Code"
});

// Initialise Attacher
let attach = new Attacher({
    pathToFiles: "/Users/carlvoller/test",
    containerName: "Test1"
});

comp.exec(); // Start Compiler

// Listen for events
comp.on("launched", () => {
    console.log("Compiler has been launched.");

    // Attach the attacher
    attach.attach();
});

// Listen for data coming out from the program. STDERR is coloured red with escape sequences.
attach.on("inc", (inc) => {
    let output = inc.out;
    console.log(`"Program output: ${output}`);
});

// Listen for when the program finishes running OR timed out
attach.on("done", (done) => {
    let output   = done.out
        timedOut = done.timedOut;   // Did the program time out. (Took > 1 minute)
    console.log(`"Program output: ${output}`);
    console.log(`"Program time out status: ${timedOut}`);
});

// Listen for any errors during initialisation
comp.on("error", (err) => console.log("An error occurred: ", err));
attach.on("error", (err) => console.log("An error occurred: ", err));