diff options
author | marcel <marcel@FreeBSD.org> | 2014-10-23 22:30:14 +0000 |
---|---|---|
committer | marcel <marcel@FreeBSD.org> | 2014-10-23 22:30:14 +0000 |
commit | fefcd296e4716886912a74acd38abba4c94bc340 (patch) | |
tree | ce8b178967e5d33cbba89d0b2f3d2c2ea45616cb /contrib/libxo/xolint | |
parent | bc4f095bf4554f6047f55bd8d28df41679cade6b (diff) | |
download | FreeBSD-src-fefcd296e4716886912a74acd38abba4c94bc340.zip FreeBSD-src-fefcd296e4716886912a74acd38abba4c94bc340.tar.gz |
Import libxo 0.1.4
Obtained from: https://github.com/Juniper/libxo
Sponsored by: Juniper Networks, Inc.
Diffstat (limited to 'contrib/libxo/xolint')
-rw-r--r-- | contrib/libxo/xolint/Makefile.am | 14 | ||||
-rw-r--r-- | contrib/libxo/xolint/xolint.1 | 89 | ||||
-rwxr-xr-x | contrib/libxo/xolint/xolint.pl | 556 |
3 files changed, 659 insertions, 0 deletions
diff --git a/contrib/libxo/xolint/Makefile.am b/contrib/libxo/xolint/Makefile.am new file mode 100644 index 0000000..0f8ef62 --- /dev/null +++ b/contrib/libxo/xolint/Makefile.am @@ -0,0 +1,14 @@ +# +# Copyright 2014, Juniper Networks, Inc. +# All rights reserved. +# This SOFTWARE is licensed under the LICENSE provided in the +# ../Copyright file. By downloading, installing, copying, or otherwise +# using the SOFTWARE, you agree to be bound by the terms of that +# LICENSE. + +man_MANS = xolint.1 + +EXTRA_DIST = xolint.1 xolint.pl + +install-exec-hook: + install ${srcdir}/xolint.pl ${bindir}/xolint diff --git a/contrib/libxo/xolint/xolint.1 b/contrib/libxo/xolint/xolint.1 new file mode 100644 index 0000000..b7ed130 --- /dev/null +++ b/contrib/libxo/xolint/xolint.1 @@ -0,0 +1,89 @@ +.\" # +.\" # Copyright (c) 2014, Juniper Networks, Inc. +.\" # All rights reserved. +.\" # This SOFTWARE is licensed under the LICENSE provided in the +.\" # ../Copyright file. By downloading, installing, copying, or +.\" # using the SOFTWARE, you agree to be bound by the terms of that +.\" # LICENSE. +.\" # Phil Shafer, July 2014 +.\" +.Dd July, 2014 +.Dt LIBXO 3 +.Os +.Sh NAME +.Nm xolint +.Nd detect errors in programs using xo_emit +.Sh SYNOPSIS +.Nm xolint +.Op Fl c +.Op Fl "C <flags>" +.Op Fl "d" +.Op Fl "D" +.Op Fl "I" +.Op Fl "p" +.Op Fl "V" +.Op Fl "X" +.Op Ar files... +.Sh DESCRIPTION +xolint is a tool for reporting common mistakes in format strings +in source code that invokes xo_emit(). It allows these errors +to be diagnosed at build time, rather than waiting until runtime. +.Pp +xolint takes the one or more C files as arguments, and reports +and errors, warning, or informational messages as needed. +.Bl -tag -width "C <flags>" +.It Fl c +Invoke 'cpp' against the input file +.It Fl "C <flags>" +Flags that are passed to 'cpp +.It Fl "d" +Enable debug output +.It Fl "D" +Generate documentation for all xolint messages +.It Fl "I" +Generate a table of xo_info_t structures. +.It Fl "p" +Print the offending lines after the error message is displayed +.It Fl "V" +Do not report errors, but instead print a complete list of +all field names, sorted alphabetically. The output can help spot +inconsistencies and spelling errors. +.It Fl "X" +Extract samples from xolint, suitable for internal testing. +.El +.Pp +Output message contain the source filename and line number, the +class of the message, the message, and, if +.Fl p +is given, the +line that contains the error: +.Bd -literal -offset indent + % xolint -t xolint.c + xolint.c: 16: error: anchor format should be "%d" + 16 xo_emit("{[:/%s}"); +.Ed +.Pp +.Sh ADDITIONAL DOCUMENTATION +.Pp +Complete documentation can be found on github: +.Bd -literal -offset indent +http://juniper.github.io/libxo/libxo-manual.html +.Ed +.Pp +libxo lives on github as: +.Bd -literal -offset indent +https://github.com/Juniper/libxo +.Ed +.Pp +The latest release of libxo is available at: +.Bd -literal -offset indent +https://github.com/Juniper/libxo/releases +.Ed +.Sh SEE ALSO +.Xr xo_emit 3 +.Sh HISTORY +The +.Fa libxo +library was added in FreeBSD 10.1. +.Sh AUTHOR +Phil Shafer diff --git a/contrib/libxo/xolint/xolint.pl b/contrib/libxo/xolint/xolint.pl new file mode 100755 index 0000000..8693e62 --- /dev/null +++ b/contrib/libxo/xolint/xolint.pl @@ -0,0 +1,556 @@ +#!/usr/bin/env perl +# +# Copyright (c) 2014, Juniper Networks, Inc. +# All rights reserved. +# This SOFTWARE is licensed under the LICENSE provided in the +# ../Copyright file. By downloading, installing, copying, or otherwise +# using the SOFTWARE, you agree to be bound by the terms of that +# LICENSE. +# Phil Shafer, August 2014 +# +# +# xolint -- a lint for inspecting xo_emit format strings +# +# Yes, that's a long way to go for a pun. + +%vocabulary = (); + +sub main { + while ($ARGV[0] =~ /^-/) { + $_ = shift @ARGV; + $opt_cpp = 1 if /^-c/; + $opt_cflags .= shift @ARGV if /^-C/; + $opt_debug = 1 if /^-d/; + extract_docs() if /^-D/; + $opt_info = $opt_vocabulary = 1 if /^-I/; + $opt_print = 1 if /^-p/; + $opt_vocabulary = 1 if /^-V/; + extract_samples() if /^-X/; + } + + for $file (@ARGV) { + parse_file($file); + } + + if ($opt_info) { + print "static xo_info_t xo_info_table[] = {\n"; + for $name (sort(keys(%vocabulary))) { + print " { \"", $name, "\", \"type\", \"desc\" },\n"; + } + print "};\n"; + print "static int xo_info_count = " + . "(sizeof(xo_info_table) / sizeof(xo_info_table[0]));\n\n"; + print "#define XO_SET_INFO() \\\n"; + print " xo_set_info(NULL, xo_info_table, xo_info_count)\n"; + } elsif ($opt_vocabulary) { + for $name (sort(keys(%vocabulary))) { + print $name, "\n"; + } + } +} + +sub extract_samples { + my $x = "\#" . "\@"; + my $cmd = "grep -B1 -i '$x Should be' $0 | grep xo_emit | sed 's/.*\#*\@//'"; + system($cmd); + exit(0); +} + +sub extract_docs { + my $x = "\#" . "\@"; + my $cmd = "grep -B1 '$x' $0"; + open INPUT, "$cmd |"; + local @input = <INPUT>; + close INPUT; + my $ln, $new = 0, $first = 1, $need_nl; + + for ($ln = 0; $ln <= $#input; $ln++) { + chomp($_ = $input[$ln]); + if (/^--/) { + $ln += 1; + $new = 1; + next; + } + if ($first) { + $new = 1; + $first = 0; + next; + } + + s/\s*\#\@\s*//; + + if ($new) { + if ($need_nl) { + print "\n\n"; + $need_nl = 0; + } + + print "*** '$_'\n\n"; + print "The message \"$_\" can be caused by code like:\n\n"; + $new = 0; + + } elsif (/xo_emit\s*\(/) { + s/^\s+//; + print " $_\n\n"; + + } elsif (/^Should be/i) { + print "This code should be replaced with code like:\n\n"; + + } else { + print "$_\n"; + $need_nl = 1; + } + } + + exit(0); +} + +sub parse_file { + local($file) = @_; + local($errors, $warnings, $info) = (0, 0, 0); + local $curfile = $file; + local $curln = 0; + + if ($opt_cpp) { + die "no such file" unless -f $file; + open INPUT, "cpp $opt_cflags $file |"; + } else { + open INPUT, $file || die "cannot open input file '$file'"; + } + local @input = <INPUT>; + close INPUT; + + local $ln, $rln, $line, $replay; + + for ($ln = 0; $ln < $#input; $ln++) { + $line = $input[$ln]; + $curln += 1; + + if ($line =~ /^\#/) { + my($num, $fn) = ($line =~ /\#\s*(\d+)\s+"(.+)"/); + ($curfile, $curln) = ($fn, $num) if $num; + next; + } + + next unless $line =~ /xo_emit\(/; + + @tokens = parse_tokens(); + print "token:\n '" . join("'\n '", @tokens) . "'\n" + if $opt_debug; + check_format($tokens[0]); + } + + print $file . ": $errors errors, $warnings warnings, $info info\n" + unless $opt_vocabulary; +} + +sub parse_tokens { + my $full = "$'"; + my @tokens = (); + my %pairs = ( "{" => "}", "[" => "]", "(" => ")" ); + my %quotes = ( "\"" => "\"", "'" => "'" ); + local @data = split(//, $full); + local @open = (); + local $current = ""; + my $quote = ""; + local $off = 0; + my $ch; + + $replay = $curln . " " . $line; + $rln = $ln + 1; + + for (;;) { + get_tokens() if $off > $#data; + die "out of data" if $off > $#data; + $ch = $data[$off++]; + + print "'$ch' ($quote) ($#open) [" . join("", @open) . "]\n" + if $opt_debug; + + last if $ch eq ";" && $#open < 0; + + if ($ch eq "," && $quote eq "" && $#open < 0) { + print "[$current]\n" if $opt_debug; + push @tokens, $current; + $current = ""; + next; + } + + next if $ch =~ /[ \t\n\r]/ && $quote eq "" && $#open < 0; + + $current .= $ch; + + if ($quote) { + if ($ch eq $quote) { + $quote = ""; + } + next; + } + if ($quotes{$ch}) { + $quote = $quotes{$ch}; + $current = substr($current, 0, -2) if $current =~ /""$/; + next; + } + + if ($pairs{$ch}) { + push @open, $pairs{$ch}; + next; + } + + if ($#open >= 0 && $ch eq $open[$#open]) { + pop @open; + next; + } + } + + push @tokens, substr($current, 0, -1); + return @tokens; +} + +sub get_tokens { + if ($ln + 1 < $#input) { + $line = $input[++$ln]; + $curln += 1; + $replay .= $curln . " " . $line; + @data = split(//, $line); + $off = 0; + } +} + +sub check_format { + my($format) = @_; + + return unless $format =~ /^".*"$/; + + my @data = split(//, $format); + my $ch; + my $braces = 0; + local $count = 0; + my $content = ""; + my $off; + my $phase = 0; + my @build = (); + local $last, $prev = ""; + + # Nukes quotes + pop @data; + shift @data; + + for (;;) { + last if $off > $#data; + $ch = $data[$off++]; + + if ($ch eq "\\") { + $ch = $data[$off++]; + $off += 1 if $ch eq "\\"; # double backslash: "\\/" + next; + } + + if ($braces) { + if ($ch eq "}") { + check_field(@build); + $braces = 0; + @build = (); + $phase = 0; + next; + } elsif ($phase == 0 && $ch eq ":") { + $phase += 1; + next; + } elsif ($ch eq "/") { + $phase += 1; + next; + } + + } else { + if ($ch eq "{") { + check_text($build[0]) if length($build[0]); + $braces = 1; + @build = (); + $last = $prev; + next; + } + } + + $prev = $ch; + $build[$phase] .= $ch; + } + + if ($braces) { + error("missing closing brace"); + check_field(@build); + } else { + check_text($build[0]) if length($build[0]); + } +} + +sub check_text { + my($text) = @_; + + print "checking text: [$text]\n" if $opt_debug; + + #@ A percent sign appearing in text is a literal + #@ xo_emit("cost: %d", cost); + #@ Should be: + #@ xo_emit("{L:cost}: {:cost/%d}", cost); + #@ This can be a bit surprising and could be a field that was not + #@ properly converted to a libxo-style format string. + info("a percent sign appearing in text is a literal") if $text =~ /%/; +} + +sub check_field { + my(@field) = @_; + print "checking field: [" . join("][", @field) . "]\n" if $opt_debug; + + if ($opt_vocabulary) { + $vocabulary{$field[1]} = 1 + if $field[1] && $field[0] !~ /[DELNPTUW\[\]]/; + return; + } + + #@ Last character before field definition is a field type + #@ A common typo: + #@ xo_emit("{T:Min} T{:Max}"); + #@ Should be: + #@ xo_emit("{T:Min} {T:Max}"); + #@ Twiddling the "{" and the field role is a common typo. + info("last character before field definition is a field type ($last)") + if $last =~ /[DELNPTUVW\[\]]/ && $field[0] !~ /[DELNPTUVW\[\]]/; + + #@ Encoding format uses different number of arguments + #@ xo_emit("{:name/%6.6s %%04d/%s}", name, number); + #@ Should be: + #@ xo_emit("{:name/%6.6s %04d/%s-%d}", name, number); + #@ Both format should consume the same number of arguments off the stack + my $cf = count_args($field[2]); + my $ce = count_args($field[3]); + warn("encoding format uses different number of arguments ($cf/$ce)") + if $ce >= 0 && $cf >= 0 && $ce != $cf; + + #@ Only one field role can be used + #@ xo_emit("{LT:Max}"); + #@ Should be: + #@ xo_emit("{T:Max}"); + my(@roles) = ($field[0] !~ /([DELNPTUVW\[\]]).*([DELNPTUVW\[\]])/); + error("only one field role can be used (" . join(", ", @roles) . ")") + if $#roles > 0; + + # Field is a note, label, or title + if ($field[0] =~ /[DLNT]/) { + + #@ Potential missing slash after N, L, or T with format + #@ xo_emit("{T:%6.6s}\n", "Max"); + #@ should be: + #@ xo_emit("{T:/%6.6s}\n", "Max"); + #@ The "%6.6s" will be a literal, not a field format. While + #@ it's possibly valid, it's likely a missing "/". + info("potential missing slash after N, L, or T with format") + if $field[1] =~ /%/; + + #@ Format cannot be given when content is present (roles: DNLT) + #@ xo_emit("{T:Max/%6.6s}", "Max"); + #@ Fields with the D, N, L, or T roles can't have both + #@ static literal content ("{T:Title}") and a + #@ format ("{T:/%s}"). + #@ This error will also occur when the content has a backslash + #@ in it, like "{N:Type of I/O}"; backslashes should be escaped, + #@ like "{N:Type of I\\/O}". Note the double backslash, one for + #@ handling 'C' strings, and one for libxo. + error("format cannot be given when content is present") + if $field[1] && $field[2]; + + #@ An encoding format cannot be given (roles: DNLT) + #@ xo_emit("{T:Max//%s}", "Max"); + #@ Fields with the D, N, L, and T roles are not emitted in + #@ the 'encoding' style (JSON, XML), so an encoding format + #@ would make no sense. + error("encoding format cannot be given when content is present") + if $field[3]; + } + + # A value field + if (length($field[0]) == 0 || $field[0] =~ /V/) { + + #@ Value field must have a name (as content)") + #@ xo_emit("{:/%s}", "value"); + #@ Should be: + #@ xo_emit("{:tag-name/%s}", "value"); + #@ The field name is used for XML and JSON encodings. These + #@ tags names are static and must appear directly in the + #@ field descriptor. + error("value field must have a name (as content)") + unless $field[1]; + + #@ Use hyphens, not underscores, for value field name + #@ xo_emit("{:no_under_scores}", "bad"); + #@ Should be: + #@ xo_emit("{:no-under-scores}", "bad"); + #@ Use of hyphens is traditional in XML, and the XOF_UNDERSCORES + #@ flag can be used to generate underscores in JSON, if desired. + #@ But the raw field name should use hyphens. + error("use hyphens, not underscores, for value field name") + if $field[1] =~ /_/; + + #@ Value field name cannot start with digit + #@ xo_emit("{:10-gig/}"); + #@ Should be: + #@ xo_emit("{:ten-gig/}"); + #@ XML element names cannot start with a digit. + error("value field name cannot start with digit") + if $field[1] =~ /^[0-9]/; + + #@ Value field name should be lower case + #@ xo_emit("{:WHY-ARE-YOU-SHOUTING}", "NO REASON"); + #@ Should be: + #@ xo_emit("{:why-are-you-shouting}", "no reason"); + #@ Lower case is more civilized. Even TLAs should be lower case + #@ to avoid scenarios where the differences between "XPath" and + #@ "Xpath" drive your users crazy. Lower case rules the seas. + error("value field name should be lower case") + if $field[1] =~ /[A-Z]/; + + #@ Value field name should be longer than two characters + #@ xo_emit("{:x}", "mumble"); + #@ Should be: + #@ xo_emit("{:something-meaningful}", "mumble"); + #@ Field names should be descriptive, and it's hard to + #@ be descriptive in less than two characters. Consider + #@ your users and try to make something more useful. + #@ Note that this error often occurs when the field type + #@ is placed after the colon ("{:T/%20s}"), instead of before + #@ it ("{T:/20s}"). + error("value field name should be longer than two characters") + if $field[1] =~ /[A-Z]/; + + #@ Value field name contains invalid character + #@ xo_emit("{:cost-in-$$/%u}", 15); + #@ Should be: + #@ xo_emit("{:cost-in-dollars/%u}", 15); + #@ An invalid character is often a sign of a typo, like "{:]}" + #@ instead of "{]:}". Field names are restricted to lower-case + #@ characters, digits, and hyphens. + error("value field name contains invalid character (" . $field[1] . ")") + unless $field[1] =~ /^[0-9a-z-]*$/; + } + + # A decoration field + if ($field[0] =~ /D/) { + + #@decoration field contains invalid character + #@ xo_emit("{D:not good}"); + #@ Should be: + #@ xo_emit("{D:((}{:good}{D:))}", "yes"); + #@ This is minor, but fields should use proper roles. Decoration + #@ fields are meant to hold puncuation and other characters used + #@ to decorate the content, typically to make it more readable + #@ to human readers. + warn("decoration field contains invalid character") + unless $field[1] =~ m:^[~!\@\#\$%^&\*\(\);\:\[\]\{\} ]+$:; + } + + if ($field[0] =~ /[\[\]]/) { + #@ Anchor content should be decimal width + #@ xo_emit("{[:mumble}"); + #@ Should be: + #@ xo_emit("{[:32}"); + #@ Anchors need an integer value to specify the width of + #@ the set of anchored fields. The value can be positive + #@ (for left padding/right justification) or negative (for + #@ right padding/left justification) and can appear in + #@ either the start or stop anchor field descriptor. + error("anchor content should be decimal width") + if $field[1] && $field[1] !~ /^-?\d+$/ ; + + #@ Anchor format should be "%d" + #@ xo_emit("{[:/%s}"); + #@ Should be: + #@ xo_emit("{[:/%d}"); + #@ Anchors only grok integer values, and if the value is not static, + #@ if must be in an 'int' argument, represented by the "%d" format. + #@ Anything else is an error. + error("anchor format should be \"%d\"") + if $field[2] && $field[2] ne "%d"; + + #@ Anchor cannot have both format and encoding format") + #@ xo_emit("{[:32/%d}"); + #@ Should be: + #@ xo_emit("{[:32}"); + #@ Anchors can have a static value or argument for the width, + #@ but cannot have both. + error("anchor cannot have both format and encoding format") + if $field[1] && $field[2]; + } +} + +sub count_args { + my($format) = @_; + + return -1 unless $format; + + my $in; + my($text, $ff, $fc, $rest); + for ($in = $format; $in; $in = $rest) { + ($text, $ff, $fc, $rest) = + ($in =~ /^([^%]*)(%[^%diouxXDOUeEfFgGaAcCsSp]*)([diouxXDOUeEfFgGaAcCsSp])(.*)$/); + unless ($ff) { + # Might be a "%%" + ($text, $ff, $rest) = ($in =~ /^([^%]*)(%%)(.*)$/); + if ($ff) { + check_text($text); + } else { + # Not sure what's going on here, but something's wrong... + error("invalid field format") if $in =~ /%/; + } + next; + } + + check_text($text); + check_field_format($ff, $fc); + } + + return 0; +} + +sub check_field_format { + my($ff, $fc) = @_; + + print "check_field_format: [$ff] [$fc]\n" if $opt_debug; + + my(@chunks) = split(/\./, $ff); + + #@ Max width only valid for strings + #@ xo_emit("{:tag/%2.4.6d}", 55); + #@ Should be: + #@ xo_emit("{:tag/%2.6d}", 55); + #@ libxo allows a true 'max width' in addition to the traditional + #@ printf-style 'max number of bytes to use for input'. But this + #@ is supported only for string values, since it makes no sense + #@ for non-strings. This error may occur from a typo, + #@ like "{:tag/%6..6d}" where only one period should be used. + error("max width only valid for strings") + if $#chunks >= 2 && $fc =~ /[sS]/; +} + +sub error { + return if $opt_vocabulary; + print STDERR $curfile . ": " .$curln . ": error: " . join(" ", @_) . "\n"; + print STDERR $replay . "\n" if $opt_print; + $errors += 1; +} + +sub warn { + return if $opt_vocabulary; + print STDERR $curfile . ": " .$curln . ": warning: " . join(" ", @_) . "\n"; + print STDERR $replay . "\n" if $opt_print; + $warnings += 1; +} + +sub info { + return if $opt_vocabulary; + print STDERR $curfile . ": " .$curln . ": info: " . join(" ", @_) . "\n"; + print STDERR $replay . "\n" if $opt_print; + $info += 1; +} + +main: { + main(); +} |