# languages/java -- lintian check script -*- perl -*- # Copyright (C) 2011 Vincent Fourmond # Copyright (C) 2021 Felix Lechner # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, you can find it on the World Wide # Web at https://www.gnu.org/copyleft/gpl.html, or write to the Free # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, # MA 02110-1301, USA. package Lintian::Check::Languages::Java; use v5.20; use warnings; use utf8; use Const::Fast; use File::Basename; use List::SomeUtils qw(any none); use Lintian::Util qw(normalize_link_target $PKGNAME_REGEX $PKGVERSION_REGEX); const my $EMPTY => q{}; const my $HYPHEN => q{-}; const my $ARROW => q{->}; const my $BYTE_CODE_VERSION_OFFSET => 44; use Moo; use namespace::clean; with 'Lintian::Check'; our $CLASS_REGEX = qr/\.(?:class|cljc?)/; sub visit_patched_files { my ($self, $item) = @_; my $java_info = $item->java_info; return unless scalar keys %{$java_info}; my $files = $java_info->{files}; $self->pointed_hint('source-contains-prebuilt-java-object', $item->pointer) if any { m/$CLASS_REGEX$/i } keys %{$files}; return; } sub installable { my ($self) = @_; my $missing_jarwrapper = 0; my $has_public_jars = 0; my $jmajlow = $HYPHEN; my $depends = $self->processable->relation('strong')->to_string; # Remove all libX-java-doc packages to avoid thinking they are java libs # - note the result may not be a valid dependency listing $depends =~ s/lib[^\s,]+-java-doc//g; my @java_lib_depends = ($depends =~ m/\b(lib[^\s,]+-java)\b/g); my $JAVA_BYTECODES= $self->data->load('java/constants', qr/\s*=\s*/); # We first loop over jar files to find problems for my $item (@{$self->processable->installed->sorted_list}) { my $java_info = $item->java_info; next unless scalar keys %{$java_info}; my $files = $java_info->{files}; my $manifest = $java_info->{manifest}; my $jar_dir = dirname($item->name); my $classes = 0; my $datafiles = 1; my $class_path = $EMPTY; my $bsname = $EMPTY; if (exists $java_info->{error}) { $self->pointed_hint('zip-parse-error', $item->pointer, $java_info->{error}); next; } # The Java Policy says very little about requires for (jars in) JVMs next if $item->name =~ m{^usr/lib/jvm(?:-exports)?/[^/]+/}; # Ignore Mozilla's jar files, see #635495 next if $item->name =~ m{^usr/lib/xul(?:-ext|runner[^/]*+)/}; if ($item->name =~ m{^usr/share/java/[^/]+\.jar$}) { $has_public_jars = 1; # java policy requires package version too; see Bug#976681 $self->pointed_hint('bad-jar-name', $item->pointer) unless basename($item->name) =~ /^$PKGNAME_REGEX-$PKGVERSION_REGEX\.jar$/; } # check for common code files like .class or .clj (Clojure files) for my $class (grep { m/$CLASS_REGEX$/i } sort keys %{$files}){ my $module_version = $files->{$class}; (my $src = $class) =~ s/\.[^.]+$/\.java/; $self->pointed_hint('jar-contains-source', $item->pointer, $src) if %{$files}{$src}; $classes = 1; next if $class =~ m/\.cljc?$/; # .class but no major version? next if $module_version eq $HYPHEN; if ($module_version < $JAVA_BYTECODES->value('lowest-known-bytecode-version') || $module_version > $JAVA_BYTECODES->value('highest-known-bytecode-version')) { # First public major version was 45 (Java1), latest # version is 55 (Java11). $self->pointed_hint('unknown-java-class-version', $item->pointer,$class, $ARROW, $module_version); # Skip the rest of this Jar. last; } # Collect the "lowest" Class version used. We assume that # mixed class formats implies special compat code for certain # JVM cases. if ($jmajlow eq $HYPHEN) { # first; $jmajlow = $module_version; } else { $jmajlow = $module_version if $module_version < $jmajlow; } } $datafiles = 0 if none { /\.(?:xml|properties|x?html|xhp)$/i } keys %{$files}; if ($item->is_executable) { $self->pointed_hint('executable-jar-without-main-class', $item->pointer) unless $manifest && $manifest->{'Main-Class'}; # Here, we need to check that the package depends on # jarwrapper. $missing_jarwrapper = 1 unless $self->processable->relation('strong') ->satisfies('jarwrapper'); } elsif ($item->name !~ m{^usr/share/}) { $self->pointed_hint('jar-not-in-usr-share', $item->pointer); } $class_path = $manifest->{'Class-Path'}//$EMPTY if $manifest; $bsname = $manifest->{'Bundle-SymbolicName'}//$EMPTY if $manifest; if ($manifest) { if (!$classes) { # Eclipse / OSGi bundles are sometimes source bundles # these do not ship classes but java files and other sources. # Javadoc jars deployed in the Maven repository also do not ship # classes but HTML files, images and CSS files if ( ( $bsname !~ m/\.source$/ && $item->name !~ m{^usr/share/maven-repo/.*-javadoc\.jar} && $item->name !~ m{\.doc(?:\.(?:user|isv))?_[^/]+.jar} && $item->name !~ m{\.source_[^/]+.jar} ) || $class_path ) { $self->pointed_hint('codeless-jar', $item->pointer); } } } elsif ($classes) { $self->pointed_hint('missing-manifest', $item->pointer); } if ($class_path) { # Only run the tests when a classpath is present my @relative; my @paths = split(m/\s++/, $class_path); for my $p (@paths) { if ($p) { # Strip leading ./ $p =~ s{^\./+}{}g; if ($p !~ m{^(?:file://)?/} && $p =~ m{/}) { my $target = normalize_link_target($jar_dir, $p); my $tinfo; # Can it be normalized? next unless defined($target); # Relative link to usr/share/java ? Works if # we are depending of a Java library. next if $target =~ m{^usr/share/java/[^/]+.jar$} && @java_lib_depends; $tinfo= $self->processable->installed->lookup($target); # Points to file or link in this package, # which is sometimes easier than # re-writing the classpath. next if defined $tinfo and ($tinfo->is_symlink or $tinfo->is_file); # Relative path with subdirectories. push @relative, $p; } # @todo add an info tag for relative paths, to educate # maintainers ? } } $self->pointed_hint('classpath-contains-relative-path', $item->pointer, join(', ', @relative)) if @relative; } # Trigger a warning when a maven plugin lib is installed in # /usr/share/java/ $self->pointed_hint('maven-plugin-in-usr-share-java', $item->pointer) if $has_public_jars && $self->processable->name =~ /^lib.*maven.*plugin.*/ && $item->name !~ m{^usr/share/maven-repo/.*\.jar}; } $self->hint('missing-dep-on-jarwrapper') if $missing_jarwrapper; if ($jmajlow ne $HYPHEN) { # Byte code numbers: # 45-49 -> Java1 - Java5 (Always ok) # 50 -> Java6 # 51 -> Java7 # 52 -> Java8 # 53 -> Java9 # 54 -> Java10 # 55 -> Java11 my $bad = 0; # If the lowest version used is greater than the requested # limit, then flag it. $bad = 1 if $jmajlow > $JAVA_BYTECODES->value('default-bytecode-version'); # Technically we ought to do some checks with Java6 class # files and dependencies/package types, but for now just skip # that. (See #673276) if ($bad) { # Map the Class version to a Java version. my $java_version = $jmajlow - $BYTE_CODE_VERSION_OFFSET; $self->hint('incompatible-java-bytecode-format', "Java$java_version version (Class format: $jmajlow)"); } } if ( !$has_public_jars && !$self->processable->is_transitional && $self->processable->name =~ /^lib[^\s,]+-java$/){ # Skip this if it installs a symlink in usr/share/java my $java_dir = $self->processable->installed->resolve_path('usr/share/java/'); my $has_jars = 0; $has_jars = 1 if $java_dir && (any { $_->name =~ m{^[^/]+\.jar$} } $java_dir->children); $self->hint('javalib-but-no-public-jars') unless $has_jars; } return; } 1; # Local Variables: # indent-tabs-mode: nil # cperl-indent-level: 4 # End: # vim: syntax=perl sw=4 sts=4 sr et