| /******************************************************************************* |
| * Copyright (c) 2000, 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.core.tests.eval; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.ServerSocket; |
| |
| import org.eclipse.jdt.core.compiler.CategorizedProblem; |
| import org.eclipse.jdt.core.tests.runtime.LocalVMLauncher; |
| import org.eclipse.jdt.core.tests.runtime.LocalVirtualMachine; |
| |
| import org.eclipse.jdt.core.tests.runtime.TargetException; |
| import org.eclipse.jdt.core.tests.runtime.TargetInterface; |
| import org.eclipse.jdt.core.tests.util.Util; |
| import org.eclipse.jdt.internal.compiler.ClassFile; |
| import org.eclipse.jdt.internal.compiler.IProblemFactory; |
| import org.eclipse.jdt.internal.compiler.batch.FileSystem; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader; |
| import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException; |
| import org.eclipse.jdt.internal.compiler.env.IBinaryField; |
| import org.eclipse.jdt.internal.compiler.env.INameEnvironment; |
| import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; |
| import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; |
| import org.eclipse.jdt.core.compiler.CharOperation; |
| import org.eclipse.jdt.internal.eval.EvaluationContext; |
| import org.eclipse.jdt.internal.eval.EvaluationResult; |
| import org.eclipse.jdt.internal.eval.GlobalVariable; |
| import org.eclipse.jdt.internal.eval.IRequestor; |
| |
| public class SimpleTest { |
| static final String JRE_PATH = Util.getJREDirectory(); |
| static final String[] COMPILATION_CLASSPATH = Util.concatWithClassLibs(Util.getOutputDirectory(), false); |
| static final String[] RUNTIME_CLASSPATH = new String[] {Util.getOutputDirectory()}; |
| static final String TARGET_PATH = Util.getOutputDirectory() + File.separator + "evaluation"; |
| protected EvaluationContext context; |
| protected LocalVirtualMachine launchedVM; |
| protected TargetInterface target; |
| protected Requestor requestor; |
| |
| class Requestor implements IRequestor { |
| int globalProblemCount = 0; |
| public boolean acceptClassFiles(ClassFile[] classFiles, char[] codeSnippetClassName) { |
| try { |
| SimpleTest.this.target.sendClasses(codeSnippetClassName != null, classFiles); |
| } catch (TargetException e) { |
| return false; |
| } |
| if (codeSnippetClassName != null) { |
| TargetInterface.Result result = SimpleTest.this.target.getResult(); |
| if (result.displayString == null) { |
| System.out.println("(No explicit return value)"); |
| } else { |
| System.out.print("("); |
| System.out.print(result.typeName); |
| System.out.print(") "); |
| System.out.println(result.displayString); |
| } |
| } else { |
| for (int i = 0, length = classFiles.length; i < length; i++) { |
| char[][] compoundName = classFiles[i].getCompoundName(); |
| if (new String(compoundName[compoundName.length-1]).startsWith("GlobalVariable")) { |
| try { |
| IBinaryField[] fields = new ClassFileReader(classFiles[i].getBytes(), null).getFields(); |
| if (fields != null) { |
| for (int j = 0; j < fields.length; j++) { |
| TargetInterface.Result result = SimpleTest.this.target.getResult(); |
| if (result.displayString == null) { |
| System.out.println("(No explicit return value)"); |
| } else { |
| System.out.print("("); |
| System.out.print(result.typeName); |
| System.out.print(") "); |
| System.out.println(result.displayString); |
| } |
| } |
| } |
| } catch (ClassFormatException e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| } |
| return true; |
| } |
| public void acceptProblem(CategorizedProblem problem, char[] fragmentSource, int fragmentKind) { |
| int localErrorCount = 0; |
| this.globalProblemCount++; |
| char[] source = fragmentSource; |
| if (localErrorCount == 0) |
| System.out.println("----------"); |
| if (fragmentKind == EvaluationResult.T_INTERNAL) { |
| System.out.print(this.globalProblemCount + (problem.isError() ? ". INTERNAL ERROR" : ". INTERNAL WARNING")); |
| System.out.print(" in generated compilation unit"); |
| } else { |
| System.out.print(this.globalProblemCount + (problem.isError() ? ". ERROR" : ". WARNING")); |
| System.out.print(" in "); |
| switch (fragmentKind) { |
| case EvaluationResult.T_PACKAGE: |
| System.out.print("package"); |
| break; |
| case EvaluationResult.T_IMPORT: |
| System.out.print("import"); |
| break; |
| case EvaluationResult.T_CODE_SNIPPET: |
| System.out.print("code snippet"); |
| break; |
| case EvaluationResult.T_VARIABLE: |
| int line = problem.getSourceLineNumber(); |
| if (line == -1) { |
| System.out.print("variable type"); |
| source = findVar(fragmentSource).getTypeName(); |
| } else if (line == 0) { |
| System.out.print("variable name"); |
| source = findVar(fragmentSource).getName(); |
| } else { |
| System.out.print("variable initializer"); |
| source = findVar(fragmentSource).getInitializer(); |
| } |
| break; |
| } |
| } |
| System.out.println(errorReportSource((DefaultProblem)problem, source)); |
| System.out.println(problem.getMessage()); |
| System.out.println("----------"); |
| if (problem.isError()) |
| localErrorCount++; |
| } |
| } |
| /** |
| * Build a char array from the given lines |
| */ |
| protected char[] buildCharArray(String[] lines) { |
| String buffer = String.join("\n", lines); |
| int length = buffer.length(); |
| char[] result = new char[length]; |
| buffer.getChars(0, length, result, 0); |
| return result; |
| } |
| protected String errorReportSource(DefaultProblem problem, char[] source) { |
| //extra from the source the innacurate token |
| //and "highlight" it using some underneath ^^^^^ |
| //put some context around too. |
| |
| //this code assumes that the font used in the console is fixed size |
| |
| //sanity ..... |
| if ((problem.getSourceStart() > problem.getSourceEnd()) || ((problem.getSourceStart() < 0) && (problem.getSourceEnd() < 0))) |
| return "\n!! no source information available !!"; |
| |
| //regular behavior....(slow code) |
| |
| final char SPACE = '\u0020'; |
| final char MARK = '^'; |
| final char TAB = '\t'; |
| //the next code tries to underline the token..... |
| //it assumes (for a good display) that token source does not |
| //contain any \r \n. This is false on statements ! |
| //(the code still works but the display is not optimal !) |
| |
| //compute the how-much-char we are displaying around the inaccurate token |
| int begin = problem.getSourceStart() >= source.length ? source.length - 1 : problem.getSourceStart(); |
| int relativeStart = 0; |
| int end = problem.getSourceEnd() >= source.length ? source.length - 1 : problem.getSourceEnd(); |
| label : for (relativeStart = 0;; relativeStart++) { |
| if (begin == 0) |
| break label; |
| if ((source[begin - 1] == '\n') || (source[begin - 1] == '\r')) |
| break label; |
| begin--; |
| } |
| label : for (;;) { |
| if ((end + 1) >= source.length) |
| break label; |
| if ((source[end + 1] == '\r') || (source[end + 1] == '\n')) { |
| break label; |
| } |
| end++; |
| } |
| //extract the message form the source |
| char[] extract = new char[end - begin + 1]; |
| System.arraycopy(source, begin, extract, 0, extract.length); |
| char c; |
| //remove all SPACE and TAB that begin the error message... |
| int trimLeftIndex = 0; |
| while (((c = extract[trimLeftIndex++]) == TAB) || (c == SPACE)) { |
| } |
| System.arraycopy(extract, trimLeftIndex - 1, extract = new char[extract.length - trimLeftIndex + 1], 0, extract.length); |
| relativeStart -= trimLeftIndex; |
| //buffer spaces and tabs in order to reach the error position |
| int pos = 0; |
| char[] underneath = new char[extract.length]; // can't be bigger |
| for (int i = 0; i <= relativeStart; i++) { |
| if (extract[i] == TAB) { |
| underneath[pos++] = TAB; |
| } else { |
| underneath[pos++] = SPACE; |
| } |
| } |
| //mark the error position |
| for (int i = problem.getSourceStart(); i <= (problem.getSourceEnd() >= source.length ? source.length - 1 : problem.getSourceEnd()); i++) |
| underneath[pos++] = MARK; |
| //resize underneathto remove 'null' chars |
| System.arraycopy(underneath, 0, underneath = new char[pos], 0, pos); |
| return |
| ((problem.getSourceLineNumber() > 0) ? |
| (" (at line " + String.valueOf(problem.getSourceLineNumber()) + ")") : |
| "" |
| ) + |
| "\n\t" + new String(extract) + "\n\t" + new String(underneath); |
| } |
| protected GlobalVariable findVar(char[] varName) { |
| GlobalVariable[] vars = this.context.allVariables(); |
| for (int i = 0; i < vars.length; i++) { |
| GlobalVariable var = vars[i]; |
| if (CharOperation.equals(var.getName(), varName)) { |
| return var; |
| } |
| } |
| return null; |
| } |
| protected INameEnvironment getEnv() { |
| |
| return new FileSystem(COMPILATION_CLASSPATH, new String[0], null); |
| } |
| protected IProblemFactory getProblemFactory() { |
| return new DefaultProblemFactory(java.util.Locale.getDefault()); |
| } |
| protected void startEvaluationContext() throws TargetException { |
| try (ServerSocket server = new ServerSocket(0)) { |
| LocalVMLauncher launcher = LocalVMLauncher.getLauncher(); |
| launcher.setVMPath(JRE_PATH); |
| launcher.setClassPath(RUNTIME_CLASSPATH); |
| int evalPort = server.getLocalPort(); |
| launcher.setEvalPort(evalPort); |
| launcher.setEvalTargetPath(TARGET_PATH); |
| this.launchedVM = launcher.launch(); |
| |
| (new Thread() { |
| @Override |
| public void run() { |
| try { |
| java.io.InputStream in = SimpleTest.this.launchedVM.getInputStream(); |
| int read = 0; |
| while (read != -1) { |
| try { |
| read = in.read(); |
| } catch (java.io.IOException e) { |
| read = -1; |
| } |
| if (read != -1) { |
| System.out.print((char)read); |
| } |
| } |
| } catch (TargetException e) { |
| } |
| } |
| }).start(); |
| |
| (new Thread() { |
| @Override |
| public void run() { |
| try { |
| java.io.InputStream in = SimpleTest.this.launchedVM.getErrorStream(); |
| int read = 0; |
| while (read != -1) { |
| try { |
| read = in.read(); |
| } catch (java.io.IOException e) { |
| read = -1; |
| } |
| if (read != -1) { |
| System.out.print((char)read); |
| } |
| } |
| } catch (TargetException e) { |
| } |
| } |
| }).start(); |
| |
| this.requestor = new Requestor(); |
| this.target = new TargetInterface(); |
| this.target.connect(server, 30000); // allow 30s max to connect (see https://bugs.eclipse.org/bugs/show_bug.cgi?id=188127) |
| this.context = new EvaluationContext(); |
| } catch (IOException e) { |
| throw new Error("Failed to open socket", e); |
| } |
| } |
| protected void stopEvaluationContext() { |
| try { |
| this.target.disconnect(); // Close the socket first so that the OS resource has a chance to be freed. |
| int retry = 0; |
| while (this.launchedVM.isRunning() && (++retry < 20)) { |
| try { |
| Thread.sleep(retry * 100); |
| } catch (InterruptedException e) { |
| } |
| } |
| if (this.launchedVM.isRunning()) { |
| this.launchedVM.shutDown(); |
| } |
| this.context = null; |
| } catch (TargetException e) { |
| throw new Error(e.getMessage()); |
| } |
| } |
| } |