Code Monkey home page Code Monkey logo

gambit's Introduction

Gambit: Mutant Generation for Solidity

Gambit is a state-of-the-art mutation system for Solidity. By applying predefined syntax transformations called mutation operators (for example, convert a + b to a - b) to a Solidity program's source code, Gambit generates variants of the program called mutants. Mutants can be used to evaluate test suites or specs used for formal verification: each mutant represents a potential bug in the program, and stronger test suites and specifications should detect more mutants.

Requirements

  1. Gambit is written in Rust. You'll need to install Rust and Cargo to build Gambit.
  2. Gambit uses solc, the Solidity compiler, to generate mutants. You'll need to have a solc binary that is compatible with the project you are mutating (see the --solc option in gambit mutate --help)

Installation

You can download prebuilt Gambit binaries for Linux x86-64 and Mac from our releases page. For Windows and Linux ARM, you must build Gambit from source.

Building Gambit from source

To build Gambit from source, clone the Gambit repository and run

cargo install --path .

from this repository's root. This will build Gambit and install it to a globally visible location on your PATH.

You can also build gambit with cargo build --release from the root of this repository. This will create a gambit binary in gambit/target/release/ which you can manually place on your path or invoke directly (e.g., by calling path/to/gambit/target/release/gambit).

Usage

Gambit has two main commands: mutate and summary. gambit mutate is responsible for mutating code, and gambit summary is a convenience command for summarizing generated mutants in a human-readable way.

Running gambit mutate will invoke solc, so make sure it is visible on your PATH. Alternatively, you can specify where Gambit can find the Solidity compiler with the option --solc path/to/solc, or specify a solc binary (e.g., solc8.12) with the option --solc solc8.12.

Note: All tests (cargo test) are currently run using solc8.13. Your tests may fail if your solc points at a different version of the compiler.

Running gambit mutate

The gambit mutate command expects either a --filename argument or a --json argument. Using --filename allows you to specify a specific Solidity file to mutate:

gambit mutate --filename file.sol

However, if you want to mutate multiple files or apply a more complex set of parameters, we recommend using a configuration file via the --json option instead:

gambit mutate --json gambit_conf.json

Run gambit --help for more information.

Note: All relative paths specified in a JSON configuration file are interpreted to be relative to the configuration file's parent directory.

In the following section we provide examples of how to run Gambit using both --filename and --json. We provide more complete documentation in the Configuration Files and CLI-Options sections below.

Examples

Unless otherwise noted, examples use code from benchmarks/ and are run from the root of the Gambit repository.

Example 1: Mutating a single file

To mutate a single file, use the --filename option (or -f), followed by the file to mutate.

gambit mutate -f benchmarks/BinaryOpMutation/BinaryOpMutation.sol

This will generate:

Generated 34 mutants in 0.69 seconds

Note: The mutated file must be located within your current working directory or one of its subdirectories. If you want to mutate code in an arbitrary directory, use the --sourceroot option.

Example 2: Mutating and downsampling

The above command produced 34 mutants which may be more than you need. Gambit provides a way to randomly downsample the number of mutants with the --num_mutants or -n option:

gambit mutate -f benchmarks/BinaryOpMutation/BinaryOpMutation.sol -n 3

which will generate:

Generated 3 mutants in 0.15 seconds

Example 3: Viewing Gambit results

Note: This example assumes you've just completed Example 2.

Gambit outputs all of its results in gambit_out:

tree -L 2 gambit_out

This produces:

gambit_out
├── gambit_results.json
├── input_json
│   ├── BinaryOpMutation.sol_json.ast
│   └── BinaryOpMutation.sol_json.ast.json
├── mutants
│   ├── 1
│   ├── 2
│   └── 3
└── mutants.log

See the Results Directory section for a detailed explanation of this layout. The gambit summary command pretty prints each mutant for easy inspection:

The output of gambit summary

By default gambit summary prints info on all mutants. If you are interested in particular mutants you can specify a subset of mutant ids with the --mids flag. For instance, gambit summary --mids 3 4 5 will only print info for mutant ids 3 through 5.

Example 4: Specifying solc pass-through arguments

The Solidity compiler (solc) may need some extra information to successfully run on a file or a project. Gambit enables this with pass-through arguments that, as the name suggests, are passed directly through to the solc compiler.

For projects that have complex dependencies and imports, you may need to:

  • Specify base paths: To specify the Solidity --base-path argument, use --solc_base_path:

    gambit mutate --filename path/to/file.sol --solc_base_path base/path/dir
  • Specify remappings: To indicate where Solidity should find libraries, use solc's import remapping syntax with --solc_remappings:

    gambit mutate --filename path/to/file.sol \
      --solc_remappings @openzeppelin=node_modules/@openzeppelin @foo=node_modules/@foo
    The paths should ***NOT*** end with a trailing /
    
  • Specify allow paths: To include additional allowed paths via solc's --allow-paths argument, use --solc_allow_paths:

    gambit mutate --filename path/to/file.sol \
      --solc_allow_paths PATH1 --solc_allow_paths PATH2 ...
  • Specify include-path: To make an additional source directory available to the default import callback via solc's [--include-path][included] argument, use --solc_include_path:

    gambit mutate --filename path/to/file.sol --solc_include_path PATH
  • Use optimization: To run the Solidity compiler with optimizations (solc's --optimize argument), use --solc_optimize:

    gambit mutate --filename path/to/file.sol --solc_optimize

Example 5: The --sourceroot option

Gambit needs to track the location of source files that it mutates within a project: for instance, imagine there are files foo/Foo.sol and bar/Foo.sol. These are separate files, and their path prefixes are needed to determine this. Gambit addresses this with the --sourceroot option: the source root indicates to Gambit the root of the files that are being mutated, and all source file paths (both original and mutated) are reported relative to this source root.

Note: If Gambit encounters a source file that does not belong to the source root it will print an error message and exit.

When running gambit mutate with the --filename option, source root defaults to the current working directory. When running gambit mutate with the --json option, source root defaults to the directory containing the configuration JSON.

Here are some examples of using the --sourceroot option.

  1. From the root of the Gambit repository, run:

    gambit mutate -f benchmarks/BinaryOpMutation/BinaryOpMutation.sol -n 1
    cat gambit_out/mutants.log
    find gambit_out/mutants -name "*.sol"

    This should output the following:

    Generated 1 mutants in 0.13 seconds
    1,BinaryOpMutation,benchmarks/BinaryOpMutation/BinaryOpMutation.sol,23:10, % ,*
    gambit_out/mutants/1/benchmarks/BinaryOpMutation/BinaryOpMutation.sol
    

    The first command generates a single mutant, and its source path is relative to ., the default source root. We can see that the reported paths in mutants.log, and the mutant file path in gambit_out/mutants/1, are the relative to this source root: benchmarks/BinaryOpMutation/BinaryOpMutation.sol

  2. Suppose we want our paths to be reported relative to benchmarks/BinaryOpMutation. We can run

    gambit mutate -f benchmarks/BinaryOpMutation/BinaryOpMutation.sol -n 1 --sourceroot benchmarks/BinaryOpMutation
    cat gambit_out/mutants.log
    find gambit_out/mutants -name "*.sol"

    which will output:

    Generated 1 mutants in 0.13 seconds
    1,BinaryOpMutation,BinaryOpMutation.sol,23:10, % ,*
    gambit_out/mutants/1/BinaryOpMutation.sol
    

    The reported filenames, and the offset path inside of gambit_out/mutants/1/, are now relative to the source root that we specified.

  3. Finally, suppose we use a source root that doesn't contain the source file:

    gambit mutate -f benchmarks/BinaryOpMutation/BinaryOpMutation.sol -n 1 --sourceroot scripts

    This will try to find the specified file inside of scripts, and since it doesn't exist Gambit reports the error:

    [ERROR gambit] [!!] Illegal Configuration: Resolved filename `/Users/USER/Gambit/benchmarks/BinaryOpMutation/BinaryOpMutation.sol` is not prefixed by the derived source root /Users/USER/Gambit/scripts
    

    Gambit prints an error and exits.

Example 6: Running Gambit using a configuration file

To run gambit with a configuration file, use the --json argument:

gambit mutate --json benchmarks/config-jsons/test1.json

The configuration file is a JSON file containing the command line arguments for gambit and additional configuration options:

{
    "filename": "../10Power/TenPower.sol",
    "sourceroot": "..",
    "solc_remappings": [
        "@openzeppelin=node_modules/@openzeppelin"
    ],
}

In addition to specifying the command line arguments, you can list the specific mutants that you want to apply, the specific functions you wish to mutate, and more. See the benchmark/config-jsons directory for examples.

Note: Any paths provided by the configuration file are resolved relative to the configuration file's parent directory.

Configuration Files

Configuration files allow you to save complex configurations and perform multiple mutations at once. Gambit uses a simple JSON object format to store mutation options, where each --option VALUE specified on the CLI is represented as a "option": VALUE key/value pair in the JSON object. Boolean --flags are enabled by storing them as true: "flag": true. For instance, --no_overwrite would be written as "no_overwrite": true.

As an example, consider the command from Example 1:

gambit mutate -f benchmarks/BinaryOpMutation/BinaryOpMutation.sol

To execute this using a configuration file you would write the following to example-1.json to the root of this repository and run gambit mutate --json example-1.json

{
  "filename": "benchmarks/BinaryOpMutation/BinaryOpMutation.sol"
}

Gambit also supports using multiple configurations in the same file: instead of a single JSON object, your configuration file should contain an array of objects:

[
    {
        "filename": "Foo.sol",
        "contract": "C",
        "functions": ["bar", "baz"],
        "solc": "solc8.12",
        "solc_optimize": true
    },
    {
        "filename": "Blip.sol",
        "contract": "D",
        "functions": ["bang"],
        "solc": "solc8.12"
        "mutations": [
          "binary-op-mutation",
          "swap-arguments-operator-mutation"
        ]
    }
]

This configuration file will perform all mutations on Foo.sol's functions bar and baz in the contract C, and only binary-op-mutation and swap-arguments-operator-mutation mutations on the function bang in the contract D. Both will compile using the Solidity compiler version solc5.12.

Paths in Configuration Files

Relative paths in a Gambit configuration file are relative to the parent directory of the configuration file. So if the JSON file listed above was moved to the benchmarks/ directory the "filename" would need to be updated to BinaryOpMutation/BinaryOpMutation.sol.

Results Directory

gambit mutate produces all results in an output directory (default: gambit_out). Here is an example:

gambit mutate -f benchmarks/BinaryOpMutation/BinaryOpMutation.sol -n 5
tree gambit_out -L 2

which produces:

Generated 5 mutants in 0.15 seconds

gambit_out
├── gambit_results.json
├── input_json
├── mutants
│   ├── 1
│   ├── 2
│   ├── 3
│   ├── 4
│   └── 5
└── mutants.log

This has the following structure:

  • gambit_results.json: a JSON file with detailed results
  • input_json/: intermediate files produced by solc that are used during mutation
  • mutants/: exported mutants. Each mutant is in its own directory named after its mutant ID (mid) 1, 2, 3, ...
  • mutants.log: a log file with all mutant information. This is similar to results.json but in a different format and with different information

CLI Options

gambit mutate supports the following options; for a comprehensive list, run gambit mutate --help:

Option Description
-o, --outdir specify Gambit's output directory (defaults to gambit_out)
--no_overwrite do not overwrite an output directory; if the output directory exists, print an error and exit
-n, --num_mutants randomly downsample to a given number of mutants.
-s, --seed specify a random seed. For reproducibility, Gambit defaults to using the seed 0. To randomize the seed use --random_seed
--random_seed use a random seed. Note that this overrides any value specified by --seed
--contract specify a specific contract name to mutate; by default mutate all contracts
--functions specify one or more functions to mutate; by default mutate all functions
--mutations specify one or more mutation operators to use; only generates mutants that are created using the specified operators
--skip_validate only generate mutants without validating them by compilation

Gambit also supports pass-through arguments, which are arguments that are passed directly to the Solidity compiler. All pass-through arguments are prefixed with solc_:

Option Description
--solc_allow_paths passes a value to solc's --allow-paths argument
--solc_base_path passes a value to solc's --base-path argument
--solc_include_path passes a value to solc's --include-path argument
--solc_remappings passes a value to directly to solc: this should be of the form prefix=path.

Mutation Operators

Gambit implements the following mutation operators

Mutation Operator Description Example
binary-op-mutation Replace a binary operator with another a+b -> a-b
unary-operator-mutation Replace a unary operator with another ~a -> -a
require-mutation Alter the condition of a require statement require(some_condition()) -> require(true)
assignment-mutation Replaces the right hand side of an assignment x = foo(); -> x = -1;
delete-expression-mutation Replaces an expression with a no-op (assert(true)) foo(); -> assert(true);
if-cond-mutation Mutate the conditional of an if statement if (C) {...} -> if (true) {...}
swap-arguments-operator-mutation Swap the order of non-commutative operators a - b -> b - a
elim-delegate-mutation Change a delegatecall() to a call() _c.delegatecall(...) -> _c.call(...)
function-call-mutation (Disabled) Changes arguments of a function add(a, b) -> add(a, a)
swap-arguments-function-mutation (Disabled) Swaps the order of a function's arguments add(a, b) -> add(b, a)

For more details on each mutation type, refer to the full documentation.

Contact

If you have ideas for interesting mutations or other features, we encourage you to make a PR or email us.

Credits

We thank Oliver Flatt and Vishal Canumalla for their excellent contributions to an earlier prototype of Gambit.

gambit's People

Contributors

benthekush avatar chandrakananandi avatar ozcodes avatar samparsky avatar stp59 avatar urikirsh avatar vcanumalla avatar yuradmt avatar bkushigian avatar teryanarmen avatar

Stargazers

Yu Pan avatar Abinash Burman avatar Sam avatar Jiefeng Li avatar  avatar  avatar Luca Liebenberg avatar Lim Zhee Khang avatar Nick Doherty avatar A avatar  avatar nisedo avatar  avatar Long Hoang avatar  avatar Chad0 avatar BAICE avatar Muhamad Rafi Pamungkas avatar leo avatar noid avatar  avatar W avatar Thomas Harper avatar Ana  avatar Sergio avatar  avatar Zero Ekkusu avatar xfu avatar Cvetan Mihaylov avatar Shebin John avatar Fodé Diop avatar Udit Gulati avatar Zi-Hao Li avatar Artem Chystiakov avatar  avatar flaskr avatar Yhtyyar Sahatov avatar  avatar David avatar Yi Li avatar neodaoist avatar shanzson avatar Georgiy Komarov avatar Dewy avatar Filip Małachowicz avatar Mojtaba Eshghie avatar jaybuidl avatar Mikhail avatar  avatar fpgaq avatar 9olidity avatar  avatar kaden avatar 0xBA5ED.eth avatar juancito avatar Sabnock avatar n0x avatar  avatar amanusk avatar horsefacts avatar  avatar Sentry avatar utx0_ avatar Daniel Zarifpour avatar Hayden Shively avatar Antonio Viggiano avatar Raymond avatar Alexander Petrovich avatar Nimrod avatar Christos avatar Théo RICHARD avatar Elliot avatar Oba avatar Haythem Sellami avatar zerosnacks avatar swaHili avatar steven avatar  avatar angello pozo avatar Maurelian avatar  avatar  avatar liquan.eth avatar Rappie avatar Harvey Specter avatar Paulius avatar Mc01.eth avatar Vladimir Khramov avatar  avatar  avatar Nikolay avatar Alejandro Roigé avatar Yu Cheng Tsai avatar rati.eth avatar Phoenix avatar Febby avatar YuFei Wang avatar Teng Zhang avatar Will avatar Víctor Nicolás Martínez Carralero avatar

Watchers

Jochen Hoenicke avatar Ghila avatar  avatar Gabriel Elizedek avatar  avatar  avatar  avatar Antti Hyvärinen avatar Miroslav Stankovic avatar Yuval Zalmenson avatar  avatar  avatar  avatar  avatar  avatar

gambit's Issues

Vyper Compatibility

Hey, sorry if I missed it in the docs, but is it possible to run Gambit against a Vyper contract?

Improve --outdir argument behavior

The current behavior of the --outdir argument in the Gambit tool presents some inconveniences when generating mutants using the command gambit mutate --filename <path1> --outdir <path2>.
The mutants are currently stored under the directory structure <path2>/mutants/[1...Nb_Max]/<path1>/<source_contract_name>.sol.
This issue proposes a more straightforward and organized directory structure for storing mutants, which would be
<path2>/mutants-<source_contract_name>/mutant[1...Nb_Max].sol

Motivation :

The current directory structure for storing mutants can become cumbersome to navigate, especially when dealing with a large number of mutants. By revising the directory structure as suggested, all mutants would be consolidated in a single location, simplifying management and improving overall usability.

Proposed Directory Structure:

The suggested modification to the --outdir behavior would create the following directory structure:

<path2>/mutants-<source_contract_name>/mutant[1...Nb_Max].sol

Where:

path2: The provided output directory path.
<source_contract_name>: The name of the source contract, extracted from .
mutant[1...Nb_Max].sol: The individual mutant files, numbered from 1 to the maximum number of mutants generated.
Benefits:

Simplified organization: All mutants for a particular source contract would be grouped together under the mutants-<source_contract_name> directory.
Improved accessibility: Users would find it easier to locate and manage mutants, as they would be stored in a single, consistent location.
Enhanced usability: The proposed directory structure eliminates the need to navigate multiple levels of directories, streamlining the process of accessing and analyzing mutants.

Issue with require-statement mutation in Gambit tool: Expression replacement not implemented

As a user of the Gambit tool, I have noticed an issue with the require-statement mutation, where the mutation is not fully implemented according to the documentation.

According to the Gambit documentation, the require-statement mutation is intended to negate or change the expression inside a require statement. The documentation suggests replacing the expression with "true" or "!".

However, I have observed that the mutation only replaces the expression with "!". The replacement with "true" is not implemented.

I believe that this is a significant limitation of the mutation. The replacement with "true" would be useful in cases where the require statement is used to check a condition that should always be true. This would allow this mutation to generate more varied and meaningful mutants.

I would like to suggest that the Gambit team review the implementation of the require-statement mutation and consider adding the ability to replace the expression with "true" or other values, as suggested in the documentation.

Thank you for your attention to this matter.

Foundry x Gambit Compatibility

Description

Work has progressed significantly on Foundry x Gambit integration. The details can be found here.

However, further improvements can be made to Gambit to improve performance and compatibility with Foundry

These improvements are highlighted below

Improvements

  • Output all generated mutant artifacts to a single dir. Use Contract names as subdirectory for mutant artifacts.
  • Remove the dependency on using outdir as the key for the returned mutant Hashmap in lib.rs and replace with Contract name
  • Replace the custom Solc invoking logic with foundry-compilers
  • Parallelize the generation of mutants
  • If possible replace the custom AST parser with foundry-compilers ast
  • Add Function level mutation inference
  • Remove unnecessary logging

Forse

`ff

<title>وب سایت شخصی من</title> <style> body { font-family: Arial, sans-serif; } .container { max-width: 800px; margin: 0 auto; } h1, h2 { color: #333; } </style>

بیوگرافی

من ... هستم و در ... زندگی می کنم.

    <h2>رزومه</h2>
    <p>تجربه کاری من شامل ... است.</p>

    <h2>جزئیات تماس</h2>
    <p>شما می توانید با من از طریق ... تماس بگیرید.</p>
</div>
<script>
    // اینجا می توانید کد JavaScript خود را قرار دهید
</script>
https://github.com/Certora/gambit/blob/862cd0f33c6b97a5e3ebd2aeb0ef431e892aea52/benchmarks/10Power/TenPower.sol#L5`

Issue with Gambit tool when generating maximum number of mutants

I encountered an issue while using the Gambit tool to generate mutants from Solidity source code. When I tried to generate the maximum number of mutants possible using the 'num-mutants' parameter in the configuration file below, the program continued to run indefinitely, forcing me to manually kill the process.

Configuration file used:
{ "filename": "src/ERC20_OZ/ERC20.sol", "contract": "ERC20", "functions": ["decreaseAllowance"], "mutations": [ "binary-op-mutation", "unary-operator-mutation", "require-mutation", "delete-expression-mutation", "function-call-mutation", "if-statement-mutation", "swap-arguments-function-mutation", "swap-arguments-operator-mutation", "swap-lines-mutation", "elim-delegate-mutation" ], "num-mutants": 10000 }

I suspect that this issue is caused by the tool not properly handling the case when the number of mutants generated exceeds the maximum possible. It would be helpful if the Gambit tool could throw an error message or exit gracefully in such cases, rather than forcing the user to manually kill the process. Thank you for your attention to this matter.

Issue with swap-arguments-function mutation in Gambit tool: Limited to functions with 2 arguments

As a user of the Gambit tool, I have noticed an issue with the swap-arguments-function mutation, where the mutation is limited to functions with only 2 arguments.

According to the Gambit documentation, the swap-arguments-function mutation is intended to swap the arguments of a function call. However, I have observed that the mutation only swaps the arguments for functions with exactly 2 arguments. Functions with more than 2 arguments are not mutated.

I believe that this is a significant limitation of the mutation operator. In Solidity contracts, it is common to have functions with more than 2 arguments, and the swap-arguments-function mutation could be more effective if it could be applied to these functions as well.

I would like to suggest that the Gambit team review the implementation of the swap-arguments-function mutation and consider expanding its functionality to include functions with more than 2 arguments.

Thank you for your attention to this matter.

Issue with function-call mutation in Gambit tool: Require statement treated as a function

As a user of the Gambit tool, I have noticed an issue with the function-call-mutation, where the require statement is being treated as a function call.

According to the Gambit documentation, the function-call mutation is intended to replace function call with one of its operands. However, I have observed that the require statement, is also being mutated by this operator.

I am unsure if this is the intended behavior of the function-call mutation. If not, I would like to suggest that the Gambit team review this behavior and make the necessary adjustments to the mutation operator.

If it is the intended behavior, I would like to request that the Gambit documentation be updated to clarify that the require statement is considered a function for the purposes of mutation testing.

Thank you for your attention to this matter.

Create a log file of all generated mutants along with meta data

It's helpful to have an overview/summary of generated mutants, which rules generated them, which files the mutants belong to, etc etc. Here is an example from Major's mutants.log:

8:EVR:<IDENTIFIER(org.apache.commons.csv.CSVFormat)>:<DEFAULT>:org.apache.commons.csv.CSVFormat$Predefined@getFormat():230:format |==> null
9:LVR:FALSE:TRUE:org.apache.commons.csv.CSVFormat:249:false |==> true
10:LVR:TRUE:FALSE:org.apache.commons.csv.CSVFormat:249:true |==> false

If I were to change anything about this I'd say that this should be in a CSV format (parsing is a little tricky, and the weird ":" delimiter runs into issues w/ the ternary expression a ? b : c anyway, so we're not really avoiding any difficulties by avoiding commas)

Request for addition of mutation for relational operators in Gambit tool

As a user of the Gambit tool, I would like to suggest the addition of a mutation for relational operators, such as "<", ">", "<=", ">=", and "!=".

Currently, the Gambit tool offers a variety of mutations, including binary-operator-mutation and unary-operator-mutation, which can be used to mutate arithmetic and bitwise operators. However, there is no mutation available specifically for relational operators.

I believe that adding a mutation for relational operators would improve the effectiveness of mutation testing for Solidity code. Relational operators are frequently used in Solidity contracts to compare values, and introducing subtle changes to these operators can reveal hidden bugs and vulnerabilities in the code.

I understand that adding a new mutation can be time-consuming and requires careful consideration. However, I believe that this addition would greatly benefit the Solidity developer community and encourage more rigorous testing practices.

Thank you for your attention to this matter.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.