[552542] Escape special characters in Strings rendered as HTML
- add code to escape special characters in Strings rendered as HTML,
including thread and class names
- add some tests to verify the validity of the generated HTML
Change-Id: I35614d50182a21a757b3d626803a926898595bfc
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery.java
index d4136d9..9c2085e 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/LeakHunterQuery.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2008, 2018 SAP AG, IBM Corporation and others.
+ * Copyright (c) 2008, 2019 SAP AG, IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -71,6 +71,7 @@
import org.eclipse.mat.snapshot.query.PieFactory;
import org.eclipse.mat.snapshot.query.SnapshotQuery;
import org.eclipse.mat.snapshot.registry.TroubleTicketResolverRegistry;
+import org.eclipse.mat.util.HTMLUtils;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
@@ -243,7 +244,7 @@
{
overview.append("<p>"); //$NON-NLS-1$
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Thread, //
- suspect.getSuspect().getDisplayName(), //
+ HTMLUtils.escapeText(suspect.getSuspect().getDisplayName()), //
formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
overview.append("</p>"); //$NON-NLS-1$
}
@@ -270,7 +271,7 @@
String classloaderName = getClassLoaderName(suspectClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Class, //
- className, classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
+ HTMLUtils.escapeText(className), classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
}
else
{
@@ -285,7 +286,7 @@
String classloaderName = getClassLoaderName(suspectClassloader, keywords);
overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_Instance, //
- className, classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
+ HTMLUtils.escapeText(className), classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
/*
* if the class name matches the skip pattern, try to find the first
@@ -326,8 +327,8 @@
// involvedClassloaders.add(suspectClassloader);
objectsForTroubleTicketInfo.add(referrer);
String referrerClassloaderName = getClassLoaderName(referrerClassloader, keywords);
- overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedByInstance, referrer
- .getDisplayName(), referrerClassloaderName));
+ overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_ReferencedByInstance, HTMLUtils.escapeText(referrer
+ .getDisplayName()), referrerClassloaderName));
}
}
@@ -359,7 +360,7 @@
String classloaderName = getClassLoaderName(accPointClassloader, keywords);
- overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_AccumulatedByLoadedBy, clazz.getName(),
+ overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_AccumulatedByLoadedBy, HTMLUtils.escapeText(clazz.getName()),
classloaderName));
}
else
@@ -373,7 +374,7 @@
objectsForTroubleTicketInfo.add(accumulationObject);
String classloaderName = getClassLoaderName(accPointClassloader, keywords);
- overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_AccumulatedByInstance, className,
+ overview.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_AccumulatedByInstance, HTMLUtils.escapeText(className),
classloaderName));
}
}
@@ -466,7 +467,7 @@
String classloaderName = getClassLoaderName(classloader, keywords);
String numberOfInstances = numberFormatter.format(suspect.getSuspectInstances().length);
- builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_InstancesOccupy, numberOfInstances, className,
+ builder.append(MessageUtil.format(Messages.LeakHunterQuery_Msg_InstancesOccupy, numberOfInstances, HTMLUtils.escapeText(className),
classloaderName, formatRetainedHeap(suspect.getSuspectRetained(), totalHeap)));
int[] suspectInstances = suspect.getSuspectInstances();
@@ -485,12 +486,13 @@
builder.append("<ul>"); //$NON-NLS-1$
for (IObject inst : bigSuspectInstances)
{
- builder.append("<li>").append(inst.getDisplayName()); //$NON-NLS-1$
+ builder.append("<li>").append(HTMLUtils.escapeText(inst.getDisplayName())); //$NON-NLS-1$
builder.append(" - ") //$NON-NLS-1$
.append(
MessageUtil.format(Messages.LeakHunterQuery_Msg_Bytes,
formatRetainedHeap(inst.getRetainedHeapSize(),
totalHeap)));
+ builder.append("</li>"); //$NON-NLS-1$
}
builder.append("</ul>"); //$NON-NLS-1$
}
@@ -643,6 +645,12 @@
return name;
}
+ /**
+ * Get the name of the class loader.
+ * @param classloader
+ * @param keywords
+ * @return The name with HTML escapes already applied.
+ */
private String getClassLoaderName(IObject classloader, Set<String> keywords)
{
if (classloader.getObjectAddress() == 0)
@@ -656,7 +664,7 @@
{
keywords.add(classloaderName);
}
- return classloaderName;
+ return HTMLUtils.escapeText(classloaderName);
}
}
@@ -754,7 +762,7 @@
{
builder.append("<b>").append(Messages.LeakHunterQuery_Keywords).append("</b><br>"); //$NON-NLS-1$ //$NON-NLS-2$
for (String s : keywords)
- builder.append(s).append("<br>"); //$NON-NLS-1$
+ builder.append(HTMLUtils.escapeText(s)).append("<br>"); //$NON-NLS-1$
}
private void appendTroubleTicketInformation(List<IObject> classloaders, StringBuilder builder)
@@ -766,12 +774,12 @@
if (!mapping.isEmpty())
{
- builder.append("<br><b>").append(resolver.getTicketSystem()).append("</b><br>"); //$NON-NLS-1$ //$NON-NLS-2$
+ builder.append("<br><b>").append(HTMLUtils.escapeText(resolver.getTicketSystem())).append("</b><br>"); //$NON-NLS-1$ //$NON-NLS-2$
for (Map.Entry<String, String> entry : mapping.entrySet())
{
builder.append(
- MessageUtil.format(Messages.LeakHunterQuery_TicketForSuspect, entry.getKey(), entry
- .getValue())).append("<br>"); //$NON-NLS-1$
+ MessageUtil.format(Messages.LeakHunterQuery_TicketForSuspect, HTMLUtils.escapeText(entry.getKey()), HTMLUtils.escapeText(entry
+ .getValue()))).append("<br>"); //$NON-NLS-1$
}
}
}
@@ -798,7 +806,7 @@
builder.append("<p>"); //$NON-NLS-1$
for (CompositeResult.Entry requestInfo : requestInfos.getResultEntries())
- builder.append(requestInfo.getName()).append(" ").append( //$NON-NLS-1$
+ builder.append(HTMLUtils.escapeText(requestInfo.getName())).append(" ").append( //$NON-NLS-1$
textResult.linkTo(Messages.LeakHunterQuery_RequestDetails,
requestInfo.getResult())).append("<br>"); //$NON-NLS-1$
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/component/ComponentReportQuery.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/component/ComponentReportQuery.java
index f57448b..f816de7 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/component/ComponentReportQuery.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/component/ComponentReportQuery.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2008, 2018 SAP AG, IBM Corporation and others
+ * Copyright (c) 2008, 2019 SAP AG, IBM Corporation and others
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -59,6 +59,7 @@
import org.eclipse.mat.snapshot.query.PieFactory;
import org.eclipse.mat.snapshot.query.RetainedSizeDerivedData;
import org.eclipse.mat.snapshot.query.SnapshotQuery;
+import org.eclipse.mat.util.HTMLUtils;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
import org.eclipse.mat.util.Units;
@@ -379,8 +380,8 @@
StringBuilder comment = new StringBuilder();
comment.append(MessageUtil.format(Messages.ComponentReportQuery_Msg_FoundOccurrences, table.getRowCount(),
- totals.getLabel(2)));
- comment.append("<p>" + Messages.ComponentReportQuery_TopElementsInclude + "</p><ul>"); //$NON-NLS-1$ //$NON-NLS-2$
+ HTMLUtils.escapeText(totals.getLabel(2))));
+ comment.append("<p>").append(Messages.ComponentReportQuery_TopElementsInclude).append("</p><ul>"); //$NON-NLS-1$ //$NON-NLS-2$
for (int rowId = 0; rowId < table.getRowCount() && rowId < 5; rowId++)
{
@@ -392,7 +393,7 @@
String size = table.getFormattedColumnValue(row, 3);
comment.append("<li>").append(table.getFormattedColumnValue(row, 1)); //$NON-NLS-1$
- comment.append(" × <strong>").append(value).append("</strong> "); //$NON-NLS-1$ //$NON-NLS-2$
+ comment.append(" × <strong>").append(HTMLUtils.escapeText(value)).append("</strong> "); //$NON-NLS-1$ //$NON-NLS-2$
comment.append(MessageUtil.format(Messages.ComponentReportQuery_Label_Bytes, size)).append("</li>"); //$NON-NLS-1$
}
comment.append("</ul>"); //$NON-NLS-1$
@@ -470,7 +471,7 @@
comment.append("<li>"); //$NON-NLS-1$
comment.append(MessageUtil.format(Messages.ComponentReportQuery_Msg_InstancesRetainBytes,
- numberOfObjects, clazz.getName(), retainedSize));
+ numberOfObjects, HTMLUtils.escapeText(clazz.getName()), retainedSize));
comment.append("</li>"); //$NON-NLS-1$
}
@@ -557,7 +558,7 @@
comment.append("<li>"); //$NON-NLS-1$
comment.append(MessageUtil.format(Messages.ComponentReportQuery_Msg_InstancesRetainBytes,
- numberOfObjects, clazz.getName(), retainedSize));
+ numberOfObjects, HTMLUtils.escapeText(clazz.getName()), retainedSize));
comment.append("</li>"); //$NON-NLS-1$
}
@@ -647,7 +648,7 @@
comment.append("<li>"); //$NON-NLS-1$
comment.append(MessageUtil.format(Messages.ComponentReportQuery_Msg_InstancesRetainBytes,
- numberOfObjects, clazz.getName(), retainedSize));
+ numberOfObjects, HTMLUtils.escapeText(clazz.getName()), retainedSize));
comment.append("</li>"); //$NON-NLS-1$
break;
}
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/jetty/JettyRequestResolver.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/jetty/JettyRequestResolver.java
index 7548c06..a2898b4 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/jetty/JettyRequestResolver.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/inspections/jetty/JettyRequestResolver.java
@@ -22,6 +22,7 @@
import org.eclipse.mat.snapshot.extension.Subject;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.query.SnapshotQuery;
+import org.eclipse.mat.util.HTMLUtils;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.mat.util.MessageUtil;
@@ -42,8 +43,8 @@
// Summary
StringBuilder buf = new StringBuilder(256);
- buf.append(MessageUtil.format(Messages.JettyRequestResolver_Msg_ThreadExecutesHTTPRequest, requestURI
- .getClassSpecificName()));
+ buf.append(MessageUtil.format(Messages.JettyRequestResolver_Msg_ThreadExecutesHTTPRequest, HTMLUtils.escapeText(requestURI
+ .getClassSpecificName())));
String summary = buf.toString();
QuerySpec spec = new QuerySpec(Messages.JettyRequestResolver_Summary);
spec.setCommand("list_objects 0x" + Long.toHexString(httpRequest.getObjectAddress())); //$NON-NLS-1$
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties
index aba5eb4..72385f1 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/internal/messages.properties
@@ -141,7 +141,7 @@
ComponentReportQuery_Msg_NoCollisionRatiosFound=No maps found with collision ratios greater than 80%.
ComponentReportQuery_Msg_NoExcessiveEmptyCollectionsFound=No excessive usage of empty collections found.
ComponentReportQuery_Msg_NoFinalizerFound=Component does not keep object with Finalizer methods alive.
-ComponentReportQuery_Msg_NoFinalizerObjects=Heap dump contains no java.lang.ref.Finalizer objects.<br/>IBM VMs implement Finalizer differently and are currently not supported by this report.
+ComponentReportQuery_Msg_NoFinalizerObjects=Heap dump contains no java.lang.ref.Finalizer objects.<br>IBM VMs implement Finalizer differently and are currently not supported by this report.
ComponentReportQuery_Msg_NoLowFillRatiosFound=No serious amount of collections with low fill ratios found.
ComponentReportQuery_Msg_NoSoftReferencesFound=Heap dump contains no soft references.
ComponentReportQuery_Msg_NoWeakReferencesFound=Heap dump contains no weak references.
diff --git a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/query/PieFactory.java b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/query/PieFactory.java
index 658f4b2..1d27e83 100644
--- a/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/query/PieFactory.java
+++ b/plugins/org.eclipse.mat.api/src/org/eclipse/mat/snapshot/query/PieFactory.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2008, 2012 SAP AG and others.
+ * Copyright (c) 2008, 2019 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -258,7 +258,7 @@
private final static class SliceImpl implements IResultPie.ColoredSlice, Serializable
{
private static final long serialVersionUID = 1L;
- private static final String HTML_BREAK = "<br/>"; //$NON-NLS-1$
+ private static final String HTML_BREAK = "<br>"; //$NON-NLS-1$
int objectId;
diff --git a/plugins/org.eclipse.mat.chart/src/org/eclipse/mat/impl/chart/HtmlPieChartRenderer.java b/plugins/org.eclipse.mat.chart/src/org/eclipse/mat/impl/chart/HtmlPieChartRenderer.java
index fd5d4a7..0007ead 100644
--- a/plugins/org.eclipse.mat.chart/src/org/eclipse/mat/impl/chart/HtmlPieChartRenderer.java
+++ b/plugins/org.eclipse.mat.chart/src/org/eclipse/mat/impl/chart/HtmlPieChartRenderer.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2008, 2011 SAP AG and others.
+ * Copyright (c) 2008, 2019 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -48,6 +48,7 @@
import org.eclipse.mat.query.IResultPie;
import org.eclipse.mat.report.IOutputter;
import org.eclipse.mat.report.Renderer;
+import org.eclipse.mat.util.HTMLUtils;
@Renderer(target = "html", result = IResultPie.class)
public class HtmlPieChartRenderer implements IOutputter
@@ -141,10 +142,10 @@
{
StringBuilder message = new StringBuilder();
message.append(Messages.HtmlPieChartRenderer_ErrorRenderingChart);
- message.append(e.getClass().getName());
+ message.append(HTMLUtils.escapeText(e.getClass().getName()));
if (e.getMessage() != null)
- message.append(": ").append(e.getMessage()); //$NON-NLS-1$
+ message.append(": ").append(HTMLUtils.escapeText(e.getMessage())); //$NON-NLS-1$
String msg = message.toString();
diff --git a/plugins/org.eclipse.mat.jruby.resolver/src/org/eclipse/mat/jruby/resolver/JRubyScriptResolver.java b/plugins/org.eclipse.mat.jruby.resolver/src/org/eclipse/mat/jruby/resolver/JRubyScriptResolver.java
index 4ee3f71..c926f1b 100644
--- a/plugins/org.eclipse.mat.jruby.resolver/src/org/eclipse/mat/jruby/resolver/JRubyScriptResolver.java
+++ b/plugins/org.eclipse.mat.jruby.resolver/src/org/eclipse/mat/jruby/resolver/JRubyScriptResolver.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2010 SAP AG.
+ * Copyright (c) 2010, 2019 SAP AG and IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -7,6 +7,7 @@
*
* Contributors:
* Dimitar Giormov - initial API and implementation
+ * Andrew Johnson (IBM Corporation) - escapes
*******************************************************************************/
package org.eclipse.mat.jruby.resolver;
@@ -25,6 +26,7 @@
import org.eclipse.mat.snapshot.model.IClass;
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.model.NamedReference;
+import org.eclipse.mat.util.HTMLUtils;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.osgi.util.NLS;
@@ -79,22 +81,22 @@
}
if (classSpecificName.length() > 0){
String fileName = new File(classSpecificName).getName();
- String summary = NLS.bind(Messages.JRubyScriptResolver_Summary, fileName);
+ String summary = NLS.bind(Messages.JRubyScriptResolver_Summary, HTMLUtils.escapeText(fileName));
String rubyCallMessage = (shortJavaName == null)
- ? NLS.bind(Messages.JRubyScriptResolver_ResultBody_RubyCall_Class, fileName)
- : NLS.bind(Messages.JRubyScriptResolver_ResultBody_RubyCall_Method, fileName, realClassName);
+ ? NLS.bind(Messages.JRubyScriptResolver_ResultBody_RubyCall_Class, HTMLUtils.escapeText(fileName))
+ : NLS.bind(Messages.JRubyScriptResolver_ResultBody_RubyCall_Method, HTMLUtils.escapeText(fileName), HTMLUtils.escapeText(realClassName));
String possibleBundleSuspectsMessage = ""; //$NON-NLS-1$
if (isOsgiBased && possibleBundleSuspects.size() > 0){
thread.addKeyword("osgi"); //$NON-NLS-1$
StringBuilder suspects = new StringBuilder();
for (String possibleBundleSuspect : possibleBundleSuspects) {
- suspects.append(possibleBundleSuspect).append(' ');
+ suspects.append(HTMLUtils.escapeText(possibleBundleSuspect)).append(' ');
}
possibleBundleSuspectsMessage = NLS.bind(Messages.JRubyScriptResolver_ResultBody_PossibleSuspects, suspects);
}
- String rubyScriptPathMessage = NLS.bind(Messages.JRubyScriptResolver_ResultBody_RubyScriptPath, classSpecificName);
+ String rubyScriptPathMessage = NLS.bind(Messages.JRubyScriptResolver_ResultBody_RubyScriptPath, HTMLUtils.escapeText(classSpecificName));
String resultBody = NLS.bind(Messages.JRubyScriptResolver_ResultBody, new Object[] { rubyCallMessage, possibleBundleSuspectsMessage, rubyScriptPathMessage });
result.addResult(Messages.JRubyScriptResolver_ResultHeader, new TextResult(resultBody, true));
diff --git a/plugins/org.eclipse.mat.jruby.resolver/src/org/eclipse/mat/jruby/resolver/RubyStacktraceDumper.java b/plugins/org.eclipse.mat.jruby.resolver/src/org/eclipse/mat/jruby/resolver/RubyStacktraceDumper.java
index 4a1a789..d92ec1d 100644
--- a/plugins/org.eclipse.mat.jruby.resolver/src/org/eclipse/mat/jruby/resolver/RubyStacktraceDumper.java
+++ b/plugins/org.eclipse.mat.jruby.resolver/src/org/eclipse/mat/jruby/resolver/RubyStacktraceDumper.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2010, 2012 SAP AG and IBM Corporation.
+ * Copyright (c) 2010, 2019 SAP AG and IBM Corporation.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -29,6 +29,7 @@
import org.eclipse.mat.snapshot.model.IObject;
import org.eclipse.mat.snapshot.model.IObjectArray;
import org.eclipse.mat.snapshot.model.NamedReference;
+import org.eclipse.mat.util.HTMLUtils;
import org.eclipse.mat.util.IProgressListener;
import org.eclipse.osgi.util.NLS;
@@ -63,7 +64,7 @@
StringBuilder stackTrace = new StringBuilder();
for (PrintableStackFrame element : stackTraceFrames){
- stackTrace.append(NLS.bind(Messages.RubyStacktraceDumper_StackTraceLine, element));
+ stackTrace.append(NLS.bind(Messages.RubyStacktraceDumper_StackTraceLine, HTMLUtils.escapeText(element.toString())));
}
String summary = NLS.bind(Messages.RubyStacktraceDumper_Summary, javaThreadName);
diff --git a/plugins/org.eclipse.mat.report/src/org/eclipse/mat/report/internal/HtmlOutputter.java b/plugins/org.eclipse.mat.report/src/org/eclipse/mat/report/internal/HtmlOutputter.java
index 3b4bdb4..6161298 100644
--- a/plugins/org.eclipse.mat.report/src/org/eclipse/mat/report/internal/HtmlOutputter.java
+++ b/plugins/org.eclipse.mat.report/src/org/eclipse/mat/report/internal/HtmlOutputter.java
@@ -96,6 +96,7 @@
// //////////////////////////////////////////////////////////////
/**
* Extract alternate text for image URL.
+ * Use result within double quotes for attributes.
* @param url
* @return the text or an empty string
*/
@@ -103,7 +104,7 @@
private String altText(URL url)
{
String alt = "";
- return HTMLUtils.escapeText(alt);
+ return HTMLUtils.escapeText(alt).replace("\"",""");
}
@SuppressWarnings("nls")
@@ -176,12 +177,12 @@
if (filter[i].isActive())
artefact.append("<td>").append(HTMLUtils.escapeText(filter[i].getCriteria())).append("</td>");
else
- artefact.append("<td/>");
+ artefact.append("<td></td>");
}
}
if (hasDetailsLink)
- artefact.append("<td/>");
+ artefact.append("<td></td>");
artefact.append("</tr>");
}
@@ -249,7 +250,7 @@
}
if (hasDetailsLink)
- artefact.append("<td/>");
+ artefact.append("<td></td>");
artefact.append("</tr>");
}
@@ -657,11 +658,10 @@
}
else
{
- writer.append("<p>");//$NON-NLS-1$
+ // <pre> is a block level tag so cannot be surrounded by <p>
writer.append("<pre>"); //$NON-NLS-1$
writer.append(HTMLUtils.escapeText(textResult.getText()));
writer.append("</pre>"); //$NON-NLS-1$
- writer.append("</p>");//$NON-NLS-1$
}
}
diff --git a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/GeneralSnapshotTests.java b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/GeneralSnapshotTests.java
index 396f88f..0947332 100644
--- a/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/GeneralSnapshotTests.java
+++ b/plugins/org.eclipse.mat.tests/src/org/eclipse/mat/tests/snapshot/GeneralSnapshotTests.java
@@ -11,15 +11,16 @@
*******************************************************************************/
package org.eclipse.mat.tests.snapshot;
+import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.either;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
-import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.collection.IsEmptyCollection.emptyCollectionOf;
import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
@@ -30,17 +31,26 @@
import static org.junit.Assume.assumeTrue;
import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
+import java.io.InputStreamReader;
import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Stack;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.mat.SnapshotException;
import org.eclipse.mat.collect.SetInt;
import org.eclipse.mat.query.IResult;
+import org.eclipse.mat.query.results.DisplayFileResult;
import org.eclipse.mat.snapshot.ISnapshot;
import org.eclipse.mat.snapshot.SnapshotFactory;
import org.eclipse.mat.snapshot.SnapshotInfo;
@@ -324,7 +334,7 @@
else if (hasMethods == Methods.FRAMES_ONLY)
{
assertEquals(1, methods);
- assertTrue(methodsWithObjects > 0);
+ assertThat(methodsWithObjects, greaterThan(0));
}
else
{
@@ -336,47 +346,257 @@
@Test
public void testClassLoaders() throws SnapshotException
{
- assertTrue(snapshot.getSnapshotInfo().getNumberOfClassLoaders() > 1);
+ assertThat(snapshot.getSnapshotInfo().getNumberOfClassLoaders(), greaterThan(1));
}
@Test
- public void testRegressionReport() throws SnapshotException
+ public void testRegressionReport() throws SnapshotException, IOException
{
SnapshotQuery query = SnapshotQuery.parse("default_report org.eclipse.mat.tests:regression", snapshot);
IResult t = query.execute(new VoidProgressListener());
assertNotNull(t);
+ checkHTMLResult(t);
}
@Test
- public void testPerformanceReport() throws SnapshotException
+ public void testPerformanceReport() throws SnapshotException, IOException
{
SnapshotQuery query = SnapshotQuery.parse("default_report org.eclipse.mat.tests:performance", snapshot);
IResult t = query.execute(new VoidProgressListener());
assertNotNull(t);
+ checkHTMLResult(t);
}
@Test
- public void testLeakSuspectsReport() throws SnapshotException
+ public void testLeakSuspectsReport() throws SnapshotException, IOException
{
SnapshotQuery query = SnapshotQuery.parse("default_report org.eclipse.mat.api:suspects", snapshot);
IResult t = query.execute(new VoidProgressListener());
assertNotNull(t);
+ checkHTMLResult(t);
}
@Test
- public void testOverviewReport() throws SnapshotException
+ public void testOverviewReport() throws SnapshotException, IOException
{
SnapshotQuery query = SnapshotQuery.parse("default_report org.eclipse.mat.api:overview", snapshot);
IResult t = query.execute(new VoidProgressListener());
assertNotNull(t);
+ checkHTMLResult(t);
}
@Test
- public void testTopComponentsReport() throws SnapshotException
+ public void testTopComponentsReport() throws SnapshotException, IOException
{
SnapshotQuery query = SnapshotQuery.parse("default_report org.eclipse.mat.api:top_components", snapshot);
IResult t = query.execute(new VoidProgressListener());
assertNotNull(t);
+ checkHTMLResult(t);
+ }
+
+ public void checkHTMLResult(IResult r) throws IOException
+ {
+ assertThat(r, instanceOf(DisplayFileResult.class));
+ if (r instanceof DisplayFileResult)
+ {
+ DisplayFileResult d = (DisplayFileResult)r;
+ File f = d.getFile();
+ checkHTMLFile(f);
+ }
+ }
+
+ /**
+ * Recursively check an HTML file.
+ * @param f
+ * @throws IOException
+ */
+ public void checkHTMLFile(File f) throws IOException
+ {
+ Set<File>seen = new HashSet<File>();
+ checkHTMLFile(f, seen);
+ }
+
+ /**
+ * Recursively check an HTML file, avoiding going
+ * into files already seen.
+ * @param f
+ * @param seen Files already seen
+ * @throws IOException
+ */
+ public void checkHTMLFile(File f, Set<File>seen) throws IOException
+ {
+ // canonical needed to avoid problems with ..
+ if (!seen.add(f.getCanonicalFile()))
+ return;
+ FileInputStream fis = new FileInputStream(f);
+ try
+ {
+ if (!f.getName().endsWith(".html"))
+ {
+ // Not HTML
+ return;
+ }
+ // Read the file into a string
+ InputStreamReader ir = new InputStreamReader(fis, StandardCharsets.UTF_8);
+ char cbuf[] = new char[(int)f.length()];
+ int l = ir.read(cbuf);
+ String s = new String(cbuf, 0, l);
+
+ /*
+ * All these checks are approximate and would be confused
+ * by false tags in attribute value string etc.
+ */
+
+ // Some basic checks
+ assertThat("Expected charset", s, containsString("content=\"text/html;charset=UTF-8\""));
+ assertThat("Possible double escaping <", s, not(containsString("&lt;")));
+ assertThat("Possible double escaping &", s, not(containsString("&amp;")));
+
+ /*
+ * Rough test for bad tag - might indicate unescaped '<'.
+ * Find a less-than sign
+ * Negative lookahead for:
+ * optional / or !
+ * series of letters
+ * then optional digits
+ * ending with a space or greater-than
+ * or !DOCTYPE
+ * then match all until next space or greater-than
+ * We normally have lower case tags and no self-closed tags.
+ */
+ Pattern p = Pattern.compile("<(?!(/?[a-z]+[0-9]*)[ >]|!DOCTYPE )[^ >]*");
+ Matcher m = p.matcher(s);
+ String v;
+ if (m.find())
+ {
+ v = m.group(0);
+ }
+ else
+ {
+ v = null;
+ }
+ assertThat("Bad tag in "+f, v, equalTo(null));
+
+ /*
+ * Rough test for bad entity or unescaped ampersand.
+ * Negative lookahead for
+ * entity name followed by semicolon
+ * entity number preceded by # followed by semicolon
+ */
+ p = Pattern.compile("&(?!([a-z]+;)|(#[0-9]+;))[^a-z#]+");
+ m = p.matcher(s);
+ if (m.find())
+ {
+ v = m.group(0);
+ }
+ else
+ {
+ v = null;
+ }
+ assertThat("Bad entity in "+f, v, equalTo(null));
+
+ /*
+ * Check for alt text for images.
+ */
+ p = Pattern.compile("<img (?![^>]*alt)[^>]*>");
+ m = p.matcher(s);
+ if (m.find())
+ {
+ v = m.group(0);
+ }
+ else
+ {
+ v = null;
+ }
+ assertThat("No alt for img in "+f, v, equalTo(null));
+
+ /*
+ * Rough check for nesting of tags.
+ */
+ Stack<String> stk = new Stack<String>();
+ Stack<Integer> stki = new Stack<Integer>();
+ // Matches tags
+ p = Pattern.compile("</?[a-z]+");
+ m = p.matcher(s);
+ while (m.find())
+ {
+ String tag = m.group().substring(1);
+ if (tag.startsWith("/"))
+ {
+ // Closing tag
+ assertThat("Stack for "+tag, stk.size(), greaterThan(0));
+ tag = tag.substring(1);
+ String tag2 = stk.pop();
+ int si = stki.pop();
+ if (tag2.equals("p") && !tag.equals("a") && !tag.equals("p"))
+ {
+ // <p> closed by any outer tag except <a>
+ tag2 = stk.pop();
+ si = stki.pop();
+ assertThat("Stack for "+tag, stk.size(), greaterThan(0));
+ }
+ String range = s.substring(si, m.end());
+ assertThat("Tag closing at " + m.start()+" "+range+" "+f, tag, equalTo(tag2));
+ }
+ else
+ {
+ // Self closing tag?
+ if (!(tag.equals("br")
+ || tag.equals("hr")
+ || tag.equals("img")
+ || tag.equals("link")
+ || tag.equals("input")
+ || tag.equals("meta")
+ || tag.equals("area")))
+ {
+ // <p> is closed by following block tag
+ if (stk.size() >= 1 && stk.peek().equals("p") && (
+ tag.equals("h1") ||
+ tag.equals("h2") ||
+ tag.equals("h3") ||
+ tag.equals("h4") ||
+ tag.equals("h5") ||
+ tag.equals("h6") ||
+ tag.equals("pre") ||
+ tag.equals("ol") ||
+ tag.equals("ul") ||
+ tag.equals("div")))
+ {
+ // Close the <p> tag
+ stk.pop();
+ stki.pop();
+ }
+ stk.push(tag);
+ stki.push(m.start());
+ }
+ }
+ }
+ assertThat("Stack should be empty", stk.size(), equalTo(0));
+
+ // Look for references to other files
+ for (int i = 0; i >= 0; )
+ {
+ String match = "href=\"";
+ i = s.indexOf(match, i);
+ if (i >= 0)
+ {
+ int j = s.indexOf("\"", i + match.length());
+ String fn = s.substring(i + match.length(), j);
+ if (!fn.startsWith("/") && !fn.contains("#") && !fn.startsWith("http") && !fn.startsWith("mat:"))
+ {
+ File d = f.getParentFile();
+ File newf = new File(d, fn);
+ checkHTMLFile(newf, seen);
+ }
+ i = j;
+ }
+ }
+ ir.close();
+ }
+ finally
+ {
+ fis.close();
+ }
}
@Test
@@ -779,7 +999,7 @@
ISnapshot sn3 = SnapshotFactory.openSnapshot(newPath, new VoidProgressListener());
try
{
- assertTrue(sn3.getHeapSize(0) >= 0);
+ assertThat(sn3.getHeapSize(0), greaterThanOrEqualTo(0L));
// Do a complex operation which requires the dump still
// to be open and alive
assertNotNull(sn3.getObject(sn2.getClassOf(0).getClassLoaderId()).getOutboundReferences());
@@ -788,7 +1008,7 @@
{
SnapshotFactory.dispose(sn3);
}
- assertTrue(sn2.getHeapSize(0) >= 0);
+ assertThat(sn2.getHeapSize(0), greaterThanOrEqualTo(0L));
// Do a complex operation which requires the dump still to be open and alive.
assertNotNull(sn2.getObject(sn2.getClassOf(0).getClassLoaderId()).getOutboundReferences());
}