Skip to content

Commit

Permalink
Prevent endless recursion, limit traversal depth.
Browse files Browse the repository at this point in the history
  • Loading branch information
sdedic committed Nov 14, 2023
1 parent 54b5d2a commit eb760ff
Show file tree
Hide file tree
Showing 20 changed files with 420 additions and 49 deletions.
13 changes: 13 additions & 0 deletions extide/gradle/apichanges.xml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,19 @@ is the proper place.
<!-- ACTUAL CHANGES BEGIN HERE: -->

<changes>
<change id="gradle-report-severity">
<api name="general"/>
<summary>Gradle project problems have severity and stacktraces</summary>
<version major="2" minor="38"/>
<date day="13" month="11" year="2023"/>
<author login="sdedic"/>
<compatibility semantic="compatible"/>
<description>
<p>
Reports from the NB tooling gradle plugin are now annotated by severity.
</p>
</description>
</change>
<change id="gradle-setting-deprecation">
<api name="general"/>
<summary>Some Gradle Settings Removed from Use</summary>
Expand Down
2 changes: 1 addition & 1 deletion extide/gradle/manifest.mf
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ AutoUpdate-Show-In-Client: true
OpenIDE-Module: org.netbeans.modules.gradle/2
OpenIDE-Module-Layer: org/netbeans/modules/gradle/layer.xml
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/gradle/Bundle.properties
OpenIDE-Module-Specification-Version: 2.37
OpenIDE-Module-Specification-Version: 2.38
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
import org.gradle.plugin.use.PluginId;
import org.gradle.util.GradleVersion;
import org.netbeans.modules.gradle.tooling.internal.NbProjectInfo;
import org.netbeans.modules.gradle.tooling.internal.NbProjectInfo.Report;

/**
*
Expand All @@ -125,6 +126,18 @@ class NbProjectInfoBuilder {
* project loader is enabled to FINER level.
*/
private static final Logger LOG = Logging.getLogger(NbProjectInfoBuilder.class);

/**
* Maximum recursion depth into structures, arrays and maps. If this depth is reached, the
* builder will record a warning and stop descending.
*/
private static final int MAX_INTROSPECTION_DEPTH = 25;

/**
* If a warning is recorded during introspection, further warnings will
* be suppressed until the introspector goes up to this level.
*/
private static final int INTROSPECTION_RESET_DEPTH_WARNING = 5;

/**
* Name of the default extensibility point for various domain objects defined by Gradle.
Expand Down Expand Up @@ -305,7 +318,7 @@ private void detectTaskProperties(NbProjectInfoModel model) {
Class nonDecorated = findNonDecoratedClass(taskClass);

taskPropertyTypes.put(task.getName(), nonDecorated.getName());
inspectObjectAndValues(taskClass, task, task.getName() + ".", globalTypes, taskPropertyTypes, taskProperties, EXCLUDE_TASK_PROPERTIES, true); // NOI18N
startInspectObjectAndValues(taskClass, task, task.getName() + ".", globalTypes, taskPropertyTypes, taskProperties, EXCLUDE_TASK_PROPERTIES, true); // NOI18N
}

model.getInfo().put("tasks.propertyValues", taskProperties); // NOI18N
Expand Down Expand Up @@ -539,19 +552,53 @@ private static boolean isPrimitiveOrString(Class c) {
* @param propertyTypes
* @param defaultValues
*/
private void startInspectObjectAndValues(Class clazz, Object object, String prefix, Map<String, Map<String, String>> globalTypes, Map<String, String> propertyTypes, Map<String, Object> defaultValues) {
depth = 0;
inspectObjectAndValues(clazz, object, prefix, globalTypes, propertyTypes, defaultValues, null, true);
}

private void startInspectObjectAndValues(Class clazz, Object object, String prefix, Map<String, Map<String, String>> globalTypes, Map<String, String> propertyTypes, Map<String, Object> defaultValues, Set<String> excludes, boolean type) {
depth = 0;
inspectObjectAndValues(clazz, object, prefix, globalTypes, propertyTypes, defaultValues, excludes, type);
}

private void inspectObjectAndValues(Class clazz, Object object, String prefix, Map<String, Map<String, String>> globalTypes, Map<String, String> propertyTypes, Map<String, Object> defaultValues) {
inspectObjectAndValues(clazz, object, prefix, globalTypes, propertyTypes, defaultValues, null, true);
}
/**
* The current introspection depth
*/
private int depth;

/**
* If true, depth exceeded warnings will not be logged.
*/
private boolean suppressDepthWarning;

private void inspectObjectAndValues(Class clazz, Object object, String prefix, Map<String, Map<String, String>> globalTypes, Map<String, String> propertyTypes, Map<String, Object> defaultValues, Set<String> excludes, boolean type) {
if (valueIdentities.put(object, Boolean.TRUE) == Boolean.TRUE) {
return;
}
try {
if (depth++ >= MAX_INTROSPECTION_DEPTH) {
if (!suppressDepthWarning) {
LOG.warn("Too deep structure, truncating");
model.noteProblem(Report.Severity.WARNING,
String.format("Object structure too deep encountered in class %s", clazz),
String.format("Object structure is too deep for the project model builder. This is unlikely to affect basic project operations. "
+ "Check logs and report this issue. The path for the object: %s, current value: %s", prefix, object));
suppressDepthWarning = true;
}
return;
} else if (depth <= INTROSPECTION_RESET_DEPTH_WARNING) {
suppressDepthWarning = false;
}
inspectObjectAndValues0(clazz, object, prefix, globalTypes, propertyTypes, defaultValues, excludes, type);
} catch (RuntimeException ex) {
LOG.warn("Error during inspection of {}, value {}, prefix {}", clazz, object, prefix);
model.noteProblem(ex, true);
} finally {
depth--;
valueIdentities.remove(object);
}
}
Expand Down Expand Up @@ -753,7 +800,7 @@ private void dumpContainerProperties(Map<String, ?> m, String prefix, Map<String
if (v == null) {
defaultValues.put(prefix + "." + k, null); // NOI18N
} else {
defaultValues.put(prefix + "." + k, Objects.toString(v)); // NOI18N
defaultValues.put(prefix + "." + k, objectToString(v)); // NOI18N
inspectObjectAndValues(v.getClass(), v, newPrefix, globalTypes, propertyTypes, defaultValues, null, false);
}
}
Expand Down Expand Up @@ -789,8 +836,8 @@ private boolean dumpValue(Object value, String prefix, Map<String, String> prope
Object v = m.get(k);
if (v == null) {
defaultValues.put(prefix + "." + k, null); // NOI18N
} else {
defaultValues.put(prefix + "." + k, Objects.toString(v)); // NOI18N
} else if (!v.equals(value)) {
defaultValues.put(prefix + "." + k, objectToString(v)); // NOI18N
inspectObjectAndValues(v.getClass(), v, newPrefix, globalTypes, propertyTypes, defaultValues, null, false);
}
}
Expand Down Expand Up @@ -821,7 +868,7 @@ private boolean dumpValue(Object value, String prefix, Map<String, String> prope
String newPrefix = prefix + "[" + index + "]."; // NOI18N
if (o == null) {
defaultValues.put(prefix + "[" + index + "]", null); //NOI18N
} else {
} else if (!o.equals(value)) {
defaultValues.put(prefix + "[" + index + "]", Objects.toString(o)); //NOI18N
inspectObjectAndValues(o.getClass(), o, newPrefix, globalTypes, propertyTypes, defaultValues, null, false);
}
Expand Down Expand Up @@ -869,8 +916,8 @@ private boolean dumpValue(Object value, String prefix, Map<String, String> prope
Object v = mvalue.get(o);
if (v == null) {
defaultValues.put(newPrefix, null); // NOI18N
} else {
defaultValues.put(newPrefix, Objects.toString(v)); // NOI18N
} else if (!v.equals(value)) {
defaultValues.put(newPrefix, objectToString(v)); // NOI18N
inspectObjectAndValues(v.getClass(), v, newPrefix + ".", globalTypes, propertyTypes, defaultValues, null, itemClass == null);
}
}
Expand All @@ -880,6 +927,17 @@ private boolean dumpValue(Object value, String prefix, Map<String, String> prope
return dumped;
}

private static String objectToString(Object o) {
if (o instanceof Map || o instanceof Iterable) {
return o.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(o));
}
try {
return Objects.toString(o);
} catch (RuntimeException ex) {
return o.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(o));
}
}

private static Class findNonDecoratedClass(Class clazz) {
while (clazz != Object.class && (clazz.getModifiers() & 0x1000 /* Modifiers.SYNTHETIC */) > 0) {
clazz = clazz.getSuperclass();
Expand Down Expand Up @@ -930,7 +988,7 @@ private void inspectExtensions(String prefix, ExtensionContainer container) {

Class c = findNonDecoratedClass(ext.getClass());
propertyTypes.put(prefix + extName, c.getName());
inspectObjectAndValues(ext.getClass(), ext, prefix + extName + ".", globalTypes, propertyTypes, values);
startInspectObjectAndValues(ext.getClass(), ext, prefix + extName + ".", globalTypes, propertyTypes, values);
if (ext instanceof ExtensionAware) {
inspectExtensions(prefix + extName + ".", ((ExtensionAware)ext).getExtensions()); // NOI18N
}
Expand Down Expand Up @@ -986,7 +1044,8 @@ private void detectProjectMetadata(NbProjectInfoModel model) {
try {
model.getInfo().put("buildClassPath", storeSet(project.getBuildscript().getConfigurations().getByName("classpath").getFiles()));
} catch (RuntimeException e) {
model.noteProblem(e);
// unexpected exception, build classpath should be available.
model.noteProblem(e, true);
}
Set<String[]> tasks = new HashSet<>();
for (org.gradle.api.Task t : project.getTasks()) {
Expand Down Expand Up @@ -1206,15 +1265,15 @@ private void detectSources(NbProjectInfoModel model) {
} catch(Exception e) {
convertOfflineException(e);
// will not be reached
model.noteProblem(e);
model.noteProblem(e, false);
}
sinceGradle("4.6", () -> {
try {
model.getInfo().put(propBase + "classpath_annotation", storeSet(getProperty(sourceSet, "annotationProcessorPath", "files")));
} catch(Exception e) {
convertOfflineException(e);
// will not be reached
model.noteProblem(e);
model.noteProblem(e, false);
}
model.getInfo().put(propBase + "configuration_annotation", getProperty(sourceSet, "annotationProcessorConfigurationName"));
});
Expand All @@ -1230,6 +1289,7 @@ private void detectSources(NbProjectInfoModel model) {
}
} else {
model.getInfo().put("sourcesets", Collections.emptySet());
// TODO: should be this converted to Problem, severity INFO ?
model.noteProblem("No sourceSets found on this project. This project mightbe a Model/Rule based one which is not supported at the moment.");
}
}
Expand All @@ -1246,12 +1306,14 @@ private void detectArtifacts(NbProjectInfoModel model) {
try {
model.getInfo().put("exploded_war_dir", getProperty(project, "explodedWar", "destinationDir"));
} catch(Exception e) {
model.noteProblem(e);
// probably not an internal error, but misconfiguration, omit trace
model.noteProblem(e, false);
}
try {
model.getInfo().put("web_classpath", getProperty(project, "war", "classpath", "files"));
} catch(Exception e) {
model.noteProblem(e);
// probably not an internal error, but misconfiguration, omit trace
model.noteProblem(e, false);
}
}
Map<String, Object> archives = new HashMap<>();
Expand Down Expand Up @@ -1540,7 +1602,7 @@ private void detectDependencies(NbProjectInfoModel model) {

walker.walkResolutionResult(it.getIncoming().getResolutionResult().getRoot());
} catch (ResolveException ex) {
model.noteProblem(ex);
model.noteProblem(ex, false);
}
} else {
unresolvedIds.addAll(componentIds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,15 @@
package org.netbeans.modules.gradle.tooling;

import java.io.Serializable;
import java.util.Arrays;
import org.netbeans.modules.gradle.tooling.internal.NbProjectInfo;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.gradle.internal.exceptions.LocationAwareException;
import org.gradle.internal.exceptions.MultiCauseException;
import org.gradle.internal.resolve.ModuleVersionResolveException;
Expand Down Expand Up @@ -76,55 +79,65 @@ void noteProblem(String s) {
* yet, but contains exception class as a data for possible matching.
* @param e reported issue as a Throwable.
*/
void noteProblem(Throwable e) {
void noteProblem(Throwable e, boolean unexpected) {
if (e instanceof MultiCauseException && !(e instanceof ModuleVersionResolveException)) {
// only handle wrapper multi-causes. They may appear in the middle of the cause chain, too, but
// it's not yet obvious if the multi-cause errors are actually useful.
MultiCauseException mce = (MultiCauseException)e;
for (Throwable t : mce.getCauses()) {
Report r = createReport(t, false);
Report r = createReport(t, false, unexpected);
if (r != null) {
ReportImpl outer = createReport(e, true);
ReportImpl outer = createReport(e, true, unexpected);
outer.addCause(r);
reports.add(outer);
}
}
} else {
Report r = createReport(e, false);
Report r = createReport(e, false, unexpected);
if (r != null) {
reports.add(r);
}
}
}

void noteProblem(Report.Severity severity, String message, String detail) {
if (message == null) {
return;
}
Report r = new ReportImpl(severity, message, detail);
reports.add(r);
}

/**
* Replicates the Throwable into a Report. If shallow is false, replicates
* the whole cause chain into a chain of Reports.
* @param e throwable to encode
* @param shallow if true, will encode just the throwable, not its chain
* @return created Report
*/
private ReportImpl createReport(Throwable e, boolean shallow) {
private ReportImpl createReport(Throwable e, boolean shallow, boolean exc) {
if (e == null) {
return null;
}

ReportImpl report;
Throwable reported = e;

StackTraceElement[] els = e.getStackTrace();
String detail = Arrays.asList(els).stream().map(Objects::toString).collect(Collectors.joining("\n"));
Report.Severity s = exc ? Report.Severity.EXCEPTION : Report.Severity.ERROR;
if (e instanceof LocationAwareException) {
LocationAwareException lae = (LocationAwareException)e;
reported = lae.getCause();
report = new ReportImpl(reported.getClass().getName(), lae.getLocation(), lae.getLineNumber(), reported.getMessage());
report = new ReportImpl(s, reported.getClass().getName(), lae.getLocation(), lae.getLineNumber(), reported.getMessage(), exc ? detail : null);
} else {
report = new ReportImpl(reported.getClass().getName(), null, -1, e.getMessage());
report = new ReportImpl(s, reported.getClass().getName(), null, -1, e.getMessage(), exc ? detail : null);
reported = e;
}
if (shallow) {
return report;
}
if (e.getCause() != null && e.getCause() != reported) {
Report nested = createReport(e.getCause(), false);
Report nested = createReport(e.getCause(), false, exc);
if (nested != null) {
report.addCause(nested);
}
Expand All @@ -150,13 +163,26 @@ static class ReportImpl implements Serializable, Report {
private final String scriptLocation;
private final int lineNumber;
private final String message;
private final String detail;
private final Severity severity;
private Report cause;

public ReportImpl(Severity severity, String message, String detail) {
this.severity = severity;
this.detail = detail;
this.errorClass = null;
this.scriptLocation = null;
this.lineNumber = -1;
this.message = message;
}

public ReportImpl(String errorClass, String scriptLocation, int lineNumber, String message) {
public ReportImpl(Severity severity, String errorClass, String scriptLocation, int lineNumber, String message, String detail) {
this.severity = Severity.ERROR;
this.errorClass = errorClass;
this.scriptLocation = scriptLocation;
this.lineNumber = lineNumber;
this.message = message;
this.detail = detail;
}

public String getErrorClass() {
Expand All @@ -182,5 +208,13 @@ public void addCause(Report cause) {
public Report getCause() {
return cause;
}

public Severity getSeverity() {
return severity;
}

public String getDetail() {
return detail;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,8 @@ public Object buildAll(String modelName, Project prj) {
Throwable cause = ex;
while ((cause != null) && (cause.getCause() != cause)) {
if (cause instanceof GradleException) {
ret.noteProblem((GradleException) cause);
// unexpected exceptions at this level
ret.noteProblem((GradleException) cause, true);
break;
}
cause = cause.getCause();
Expand Down
Loading

0 comments on commit eb760ff

Please sign in to comment.