Affix

 view release on metacpan or  search on metacpan

infix/include/infix/infix.h  view on Meta::CPAN

 *     printf("Result: %d\n", result); // Output: Result: 42
 *
 *     infix_forward_destroy(trampoline);
 *     return 0;
 * }
 * ```
 */
#pragma once
/**
 * @defgroup version_info Version Information
 * @brief Macros defining the semantic version of the infix library.
 * @details The versioning scheme follows Semantic Versioning 2.0.0 (SemVer).
 * @{
 */
#define INFIX_MAJOR 0 /**< The major version number. Changes with incompatible API updates. */
#define INFIX_MINOR 2 /**< The minor version number. Changes with new, backward-compatible features. */
#define INFIX_PATCH 1 /**< The patch version number. Changes with backward-compatible bug fixes. */

#if defined(__has_c_attribute)
#define _INFIX_HAS_C_ATTRIBUTE(x) __has_c_attribute(x)
#else

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


// C++ Headers must be included BEFORE extern "C"
#if defined(__cplusplus)
#include <atomic>
#endif

#ifdef __cplusplus
extern "C" {
#endif

// Portability Macros for Atomics and Thread-Local Storage
#if defined(__cplusplus)
#define TAP_ATOMIC_SIZE_T std::atomic<size_t>
#define TAP_ATOMIC_FETCH_ADD(ptr, val) std::atomic_fetch_add(ptr, (size_t)(val))
#define TAP_ATOMIC_INIT(val) (val)
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L && !defined(__STDC_NO_ATOMICS__)
#include <stdatomic.h>
#define TAP_ATOMIC_SIZE_T _Atomic size_t
#define TAP_ATOMIC_FETCH_ADD(ptr, val) atomic_fetch_add(ptr, val)
#define TAP_ATOMIC_INIT(val) = val
#elif defined(__GNUC__) || defined(__clang__)

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

void tap_skip(size_t count, const char * reason, ...) DBLTAP_PRINTF_FORMAT(2, 3);
void tap_skip_all(const char * reason, ...) DBLTAP_PRINTF_FORMAT(1, 2);
void diag(const char * fmt, ...) DBLTAP_PRINTF_FORMAT(1, 2);
void tap_note(const char * fmt, ...) DBLTAP_PRINTF_FORMAT(1, 2);
void test_body(void);

#ifdef __cplusplus
}
#endif

// Public Test Harness Macros
/** @brief Declares the total number of tests to be run in the current scope. Must be called before any tests. */
#define plan(count) tap_plan(count)
/** @brief Concludes testing, validates the plan, and returns an exit code based on success or failure. */
#define done() tap_done()
/** @brief Immediately terminates the entire test suite with a failure message. Useful for fatal setup errors. */
#define bail_out(...) tap_bail_out(__VA_ARGS__)
/** @brief The core assertion macro. Checks a condition and prints an "ok" or "not ok" TAP line with diagnostics on
 * failure. */
#define ok(cond, ...) tap_ok(!!(cond), __FILE__, __LINE__, __func__, #cond, __VA_ARGS__)
/** @brief A convenience macro that always passes. Equivalent to `ok(true, ...)`. */

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

 *
 * Its most critical function is to select the correct **Application Binary Interface (ABI)**
 * implementation to use for JIT code generation. This is achieved through a cascade
 * of preprocessor checks that can be overridden by the user for cross-compilation.
 * By the end of this file, exactly one `INFIX_ABI_*` macro must be defined, which
 * determines which `abi_*.c` file is included in the unity build.
 *
 * @internal
 */
#pragma once
// System Feature Test Macros
/**
 * @details These macros are defined to ensure that standard POSIX and other
 * necessary function declarations (like `dlopen`, `dlsym`, `snprintf`, `shm_open`)
 * are made available by system headers in a portable way across different C
 * library implementations (glibc, musl, BSD libc, etc.). Failing to define these
 * can lead to compilation failures due to implicitly declared functions on
 * stricter build environments.
 */
#if !defined(_POSIX_C_SOURCE)
#define _POSIX_C_SOURCE 200809L

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

 * stub. It receives the marshalled arguments and dispatches the call to either
 * the type-safe callback (via a cached forward trampoline) or the generic closure handler.
 * @param[in] context The `infix_reverse_t` context for this call.
 * @param[out] return_value_ptr A pointer to the stack buffer for the return value.
 * @param[in] args_array A pointer to the `void**` array of argument pointers.
 */
INFIX_INTERNAL void infix_internal_dispatch_callback_fn_impl(infix_reverse_t * context,
                                                             void * return_value_ptr,
                                                             void ** args_array);

// Utility Macros & Inlines
/** @brief Appends a sequence of bytes (e.g., an instruction opcode) to a code buffer. */
#define EMIT_BYTES(buf, ...)                             \
    do {                                                 \
        const uint8_t bytes[] = {__VA_ARGS__};           \
        code_buffer_append((buf), bytes, sizeof(bytes)); \
    } while (0)
/**
 * @brief Aligns a value up to the next multiple of a power-of-two alignment.
 * @param value The value to align.
 * @param alignment The alignment boundary (must be a power of two).

lib/Affix.h  view on Meta::CPAN

    HV * enum_registry;
    // Cache for coercion strings to avoid re-fetching from SV objects
    HV * coercion_cache;
    HV * stash_pointer;  // Cache for Affix::Pointer stash
} my_cxt_t;
START_MY_CXT;
/** @} */

// Helper macro to fetch a value from a hash if it exists, otherwise return a default.
#define hv_existsor(hv, key, _or) hv_exists(hv, key, strlen(key)) ? *hv_fetch(hv, key, strlen(key), 0) : _or
// Macros to handle passing the Perl interpreter context ('THX') explicitly,
// which is necessary for thread-safe code.
#ifdef MULTIPLICITY
#define storeTHX(var) (var) = aTHX
#define dTHXfield(var) tTHX var;
#else
#define storeTHX(var) dNOOP
#define dTHXfield(var)
#endif

// Forward-declare the primary structures.

lib/Affix.h  view on Meta::CPAN

#ifdef newXS_flags
#define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0)
#else
#define newXSproto_portable(name, c_impl, file, proto) \
    (PL_Sv = (SV *)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV *)PL_Sv)
#endif
#define newXS_deffile(a, b) Perl_newXS_deffile(aTHX_ a, b)
#define export_function(package, what, tag) \
    _export_function(aTHX_ get_hv(form("%s::EXPORT_TAGS", package), GV_ADD), what, tag)

// Debugging Macros
#if DEBUG > 1
#define PING warn("Ping at %s line %d", __FILE__, __LINE__);
#else
#define PING
#endif
#define DumpHex(addr, len) _DumpHex(aTHX_ addr, len, __FILE__, __LINE__)
void _DumpHex(pTHX_ const void *, size_t, const char *, int);
#define DD(scalar) _DD(aTHX_ scalar, __FILE__, __LINE__)
void _DD(pTHX_ SV *, const char *, int);

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

        method affix {
            return $definition->affix if defined $definition;
            return $type->affix       if builtin::blessed($type);

            # Fallback: if it's just a string, wrap it in a Reference object
            return Affix::Type::Reference->new( name => $type =~ s/^@//r ) if defined $type;
            return Affix::Void();
        }
    }
    class    #
        Affix::Wrap::Macro : isa(Affix::Wrap::Entity) {
        field $value : reader : param //= ();
        method set_value ($v) { $value = $v }

        method affix_type {
            my $v = $self->value // return '';

            # Sanitize C string concatenations in macros (e.g. "a" "b" -> "ab")
            $v =~ s/"\s+"//g;

            # Strip outer quotes from C string literals

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

        method _walk( $node, $acc, $current_file ) {
            return unless ref $node eq 'HASH';
            my $kind      = $node->{kind} // 'Unknown';
            my $node_file = $self->_get_node_file($node);
            if ($node_file) {
                $current_file   = $self->_normalize($node_file);
                $last_seen_file = $current_file;
            }
            elsif ( defined $last_seen_file ) { $current_file = $last_seen_file; }
            if    ( $self->_is_valid_file($current_file) && !$node->{isImplicit} ) {
                if ( $kind eq 'MacroDefinitionRecord' ) {
                    if ( $node->{range} ) { $self->_macro( $node, $acc, $current_file ); }
                }
                elsif ( $kind eq 'TypedefDecl' ) { $self->_typedef( $node, $acc, $current_file ); }
                elsif ( $kind eq 'RecordDecl' || $kind eq 'CXXRecordDecl' ) {
                    $self->_record( $node, $acc, $current_file );
                    return;
                }
                elsif ( $kind eq 'EnumDecl' ) {
                    $self->_enum( $node, $acc, $current_file );
                    return;

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

            my $t = $self->_extract_trailing( $f, $e );
            return $d unless defined $t && length $t;
            return $t unless defined $d && length $d;
            return "$d\n$t";
        }

        method _macro( $n, $acc, $f ) {
            my ( $s, $e, $l, $el ) = $self->_meta($n);
            my $val = $self->_extract_macro_val( $n, $f );
            push @$acc,
                Affix::Wrap::Macro->new(
                name         => $n->{name},
                file         => $f,
                line         => $l,
                end_line     => $el,
                value        => $val,
                doc          => $self->_extract_doc( $f, $s ),
                start_offset => $s,
                end_offset   => $e
                );
        }

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

                    $v =~ s/\/\/.*$//;
                    $v =~ s/\/\*.*?\*\///g;
                    $v =~ s/^\s+|\s+$//g;
                    return $v;
                }
            }
            '';
        }

        method _scan_macros_fallback($acc) {
            my %seen = map { $_->name => 1 } grep { ref($_) eq 'Affix::Wrap::Macro' } @$acc;
            for my $f ( keys %$allowed_files ) {
                next unless $self->_is_valid_file($f);
                my $c = $self->_get_content($f);
                while ( $c =~ /^\s*#\s*define\s+(\w+)(?:[ \t]+(.*?))?\s*$/mg ) {
                    my $name = $1;
                    next if $seen{$name};
                    my $val  = $2 // '';
                    my $off  = $-[0];
                    my $end  = $+[0];
                    my $pre  = substr( $c, 0, $off );
                    my $line = ( $pre =~ tr/\n// ) + 1;
                    $val =~ s/\/\/.*$//;
                    $val =~ s/\/\*.*?\*\///g;
                    $val =~ s/^\s+|\s+$//g;
                    push @$acc,
                        Affix::Wrap::Macro->new(
                        name         => $name,
                        file         => $f,
                        line         => $line,
                        end_line     => $line,
                        value        => $val,
                        doc          => $self->_extract_doc( $f, $off ),
                        start_offset => $off,
                        end_offset   => $end
                        );
                }

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


        method _read($f) {
            my $abs = $self->_normalize($f);
            return $file_cache->{$abs} if exists $file_cache->{$abs};
            return $file_cache->{$abs} = Path::Tiny::path($f)->slurp_utf8;
        }

        method _scan( $f, $acc ) {
            my $c = $self->_read($f);

            # Macros
            while ( $c =~ /^\s*#\s*define\s+(\w+)(?:[ \t]+(.*?))?$/gm ) {
                my $name = $1;
                my $val  = $2 // '';
                my $s    = $-[0];
                my $e    = $+[0];
                $val =~ s/\/\/.*$//;
                $val =~ s/\/\*.*?\*\///g;
                $val =~ s/^\s+|\s+$//g;
                push @$acc,
                    Affix::Wrap::Macro->new(
                    name         => $name,
                    value        => $val,
                    file         => $f,
                    line         => $self->_ln( $c, $s ),
                    end_line     => $self->_ln( $c, $e ),
                    doc          => $self->_doc( $c, $s ),
                    start_offset => $s,
                    end_offset   => $e
                    );
            }

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

        method parse( $entry_point //= () ) {
            $entry_point //= $project_files->[0];
            my @nodes = $driver->parse( $entry_point, $include_dirs );
            $self->_resolve_macros( \@nodes );
            return @nodes;
        }

        method _resolve_macros ($nodes) {
            my %macros;
            for my $node (@$nodes) {
                if ( $node isa Affix::Wrap::Macro ) {
                    my $val = $node->value // '';
                    $val =~ s/(?<=\d)[Uu][Ll]{0,2}//g;    # Strip C suffixes like 100ULL
                    $macros{ $node->name } = $val;
                }
            }
            my %cache;
            my $resolve;
            $resolve = sub {
                my ($token) = @_;
                return undef unless defined $token;

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

                        # 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;
                    }
                }

                # Fallback: Treat as simple alias (A -> B)
                return $cache{$token} = $resolve->($expr);
            };
            for my $node (@$nodes) {
                if ( $node isa Affix::Wrap::Macro ) {
                    my $val = $resolve->( $node->name );
                    $node->set_value($val) if defined $val;
                }
            }
        }

        method _generate_code( $lib, $pkg ) {
            Carp::croak("Affix::Wrap::generate/wrap requires a valid package name") unless defined $pkg && $pkg =~ /^[a-zA-Z_]\w*(::\w+)*$/;
            my @nodes = $self->parse;
            my %unique_types;
            my %referenced_names;
            for my $node (@nodes) {
                if ( $node->can('name') && $node->name && $node->name ne '(anonymous)' ) {
                    next if $node isa Affix::Wrap::Macro || $node isa Affix::Wrap::Function || $node isa Affix::Wrap::Variable;
                    $unique_types{ $node->name } = $node;
                }

                # Collect dependencies from affix_type strings (e.g. '@sockaddr')
                my $sig = eval { $node->affix . "" } // '';
                while ( $sig =~ /@([a-zA-Z_]\w*)/g ) { $referenced_names{$1} = 1; }
            }

            # Atomic Engine Batch
            my @fwd = sort keys %{ { map { $_ => 1 } ( keys %unique_types, keys %referenced_names ) } };

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

                $out .= "    typedef '$name';\n";
            }
            $out .= $batch_str;
            $out .= "\n    #\n";
            for my $node (@nodes) {
                $out .= "    " . $node->perl_constants . "\n" if $node isa Affix::Wrap::Enum;
            }
            $out .= "\n    #\n";
            for my $node (@nodes) {
                my $code = $node->affix_type;
                if ( $code && ( $node isa Affix::Wrap::Function || $node isa Affix::Wrap::Variable || $node isa Affix::Wrap::Macro ) ) {
                    $out .= "    $code;\n";
                }
            }
            $out .= "};\n1;\n";
        }

        method generate( $lib, $pkg, $file ) {
            my ( $code, $nodes ) = $self->_generate_code( $lib, $pkg );
            Path::Tiny::path($file)->spew_utf8($code);
        }

        method wrap ( $lib, $pkg //= [caller]->[0] ) {
            my ( $code, $nodes ) = $self->_generate_code( $lib, $pkg );
            eval $code;
            if ($@) {
                Carp::croak("Affix::Wrap wrap() compilation failed: $@\n\nCode:\n$code");
            }
            return grep { $_ isa Affix::Wrap::Function || $_ isa Affix::Wrap::Variable || $_ isa Affix::Wrap::Macro } @$nodes;
        }

        method list_symbols ($lib_path) {
            my $abs = path($lib_path)->absolute->stringify;
            return [] unless -e $abs;
            my @symbols;
            my ( $out, $err, $exit );

            # llvm-nm
            # We use --extern-only to find the public API

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

What C<Affix::Wrap> extracts and bridges from your headers:

=over

=item * Function signatures (including pointer-to-function arguments)

=item * Nested Structs and Unions

=item * Enums (maps them to Affix Dualvar Enums)

=item * Macros (numeric and string constants)

=item * Typedefs (follows deep typedef chains)

=item * Extern Global Variables (binds them via C<Affix::pin>)

=item * Doxygen/Markdown Comments (extracts to POD when generating modules)

=back

=head1 CONSTRUCTOR

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

A global C<extern> variable.

=over

=item * C<affix_type>: Returns string C<pin my $var, $lib, name =E<gt> Type>.

=item * C<affix( $lib, $pkg )>: Installs the variable accessor into C<$pkg>.

=back

=head2 Affix::Wrap::Macro

A preprocessor C<#define>. Only simple value macros are captured.

=over

=item * C<affix_type>: Returns string C<use constant Name =E<gt> Value>. Expressions (e.g., C<A + B>) are quoted as strings, while literals are preserved.

=item * C<affix( undef, $pkg )>: Installs the constant into C<$pkg>.

=back

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

            is( $buf->value, '1024', 'BUF_SIZE value' );
            like( $buf->doc, qr/Buffer Size/, 'BUF_SIZE doc' );
            my ($calc) = grep { $_->name eq 'CALC_VAL' } @objs;
            ok( $calc, 'Found CALC_VAL' );

            # affix_type should quote expressions: '(10 + 20)' -> "'(10 + 20)'" or similar
            like( $calc->affix_type, qr/'?\(10 \+ 20\)'?/, 'Expression quoted in affix_type' );

            # Test bitwise OR resolution in wrap()
            my $wrap   = Affix::Wrap->new( driver => $parser );
            my $target = "Test::Macro::" . $label;
            $target =~ s/\W+/_/g;
            $wrap->wrap( undef, $target );
            is( $target->can('FLAGS_AB')->(), 3, 'FLAGS_AB resolved to 3' );
        };
        subtest 'Records (Structs & Unions)' => sub {
            my $dir = Path::Tiny->tempdir;
            spew_files(
                $dir,
                'structs.h' => <<'EOF',
/** @brief A Point */

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


    # 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' );
    my $result = $valid->affix( undef, 'Test::H7Valid' );
    ok defined $result, 'affix() returns for valid name';

    # The generated constant should work via use constant
    my $code = $valid->affix_type;
    ok eval "package Test::H7Valid; $code; 1", 'Generated constant compiles' or diag "eval error: $@";

    # Evil macro name — should NOT install into symbol table
    my $evil = Affix::Wrap::Macro->new( name => 'EVIL; system("echo PWNED")', value => '1', );
    $result = $evil->affix( undef, 'Test::H7Evil' );
    ok defined $result, 'affix() returns for evil name (no crash)';

    # The evil name must NOT be accessible as a subroutine in the target package
    ok !defined $Test::H7Evil::{'EVIL; system("echo PWNED")'}, 'Evil name NOT installed in symbol table';
};
#
subtest 'H7/H8: Enum constant name validation' => sub {
    use Affix qw[Enum];
    my $type = Enum [ [ RED => 0 ], [ GREEN => 1 ], [ BLUE => 2 ], ];
    ok defined $type, 'Enum type created';
    my ( $const_map, $val_map ) = $type->resolve();
    is ref $const_map, 'HASH', 'const_map is a hash';
    ok exists $const_map->{RED},   'RED in const_map';
    ok exists $const_map->{GREEN}, 'GREEN in const_map';
    ok exists $const_map->{BLUE},  'BLUE in const_map';
};
#
subtest 'M4: Double-quote C string literal stripping in affix_type' => sub {
    use Affix::Wrap;
    my $macro = Affix::Wrap::Macro->new( name => 'GREETING', value => '"Hello World"', );
    my $code  = $macro->affix_type;
    like $code,   qr/use constant GREETING => 'Hello World'/, 'Double-quoted string stripped of outer quotes';
    unlike $code, qr/"Hello World"/,                          'No remaining double quotes in generated code';
    ok eval "package Test::M4; $code; 1", 'Generated constant compiles' or diag "eval error: $@";
};
#
subtest 'M5: Path traversal prevention in Affix::Build output name' => sub {
    skip_all 'No CC' unless $Config{cc};
    use Affix::Build;
    my $TMP_DIR = Path::Tiny->tempdir( CLEANUP => 1 );



( run in 3.132 seconds using v1.01-cache-2.11-cpan-14f38c9f855 )