Skip to content

Commit

Permalink
h2ph: define all symbols at runtime
Browse files Browse the repository at this point in the history
Preprocessor directives must be processed strictly in order. `#if` and
`#ifdef` directives can inspect the current state of defined symbols.
That's why it is wrong to translate `#define FOO() ...` to `sub foo() {
... }` since subroutine definitions are processed unconditionally at
compile time, before the rest of the code starts running.

In particular,

    unless(defined(&FOO)) {
        sub FOO () { eval q(1); }
    }

is equivalent to

    # at compile time:
    sub FOO () { eval q(1); }

    # ... later, at runtime:
    unless(defined(&FOO)) {
        # does nothing
    }

Fix this case by always wrapping subroutines in eval '...', which moves
the symbol definition to runtime, regardless of what $t (our indentation
state) is.

Similarly, generate `_h2ph_pre.ph` without the functionally useless
`unless (defined &...) { }` blocks. We don't need runtime definitions
(via eval) here because nothing in this file depends on the dynamic
state of macro definitions. It's all `#define`s, no `#if`s.

Fixes Perl#22109.
  • Loading branch information
mauke committed Mar 29, 2024
1 parent 1c90da2 commit b759eae
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 24 deletions.
9 changes: 9 additions & 0 deletions t/lib/h2ph.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@
#define ERROR(x) fprintf(stderr, "%s\n", x[2][3][0])
#endif /* ERROR */

/* check for correct order of definitions vs. conditionals */
#ifdef NOT_DEFINED_HERE()
/* handle indented directives */
#error "NOT_DEFINED_HERE should not be defined at this point!"
#endif

/* function-like macro with no parameters, outside of any conditional */
#define NOT_DEFINED_HERE() 42

#ifndef _H2PH_H_
#define _H2PH_H_

Expand Down
14 changes: 9 additions & 5 deletions t/lib/h2ph.pht
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@ require '_h2ph_pre.ph';

no warnings qw(redefine misc);

unless(defined(&SQUARE)) {
sub SQUARE {
my($x) = @_;
eval 'sub SQUARE {
my($x) = @_;
eval q((($x)*($x)));
}
}
}' unless defined(&SQUARE);
unless(defined(&ERROR)) {
eval 'sub ERROR {
my($x) = @_;
eval q( &fprintf( &stderr, \\"%s\\\\n\\", $x->[2][3][0]));
}' unless defined(&ERROR);
}
if(defined(&NOT_DEFINED_HERE)) {
die("NOT_DEFINED_HERE should not be defined at this point!");
}
eval 'sub NOT_DEFINED_HERE () {
eval q(42);
}' unless defined(&NOT_DEFINED_HERE);
unless(defined(&_H2PH_H_)) {
eval 'sub _H2PH_H_ () {1;}' unless defined(&_H2PH_H_);
# "$Revision h2ph.h,v 1.0 98/05/04 20:42:14 billy $"
Expand Down
27 changes: 8 additions & 19 deletions utils/h2ph.PL
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,6 @@ sub EMIT {
$new = reindent($new);
$args = reindent($args);
if ($t ne '') {
$new =~ s/(['\\])/\\$1/g; #']);
if ($opt_h) {
print OUT $t,
Expand All @@ -402,9 +401,6 @@ sub EMIT {
print OUT $t,
"eval 'sub $name $proto\{\n$t ${args}eval q($new);\n$t}' unless defined(\&$name);\n";
}
} else {
print OUT "unless(defined(\&$name)) {\n sub $name $proto\{\n\t${args}eval q($new);\n }\n}\n";
}
%curargs = ();
return;
}
Expand Down Expand Up @@ -774,7 +770,7 @@ sub inc_dirs
sub build_preamble_if_necessary
{
# Increment $VERSION every time this function is modified:
my $VERSION = 4;
my $VERSION = 5;
my $preamble = "$Dest_dir/_h2ph_pre.ph";
# Can we skip building the preamble file?
Expand Down Expand Up @@ -812,18 +808,16 @@ sub build_preamble_if_necessary
my $def = $define{$_};
$def =~ s/$arg/\$\{$arg\}/g;
print PREAMBLE <<DEFINE;
unless (defined &$macro) { sub $macro(\$) { my (\$$arg) = \@_; \"$def\" } }
sub $macro(\$) { my (\$$arg) = \@_; \"$def\" }
DEFINE
} elsif
($define{$_} =~ /^([+-]?(\d+)?\.\d+([eE][+-]?\d+)?)[FL]?$/) {
# float:
print PREAMBLE
"unless (defined &$_) { sub $_() { $1 } }\n\n";
print PREAMBLE "sub $_() { $1 }\n\n";
} elsif ($define{$_} =~ /^([+-]?\d+)U?L{0,2}$/i) {
# integer:
print PREAMBLE
"unless (defined &$_) { sub $_() { $1 } }\n\n";
print PREAMBLE "sub $_() { $1 }\n\n";
} elsif ($define{$_} =~ /^([+-]?0x[\da-f]+)U?L{0,2}$/i) {
# hex integer
# Special cased, since perl warns on hex integers
Expand All @@ -834,21 +828,16 @@ DEFINE
# platform-specific definition.
my $code = $1;
$code = "hex('$code')" if length $code > 10;
print PREAMBLE
"unless (defined &$_) { sub $_() { $code } }\n\n";
print PREAMBLE "sub $_() { $code }\n\n";
} elsif ($define{$_} =~ /^\w+$/) {
my $def = $define{$_};
if ($isatype{$def}) {
print PREAMBLE
"unless (defined &$_) { sub $_() { \"$def\" } }\n\n";
print PREAMBLE "sub $_() { \"$def\" }\n\n";
} else {
print PREAMBLE
"unless (defined &$_) { sub $_() { &$def } }\n\n";
print PREAMBLE "sub $_() { &$def }\n\n";
}
} else {
print PREAMBLE
"unless (defined &$_) { sub $_() { \"",
quotemeta($define{$_}), "\" } }\n\n";
print PREAMBLE "sub $_() { \"\Q$define{$_}\E\" }\n\n";
}
}
print PREAMBLE "\n1;\n"; # avoid 'did not return a true value' when empty
Expand Down

0 comments on commit b759eae

Please sign in to comment.