summaryrefslogtreecommitdiff
path: root/cil/lib/OutputFile.pm
blob: 8f02ba23e4689a67c6e24615e395f9af9b7618c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
package OutputFile;
@ISA = ();

use strict;
use Carp;
use File::Basename;
use File::Spec;


########################################################################


my $debug = 0;


sub new {
    croak 'bad argument count' unless @_ == 3;
    my ($proto, $basis, $filename) = @_;
    my $class = ref($proto) || $proto;

    $basis = $basis->basis if ref $basis;
    my $ref = { filename => $filename,
		basis => $basis };
    my $self = bless $ref, $class;

    $self->checkRef($filename);
    $self->checkRef($basis);
    $self->checkProtected();
    $self->checkTemporary();

    Carp::cluck "OutputFile: filename == $filename, basis == $basis" if $debug;
    return $self;
}


sub filename {
    my ($self) = @_;
    return $self->{filename};
}


sub basis {
    my ($self) = @_;
    return $self->{basis};
}


########################################################################


sub checkRef {
    my ($self, $filename) = @_;
    confess "ref found where string expected: $filename" if ref $filename;
    confess "stringified ref found where string expected: $filename" if $filename =~ /\w+=HASH\(0x[0-9a-f]+\)/;
}


sub checkTemporary {
    my ($self) = @_;
    my ($basename, $path) = fileparse $self->filename;
    return if $path eq File::Spec->tmpdir . '/';
    confess "found temporary file in wrong directory: ", $self->filename
	if $basename =~ /^cil-[a-zA-Z0-9]{8}\./;
}


########################################################################


my @protected = ();


sub checkProtected {
    my ($self) = @_;
    my $abs = File::Spec->rel2abs($self->filename);

    foreach (@protected) {
	confess "caught attempt to overwrite protected file: ", $self->filename
	    if $_ eq $abs;
    }
}


sub protect {
    my ($self, @precious) = @_;
    push @protected, File::Spec->rel2abs($_)
	foreach @precious;
}


########################################################################


1;

__END__


=head1 Name

OutputFile - base class for intermediate compiler output files

=head1 Description

C<OutputFile> represents an intermediate output file generated by some
stage of a C<Cilly>-based compiler.  This is an abstract base class
and should never be instantiated directly.  It provides common
behaviors used by concrete subclasses L<KeptFile|KeptFile> and
L<TempFile|TempFile>.

=head2 Public Methods

=over

=item filename

An C<OutputFile> instance is a smart wrapper around a file name.  C<<
$out->filename >> returns the name of the file represented by
C<OutputFile> instance C<$out>.  When building a command line, this is
the string to use for the file.  For example:

    my $out = ... ;		# some OutputFile subclass
    my @argv = ('gcc', '-E', '-o', $out->filename, 'input.c');
    system @argv;

C<Cilly> often creates command vectors with a mix of strings and
C<OutputFile> objects.  This is fine, but before using a mixed vector
as a command line, you must replace all C<OutputFile> objects with
their corresponding file names:

    my @mixed = (...);		# mix of strings and objects
    my @normalized = @mixed;
    $_ = (ref $_ ? $_->filename : $_) foreach @normalized;
    system @normalized;

Common utility methods like C<Cilly::runShell> already do exactly this
normalization, but you may need to do it yourself if you are running
external commands on your own.

=item protect

C<OutputFile> contains safety interlocks that help it avoid stomping
on user input files.  C<< OutputFile->protect($precious) >> marks
C<$precious> as a protected input file which should not be
overwritten.  If any C<OutputFile> tries to claim this same file name,
an error will be raised.  In theory, this never happens.  In practice,
scripts can have bugs, and it's better to be safe than sorry.

C<Cilly> uses this method to register input files that it discovers
during command line processing.  If you add special command line
processing of your own, or if you identify input files through other
means, we highly recommend using this method as well.  Otherwise,
there is some risk that a buggy client script could mistakenly create
an output file that destroys the user's source code.

Note that C<protect> is a class method: call it on the C<OutputFile>
module, rather than on a specific instance.

=back

=head2 Internal Methods

The following methods are used within C<OutputFile> or by
C<OutputFile> subclasses.  They are not intended for use by outside
scripts.

=over

=item basis

In addition to L<its own file name|/"filename">, each C<OutputFile>
instance records a second file name: its I<basis>.  The basis file
name is initialized and used differently by different subclasses, but
typically represents the input file from which this output file is
derived.  C<< $out->basis >> returns the basis file name for instance
C<$out>.

When instantiating an C<OutputFile>, the caller can provide either a
file name string as the basis or another C<OutputFile> instance.
However, basis file names are not chained: if C<< $a->basis >> is
F<foo.c>, and C<$b> is constructed with C<$a> as its basis, C<<
$b->basis >> will return F<foo.c>, not C<$a> or C<< $a->filename >>.
This flattening is done at construction time.

See L<KeptFile/"new"> and L<TempFile/"new"> for more details on how
basis file names are used.

=item checkRef

C<< OutputFile->checkRef($filename) >> raises an error if C<$filename>
is an object reference, or looks like the string representation of an
object reference.  Used to sanity check arguments to various methods.

=item checkTemporary

C<< $out->checkTemporary >> raises an error if C<< $out->filename >>
looks like a temporary file name but is not in the system temporary
directory.  Used to sanity check arguments in various methods.

=item checkProtected

C<< $out->checkProtected >> raises an error if C<< $out->filename >>
is listed as a protected file.  This check, performed at construction
time, implements a safety interlock to prevent overwriting of user
input files.  Protected files are registered using L<"protect">.

=back

=head1 See Also

L<KeptFile>, L<TempFile>.

=cut