package com.oracle.svm.hosted.c;
import static com.oracle.svm.core.util.VMError.shouldNotReachHere;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.SubstrateUtil;
import com.oracle.svm.core.c.libc.LibCBase;
import com.oracle.svm.core.util.InterruptImageBuilding;
import com.oracle.svm.core.util.UserError;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.hosted.c.codegen.CCompilerInvoker;
import com.oracle.svm.hosted.c.codegen.QueryCodeWriter;
import com.oracle.svm.hosted.c.info.InfoTreeBuilder;
import com.oracle.svm.hosted.c.info.NativeCodeInfo;
import com.oracle.svm.hosted.c.query.QueryResultParser;
import com.oracle.svm.hosted.c.query.RawStructureLayoutPlanner;
import com.oracle.svm.hosted.c.query.SizeAndSignednessVerifier;
public class CAnnotationProcessor {
private final NativeCodeContext codeCtx;
private final NativeLibraries nativeLibs;
private CCompilerInvoker compilerInvoker;
private Path tempDirectory;
private Path queryCodeDirectory;
private NativeCodeInfo codeInfo;
private QueryCodeWriter writer;
public CAnnotationProcessor(NativeLibraries nativeLibs, NativeCodeContext codeCtx) {
this.nativeLibs = nativeLibs;
this.codeCtx = codeCtx;
if (ImageSingletons.contains(CCompilerInvoker.class)) {
this.compilerInvoker = ImageSingletons.lookup(CCompilerInvoker.class);
this.queryCodeDirectory = compilerInvoker.tempDirectory;
this.tempDirectory = compilerInvoker.tempDirectory;
} else {
assert CAnnotationProcessorCache.Options.UseCAPCache.getValue();
}
if (CAnnotationProcessorCache.Options.QueryCodeDir.hasBeenSet()) {
String queryCodeDir = CAnnotationProcessorCache.Options.QueryCodeDir.getValue();
this.queryCodeDirectory = FileSystems.getDefault().getPath(queryCodeDir).toAbsolutePath();
}
}
public NativeCodeInfo process(CAnnotationProcessorCache cache) {
InfoTreeBuilder constructor = new InfoTreeBuilder(nativeLibs, codeCtx);
codeInfo = constructor.construct();
if (nativeLibs.getErrors().size() > 0) {
return codeInfo;
}
if (CAnnotationProcessorCache.Options.UseCAPCache.getValue()) {
cache.get(nativeLibs, codeInfo);
} else {
writer = new QueryCodeWriter(queryCodeDirectory);
Path queryFile = writer.write(codeInfo);
if (nativeLibs.getErrors().size() > 0) {
return codeInfo;
}
assert Files.exists(queryFile);
if (CAnnotationProcessorCache.Options.ExitAfterQueryCodeGeneration.getValue()) {
return codeInfo;
}
Path binary = compileQueryCode(queryFile);
if (nativeLibs.getErrors().size() > 0) {
return codeInfo;
}
makeQuery(cache, binary.toString());
if (nativeLibs.getErrors().size() > 0) {
return codeInfo;
}
}
RawStructureLayoutPlanner.plan(nativeLibs, codeInfo);
SizeAndSignednessVerifier.verify(nativeLibs, codeInfo);
return codeInfo;
}
private void makeQuery(CAnnotationProcessorCache cache, String binaryName) {
Process printingProcess = null;
try {
ProcessBuilder pb = new ProcessBuilder().command(binaryName).directory(tempDirectory.toFile());
printingProcess = pb.start();
try (InputStream is = printingProcess.getInputStream()) {
List<String> lines = QueryResultParser.parse(nativeLibs, codeInfo, is);
if (CAnnotationProcessorCache.Options.NewCAPCache.getValue()) {
cache.put(codeInfo, lines);
}
}
printingProcess.waitFor();
} catch (IOException ex) {
throw shouldNotReachHere(ex);
} catch (InterruptedException e) {
throw new InterruptImageBuilding();
} finally {
if (printingProcess != null) {
printingProcess.destroy();
}
}
}
private Path compileQueryCode(Path queryFile) {
Path fileNamePath = queryFile.getFileName();
if (fileNamePath == null) {
throw VMError.shouldNotReachHere(queryFile + " invalid queryFile");
}
String fileName = fileNamePath.toString();
Path binary = tempDirectory.resolve(compilerInvoker.asExecutableName(fileName.substring(0, fileName.lastIndexOf("."))));
ArrayList<String> options = new ArrayList<>();
options.addAll(codeCtx.getDirectives().getOptions());
if (Platform.includedIn(Platform.LINUX.class)) {
options.addAll(LibCBase.singleton().getAdditionalQueryCodeCompilerOptions());
}
compilerInvoker.compileAndParseError(SubstrateOptions.StrictQueryCodeCompilation.getValue(), options, queryFile, binary, this::reportCompilerError);
return binary;
}
protected void reportCompilerError(ProcessBuilder current, Path queryFile, String line) {
for (String header : codeCtx.getDirectives().getHeaderFiles()) {
if (line.contains(header.substring(1, header.length() - 1) + ": No such file or directory")) {
UserError.abort("Basic header file missing (%s). Make sure headers are available on your system.", header);
}
}
List<Object> elements = new ArrayList<>();
int fileNameStart = line.indexOf(queryFile.toString());
if (fileNameStart != -1) {
int firstColon = line.indexOf(':', fileNameStart + 1);
if (firstColon != -1) {
int secondColon = line.indexOf(':', firstColon + 1);
if (secondColon != -1) {
String lineNumberStr = line.substring(firstColon + 1, secondColon);
try {
int lineNumber = Integer.parseInt(lineNumberStr);
elements.add(writer.getElementForLineNumber(lineNumber));
elements.add("C file contents around line " + lineNumber + ":");
for (int i = Math.max(lineNumber - 1, 1); i <= lineNumber + 1; i++) {
elements.add(queryFile.toString() + ":" + i + ": " + writer.getLine(i));
}
} catch (NumberFormatException ex) {
}
}
}
}
CInterfaceError error = new CInterfaceError(
String.format("Error compiling query code (in %s). Compiler command '%s' output included error: %s",
queryFile,
SubstrateUtil.getShellCommandString(current.command(), false),
line),
elements);
nativeLibs.getErrors().add(error);
}
}