1 /******************************************************************************* 2 * Copyright (c) 2000, 2015 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.debug.eval; 15 16 import java.io.File; 17 import java.io.FileOutputStream; 18 import java.io.IOException; 19 import java.text.MessageFormat; 20 import java.util.ArrayList; 21 import java.util.Arrays; 22 import java.util.Collections; 23 import java.util.Iterator; 24 import java.util.List; 25 26 import org.eclipse.core.resources.IMarker; 27 import org.eclipse.core.runtime.CoreException; 28 import org.eclipse.core.runtime.IProgressMonitor; 29 import org.eclipse.core.runtime.IStatus; 30 import org.eclipse.core.runtime.Status; 31 import org.eclipse.debug.core.DebugEvent; 32 import org.eclipse.debug.core.DebugException; 33 import org.eclipse.debug.core.model.IVariable; 34 import org.eclipse.jdt.core.IJavaProject; 35 import org.eclipse.jdt.core.IType; 36 import org.eclipse.jdt.core.JavaModelException; 37 import org.eclipse.jdt.core.eval.ICodeSnippetRequestor; 38 import org.eclipse.jdt.core.eval.IEvaluationContext; 39 import org.eclipse.jdt.debug.core.IEvaluationRunnable; 40 import org.eclipse.jdt.debug.core.IJavaClassObject; 41 import org.eclipse.jdt.debug.core.IJavaClassType; 42 import org.eclipse.jdt.debug.core.IJavaDebugTarget; 43 import org.eclipse.jdt.debug.core.IJavaObject; 44 import org.eclipse.jdt.debug.core.IJavaStackFrame; 45 import org.eclipse.jdt.debug.core.IJavaThread; 46 import org.eclipse.jdt.debug.core.IJavaType; 47 import org.eclipse.jdt.debug.core.IJavaValue; 48 import org.eclipse.jdt.debug.core.IJavaVariable; 49 import org.eclipse.jdt.debug.core.JDIDebugModel; 50 import org.eclipse.jdt.debug.eval.IClassFileEvaluationEngine; 51 import org.eclipse.jdt.debug.eval.IEvaluationEngine; 52 import org.eclipse.jdt.debug.eval.IEvaluationListener; 53 import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin; 54 import org.eclipse.jdt.internal.debug.core.JavaDebugUtils; 55 import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget; 56 import org.eclipse.jdt.internal.debug.core.model.JDIValue; 57 58 import com.sun.jdi.InvocationException; 59 import com.sun.jdi.ObjectReference; 60 61 /** 62 * An evaluation engine that deploys class files locally 63 */ 64 65 public class LocalEvaluationEngine implements IClassFileEvaluationEngine, 66 ICodeSnippetRequestor, IEvaluationRunnable { 67 68 private static final String CODE_SNIPPET_NAME = "CodeSnippet.class"; //$NON-NLS-1$ 69 70 /** 71 * A count of the number of engines created. Count is incremented on 72 * instantiation and decremented on dispose. When the count == 0, the 73 * special CodeSnippet.class is deleted as this class file is shared by all. 74 */ 75 private static int ENGINE_COUNT = 0; 76 77 /** 78 * The Java project context in which to compile snippets. 79 */ 80 private IJavaProject fJavaProject; 81 82 /** 83 * The debug target on which to execute snippets 84 */ 85 private IJavaDebugTarget fDebugTarget; 86 87 /** 88 * The location in which to deploy snippet class files 89 */ 90 private File fOutputDirectory; 91 92 /** 93 * The listener to notify when the current evaluation is complete. 94 */ 95 private IEvaluationListener fListener; 96 97 /** 98 * The stack frame context for the current evaluation or <code>null</code> 99 * if there is no stack frame context. 100 */ 101 private IJavaStackFrame fStackFrame; 102 103 /** 104 * The result of this evaluation 105 */ 106 private EvaluationResult fResult; 107 108 /** 109 * Collection of deployed snippet class files 110 */ 111 private List<File> fSnippetFiles; 112 113 /** 114 * Collection of directories created by this evaluation engine. 115 */ 116 private List<File> fDirectories; 117 118 /** 119 * Evaluation context for the Java project associated with this evaluation 120 * engine. 121 */ 122 private IEvaluationContext fEvaluationContext; 123 124 /** 125 * Array of modifier constants for visible local variables in the current 126 * evaluation. 127 * 128 * XXX: constants should be 'default' or 'final'. Where are these constants 129 * defined. 130 */ 131 private int[] fLocalVariableModifiers; 132 133 /** 134 * Array of names of visible local variables in the current evaluation. 135 */ 136 private String[] fLocalVariableNames; 137 138 /** 139 * Array of type names of visible local variables in the current evaluation. 140 */ 141 private String[] fLocalVariableTypeNames; 142 143 /** 144 * The 'this' object for the current evaluation or <code>null</code> if 145 * there is no 'this' context (static method, or not context) 146 */ 147 private IJavaObject fThis; 148 149 /** 150 * Whether this engine has been disposed. 151 */ 152 private boolean fDisposed = false; 153 154 /** 155 * The number of evaluations currently being performed. 156 */ 157 private int fEvaluationCount = 0; 158 159 /** 160 * The name of the code snippet class to instantiate 161 */ 162 private String fCodeSnippetClassName = null; 163 164 /** 165 * Whether to hit breakpoints in the evaluation thread 166 */ 167 private boolean fHitBreakpoints = false; 168 169 /** 170 * Constant for empty array of <code>java.lang.String</code> 171 */ 172 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 173 174 /** 175 * Constant for empty array of <code>int</code> 176 */ 177 private static final int[] EMPTY_INT_ARRAY = new int[0]; 178 179 /** 180 * Constructs a new evaluation engine for the given VM in the context of the 181 * specified project. Class files required for the evaluation will be 182 * deployed to the specified directory (which must be on the class path of 183 * the VM in order for evaluation to work). 184 * 185 * @param project 186 * context in which to compile snippets 187 * @param vm 188 * debug target in which to evaluate snippets 189 * @param directory 190 * location where snippet class files will be deployed for 191 * execution. The directory must exist 192 */ LocalEvaluationEngine(IJavaProject project, IJavaDebugTarget vm, File directory)193 public LocalEvaluationEngine(IJavaProject project, IJavaDebugTarget vm, 194 File directory) { 195 setJavaProject(project); 196 setDebugTarget(vm); 197 setOutputDirectory(directory); 198 ENGINE_COUNT++; 199 } 200 201 /** 202 * @see ICodeSnippetRequestor#acceptClassFiles(byte[][], String[][], String) 203 */ 204 @Override acceptClassFiles(byte[][] classFileBytes, String[][] classFileCompoundNames, String codeSnippetClassName)205 public boolean acceptClassFiles(byte[][] classFileBytes, 206 String[][] classFileCompoundNames, String codeSnippetClassName) { 207 try { 208 deploy(classFileBytes, classFileCompoundNames); 209 } catch (DebugException e) { 210 getResult().setException(e); 211 return false; 212 } 213 if (codeSnippetClassName != null) { 214 setCodeSnippetClassName(codeSnippetClassName); 215 try { 216 getThread().runEvaluation(this, null, DebugEvent.EVALUATION, 217 getHitBreakpoints()); 218 } catch (DebugException e) { 219 // exception handling is in evaluation runnable 220 } 221 } 222 return true; 223 } 224 225 @Override run(IJavaThread thread, IProgressMonitor monitor)226 public void run(IJavaThread thread, IProgressMonitor monitor) { 227 IJavaObject codeSnippetInstance = null; 228 try { 229 codeSnippetInstance = newInstance(getCodeSnippetClassName()); 230 initializeLocals(codeSnippetInstance); 231 codeSnippetInstance.sendMessage(RUN_METHOD, "()V", null, getThread(), false); //$NON-NLS-1$ 232 restoreLocals(codeSnippetInstance); 233 234 // now retrieve the description of the result 235 IVariable[] fields = codeSnippetInstance.getVariables(); 236 IJavaVariable resultValue = null; 237 IJavaVariable resultType = null; 238 for (IVariable field : fields) { 239 if (field.getName().equals(RESULT_TYPE_FIELD)) { 240 resultType = (IJavaVariable) field; 241 } 242 if (field.getName().equals(RESULT_VALUE_FIELD)) { 243 resultValue = (IJavaVariable) field; 244 } 245 } 246 IJavaValue result = convertResult((IJavaClassObject) resultType.getValue(), (IJavaValue) resultValue.getValue()); 247 getResult().setValue(result); 248 } catch (DebugException e) { 249 getResult().setException(e); 250 251 Throwable underlyingException = e.getStatus().getException(); 252 if (underlyingException instanceof InvocationException) { 253 ObjectReference theException = ((InvocationException) underlyingException) 254 .exception(); 255 if (theException != null) { 256 try { 257 try { 258 IJavaObject v = (IJavaObject) JDIValue.createValue( 259 (JDIDebugTarget) getDebugTarget(), 260 theException); 261 v.sendMessage( 262 "printStackTrace", "()V", null, getThread(), false); //$NON-NLS-2$ //$NON-NLS-1$ 263 } catch (DebugException de) { 264 JDIDebugPlugin.log(de); 265 } 266 } catch (RuntimeException re) { 267 JDIDebugPlugin.log(re); 268 } 269 } 270 } 271 } 272 273 } 274 275 /** 276 * Initializes the value of instance variables in the 'code snippet object' 277 * that are used as place-holders for locals and 'this' in the current stack 278 * frame. 279 * 280 * @param object 281 * instance of code snippet class that will be run 282 * @exception DebugException 283 * if an exception is thrown accessing the given object 284 */ initializeLocals(IJavaObject object)285 protected void initializeLocals(IJavaObject object) throws DebugException { 286 IJavaVariable[] locals = null; 287 IJavaObject thisObject = getThis(); 288 if (getStackFrame() != null) { 289 locals = getStackFrame().getLocalVariables(); 290 } 291 if (locals != null) { 292 for (IJavaVariable local : locals) { 293 IJavaVariable field = object.getField( 294 LOCAL_VAR_PREFIX + local.getName(), false); 295 // internal error if field is not found 296 if (field == null) { 297 throw new DebugException( 298 new Status( 299 IStatus.ERROR, 300 JDIDebugModel.getPluginIdentifier(), 301 DebugException.REQUEST_FAILED, 302 EvaluationMessages.LocalEvaluationEngine_Evaluation_failed___unable_to_initialize_local_variables__4, 303 null)); 304 } 305 field.setValue(local.getValue()); 306 } 307 } 308 if (thisObject != null) { 309 IJavaVariable field = object.getField(DELEGATE_THIS, false); 310 // internal error if field is not found 311 if (field == null) { 312 throw new DebugException( 313 new Status( 314 IStatus.ERROR, 315 JDIDebugModel.getPluginIdentifier(), 316 DebugException.REQUEST_FAILED, 317 EvaluationMessages.LocalEvaluationEngine_Evaluation_failed___unable_to_initialize___this___context__5, 318 null)); 319 } 320 field.setValue(thisObject); 321 } 322 } 323 324 /** 325 * Restores the value local variables from the instance variables in the 326 * 'code snippet object' that are used as place-holders for locals in the 327 * current stack frame. 328 * 329 * @param object 330 * instance of code snippet class that was run 331 * @exception DebugException 332 * if an exception is thrown accessing the given object 333 */ restoreLocals(IJavaObject object)334 protected void restoreLocals(IJavaObject object) throws DebugException { 335 IJavaVariable[] locals = null; 336 if (getStackFrame() != null) { 337 locals = getStackFrame().getLocalVariables(); 338 } 339 if (locals != null) { 340 for (IJavaVariable local : locals) { 341 IJavaVariable field = object.getField( 342 LOCAL_VAR_PREFIX + local.getName(), false); 343 // internal error if field is not found 344 if (field == null) { 345 throw new DebugException( 346 new Status( 347 IStatus.ERROR, 348 JDIDebugModel.getPluginIdentifier(), 349 DebugException.REQUEST_FAILED, 350 EvaluationMessages.LocalEvaluationEngine_Evaluation_failed___unable_to_initialize_local_variables__6, 351 null)); 352 } 353 local.setValue(field.getValue()); 354 } 355 } 356 } 357 358 /** 359 * @see ICodeSnippetRequestor#acceptProblem(IMarker, String, int) 360 */ 361 @Override acceptProblem(IMarker problemMarker, String fragmentSource, int fragmentKind)362 public void acceptProblem(IMarker problemMarker, String fragmentSource, 363 int fragmentKind) { 364 if (problemMarker.getAttribute(IMarker.SEVERITY, -1) != IMarker.SEVERITY_ERROR) { 365 return; 366 } 367 getResult().addError(problemMarker.getAttribute(IMarker.MESSAGE, "")); //$NON-NLS-1$ 368 } 369 370 /** 371 * @see IEvaluationEngine#getDebugTarget() 372 */ 373 @Override getDebugTarget()374 public IJavaDebugTarget getDebugTarget() { 375 return fDebugTarget; 376 } 377 378 /** 379 * Sets the debug target in which snippets are executed. 380 * 381 * @param debugTarget 382 * the debug target in which snippets are executed 383 */ setDebugTarget(IJavaDebugTarget debugTarget)384 private void setDebugTarget(IJavaDebugTarget debugTarget) { 385 fDebugTarget = debugTarget; 386 } 387 388 /** 389 * @see IEvaluationEngine#getJavaProject() 390 */ 391 @Override getJavaProject()392 public IJavaProject getJavaProject() { 393 return fJavaProject; 394 } 395 396 /** 397 * Sets the Java project in which snippets are compiled. 398 * 399 * @param javaProject 400 * the Java project in which snippets are compiled 401 */ setJavaProject(IJavaProject javaProject)402 private void setJavaProject(IJavaProject javaProject) { 403 fJavaProject = javaProject; 404 } 405 406 /** 407 * Returns the directory in which snippet class files are deployed. 408 * 409 * @return the directory in which snippet class files are deployed. 410 */ getOutputDirectory()411 public File getOutputDirectory() { 412 return fOutputDirectory; 413 } 414 415 /** 416 * Sets the directory in which snippet class files are deployed. 417 * 418 * @param outputDirectory 419 * location to deploy snippet class files 420 */ setOutputDirectory(File outputDirectory)421 private void setOutputDirectory(File outputDirectory) { 422 fOutputDirectory = outputDirectory; 423 } 424 425 /** 426 * @see IClassFileEvaluationEngine#evaluate(String, IJavaThread, 427 * IEvaluationListener) 428 */ 429 @Override evaluate(String snippet, IJavaThread thread, IEvaluationListener listener, boolean hitBreakpoints)430 public void evaluate(String snippet, IJavaThread thread, 431 IEvaluationListener listener, boolean hitBreakpoints) 432 throws DebugException { 433 checkDisposed(); 434 checkEvaluating(); 435 try { 436 evaluationStarted(); 437 setListener(listener); 438 setHitBreakpoints(hitBreakpoints); 439 setResult(new EvaluationResult(this, snippet, thread)); 440 checkThread(); 441 // no receiver/stack frame context 442 setThis(null); 443 setLocalVariableNames(EMPTY_STRING_ARRAY); 444 setLocalVariableTypeNames(EMPTY_STRING_ARRAY); 445 setLocalVariableModifiers(EMPTY_INT_ARRAY); 446 447 // do the evaluation in a different thread 448 Runnable r = new Runnable() { 449 @Override 450 public void run() { 451 try { 452 LocalEvaluationEngine.this 453 .getEvaluationContext() 454 .evaluateCodeSnippet( 455 LocalEvaluationEngine.this.getSnippet(), 456 LocalEvaluationEngine.this, null); 457 } catch (JavaModelException e) { 458 LocalEvaluationEngine.this.getResult().setException( 459 new DebugException(e.getStatus())); 460 } finally { 461 LocalEvaluationEngine.this.evaluationComplete(); 462 } 463 } 464 }; 465 466 Thread t = new Thread(r); 467 t.setDaemon(true); 468 t.start(); 469 } catch (DebugException d) { 470 evaluationAborted(); 471 throw d; 472 } 473 474 } 475 476 /** 477 * @see IEvaluationEngine#evaluate(String, IJavaStackFrame, 478 * IEvaluationListener, int) 479 */ 480 @Override evaluate(String snippet, IJavaStackFrame frame, IEvaluationListener listener, int evaluationDetail, boolean hitBreakpoints)481 public void evaluate(String snippet, IJavaStackFrame frame, 482 IEvaluationListener listener, int evaluationDetail, 483 boolean hitBreakpoints) throws DebugException { 484 checkDisposed(); 485 checkEvaluating(); 486 try { 487 evaluationStarted(); 488 setListener(listener); 489 setStackFrame(frame); 490 setHitBreakpoints(hitBreakpoints); 491 setResult(new EvaluationResult(this, snippet, 492 (IJavaThread) frame.getThread())); 493 checkThread(); 494 495 // set up local variables and 'this' context for evaluation 496 IJavaVariable[] locals = frame.getLocalVariables(); 497 498 List<String> typeNames = new ArrayList<>(locals.length); 499 List<String> varNames = new ArrayList<>(locals.length); 500 501 for (IJavaVariable var : locals) { 502 String typeName = getTranslatedTypeName(var 503 .getReferenceTypeName()); 504 if (typeName != null) { 505 typeNames.add(typeName); 506 varNames.add(var.getName()); 507 } 508 } 509 510 setLocalVariableTypeNames(typeNames 511 .toArray(new String[typeNames.size()])); 512 setLocalVariableNames(varNames 513 .toArray(new String[varNames.size()])); 514 int[] modifiers = new int[typeNames.size()]; 515 // cannot determine if local is final, so specify as default 516 Arrays.fill(modifiers, 0); 517 setLocalVariableModifiers(modifiers); 518 setThis(frame.getThis()); 519 520 final boolean isStatic = frame.isStatic(); 521 final boolean isConstructor = frame.isConstructor(); 522 final IType receivingType = JavaDebugUtils 523 .resolveDeclaringType(frame); 524 validateReceivingType(receivingType); 525 526 // do the evaluation in a different thread 527 Runnable r = new Runnable() { 528 @Override 529 public void run() { 530 try { 531 LocalEvaluationEngine.this 532 .getEvaluationContext() 533 .evaluateCodeSnippet( 534 LocalEvaluationEngine.this.getSnippet(), 535 LocalEvaluationEngine.this 536 .getLocalVariableTypeNames(), 537 LocalEvaluationEngine.this 538 .getLocalVariableNames(), 539 LocalEvaluationEngine.this 540 .getLocalVariableModifiers(), 541 receivingType, isStatic, isConstructor, 542 LocalEvaluationEngine.this, null); 543 } catch (JavaModelException e) { 544 LocalEvaluationEngine.this.getResult().setException( 545 new DebugException(e.getStatus())); 546 } finally { 547 LocalEvaluationEngine.this.evaluationComplete(); 548 } 549 } 550 }; 551 552 Thread t = new Thread(r); 553 t.setDaemon(true); 554 t.start(); 555 } catch (DebugException d) { 556 evaluationAborted(); 557 throw d; 558 } catch (CoreException e) { 559 evaluationAborted(); 560 throw new DebugException(e.getStatus()); 561 } 562 } 563 564 /** 565 * Verifies the receiving type was resolved and is not an inner type. 566 * 567 * @param receivingType 568 * @throws DebugException 569 */ validateReceivingType(final IType receivingType)570 private void validateReceivingType(final IType receivingType) 571 throws DebugException { 572 if (receivingType == null) { 573 throw new DebugException( 574 new Status( 575 IStatus.ERROR, 576 JDIDebugModel.getPluginIdentifier(), 577 DebugException.REQUEST_FAILED, 578 EvaluationMessages.LocalEvaluationEngine_Evaluation_failed___unable_to_determine_receiving_type_context__18, 579 null)); 580 } 581 582 if (receivingType.getDeclaringType() != null) { 583 throw new DebugException( 584 new Status( 585 IStatus.ERROR, 586 JDIDebugModel.getPluginIdentifier(), 587 DebugException.REQUEST_FAILED, 588 EvaluationMessages.LocalEvaluationEngine_Evaluation_in_context_of_inner_type_not_supported__19, 589 null)); 590 } 591 } 592 593 /** 594 * @see IEvaluationEngine#evaluate(String, IJavaObject, IJavaThread, 595 * IEvaluationListener, int) 596 */ 597 @Override evaluate(String snippet, IJavaObject thisContext, IJavaThread thread, IEvaluationListener listener, int evaluationDetail, boolean hitBreakpoints)598 public void evaluate(String snippet, IJavaObject thisContext, 599 IJavaThread thread, IEvaluationListener listener, 600 int evaluationDetail, boolean hitBreakpoints) throws DebugException { 601 checkDisposed(); 602 checkEvaluating(); 603 try { 604 evaluationStarted(); 605 setListener(listener); 606 setHitBreakpoints(hitBreakpoints); 607 setResult(new EvaluationResult(this, snippet, thread)); 608 checkThread(); 609 610 // no locals 611 setLocalVariableTypeNames(new String[0]); 612 setLocalVariableNames(new String[0]); 613 setLocalVariableModifiers(new int[0]); 614 615 setThis(thisContext); 616 617 final boolean isStatic = false; 618 final boolean isConstructor = false; 619 final IType receivingType = JavaDebugUtils.resolveType(thisContext 620 .getJavaType()); 621 validateReceivingType(receivingType); 622 623 // do the evaluation in a different thread 624 Runnable r = new Runnable() { 625 @Override 626 public void run() { 627 try { 628 LocalEvaluationEngine.this 629 .getEvaluationContext() 630 .evaluateCodeSnippet( 631 LocalEvaluationEngine.this.getSnippet(), 632 LocalEvaluationEngine.this 633 .getLocalVariableTypeNames(), 634 LocalEvaluationEngine.this 635 .getLocalVariableNames(), 636 LocalEvaluationEngine.this 637 .getLocalVariableModifiers(), 638 receivingType, isStatic, isConstructor, 639 LocalEvaluationEngine.this, null); 640 } catch (JavaModelException e) { 641 LocalEvaluationEngine.this.getResult().setException( 642 new DebugException(e.getStatus())); 643 } finally { 644 LocalEvaluationEngine.this.evaluationComplete(); 645 } 646 } 647 }; 648 649 Thread t = new Thread(r); 650 t.setDaemon(true); 651 t.start(); 652 } catch (DebugException d) { 653 evaluationAborted(); 654 throw d; 655 } catch (CoreException e) { 656 evaluationAborted(); 657 throw new DebugException(e.getStatus()); 658 } 659 } 660 661 /** 662 * Throws an exception if this engine has already been disposed. 663 * 664 * @exception DebugException 665 * if this engine has been disposed 666 */ checkDisposed()667 protected void checkDisposed() throws DebugException { 668 if (isDisposed()) { 669 throw new DebugException( 670 new Status( 671 IStatus.ERROR, 672 JDIDebugModel.getPluginIdentifier(), 673 DebugException.REQUEST_FAILED, 674 EvaluationMessages.LocalEvaluationEngine_Evaluation_failed___evaluation_context_has_been_disposed__7, 675 null)); 676 } 677 } 678 679 /** 680 * Throws an exception if this engine is already in an evaluation. 681 * 682 * @exception DebugException 683 * if this engine is currently performing an evaluation 684 */ checkEvaluating()685 protected void checkEvaluating() throws DebugException { 686 if (isEvaluating()) { 687 throw new DebugException(new Status(IStatus.ERROR, 688 JDIDebugModel.getPluginIdentifier(), 689 DebugException.REQUEST_FAILED, 690 "Cannot perform nested evaluations.", null) //$NON-NLS-1$ 691 ); 692 } 693 } 694 695 /** 696 * Throws an exception if this engine's current evaluation thread is not 697 * suspended. 698 * 699 * @exception DebugException 700 * if this engine's current evaluation thread is not 701 * suspended 702 */ checkThread()703 protected void checkThread() throws DebugException { 704 if (!getThread().isSuspended()) { 705 throw new DebugException( 706 new Status( 707 IStatus.ERROR, 708 JDIDebugModel.getPluginIdentifier(), 709 DebugException.REQUEST_FAILED, 710 EvaluationMessages.LocalEvaluationEngine_Evaluation_failed___evaluation_thread_must_be_suspended__8, 711 null)); 712 } 713 } 714 715 /** 716 * Deletes deployed class files, and clears state. 717 * 718 * @see IEvaluationEngine#dispose() 719 */ 720 @Override dispose()721 public void dispose() { 722 fDisposed = true; 723 ENGINE_COUNT--; 724 if (isEvaluating()) { 725 // cannot dispose if in an evaluation, must 726 // wait for evaluation to complete 727 return; 728 } 729 List<File> snippetFiles = getSnippetFiles(); 730 Iterator<File> iter = snippetFiles.iterator(); 731 while (iter.hasNext()) { 732 File file = iter.next(); 733 if (file.exists()) { 734 if (CODE_SNIPPET_NAME.equals(file.getName()) 735 && ENGINE_COUNT > 0) { 736 continue; // do not delete the common file for other engines 737 } 738 if (!file.delete()) { 739 JDIDebugPlugin 740 .log(new Status( 741 IStatus.ERROR, 742 JDIDebugModel.getPluginIdentifier(), 743 DebugException.REQUEST_FAILED, 744 MessageFormat 745 .format("Unable to delete temporary evaluation class file {0}.", new Object[] { file.getAbsolutePath() }), null) //$NON-NLS-1$ 746 ); 747 } 748 } 749 } 750 List<File> directories = getDirectories(); 751 // remove directories in bottom up order 752 int i = directories.size() - 1; 753 while (i >= 0) { 754 File dir = directories.get(i); 755 String[] listing = dir.list(); 756 if (dir.exists() && listing != null && listing.length == 0 757 && !dir.delete()) { 758 JDIDebugPlugin 759 .log(new Status( 760 IStatus.ERROR, 761 JDIDebugModel.getPluginIdentifier(), 762 DebugException.REQUEST_FAILED, 763 MessageFormat 764 .format("Unable to delete temporary evaluation directory {0}.", new Object[] { dir.getAbsolutePath() }), null) //$NON-NLS-1$ 765 ); 766 } 767 i--; 768 } 769 reset(); 770 setJavaProject(null); 771 setDebugTarget(null); 772 setOutputDirectory(null); 773 setResult(null); 774 setEvaluationContext(null); 775 } 776 777 /** 778 * Resets this engine for another evaluation. 779 */ reset()780 private void reset() { 781 setThis(null); 782 setStackFrame(null); 783 setListener(null); 784 } 785 786 /** 787 * Returns the listener to notify when the current evaluation is complete. 788 * 789 * @return the listener to notify when the current evaluation is complete 790 */ getListener()791 protected IEvaluationListener getListener() { 792 return fListener; 793 } 794 795 /** 796 * Sets the listener to notify when the current evaluation is complete. 797 * 798 * @param listener 799 * the listener to notify when the current evaluation is complete 800 */ setListener(IEvaluationListener listener)801 private void setListener(IEvaluationListener listener) { 802 fListener = listener; 803 } 804 805 /** 806 * Returns the stack frame context for the current evaluation, or 807 * <code>null</code> if none. 808 * 809 * @return the stack frame context for the current evaluation, or 810 * <code>null</code> if none 811 */ getStackFrame()812 protected IJavaStackFrame getStackFrame() { 813 return fStackFrame; 814 } 815 816 /** 817 * Sets the stack frame context for the current evaluation. 818 * 819 * @param stackFrame 820 * stack frame context or <code>null</code> if none 821 */ setStackFrame(IJavaStackFrame stackFrame)822 private void setStackFrame(IJavaStackFrame stackFrame) { 823 fStackFrame = stackFrame; 824 } 825 826 /** 827 * Returns the thread in which the current evaluation is to be executed. 828 * 829 * @return the thread in which the current evaluation is to be executed 830 */ getThread()831 protected IJavaThread getThread() { 832 return getResult().getThread(); 833 } 834 835 /** 836 * Returns the code snippet being evaluated. 837 * 838 * @return the code snippet being evaluated. 839 */ getSnippet()840 protected String getSnippet() { 841 return getResult().getSnippet(); 842 } 843 844 /** 845 * Returns the current evaluation result. 846 * 847 * @return the current evaluation result 848 */ getResult()849 protected EvaluationResult getResult() { 850 return fResult; 851 } 852 853 /** 854 * Sets the current evaluation result. 855 * 856 * @param result 857 * the current evaluation result 858 */ setResult(EvaluationResult result)859 private void setResult(EvaluationResult result) { 860 fResult = result; 861 } 862 863 /** 864 * Deploys the given class files to this engine's output location, and adds 865 * the files to this engines list of temporary files to be deleted when 866 * disposed. 867 * 868 * @exception DebugException 869 * if this fails due to a lower level exception. 870 */ deploy(byte[][] classFiles, String[][] classFileNames)871 protected void deploy(byte[][] classFiles, String[][] classFileNames) 872 throws DebugException { 873 for (int i = 0; i < classFiles.length; i++) { 874 String[] compoundName = classFileNames[i]; 875 // create required folders 876 File dir = LocalEvaluationEngine.this.getOutputDirectory(); 877 try { 878 String pkgDirName = dir.getCanonicalPath(); 879 for (int j = 0; j < (compoundName.length - 1); j++) { 880 pkgDirName += File.separator + compoundName[j]; 881 File pkgDir = new File(pkgDirName); 882 if (!pkgDir.exists()) { 883 pkgDir.mkdir(); 884 addDirectory(pkgDir); 885 } 886 } 887 String name = compoundName[compoundName.length - 1] + ".class"; //$NON-NLS-1$ 888 File classFile = new File(pkgDirName + File.separator + name); 889 if (!classFile.exists()) { 890 classFile.createNewFile(); 891 } 892 try (FileOutputStream stream = new FileOutputStream(classFile)) { 893 stream.write(classFiles[i]); 894 } 895 LocalEvaluationEngine.this.addSnippetFile(classFile); 896 } catch (IOException e) { 897 throw new DebugException( 898 new Status( 899 IStatus.ERROR, 900 JDIDebugModel.getPluginIdentifier(), 901 DebugException.REQUEST_FAILED, 902 MessageFormat 903 .format(EvaluationMessages.LocalEvaluationEngine__0__occurred_deploying_class_file_for_evaluation_9, 904 new Object[] { e.toString() }), 905 e)); 906 } 907 } 908 } 909 910 /** 911 * Adds the given file to this engine's collection of deployed snippet class 912 * files, which are to be deleted when this engine is disposed. 913 * 914 * @param File 915 * snippet class file 916 */ addSnippetFile(File file)917 private void addSnippetFile(File file) { 918 if (fSnippetFiles == null) { 919 fSnippetFiles = new ArrayList<>(); 920 } 921 fSnippetFiles.add(file); 922 } 923 924 /** 925 * Adds the given file to this engine's collection of created directories, 926 * which are to be deleted when this engine is disposed. 927 * 928 * @param file 929 * directory created for class file deployment 930 */ addDirectory(File file)931 private void addDirectory(File file) { 932 if (fDirectories == null) { 933 fDirectories = new ArrayList<>(); 934 } 935 fDirectories.add(file); 936 } 937 938 /** 939 * Returns an evaluation context for this evaluation engine. An evaluation 940 * context is associated with a specific Java project. The evaluation context 941 * is created lazily on the first access. 942 * 943 * @return evaluation context 944 */ getEvaluationContext()945 protected IEvaluationContext getEvaluationContext() { 946 if (fEvaluationContext == null) { 947 fEvaluationContext = getJavaProject().newEvaluationContext(); 948 } 949 return fEvaluationContext; 950 } 951 952 /** 953 * Sets the evaluation context for this evaluation engine. 954 * 955 * @param context 956 * evaluation context 957 */ setEvaluationContext(IEvaluationContext context)958 private void setEvaluationContext(IEvaluationContext context) { 959 fEvaluationContext = context; 960 } 961 962 /** 963 * Returns a collection of snippet class file deployed by this evaluation 964 * engine, possibly empty. 965 * 966 * @return deployed class files 967 */ getSnippetFiles()968 protected List<File> getSnippetFiles() { 969 if (fSnippetFiles == null) { 970 return Collections.EMPTY_LIST; 971 } 972 return fSnippetFiles; 973 } 974 975 /** 976 * Returns a collection of directories created by this evaluation engine, 977 * possibly empty. 978 * 979 * @return directories created when deploying class files 980 */ getDirectories()981 protected List<File> getDirectories() { 982 if (fDirectories == null) { 983 return Collections.EMPTY_LIST; 984 } 985 return fDirectories; 986 } 987 988 /** 989 * Returns whether this evaluation engine has been disposed. 990 * 991 * @return whether this evaluation engine has been disposed 992 */ isDisposed()993 protected boolean isDisposed() { 994 return fDisposed; 995 } 996 997 /** 998 * The evaluation is complete. Notify the current listener and reset for the 999 * next evaluation. 1000 */ evaluationComplete()1001 protected void evaluationComplete() { 1002 // only notify if plug-in not yet shutdown (bug# 8693) 1003 if (JDIDebugPlugin.getDefault() != null) { 1004 getListener().evaluationComplete(getResult()); 1005 } 1006 evaluationEnded(); 1007 reset(); 1008 if (isDisposed()) { 1009 // if the engine was disposed during an evaluation 1010 // do the cleanup now 1011 dispose(); 1012 } 1013 } 1014 1015 /** 1016 * Increments the evaluation counter. 1017 */ evaluationStarted()1018 private void evaluationStarted() { 1019 fEvaluationCount++; 1020 } 1021 1022 /** 1023 * Decrements the evaluation counter. 1024 */ evaluationEnded()1025 private void evaluationEnded() { 1026 if (fEvaluationCount > 0) { 1027 fEvaluationCount--; 1028 } 1029 } 1030 1031 /** 1032 * Returns whether this engine is currently in the midst of an evaluation. 1033 */ isEvaluating()1034 protected boolean isEvaluating() { 1035 return fEvaluationCount > 0; 1036 } 1037 1038 /** 1039 * Called when an evaluation is aborted due to an exception. Decrements the 1040 * evaluation count, and disposes this engine if the target VM disconnected 1041 * or terminated during the evaluation attempt. 1042 */ evaluationAborted()1043 private void evaluationAborted() { 1044 evaluationEnded(); 1045 if (isDisposed()) { 1046 // if the engine was disposed during an evaluation 1047 // do the cleanup now 1048 dispose(); 1049 } 1050 } 1051 1052 /** 1053 * Constructs and returns a new instance of the specified class on the 1054 * target VM. 1055 * 1056 * @param className 1057 * fully qualified class name 1058 * @return a new instance on the target, as an <code>IJavaValue</code> 1059 * @exception DebugException 1060 * if creation fails 1061 */ newInstance(String className)1062 protected IJavaObject newInstance(String className) throws DebugException { 1063 IJavaObject object = null; 1064 IJavaClassType clazz = null; 1065 IJavaType[] types = getDebugTarget().getJavaTypes(className); 1066 if (types != null && types.length > 0) { 1067 clazz = (IJavaClassType) types[0]; 1068 } 1069 if (clazz == null) { 1070 // The class is not loaded on the target VM. 1071 // Force the load of the class. 1072 types = getDebugTarget().getJavaTypes("java.lang.Class"); //$NON-NLS-1$ 1073 IJavaClassType classClass = null; 1074 if (types != null && types.length > 0) { 1075 classClass = (IJavaClassType) types[0]; 1076 } 1077 if (classClass == null) { 1078 // unable to load the class 1079 throw new DebugException( 1080 new Status( 1081 IStatus.ERROR, 1082 JDIDebugModel.getPluginIdentifier(), 1083 DebugException.REQUEST_FAILED, 1084 EvaluationMessages.LocalEvaluationEngine_Evaluation_failed___unable_to_instantiate_code_snippet_class__11, 1085 null)); 1086 } 1087 IJavaValue[] args = new IJavaValue[] { getDebugTarget().newValue( 1088 className) }; 1089 IJavaObject classObject = (IJavaObject) classClass 1090 .sendMessage( 1091 "forName", "(Ljava/lang/String;)Ljava/lang/Class;", args, getThread()); //$NON-NLS-2$ //$NON-NLS-1$ 1092 object = (IJavaObject) classObject 1093 .sendMessage( 1094 "newInstance", "()Ljava/lang/Object;", null, getThread(), false); //$NON-NLS-2$ //$NON-NLS-1$ 1095 } else { 1096 object = clazz.newInstance("<init>", null, getThread()); //$NON-NLS-1$ 1097 } 1098 return object; 1099 } 1100 1101 /** 1102 * Interprets and returns the result of the running the snippet class file. 1103 * The type of the result is described by an instance of 1104 * <code>java.lang.Class</code>. The value is interpreted based on the 1105 * result type. 1106 * <p> 1107 * Objects as well as primitive data types (boolean, int, etc.), have class 1108 * objects, which are created by the VM. If the class object represents a 1109 * primitive data type, then the associated value is stored in an instance 1110 * of its "object" class. For example, when the result type is the class 1111 * object for <code>int</code>, the result object is an instance of 1112 * <code>java.lang.Integer</code>, and the actual <code>int</code> is stored 1113 * in the </code>intValue()</code>. When the result type is the class object 1114 * for <code>java.lang.Integer</code> the result object is an instance of 1115 * <code>java.lang.Integer</code>, to be interpreted as a 1116 * <code>java.lang.Integer</code>. 1117 * </p> 1118 * 1119 * @param resultType 1120 * the class of the result 1121 * @param resultValue 1122 * the value of the result, to be interpreted based on 1123 * resultType 1124 * @return the result of running the code snippet class file 1125 */ convertResult(IJavaClassObject resultType, IJavaValue result)1126 protected IJavaValue convertResult(IJavaClassObject resultType, 1127 IJavaValue result) throws DebugException { 1128 if (resultType == null) { 1129 // there was an exception or compilation problem - no result 1130 return null; 1131 } 1132 1133 // check the type of the result - if a primitive type, convert it 1134 String sig = resultType.getInstanceType().getSignature(); 1135 if (sig.equals("V") || sig.equals("Lvoid;")) { //$NON-NLS-2$ //$NON-NLS-1$ 1136 // void 1137 return getDebugTarget().voidValue(); 1138 } 1139 1140 if (result.getJavaType() == null) { 1141 // null result 1142 return result; 1143 } 1144 1145 if (sig.length() == 1) { 1146 // primitive type - find the instance variable with the 1147 // signature of the result type we are looking for 1148 IVariable[] vars = result.getVariables(); 1149 IJavaVariable var = null; 1150 for (IVariable var2 : vars) { 1151 IJavaVariable jv = (IJavaVariable) var2; 1152 if (!jv.isStatic() && jv.getSignature().equals(sig)) { 1153 var = jv; 1154 break; 1155 } 1156 } 1157 if (var != null) { 1158 return (IJavaValue) var.getValue(); 1159 } 1160 } else { 1161 // an object 1162 return result; 1163 } 1164 throw new DebugException( 1165 new Status( 1166 IStatus.ERROR, 1167 JDIDebugModel.getPluginIdentifier(), 1168 DebugException.REQUEST_FAILED, 1169 EvaluationMessages.LocalEvaluationEngine_Evaluation_failed___internal_error_retreiving_result__17, 1170 null)); 1171 } 1172 1173 /** 1174 * Returns the modifiers of the local variables visible in this evaluation, 1175 * possibly empty. 1176 * 1177 * @return array of modifiers 1178 */ getLocalVariableModifiers()1179 private int[] getLocalVariableModifiers() { 1180 return fLocalVariableModifiers; 1181 } 1182 1183 /** 1184 * Sets the modifiers of the local variables visible in this evaluation, 1185 * possibly empty. 1186 * 1187 * @param localVariableModifiers 1188 * array of modifiers 1189 */ setLocalVariableModifiers(int[] localVariableModifiers)1190 private void setLocalVariableModifiers(int[] localVariableModifiers) { 1191 fLocalVariableModifiers = localVariableModifiers; 1192 } 1193 1194 /** 1195 * Returns the names of the local variables visible in this evaluation, 1196 * possibly empty. 1197 * 1198 * @param array 1199 * of names 1200 */ getLocalVariableNames()1201 private String[] getLocalVariableNames() { 1202 return fLocalVariableNames; 1203 } 1204 1205 /** 1206 * Sets the names of the local variables visible in this evaluation, 1207 * possibly empty. 1208 * 1209 * @param localVariableNames 1210 * array of names 1211 */ setLocalVariableNames(String[] localVariableNames)1212 private void setLocalVariableNames(String[] localVariableNames) { 1213 fLocalVariableNames = localVariableNames; 1214 } 1215 1216 /** 1217 * Returns the type names of the local variables visible in this evaluation, 1218 * possibly empty. 1219 * 1220 * @param array 1221 * of type names 1222 */ getLocalVariableTypeNames()1223 private String[] getLocalVariableTypeNames() { 1224 return fLocalVariableTypeNames; 1225 } 1226 1227 /** 1228 * Sets the type names of the local variables visible in this evaluation, 1229 * possibly empty. 1230 * 1231 * @param localVariableTypeNames 1232 * array of type names 1233 */ setLocalVariableTypeNames(String[] localVariableTypeNames)1234 private void setLocalVariableTypeNames(String[] localVariableTypeNames) { 1235 fLocalVariableTypeNames = localVariableTypeNames; 1236 } 1237 1238 /** 1239 * Sets the receiver context for the associated evaluation, possibly 1240 * <code>null</code> if the evaluation is in the context of a static method 1241 * or there is no object context. 1242 * 1243 * @param thisObject 1244 * the receiver content of the associated evaluation, or 1245 * <code>null</code> 1246 */ setThis(IJavaObject thisObject)1247 private void setThis(IJavaObject thisObject) { 1248 fThis = thisObject; 1249 } 1250 1251 /** 1252 * Returns the receiver context for the associated evaluation, or 1253 * <code>null</code> if the evaluation is in the context of a static method 1254 * or there is no object context. 1255 * 1256 * @return the receiver context of the associated evaluation or 1257 * <code>null</code> 1258 */ getThis()1259 private IJavaObject getThis() { 1260 return fThis; 1261 } 1262 1263 /** 1264 * Returns a copy of the type name with '$' replaced by '.', or returns 1265 * <code>null</code> if the given type name refers to an anonymous inner 1266 * class. 1267 * 1268 * @param typeName 1269 * a fully qualified type name 1270 * @return a copy of the type name with '$' replaced by '.', or returns 1271 * <code>null</code> if the given type name refers to an anonymous 1272 * inner class. 1273 */ getTranslatedTypeName(String typeName)1274 protected String getTranslatedTypeName(String typeName) { 1275 int index = typeName.lastIndexOf('$'); 1276 if (index == -1) { 1277 return typeName; 1278 } 1279 if (index + 1 > typeName.length()) { 1280 // invalid name 1281 return typeName; 1282 } 1283 String last = typeName.substring(index + 1); 1284 try { 1285 Integer.parseInt(last); 1286 return null; 1287 } catch (NumberFormatException e) { 1288 return typeName.replace('$', '.'); 1289 } 1290 } 1291 1292 /** 1293 * Returns an array of simple type names that are part of the given type's 1294 * qualified name. For example, if the given name is <code>x.y.A$B</code>, 1295 * an array with <code>["A", "B"]</code> is returned. 1296 * 1297 * @param typeName 1298 * fully qualified type name 1299 * @return array of nested type names 1300 */ getNestedTypeNames(String typeName)1301 protected String[] getNestedTypeNames(String typeName) { 1302 int index = typeName.lastIndexOf('.'); 1303 if (index >= 0) { 1304 typeName = typeName.substring(index + 1); 1305 } 1306 index = typeName.indexOf('$'); 1307 ArrayList<String> list = new ArrayList<>(1); 1308 while (index >= 0) { 1309 list.add(typeName.substring(0, index)); 1310 typeName = typeName.substring(index + 1); 1311 index = typeName.indexOf('$'); 1312 } 1313 list.add(typeName); 1314 return list.toArray(new String[list.size()]); 1315 } 1316 1317 /** 1318 * @see IClassFileEvaluationEngine#getImports() 1319 */ 1320 @Override getImports()1321 public String[] getImports() { 1322 return getEvaluationContext().getImports(); 1323 } 1324 1325 /** 1326 * @see IClassFileEvaluationEngine#setImports(String[]) 1327 */ 1328 @Override setImports(String[] imports)1329 public void setImports(String[] imports) { 1330 getEvaluationContext().setImports(imports); 1331 } 1332 1333 /** 1334 * Sets the name of the code snippet to instantiate to run the current 1335 * evaluation. 1336 * 1337 * @param name 1338 * the name of the deployed code snippet to instantiate and run 1339 */ setCodeSnippetClassName(String name)1340 private void setCodeSnippetClassName(String name) { 1341 fCodeSnippetClassName = name; 1342 } 1343 1344 /** 1345 * Returns the name of the code snippet to instantiate to run the current 1346 * evaluation. 1347 * 1348 * @return the name of the deployed code snippet to instantiate and run 1349 */ getCodeSnippetClassName()1350 protected String getCodeSnippetClassName() { 1351 return fCodeSnippetClassName; 1352 } 1353 1354 /** 1355 * @see ICodeSnippetRequestor#isRequestingClassFiles() 1356 */ isRequestingClassFiles()1357 public boolean isRequestingClassFiles() { 1358 return true; 1359 } 1360 1361 /** 1362 * Returns whether to hit breakpoints in the evaluation thread. 1363 * 1364 * @return whether to hit breakpoints in the evaluation thread 1365 */ getHitBreakpoints()1366 protected boolean getHitBreakpoints() { 1367 return fHitBreakpoints; 1368 } 1369 1370 /** 1371 * Sets whether to hit breakpoints in the evaluation thread. 1372 * 1373 * @param hit 1374 * whether to hit breakpoints in the evaluation thread 1375 */ setHitBreakpoints(boolean hit)1376 private void setHitBreakpoints(boolean hit) { 1377 fHitBreakpoints = hit; 1378 } 1379 1380 } 1381