1/*
2 * Copyright (c) 2010, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug 8041648
27 * @summary Verify that end positions are sane if semicolons are missing.
28 * @modules jdk.compiler/com.sun.tools.javac.api
29 *          jdk.compiler/com.sun.tools.javac.file
30 *          jdk.compiler/com.sun.tools.javac.parser
31 *          jdk.compiler/com.sun.tools.javac.tree
32 *          jdk.compiler/com.sun.tools.javac.util
33 * @run main MissingSemicolonTest MissingSemicolonTest.java
34 */
35
36import java.io.File;
37import java.io.IOException;
38import java.net.URI;
39import java.nio.file.Files;
40import java.util.*;
41
42import javax.tools.*;
43
44import com.sun.source.tree.*;
45import com.sun.source.tree.Tree.Kind;
46import com.sun.source.util.*;
47import com.sun.tools.javac.api.JavacTool;
48import com.sun.tools.javac.parser.Scanner;
49import com.sun.tools.javac.parser.ScannerFactory;
50import com.sun.tools.javac.tree.JCTree;
51import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
52import com.sun.tools.javac.util.Context;
53
54public class MissingSemicolonTest {
55    public static void main(String... args) throws IOException {
56        String testSrc = System.getProperty("test.src");
57        File baseDir = new File(testSrc);
58        boolean ok = new MissingSemicolonTest().run(baseDir, args);
59        if (!ok) {
60            throw new Error("failed");
61        }
62    }
63
64    boolean run(File baseDir, String... args) throws IOException {
65        try {
66            if (args.length == 0) {
67                throw new IllegalStateException("Needs input files.");
68            }
69
70            for (String arg : args) {
71                File file = new File(baseDir, arg);
72                if (file.exists())
73                    test(file);
74                else
75                    error("File not found: " + file);
76            }
77
78            System.err.println(fileCount + " files read");
79            if (errors > 0)
80                System.err.println(errors + " errors");
81
82            return errors == 0;
83        } finally {
84            fm.close();
85        }
86    }
87
88    void test(File file) {
89        if (file.isFile() && file.getName().endsWith(".java")) {
90            try {
91                fileCount++;
92                String content = new String(Files.readAllBytes(file.toPath()));
93                List<int[]> spans = gatherTreeSpans(file, content);
94                int nextSemicolon = -1;
95
96                //remove semicolons, one at a time, and verify the positions are still meaningful:
97                while ((nextSemicolon = content.indexOf(';', nextSemicolon + 1)) != (-1)) {
98                    String updatedContent =
99                            content.substring(0, nextSemicolon) +
100                                                 " " +
101                                                 content.substring(nextSemicolon + 1);
102                    verifyTreeSpans(file, spans, updatedContent, nextSemicolon);
103                }
104            } catch (IOException e) {
105                error("Error reading " + file + ": " + e);
106            }
107        }
108    }
109
110    public List<int[]> gatherTreeSpans(File file, String content) throws IOException {
111        JCCompilationUnit unit = read(file.toURI(), content);
112        List<int[]> spans = new ArrayList<>();
113        new TreePathScanner<Void, Void>() {
114            @Override
115            public Void scan(Tree tree, Void p) {
116                if (tree != null) {
117                    int start = ((JCTree) tree).getStartPosition();
118                    int end = ((JCTree) tree).getEndPosition(unit.endPositions);
119
120                    spans.add(new int[] {start, end});
121                }
122                return super.scan(tree, p);
123            }
124        }.scan(unit, null);
125        return spans;
126    }
127
128    public void verifyTreeSpans(File file, List<int[]> spans,
129                                String updatedContent, int semicolon) throws IOException {
130        JCCompilationUnit updated = read(file.toURI(), updatedContent);
131        Iterator<int[]> nextSpan = spans.iterator();
132        new TreePathScanner<Void, Void>() {
133            @Override
134            public Void scan(Tree tree, Void p) {
135                if (tree != null) {
136                    int start = ((JCTree) tree).getStartPosition();
137                    int end = ((JCTree) tree).getEndPosition(updated.endPositions);
138
139                    if (tree.getKind() != Kind.ERRONEOUS) {
140                        int[] expected = nextSpan.next();
141                        int expectedEnd = expected[1];
142
143                        if (expectedEnd == semicolon + 1) {
144                            Scanner scanner = scannerFactory.newScanner(updatedContent, true);
145                            scanner.nextToken();
146                            while (scanner.token().pos < expectedEnd)
147                                scanner.nextToken();
148                            expectedEnd = scanner.token().pos;
149                        }
150
151                        if (expected[0] != start || expectedEnd != end) {
152                            error(updatedContent + "; semicolon: " + semicolon + "; expected: " +
153                                  expected[0] + "-" + expectedEnd + "; found=" + start + "-" + end +
154                                  ";" + tree);
155                        }
156                    }
157                }
158                return super.scan(tree, p);
159            }
160        }.scan(updated, null);
161    }
162
163    DiagnosticListener<JavaFileObject> devNull = (d) -> {};
164    JavacTool tool = JavacTool.create();
165    StandardJavaFileManager fm = tool.getStandardFileManager(devNull, null, null);
166    ScannerFactory scannerFactory = ScannerFactory.instance(new Context());
167
168    /**
169     * Read a file.
170     * @param file the file to be read
171     * @return the tree for the content of the file
172     * @throws IOException if any IO errors occur
173     * @throws MissingSemicolonTest.ParseException if any errors occur while parsing the file
174     */
175    JCCompilationUnit read(URI uri, String content) throws IOException {
176        JavacTool tool = JavacTool.create();
177        JavacTask task = tool.getTask(null, fm, devNull, Collections.<String>emptyList(), null,
178                Arrays.<JavaFileObject>asList(new JavaSource(uri, content)));
179        Iterable<? extends CompilationUnitTree> trees = task.parse();
180        Iterator<? extends CompilationUnitTree> iter = trees.iterator();
181        if (!iter.hasNext())
182            throw new Error("no trees found");
183        JCCompilationUnit t = (JCCompilationUnit) iter.next();
184        if (iter.hasNext())
185            throw new Error("too many trees found");
186        return t;
187    }
188
189    class JavaSource extends SimpleJavaFileObject {
190
191        private final String content;
192        public JavaSource(URI uri, String content) {
193            super(uri, JavaFileObject.Kind.SOURCE);
194            this.content = content;
195        }
196
197        @Override
198        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
199            return content;
200        }
201    }
202
203    /**
204     * Report an error. When the program is complete, the program will either
205     * exit or throw an Error if any errors have been reported.
206     * @param msg the error message
207     */
208    void error(String msg) {
209        System.err.println(msg);
210        errors++;
211    }
212
213    /** Number of files that have been analyzed. */
214    int fileCount;
215    /** Number of errors reported. */
216    int errors;
217
218}
219
220class TestCase {
221    String str1;
222    String str2;
223    public TestCase() {
224        super();
225        super.hashCode();
226    }
227    public TestCase(String str1, String str2) {
228        super();
229        this.str1 = str1;
230        this.str2 = str2;
231        assert true;
232    }
233
234    void newClass() {
235        new String();
236        new String();
237    }
238
239    void localVars() {
240        String str1 = "";
241        String str2;
242        String str3;
243        final String str4;
244    }
245
246    void throwsException() {
247        throw new IllegalStateException();
248    }
249
250    int returnWithExpression() {
251        return 1;
252    }
253
254    void returnWithoutExpression() {
255        return ;
256    }
257
258    void doWhileBreakContinue() {
259        do {
260            if (true)
261                break;
262            if (false)
263                continue;
264        } while(true);
265    }
266
267    void labelled() {
268        LABEL: doWhileBreakContinue();
269    }
270
271}
272