Kotlinc: catch FileNotFoundException caused by problematic destination.

This commit is contained in:
scaventz
2021-01-28 11:53:27 +08:00
committed by Alexander Udalov
parent 149282a888
commit 28452fc38e
14 changed files with 103 additions and 7 deletions
@@ -23,13 +23,24 @@ import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil
import java.io.File import java.io.File
import java.io.FileNotFoundException
fun OutputFileCollection.writeAll(outputDir: File, report: ((file: OutputFile, sources: List<File>, output: File) -> Unit)?) { fun OutputFileCollection.writeAll(outputDir: File, report: ((file: OutputFile, sources: List<File>, output: File) -> Unit)?) {
for (file in asList()) { for (file in asList()) {
val sources = file.sourceFiles val sources = file.sourceFiles
val output = File(outputDir, file.relativePath) val output = File(outputDir, file.relativePath)
report?.invoke(file, sources, output) report?.invoke(file, sources, output)
FileUtil.writeToFile(output, file.asByteArray()) try {
FileUtil.writeToFile(output, file.asByteArray())
} catch (e: FileNotFoundException) {
if (outputDir.isDirectory) {
// output directory exists
throw NoPermissionException("error while writing $output (Permission denied)", e)
}
// Failed to create directory, possibly due to lack of write permission or clash with existing file.
// In both cases, the directory will not exist, so for the sake of simplicity, we treat them same way.
throw e
}
} }
} }
@@ -38,8 +49,16 @@ fun OutputFileCollection.writeAllTo(outputDir: File) {
} }
fun OutputFileCollection.writeAll(outputDir: File, messageCollector: MessageCollector, reportOutputFiles: Boolean) { fun OutputFileCollection.writeAll(outputDir: File, messageCollector: MessageCollector, reportOutputFiles: Boolean) {
if (!reportOutputFiles) writeAllTo(outputDir) try {
else writeAll(outputDir) { _, sources, output -> if (!reportOutputFiles) writeAllTo(outputDir)
messageCollector.report(CompilerMessageSeverity.OUTPUT, OutputMessageUtil.formatOutputMessage(sources, output)) else writeAll(outputDir) { _, sources, output ->
messageCollector.report(CompilerMessageSeverity.OUTPUT, OutputMessageUtil.formatOutputMessage(sources, output))
}
} catch (e: NoPermissionException) {
messageCollector.report(CompilerMessageSeverity.ERROR, e.message!!)
} catch (e: FileNotFoundException) {
messageCollector.report(CompilerMessageSeverity.ERROR, "directory not found: $outputDir")
} }
} }
private class NoPermissionException(message: String, cause: Throwable?) : IllegalStateException(message, cause)
@@ -24,6 +24,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.output.OutputFile; import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection; import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity;
import org.jetbrains.kotlin.cli.common.messages.MessageCollector; import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
import org.jetbrains.kotlin.cli.common.modules.ModuleChunk; import org.jetbrains.kotlin.cli.common.modules.ModuleChunk;
import org.jetbrains.kotlin.cli.common.modules.ModuleXmlParser; import org.jetbrains.kotlin.cli.common.modules.ModuleXmlParser;
@@ -104,16 +105,24 @@ public class CompileEnvironmentUtil {
} }
public static void writeToJar( public static void writeToJar(
File jarPath, boolean jarRuntime, boolean noReflect, boolean resetJarTimestamps, FqName mainClass, OutputFileCollection outputFiles File jarPath,
boolean jarRuntime,
boolean noReflect,
boolean resetJarTimestamps,
FqName mainClass,
OutputFileCollection outputFiles,
MessageCollector messageCollector
) { ) {
FileOutputStream outputStream = null; FileOutputStream outputStream = null;
try { try {
// we should try to create the output dir first
jarPath.getParentFile().mkdirs();
outputStream = new FileOutputStream(jarPath); outputStream = new FileOutputStream(jarPath);
doWriteToJar(outputFiles, outputStream, mainClass, jarRuntime, noReflect, resetJarTimestamps); doWriteToJar(outputFiles, outputStream, mainClass, jarRuntime, noReflect, resetJarTimestamps);
outputStream.close(); outputStream.close();
} }
catch (FileNotFoundException e) { catch (FileNotFoundException e) {
throw new CompileEnvironmentException("Invalid jar path " + jarPath, e); messageCollector.report(CompilerMessageSeverity.ERROR, "Invalid jar path: " + jarPath, null);
} }
catch (IOException e) { catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e); throw ExceptionUtilsKt.rethrow(e);
@@ -88,7 +88,7 @@ object KotlinToJVMBytecodeCompiler {
val includeRuntime = configuration.get(JVMConfigurationKeys.INCLUDE_RUNTIME, false) val includeRuntime = configuration.get(JVMConfigurationKeys.INCLUDE_RUNTIME, false)
val noReflect = configuration.get(JVMConfigurationKeys.NO_REFLECT, false) val noReflect = configuration.get(JVMConfigurationKeys.NO_REFLECT, false)
val resetJarTimestamps = !configuration.get(JVMConfigurationKeys.NO_RESET_JAR_TIMESTAMPS, false) val resetJarTimestamps = !configuration.get(JVMConfigurationKeys.NO_RESET_JAR_TIMESTAMPS, false)
CompileEnvironmentUtil.writeToJar(jarPath, includeRuntime, noReflect, resetJarTimestamps, mainClassFqName, outputFiles) CompileEnvironmentUtil.writeToJar(jarPath, includeRuntime, noReflect, resetJarTimestamps, mainClassFqName, outputFiles, messageCollector)
if (reportOutputFiles) { if (reportOutputFiles) {
val message = OutputMessageUtil.formatOutputMessage(outputFiles.asList().flatMap { it.sourceFiles }.distinct(), jarPath) val message = OutputMessageUtil.formatOutputMessage(outputFiles.asList().flatMap { it.sourceFiles }.distinct(), jarPath)
messageCollector.report(OUTPUT, message) messageCollector.report(OUTPUT, message)
@@ -0,0 +1,4 @@
ERR:
error: directory not found: [Temp]/clashingFile
Return code: 1
@@ -0,0 +1 @@
class Test
@@ -0,0 +1 @@
Return code: 0
@@ -0,0 +1 @@
class Test
@@ -0,0 +1,4 @@
ERR:
error: error while writing [Temp]/noPermissionDir/Test.class (Permission denied)
Return code: 1
@@ -0,0 +1 @@
class Test
@@ -0,0 +1,4 @@
ERR:
error: invalid jar path: [Temp]/clashingFile/test.jar
Return code: 1
@@ -0,0 +1 @@
class Test
@@ -0,0 +1,4 @@
ERR:
error: invalid jar path: [Temp]/noPermissionDir/test.jar
Return code: 1
@@ -0,0 +1 @@
class A
@@ -201,4 +201,50 @@ public class CompilerSmokeTest extends CompilerSmokeTestBase {
runCompiler("noReflect.compile", "-include-runtime", "-no-reflect", "noReflect.kt", "-d", jar)); runCompiler("noReflect.compile", "-include-runtime", "-no-reflect", "noReflect.kt", "-d", jar));
run("noReflect.run", "-cp", jar, "noReflect.NoReflectKt"); run("noReflect.run", "-cp", jar, "noReflect.NoReflectKt");
} }
// related to KT-14772, destination is not a jar, and clashing with an existing file
public void testDestinationDirClashingWithExistingFile() throws Exception {
String outputDir = tmpdir.getAbsolutePath() + File.separator + "clashingFile";
File file = new File(outputDir);
file.createNewFile();
runCompiler("test.compile", "test.kt", "-d", outputDir);
}
// related to KT-18184, destination is not a jar, and permission denied
public void testDestinationDirNoPermission() throws Exception {
String outputDir = tmpdir.getAbsolutePath() + File.separator + "noPermissionDir";
File file = new File(outputDir, "Test.class");
file.getParentFile().mkdirs();
file.createNewFile();
// file.getParentFile().setReadOnly(); // won't work on Windows
file.setReadOnly(); // So we use this one as an alternative
runCompiler("test.compile", "test.kt", "-d", outputDir);
}
// related to KT-18184, destination is a jar, and output directory does not exist, we should try to create
// output directory for it.
public void testDestinationDirDoesNotExist() throws Exception {
String jar = tmpdir.getAbsolutePath() + File.separator + "nonExistingDir" + File.separator + "test.jar";
runCompiler("test.compile", "test.kt", "-d", jar);
}
// related to KT-18184, destination is a jar, and output directory does not exist, and failed to created
// output directory due to clash with existing file.
public void testDestinationJarClashingWithExistingFile() throws Exception {
String jar = tmpdir.getAbsolutePath() + File.separator + "clashingFile" + File.separator + "test.jar";
File file = new File(jar);
file.getParentFile().createNewFile();
runCompiler("test.compile", "test.kt", "-d", jar);
}
// related to KT-18184, Destination is a jar, and output directory exist, and permission denied to write jar.
public void testDestinationJarNoPermission() throws Exception {
String outputDir = tmpdir.getAbsolutePath() + File.separator + "noPermissionDir";
File jar = new File(outputDir, "test.jar");
jar.getParentFile().mkdirs();
jar.createNewFile();
// jar.getParentFile().setReadOnly(); // won't work on Windows
jar.setReadOnly(); // So we use this one as an alternative
runCompiler("test.compile", "test.kt", "-d", jar.getCanonicalPath());
}
} }