diff --git a/dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java b/dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java index 2078f09ecec..0114503447c 100644 --- a/dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java +++ b/dd-java-agent/src/main/java/datadog/trace/bootstrap/AgentBootstrap.java @@ -216,41 +216,74 @@ private static boolean alreadyInitialized() { return false; } + /** + * Returns {@code true} if the JVM is running a JDK diagnostic/development tool rather than a user + * application, in which case the agent should abort early. + * + *
How to discover new entries when a tool is missed: + * + *
JAVA_TOOL_OPTIONS="-javaagent:/path/to/agent.jar" $JAVA_HOME/bin/<tool>+ * The value of {@code jdk.module.main} is the module name to add to the first switch. + *
Native binaries (e.g. {@code jitserver}, {@code asprof}) report both properties as {@code + * null} and are automatically ignored — no switch entry needed for them. + */ static boolean isJdkTool() { String moduleMain = SystemProperties.get("jdk.module.main"); - if (null != moduleMain && !moduleMain.isEmpty() && moduleMain.charAt(0) == 'j') { - switch (moduleMain) { - case "java.base": // keytool - case "java.corba": - case "java.desktop": - case "java.rmi": - case "java.scripting": - case "java.security.jgss": - case "jdk.aot": - case "jdk.compiler": - case "jdk.dev": - case "jdk.hotspot.agent": - case "jdk.httpserver": - case "jdk.jartool": - case "jdk.javadoc": - case "jdk.jcmd": - case "jdk.jconsole": - case "jdk.jdeps": - case "jdk.jdi": - case "jdk.jfr": - case "jdk.jlink": - case "jdk.jpackage": - case "jdk.jshell": - case "jdk.jstatd": - case "jdk.jvmstat.rmi": - case "jdk.pack": - case "jdk.pack200": - case "jdk.policytool": - case "jdk.rmic": - case "jdk.scripting.nashorn.shell": - case "jdk.xml.bind": - case "jdk.xml.ws": - return true; + if (null != moduleMain && !moduleMain.isEmpty()) { + char firstChar = moduleMain.charAt(0); + if (firstChar == 'j') { + // Standard JDK 9+ module-based tools (module names start with 'java.' or 'jdk.') + switch (moduleMain) { + case "java.base": // keytool + case "java.corba": + case "java.desktop": + case "java.rmi": + case "java.scripting": + case "java.security.jgss": + case "jdk.aot": + case "jdk.compiler": + case "jdk.dev": + case "jdk.hotspot.agent": + case "jdk.httpserver": + case "jdk.jartool": + case "jdk.javadoc": + case "jdk.jcmd": + case "jdk.jconsole": + case "jdk.jdeps": + case "jdk.jdi": + case "jdk.jfr": + case "jdk.jlink": + case "jdk.jpackage": + case "jdk.jshell": + case "jdk.jstatd": + case "jdk.jvmstat.rmi": + case "jdk.pack": + case "jdk.pack200": + case "jdk.policytool": + case "jdk.rmic": + case "jdk.scripting.nashorn.shell": + case "jdk.xml.bind": + case "jdk.xml.ws": + return true; + } + } else if (firstChar == 'o') { + // OpenJ9 / Semeru 11+ module-based tools (module names start with 'openj9.') + switch (moduleMain) { + case "openj9.dtfj": // jextract, jpackcore + case "openj9.dtfjview": // jdmpview + case "openj9.traceformat": // traceformat + return true; + } } } // Handles JDK 8 tools (IBM J9 and standard JDK 8 vendors) @@ -267,9 +300,18 @@ static boolean isJdkTool() { case "com.ibm.security.krb5.internal.tools.Klist": // klist case "com.ibm.security.krb5.internal.tools.Ktab": // ktab case "com.ibm.jvm.dtfjview.DTFJView": // jdmpview + case "com.ibm.jvm.j9.dump.extract.Main": // jextract + case "com.ibm.gsk.ikeyman.Ikeyman": // ikeyman case "com.ibm.gsk.ikeyman.ikeycmd": // ikeycmd case "com.ibm.CosNaming.TransientNameServer": // tnameserv case "com.ibm.idl.toJavaPortable.Compile": // idlj + // OpenJ9 / Semeru 8 specific tool main classes (OpenJ9 reimplementation of HotSpot tools) + case "openj9.tools.attach.diagnostics.tools.Jcmd": // jcmd + case "openj9.tools.attach.diagnostics.tools.Jps": // jps + case "openj9.tools.attach.diagnostics.tools.Jstat": // jstat + case "openj9.tools.attach.diagnostics.tools.Jmap": // jmap + case "openj9.tools.attach.diagnostics.tools.Jstack": // jstack + case "com.ibm.jvm.TraceFormat": // traceformat // Standard JDK 8 tool main classes (shared by IBM J9 and Oracle/OpenJDK 8) case "sun.tools.jar.Main": // jar case "com.sun.tools.javac.Main": // javac @@ -292,6 +334,7 @@ static boolean isJdkTool() { case "com.sun.tools.internal.xjc.Driver": // xjc case "com.sun.tools.internal.jxc.SchemaGenerator": // schemagen case "com.sun.tools.script.shell.Main": // jrunscript + case "jdk.nashorn.tools.Shell": // jjs (Nashorn JS shell, JDK 8) case "sun.tools.jconsole.JConsole": // jconsole case "sun.applet.Main": // appletviewer case "com.sun.corba.se.impl.naming.cosnaming.TransientNameServer": // tnameserv @@ -310,6 +353,8 @@ static boolean isJdkTool() { case "jdk.jfr.internal.tool.Main": // jfr, backported to OpenJDK 8 in 8u262 (JEP 328 // backport, July 2020) case "sun.jvm.hotspot.jdi.SADebugServer": // jsadebugd + case "sun.jvm.hotspot.HSDB": // hsdb (HotSpot SA GUI debugger, JDK 8) + case "sun.jvm.hotspot.CLHSDB": // clhsdb (HotSpot SA command-line debugger, JDK 8) return true; } } diff --git a/dd-java-agent/src/test/java/datadog/trace/bootstrap/AgentBootstrapAbortOnJdkToolTest.java b/dd-java-agent/src/test/java/datadog/trace/bootstrap/AgentBootstrapAbortOnJdkToolTest.java new file mode 100644 index 00000000000..baa92b46ecb --- /dev/null +++ b/dd-java-agent/src/test/java/datadog/trace/bootstrap/AgentBootstrapAbortOnJdkToolTest.java @@ -0,0 +1,149 @@ +package datadog.trace.bootstrap; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class AgentBootstrapAbortOnJdkToolTest { + + private String savedModuleMain; + private String savedJavaCommand; + + @BeforeEach + void saveAndClearProperties() { + savedModuleMain = System.clearProperty("jdk.module.main"); + savedJavaCommand = System.clearProperty("sun.java.command"); + } + + @AfterEach + void restoreProperties() { + restoreProperty("jdk.module.main", savedModuleMain); + restoreProperty("sun.java.command", savedJavaCommand); + } + + private static void restoreProperty(String key, String previousValue) { + if (previousValue == null) { + System.clearProperty(key); + } else { + System.setProperty(key, previousValue); + } + } + + @Test + void notAJdkToolWhenNoPropertiesSet() { + assertFalse(AgentBootstrap.isJdkTool()); + } + + @Test + void notAJdkToolWhenCommandIsNotAKnownTool() { + System.setProperty("sun.java.command", "com.example.MyApplication"); + assertFalse(AgentBootstrap.isJdkTool()); + } + + @Test + void notAJdkToolWhenModuleMainIsNotAKnownTool() { + System.setProperty("jdk.module.main", "com.example.myapp"); + assertFalse(AgentBootstrap.isJdkTool()); + } + + @ParameterizedTest + @ValueSource( + strings = { + // Standard JDK 9+ module-based tools + "java.base", // keytool + "jdk.compiler", // javac + "jdk.jartool", // jar + "jdk.javadoc", // javadoc + "jdk.jcmd", // jcmd + "jdk.jconsole", // jconsole + "jdk.jshell", // jshell + "jdk.jfr", // jfr (JDK 9+) + // OpenJ9 / Semeru 11+ module-based tools + "openj9.dtfj", // jextract, jpackcore + "openj9.dtfjview", // jdmpview + "openj9.traceformat", // traceformat + }) + void isJdkToolByModuleMain(String moduleMain) { + System.setProperty("jdk.module.main", moduleMain); + assertTrue(AgentBootstrap.isJdkTool()); + } + + @ParameterizedTest + @ValueSource( + strings = { + // IBM J9 JDK 8 specific tool main classes + "com.ibm.crypto.tools.KeyTool", // keytool + "com.ibm.security.krb5.internal.tools.Kinit", // kinit + "com.ibm.security.krb5.internal.tools.Klist", // klist + "com.ibm.security.krb5.internal.tools.Ktab", // ktab + "com.ibm.jvm.dtfjview.DTFJView", // jdmpview + "com.ibm.jvm.j9.dump.extract.Main", // jextract + "com.ibm.gsk.ikeyman.Ikeyman", // ikeyman + "com.ibm.gsk.ikeyman.ikeycmd", // ikeycmd + "com.ibm.CosNaming.TransientNameServer", // tnameserv + "com.ibm.idl.toJavaPortable.Compile", // idlj + // OpenJ9 / Semeru 8 specific tool main classes (OpenJ9 reimplementation of HotSpot tools) + "openj9.tools.attach.diagnostics.tools.Jcmd", // jcmd + "openj9.tools.attach.diagnostics.tools.Jps", // jps + "openj9.tools.attach.diagnostics.tools.Jstat", // jstat + "openj9.tools.attach.diagnostics.tools.Jmap", // jmap + "openj9.tools.attach.diagnostics.tools.Jstack", // jstack + "com.ibm.jvm.TraceFormat", // traceformat + // Standard JDK 8 tool main classes (Corretto 8 / OpenJDK 8) + "sun.tools.jar.Main", // jar + "com.sun.tools.javac.Main", // javac + "com.sun.tools.javadoc.Main", // javadoc + "com.sun.tools.javap.Main", // javap + "com.sun.tools.javah.Main", // javah + "sun.security.tools.keytool.Main", // keytool + "sun.security.tools.jarsigner.Main", // jarsigner + "sun.security.tools.policytool.PolicyTool", // policytool + "com.sun.tools.example.debug.tty.TTY", // jdb + "com.sun.tools.jdeps.Main", // jdeps + "sun.rmi.rmic.Main", // rmic + "sun.rmi.registry.RegistryImpl", // rmiregistry + "sun.rmi.server.Activation", // rmid + "com.sun.tools.extcheck.Main", // extcheck + "sun.tools.serialver.SerialVer", // serialver + "sun.tools.native2ascii.Main", // native2ascii + "com.sun.tools.internal.ws.WsGen", // wsgen + "com.sun.tools.internal.ws.WsImport", // wsimport + "com.sun.tools.internal.xjc.Driver", // xjc + "com.sun.tools.internal.jxc.SchemaGenerator", // schemagen + "com.sun.tools.script.shell.Main", // jrunscript + "sun.tools.jconsole.JConsole", // jconsole + "sun.applet.Main", // appletviewer + "com.sun.corba.se.impl.naming.cosnaming.TransientNameServer", // tnameserv + "com.sun.tools.corba.se.idl.toJavaPortable.Compile", // idlj + "com.sun.corba.se.impl.activation.ORBD", // orbd + "com.sun.corba.se.impl.activation.ServerTool", // servertool + "sun.tools.jps.Jps", // jps + "sun.tools.jstack.JStack", // jstack + "sun.tools.jmap.JMap", // jmap + "sun.tools.jinfo.JInfo", // jinfo + "com.sun.tools.hat.Main", // jhat + "sun.tools.jstat.Jstat", // jstat + "sun.tools.jstatd.Jstatd", // jstatd + "sun.tools.jcmd.JCmd", // jcmd + "jdk.jfr.internal.tool.Main", // jfr (OpenJDK 8u262+ backport) + "sun.jvm.hotspot.jdi.SADebugServer", // jsadebugd + "jdk.nashorn.tools.Shell", // jjs (Nashorn JS shell, JDK 8) + "sun.jvm.hotspot.HSDB", // hsdb (HotSpot SA GUI debugger, JDK 8) + "sun.jvm.hotspot.CLHSDB", // clhsdb (HotSpot SA command-line debugger, JDK 8) + }) + void isJdkToolByCommand(String mainClass) { + System.setProperty("sun.java.command", mainClass); + assertTrue(AgentBootstrap.isJdkTool()); + } + + @Test + void isJdkToolWhenCommandIncludesArguments() { + System.setProperty("sun.java.command", "com.ibm.crypto.tools.KeyTool -list -v"); + assertTrue(AgentBootstrap.isJdkTool()); + } +} diff --git a/dd-smoke-tests/jdk-tool-abort/build.gradle b/dd-smoke-tests/jdk-tool-abort/build.gradle new file mode 100644 index 00000000000..81d57f36d80 --- /dev/null +++ b/dd-smoke-tests/jdk-tool-abort/build.gradle @@ -0,0 +1,6 @@ +apply from: "$rootDir/gradle/java.gradle" +description = 'JDK Tool Abort Smoke Tests' + +dependencies { + testImplementation project(':dd-smoke-tests') +} diff --git a/dd-smoke-tests/jdk-tool-abort/gradle.lockfile b/dd-smoke-tests/jdk-tool-abort/gradle.lockfile new file mode 100644 index 00000000000..f3b2c0bd606 --- /dev/null +++ b/dd-smoke-tests/jdk-tool-abort/gradle.lockfile @@ -0,0 +1,112 @@ +# This is a Gradle generated file for dependency locking. +# Manual edits can break the build and are not advised. +# This file is expected to be part of source control. +cafe.cryptography:curve25519-elisabeth:0.1.0=testRuntimeClasspath +cafe.cryptography:ed25519-elisabeth:0.1.0=testRuntimeClasspath +ch.qos.logback:logback-classic:1.2.13=testCompileClasspath,testRuntimeClasspath +ch.qos.logback:logback-core:1.2.13=testCompileClasspath,testRuntimeClasspath +com.blogspot.mydailyjava:weak-lock-free:0.17=testCompileClasspath,testRuntimeClasspath +com.datadoghq.okhttp3:okhttp:3.12.15=testCompileClasspath,testRuntimeClasspath +com.datadoghq.okio:okio:1.17.6=testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-instrument-java:0.0.3=testCompileClasspath,testRuntimeClasspath +com.datadoghq:dd-javac-plugin-client:0.2.2=testCompileClasspath,testRuntimeClasspath +com.datadoghq:java-dogstatsd-client:4.4.3=testRuntimeClasspath +com.datadoghq:sketches-java:0.8.3=testRuntimeClasspath +com.github.javaparser:javaparser-core:3.25.6=codenarc +com.github.jnr:jffi:1.3.14=testRuntimeClasspath +com.github.jnr:jnr-a64asm:1.0.0=testRuntimeClasspath +com.github.jnr:jnr-constants:0.10.4=testRuntimeClasspath +com.github.jnr:jnr-enxio:0.32.19=testRuntimeClasspath +com.github.jnr:jnr-ffi:2.2.18=testRuntimeClasspath +com.github.jnr:jnr-posix:3.1.21=testRuntimeClasspath +com.github.jnr:jnr-unixsocket:0.38.24=testRuntimeClasspath +com.github.jnr:jnr-x86asm:1.0.2=testRuntimeClasspath +com.github.spotbugs:spotbugs-annotations:4.9.8=compileClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath +com.github.spotbugs:spotbugs:4.9.8=spotbugs +com.github.stephenc.jcip:jcip-annotations:1.0-1=spotbugs +com.google.code.findbugs:jsr305:3.0.2=compileClasspath,spotbugs,testCompileClasspath,testRuntimeClasspath +com.google.code.gson:gson:2.13.2=spotbugs +com.google.errorprone:error_prone_annotations:2.41.0=spotbugs +com.google.guava:guava:20.0=testCompileClasspath,testRuntimeClasspath +com.google.re2j:re2j:1.7=testRuntimeClasspath +com.squareup.moshi:moshi:1.11.0=testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:logging-interceptor:3.12.12=testCompileClasspath,testRuntimeClasspath +com.squareup.okhttp3:okhttp:3.12.12=testCompileClasspath,testRuntimeClasspath +com.squareup.okio:okio:1.17.5=testCompileClasspath,testRuntimeClasspath +com.thoughtworks.qdox:qdox:1.12.1=codenarc +commons-fileupload:commons-fileupload:1.5=testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.11.0=testCompileClasspath,testRuntimeClasspath +commons-io:commons-io:2.20.0=spotbugs +de.thetaphi:forbiddenapis:3.10=compileClasspath,testCompileClasspath,testRuntimeClasspath +io.leangen.geantyref:geantyref:1.3.16=testRuntimeClasspath +io.sqreen:libsqreen:17.3.0=testRuntimeClasspath +javax.servlet:javax.servlet-api:3.1.0=testCompileClasspath,testRuntimeClasspath +jaxen:jaxen:2.0.0=spotbugs +junit:junit:4.13.2=testRuntimeClasspath +net.bytebuddy:byte-buddy-agent:1.18.3=testCompileClasspath,testRuntimeClasspath +net.bytebuddy:byte-buddy:1.18.3=testCompileClasspath,testRuntimeClasspath +net.java.dev.jna:jna-platform:5.8.0=testRuntimeClasspath +net.java.dev.jna:jna:5.8.0=testRuntimeClasspath +net.sf.saxon:Saxon-HE:12.9=spotbugs +org.apache.ant:ant-antlr:1.10.14=codenarc +org.apache.ant:ant-junit:1.10.14=codenarc +org.apache.bcel:bcel:6.11.0=spotbugs +org.apache.commons:commons-lang3:3.19.0=spotbugs +org.apache.commons:commons-text:1.14.0=spotbugs +org.apache.logging.log4j:log4j-api:2.25.2=spotbugs +org.apache.logging.log4j:log4j-core:2.25.2=spotbugs +org.apiguardian:apiguardian-api:1.1.2=testCompileClasspath +org.codehaus.groovy:groovy-ant:3.0.23=codenarc +org.codehaus.groovy:groovy-docgenerator:3.0.23=codenarc +org.codehaus.groovy:groovy-groovydoc:3.0.23=codenarc +org.codehaus.groovy:groovy-json:3.0.23=codenarc +org.codehaus.groovy:groovy-json:3.0.25=testCompileClasspath,testRuntimeClasspath +org.codehaus.groovy:groovy-templates:3.0.23=codenarc +org.codehaus.groovy:groovy-xml:3.0.23=codenarc +org.codehaus.groovy:groovy:3.0.23=codenarc +org.codehaus.groovy:groovy:3.0.25=testCompileClasspath,testRuntimeClasspath +org.codenarc:CodeNarc:3.7.0=codenarc +org.dom4j:dom4j:2.2.0=spotbugs +org.gmetrics:GMetrics:2.1.0=codenarc +org.hamcrest:hamcrest-core:1.3=testRuntimeClasspath +org.hamcrest:hamcrest:3.0=testCompileClasspath,testRuntimeClasspath +org.jctools:jctools-core-jdk11:4.0.6=testRuntimeClasspath +org.jctools:jctools-core:4.0.6=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-api:5.14.1=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter-engine:5.14.1=testRuntimeClasspath +org.junit.jupiter:junit-jupiter-params:5.14.1=testCompileClasspath,testRuntimeClasspath +org.junit.jupiter:junit-jupiter:5.14.1=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-commons:1.14.1=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-engine:1.14.1=testCompileClasspath,testRuntimeClasspath +org.junit.platform:junit-platform-launcher:1.14.1=testRuntimeClasspath +org.junit.platform:junit-platform-runner:1.14.1=testRuntimeClasspath +org.junit.platform:junit-platform-suite-api:1.14.1=testRuntimeClasspath +org.junit.platform:junit-platform-suite-commons:1.14.1=testRuntimeClasspath +org.junit:junit-bom:5.14.0=spotbugs +org.junit:junit-bom:5.14.1=testCompileClasspath,testRuntimeClasspath +org.mockito:mockito-core:4.4.0=testRuntimeClasspath +org.msgpack:msgpack-core:0.8.24=testRuntimeClasspath +org.objenesis:objenesis:3.3=testCompileClasspath,testRuntimeClasspath +org.opentest4j:opentest4j:1.3.0=testCompileClasspath,testRuntimeClasspath +org.ow2.asm:asm-analysis:9.7.1=testRuntimeClasspath +org.ow2.asm:asm-analysis:9.9=spotbugs +org.ow2.asm:asm-commons:9.7.1=testRuntimeClasspath +org.ow2.asm:asm-commons:9.9=spotbugs +org.ow2.asm:asm-tree:9.7.1=testRuntimeClasspath +org.ow2.asm:asm-tree:9.9=spotbugs +org.ow2.asm:asm-util:9.7.1=testRuntimeClasspath +org.ow2.asm:asm-util:9.9=spotbugs +org.ow2.asm:asm:9.9=spotbugs +org.ow2.asm:asm:9.9.1=testCompileClasspath,testRuntimeClasspath +org.slf4j:jcl-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath +org.slf4j:jul-to-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath +org.slf4j:log4j-over-slf4j:1.7.30=testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:1.7.32=testCompileClasspath,testRuntimeClasspath +org.slf4j:slf4j-api:2.0.17=spotbugs,spotbugsSlf4j +org.slf4j:slf4j-simple:2.0.17=spotbugsSlf4j +org.spockframework:spock-bom:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath +org.spockframework:spock-core:2.4-groovy-3.0=testCompileClasspath,testRuntimeClasspath +org.tabletest:tabletest-junit:1.2.1=testCompileClasspath,testRuntimeClasspath +org.tabletest:tabletest-parser:1.2.0=testCompileClasspath,testRuntimeClasspath +org.xmlresolver:xmlresolver:5.3.3=spotbugs +empty=annotationProcessor,runtimeClasspath,spotbugsPlugins,testAnnotationProcessor diff --git a/dd-smoke-tests/jdk-tool-abort/src/test/groovy/datadog/smoketest/JdkToolAbortSmokeTest.groovy b/dd-smoke-tests/jdk-tool-abort/src/test/groovy/datadog/smoketest/JdkToolAbortSmokeTest.groovy new file mode 100644 index 00000000000..5078ca9c361 --- /dev/null +++ b/dd-smoke-tests/jdk-tool-abort/src/test/groovy/datadog/smoketest/JdkToolAbortSmokeTest.groovy @@ -0,0 +1,194 @@ +package datadog.smoketest + +import datadog.trace.agent.test.server.http.TestHttpServer +import spock.lang.AutoCleanup +import spock.lang.Shared +import spock.lang.Specification +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicInteger + +import static datadog.trace.agent.test.server.http.TestHttpServer.httpServer + +/** + * Smoke tests that verify the agent aborts transparently when attached to a JDK tool binary. + *
+ * Each test case discovers a real JDK tool binary from the test JVM's {@code $JAVA_HOME/bin/}, + * runs it with the agent attached via {@code -J-javaagent:}, and asserts that no {@code /info} + * request reached the test backend. The {@code /info} call is the first thing a fully-initialized + * agent makes to negotiate capabilities; its absence proves the early-abort path was taken. + */ +class JdkToolAbortSmokeTest extends Specification { + + private static final int TOOL_TIMEOUT_SECS = 15 + + /** + * Binaries excluded from testing, for one of the following reasons: + *