1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2014 Eric Lafortune (eric@graphics.cornell.edu)
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21package proguard;
22
23import proguard.classfile.util.WarningPrinter;
24
25import java.io.IOException;
26
27/**
28 * This class performs sanity checks on a given configurations.
29 *
30 * @author Eric Lafortune
31 */
32public class ConfigurationChecker
33{
34    private final Configuration configuration;
35
36
37    /**
38     * Creates a new ConfigurationChecker with the given configuration.
39     */
40    public ConfigurationChecker(Configuration configuration)
41    {
42        this.configuration = configuration;
43    }
44
45
46    /**
47     * Checks the given configuration for potential problems.
48     */
49    public void check() throws IOException
50    {
51        ClassPath programJars = configuration.programJars;
52        ClassPath libraryJars = configuration.libraryJars;
53
54        // Check that the input isn't empty.
55        if (programJars == null)
56        {
57            throw new IOException("The input is empty. You have to specify one or more '-injars' options.");
58        }
59
60        // Check that the first jar is an input jar.
61        ClassPathEntry firstEntry = programJars.get(0);
62        if (firstEntry.isOutput())
63        {
64            throw new IOException("The output jar [" + firstEntry.getName() +
65                                  "] must be specified after an input jar, or it will be empty.");
66        }
67
68        // Check that the first of two subsequent the output jars has a filter.
69        for (int index = 0; index < programJars.size() - 1; index++)
70        {
71            ClassPathEntry entry = programJars.get(index);
72            if (entry.isOutput()    &&
73                !entry.isFiltered() &&
74                programJars.get(index + 1).isOutput())
75            {
76                throw new IOException("The output jar [" + entry.getName() +
77                                      "] must have a filter, or all subsequent output jars will be empty.");
78            }
79        }
80
81        // Check for conflicts between input/output entries of the class paths.
82        checkConflicts(programJars, programJars);
83        checkConflicts(programJars, libraryJars);
84        checkConflicts(libraryJars, libraryJars);
85
86        // Print out some general notes if necessary.
87        if ((configuration.note == null ||
88             !configuration.note.isEmpty()))
89        {
90            // Check for potential problems with mixed-case class names on
91            // case-insensitive file systems.
92            if (configuration.obfuscate &&
93                configuration.useMixedCaseClassNames &&
94                configuration.classObfuscationDictionary == null)
95            {
96                String os = System.getProperty("os.name").toLowerCase();
97                if (os.startsWith("windows") ||
98                    os.startsWith("mac os"))
99                {
100                    // Go over all program class path entries.
101                    for (int index = 0; index < programJars.size(); index++)
102                    {
103                        // Is it an output directory?
104                        ClassPathEntry entry = programJars.get(index);
105                        if (entry.isOutput() &&
106                            !entry.isApk() &&
107                            !entry.isJar() &&
108                            !entry.isAar() &&
109                            !entry.isWar() &&
110                            !entry.isEar() &&
111                            !entry.isZip())
112                        {
113                            System.out.println("Note: you're writing the processed class files to a directory [" + entry.getName() + "].");
114                            System.out.println("      This will likely cause problems with obfuscated mixed-case class names.");
115                            System.out.println("      You should consider writing the output to a jar file, or otherwise");
116                            System.out.println("      specify '-dontusemixedcaseclassnames'.");
117
118                            break;
119                        }
120                    }
121                }
122            }
123
124            // Check if -adaptresourcefilecontents has a proper filter.
125            if (configuration.adaptResourceFileContents != null &&
126                (configuration.adaptResourceFileContents.isEmpty() ||
127                 configuration.adaptResourceFileContents.get(0).equals(ConfigurationConstants.ANY_FILE_KEYWORD)))
128            {
129                System.out.println("Note: you're specifying '-adaptresourcefilecontents' for all resource files.");
130                System.out.println("      This will most likely cause problems with binary files.");
131            }
132
133            // Check if all -keepclassmembers options indeed have class members.
134            WarningPrinter keepClassMemberNotePrinter = new WarningPrinter(System.out, configuration.note);
135
136            new KeepClassMemberChecker(keepClassMemberNotePrinter).checkClassSpecifications(configuration.keep);
137
138            // Check if -assumenosideffects options don't specify all methods.
139            WarningPrinter assumeNoSideEffectsNotePrinter = new WarningPrinter(System.out, configuration.note);
140
141            new AssumeNoSideEffectsChecker(assumeNoSideEffectsNotePrinter).checkClassSpecifications(configuration.assumeNoSideEffects);
142
143            // Print out a summary of the notes, if necessary.
144            int keepClassMemberNoteCount = keepClassMemberNotePrinter.getWarningCount();
145            if (keepClassMemberNoteCount > 0)
146            {
147                System.out.println("Note: there were " + keepClassMemberNoteCount +
148                                   " '-keepclassmembers' options that didn't specify class");
149                System.out.println("      members. You should specify at least some class members or consider");
150                System.out.println("      if you just need '-keep'.");
151                System.out.println("      (http://proguard.sourceforge.net/manual/troubleshooting.html#classmembers)");
152            }
153
154            int assumeNoSideEffectsNoteCount = assumeNoSideEffectsNotePrinter.getWarningCount();
155            if (assumeNoSideEffectsNoteCount > 0)
156            {
157                System.out.println("Note: there were " + assumeNoSideEffectsNoteCount +
158                                   " '-assumenosideeffects' options that try to match all");
159                System.out.println("      methods with wildcards. This will likely cause problems with methods like");
160                System.out.println("      'wait()' and 'notify()'. You should specify the methods more precisely.");
161                System.out.println("      (http://proguard.sourceforge.net/manual/troubleshooting.html#nosideeffects)");
162            }
163        }
164    }
165
166
167    /**
168     * Performs some sanity checks on the class paths.
169     */
170    private void checkConflicts(ClassPath classPath1,
171                                ClassPath classPath2)
172    throws IOException
173    {
174        if (classPath1 == null ||
175            classPath2 == null)
176        {
177            return;
178        }
179
180        for (int index1 = 0; index1 < classPath1.size(); index1++)
181        {
182            ClassPathEntry entry1 = classPath1.get(index1);
183
184            for (int index2 = 0; index2 < classPath2.size(); index2++)
185            {
186                if (classPath1 != classPath2 || index1 != index2)
187                {
188                    ClassPathEntry entry2 = classPath2.get(index2);
189
190                    if (entry2.getName().equals(entry1.getName()))
191                    {
192                        if (entry1.isOutput())
193                        {
194                            if (entry2.isOutput())
195                            {
196                                // Output / output.
197                                throw new IOException("The same output jar ["+entry1.getName()+"] is specified twice.");
198                            }
199                            else
200                            {
201                                // Output / input.
202                                throw new IOException("Input jars and output jars must be different ["+entry1.getName()+"].");
203                            }
204                        }
205                        else
206                        {
207                            if (entry2.isOutput())
208                            {
209                                // Input / output.
210                                throw new IOException("Input jars and output jars must be different ["+entry1.getName()+"].");
211                            }
212                            else if (!entry1.isFiltered() ||
213                                     !entry2.isFiltered())
214                            {
215                                // Input / input.
216                                throw new IOException("The same input jar ["+entry1.getName()+"] is specified twice.");
217                            }
218                        }
219                    }
220                }
221            }
222        }
223    }
224}
225