Affix

 view release on metacpan or  search on metacpan

CONTRIBUTING.md  view on Meta::CPAN

2.  **Fork the Repository**: Start by forking the main repository to your own GitHub account.
3.  **Create a Branch**: Create a new branch for your feature or bugfix from the `main` branch. Please use a descriptive name.

    ```bash
    # For a new feature:
    git checkout -b feature/add-riscv-support
    # For a bug fix:
    git checkout -b fix/struct-classification-bug
    ```

4.  **Make Your Changes**: Write your code. Please adhere to the existing coding style and add comments to new or complex logic.
5.  **Test Your Changes**: A pull request is far more likely to be accepted if it includes tests. If you add new functionality, please add a corresponding test case.
6.  **Update the Changelog**: Add an entry to the `[Unreleased]` section of `CHANGELOG.md` describing your change.
7.  **Submit a Pull Request**: Push your branch to your fork and open a pull request against the `main` branch of the original repository. Please provide a clear description of your changes and link to the relevant issue (e.g., `Fixes #123`).

Changes.md  view on Meta::CPAN


### Added

- Float16 support
 - Added the Float16 keyword to Affix.pm.
 - Implemented float_to_half and half_to_float conversion logic in Affix.c (IEEE 754).
 - Added optimized opcodes (OP_PUSH_FLOAT16, OP_RET_FLOAT16) to the internal VM dispatcher for high-performance marshalling.

- Bitfield Support:
 - Enhanced Struct [...] syntax in Affix.pm to support bitfield widths (e.g., a => UInt32, 3).
 - Implemented bitmask-based marshalling in Affix.c to correctly pack and unpack C-style bitfields within structs.

- SIMD Vector Improvements:
 - Added M512, M512d, and M512i type helpers.
 - Ensured compatibility with infix's refined vector alignment and passing rules.
- [infix] Added support for half-precision floating-point (`float16`).
- [infix] Implemented C++ exception propagation through JIT frames on Linux (x86-64 and ARM64) using manual DWARF `.eh_frame` generation and `__register_frame`.
- [infix] Implemented Structured Exception Handling (SEH) for Windows x64 and ARM64 for C++ exception propagation through trampolines.
- [infix] Added `infix_forward_create_safe` API to establish an exception boundary that catches native exceptions and returns a dedicated error code (`INFIX_CODE_NATIVE_EXCEPTION`).
- [infix] Added support for 256-bit (AVX) and 512-bit (AVX-512) vectors in the System V ABI.
- [infix] Added support for receiving bitfield structs in reverse call trampolines.

README.md  view on Meta::CPAN


## Callbacks & Functions

- **`Callback[ [$params] => $ret ]`**: Defines the signature of a C function pointer. Allows you to pass Perl subroutines into C functions.

    ```perl
    # C: void set_handler( void (*cb)(int) );
    affix $lib, 'set_handler', [ Callback[ [Int] => Void ] ] => Void;
    ```

- **`ThisCall( $cb_or_sig )`**: Helper for C++-style `__thiscall` callbacks. Prepends a `Pointer[Void]` (the `this` pointer) to the signature.

## Variadic Functions (VarArgs)

Affix supports C functions that take a variable number of arguments (e.g., `printf`, `ioctl`). When defining a
signature, use the `VarArgs` token at the end of the argument list.

### Basic Usage

```perl
# C: int printf(const char* format, ...);

infix/src/common/compat_c23.h  view on Meta::CPAN

 *
 * @internal
 */
#pragma once
#include "common/infix_config.h"
#include <infix/infix.h>
#include <stdbool.h>
#include <stddef.h>
/**
 * @def nullptr
 * @brief Defines `nullptr` as a standard C-style null pointer constant (`(void*)0`).
 *
 * @details This provides a consistent null pointer constant across C and C++
 * compilation environments. While `NULL` is standard in C, `nullptr` is preferred
 * in modern C++ and is being adopted in newer C standards. This macro ensures
 * the codebase can use `nullptr` consistently without causing compilation errors
 * in a strict C11/C17 environment.
 */
#if !defined(nullptr) && !defined(__cplusplus)
#define nullptr ((void *)0)
#endif

infix/src/common/double_tap.h  view on Meta::CPAN

#endif

#if defined(__GNUC__) || defined(__clang__)
#define DBLTAP_NOINLINE __attribute__((noinline))
#elif defined(_MSC_VER)
#define DBLTAP_NOINLINE __declspec(noinline)
#else
#define DBLTAP_NOINLINE
#endif

// Compiler-specific attribute for printf-style format checking.
#if defined(__GNUC__) || defined(__clang__)
#define DBLTAP_PRINTF_FORMAT(fmt_index, arg_index) __attribute__((format(printf, fmt_index, arg_index)))
#else
#define DBLTAP_PRINTF_FORMAT(fmt_index, arg_index)
#endif

// Public Test Harness Functions (wrapped by macros for convenience)
void tap_init(void);
void tap_plan(size_t count);
int tap_done(void);

infix/src/core/error.c  view on Meta::CPAN

 * @param code The specific error code.
 * @param position For parser errors, the byte offset into the signature string where the error occurred.
 */
void _infix_set_error(infix_error_category_t category, infix_error_code_t code, size_t position) {
    g_infix_last_error.category = category;
    g_infix_last_error.code = code;
    g_infix_last_error.position = position;
    g_infix_last_error.system_error_code = 0;
    // Check if we can generate a rich parser error message.
    if (category == INFIX_CATEGORY_PARSER && g_infix_last_signature_context != nullptr) {
        // Generate a rich, GCC-style error message for parser failures.
        const char * signature = g_infix_last_signature_context;
        size_t sig_len = strlen(signature);
        const size_t radius = 20;  // Number of characters to show around the error position.
        // Calculate the start and end of the snippet to display.
        size_t start = (position > radius) ? (position - radius) : 0;
        size_t end = (position + radius < sig_len) ? (position + radius) : sig_len;
        // Add indicators if the snippet is truncated.
        const char * start_indicator = (start > 0) ? "... " : "";
        const char * end_indicator = (end < sig_len) ? " ..." : "";
        size_t start_indicator_len = (start > 0) ? 4 : 0;

infix/src/core/signature.c  view on Meta::CPAN

 * @param[in,out] state The current parser state.
 * @param[in] code The error code to set.
 */
INFIX_INTERNAL void _infix_set_parser_error(parser_state * state, infix_error_code_t code) {
    _infix_set_error(INFIX_CATEGORY_PARSER, code, (size_t)(state->p - state->start));
}
INFIX_INTERNAL void skip_whitespace(parser_state * state);

/**
 * @internal
 * @brief Advances the parser's cursor past any whitespace or C-style line comments.
 * @param[in,out] state The parser state to modify.
 */
INFIX_INTERNAL void skip_whitespace(parser_state * state) {
    while (true) {
        while (isspace((unsigned char)*state->p))
            state->p++;
        if (*state->p == '#')  // C-style line comments
            while (*state->p != '\n' && *state->p != '\0')
                state->p++;
        else
            break;
    }
}
/**
 * @internal
 * @brief Parses an unsigned integer from the string, used for array/vector sizes.
 * @param[in,out] state The parser state.

infix/src/core/signature.c  view on Meta::CPAN

    if (val == (unsigned long long)SIZE_MAX) {
        _infix_set_parser_error(state, INFIX_CODE_INTEGER_OVERFLOW);
        return false;
    }
    *out_val = (size_t)val;
    state->p = end;
    return true;
}
/**
 * @internal
 * @brief Parses a C-style identifier from the string.
 * @details This is used for member names, named types, and function argument names.
 * It handles simple identifiers (`my_var`) and C++-style namespaces (`NS::Name`).
 * @param[in,out] state The parser state.
 * @return An arena-allocated string for the identifier, or `nullptr` on failure.
 */
static const char * parse_identifier(parser_state * state) {
    skip_whitespace(state);
    const char * start = state->p;
    if (!isalpha((unsigned char)*start) && *start != '_')
        return nullptr;
    while (isalnum((unsigned char)*state->p) || *state->p == '_' || *state->p == ':') {
        if (*state->p == ':' && state->p[1] != ':')

infix/src/core/signature.c  view on Meta::CPAN

    for (size_t i = 0; i < num_members; ++i)
        total_size += members[i].type->size;
    infix_status status =
        infix_type_create_packed_struct(state->arena, &packed_type, total_size, alignment, members, num_members);
    if (status != INFIX_SUCCESS)
        return nullptr;
    return packed_type;
}
/**
 * @internal
 * @brief Helper to preserve the semantic name of C-style primitive aliases.
 * @param state The parser's state.
 * @param id The primitive type id.
 * @param name The string the user used to describe this type.
 */
static infix_type * _create_named_primitive(parser_state * state, infix_primitive_type_id id, const char * name) {
    infix_type * t = infix_arena_calloc(state->arena, 1, sizeof(infix_type), _Alignof(infix_type));
    if (!t)
        return nullptr;

    *t = *infix_type_create_primitive(id);

infix/src/core/signature.c  view on Meta::CPAN

    if (consume_keyword(state, "float16"))
        return infix_type_create_primitive(INFIX_PRIMITIVE_FLOAT16);
    if (consume_keyword(state, "float32"))
        return infix_type_create_primitive(INFIX_PRIMITIVE_FLOAT);
    if (consume_keyword(state, "float64"))
        return infix_type_create_primitive(INFIX_PRIMITIVE_DOUBLE);
    if (consume_keyword(state, "bool"))
        return infix_type_create_primitive(INFIX_PRIMITIVE_BOOL);
    if (consume_keyword(state, "void"))
        return infix_type_create_void();
    // C-style convenience aliases
    if (consume_keyword(state, "uchar"))
        return infix_type_create_primitive(INFIX_PRIMITIVE_UINT8);
    if (consume_keyword(state, "char"))
        return infix_type_create_primitive(INFIX_PRIMITIVE_SINT8);
    if (consume_keyword(state, "ushort"))
        return infix_type_create_primitive(INFIX_PRIMITIVE_UINT16);
    if (consume_keyword(state, "short"))
        return infix_type_create_primitive(INFIX_PRIMITIVE_SINT16);
    if (consume_keyword(state, "uint"))
        return infix_type_create_primitive(INFIX_PRIMITIVE_UINT32);

infix/src/core/signature.c  view on Meta::CPAN

    // MSVC mangling state
    const infix_type * msvc_types[10]; /**< First 10 encountered types for back-referencing. */
    size_t msvc_type_count;            /**< Number of types encountered. */
    size_t depth;                      /**< Current recursion depth, to bound cyclic type graphs. */
} printer_state;
/**
 * @internal
 * @brief A safe `vsnprintf` wrapper for building the signature string.
 * Updates the printer state and sets an error on buffer overflow.
 * @param[in,out] state The printer state.
 * @param[in] fmt The `printf`-style format string.
 * @param[in] ... Arguments for the format string.
 */
static void _print(printer_state * state, const char * fmt, ...) {
    if (state->status != INFIX_SUCCESS)
        return;
    va_list args;
    va_start(args, fmt);
    int written = vsnprintf(state->p, state->remaining, fmt, args);
    va_end(args);
    if (written < 0 || (size_t)written >= state->remaining)

infix/src/core/type_registry.c  view on Meta::CPAN

 * @details This is distinct from the main signature `parser_state` because the
 * grammar for definitions (`@Name=...;`) is much simpler and can be handled
 * with a simpler, non-recursive parser.
 */
typedef struct {
    const char * p;     /**< The current position in the definition string. */
    const char * start; /**< The start of the definition string for error reporting. */
} _registry_parser_state_t;
/**
 * @internal
 * @brief Skips whitespace and C++-style line comments in a definition string.
 * @param state The parser state to advance.
 */
static void _registry_parser_skip_whitespace(_registry_parser_state_t * state) {
    while (1) {
        while (isspace((unsigned char)*state->p))
            state->p++;
        if (*state->p == '#')  // Skip comments
            while (*state->p != '\n' && *state->p != '\0')
                state->p++;
        else

lib/Affix.pod  view on Meta::CPAN


=head2 Callbacks & Functions

=over

=item * B<C<< Callback[ [$params] => $ret ] >>>: Defines the signature of a C function pointer. Allows you to pass Perl subroutines into C functions.

    # C: void set_handler( void (*cb)(int) );
    affix $lib, 'set_handler', [ Callback[ [Int] => Void ] ] => Void;

=item * B<C<ThisCall( $cb_or_sig )>>: Helper for C++-style C<__thiscall> callbacks. Prepends a C<Pointer[Void]> (the C<this> pointer) to the signature.

=back

=head2 Variadic Functions (VarArgs)

Affix supports C functions that take a variable number of arguments (e.g., C<printf>, C<ioctl>). When defining a
signature, use the C<VarArgs> token at the end of the argument list.

=head3 Basic Usage

lib/Affix/Wrap.pm  view on Meta::CPAN

                return undef unless defined $expr;
                1 while $expr =~ s/^\((.*)\)$/$1/;    # Strip outer parens

                # Resolve bitwise and arithmetic expressions
                if ( $expr =~ /[|&<>+\-*\/]/ ) {
                    my $evaluable = $expr;

                    # Using {} delimiters so the // operator doesn't break the regex parser
                    $evaluable =~ s{\b([a-zA-Z_]\w*)\b}{ $resolve->($1) // $1 }ge;

                    # Clean up any C-style suffixes that might have survived
                    $evaluable =~ s/\b(\d+)L+\b/$1/g;

                    # If everything is now numeric/operators/whitespace, we can safely eval
                    if ( $evaluable =~ /^[0-9\s|&<>\+\-\*\/\(\).xXa-fA-F]+$/ ) {

                        # Using a string eval here to let Perl's engine handle C-like precedence
                        my $res = eval $evaluable;
                        return $cache{$token} = $res if defined $res;
                    }
                }

t/025_affix_wrap.t  view on Meta::CPAN

                    [ 'Evil::system("echo PWNED")', 'parens in name' ],
                    [ 'Evil->pkg',                  'arrow instead of ::' ],
                );
                for my $t (@borderline) {
                    my ( $bad, $desc ) = @$t;
                    ok dies { $binder->generate( 'lib', $bad, '/dev/null' ) }, "rejects $desc: $bad";
                }
            };

            # $lib with backslashes (Windows paths) must be handled
            subtest 'Windows-style lib paths' => sub {
                my $win_lib = 'C:\Users\Test\lib.dll';
                my $pm_file = $dir->child('winpath.pm');
                lives { $binder->generate( $win_lib, 'WinPathTest', $pm_file->stringify ) }
                    or bail_out 'generate() died on Windows path';
                my $content = $pm_file->slurp_utf8;

                # Backslashes are doubled when escaped for q[...] (\\ -> \\\\)
                like $content, qr/q\[.*C:\\\\Users\\\\Test\\\\lib\.dll\]/, 'Windows path safely quoted';
                my ( undef, undef, $exit ) = capture { system $^X, '-Ilib', '-c', $pm_file->stringify };
                is $exit >> 8, 0, 'Generated code compiles with Windows path';

t/035_magic_struct.t  view on Meta::CPAN


        # $root goes out of scope here.
        # In the new system, $element_ref's magic keeps the root alive.
    }

    # ASSERT: We can still read and write to the element
    is $$element_ref, 12345, 'Magical element SV kept root memory alive';
    $$element_ref = 54321;
    is $$element_ref, 54321, 'Magical element SV still writable after root object scope ended';
};
subtest 'Out of bounds (C-style)' => sub {

    # In C, pointers don't have bounds. Our magical system should behave like C.
    # To prevent ACTUAL heap corruption (which crashes the Perl allocator
    # during global destruction), we allocate enough backing memory here.
    my $ptr = Affix::malloc( 15 * sizeof(Int) );
    $ptr = cast( $ptr, Pointer [Int] );

    # This is "dangerous" but should work without a Perl exception
    # because it's just pointer arithmetic.
    #~ ok lives { $ptr->[10] = 0 }, 'Accessing out-of-bounds index does not throw Perl exception';

t/070_security_fixes.t  view on Meta::CPAN


    # We can't easily test the regex directly since \Q is consumed by qr//,
    # but we CAN test that malicious library names don't produce false matches.
    # Verify the regex is compiled (won't die when constructed with evil name)
    my $evil_name = q[m; rm -rf /];
    my $regex     = qr[-l\Q$evil_name\E\.[^\s]+.+\s*=>\s*(.+)$];

    # The escaped name should NOT match a legitimate ldconfig output line
    unlike '-lm.1 => /lib/libm.so.1', $regex, 'Evil name does not match legitimate ldconfig line';

    # A safe name should match ldconfig-style output
    my $safe_name  = 'm';
    my $safe_regex = qr[-l\Q$safe_name\E\.[^\s]+.+\s*=>\s*(.+)$];
    like '-lm.1 => /lib/libm.so.1', $safe_regex, 'Safe name matches ldconfig output format';
};
#
subtest 'H7/H8: Macro name validation prevents symbol table injection' => sub {
    use Affix::Wrap;

    # Valid macro
    my $valid  = Affix::Wrap::Macro->new( name => 'SAFE_CONST', value => '42' );



( run in 2.466 seconds using v1.01-cache-2.11-cpan-aadc1410aed )