| /* |
| * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code 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 |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| import com.sun.tools.classfile.*; |
| |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.stream.IntStream; |
| import java.util.stream.Stream; |
| import javax.tools.JavaFileObject; |
| |
| import static com.sun.tools.classfile.Attribute.Code; |
| import static com.sun.tools.classfile.Attribute.LineNumberTable; |
| import static java.lang.String.format; |
| import static java.util.stream.Collectors.toList; |
| |
| /** |
| * Base class for line number table attribute tests. |
| * To add new tests cases(e.g. for new language constructions) you should modify TestData in LineNumberTest. |
| * If you plan to add new tests you should extends LineNumberTestBase and invoke one of two "test(...)" methods. |
| * |
| * @see #test(Container) test methods for more info. |
| */ |
| public class LineNumberTestBase extends TestBase { |
| /** |
| * Generates test cases and passes to {@link #test(java.util.List)} |
| * Generation: Replaces placeholder in template by value of enum {@link Constructions}. |
| */ |
| protected void test(Container container) throws Exception { |
| test(container.generate(Constructions.values())); |
| } |
| |
| /** |
| * Takes list of test cases. Compiles source of test case. |
| * Checks what expected lines are covered by line number table. |
| * Does general check of line number table for consistency. |
| * |
| * @param testCases list of test cases. |
| */ |
| protected void test(List<TestCase> testCases) throws Exception { |
| boolean failed = false; |
| for (TestCase testCase : testCases) { |
| try { |
| Set<Integer> coveredLines = new HashSet<>(); |
| for (JavaFileObject file : compile(testCase.src).getClasses().values()) { |
| ClassFile classFile = ClassFile.read(file.openInputStream()); |
| for (Method m : classFile.methods) { |
| Code_attribute code_attribute = (Code_attribute) m.attributes.get(Code); |
| |
| assertEquals( |
| countAttributes(LineNumberTable, code_attribute.attributes.attrs, classFile.constant_pool), |
| 1, |
| "Can be more than one LNT attribute, but javac should generate only one."); |
| |
| LineNumberTable_attribute tableAttribute = |
| (LineNumberTable_attribute) code_attribute.attributes.get(LineNumberTable); |
| checkAttribute(testCase, tableAttribute, code_attribute.code_length); |
| coveredLines.addAll( |
| Stream.of(tableAttribute.line_number_table) |
| .map(e -> e.line_number) |
| .collect(toList())); |
| } |
| } |
| assertTrue(coveredLines.containsAll(testCase.expectedLines), |
| format("All significant lines are not covered.%n" + |
| "Covered: %s%n" + |
| "Expected: %s%n", coveredLines, testCase.expectedLines)); |
| } catch (AssertionFailedException | CompilationException ex) { |
| System.err.printf("# %-20s#%n", testCase.getName()); |
| int l = 0; |
| for (String line : testCase.src.split("\n")) { |
| System.err.println(++l + line); |
| } |
| System.err.println(ex); |
| failed = true; |
| continue; |
| } |
| System.err.printf("# %-20s#%n", testCase.getName()); |
| System.err.println("Passed"); |
| } |
| if (failed) { |
| throw new RuntimeException("Test failed"); |
| } |
| } |
| |
| private int countAttributes(String name, Attribute[] attrs, ConstantPool constant_pool) throws ConstantPoolException { |
| int i = 0; |
| for (Attribute attribute : attrs) { |
| if (name.equals(attribute.getName(constant_pool))) { |
| i++; |
| } |
| } |
| return i; |
| } |
| |
| private void checkAttribute(TestCase testCase, LineNumberTable_attribute tableAttribute, int code_length) { |
| assertEquals(tableAttribute.line_number_table_length, tableAttribute.line_number_table.length, |
| "Incorrect line number table length."); |
| //attribute length is offset(line_number_table_length) + element_size*element_count |
| assertEquals(tableAttribute.attribute_length, 2 + 4 * tableAttribute.line_number_table_length, |
| "Incorrect attribute length"); |
| testNonEmptyLine(testCase.src.split("\n"), tableAttribute); |
| assertEquals( |
| Stream.of(tableAttribute.line_number_table) |
| .filter(e -> e.start_pc >= code_length) |
| .count() |
| , 0L, "StartPC is out of bounds."); |
| } |
| |
| /** |
| * Expects line number table point to non empty lines. |
| * The method can't recognize commented lines as empty(insensible) in case of multiline comment. |
| */ |
| private void testNonEmptyLine(String[] source, LineNumberTable_attribute attribute) { |
| for (LineNumberTable_attribute.Entry e : attribute.line_number_table) { |
| String line = source[e.line_number - 1].trim(); |
| assertTrue(!("".equals(line) || line.startsWith("//") || line.startsWith("/*")), |
| format("Expect that line #%d is not empty.%n", e.line_number)); |
| } |
| } |
| |
| protected static enum Constructions implements Container.Construction { |
| STORE("testField = 10;"), |
| LOAD("int p;\n" + |
| "p = testField;", 2), |
| ASSERT("assert false: \"Assert error\";"), |
| ARRAY("double arr[] = new double[10];"), |
| ARRAY2("int arr2[][] = {{1,2},{}};"), |
| LAMBDA("Runnable runnable = () -> \n" + |
| " System.out.println();"), |
| LAMBDA_BODY("Runnable runnable = () -> {\n" + |
| " testField++;\n" + |
| "};"), |
| METHOD_REFERENCE("Runnable run = System.out::println;\nrun.run();"), |
| INVOKE_STATIC_METHOD("System.out.println(\"\");"), |
| INVOKE_INTERFACE("Runnable runnable = new Runnable() {\n" + |
| " @Override\n" + |
| " public void run() {\n" + |
| " System.out.println(\"runnable\");\n" + |
| " }\n" + |
| "};\n" + |
| "runnable.run();", 1, 7), |
| INVOKE_VIRTUAL_METHOD("testMethod();"), |
| INVOKE_CONSTRUCTOR("new Integer(2);"), |
| INVOKE_LAMBDA(LAMBDA.getSource() + "\n" + |
| "runnable.run();"), |
| DO_WHILE("do{\n" + |
| " testField++;\n" + |
| "}while(testField == 1);", 2, 3), |
| WHILE("while(testField == 1);"), |
| FOR("for(int i = 0; i < 3 ; i++);"), |
| FOR_ENHANCEMENT("int[] ints = {1,2,3};\n" + |
| "for(int i: ints);"), |
| LABEL("int i=0;\n" + |
| "label:{\n" + |
| " label2:\n" + |
| " for(;i<5;i++){\n" + |
| " if(i==3)\n" + |
| " break label;\n" + |
| " if(i==0){\n" + |
| " continue label2;\n" + |
| " }\n" + |
| " return;\n" + |
| " }\n" + |
| " i++;\n" + |
| "}\n" |
| , 1, 4, 5, 6, 7, 8, 10, 12), |
| CONDITION("int res = \n" + |
| "testField == 2 ?\n" + |
| "10\n" + |
| ":9;", 1, 3, 4), // see issue https://bugs.openjdk.java.net/browse/JDK-8050993 |
| TRY("try{\n" + |
| " --testField;\n" + |
| "}\n" + |
| "catch(Exception e){\n" + |
| " --testField;\n" + |
| "}\n" + |
| "catch(Error e){\n" + |
| " System.out.print(e);\n" + |
| " throw e;\n " + |
| "}\n" + |
| "finally{\n" + |
| " ++testField;\n" + |
| "}", 2, 4, 5, 7, 8, 9, 12), |
| TRY_WITH_RESOURCES("try (\n" + |
| " Writer writer = new StringWriter();\n" + |
| " Reader reader = new StringReader(\"\")) {\n" + |
| " writer.write(1);\n" + |
| " reader.read();\n" + |
| "} catch (IOException e) {}\n" |
| , 2, 3, 4, 5), |
| SYNCHRONIZE("" + |
| "synchronized(this){\n" + |
| " testField++;\n" + |
| "}"), |
| SWITCH("switch (testField){\n" + |
| "case 1:\n" + |
| " break;\n" + |
| "case 2:\n" + |
| " testField++;\n" + |
| "default: \n" + |
| " testField+=2; \n" + |
| "}", 1, 3, 5, 7), |
| SWITCH_STRING( |
| "String str = String.valueOf(testField);\n" + |
| "switch (str){\n" + |
| "case \"1\":\n" + |
| " break;\n" + |
| "case \"2\":\n" + |
| " testField++;\n" + |
| "default: \n" + |
| " testField+=2; \n" + |
| "}", 1, 2, 4, 6, 8); |
| |
| private final String source; |
| private int[] expectedLines; |
| |
| Constructions(String source) { |
| this.source = source; |
| expectedLines = IntStream.rangeClosed(1, source.split("\n").length).toArray(); |
| } |
| |
| Constructions(String source, int... expectedLines) { |
| this.source = source; |
| this.expectedLines = expectedLines; |
| } |
| |
| @Override |
| public String getSource() { |
| return source; |
| } |
| |
| @Override |
| public int[] getExpectedLines() { |
| return expectedLines; |
| } |
| } |
| |
| protected static class MainContainer extends Container { |
| |
| public MainContainer() { |
| super("import java.io.*;\n" + |
| "public class Main{\n" + |
| " public int testField;\n" + |
| "\n" + |
| " public void testMethod() {\n" + |
| " #SUB_TEMPLATE\n" + |
| " }\n" + |
| "}"); |
| } |
| } |
| |
| protected static class LocalClassContainer extends Container { |
| |
| public LocalClassContainer() { |
| |
| super("class Local#LEVEL{\n" + |
| " public void m(){\n" + |
| " #SUB_TEMPLATE\n" + |
| " return;\n" + |
| " }" + |
| "}"); |
| } |
| } |
| |
| protected static class LambdaContainer extends Container { |
| |
| public LambdaContainer() { |
| super("Runnable lambda#LEVEL = () -> {\n" + |
| " #SUB_TEMPLATE\n" + |
| "};\n" + |
| "lambda#LEVEL.run();\n"); |
| } |
| } |
| } |