Module eqc_c

This module provides functions for testing code written in C.

Copyright © Quviq AB, 2009-2023

Version: 1.46.3

Description

This module provides functions for testing code written in C.

The purpose of this module is to make it easy to test C programs with QuickCheck. To do this it provides a way to call C functions from Erlang together with a number of functions to manipulate C values.

Introduction

Suppose we have a C file example.c containing the following functions:
 // Add two integers
 int plus (int x, int y) {
   return x + y;
 }

 // Sum an array of integers
 int sum (int *array, int len) {
   int n;
   int sum = 0;
   for (n = 0; n < len; n++)
     sum += array[n];
   return sum;
 } 
To make these functions available to call from Erlang, simply call the start function:
 1> eqc_c:start(example).
 ok 
This creates an Erlang module example containing Erlang wrappers for the functions from the C file example.c.
 2> example:plus(3, 4).
 7 
The sum function takes a pointer to an array as an argument, so in order to call it we need to get that pointer from somewhere. For instance, using create_array/2 to allocate a new array:
 3> P = eqc_c:create_array(int, [1, 2, 3, 4]).
 {ptr,int,1048864}
 4> example:sum(P, 4).
 10
 

Details

When calling start/1, the C source is preprocessed and analysed and then three things happen:

Global variables

To access global variables appearing in the C program, the following functions are provided: address_of/1, value_of/1, and set_value/2. The address_of/1 function gives you a pointer to the global variable of the specified name, and can also be used to get pointers to exported functions. value_of/1 and set_value/2 allows you to get and set the value of a global variable.

Working with arrays

Arrays in structs or as global variables. When an array appears as a global variable or as a field of a struct it is represented by a list on the Erlang side. For instance, the declarations
 int xs[3] = {1, 2, 3};
 struct arr { int ys[2]; };
 
can be interacted with as follows
 3> eqc_c:value_of(xs).
 [1,2,3]
 4> eqc_c:alloc({struct, arr}, {arr, [4,5]}).
 {ptr,{struct,arr},4297064576}
 
Functions taking array arguments. If an array appears as an argument to a function, it is treated as a pointer in C. For instance, the following function expects a pointer to an array as its argument:
 int sum(int a[2])
 {
   return a[0] + a[1];
 }
 
Consequently, the type assigned to this function on the Erlang side is
 5> eqc_c:type_of(sum).
 {func,int,[{ptr,{array,int}}]}
 
and it can be called (assuming it's defined in example.c) as
 6> P = eqc_c:create_array(int, [7,11]).
 {ptr,int,4297064592}
 7> example:sum(P).
 18
 

Note that a pointer to an integer is happily accepted as a pointer to an array of integers.

Automatic allocation. For convenience, a single value or a list of values is always accepted whenever a pointer is required. An array containing the given elements will then be allocated and the pointer to it passed along. This is particularly handy for functions expecting string arguments. For instance,
 void hello(char *s)
 {
   printf("Hello %s!\n", s);
 }
 
can be called as
 8> example:hello("world").
 Hello world!
 ok
 
rather than
 9> example:hello(eqc_c:create_string("world")).
 Hello world!
 ok
 

Note that values created in this way will not be freed and can thus cause a memory leak.

Badly behaved C programs

If the C program crashes an exception is raised in the calling Erlang process and the C program is restarted. For instance, trying to dereference a null pointer produces the following (the particular exception raised may depend on the platform):
 5> eqc_c:deref({ptr, int, 0}).
 ** exception error: bus_error 

In the case of a looping C program a timeout exception is raised in the Erlang process. The timeout value can be set when starting the C program (see start/2) or using set_timeout/1. Note: when a timeout occurs a new instance of the C program is started and will be used for subsequent function calls, but the old instance is still running, possibly consuming a lot of resources.

Current limitations

At the moment there is no support for functions with variable arity. These can appear in your C program, but you will not be able to call functions with variable arity from Erlang.

Many standard C header files declare functions which are not actually defined in the corresponding libraries, which results in link errors if wrappers are generated for them. This can be prevented by using the definitions_only option to start/2 (which is the default using start/1), or by ensuring such header files are not included in the C file you pass to start/2, or by specifying the include_functions or exclude_functions option to prevent the problematic wrappers being generated.

Data Types

ptr()

ptr() = {ptr, type(), integer()} | 'NULL'

The value of a C pointer. Contains the memory address as well as the type of the value pointed to.

type()

type() = void | bool | char | unsigned_char | short | unsigned_short | int | unsigned_int | long | unsigned_long | long_long | unsigned_long_long | int128 | unsigned_int128 | float | double | long_double | float128 | complex_float | complex_double | complex_long_double | {ptr, type()} | {func, type(), [type()]} | {closure, type(), [type()]} | {array, integer(), type()} | {array, type()} | {struct, atom()} | {union, atom()} | {enum, atom()} | string()

Supported C types.

Function Index

add_to_ptr/2Add N to a pointer.
address_of/1Get a pointer to a C function or global variable.
alloc/1Allocate memory for an object of type Type.
alloc/2Allocate memory for an object of type Type and initialize it to the value X.
array_index/2Return the value at the given index of an array.
array_index/3Set the value at the given index of an array.
cast_ptr/2Change the type of a pointer.
create_array/2Allocate an array of the given type containing the values Xs.
create_string/1 Allocate a C string containing the given string.
deref/1Dereference a pointer.
expand_type/1Expand all type definitions in a type.
free/1Free the memory pointed to by a pointer.
read_array/2Read the elements of an array.
read_string/1Read the value of a null terminated C string.
restart/0Restart the C program.
running/0Check if the C program is running.
set_timeout/1Set the timeout for C function calls to N milliseconds.
set_value/2Set the value of a global variable.
sizeof/1The size in bytes required to store an object of the given type.
start/1Equivalent to start(Module, [definitions_only]).
start/2Set up an interface to a given C file.
start_functions/2Equivalent to start(Module, [{include_functions, Functions}]).
stop/0Stop the C program.
store/2Store a value at the location pointed to by a pointer.
type_info/1Get information on a type.
type_of/1Get the type of a C function or global variable.
type_of/2Get the type of a C function or global variable.
value_of/1Get the value of a global variable.
write_array/2Store values in the array pointed to be the first argument.

Function Details

add_to_ptr/2

add_to_ptr(Ptr::ptr(), N::integer()) -> ptr()

Add N to a pointer. Equivalent to Ptr += N in C. In other words the value of the resulting pointer depends on the size of the type pointed to.

address_of/1

address_of(X::atom()) -> ptr()

Get a pointer to a C function or global variable.

alloc/1

alloc(Type::type()) -> ptr()

Allocate memory for an object of type Type. The allocated memory is initialized with zeroes.

alloc/2

alloc(Type::type(), X::term()) -> ptr()

Allocate memory for an object of type Type and initialize it to the value X. Type checks the value before allocating the memory.

array_index/2

array_index(Ptr::ptr(), Index::integer()) -> term()

Return the value at the given index of an array. Equivalent to the C code Ptr[Index]. Indexing starts at index 0.

array_index/3

array_index(Ptr::ptr(), Index::integer(), Val::term()) -> ok

Set the value at the given index of an array. Equivalent to the C code Ptr[Index] = Val. Indexing starts at index 0.

cast_ptr/2

cast_ptr(To::type(), Ptr::ptr()) -> ptr()

Change the type of a pointer. Equivalent to the C code (Type *)Ptr. Note: This is unsafe and should be used with care.

create_array/2

create_array(Type::type(), Xs::[term()]) -> ptr()

Allocate an array of the given type containing the values Xs. The values are type checked before allocation.

create_string/1

create_string(S::string()) -> ptr()

Allocate a C string containing the given string.

deref/1

deref(Ptr::ptr()) -> term()

Dereference a pointer.

expand_type/1

expand_type(Type::type()) -> type()

Expand all type definitions in a type.

free/1

free(Ptr::ptr()) -> ok

Free the memory pointed to by a pointer. Equivalent to the C code free(Ptr).

read_array/2

read_array(Ptr::ptr(), N::integer()) -> [term()]

Read the elements of an array. Since C arrays do not store their length, the length has to be supplied separately.

read_string/1

read_string(Ptr::ptr()) -> string()

Read the value of a null terminated C string.

restart/0

restart() -> any()

Restart the C program. Just restarts the executable, no recompilation is performed.

running/0

running() -> any()

Check if the C program is running.

set_timeout/1

set_timeout(N) -> any()

Set the timeout for C function calls to N milliseconds. Can also be set from start/2.

set_value/2

set_value(X::atom(), V::term()) -> ok

Equivalent to store(address_of(X), V).

Set the value of a global variable.

sizeof/1

sizeof(Type::type()) -> integer()

The size in bytes required to store an object of the given type. Equivalent to sizeof(Type) in C.

start/1

start(Module) -> any()

Equivalent to start(Module, [definitions_only]).

start/2

start(Module::atom(), Options::proplist()) -> ok | failed | {error, string()}

Set up an interface to a given C file. Generates and loads an Erlang wrapper Module for the C file specified by the c_src option (atom_to_list(Module) ++ ".c" by default). For instance, if foo.c defines a function int plus(int x, int y), calling eqc_c:start(foo, []) allows plus to be called as foo:plus(1, 2).

Interface generation options:
{c_src, string()}
The name of the C source file (default: atom_to_list(Module) ++ ".c").
definitions_only
Only generate wrappers for function definitions (and not function prototypes).
{exclude_functions, [atom() | string()]}
Do not generate wrappers for the specified functions.
{include_functions, [atom() | string()]}
Only generate wrappers for the specified functions.
keep_files
Don't remove temporary files
{hrl, string() | none}
The name of the Erlang header file to be created containing generated record definitions, or none if no file should be created (default: atom_to_list(Module) ++ ".hrl").
{rename, [{CName::atom() | string(), ErlName::atom() | string()}]}
Give specific Erlang names to certain C functions. Multiple C functions can be given the same Erlang name. If they have different number of arguments, Erlang already treats them as distinct functions. In case multiple functions with the same arity are overloaded, there is a runtime disambiguation based on the arguments passed to the function. More precisely, if the given arguments type check against the argument types of exactly one of the overloaded functions, this function gets called, otherwise an error is raised.
Runtime options:
{timeout, integer()}
Specify the timeout (in milliseconds) for C function calls (default 500 ms).
{casts, atom() | [atom()]}
Specify callback modules for performing runtime conversions of C values. The given modules should export two functions to_c/2 and from_c/2, which given a C type and a value performs the desired conversions. The functions should throw an exception for types that are not to be converted.

A typical use case is when the C program could have used an enum type, but didn't. For instance, if the C code defines
 typedef int Answer;
 #define YES 1
 #define NO  0
a casts module might define
 to_c("Answer", yes) -> 1;
 to_c("Answer", no)  -> 0.

 from_c("Answer, 1) -> yes;
 from_c("Answer, 0) -> no.
See also the eqc_c_enum module for a tool to generate to_c and from_c functions in cases like this one.
C compiler options:
verbose
Print the commands used to build the C code.
silent
Don't print warnings and errors from the C compiler.
return_error
Return an error tuple with the error message from the C compiler on failure.
{cc, string()}
Specifies how to call the C compiler (default: gcc).
{cpp, string()}
Specifies how to call the C preprocessor (default: gcc).
{coutput_flag, string()}
The flag to specify the output file of the C compiler (default: "-o").
{cmacro_flag, string()}
The flag to pass to the C compiler to define a preprocessing macro (default: "-D").
{cflags, string()}
Additional flags to the C compiler.
{cppflag, string()}
Specifies the flag to use when preprocessing (default: "-E").
{cppflags, string()}
Additional flags to the C preprocessor.
{wrapper_src, string()}
Specifies the location of the C wrapper library (default: filename:join(code:lib_dir(eqc,include),"eqc_c_lib.c").
{additional_files, [string()]}
Additional files (C sources or object files) to compile or link.
{exec_command_line, fun((string()) -> {string(), [string()]})}
Specifies the command line to start up the C executable as a function from the name of the generated executable to a pair of the program to run and a list of its arguments (default: fun(Exe) -> {Exe, []} end). For instance, to start up the C program with Valgrind you can use fun(Exe) -> {os:find_executable("valgrind"), [Exe]} end.

start_functions/2

start_functions(Module, Functions) -> any()

Equivalent to start(Module, [{include_functions, Functions}]).

stop/0

stop() -> any()

Stop the C program.

store/2

store(Ptr::ptr(), Val::term()) -> ok

Store a value at the location pointed to by a pointer. Equivalent to the C code *Ptr = Val. The value is type checked against the type of the pointer.

type_info/1

type_info(Type::Defined | Datatype) -> Definition | Fields | Tags

Get information on a type. For defined types, type_info returns the definition without expanding nested typedefs (to get the fully expanded type use expand_type/1). For struct and union types, the list of fields is returned, where each field is a pair of a type and a field name. For enum types, the list of tags is returned.

type_of/1

type_of(X::atom()) -> type()

Equivalent to type_of(X, false).

Get the type of a C function or global variable.

type_of/2

type_of(X::atom(), Expand::bool()) -> type()

Get the type of a C function or global variable. If Expand is true, type definitions are unfolded. For instance, given the C program

 typedef unsigned int UINT;
 UINT some_int = 42;
 
we would get the following results from type_of:
 2> eqc_c:type_of(some_int, true).
 unsigned_int
 3> eqc_c:type_of(some_int, false).
 "UINT"
 

value_of/1

value_of(X::atom()) -> term()

Equivalent to deref(address_of(X)).

Get the value of a global variable.

write_array/2

write_array(Ptr::ptr(), List::term()) -> ok

Store values in the array pointed to be the first argument. The values are type checked against the type of the pointer.


Generated by EDoc