diff --git a/ClangAstParser/build.gradle b/ClangAstParser/build.gradle
index 26a50196f..81313b2d7 100644
--- a/ClangAstParser/build.gradle
+++ b/ClangAstParser/build.gradle
@@ -22,6 +22,7 @@ dependencies {
implementation ":jOptions"
implementation ":SpecsUtils"
+ implementation 'com.google.code.gson:gson:2.12.1'
implementation 'com.google.guava:guava:33.4.0-jre'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
@@ -49,6 +50,10 @@ sourceSets {
}
}
+processResources {
+ from('clang-dumper-release.tag')
+}
+
// Test coverage configuration
jacocoTestReport {
reports {
diff --git a/ClangAstParser/clang-dumper-release.tag b/ClangAstParser/clang-dumper-release.tag
new file mode 100644
index 000000000..9acbd12ca
--- /dev/null
+++ b/ClangAstParser/clang-dumper-release.tag
@@ -0,0 +1 @@
+v18.1.8_1
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstFileResource.java b/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstFileResource.java
deleted file mode 100644
index 6e2ac72f6..000000000
--- a/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstFileResource.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/**
- * Copyright 2018 SPeCS.
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
- * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations under the License.
- */
-
-package pt.up.fe.specs.clang;
-
-import pt.up.fe.specs.util.providers.FileResourceProvider;
-
-import java.util.function.Supplier;
-
-public enum ClangAstFileResource implements Supplier {
-
- LIBC_CXX_LINUX_COMPLETE(ClangAstWebResource.LIBC_CXX_LINUX_COMPLETE),
- LIBC_CXX_MACOS_COMPLETE(ClangAstWebResource.LIBC_CXX_MACOS_COMPLETE),
- LIBC_CXX_WIN32_COMPLETE(ClangAstWebResource.LIBC_CXX_WIN32_COMPLETE),
- OPENMP_INCLUDES(ClangAstWebResource.OPENMP_INCLUDES),
- CUDA_LIB(ClangAstWebResource.CUDA_LIB),
- WIN_EXE(ClangAstWebResource.WIN_EXE),
- WIN_DLL1(ClangAstWebResource.WIN_DLL1),
- WIN_DLL2(ClangAstWebResource.WIN_DLL2),
- WIN_DLL3(ClangAstWebResource.WIN_DLL3),
- WIN_DLL4(ClangAstWebResource.WIN_DLL4),
- WIN_DLL5(ClangAstWebResource.WIN_DLL5),
- WIN_DLL6(ClangAstWebResource.WIN_DLL6),
- WIN_DLL7(ClangAstWebResource.WIN_DLL7),
- WIN_DLL8(ClangAstWebResource.WIN_DLL8),
- WIN_DLL9(ClangAstWebResource.WIN_DLL9),
- WIN_CLANG_DLL(ClangAstWebResource.WIN_CLANG_DLL),
- WIN_LLVM_DLL(ClangAstWebResource.WIN_LLVM_DLL),
- LINUX_EXE(ClangAstWebResource.LINUX_EXE),
- LINUX_PLUGIN(ClangAstWebResource.LINUX_PLUGIN),
- LINUX_LLVM_DLL(ClangAstWebResource.LINUX_LLVM_DLL),
- MAC_OS_EXE(ClangAstWebResource.MAC_OS_EXE),
- MAC_OS_LLVM_DLL(ClangAstWebResource.MAC_OS_LLVM_DLL),
- MAC_OS_DLL1(ClangAstWebResource.MAC_OS_DLL1);
-
- private final FileResourceProvider provider;
-
- ClangAstFileResource(FileResourceProvider provider) {
- this.provider = provider;
- }
-
- @Override
- public FileResourceProvider get() {
- return provider;
- }
-}
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstKeys.java b/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstKeys.java
index fd9907689..1fef8afdf 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstKeys.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstKeys.java
@@ -16,6 +16,7 @@
import org.suikasoft.jOptions.Datakey.DataKey;
import org.suikasoft.jOptions.Datakey.KeyFactory;
import org.suikasoft.jOptions.Interfaces.DataStore;
+import pt.up.fe.specs.clang.ClangAstWebResource.LocalBuild;
import pt.up.fe.specs.clava.ClavaLog;
import pt.up.fe.specs.clava.ClavaOptions;
import pt.up.fe.specs.clava.language.Standard;
@@ -27,8 +28,6 @@
public interface ClangAstKeys {
- DataKey CLANGAST_VERSION = KeyFactory.string("clangast_version", "");
-
/**
* What libc/libcxx mode should be used.
*/
@@ -55,7 +54,6 @@ public static String getFlagIgnoreIncludes() {
static DataStore toDataStore(List flags) {
DataStore config = DataStore.newInstance(ClavaOptions.STORE_DEFINITION, false);
final String stdPrefix = "-std=";
- final String clangAstDumperPrefix = "-clang-dumper=";
final String cilkFlag = "-fcilkplus";
// Search options
@@ -78,13 +76,6 @@ static DataStore toDataStore(List flags) {
continue;
}
- // If ClangAstDumper version, parse option
- if (flag.startsWith(clangAstDumperPrefix)) {
- String version = flag.substring(clangAstDumperPrefix.length());
- config.set(ClangAstKeys.CLANGAST_VERSION, version);
- continue;
- }
-
// If Cilk flag, add option
if (flag.equals(cilkFlag)) {
config.set(ClangAstKeys.USES_CILK);
@@ -108,6 +99,10 @@ static DataStore toDataStore(List flags) {
config.add(ClavaOptions.FLAGS_LIST, parsedFlags);
+ if (ClangAstWebResource.getDumperSource() instanceof LocalBuild) {
+ config.set(ClangAstKeys.LIBC_CXX_MODE, LibcMode.SYSTEM);
+ }
+
return config;
}
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstResource.java b/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstResource.java
index a80188a10..c0c53d5ca 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstResource.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstResource.java
@@ -20,12 +20,9 @@
*
*/
public enum ClangAstResource implements ResourceProvider {
- // BUILTIN_INCLUDES_3_8(ClangAstWebResource.BUILTIN_INCLUDES_3_8),
-
TEST_INCLUDES_C("test_includes.c"),
TEST_INCLUDES_CPP("test_includes.cpp");
- // private final WebResourceProvider webResource;
private final String resource;
private static final String basePackage = "clangast/";
@@ -35,24 +32,10 @@ public enum ClangAstResource implements ResourceProvider {
*/
private ClangAstResource(String resource) {
this.resource = basePackage + resource;
- // this.webResource = null;
}
- // private ClangAstResource(WebResourceProvider webResource) {
- // this.resource = null;
- // this.webResource = webResource;
- // }
-
- /* (non-Javadoc)
- * @see org.suikasoft.SharedLibrary.Interfaces.ResourceProvider#getResource()
- */
@Override
public String getResource() {
return resource;
- // if (resource != null) {
- // return resource;
- // }
-
- // return webResource.getResourceUrl();
}
}
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstWebResource.java b/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstWebResource.java
index 3a779583d..9689587a9 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstWebResource.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/ClangAstWebResource.java
@@ -13,40 +13,148 @@
package pt.up.fe.specs.clang;
+import com.google.gson.Gson;
+import pt.up.fe.specs.util.SpecsIo;
import pt.up.fe.specs.util.providers.WebResourceProvider;
-public interface ClangAstWebResource {
+import java.io.File;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
- String ROOT_16_0_5 = "https://github.com/specs-feup/clava/releases/download/clang_ast_dumper_16.0.5/";
- String ROOT_12_0_7 = "https://github.com/specs-feup/clava/releases/download/clang_ast_dumper_v12.0.7.1/";
+public final class ClangAstWebResource {
- WebResourceProvider LIBC_CXX_LINUX_COMPLETE = WebResourceProvider.newInstance(ROOT_16_0_5, "libc_cxx_linux_complete.zip", "v16.0.5");
- WebResourceProvider LIBC_CXX_MACOS_COMPLETE = WebResourceProvider.newInstance(ROOT_16_0_5, "libc_cxx_macos_complete.zip", "v16.0.6");
- WebResourceProvider LIBC_CXX_WIN32_COMPLETE = WebResourceProvider.newInstance(ROOT_16_0_5, "libc_cxx_win32_complete.zip", "v16.0.5");
+ private static final String RELEASE_ROOT = "https://github.com/specs-feup/clang-dumper/releases/download/";
+ private static final String CUDA_RELEASE_ROOT =
+ "https://github.com/specs-feup/clava/releases/download/clang_ast_dumper_v12.0.7.1/";
+ public static final String CUDA_LIB_FILENAME = "cudalib.zip";
+ static final WebResourceProvider CUDA_LIB =
+ WebResourceProvider.newInstance(CUDA_RELEASE_ROOT, CUDA_LIB_FILENAME, "v11.3.0");
+ private static final String RELEASE_TAG_RESOURCE = "clang-dumper-release.tag";
+ public static final String MANIFEST_FILENAME = "clang-dumper-release-manifest.json";
- WebResourceProvider OPENMP_INCLUDES = WebResourceProvider.newInstance(ROOT_16_0_5, "openmp_includes.zip");
+ private static final Gson GSON = new Gson();
+ private static final DumperSource DUMPER_SOURCE = readDumperSource();
- WebResourceProvider CUDA_LIB = WebResourceProvider.newInstance(ROOT_12_0_7, "cudalib.zip", "v11.3.0");
+ private ClangAstWebResource() {
+ }
- WebResourceProvider WIN_EXE = WebResourceProvider.newInstance(ROOT_16_0_5, "clang_ast_windows.exe", "v16.0.5_1");
- WebResourceProvider WIN_DLL1 = WebResourceProvider.newInstance(ROOT_12_0_7, "libwinpthread-1.dll");
- WebResourceProvider WIN_DLL2 = WebResourceProvider.newInstance(ROOT_12_0_7, "zlib1.dll");
- WebResourceProvider WIN_DLL3 = WebResourceProvider.newInstance(ROOT_12_0_7, "libzstd.dll");
- WebResourceProvider WIN_DLL4 = WebResourceProvider.newInstance(ROOT_12_0_7, "libstdc++-6.dll");
- WebResourceProvider WIN_DLL5 = WebResourceProvider.newInstance(ROOT_12_0_7, "libgcc_s_seh-1.dll");
- WebResourceProvider WIN_DLL6 = WebResourceProvider.newInstance(ROOT_12_0_7, "libffi-8.dll");
- WebResourceProvider WIN_DLL7 = WebResourceProvider.newInstance(ROOT_12_0_7, "libxml2-2.dll");
- WebResourceProvider WIN_DLL8 = WebResourceProvider.newInstance(ROOT_12_0_7, "liblzma-5.dll");
- WebResourceProvider WIN_DLL9 = WebResourceProvider.newInstance(ROOT_12_0_7, "libiconv-2.dll");
- WebResourceProvider WIN_LLVM_DLL = WebResourceProvider.newInstance(ROOT_16_0_5, "libLLVM-16.dll");
- WebResourceProvider WIN_CLANG_DLL = WebResourceProvider.newInstance(ROOT_16_0_5, "libclang-cpp.dll");
+ public static DumperSource getDumperSource() {
+ return DUMPER_SOURCE;
+ }
- WebResourceProvider LINUX_EXE = WebResourceProvider.newInstance(ROOT_16_0_5, "clang_ast_linux", "v16.0.5");
- WebResourceProvider LINUX_PLUGIN = WebResourceProvider.newInstance(ROOT_16_0_5, "clang-plugin.so", "v16.0.5");
- WebResourceProvider LINUX_LLVM_DLL = WebResourceProvider.newInstance(ROOT_16_0_5, "libLLVM-16.so.1", "v16.0.5");
+ private static DumperSource readDumperSource() {
+ var inputStream = ClangAstWebResource.class.getClassLoader().getResourceAsStream(RELEASE_TAG_RESOURCE);
- WebResourceProvider MAC_OS_EXE = WebResourceProvider.newInstance(ROOT_16_0_5, "clang_ast_macos", "v16.0.5");
- WebResourceProvider MAC_OS_LLVM_DLL = WebResourceProvider.newInstance(ROOT_16_0_5, "libLLVM.dylib", "v16.0.5");
- WebResourceProvider MAC_OS_DLL1 = WebResourceProvider.newInstance(ROOT_16_0_5, "libzstd.1.dylib", "v16.0.5");
+ if (inputStream == null) {
+ throw new RuntimeException("Could not find resource '" + RELEASE_TAG_RESOURCE + "'");
+ }
+ String value;
+ try (inputStream) {
+ value = SpecsIo.read(inputStream).trim();
+ } catch (IOException e) {
+ throw new UncheckedIOException("Could not read resource '" + RELEASE_TAG_RESOURCE + "'", e);
+ }
+
+ if (value.isBlank()) {
+ throw new RuntimeException("Resource '" + RELEASE_TAG_RESOURCE + "' is empty");
+ }
+
+ return parseDumperSource(value);
+ }
+
+ static DumperSource parseDumperSource(String value) {
+ var path = Path.of(value);
+ if (path.isAbsolute()) {
+ return new LocalBuild(path.toFile());
+ }
+
+ if (value.contains("/") || value.contains("\\") || value.equals(".") || value.equals("..")) {
+ throw new RuntimeException("Relative paths are not supported in resource '" + RELEASE_TAG_RESOURCE
+ + "': '" + value + "'");
+ }
+
+ return new Release(value);
+ }
+
+ public static String getReleaseTag() {
+ var source = getDumperSource();
+ if (source instanceof Release release) {
+ return release.tag();
+ }
+
+ throw new IllegalStateException("The clang-dumper resource points to a local build");
+ }
+
+ public static ClangDumperManifest getManifest(File resourceFolder) {
+ var releaseTag = getReleaseTag();
+ var manifestResource = WebResourceProvider.newInstance(getReleaseBaseUrl(releaseTag), MANIFEST_FILENAME,
+ releaseTag);
+ var manifestFile = manifestResource.writeVersioned(resourceFolder, ClangAstWebResource.class).getFile();
+ var manifest = GSON.fromJson(SpecsIo.read(manifestFile), ClangDumperManifest.class);
+
+ if (manifest == null) {
+ throw new RuntimeException("Could not parse clang-dumper manifest from '" + manifestFile + "'");
+ }
+
+ manifest.validate();
+ return manifest;
+ }
+
+ public static WebResourceProvider getAssetResource(ClangDumperManifestAsset asset) {
+ var releaseTag = getReleaseTag();
+ return WebResourceProvider.newInstance(getReleaseBaseUrl(releaseTag), asset.filename(),
+ releaseTag + "-" + asset.sha256());
+ }
+
+ private static String getReleaseBaseUrl(String releaseTag) {
+ return RELEASE_ROOT + releaseTag + "/";
+ }
+
+ public sealed interface DumperSource permits Release, LocalBuild {
+ }
+
+ public record Release(String tag) implements DumperSource {
+ }
+
+ public record LocalBuild(File folder) implements DumperSource {
+ }
+
+ public record ClangDumperManifest(int schema_version, List assets) {
+
+ public void validate() {
+ if (schema_version != 1) {
+ throw new RuntimeException("Unsupported clang-dumper manifest schema version: " + schema_version);
+ }
+
+ if (assets == null || assets.isEmpty()) {
+ throw new RuntimeException("Clang-dumper manifest does not contain assets");
+ }
+ }
+
+ public ClangDumperManifestAsset getAsset(String platform, String arch, String kind) {
+ Objects.requireNonNull(platform);
+ Objects.requireNonNull(arch);
+ Objects.requireNonNull(kind);
+
+ Optional asset = assets.stream()
+ .filter(candidate -> candidate.matches(platform, arch, kind))
+ .findFirst();
+
+ return asset.orElseThrow(() -> new RuntimeException("Could not find clang-dumper asset for platform '"
+ + platform + "', architecture '" + arch + "' and kind '" + kind + "'"));
+ }
+ }
+
+ public record ClangDumperManifestAsset(String filename, String kind, String platform, String arch, int llvm_major,
+ String sha256) {
+
+ public boolean matches(String platform, String arch, String kind) {
+ return this.platform.equals(platform) && this.arch.equals(arch) && this.kind.equals(kind);
+ }
+ }
}
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/ClangResources.java b/ClangAstParser/src/pt/up/fe/specs/clang/ClangResources.java
index 3aa4b5659..9d54bf5eb 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/ClangResources.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/ClangResources.java
@@ -13,6 +13,9 @@
package pt.up.fe.specs.clang;
+import pt.up.fe.specs.clang.ClangAstWebResource.ClangDumperManifest;
+import pt.up.fe.specs.clang.ClangAstWebResource.ClangDumperManifestAsset;
+import pt.up.fe.specs.clang.ClangAstWebResource.LocalBuild;
import pt.up.fe.specs.clang.codeparser.CodeParser;
import pt.up.fe.specs.clang.dumper.ClangAstDumper;
import pt.up.fe.specs.clang.parsers.TopLevelNodesParser;
@@ -20,111 +23,152 @@
import pt.up.fe.specs.util.SpecsIo;
import pt.up.fe.specs.util.SpecsLogs;
import pt.up.fe.specs.util.SpecsSystem;
-import pt.up.fe.specs.util.lazy.Lazy;
-import pt.up.fe.specs.util.providers.FileResourceManager;
-import pt.up.fe.specs.util.providers.FileResourceProvider;
import pt.up.fe.specs.util.providers.FileResourceProvider.ResourceWriteData;
import pt.up.fe.specs.util.system.ProcessOutputAsString;
import java.io.File;
-import java.util.*;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.nio.file.DirectoryNotEmptyException;
+import java.nio.file.FileAlreadyExistsException;
+import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
+import java.nio.file.Path;
+import java.nio.file.StandardOpenOption;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HexFormat;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
public class ClangResources {
- private static final FileResourceManager CLANG_AST_RESOURCES = FileResourceManager
- .fromEnum(ClangAstFileResource.class);
-
private static final Map CLANG_FILES_CACHE = new ConcurrentHashMap<>();
-
+ private static final Map CLANG_FILES_LOCKS = new ConcurrentHashMap<>();
private final static String CLANG_FOLDERNAME = "clang_ast_exe";
+ private final static String INCLUDES_FOLDERNAME = "includes";
+ private final static String LAST_USED_FILENAME = "last-used.txt";
+ private final static String CACHE_LOCK_FOLDERNAME = ".cache.lock";
+ private final static String CACHE_LOCK_OWNER_PREFIX = "owner-";
+ private final static Duration CACHE_LOCK_RETRY_INTERVAL = Duration.ofMillis(100);
+ private final static Duration CACHE_LOCK_STALE_MAX_AGE = Duration.ofMinutes(5);
+ private final static Duration STALE_CACHE_MAX_AGE = Duration.ofDays(60);
- private final Lazy cudalibFolder = Lazy.newInstance(this::prepareBuiltinCudaLib);
+ private static final Map HAS_LIBC = new ConcurrentHashMap<>();
private final CodeParser options;
-
- private static final AtomicInteger HAS_LIBC = new AtomicInteger(-1);
-
public ClangResources(CodeParser options) {
this.options = options;
}
- public ClangFiles getClangFiles(String version, LibcMode libcMode) {
-
- // Create key
- var key = libcMode.name() + "_" + version + "_" + getClangResourceFolder().getAbsolutePath();
+ public File getBuiltinCudaLib() {
+ var cudaResourceFolder = SpecsIo.mkdir(options.get(CodeParser.DUMPER_FOLDER), "cuda");
+ var cudaFolder = SpecsIo.mkdir(cudaResourceFolder, "cudalib");
+ var zipFile = ClangAstWebResource.CUDA_LIB.writeVersioned(cudaResourceFolder, ClangResources.class);
- // Check if cached
- var files = CLANG_FILES_CACHE.get(key);
- if (files != null) {
- SpecsLogs.debug(() -> "Using cached version of Clang files: " + files);
- return files;
+ if (zipFile.isNewFile() || !isCudaInstallation(cudaFolder)) {
+ SpecsIo.deleteFolderContents(cudaFolder);
+ SpecsIo.extractZip(zipFile.getFile(), cudaFolder);
}
- File clangExecutable = prepareResources(version);
- List builtinIncludes = prepareIncludes(clangExecutable, libcMode);
+ return cudaFolder;
+ }
- var newFiles = new ClangFiles(clangExecutable, builtinIncludes);
- SpecsLogs.debug(() -> "Using downloaded version of Clang files: " + newFiles);
+ private static boolean isCudaInstallation(File folder) {
+ return folder.isDirectory() && new File(folder, "include/cuda_runtime.h").isFile();
+ }
- // Store in cache
- CLANG_FILES_CACHE.put(key, newFiles);
+ public ClangFiles getClangFiles(LibcMode libcMode) {
- return newFiles;
- }
+ var source = ClangAstWebResource.getDumperSource();
- /**
- * @return path to the executable that was copied
- */
- private File prepareResources(String version) {
+ if (source instanceof LocalBuild localBuild) {
+ return new ClangFiles(getLocalExecutable(localBuild.folder()), List.of());
+ }
- File resourceFolder = getClangResourceFolder();
+ var useBuiltinCuda = options.get(CodeParser.CUDA_PATH).equalsIgnoreCase(CodeParser.getBuiltinOption());
+ var resourceFolder = getClangResourceFolder();
+ var key = libcMode.name() + "_" + useBuiltinCuda + "_" + source + "_"
+ + resourceFolder.getAbsolutePath();
+ var jvmLock = CLANG_FILES_LOCKS.computeIfAbsent(resourceFolder.getAbsolutePath(), ignored -> new Object());
+ synchronized (jvmLock) {
+ var lockFolder = getCacheLockFolder(resourceFolder);
+ try (var ignored = acquireCacheLock(resourceFolder)) {
+ var files = CLANG_FILES_CACHE.get(key);
+ if (files != null) {
+ if (files.clangExecutable().isFile()) {
+ writeLastUsed(resourceFolder, Instant.now());
+ SpecsLogs.debug(() -> "Using cached version of Clang files: " + files);
+ return files;
+ }
+
+ CLANG_FILES_CACHE.remove(key, files);
+ }
- SupportedPlatform platform = SupportedPlatform.getCurrentPlatform();
- FileResourceProvider executableResource = getVersionedResource(getExecutableResource(platform), version);
+ var manifest = ClangAstWebResource.getManifest(resourceFolder);
+ File clangExecutable = prepareResources(manifest, resourceFolder);
+ List builtinIncludes = prepareIncludes(manifest, resourceFolder, clangExecutable, libcMode);
- // Copy executable
- ResourceWriteData executable = executableResource.writeVersioned(resourceFolder, ClangResources.class);
+ if (useBuiltinCuda) {
+ getBuiltinCudaLib();
+ }
- // If Windows, copy additional dependencies
- if (platform == SupportedPlatform.WINDOWS) {
- for (FileResourceProvider resource : getWindowsResources()) {
- resource.writeVersioned(resourceFolder, ClangResources.class);
- }
- } else if (platform == SupportedPlatform.MAC_OS) {
- for (FileResourceProvider resource : getMacOSResources()) {
- resource.writeVersioned(resourceFolder, ClangResources.class);
- }
- } else if (platform == SupportedPlatform.LINUX) {
- for (FileResourceProvider resource : getLinuxResources()) {
- resource.writeVersioned(resourceFolder, ClangResources.class);
+ validateTopLevelCacheFiles(manifest, resourceFolder);
+ updateLastUsedAndCleanupStaleVersions(resourceFolder);
+
+ var newFiles = new ClangFiles(clangExecutable, builtinIncludes);
+ SpecsLogs.debug(() -> "Using downloaded version of Clang files: " + newFiles);
+
+ CLANG_FILES_CACHE.put(key, newFiles);
+ return newFiles;
+ } catch (IOException e) {
+ throw new UncheckedIOException("Could not lock clang-dumper cache '" + lockFolder + "'", e);
}
}
+ }
+
+ static File getLocalExecutable(File buildFolder) {
+ if (!buildFolder.isDirectory()) {
+ throw new RuntimeException("Local clang-dumper build directory does not exist: '" + buildFolder + "'");
+ }
+
+ String filename;
+ if (ClangAstDumper.usePlugin()) {
+ filename = System.mapLibraryName("plugin");
+ } else {
+ filename = SupportedPlatform.getCurrentPlatform().isWindows() ? "tool.exe" : "tool";
+ }
+
+ var executable = new File(buildFolder, filename);
+ if (!executable.isFile()) {
+ throw new RuntimeException("Could not find local clang-dumper "
+ + (ClangAstDumper.usePlugin() ? "plugin" : "tool") + " '" + executable + "'");
+ }
+
+ SpecsLogs.info("Using local clang-dumper build: " + executable);
+ return executable;
+ }
+
+ private File prepareResources(ClangDumperManifest manifest, File resourceFolder) {
+ SupportedPlatform platform = SupportedPlatform.getCurrentPlatform();
+
+ var executableKind = ClangAstDumper.usePlugin() ? "plugin" : "tool";
+ ResourceWriteData executable = downloadAsset(manifest, executableKind, resourceFolder);
- // If on Windows, preemptively unblock file, due to possible Mark-of-the-Web restrictions
if (platform.isWindows()) {
- var command = List.of(SpecsSystem.getWindowsPowershell(), "-NoLogo", "-NoProfile", "-NonInteractive",
- "-ExecutionPolicy", "Bypass",
- "-Command",
- "Unblock-File",
- "-Path",
- "\"" + executable.getFile().getAbsolutePath() + "\"",
- "-ErrorAction",
- "Stop"
- );
-
- var output = SpecsSystem.runProcess(command, true, true);
- if (output.getReturnValue() == 0) {
- SpecsLogs.info("Successfully unblocked dumper executable");
- } else {
- SpecsLogs.info("Could not unblock dumper executable");
- }
+ unblockWindowsFile(executable.getFile());
}
- // If file is new and we are in a flavor of Linux or MacOS, make file executable
if (executable.isNewFile() && (platform.isLinux() || platform.isMacOs())) {
SpecsSystem.runProcess(Arrays.asList("chmod", "+x", executable.getFile().getAbsolutePath()), false, true);
}
@@ -132,270 +176,521 @@ private File prepareResources(String version) {
return executable.getFile();
}
- private FileResourceProvider getVersionedResource(FileResourceProvider resource, String version) {
-
- // If version not defined, use the latest version of the resource
- if (version.isEmpty()) {
- version = resource.version();
+ private void unblockWindowsFile(File executable) {
+ var command = List.of(SpecsSystem.getWindowsPowershell(), "-NoLogo", "-NoProfile", "-NonInteractive",
+ "-ExecutionPolicy", "Bypass",
+ "-Command",
+ "Unblock-File",
+ "-Path",
+ "\"" + executable.getAbsolutePath() + "\"",
+ "-ErrorAction",
+ "Stop"
+ );
+
+ var output = SpecsSystem.runProcess(command, true, true);
+ if (output.getReturnValue() == 0) {
+ SpecsLogs.info("Successfully unblocked dumper executable");
+ } else {
+ SpecsLogs.info("Could not unblock dumper executable");
}
-
- // ClangAst executable versions are separated by an underscore
- resource = resource.createResourceVersion("_" + version);
- return resource;
}
public File getClangResourceFolder() {
- return options.get(CodeParser.DUMPER_FOLDER);
+ return SpecsIo.mkdir(options.get(CodeParser.DUMPER_FOLDER), ClangAstWebResource.getReleaseTag());
}
public static File getDefaultTempFolder() {
return SpecsIo.getTempFolder(CLANG_FOLDERNAME);
}
- private FileResourceProvider getExecutableResource(SupportedPlatform platform) {
- switch (platform) {
- case WINDOWS:
- return CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_EXE);
- case LINUX:
- if (ClangAstDumper.usePlugin()) {
- return CLANG_AST_RESOURCES.get(ClangAstFileResource.LINUX_PLUGIN);
- } else {
- return CLANG_AST_RESOURCES.get(ClangAstFileResource.LINUX_EXE);
+ public static boolean useBuiltinLibc(File clangExecutable, LibcMode libcMode) {
+ return switch (libcMode) {
+ case AUTO -> !hasLibC(clangExecutable);
+ case BUILTIN_AND_LIBC -> true;
+ case SYSTEM -> false;
+ };
+ }
+
+ private static boolean hasLibC(File clangExecutable) {
+ var executableKey = SpecsIo.getCanonicalPath(clangExecutable);
+ return HAS_LIBC.computeIfAbsent(executableKey, ignored -> detectLibC(clangExecutable));
+ }
+
+ private static boolean detectLibC(File clangExecutable) {
+ File clangTest = SpecsIo.getTempFolder("clang_ast_test_" + UUID.randomUUID());
+
+ try {
+ var testFiles = List.of(
+ ClangAstResource.TEST_INCLUDES_C.write(clangTest),
+ ClangAstResource.TEST_INCLUDES_CPP.write(clangTest));
+
+ boolean needsLib = false;
+ for (var testFile : testFiles) {
+ var output = runClangAstDumper(clangExecutable, testFile);
+
+ if (output.getReturnValue() != 0) {
+ ClavaLog.info("Problems while running dumper to test if libc/libcxx is needed");
+ needsLib = true;
+ break;
}
- case MAC_OS:
- return CLANG_AST_RESOURCES.get(ClangAstFileResource.MAC_OS_EXE);
- default:
- throw new RuntimeException("Case not defined: '" + platform + "'");
+ if (testFile.getName().endsWith(".cpp")
+ && !output.getOutput().contains(TopLevelNodesParser.getTopLevelNodesHeader())) {
+ needsLib = true;
+ break;
+ }
+ }
+
+ if (needsLib) {
+ ClavaLog.debug("Could not find system libc/libcxx");
+ } else {
+ ClavaLog.debug("Detected system's libc and libcxx");
+ }
+
+ return !needsLib;
+ } finally {
+ SpecsIo.deleteFolder(clangTest);
}
}
- private List getWindowsResources() {
- List windowsResources = new ArrayList<>();
+ private static ProcessOutputAsString runClangAstDumper(File clangExecutable, File testFile) {
+ List arguments = List.of(clangExecutable.getAbsolutePath(), testFile.getAbsolutePath(), "--");
+ return SpecsSystem.runProcess(arguments, true, false);
+ }
- windowsResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_DLL1));
- windowsResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_DLL2));
- windowsResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_DLL3));
- windowsResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_DLL4));
- windowsResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_DLL5));
- windowsResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_DLL6));
- windowsResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_DLL7));
- windowsResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_DLL8));
- windowsResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_DLL9));
- windowsResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_CLANG_DLL));
- windowsResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.WIN_LLVM_DLL));
+ private List prepareIncludes(ClangDumperManifest manifest, File resourceFolder, File clangExecutable,
+ LibcMode libcMode) {
+ var useBuiltinLibc = useBuiltinLibc(clangExecutable, libcMode);
+ var useBuiltinCuda = options.get(CodeParser.CUDA_PATH).equalsIgnoreCase(CodeParser.getBuiltinOption());
- return windowsResources;
- }
+ if (!useBuiltinLibc && !useBuiltinCuda) {
+ return List.of();
+ }
- private List getMacOSResources() {
- List macosResources = new ArrayList<>();
+ return prepareIncludes(manifest, resourceFolder);
+ }
- macosResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.MAC_OS_LLVM_DLL));
- macosResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.MAC_OS_DLL1));
+ private List prepareIncludes(ClangDumperManifest manifest, File resourceFolder) {
+ var extractedFolder = prepareIncludesFolder(manifest, resourceFolder);
+ var includeFolders = getIncludeFolders(extractedFolder);
+ SpecsLogs.debug(() -> "Includes folders: " + includeFolders);
- return macosResources;
+ return includeFolders.stream().map(File::getAbsolutePath).toList();
}
- private List getLinuxResources() {
- List linuxResources = new ArrayList<>();
+ private File prepareIncludesFolder(ClangDumperManifest manifest, File resourceFolder) {
+ var includesAsset = getCurrentAsset(manifest, "includes");
+ var extractedFolder = new File(resourceFolder, INCLUDES_FOLDERNAME);
- linuxResources.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.LINUX_LLVM_DLL));
+ if (isIncludesCacheValid(extractedFolder)) {
+ return extractedFolder;
+ }
+
+ ResourceWriteData zipFile = downloadAsset(includesAsset, resourceFolder);
+
+ try {
+ SpecsIo.mkdir(extractedFolder);
+ SpecsIo.deleteFolderContents(extractedFolder);
+ SpecsIo.extractZip(zipFile.getFile(), extractedFolder);
+ } finally {
+ SpecsIo.delete(zipFile.getFile());
+ }
- return linuxResources;
+ return extractedFolder;
}
- private List prepareIncludes(File clangExecutable, LibcMode libcMode) {
+ private List getIncludeFolders(File extractedFolder) {
+ var entrypointsFile = new File(extractedFolder, "entrypoints.txt");
+ if (!entrypointsFile.isFile()) {
+ throw new RuntimeException("Could not find include archive entrypoints file '" + entrypointsFile + "'");
+ }
- // Get base resource folder
- File resourceFolder = getClangResourceFolder();
+ return SpecsIo.read(entrypointsFile).lines()
+ .map(String::trim)
+ .filter(line -> !line.isEmpty())
+ .map(line -> new File(extractedFolder, line))
+ .toList();
+ }
- // Create list of include zips
- List includesZips = new ArrayList<>();
+ private ResourceWriteData downloadAsset(ClangDumperManifest manifest, String kind, File resourceFolder) {
+ var asset = getCurrentAsset(manifest, kind);
+ return downloadAsset(asset, resourceFolder);
+ }
- // Get libc/libcxx resources, if required
- if (useBuiltinLibc(clangExecutable, libcMode)) {
+ private ResourceWriteData downloadAsset(ClangDumperManifestAsset asset, File resourceFolder) {
+ var resource = ClangAstWebResource.getAssetResource(asset);
+ var writeData = resource.writeVersioned(resourceFolder, ClangResources.class);
- // MacOS
- if (SupportedPlatform.getCurrentPlatform().isMacOs()) {
- var macosBuiltinResource = CLANG_AST_RESOURCES.get(ClangAstFileResource.LIBC_CXX_MACOS_COMPLETE);
- includesZips.add(getVersionedResource(macosBuiltinResource, macosBuiltinResource.version()));
- }
- // Linux
- else if (SupportedPlatform.getCurrentPlatform().isLinux()) {
- var linuxBuiltinResource = CLANG_AST_RESOURCES.get(ClangAstFileResource.LIBC_CXX_LINUX_COMPLETE);
- includesZips.add(getVersionedResource(linuxBuiltinResource, linuxBuiltinResource.version()));
- }
- // Windows
- else if (SupportedPlatform.getCurrentPlatform().isWindows()) {
- var windowsBuiltinResource = CLANG_AST_RESOURCES.get(ClangAstFileResource.LIBC_CXX_WIN32_COMPLETE);
- includesZips.add(getVersionedResource(windowsBuiltinResource, windowsBuiltinResource.version()));
- } else {
- throw new RuntimeException("Unsupported platform: " + SupportedPlatform.getCurrentPlatform());
- }
+ if (!writeData.isNewFile()) {
+ return writeData;
+ }
+ if (!hasExpectedSha256(writeData.getFile(), asset)) {
+ SpecsLogs.info("Downloaded clang-dumper asset '" + asset.filename()
+ + "' does not match the expected checksum, downloading it again.");
+ SpecsIo.delete(writeData.getFile());
+ writeData = resource.writeVersioned(resourceFolder, ClangResources.class);
+ }
+ if (!hasExpectedSha256(writeData.getFile(), asset)) {
+ throw new RuntimeException("Downloaded clang-dumper asset '" + asset.filename()
+ + "' does not match expected SHA-256 '" + asset.sha256() + "'");
}
- // Always add OpenMP includes
- includesZips.add(CLANG_AST_RESOURCES.get(ClangAstFileResource.OPENMP_INCLUDES));
+ return writeData;
+ }
- // Download includes zips, later we check if any of them is new
- List zipFiles = includesZips.stream()
- .map(resource -> resource.writeVersioned(resourceFolder, ClangResources.class))
- .collect(Collectors.toList());
+ private ClangDumperManifestAsset getCurrentAsset(ClangDumperManifest manifest, String kind) {
+ var platform = getManifestPlatform();
+ var arch = getManifestArch(platform);
+ return manifest.getAsset(platform, arch, kind);
+ }
+ static boolean isIncludesCacheValid(File includesFolder) {
- var extractedFolders = new ArrayList();
+ if (!includesFolder.isDirectory()) {
+ return false;
+ }
- // If a new file has been written or if folder exists but is empty, delete corresponding includes folder, and extract zip again
- for (var zipFile : zipFiles) {
+ var entrypointsFile = new File(includesFolder, "entrypoints.txt");
+ if (!entrypointsFile.isFile()) {
+ SpecsLogs.info("Cached clang-dumper includes are missing entrypoints, extracting them again.");
+ return false;
+ }
- // Obtain folder for zip
- var zipFoldername = "include_" + SpecsIo.removeExtension(zipFile.getFile());
- var extractedFolder = SpecsIo.mkdir(resourceFolder, zipFoldername);
+ return true;
+ }
- // Add to extracted folders list
- extractedFolders.add(extractedFolder);
+ private void validateTopLevelCacheFiles(ClangDumperManifest manifest, File resourceFolder) {
+ var executableKind = ClangAstDumper.usePlugin() ? "plugin" : "tool";
+ Set expectedNames = Set.of(
+ ClangAstWebResource.MANIFEST_FILENAME,
+ getCurrentAsset(manifest, executableKind).filename(),
+ INCLUDES_FOLDERNAME,
+ LAST_USED_FILENAME);
+
+ var files = resourceFolder.listFiles();
+ if (files == null) {
+ return;
+ }
- // Skip extraction if zip is not new and folder is not empty
- if (!zipFile.isNewFile() && !SpecsIo.isEmptyFolder(extractedFolder)) {
+ for (var file : files) {
+ if (expectedNames.contains(file.getName())) {
continue;
}
- // Clean folder
- SpecsIo.deleteFolderContents(extractedFolder);
+ SpecsLogs.info("Deleting unexpected file from clang-dumper cache: " + file);
+ SpecsIo.delete(file);
+ }
+ }
- // Extract zip contents to folder
- SpecsIo.extractZip(zipFile.getFile(), extractedFolder);
+ private void updateLastUsedAndCleanupStaleVersions(File resourceFolder) {
+ var now = Instant.now();
+ writeLastUsed(resourceFolder, now);
+
+ var staleCleanup = new Thread(() -> deleteStaleVersions(now, resourceFolder),
+ "clang-dumper-stale-cache-cleanup");
+ staleCleanup.setDaemon(true);
+ staleCleanup.start();
+ }
+
+ private static void writeLastUsed(File resourceFolder, Instant timestamp) {
+ writeTimestamp(new File(resourceFolder, LAST_USED_FILENAME), timestamp);
+ }
+
+ private static void writeTimestamp(File file, Instant timestamp) {
+ SpecsIo.write(file, timestamp.toString());
+ }
+
+ void deleteStaleVersions(Instant now, File currentVersionFolder) {
+ File cacheBaseFolder = options.get(CodeParser.DUMPER_FOLDER);
+ var versions = cacheBaseFolder.listFiles(File::isDirectory);
+ if (versions == null) {
+ return;
}
- // Add all folders inside extracted folders as system include
- var includesFiles = new ArrayList();
- for (var extractedFolder : extractedFolders) {
- var includeFolders = SpecsIo.getFolders(extractedFolder);
+ for (var versionFolder : versions) {
+ if (versionFolder.getAbsoluteFile().equals(currentVersionFolder.getAbsoluteFile())) {
+ continue;
+ }
- includesFiles.addAll(includeFolders);
+ var jvmLock = CLANG_FILES_LOCKS.computeIfAbsent(versionFolder.getAbsolutePath(), ignored -> new Object());
+ try {
+ synchronized (jvmLock) {
+ var lastUsedFile = new File(versionFolder, LAST_USED_FILENAME);
+ if (!lastUsedFile.isFile()) {
+ continue;
+ }
+
+ try (var lock = tryAcquireCacheLock(versionFolder)) {
+ if (lock == null) {
+ SpecsLogs.debug(() -> "Skipping locked clang-dumper cache folder: " + versionFolder);
+ continue;
+ }
+
+ if (!lastUsedFile.isFile()) {
+ continue;
+ }
+
+ var lastUsed = Instant.parse(SpecsIo.read(lastUsedFile).trim());
+ if (lastUsed.isBefore(now.minus(STALE_CACHE_MAX_AGE))) {
+ SpecsLogs.info("Deleting stale clang-dumper cache folder: " + versionFolder);
+ SpecsIo.deleteFolder(versionFolder);
+ }
+ }
+ }
+ } catch (IOException | RuntimeException e) {
+ SpecsLogs.warn("Could not inspect clang-dumper cache folder '" + versionFolder + "'", e);
+ }
}
+ }
+
+ static CacheLock acquireCacheLock(File versionFolder) throws IOException {
+ return acquireCacheLock(versionFolder, true);
+ }
+ private static CacheLock tryAcquireCacheLock(File versionFolder) throws IOException {
+ return acquireCacheLock(versionFolder, false);
+ }
+
+ private static CacheLock acquireCacheLock(File versionFolder, boolean wait) throws IOException {
+ var lockFolder = getCacheLockFolder(versionFolder);
+ Files.createDirectories(lockFolder.getParentFile().toPath());
+
+ while (true) {
+ try {
+ Files.createDirectory(lockFolder.toPath());
+ } catch (FileAlreadyExistsException e) {
+ if (!isCacheLockStale(lockFolder)) {
+ if (!wait) {
+ return null;
+ }
+
+ waitForCacheLock();
+ continue;
+ }
+
+ recoverStaleCacheLock(lockFolder);
+ continue;
+ }
- // Sort them alphabetically, by last foldername, include order is important
- Collections.sort(includesFiles, Comparator.comparing(File::getName));
- SpecsLogs.debug(() -> "Includes folders: " + includesFiles);
+ var ownerFile = new File(lockFolder, CACHE_LOCK_OWNER_PREFIX + UUID.randomUUID());
+ try {
+ Files.writeString(ownerFile.toPath(), getProcessIdentity(), StandardOpenOption.CREATE_NEW,
+ StandardOpenOption.WRITE);
+ } catch (NoSuchFileException e) {
+ // Stale-lock recovery removed the directory while this process was claiming it.
+ continue;
+ } catch (IOException e) {
+ deleteEmptyCacheLock(lockFolder);
+ throw e;
+ }
- return includesFiles.stream().map(File::getAbsolutePath).toList();
+ return new CacheLock(lockFolder, ownerFile);
+ }
}
- public static boolean useBuiltinLibc(File clangExecutable, LibcMode libcMode) {
+ /**
+ * Returns the temporary lock folder for a cache version.
+ *
+ * The lock folder is outside the version folder because stale cleanup deletes that folder while holding the
+ * lock. The folder is removed when the lock is released, so normal operation leaves no lock artifact behind.
+ */
+ static File getCacheLockFolder(File versionFolder) {
+ var absoluteVersionFolder = versionFolder.getAbsoluteFile();
+ return new File(absoluteVersionFolder.getParentFile(), absoluteVersionFolder.getName() + CACHE_LOCK_FOLDERNAME);
+ }
- return switch (libcMode) {
- case AUTO -> !hasLibC(clangExecutable);
- case BUILTIN_AND_LIBC -> true;
- case SYSTEM -> false;
- };
+ private static String getProcessIdentity() {
+ var process = ProcessHandle.current();
+ var startTime = process.info().startInstant().map(Instant::toString).orElse("");
+ return process.pid() + System.lineSeparator() + startTime;
}
- private static boolean hasLibC(File clangExecutable) {
- var value = HAS_LIBC.get();
+ private static boolean isCacheLockStale(File lockFolder) throws IOException {
+ if (!lockFolder.exists()) {
+ return true;
+ }
- // Check if initiallized
- if (value == -1) {
- var hasLibC = detectLibC(clangExecutable);
- value = hasLibC ? 1 : 0;
- HAS_LIBC.set(value);
+ if (!lockFolder.isDirectory()) {
+ throw new IOException("Cache lock path is not a directory: '" + lockFolder + "'");
}
- if (value == 0) {
- return false;
+ var ownerFiles = lockFolder.listFiles(File::isFile);
+ if (ownerFiles == null) {
+ if (!lockFolder.exists()) {
+ return true;
+ }
+
+ throw new IOException("Could not list cache lock folder: '" + lockFolder + "'");
}
- if (value == 1) {
- return true;
+ if (ownerFiles.length == 0) {
+ return isCacheLockOld(lockFolder);
+ }
+
+ for (var ownerFile : ownerFiles) {
+ if (!isCacheLockOwnerStale(lockFolder, ownerFile)) {
+ return false;
+ }
}
- throw new RuntimeException("Unexpected value: '" + value + "'");
+ return true;
}
- /**
- * Detects if the system has libc/licxx installed.
- *
- * @param clangExecutable
- * @return
- */
- private static boolean detectLibC(File clangExecutable) {
+ private static boolean isCacheLockOwnerStale(File lockFolder, File ownerFile) throws IOException {
+ List lines;
+ try {
+ lines = Files.readAllLines(ownerFile.toPath());
+ } catch (NoSuchFileException e) {
+ return true;
+ }
- File clangTest = SpecsIo.mkdir(SpecsIo.getTempFolder(), "clang_ast_test");
+ if (lines.isEmpty()) {
+ return isCacheLockOld(lockFolder);
+ }
- // Write test files
- List testFiles = Arrays.asList(ClangAstResource.TEST_INCLUDES_C, ClangAstResource.TEST_INCLUDES_CPP)
- .stream()
- .map(resource -> resource.write(clangTest))
- .collect(Collectors.toList());
+ try {
+ var pid = Long.parseLong(lines.get(0).trim());
+ var process = ProcessHandle.of(pid);
+ if (process.isEmpty() || !process.get().isAlive()) {
+ return true;
+ }
- boolean needsLib = false;
- for (File testFile : testFiles) {
+ if (lines.size() > 1 && !lines.get(1).isBlank()) {
+ var processStart = process.get().info().startInstant();
+ if (processStart.isPresent() && !processStart.get().toString().equals(lines.get(1).trim())) {
+ return true;
+ }
+ }
- // Invoke dumper
- var output = runClangAstDumper(clangExecutable, testFile);
+ return false;
+ } catch (NumberFormatException e) {
+ return isCacheLockOld(lockFolder);
+ }
+ }
- // First check if there where no problems running the dumper
- if (output.getReturnValue() != 0) {
- ClavaLog.info("Problems while running dumper to test in libc/libcxx is needed");
- needsLib = true;
- break;
- }
+ private static boolean isCacheLockOld(File lockFolder) throws IOException {
+ try {
+ return Files.getLastModifiedTime(lockFolder.toPath()).toInstant()
+ .isBefore(Instant.now().minus(CACHE_LOCK_STALE_MAX_AGE));
+ } catch (NoSuchFileException e) {
+ return true;
+ }
+ }
- // Test files where built in such a way so that if a system include is present, it will generate code with a
- // top level nodes, otherwise it generates an empty file
- var topLevelNodesHeader = TopLevelNodesParser.getTopLevelNodesHeader();
+ private static void waitForCacheLock() throws IOException {
+ try {
+ Thread.sleep(CACHE_LOCK_RETRY_INTERVAL.toMillis());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IOException("Interrupted while waiting for clang-dumper cache lock", e);
+ }
+ }
+
+ private static void recoverStaleCacheLock(File lockFolder) throws IOException {
+ if (!lockFolder.isDirectory()) {
+ return;
+ }
- var foundInclude = output.getOutput().contains(topLevelNodesHeader);
+ var ownerFiles = lockFolder.listFiles(File::isFile);
+ if (ownerFiles == null) {
+ return;
+ }
- if (!foundInclude) {
- needsLib = true;
- break;
+ for (var ownerFile : ownerFiles) {
+ if (isCacheLockOwnerStale(lockFolder, ownerFile)) {
+ Files.deleteIfExists(ownerFile.toPath());
}
+ }
+ deleteEmptyCacheLock(lockFolder);
+ }
+
+ private static void deleteEmptyCacheLock(File lockFolder) throws IOException {
+ try {
+ Files.deleteIfExists(lockFolder.toPath());
+ } catch (DirectoryNotEmptyException e) {
+ // A replacement owner claimed the lock while stale recovery was in progress.
}
+ }
- if (needsLib) {
- ClavaLog.debug("Could not find system libc/libcxx");
- } else {
- ClavaLog.debug("Detected system's libc and libcxx");
+ /**
+ * A temporary claim on one cache version. Each claim has its own owner marker, so releasing an old claim cannot
+ * remove a newer claim created after stale-lock recovery.
+ */
+ static final class CacheLock implements AutoCloseable {
+
+ private final File lockFolder;
+ private final File ownerFile;
+
+ private CacheLock(File lockFolder, File ownerFile) {
+ this.lockFolder = lockFolder;
+ this.ownerFile = ownerFile;
}
- return !needsLib;
+ File ownerFile() {
+ return ownerFile;
+ }
+ @Override
+ public void close() {
+ try {
+ Files.deleteIfExists(ownerFile.toPath());
+ deleteEmptyCacheLock(lockFolder);
+ } catch (IOException e) {
+ SpecsLogs.warn("Could not remove temporary clang-dumper cache lock '" + lockFolder + "'", e);
+ }
+ }
}
- private static ProcessOutputAsString runClangAstDumper(File clangExecutable, File testFile) {
- List arguments = Arrays.asList(clangExecutable.getAbsolutePath(), testFile.getAbsolutePath(), "--");
- return SpecsSystem.runProcess(arguments, true, false);
+ private static boolean hasExpectedSha256(File file, ClangDumperManifestAsset asset) {
+ return asset.sha256().equalsIgnoreCase(calculateSha256(file));
}
- public File getBuiltinCudaLib() {
- return cudalibFolder.get();
+ private static String calculateSha256(File file) {
+ try {
+ var digest = MessageDigest.getInstance("SHA-256");
+ try (var inputStream = new DigestInputStream(Files.newInputStream(file.toPath()), digest)) {
+ inputStream.transferTo(OutputStream.nullOutputStream());
+ }
+
+ return HexFormat.of().formatHex(digest.digest());
+ } catch (IOException | NoSuchAlgorithmException e) {
+ throw new RuntimeException("Could not calculate SHA-256 for file '" + file + "'", e);
+ }
}
- private File prepareBuiltinCudaLib() {
- var fileResource = CLANG_AST_RESOURCES.get(ClangAstFileResource.CUDA_LIB);
- var resourceFolder = getClangResourceFolder();
- var cudalibFolder = SpecsIo.mkdir(new File(resourceFolder, "cudalib"));
+ private static String getManifestPlatform() {
+ var platform = SupportedPlatform.getCurrentPlatform();
- // Download includes zips, check if any of them is new
- ResourceWriteData zipFile = fileResource.writeVersioned(resourceFolder, ClangResources.class);
+ if (platform.isLinux()) {
+ return "linux";
+ }
- // If a new file has been written, delete includes folder, and extract all zips again
- // Extracting all because zips might have several folders and we are not determining which should be updated
- if (zipFile.isNewFile()) {
- // Clean folder
- SpecsIo.deleteFolderContents(cudalibFolder);
+ if (platform.isMacOs()) {
+ return "macos";
+ }
- // Extract zip
- SpecsIo.extractZip(zipFile.getFile(), cudalibFolder);
+ if (platform.isWindows()) {
+ return "windows";
}
- // Returnb cuda lib folder
- return cudalibFolder;
+ throw new RuntimeException("Unsupported platform: " + platform);
}
+
+ private static String getManifestArch(String platform) {
+ var osArch = System.getProperty("os.arch").toLowerCase();
+
+ if (osArch.equals("amd64") || osArch.equals("x86_64")) {
+ return platform.equals("windows") ? "x86_64" : "x64";
+ }
+
+ if (osArch.equals("aarch64") || osArch.equals("arm64")) {
+ return "arm64";
+ }
+
+ throw new RuntimeException("Unsupported architecture for clang-dumper: " + osArch);
+ }
+
}
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/SupportedPlatform.java b/ClangAstParser/src/pt/up/fe/specs/clang/SupportedPlatform.java
index 094c8662e..0564571a8 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/SupportedPlatform.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/SupportedPlatform.java
@@ -74,10 +74,6 @@ private static SupportedPlatform calculateCurrentPlatform() {
// Linux
if (SpecsPlatforms.isLinux()) {
- if (SpecsPlatforms.isLinuxArm()) {
- throw new RuntimeException("ARM-based platforms are not currently supported");
- }
-
return LINUX;
}
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/codeparser/CodeParser.java b/ClangAstParser/src/pt/up/fe/specs/clang/codeparser/CodeParser.java
index 5c154cd24..f7441ed77 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/codeparser/CodeParser.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/codeparser/CodeParser.java
@@ -45,7 +45,7 @@ public abstract class CodeParser extends ADataClass {
.setLabel("CUDA Path (empty: uses system installed; : uses builtin version)")
.setDefaultString("");
public static final DataKey DUMPER_FOLDER = KeyFactory.folder("dumperFolder")
- .setLabel("The work folder for the clang-dumper. Clava will look for it in this folder, and if not found, will download it. If not set, a temporary folder will be used.")
+ .setLabel("The base cache folder for the clang-dumper. Clava stores each dumper release in a versioned subfolder and downloads it if not found. If not set, a temporary folder will be used.")
.setDefault(ClangResources::getDefaultTempFolder);
/**
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/codeparser/ParallelCodeParser.java b/ClangAstParser/src/pt/up/fe/specs/clang/codeparser/ParallelCodeParser.java
index 399d91621..d16c2ce82 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/codeparser/ParallelCodeParser.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/codeparser/ParallelCodeParser.java
@@ -104,8 +104,6 @@ public App parse(List inputSources, List compilerOptions, ClavaCon
// Standard standard = getStandard(allUserSources.values(), options);
// config.getTry(ClavaOptions.STANDARD).ifPresent(standard -> arguments.add(standard.getFlag()));
- // Get version for the executable
- String version = options.get(ClangAstKeys.CLANGAST_VERSION);
// System.out.println("PARALLEL OPTIONS: " + options);
// Prepare resources before execution
// ClangResources clangResources = new ClangResources(get(SHOW_CLANG_DUMP));
@@ -117,7 +115,7 @@ public App parse(List inputSources, List compilerOptions, ClavaCon
ClavaLog.debug(() -> "In Linux, ClangAstDumper is a plugin. LIBC_CXX_MODE is reset to SYSTEM.");
}
- var clangFiles = clangResources.getClangFiles(version, get(ClangAstKeys.LIBC_CXX_MODE));
+ var clangFiles = clangResources.getClangFiles(get(ClangAstKeys.LIBC_CXX_MODE));
// File clangExecutable = clangResources.prepareResources(version);
// List builtinIncludes = clangResources.prepareIncludes(clangExecutable,
// get(ClangAstKeys.USE_PLATFORM_INCLUDES));
@@ -375,8 +373,7 @@ private ClangAstData parseSource(File sourceFile, String id, Standard standard,
// Only show output of console after parsing is done, when using parallel parsing
boolean streamConsoleOutput = !get(PARALLEL_PARSING);
- ClangAstDumper clangParser = new ClangAstDumper(streamConsoleOutput, clangExecutable, builtinIncludes,
- this)
+ ClangAstDumper clangParser = new ClangAstDumper(streamConsoleOutput, clangExecutable, builtinIncludes, this)
.setBaseFolder(parsingFolder)
.setSystemIncludesThreshold(get(SYSTEM_INCLUDES_THRESHOLD));
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/dumper/ClangAstDumper.java b/ClangAstParser/src/pt/up/fe/specs/clang/dumper/ClangAstDumper.java
index 0a319a6d8..ba7f29de3 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/dumper/ClangAstDumper.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/dumper/ClangAstDumper.java
@@ -52,6 +52,7 @@
public class ClangAstDumper {
private final static boolean USE_PLUGIN = false;
+ private final static String SYSTEM_HEADER_THRESHOLD_OPTION = "-system-header-threshold=";
public static boolean usePlugin() {
return USE_PLUGIN;
@@ -60,7 +61,7 @@ public static boolean usePlugin() {
private final static String CLANG_DUMP_FILENAME = "clangDump.txt";
private final static String STDERR_DUMP_FILENAME = "stderr.txt";
- private static final List CLANG_AST_DUMPER_TEMP_FILES = Arrays.asList("includes.txt", CLANG_DUMP_FILENAME,
+ private static final List CLANG_AST_DUMPER_TEMP_FILES = List.of("includes.txt", CLANG_DUMP_FILENAME,
// "clavaDump.txt", "nodetypes.txt", "types.txt", "is_temporary.txt", "template_args.txt",
"clavaDump.txt", "nodetypes.txt", "types.txt", "is_temporary.txt",
"omp.txt", "invalid_source.txt", "enum_integer_type.txt", "consumer_order.txt",
@@ -86,7 +87,7 @@ public static List getTempFiles() {
private File clangExecutable;
private List builtinIncludes;
private int systemIncludesThreshold;
- private ClangResources clangResources;
+ private final ClangResources clangResources;
private final CodeParser parserConfig;
@@ -168,7 +169,7 @@ private ClangAstData parsePrivate(File sourceFile, String id, Standard standard,
arguments.add("-Xclang");
arguments.add("-plugin-arg-DumpAst");
arguments.add("-Xclang");
- arguments.add("-system-threshold=" + systemIncludesThreshold);
+ arguments.add(SYSTEM_HEADER_THRESHOLD_OPTION + systemIncludesThreshold);
} else {
arguments.add(clangExecutable.getAbsolutePath());
@@ -176,7 +177,7 @@ private ClangAstData parsePrivate(File sourceFile, String id, Standard standard,
arguments.add("-id=" + id);
- arguments.add("-system-header-threshold=" + systemIncludesThreshold);
+ arguments.add(SYSTEM_HEADER_THRESHOLD_OPTION + systemIncludesThreshold);
arguments.add("--");
}
@@ -193,8 +194,10 @@ private ClangAstData parsePrivate(File sourceFile, String id, Standard standard,
arguments.add("-std=cl2.0");
}
// Set standard to CUDA
- else if (isCuda && !standard.isCuda()) {
- arguments.add("-std=cuda");
+ else if (isCuda) {
+ // The LLVM 18 driver bundled with clang-dumper rejects '-std=cuda'. The .cu extension already
+ // selects CUDA mode, so use a C++ standard for host-side parsing.
+ arguments.add(standard.isCxx() ? standard.getFlag() : Standard.CXX17.getFlag());
} else {
arguments.add(standard.getFlag());
}
@@ -223,24 +226,19 @@ else if (isCuda && !standard.isCuda()) {
}
// If CUDA, add corresponding flags
else if (isCuda) {
- if (SpecsPlatforms.isWindows()) {
- ClavaLog.info("CUDA parsing is not supported in Windows, run at your own risk");
- arguments.addAll(Arrays.asList("-fms-compatibility", "-D_MSC_VER", "-D_LIBCPP_MSVCRT"));
+ if (!SpecsPlatforms.isLinux()) {
+ ClavaLog.info("We only officially support CUDA parsing in Linux, run at your own risk");
+ arguments.add("-fms-compatibility");
+ if (SpecsPlatforms.isWindows()) {
+ arguments.add("-D_MSC_VER");
+ arguments.add("-D_LIBCPP_MSVCRT");
+ }
}
arguments.add("--cuda-gpu-arch=" + parserConfig.get(CodeParser.CUDA_GPU_ARCH));
var cudaPath = parserConfig.get(CodeParser.CUDA_PATH);
- if (!cudaPath.isBlank()) {
-
- // Check if should use built-in CUDA lib
- File cudaFolder = cudaPath.toUpperCase().equals(CodeParser.getBuiltinOption())
- ? clangResources.getBuiltinCudaLib()
- : SpecsIo.existingFolder(cudaPath);
-
- ClavaLog.debug("Setting --cuda-path to folder '" + cudaFolder.getAbsolutePath() + "'");
- arguments.add("--cuda-path=" + cudaFolder.getAbsolutePath());
- }
+ addCudaPathArgument(arguments, cudaPath);
// Since we only need parsing, enable host-only
// Can help with errors such as "__float128 is not supported on this target"
@@ -299,7 +297,7 @@ else if (SourceType.isHeader(sourceFile)) {
workingFolders.add(lastWorkingFolder);
output = SpecsSystem.runProcess(arguments, lastWorkingFolder,
- inputStream -> this.processOutput(sourceFile, inputStream),
+ this::processOutput,
inputStream -> this.processStdErr(inputStream, config.get(ClavaNode.CONTEXT)));
if (output.isError()) {
@@ -336,7 +334,24 @@ else if (SourceType.isHeader(sourceFile)) {
return parsedData;
}
- private String processOutput(File sourceFile, InputStream inputStream) {
+ private void addCudaPathArgument(List arguments, String cudaPath) {
+ var useBuiltinCudaLib = cudaPath.toUpperCase().equals(CodeParser.getBuiltinOption());
+
+ if (useBuiltinCudaLib) {
+ File cudaFolder = clangResources.getBuiltinCudaLib();
+
+ ClavaLog.debug("Setting --cuda-path to built-in CUDA folder '"
+ + cudaFolder.getAbsolutePath() + "'");
+ arguments.add("--cuda-path=" + cudaFolder.getAbsolutePath());
+ } else if (!cudaPath.isBlank()) {
+ File cudaFolder = SpecsIo.existingFolder(cudaPath);
+
+ ClavaLog.debug("Setting --cuda-path to folder '" + cudaFolder.getAbsolutePath() + "'");
+ arguments.add("--cuda-path=" + cudaFolder.getAbsolutePath());
+ }
+ }
+
+ private String processOutput(InputStream inputStream) {
StringBuilder output = new StringBuilder();
try (LineStream lines = LineStream.newInstance(inputStream, null)) {
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/dumper/ClangAstParser.java b/ClangAstParser/src/pt/up/fe/specs/clang/dumper/ClangAstParser.java
index 23d605105..ccfb24385 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/dumper/ClangAstParser.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/dumper/ClangAstParser.java
@@ -147,7 +147,6 @@ public TranslationUnit parseTu(File sourceFile) {
}
ClavaNode parsedNode = data.get(ClangAstData.CLAVA_NODES).get(topLevelTypeId);
Objects.requireNonNull(parsedNode, () -> "No node for type '" + topLevelTypeId + "'");
-
}
// Parse top-level attributes
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/ClavaNodeParser.java b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/ClavaNodeParser.java
index 0cfe3473f..19d3bfbcf 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/ClavaNodeParser.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/ClavaNodeParser.java
@@ -171,10 +171,6 @@ private ClavaNode parseNode(String nodeId, String classname, ClangAstData data,
if (nodeData == null) {
throw new RuntimeException("No ClavaData/DataStore for node '" + nodeId + "' (classname: " + classname
+ "), data dumper is not being called (linestream index '" + lineStream.getLastLineIndex() + "')");
- // if (debug)
- // SpecsLogs.msgInfo("No ClavaData for node '" + nodeId + "' (classname: " + classname
- // + "), data dumper is not being called");
- // return new UnsupportedNode(classname, ClavaData.empty(), Collections.emptyList());
}
// Get corresponding ClavaNode class
@@ -240,8 +236,7 @@ private ClavaNode parseNode(String nodeId, String classname, ClangAstData data,
int index = i;
Objects.requireNonNull(child,
() -> "Did not find ClavaNode for child with index '" + index + "' and id '" + childId
- + "' when parsing "
- + clavaNodeClass.getSimpleName() + " -> " + nodeData);
+ + "' when parsing " + clavaNodeClass.getSimpleName() + " -> " + nodeData);
child = processChild(child, clavaNodeClass, data);
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/ClavaNodes.java b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/ClavaNodes.java
index 964fe3889..30438176c 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/ClavaNodes.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/ClavaNodes.java
@@ -148,9 +148,9 @@ public void queueSetNode(DataClass> data, DataKey key
+ "', if node can be null, use queueOptional instead. Node data:\n" + data);
}
- // Get node
- ClavaNode node = get(nodeId);
-
+ ClavaNode node = Objects.requireNonNull(clavaNodes.get(nodeId),
+ () -> "Could not resolve required node '" + nodeId + "' for key '" + key.getName()
+ + "'. Node data:\n" + data);
Class valueClass = key.getValueClass();
ClavaNode adaptedNode = adaptNode(node, valueClass);
@@ -199,8 +199,11 @@ public void queueSetOptionalNode(DataClass> data, DataKe
Runnable nodeToAdd = () -> {
+ @SuppressWarnings("unchecked")
Optional value = isNullId(nodeId) ? Optional.empty()
- : key.getValueClass().cast(getOptional(nodeId));
+ : Optional.of((T) Objects.requireNonNull(clavaNodes.get(nodeId),
+ () -> "Could not resolve optional node '" + nodeId + "' for key '" + key.getName()
+ + "'. Node data:\n" + data));
data.set(key, value);
};
@@ -213,7 +216,10 @@ public void queueSetNullableNode(DataClass> data, DataKe
Runnable nodeToAdd = () -> {
- ClavaNode value = isNullId(nodeId) ? getNullNodeType(nodeId).newNullNode(factory) : get(nodeId);
+ ClavaNode value = isNullId(nodeId) ? getNullNodeType(nodeId).newNullNode(factory)
+ : Objects.requireNonNull(clavaNodes.get(nodeId),
+ () -> "Could not resolve nullable node '" + nodeId + "' for key '" + key.getName()
+ + "'. Node data:\n" + data);
data.set(key, key.getValueClass().cast(value));
};
@@ -227,7 +233,11 @@ public void queueSetNodeList(DataClass> data, DataKey {
@SuppressWarnings("unchecked") // If the nodes exist, they should be of the requested type
- List nodes = nodeIds.stream().map(id -> (T) get(id)).collect(Collectors.toList());
+ List nodes = nodeIds.stream()
+ .map(id -> (T) Objects.requireNonNull(clavaNodes.get(id),
+ () -> "Could not resolve node '" + id + "' in list for key '" + key.getName()
+ + "'. Node data:\n" + data))
+ .collect(Collectors.toList());
data.set(key, nodes);
};
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/LanguageParser.java b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/LanguageParser.java
index e366767bc..3524f18ba 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/LanguageParser.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/LanguageParser.java
@@ -55,6 +55,7 @@ public void apply(LineStream lineStream, ClangAstData data) {
.set(Language.C_PLUS_PLUS_17, LineStreamParsers.oneOrZero(lineStream))
.set(Language.C_PLUS_PLUS_20, LineStreamParsers.oneOrZero(lineStream))
.set(Language.C_PLUS_PLUS_23, LineStreamParsers.oneOrZero(lineStream))
+ .set(Language.C_PLUS_PLUS_26, LineStreamParsers.oneOrZero(lineStream))
.set(Language.HAS_DIGRAPHS, LineStreamParsers.oneOrZero(lineStream))
.set(Language.IS_GNU, LineStreamParsers.oneOrZero(lineStream))
.set(Language.HEX_FLOATS, LineStreamParsers.oneOrZero(lineStream))
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/ExprDataParser.java b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/ExprDataParser.java
index 3687d9e62..858213516 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/ExprDataParser.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/ExprDataParser.java
@@ -119,7 +119,7 @@ public static DataStore parseCharacterLiteralData(LineStream lines, ClangAstData
DataStore data = parseLiteralData(lines, dataStore);
data.add(CharacterLiteral.VALUE, LineStreamParsers.longInt(lines));
- data.add(CharacterLiteral.KIND, LineStreamParsers.enumFromInt(CharacterKind.getEnumHelper(), lines));
+ data.add(CharacterLiteral.KIND, LineStreamParsers.enumFromName(CharacterKind.getEnumHelper(), lines));
return data;
}
diff --git a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/TypeDataParser.java b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/TypeDataParser.java
index 2dbe71624..48d743ff2 100644
--- a/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/TypeDataParser.java
+++ b/ClangAstParser/src/pt/up/fe/specs/clang/parsers/data/TypeDataParser.java
@@ -366,7 +366,7 @@ public static DataStore parseUnaryTransformTypeData(LineStream lines, ClangAstDa
DataStore data = parseTypeData(lines, parserData);
data.add(UnaryTransformType.KIND, LineStreamParsers.enumFromName(UnaryTransformTypeKind.class, lines));
- parserData.getClavaNodes().queueSetNode(data, UnaryTransformType.UNDERLYING_TYPE, lines.nextLine());
+ parserData.getClavaNodes().queueSetOptionalNode(data, UnaryTransformType.UNDERLYING_TYPE, lines.nextLine());
parserData.getClavaNodes().queueSetNode(data, UnaryTransformType.BASE_TYPE, lines.nextLine());
return data;
diff --git a/ClangAstParser/test-resources/cxx/dependent_scope_decl_ref_expr.cpp.txt b/ClangAstParser/test-resources/cxx/dependent_scope_decl_ref_expr.cpp.txt
index cce55922e..5b84d42dc 100644
--- a/ClangAstParser/test-resources/cxx/dependent_scope_decl_ref_expr.cpp.txt
+++ b/ClangAstParser/test-resources/cxx/dependent_scope_decl_ref_expr.cpp.txt
@@ -7,5 +7,6 @@ void compute_boundaries(FloatType value) {
// value = 0.F * 2^(1 - bias) = ( F) * 2^(1 - bias - (p-1))
// If v is normalized:
// value = 1.F * 2^(E - bias) = (2^(p-1) + F) * 2^(E - bias - (p-1))
- static_assert(std::numeric_limits::is_iec559, "internal error: dtoa_short requires an IEEE-754 floating-point implementation");
+ static_assert(std::numeric_limits::is_iec559, "internal error: dtoa_short requires an IEEE-754 "
+ "floating-point implementation");
}
diff --git a/ClangAstParser/test-resources/cxx/paren_list_initialization.cpp b/ClangAstParser/test-resources/cxx/paren_list_initialization.cpp
new file mode 100644
index 000000000..8b84bb9cf
--- /dev/null
+++ b/ClangAstParser/test-resources/cxx/paren_list_initialization.cpp
@@ -0,0 +1,8 @@
+struct Point {
+ int x;
+ int y;
+};
+
+void test() {
+ Point point(1, 2);
+}
diff --git a/ClangAstParser/test-resources/cxx/source_locations.cpp b/ClangAstParser/test-resources/cxx/source_locations.cpp
new file mode 100644
index 000000000..a80c933e5
--- /dev/null
+++ b/ClangAstParser/test-resources/cxx/source_locations.cpp
@@ -0,0 +1,17 @@
+#define VALUE 7
+#define CAT_IMPL(left, right) left##right
+#define CAT(left, right) CAT_IMPL(left, right)
+#define DECL(name) int name = VALUE;
+
+DECL(CAT(macro_, value))
+int ordinary = 0;
+int foobar = 1;
+int pasted_reference = CAT(foo, bar);
+
+namespace std {
+using uint8_t = unsigned char;
+template class vector;
+} // namespace std
+
+template >
+class Holder {};
diff --git a/ClangAstParser/test-resources/cxx/unevaluated_strings.cpp b/ClangAstParser/test-resources/cxx/unevaluated_strings.cpp
new file mode 100644
index 000000000..b808d729b
--- /dev/null
+++ b/ClangAstParser/test-resources/cxx/unevaluated_strings.cpp
@@ -0,0 +1,3 @@
+static_assert(true, "plain message");
+static_assert(true, "line\nbreak");
+static_assert(true, "\u00e9");
diff --git a/ClangAstParser/test-resources/cxx/unevaluated_strings.cpp.txt b/ClangAstParser/test-resources/cxx/unevaluated_strings.cpp.txt
new file mode 100644
index 000000000..b808d729b
--- /dev/null
+++ b/ClangAstParser/test-resources/cxx/unevaluated_strings.cpp.txt
@@ -0,0 +1,3 @@
+static_assert(true, "plain message");
+static_assert(true, "line\nbreak");
+static_assert(true, "\u00e9");
diff --git a/ClangAstParser/test/pt/up/fe/specs/clang/ClangResourcesTest.java b/ClangAstParser/test/pt/up/fe/specs/clang/ClangResourcesTest.java
new file mode 100644
index 000000000..7da6ca019
--- /dev/null
+++ b/ClangAstParser/test/pt/up/fe/specs/clang/ClangResourcesTest.java
@@ -0,0 +1,672 @@
+/**
+ * Copyright 2026 SPeCS.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package pt.up.fe.specs.clang;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assumptions.assumeTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import pt.up.fe.specs.clang.ClangAstWebResource.LocalBuild;
+import pt.up.fe.specs.clang.ClangAstWebResource.Release;
+import pt.up.fe.specs.clang.codeparser.CodeParser;
+import pt.up.fe.specs.clang.parsers.TopLevelNodesParser;
+
+public class ClangResourcesTest {
+
+ private static final Duration PROCESS_TIMEOUT = Duration.ofSeconds(10);
+
+ @TempDir
+ Path tempFolder;
+
+ @Test
+ public void releaseTagIsParsedAsRelease() {
+ var release = assertInstanceOf(Release.class, ClangAstWebResource.parseDumperSource("v16.0.5_3"));
+
+ assertEquals("v16.0.5_3", release.tag());
+ }
+
+ @Test
+ public void absolutePathIsParsedAsLocalBuild() {
+ var localBuild = assertInstanceOf(LocalBuild.class,
+ ClangAstWebResource.parseDumperSource(tempFolder.toString()));
+
+ assertEquals(tempFolder.toFile(), localBuild.folder());
+ }
+
+ @Test
+ public void relativePathIsRejected() {
+ assertThrows(RuntimeException.class,
+ () -> ClangAstWebResource.parseDumperSource("../clang-dumper/build"));
+ }
+
+ @Test
+ public void localBuildSelectsExpectedTool() throws IOException {
+ var toolName = SupportedPlatform.getCurrentPlatform().isWindows() ? "tool.exe" : "tool";
+ var tool = tempFolder.resolve(toolName).toFile();
+ assertTrue(tool.createNewFile());
+
+ assertEquals(tool, ClangResources.getLocalExecutable(tempFolder.toFile()));
+ }
+
+ @Test
+ public void localBuildRequiresExpectedTool() {
+ assertThrows(RuntimeException.class, () -> ClangResources.getLocalExecutable(tempFolder.toFile()));
+ }
+
+ @Test
+ public void includesCacheValidationOnlyChecksRequiredFiles() throws IOException {
+ var includesFolder = tempFolder.resolve("includes");
+ assertFalse(ClangResources.isIncludesCacheValid(includesFolder.toFile()));
+
+ Files.createDirectory(includesFolder);
+ assertFalse(ClangResources.isIncludesCacheValid(includesFolder.toFile()));
+
+ Files.createDirectories(includesFolder.resolve("builtin"));
+ Files.writeString(includesFolder.resolve("entrypoints.txt"), "builtin\n");
+ Files.writeString(includesFolder.resolve("builtin/header.h"), "original");
+ Files.writeString(includesFolder.resolve("unexpected.txt"), "extra");
+
+ assertTrue(ClangResources.isIncludesCacheValid(includesFolder.toFile()));
+
+ Files.writeString(includesFolder.resolve("builtin/header.h"), "modified");
+ assertTrue(ClangResources.isIncludesCacheValid(includesFolder.toFile()));
+ }
+
+ @Test
+ public void staleCacheCleanupSkipsLockedVersions() throws IOException {
+ var currentVersion = Files.createDirectory(tempFolder.resolve("current")).toFile();
+ var staleVersion = Files.createDirectory(tempFolder.resolve("stale")).toFile();
+ Files.writeString(staleVersion.toPath().resolve("last-used.txt"),
+ Instant.now().minus(Duration.ofDays(61)).toString());
+
+ var parser = CodeParser.newInstance();
+ parser.set(CodeParser.DUMPER_FOLDER, tempFolder.toFile());
+ var resources = new ClangResources(parser);
+
+ try (var ignored = ClangResources.acquireCacheLock(staleVersion)) {
+
+ resources.deleteStaleVersions(Instant.now(), currentVersion);
+ assertTrue(staleVersion.isDirectory());
+ }
+
+ assertFalse(ClangResources.getCacheLockFolder(staleVersion).exists());
+
+ resources.deleteStaleVersions(Instant.now(), currentVersion);
+ assertFalse(staleVersion.exists());
+ }
+
+ @Test
+ public void cacheLockSerializesConcurrentAcquisition() throws Exception {
+ var versionFolder = Files.createDirectory(tempFolder.resolve("version")).toFile();
+ var executor = Executors.newSingleThreadExecutor();
+ ClangResources.CacheLock firstLock = ClangResources.acquireCacheLock(versionFolder);
+
+ try {
+ var secondLock = executor.submit(() -> ClangResources.acquireCacheLock(versionFolder));
+ assertFalse(secondLock.isDone());
+
+ firstLock.close();
+ firstLock = null;
+ try (var ignored = secondLock.get(10, TimeUnit.SECONDS)) {
+ assertTrue(versionFolder.isDirectory());
+ }
+ } finally {
+ if (firstLock != null) {
+ firstLock.close();
+ }
+ executor.shutdownNow();
+ executor.awaitTermination(10, TimeUnit.SECONDS);
+ }
+
+ assertFalse(ClangResources.getCacheLockFolder(versionFolder).exists());
+ }
+
+ @Test
+ public void cacheLockSerializesAcquisitionAcrossJvms() throws Exception {
+ var versionFolder = Files.createDirectory(tempFolder.resolve("version")).toFile();
+ var firstAcquired = tempFolder.resolve("first.acquired");
+ var firstRelease = tempFolder.resolve("first.release");
+ var secondAcquired = tempFolder.resolve("second.acquired");
+ var secondRelease = tempFolder.resolve("second.release");
+ var firstLog = tempFolder.resolve("first.log");
+ var secondLog = tempFolder.resolve("second.log");
+
+ Process first = startCacheLockProcess(versionFolder, firstAcquired, firstRelease, firstLog);
+ Process second = null;
+ try {
+ assertTrue(waitForFile(firstAcquired, PROCESS_TIMEOUT));
+
+ second = startCacheLockProcess(versionFolder, secondAcquired, secondRelease, secondLog);
+ assertFalse(waitForFile(secondAcquired, Duration.ofMillis(750)),
+ "A second JVM acquired a cache lock that was still held");
+
+ Files.createFile(firstRelease);
+ assertTrue(waitForFile(secondAcquired, PROCESS_TIMEOUT));
+
+ Files.createFile(secondRelease);
+ waitForProcess(first, firstLog);
+ waitForProcess(second, secondLog);
+ } finally {
+ releaseProcess(firstRelease);
+ releaseProcess(secondRelease);
+ stopProcess(first);
+ stopProcess(second);
+ }
+
+ assertFalse(ClangResources.getCacheLockFolder(versionFolder).exists());
+ }
+
+ @Test
+ public void cacheLockAllowsDifferentVersionsToProceedAcrossJvms() throws Exception {
+ var firstVersion = Files.createDirectory(tempFolder.resolve("version-1")).toFile();
+ var secondVersion = Files.createDirectory(tempFolder.resolve("version-2")).toFile();
+ var firstAcquired = tempFolder.resolve("first.acquired");
+ var firstRelease = tempFolder.resolve("first.release");
+ var secondAcquired = tempFolder.resolve("second.acquired");
+ var secondRelease = tempFolder.resolve("second.release");
+ var firstLog = tempFolder.resolve("first.log");
+ var secondLog = tempFolder.resolve("second.log");
+
+ Process first = startCacheLockProcess(firstVersion, firstAcquired, firstRelease, firstLog);
+ Process second = null;
+ try {
+ assertTrue(waitForFile(firstAcquired, PROCESS_TIMEOUT));
+
+ second = startCacheLockProcess(secondVersion, secondAcquired, secondRelease, secondLog);
+ assertTrue(waitForFile(secondAcquired, PROCESS_TIMEOUT),
+ "A different dumper version was blocked by an unrelated cache lock");
+
+ Files.createFile(firstRelease);
+ Files.createFile(secondRelease);
+ waitForProcess(first, firstLog);
+ waitForProcess(second, secondLog);
+ } finally {
+ releaseProcess(firstRelease);
+ releaseProcess(secondRelease);
+ stopProcess(first);
+ stopProcess(second);
+ }
+
+ assertFalse(ClangResources.getCacheLockFolder(firstVersion).exists());
+ assertFalse(ClangResources.getCacheLockFolder(secondVersion).exists());
+ }
+
+ @Test
+ public void cacheLockRecoversAfterOwningJvmIsTerminated() throws Exception {
+ var versionFolder = Files.createDirectory(tempFolder.resolve("version")).toFile();
+ var firstAcquired = tempFolder.resolve("first.acquired");
+ var firstRelease = tempFolder.resolve("first.release");
+ var secondAcquired = tempFolder.resolve("second.acquired");
+ var secondRelease = tempFolder.resolve("second.release");
+ var firstLog = tempFolder.resolve("first.log");
+ var secondLog = tempFolder.resolve("second.log");
+
+ Process first = startCacheLockProcess(versionFolder, firstAcquired, firstRelease, firstLog);
+ Process second = null;
+ try {
+ assertTrue(waitForFile(firstAcquired, PROCESS_TIMEOUT));
+ first.destroyForcibly();
+ assertTrue(first.waitFor(PROCESS_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS));
+
+ second = startCacheLockProcess(versionFolder, secondAcquired, secondRelease, secondLog);
+ assertTrue(waitForFile(secondAcquired, PROCESS_TIMEOUT),
+ "A cache lock left by a terminated JVM was not recovered");
+
+ Files.createFile(secondRelease);
+ waitForProcess(second, secondLog);
+ } finally {
+ releaseProcess(firstRelease);
+ releaseProcess(secondRelease);
+ stopProcess(first);
+ stopProcess(second);
+ }
+
+ assertFalse(ClangResources.getCacheLockFolder(versionFolder).exists());
+ }
+
+ @Test
+ public void cacheLockDoesNotTreatLiveOwnerAsStaleWhenDirectoryIsOld() throws Exception {
+ var versionFolder = Files.createDirectory(tempFolder.resolve("version")).toFile();
+ var lockFolder = ClangResources.getCacheLockFolder(versionFolder).toPath();
+ var secondAcquired = tempFolder.resolve("second.acquired");
+ var secondRelease = tempFolder.resolve("second.release");
+ var secondLog = tempFolder.resolve("second.log");
+
+ ClangResources.CacheLock first = ClangResources.acquireCacheLock(versionFolder);
+ Process second = null;
+ try {
+ Files.setLastModifiedTime(lockFolder, FileTime.from(Instant.now().minus(Duration.ofHours(1))));
+
+ second = startCacheLockProcess(versionFolder, secondAcquired, secondRelease, secondLog);
+ assertFalse(waitForFile(secondAcquired, Duration.ofMillis(750)),
+ "A live cache-lock owner was incorrectly treated as stale");
+
+ first.close();
+ first = null;
+ assertTrue(waitForFile(secondAcquired, PROCESS_TIMEOUT));
+ Files.createFile(secondRelease);
+ waitForProcess(second, secondLog);
+ } finally {
+ if (first != null) {
+ first.close();
+ }
+ releaseProcess(secondRelease);
+ stopProcess(second);
+ }
+
+ assertFalse(lockFolder.toFile().exists());
+ }
+
+ @Test
+ public void cacheLockUsesProcessStartTimeWhenPidIsStillAlive() throws Exception {
+ var versionFolder = Files.createDirectory(tempFolder.resolve("version")).toFile();
+ var lockFolder = ClangResources.getCacheLockFolder(versionFolder).toPath();
+ Files.createDirectory(lockFolder);
+ assumeTrue(ProcessHandle.current().info().startInstant().isPresent(),
+ "The current platform does not expose process start times");
+ Files.writeString(lockFolder.resolve("owner"),
+ ProcessHandle.current().pid() + System.lineSeparator() + Instant.EPOCH + System.lineSeparator());
+
+ var acquired = tempFolder.resolve("acquired");
+ var release = tempFolder.resolve("release");
+ var log = tempFolder.resolve("child.log");
+ Process child = startCacheLockProcess(versionFolder, acquired, release, log);
+ try {
+ assertTrue(waitForFile(acquired, PROCESS_TIMEOUT),
+ "A lock with a reused PID and a different process start time was not recovered");
+ Files.createFile(release);
+ waitForProcess(child, log);
+ } finally {
+ releaseProcess(release);
+ stopProcess(child);
+ }
+
+ assertFalse(lockFolder.toFile().exists());
+ }
+
+ @Test
+ public void cacheLockRecoversAnOldOwnerlessLock() throws Exception {
+ var versionFolder = Files.createDirectory(tempFolder.resolve("version")).toFile();
+ var lockFolder = ClangResources.getCacheLockFolder(versionFolder).toPath();
+ Files.createDirectory(lockFolder);
+ Files.setLastModifiedTime(lockFolder, FileTime.from(Instant.now().minus(Duration.ofHours(1))));
+
+ var acquired = tempFolder.resolve("acquired");
+ var release = tempFolder.resolve("release");
+ var log = tempFolder.resolve("child.log");
+ Process child = startCacheLockProcess(versionFolder, acquired, release, log);
+ try {
+ assertTrue(waitForFile(acquired, PROCESS_TIMEOUT), "An old ownerless cache lock was not recovered");
+ Files.createFile(release);
+ waitForProcess(child, log);
+ } finally {
+ releaseProcess(release);
+ stopProcess(child);
+ }
+
+ assertFalse(lockFolder.toFile().exists());
+ }
+
+ @Test
+ public void cacheLockDoesNotStealARecentOwnerlessLock() throws Exception {
+ var versionFolder = Files.createDirectory(tempFolder.resolve("version")).toFile();
+ var lockFolder = ClangResources.getCacheLockFolder(versionFolder).toPath();
+ Files.createDirectory(lockFolder);
+
+ var acquired = tempFolder.resolve("acquired");
+ var release = tempFolder.resolve("release");
+ var log = tempFolder.resolve("child.log");
+ Process child = startCacheLockProcess(versionFolder, acquired, release, log);
+ try {
+ assertFalse(waitForFile(acquired, Duration.ofMillis(750)),
+ "A lock without owner metadata was stolen before its claim was old enough");
+
+ Files.delete(lockFolder);
+ assertTrue(waitForFile(acquired, PROCESS_TIMEOUT));
+ Files.createFile(release);
+ waitForProcess(child, log);
+ } finally {
+ releaseProcess(release);
+ stopProcess(child);
+ }
+
+ assertFalse(lockFolder.toFile().exists());
+ }
+
+ @Test
+ public void staleCacheCleanupSkipsLockedVersionAcrossJvms() throws Exception {
+ var currentVersion = Files.createDirectory(tempFolder.resolve("current")).toFile();
+ var staleVersion = Files.createDirectory(tempFolder.resolve("stale")).toFile();
+ Files.writeString(staleVersion.toPath().resolve("last-used.txt"),
+ Instant.now().minus(Duration.ofDays(61)).toString());
+
+ var acquired = tempFolder.resolve("acquired");
+ var release = tempFolder.resolve("release");
+ var log = tempFolder.resolve("child.log");
+ Process child = startCacheLockProcess(staleVersion, acquired, release, log);
+ try {
+ assertTrue(waitForFile(acquired, PROCESS_TIMEOUT));
+
+ var parser = CodeParser.newInstance();
+ parser.set(CodeParser.DUMPER_FOLDER, tempFolder.toFile());
+ new ClangResources(parser).deleteStaleVersions(Instant.now(), currentVersion);
+ assertTrue(staleVersion.isDirectory(), "Stale cleanup deleted a version locked by another JVM");
+
+ Files.createFile(release);
+ waitForProcess(child, log);
+ } finally {
+ releaseProcess(release);
+ stopProcess(child);
+ }
+
+ var parser = CodeParser.newInstance();
+ parser.set(CodeParser.DUMPER_FOLDER, tempFolder.toFile());
+ new ClangResources(parser).deleteStaleVersions(Instant.now(), currentVersion);
+ assertFalse(staleVersion.exists());
+ }
+
+ @Test
+ public void releaseResourcesCanBeInitializedBySeparateJvms() throws Exception {
+ var cacheFolder = Files.createDirectory(tempFolder.resolve("cache")).toFile();
+ var firstDone = tempFolder.resolve("first.done");
+ var secondDone = tempFolder.resolve("second.done");
+ var firstLog = tempFolder.resolve("first.log");
+ var secondLog = tempFolder.resolve("second.log");
+
+ Process first = startResourceProcess(cacheFolder, firstDone, firstLog);
+ Process second = startResourceProcess(cacheFolder, secondDone, secondLog);
+ try {
+ waitForProcess(first, firstLog);
+ waitForProcess(second, secondLog);
+ } finally {
+ stopProcess(first);
+ stopProcess(second);
+ }
+
+ var firstExecutable = new File(Files.readString(firstDone).trim());
+ var secondExecutable = new File(Files.readString(secondDone).trim());
+ assertEquals(firstExecutable.getAbsoluteFile(), secondExecutable.getAbsoluteFile());
+ assertTrue(firstExecutable.isFile());
+ assertFalse(cacheFolder.toPath().resolve(ClangAstWebResource.getReleaseTag() + ".cache.lock").toFile().exists());
+ }
+
+ @Test
+ public void cacheLockReleaseDoesNotRemoveAReclaimedLock() throws Exception {
+ var versionFolder = Files.createDirectory(tempFolder.resolve("version")).toFile();
+ var lockFolder = ClangResources.getCacheLockFolder(versionFolder).toPath();
+ ClangResources.CacheLock first = ClangResources.acquireCacheLock(versionFolder);
+
+ Files.writeString(first.ownerFile().toPath(), Long.MAX_VALUE + System.lineSeparator());
+ ClangResources.CacheLock second = ClangResources.acquireCacheLock(versionFolder);
+
+ try {
+ first.close();
+ assertTrue(Files.isDirectory(lockFolder),
+ "A stale lock owner released and deleted a replacement owner's lock");
+ } finally {
+ second.close();
+ }
+ }
+
+ @Test
+ public void cachedUseDoesNotBypassTheCacheLockBeforeUpdatingLastUsed() throws Exception {
+ var parser = CodeParser.newInstance();
+ parser.set(CodeParser.DUMPER_FOLDER, tempFolder.toFile());
+ var resources = new ClangResources(parser);
+ var versionFolder = resources.getClangResourceFolder();
+ var fakeExecutable = Files.createFile(tempFolder.resolve("fake-tool")).toFile();
+ var cache = getClangFilesCache();
+ var cacheKey = getClangFilesCacheKey(resources, LibcMode.SYSTEM);
+ var cachedFiles = new ClangFiles(fakeExecutable, List.of());
+ cache.put(cacheKey, cachedFiles);
+
+ ClangResources.CacheLock lock = ClangResources.acquireCacheLock(versionFolder);
+ var executor = Executors.newSingleThreadExecutor();
+ try {
+ var future = executor.submit(() -> resources.getClangFiles(LibcMode.SYSTEM));
+ assertThrows(TimeoutException.class, () -> future.get(750, TimeUnit.MILLISECONDS),
+ "A cached use updated last-used without coordinating with the cache lock");
+
+ lock.close();
+ lock = null;
+ assertSame(cachedFiles, future.get(PROCESS_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS));
+ } finally {
+ if (lock != null) {
+ lock.close();
+ }
+ executor.shutdownNow();
+ executor.awaitTermination(PROCESS_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+ cache.remove(cacheKey);
+ }
+ }
+
+ @Test
+ public void sameJvmInstancesShareReleaseCacheInitialization() throws Exception {
+ var firstParser = CodeParser.newInstance();
+ firstParser.set(CodeParser.DUMPER_FOLDER, tempFolder.toFile());
+ firstParser.set(CodeParser.CUDA_PATH, CodeParser.getBuiltinOption());
+
+ var secondParser = CodeParser.newInstance();
+ secondParser.set(CodeParser.DUMPER_FOLDER, tempFolder.toFile());
+ secondParser.set(CodeParser.CUDA_PATH, CodeParser.getBuiltinOption());
+
+ var thirdParser = CodeParser.newInstance();
+ thirdParser.set(CodeParser.DUMPER_FOLDER, tempFolder.toFile());
+ thirdParser.set(CodeParser.CUDA_PATH, CodeParser.getBuiltinOption());
+
+ var executor = Executors.newFixedThreadPool(3);
+ try {
+ var first = executor.submit(() -> new ClangResources(firstParser).getClangFiles(LibcMode.SYSTEM));
+ var second = executor.submit(() -> new ClangResources(secondParser).getClangFiles(LibcMode.SYSTEM));
+ var third = executor.submit(
+ () -> new ClangResources(thirdParser).getClangFiles(LibcMode.BUILTIN_AND_LIBC));
+
+ var firstFiles = first.get(PROCESS_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+ var secondFiles = second.get(PROCESS_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+ var thirdFiles = third.get(PROCESS_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+
+ assertSame(firstFiles, secondFiles, "Same-JVM instances did not share the ClangFiles cache entry");
+ assertEquals(firstFiles.clangExecutable().getAbsoluteFile(), thirdFiles.clangExecutable().getAbsoluteFile());
+ assertTrue(firstFiles.clangExecutable().isFile());
+ } finally {
+ executor.shutdownNow();
+ executor.awaitTermination(PROCESS_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+ }
+ }
+
+ private Process startCacheLockProcess(File versionFolder, Path acquired, Path release, Path log)
+ throws IOException {
+
+ var javaExecutable = Path.of(System.getProperty("java.home"), "bin",
+ SupportedPlatform.getCurrentPlatform().isWindows() ? "java.exe" : "java");
+
+ return new ProcessBuilder(
+ javaExecutable.toString(),
+ "-cp",
+ System.getProperty("java.class.path"),
+ CacheLockProcess.class.getName(),
+ versionFolder.getAbsolutePath(),
+ acquired.toAbsolutePath().toString(),
+ release.toAbsolutePath().toString())
+ .redirectErrorStream(true)
+ .redirectOutput(log.toFile())
+ .start();
+ }
+
+ private Process startResourceProcess(File cacheFolder, Path done, Path log) throws IOException {
+ var javaExecutable = Path.of(System.getProperty("java.home"), "bin",
+ SupportedPlatform.getCurrentPlatform().isWindows() ? "java.exe" : "java");
+
+ return new ProcessBuilder(
+ javaExecutable.toString(),
+ "-cp",
+ System.getProperty("java.class.path"),
+ CacheLockProcess.class.getName(),
+ "resources",
+ cacheFolder.getAbsolutePath(),
+ done.toAbsolutePath().toString())
+ .redirectErrorStream(true)
+ .redirectOutput(log.toFile())
+ .start();
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map getClangFilesCache() throws ReflectiveOperationException {
+ var cacheField = ClangResources.class.getDeclaredField("CLANG_FILES_CACHE");
+ cacheField.setAccessible(true);
+ return (Map) cacheField.get(null);
+ }
+
+ private String getClangFilesCacheKey(ClangResources resources, LibcMode libcMode) {
+ var source = ClangAstWebResource.getDumperSource();
+ var sourceKey = source instanceof Release
+ ? source + "_" + resources.getClangResourceFolder().getAbsolutePath()
+ : source.toString();
+ return libcMode.name() + "_false_" + sourceKey;
+ }
+
+ private boolean waitForFile(Path file, Duration timeout) throws InterruptedException {
+ var deadline = System.nanoTime() + timeout.toNanos();
+ do {
+ if (Files.isRegularFile(file)) {
+ return true;
+ }
+
+ Thread.sleep(10);
+ } while (System.nanoTime() < deadline);
+
+ return Files.isRegularFile(file);
+ }
+
+ private void waitForProcess(Process process, Path log) throws Exception {
+ assertTrue(process.waitFor(PROCESS_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS),
+ () -> "Child JVM did not finish. Output: " + readLog(log));
+ assertEquals(0, process.exitValue(), () -> "Child JVM failed. Output: " + readLog(log));
+ }
+
+ private String readLog(Path log) {
+ try {
+ return Files.readString(log);
+ } catch (IOException e) {
+ return "";
+ }
+ }
+
+ private void releaseProcess(Path release) throws IOException {
+ if (release != null && !Files.exists(release)) {
+ Files.createFile(release);
+ }
+ }
+
+ private void stopProcess(Process process) throws InterruptedException {
+ if (process == null || !process.isAlive()) {
+ return;
+ }
+
+ process.destroyForcibly();
+ process.waitFor(PROCESS_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
+ }
+
+ public static final class CacheLockProcess {
+
+ private CacheLockProcess() {
+ }
+
+ public static void main(String[] args) throws Exception {
+ if (args[0].equals("resources")) {
+ var parser = CodeParser.newInstance();
+ parser.set(CodeParser.DUMPER_FOLDER, new File(args[1]));
+ var clangFiles = new ClangResources(parser).getClangFiles(LibcMode.SYSTEM);
+ Files.writeString(Path.of(args[2]), clangFiles.clangExecutable().getAbsolutePath());
+ return;
+ }
+
+ var versionFolder = new File(args[0]);
+ var acquired = Path.of(args[1]);
+ var release = Path.of(args[2]);
+
+ try (var ignored = ClangResources.acquireCacheLock(versionFolder)) {
+ Files.writeString(acquired, Long.toString(ProcessHandle.current().pid()));
+ while (!Files.exists(release)) {
+ Thread.sleep(10);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void builtinCudaIncludesAreAvailableWithSystemLibc() {
+ var parser = CodeParser.newInstance();
+ parser.set(CodeParser.CUDA_PATH, CodeParser.getBuiltinOption());
+
+ var clangFiles = new ClangResources(parser).getClangFiles(LibcMode.SYSTEM);
+ var hasCudaWrapper = clangFiles.builtinIncludes().stream()
+ .map(folder -> new File(folder, "__clang_cuda_runtime_wrapper.h"))
+ .anyMatch(File::isFile);
+
+ assertTrue(hasCudaWrapper, "Built-in CUDA must provide Clang's CUDA runtime wrapper independently of libc");
+ }
+
+ @Test
+ public void builtinCudaArchiveHasCanonicalInstallationLayout() {
+ var parser = CodeParser.newInstance();
+ parser.set(CodeParser.DUMPER_FOLDER, tempFolder.toFile());
+ parser.set(CodeParser.CUDA_PATH, CodeParser.getBuiltinOption());
+
+ var cudaFolder = new ClangResources(parser).getBuiltinCudaLib();
+
+ assertEquals(tempFolder.resolve("cuda/cudalib").toFile().getAbsolutePath(),
+ cudaFolder.getAbsolutePath());
+ assertTrue(new File(cudaFolder, "include/cuda.h").isFile());
+ assertTrue(new File(cudaFolder, "include/cuda_runtime.h").isFile());
+ assertTrue(new File(cudaFolder, "nvvm/libdevice/libdevice.10.bc").isFile());
+ }
+
+ @Test
+ public void libcDetectionIsScopedToTheExecutable() throws IOException {
+ assumeTrue(!SupportedPlatform.getCurrentPlatform().isWindows(), "Shell fixtures require a Unix executable");
+
+ var systemLibcDumper = tempFolder.resolve("system-libc-dumper");
+ Files.writeString(systemLibcDumper,
+ "#!/bin/sh\nprintf '%s\\n' '" + TopLevelNodesParser.getTopLevelNodesHeader() + "'\n");
+ assertTrue(systemLibcDumper.toFile().setExecutable(true));
+
+ var builtinLibcDumper = tempFolder.resolve("builtin-libc-dumper");
+ Files.writeString(builtinLibcDumper, "#!/bin/sh\nexit 1\n");
+ assertTrue(builtinLibcDumper.toFile().setExecutable(true));
+
+ assertFalse(ClangResources.useBuiltinLibc(systemLibcDumper.toFile(), LibcMode.AUTO));
+ assertTrue(ClangResources.useBuiltinLibc(builtinLibcDumper.toFile(), LibcMode.AUTO));
+ }
+}
diff --git a/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/CxxCudaTest.java b/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/CxxCudaTest.java
index 1a6b37d3f..2a857db62 100644
--- a/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/CxxCudaTest.java
+++ b/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/CxxCudaTest.java
@@ -15,21 +15,25 @@
import org.junit.jupiter.api.Test;
+import pt.up.fe.specs.clang.ClangAstKeys;
+import pt.up.fe.specs.clang.LibcMode;
import pt.up.fe.specs.clang.parser.CxxCudaTester;
-/**
- * Disabled tests, they are failing in the CI server. Even when passing the --cuda-path built-in library, the parser
- * fails to find the CUDA library.
- *
- * @author JBispo
- *
- */
+/** Verifies built-in CUDA parsing through Clava's historical cudalib archive. */
public class CxxCudaTest {
@Test
public void testAtomicAdd() {
new CxxCudaTester("atomicAdd.cu").test();
}
+ @Test
+ public void testAtomicAddWithSystemLibc() {
+ new CxxCudaTester("atomicAdd.cu")
+ .set(ClangAstKeys.LIBC_CXX_MODE, LibcMode.SYSTEM)
+ .onePass()
+ .test();
+ }
+
@Test
public void testConvolutionCache() {
new CxxCudaTester("convolution_cache.cu").test();
diff --git a/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/CxxTest.java b/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/CxxTest.java
index b8cdfe572..7de8c9042 100644
--- a/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/CxxTest.java
+++ b/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/CxxTest.java
@@ -287,6 +287,13 @@ public void testStrings() {
new CxxTester("strings.cpp").test();
}
+ @Test
+ public void testUnevaluatedStrings() {
+ new CxxTester("unevaluated_strings.cpp")
+ .addFlags("-std=c++26")
+ .test();
+ }
+
// -Xclang-ast-dump-nostdinc-nocudalib-nocudainc--cuda-gpu-arch=sm_30
// "--cuda-device-only"
diff --git a/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/InitializationStyleTest.java b/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/InitializationStyleTest.java
new file mode 100644
index 000000000..5e1f99532
--- /dev/null
+++ b/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/InitializationStyleTest.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright 2026 SPeCS.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package pt.up.fe.specs.clang.parser.tests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import pt.up.fe.specs.clang.codeparser.CodeParser;
+import pt.up.fe.specs.clava.ast.decl.VarDecl;
+import pt.up.fe.specs.clava.ast.decl.enums.InitializationStyle;
+import pt.up.fe.specs.clava.ast.extra.App;
+import pt.up.fe.specs.util.SpecsIo;
+import pt.up.fe.specs.util.SpecsSystem;
+
+public class InitializationStyleTest {
+
+ @TempDir
+ Path tempFolder;
+
+ @Test
+ public void parenthesizedListInitializationKeepsAllArguments() {
+ SpecsSystem.programStandardInit();
+
+ File sourceFile = SpecsIo.resourceCopy("cxx/paren_list_initialization.cpp", tempFolder.toFile(), false, true);
+ App app = CodeParser.newInstance().parse(List.of(sourceFile), List.of("-std=c++20"));
+
+ VarDecl point = app.getDescendants(VarDecl.class).stream()
+ .filter(varDecl -> varDecl.getDeclName().equals("point"))
+ .findFirst()
+ .orElseThrow();
+
+ assertEquals(InitializationStyle.ParenListInit, point.get(VarDecl.INIT_STYLE));
+ assertEquals("Point point(1, 2)", point.getCode());
+ }
+
+ @Test
+ public void javascriptInitializationStyleNamesRemainCompatible() {
+ assertEquals("callinit", InitializationStyle.CALL_INIT.getString());
+ assertEquals("listinit", InitializationStyle.LIST_INIT.getString());
+ }
+}
diff --git a/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/SourceLocationsTest.java b/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/SourceLocationsTest.java
new file mode 100644
index 000000000..c9e24c6c8
--- /dev/null
+++ b/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/SourceLocationsTest.java
@@ -0,0 +1,88 @@
+/**
+ * Copyright 2026 SPeCS.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package pt.up.fe.specs.clang.parser.tests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import java.io.File;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.function.Predicate;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import pt.up.fe.specs.clang.codeparser.CodeParser;
+import pt.up.fe.specs.clava.ClavaNode;
+import pt.up.fe.specs.clava.SourceRange;
+import pt.up.fe.specs.clava.ast.decl.TemplateTypeParmDecl;
+import pt.up.fe.specs.clava.ast.decl.VarDecl;
+import pt.up.fe.specs.clava.ast.extra.App;
+import pt.up.fe.specs.util.SpecsIo;
+import pt.up.fe.specs.util.SpecsSystem;
+
+public class SourceLocationsTest {
+
+ private static final String RESOURCE = "cxx/source_locations.cpp";
+
+ @TempDir
+ Path tempFolder;
+
+ @Test
+ public void sourceLocationsUseRealFileCoordinates() {
+ SpecsSystem.programStandardInit();
+
+ File sourceFile = SpecsIo.resourceCopy(RESOURCE, tempFolder.toFile(), false, true);
+ App app = CodeParser.newInstance().parse(List.of(sourceFile), List.of("-std=c++11"));
+
+ VarDecl ordinary = find(app.getDescendants(VarDecl.class), varDecl -> varDecl.getDeclName().equals("ordinary"));
+ assertRange(ordinary, false, 7, 1, 7, 16);
+
+ VarDecl macro = find(app.getDescendants(VarDecl.class), varDecl -> varDecl.getDeclName().equals("macro_value"));
+ assertRange(macro, true, 6, 1, 6, 24);
+ assertRange(macro.getInit().orElseThrow(), true, 6, 1, 6, 24);
+
+ VarDecl pastedReference = find(app.getDescendants(VarDecl.class),
+ varDecl -> varDecl.getDeclName().equals("pasted_reference"));
+ assertRange(pastedReference.getInit().orElseThrow(), true, 9, 24, 9, 36);
+
+ TemplateTypeParmDecl templateParameter = find(app.getDescendantsAndFields(TemplateTypeParmDecl.class),
+ parameter -> parameter.getDeclName().equals("BinaryType"));
+ assertRange(templateParameter, false, 16, 11, 16, 54);
+ }
+
+ private static T find(List nodes, Predicate predicate) {
+ return nodes.stream()
+ .filter(predicate)
+ .findFirst()
+ .orElseThrow();
+ }
+
+ private static void assertRange(ClavaNode node, boolean isMacro, int startLine, int startColumn, int endLine,
+ int endColumn) {
+
+ SourceRange location = node.getLocation();
+
+ assertEquals("source_locations.cpp", location.getFilename());
+ assertEquals("source_locations.cpp", Path.of(location.getStartFilepath()).getFileName().toString());
+ assertEquals("source_locations.cpp", Path.of(location.getEndFilepath()).getFileName().toString());
+ assertEquals(startLine, location.getStartLine());
+ assertEquals(startColumn, location.getStartCol());
+ assertEquals(endLine, location.getEndLine());
+ assertEquals(endColumn, location.getEndCol());
+ assertEquals(isMacro, node.get(ClavaNode.IS_MACRO));
+ assertFalse(location.toString().contains(""));
+ }
+}
diff --git a/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/UnaryTransformTypeTest.java b/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/UnaryTransformTypeTest.java
new file mode 100644
index 000000000..27d54bd00
--- /dev/null
+++ b/ClangAstParser/test/pt/up/fe/specs/clang/parser/tests/UnaryTransformTypeTest.java
@@ -0,0 +1,85 @@
+/**
+ * Copyright 2026 SPeCS.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package pt.up.fe.specs.clang.parser.tests;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import pt.up.fe.specs.clang.codeparser.CodeParser;
+import pt.up.fe.specs.clava.ast.extra.App;
+import pt.up.fe.specs.clava.ast.type.Type;
+import pt.up.fe.specs.clava.ast.type.UnaryTransformType;
+import pt.up.fe.specs.clava.ast.type.enums.UnaryTransformTypeKind;
+import pt.up.fe.specs.util.SpecsSystem;
+
+public class UnaryTransformTypeTest {
+
+ private static final String SOURCE = """
+ template
+ struct Dependent {
+ using type = __underlying_type(T);
+ };
+
+ enum Resolved { Value };
+ using ResolvedType = __underlying_type(Resolved);
+ """;
+
+ @TempDir
+ Path tempFolder;
+
+ @Test
+ public void dependentTransformsMayHaveNoUnderlyingType() throws IOException {
+ SpecsSystem.programStandardInit();
+
+ File sourceFile = tempFolder.resolve("unary_transform_type.cpp").toFile();
+ Files.writeString(sourceFile.toPath(), SOURCE);
+
+ App app = CodeParser.newInstance().parse(List.of(sourceFile), List.of("-std=c++11"));
+
+ List transforms = app.getDescendantsAndFields(UnaryTransformType.class).stream()
+ .filter(transform -> transform.get(UnaryTransformType.KIND) == UnaryTransformTypeKind.EnumUnderlyingType)
+ .toList();
+
+ assertEquals(2, transforms.size());
+
+ UnaryTransformType dependentTransform = transforms.stream()
+ .filter(transform -> transform.getUnderlyingType().isEmpty())
+ .findFirst()
+ .orElseThrow();
+
+ assertNotNull(dependentTransform.getBaseType());
+ assertEquals(1, dependentTransform.getNodeFields().size());
+ assertTrue(dependentTransform.getNodeFields().contains(dependentTransform.getBaseType()));
+
+ UnaryTransformType resolvedTransform = transforms.stream()
+ .filter(transform -> transform.getUnderlyingType().isPresent())
+ .findFirst()
+ .orElseThrow();
+
+ Type resolvedUnderlyingType = resolvedTransform.getUnderlyingType().orElseThrow();
+ assertNotNull(resolvedTransform.getBaseType());
+ assertTrue(resolvedTransform.getNodeFields().contains(resolvedTransform.getBaseType()));
+ assertTrue(resolvedTransform.getNodeFields().contains(resolvedUnderlyingType));
+ }
+}
diff --git a/ClangAstParser/test/pt/up/fe/specs/clang/parsers/ClavaNodesTest.java b/ClangAstParser/test/pt/up/fe/specs/clang/parsers/ClavaNodesTest.java
new file mode 100644
index 000000000..f4a42736a
--- /dev/null
+++ b/ClangAstParser/test/pt/up/fe/specs/clang/parsers/ClavaNodesTest.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright 2026 SPeCS.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package pt.up.fe.specs.clang.parsers;
+
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+import org.suikasoft.jOptions.Interfaces.DataStore;
+
+import pt.up.fe.specs.clava.ast.expr.Expr;
+import pt.up.fe.specs.clava.context.ClavaContext;
+
+public class ClavaNodesTest {
+
+ @Test
+ public void optionalNodeResolvesPresentNode() {
+ var context = new ClavaContext();
+ var factory = context.getFactory();
+ var clavaNodes = new ClavaNodes(factory);
+ DataStore data = factory.newDataStore(Expr.class);
+ var type = factory.nullType();
+
+ clavaNodes.getNodes().put("type-id", type);
+ clavaNodes.queueSetOptionalNode(data, Expr.TYPE, "type-id");
+ clavaNodes.getQueuedActions().forEach(Runnable::run);
+
+ assertSame(type, data.get(Expr.TYPE).orElseThrow());
+ }
+
+ @Test
+ public void optionalNodeUsesEmptyForExplicitNullId() {
+ var context = new ClavaContext();
+ var factory = context.getFactory();
+ var clavaNodes = new ClavaNodes(factory);
+ DataStore data = factory.newDataStore(Expr.class);
+
+ clavaNodes.queueSetOptionalNode(data, Expr.TYPE, "nullptr_type");
+ clavaNodes.getQueuedActions().forEach(Runnable::run);
+
+ assertTrue(data.get(Expr.TYPE).isEmpty());
+ }
+
+ @Test
+ public void optionalNodeRejectsUnresolvedNodeId() {
+ var context = new ClavaContext();
+ var factory = context.getFactory();
+ var clavaNodes = new ClavaNodes(factory);
+ DataStore data = factory.newDataStore(Expr.class);
+
+ clavaNodes.queueSetOptionalNode(data, Expr.TYPE, "missing-id");
+
+ var exception = assertThrows(NullPointerException.class,
+ () -> clavaNodes.getQueuedActions().forEach(Runnable::run));
+
+ assertTrue(exception.getMessage().contains("Could not resolve optional node 'missing-id'"));
+ assertTrue(exception.getMessage().contains("key 'type'"));
+ }
+}
diff --git a/Clava-JS/api/Joinpoints.ts b/Clava-JS/api/Joinpoints.ts
index 659971877..98997a143 100644
--- a/Clava-JS/api/Joinpoints.ts
+++ b/Clava-JS/api/Joinpoints.ts
@@ -2811,12 +2811,12 @@ export class TypedefType extends Type {
* This and the "type" declaration below.
*/
export const StorageClass = {
- NONE: "none",
- AUTO: "auto",
- EXTERN: "extern",
- PRIVATE_EXTERN: "private_extern",
- REGISTER: "register",
- STATIC: "static",
+ NONE: "NONE",
+ AUTO: "AUTO",
+ EXTERN: "EXTERN",
+ PRIVATE_EXTERN: "PRIVATE_EXTERN",
+ REGISTER: "REGISTER",
+ STATIC: "STATIC",
} as const;
export type StorageClass = typeof StorageClass[keyof typeof StorageClass];
@@ -2826,12 +2826,12 @@ export type StorageClass = typeof StorageClass[keyof typeof StorageClass];
* This and the "type" declaration below.
*/
export const Relation = {
- LE: "le",
- LT: "lt",
- GE: "ge",
- GT: "gt",
- EQ: "eq",
- NE: "ne",
+ LE: "LE",
+ LT: "LT",
+ GE: "GE",
+ GT: "GT",
+ EQ: "EQ",
+ NE: "NE",
} as const;
export type Relation = typeof Relation[keyof typeof Relation];
diff --git a/Clava-JS/code/sideEffects.ts b/Clava-JS/code/sideEffects.ts
index 59d7bf76f..2c62d3bf4 100644
--- a/Clava-JS/code/sideEffects.ts
+++ b/Clava-JS/code/sideEffects.ts
@@ -18,14 +18,14 @@ const datastore = Weaver.getWeaverEngine().getData().get();
datastore.set(CxxWeaverOptions.DISABLE_CLAVA_INFO, true);
datastore.set(
CodeParser.DUMPER_FOLDER,
- new JavaTypes.File(getVersionedCacheDir())
+ new JavaTypes.File(getClangDumperCacheDir())
);
/** Code to obtain temporary folder **/
-function getVersionedCacheDir(): string {
- // Use name+version to isolate different installed versions
- return path.join(getCacheBaseDir(), pkg.name, pkg.version);
+function getClangDumperCacheDir(): string {
+ // The version will be added by the installer to isolate different installed versions
+ return path.join(getCacheBaseDir(), pkg.name, "clang-dumper");
}
function getCacheBaseDir(): string {
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/attr/enums/AttributeKind.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/attr/enums/AttributeKind.java
index 8158847f4..5b0e3d7e7 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/attr/enums/AttributeKind.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/attr/enums/AttributeKind.java
@@ -24,12 +24,19 @@
import pt.up.fe.specs.util.utilities.CachedItems;
public enum AttributeKind implements StringProvider {
-
AddressSpace,
AnnotateType,
+ ArmIn,
+ ArmInOut,
ArmMveStrictPolymorphism,
+ ArmOut,
+ ArmPreserves,
+ ArmStreaming,
+ ArmStreamingCompatible,
BTFTypeTag,
CmseNSCall,
+ HLSLGroupSharedAddressSpace,
+ HLSLParamModifier,
NoDeref,
ObjCGC,
ObjCInertUnsafeUnretained,
@@ -49,15 +56,17 @@ public enum AttributeKind implements StringProvider {
TypeNullable,
TypeNullableResult,
UPtr,
+ WebAssemblyFuncref,
+ CodeAlign,
FallThrough,
Likely,
MustTail,
OpenCLUnrollHint,
- Suppress,
Unlikely,
AlwaysInline,
NoInline,
NoMerge,
+ Suppress,
AArch64SVEPcs,
AArch64VectorPcs,
AMDGPUKernelCall,
@@ -67,6 +76,7 @@ public enum AttributeKind implements StringProvider {
FastCall,
IntelOclBicc,
LifetimeBound,
+ M68kRTD,
MSABI,
NSReturnsRetained,
ObjCOwnership,
@@ -94,6 +104,8 @@ public enum AttributeKind implements StringProvider {
PassObjectSize,
ReleaseHandle,
UseHandle,
+ HLSLSV_DispatchThreadID,
+ HLSLSV_GroupIndex,
AMDGPUFlatWorkGroupSize,
AMDGPUNumSGPR,
AMDGPUNumVGPR,
@@ -116,6 +128,8 @@ public enum AttributeKind implements StringProvider {
ArcWeakrefUnavailable,
ArgumentWithTypeTag,
ArmBuiltinAlias,
+ ArmLocallyStreaming,
+ ArmNew,
Artificial,
AsmLabel,
AssertCapability,
@@ -124,7 +138,9 @@ public enum AttributeKind implements StringProvider {
AssumeAligned,
Assumption,
Availability,
+ AvailableOnlyInDefaultEvalMethod,
BPFPreserveAccessIndex,
+ BPFPreserveStaticOffset,
BTFDeclTag,
Blocks,
Builtin,
@@ -153,6 +169,7 @@ public enum AttributeKind implements StringProvider {
CapturedRecord,
Cleanup,
CmseNSEntry,
+ CodeModel,
CodeSeg,
Cold,
Common,
@@ -163,6 +180,12 @@ public enum AttributeKind implements StringProvider {
ConsumableAutoCast,
ConsumableSetOnRead,
Convergent,
+ CoroDisableLifetimeBound,
+ CoroLifetimeBound,
+ CoroOnlyDestroyWhenComplete,
+ CoroReturnType,
+ CoroWrapper,
+ CountedBy,
DLLExport,
DLLExportStaticLocal,
DLLImport,
@@ -193,7 +216,8 @@ public enum AttributeKind implements StringProvider {
GuardedVar,
HIPManaged,
HLSLNumThreads,
- HLSLSV_GroupIndex,
+ HLSLResource,
+ HLSLResourceBinding,
HLSLShader,
Hot,
IBAction,
@@ -209,6 +233,7 @@ public enum AttributeKind implements StringProvider {
M68kInterrupt,
MIGServerRoutine,
MSAllocator,
+ MSConstexpr,
MSInheritance,
MSNoVTable,
MSP430Interrupt,
@@ -216,6 +241,7 @@ public enum AttributeKind implements StringProvider {
MSVtorDisp,
MaxFieldAlignment,
MayAlias,
+ MaybeUndef,
MicroMips,
MinSize,
MinVectorWidth,
@@ -227,6 +253,7 @@ public enum AttributeKind implements StringProvider {
NSErrorDomain,
NSReturnsAutoreleased,
NSReturnsNotRetained,
+ NVPTXKernel,
Naked,
NoAlias,
NoCommon,
@@ -246,6 +273,7 @@ public enum AttributeKind implements StringProvider {
NoThreadSafetyAnalysis,
NoThrow,
NoUniqueAddress,
+ NoUwtable,
NotTailCalled,
OMPAllocateDecl,
OMPCaptureNoInit,
@@ -288,11 +316,13 @@ public enum AttributeKind implements StringProvider {
PragmaClangRodataSection,
PragmaClangTextSection,
PreferredName,
+ PreferredType,
PtGuardedBy,
PtGuardedVar,
Pure,
RISCVInterrupt,
RandomizeLayout,
+ ReadOnlyPlacement,
Reinitializes,
ReleaseCapability,
ReqdWorkGroupSize,
@@ -313,6 +343,7 @@ public enum AttributeKind implements StringProvider {
SpeculativeLoadHardening,
StandaloneDebug,
StrictFP,
+ StrictGuardStackCheck,
SwiftAsync,
SwiftAsyncError,
SwiftAsyncName,
@@ -320,12 +351,15 @@ public enum AttributeKind implements StringProvider {
SwiftBridge,
SwiftBridgedTypedef,
SwiftError,
+ SwiftImportAsNonGeneric,
+ SwiftImportPropertyAsAccessors,
SwiftName,
SwiftNewType,
SwiftPrivate,
TLSModel,
Target,
TargetClones,
+ TargetVersion,
TestTypestate,
TransparentUnion,
TrivialABI,
@@ -334,6 +368,7 @@ public enum AttributeKind implements StringProvider {
TypeVisibility,
Unavailable,
Uninitialized,
+ UnsafeBufferUsage,
Unused,
Used,
UsingIfExists,
@@ -382,6 +417,8 @@ public enum AttributeKind implements StringProvider {
Overloadable,
RenderScriptKernel,
SwiftObjCMembers,
+ SwiftVersionedAddition,
+ SwiftVersionedRemoval,
Thread,
FirstAttr,
LastAttr,
@@ -398,7 +435,9 @@ public enum AttributeKind implements StringProvider {
FirstInheritableParamAttr,
LastInheritableParamAttr,
FirstParameterABIAttr,
- LastParameterABIAttr;
+ LastParameterABIAttr,
+ FirstHLSLAnnotationAttr,
+ LastHLSLAnnotationAttr;
private static final Lazy> ENUM_HELPER = EnumHelperWithValue
.newLazyHelperWithValue(AttributeKind.class);
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/decl/enums/InitializationStyle.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/decl/enums/InitializationStyle.java
index 21e3022c4..45a8b01f6 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/decl/enums/InitializationStyle.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/decl/enums/InitializationStyle.java
@@ -24,13 +24,15 @@
import pt.up.fe.specs.util.providers.StringProvider;
public enum InitializationStyle implements StringProvider {
- NO_INIT,
CINIT, // C-style initialization with assignment
+ // Keep these explicit names for compatibility: build-interfaces exposes strings to TS, with no TS enum to convert
+ // them to.
CALL_INIT("callinit"), // Call-style initialization (C++98)
- LIST_INIT("listinit"); // Direct list-initialization (C++11)
+ LIST_INIT("listinit"), // Direct list-initialization (C++11)
+ ParenListInit;
private static Lazy> ENUM_HELPER = EnumHelperWithValue
- .newLazyHelperWithValue(InitializationStyle.class, NO_INIT);
+ .newLazyHelperWithValue(InitializationStyle.class);
public static EnumHelperWithValue getHelper() {
return ENUM_HELPER.get();
@@ -56,10 +58,10 @@ public String getCode(VarDecl node) {
switch (this) {
case CINIT:
return cinitCode(node);
- case NO_INIT:
- return "";
case CALL_INIT:
return callInitCode(node);
+ case ParenListInit:
+ return parenListInitCode(node);
case LIST_INIT:
return listInitCode(node);
default:
@@ -98,6 +100,16 @@ private String callInitCode(VarDecl node) {
return "(" + init.getCode() + ")";
}
+ private String parenListInitCode(VarDecl node) {
+ Preconditions.checkArgument(node.getNumChildren() == 1, "Expected one child");
+ ClavaNode init = node.getChild(0);
+
+ Preconditions.checkArgument(init instanceof Expr,
+ "Expected an Expr, got '" + init.getClass().getSimpleName() + "'");
+
+ return init.getCode();
+ }
+
private String listInitCode(VarDecl node) {
// Must be present
Expr initList = node.getInit().get();
@@ -111,4 +123,4 @@ private String listInitCode(VarDecl node) {
// .orElseThrow(() -> new RuntimeException());
}
-}
\ No newline at end of file
+}
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/decl/enums/Linkage.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/decl/enums/Linkage.java
index 6ae4a3394..f8c27a931 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/decl/enums/Linkage.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/decl/enums/Linkage.java
@@ -22,16 +22,17 @@
*
*/
public enum Linkage {
+ Invalid,
/**
* The entity is unique and can only be referred to from within its scope.
*/
- NoLinkage,
+ None,
/**
* the entity can be referred to from within the translation unit, but not other translation units.
*/
- InternalLinkage,
+ Internal,
/**
* External linkage within a unique namespace.
@@ -41,30 +42,23 @@ public enum Linkage {
* namespace, their names are unique to this translation unit, which is equivalent to having internal linkage from
* the code-generation point of view.
*/
- UniqueExternalLinkage,
+ UniqueExternal,
/**
* No linkage according to the standard, but is visible from other translation units because of types defined in a
* inline function.
*/
- VisibleNoLinkage,
-
- /**
- * Internal linkage according to the Modules TS, but can be referred to from other translation units indirectly
- * through inline functions and templates in the module interface.
- *
- */
- ModuleInternalLinkage,
+ VisibleNone,
/**
* Module linkage, which indicates that the entity can be referred to from other translation units within the same
* module, and indirectly from arbitrary other translation units through inline functions and templates in the
* module interface.
*/
- ModuleLinkage,
+ Module,
/**
* The entity can be referred to from other translation units.
*/
- ExternalLinkage;
+ External;
}
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/CXXParenListInitExpr.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/CXXParenListInitExpr.java
new file mode 100644
index 000000000..92f93c195
--- /dev/null
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/CXXParenListInitExpr.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright 2026 SPeCS.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+package pt.up.fe.specs.clava.ast.expr;
+
+import java.util.Collection;
+
+import org.suikasoft.jOptions.Interfaces.DataStore;
+
+import pt.up.fe.specs.clava.ClavaNode;
+
+/**
+ * A C++20 parenthesized list-initialization expression.
+ */
+public class CXXParenListInitExpr extends ParenListExpr {
+
+ public CXXParenListInitExpr(DataStore data, Collection extends ClavaNode> children) {
+ super(data, children);
+ }
+}
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/StringLiteral.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/StringLiteral.java
index fc9b3f556..8937042ae 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/StringLiteral.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/StringLiteral.java
@@ -55,6 +55,12 @@ public StringLiteral(DataStore data, Collection extends ClavaNode> children) {
@Override
public String getLiteral() {
+ // Unevaluated strings can contain source-level details, such as adjacent tokens separated by a line break, that
+ // are lost when using the evaluated byte payload. Keep Clang's validated source spelling.
+ if (get(STRING_KIND) == StringKind.UNEVALUATED) {
+ return super.getLiteral();
+ }
+
// Update: Unfortunately it is not possible to blindly use the source code literal
// For instance, if directives and macros appear in the middle of the literal, they will also appear in the
// generated source code
@@ -90,7 +96,7 @@ private String getStringFromBytes() {
return SpecsStrings.escapeJson(new String(bytes, kind.getCharset()));
}
- // If ASCII, convert each byte directly
+ // Ordinary strings use one-byte characters; convert each byte directly.
if (kind == StringKind.ORDINARY) {
var literal = new StringBuilder();
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/enums/StringKind.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/enums/StringKind.java
index afdcfbb29..8f141a1a5 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/enums/StringKind.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/expr/enums/StringKind.java
@@ -18,12 +18,12 @@
import pt.up.fe.specs.util.exceptions.NotImplementedException;
public enum StringKind {
-
ORDINARY,
WIDE,
UTF8(true),
UTF16(true),
- UTF32(true);
+ UTF32(true),
+ UNEVALUATED;
private final boolean isUTF;
@@ -47,6 +47,8 @@ public String getPrefix() {
return "u";
case UTF32:
return "U";
+ case UNEVALUATED:
+ return "";
default:
throw new NotImplementedException(this);
}
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/extra/data/Language.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/extra/data/Language.java
index b0a37b399..d930ebb62 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/extra/data/Language.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/extra/data/Language.java
@@ -70,6 +70,11 @@ public class Language extends ADataClass {
*/
public static final DataKey C_PLUS_PLUS_23 = KeyFactory.bool("c++23");
+ /**
+ * True if is a C++26 variant (or later).
+ */
+ public static final DataKey C_PLUS_PLUS_26 = KeyFactory.bool("c++26");
+
/**
* True if supports digraphs.
*/
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/type/ElaboratedType.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/type/ElaboratedType.java
index 0772dcace..cb0809429 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/type/ElaboratedType.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/type/ElaboratedType.java
@@ -63,7 +63,6 @@ public void setNamedType(Type namedType) {
@Override
public String getCode(ClavaNode sourceNode, String name) {
-
String code = getKeyword().getCode();
if (!code.isEmpty()) {
code += " ";
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/type/UnaryTransformType.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/type/UnaryTransformType.java
index 24c177369..827d4bf60 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/type/UnaryTransformType.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/type/UnaryTransformType.java
@@ -14,6 +14,7 @@
package pt.up.fe.specs.clava.ast.type;
import java.util.Collection;
+import java.util.Optional;
import org.suikasoft.jOptions.Datakey.DataKey;
import org.suikasoft.jOptions.Datakey.KeyFactory;
@@ -35,7 +36,11 @@ public class UnaryTransformType extends Type {
public final static DataKey KIND = KeyFactory
.enumeration("kind", UnaryTransformTypeKind.class);
- public final static DataKey UNDERLYING_TYPE = KeyFactory.object("underlyingType", Type.class);
+ /**
+ * The transformed type, when Clang has resolved it. Dependent unary
+ * transforms can have no underlying type while still having a base type.
+ */
+ public final static DataKey> UNDERLYING_TYPE = KeyFactory.optional("underlyingType");
public final static DataKey BASE_TYPE = KeyFactory.object("baseType", Type.class);
@@ -49,7 +54,7 @@ public Type getBaseType() {
return get(BASE_TYPE);
}
- public Type getUnderlyingType() {
+ public Optional getUnderlyingType() {
return get(UNDERLYING_TYPE);
}
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/CallingConvention.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/CallingConvention.java
index b5f58e3af..47d8f46fb 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/CallingConvention.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/CallingConvention.java
@@ -18,7 +18,6 @@
import pt.up.fe.specs.util.providers.StringProvider;
public enum CallingConvention implements StringProvider {
-
C,
X86StdCall,
X86FastCall,
@@ -39,7 +38,8 @@ public enum CallingConvention implements StringProvider {
PreserveAll,
AArch64VectorCall,
AArch64SVEPCS,
- AMDGPUKernelCall;
+ AMDGPUKernelCall,
+ M68kRTD;
private static final Lazy> HELPER = EnumHelperWithValue
.newLazyHelperWithValue(CallingConvention.class);
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/ElaboratedTypeKeyword.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/ElaboratedTypeKeyword.java
index b9a3be4ba..3827d3650 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/ElaboratedTypeKeyword.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/ElaboratedTypeKeyword.java
@@ -19,13 +19,13 @@
import pt.up.fe.specs.util.providers.StringProvider;
public enum ElaboratedTypeKeyword implements StringProvider {
- STRUCT,
- INTERFACE,
- UNION,
- CLASS,
- ENUM,
- TYPENAME,
- NONE;
+ Struct,
+ Interface,
+ Union,
+ Class,
+ Enum,
+ Typename,
+ None;
private static final Lazy> HELPER = EnumHelperWithValue
.newLazyHelperWithValue(ElaboratedTypeKeyword.class);
@@ -35,7 +35,7 @@ public static EnumHelperWithValue getHelper() {
}
public String getCode() {
- if (this == NONE) {
+ if (this == None) {
return "";
}
@@ -46,4 +46,4 @@ public String getCode() {
public String getString() {
return SpecsStrings.toCamelCase(name());
}
-}
\ No newline at end of file
+}
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/UnaryTransformTypeKind.java b/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/UnaryTransformTypeKind.java
index 920be2ab4..7710528a1 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/UnaryTransformTypeKind.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/ast/type/enums/UnaryTransformTypeKind.java
@@ -14,8 +14,21 @@
package pt.up.fe.specs.clava.ast.type.enums;
public enum UnaryTransformTypeKind {
-
+ AddLvalueReference,
+ AddPointer,
+ AddRvalueReference,
Decay,
+ MakeSigned,
+ MakeUnsigned,
+ RemoveAllExtents,
+ RemoveConst,
+ RemoveCV,
+ RemoveCVRef,
+ RemoveExtent,
+ RemovePointer,
+ RemoveReference,
+ RemoveRestrict,
+ RemoveVolatile,
EnumUnderlyingType;
}
diff --git a/ClavaAst/src/pt/up/fe/specs/clava/language/Standard.java b/ClavaAst/src/pt/up/fe/specs/clava/language/Standard.java
index f05370f1b..b35aca2dd 100644
--- a/ClavaAst/src/pt/up/fe/specs/clava/language/Standard.java
+++ b/ClavaAst/src/pt/up/fe/specs/clava/language/Standard.java
@@ -33,21 +33,29 @@ public enum Standard implements StringProvider {
C99,
C11,
C17,
+ C18,
+ C23,
GNU90,
GNU99,
GNU11,
GNU17,
+ GNU18,
+ GNU23,
CXX98("c++98", true),
CXX03("c++03", true),
CXX11("c++11", true),
CXX14("c++14", true),
CXX17("c++17", true),
- CXX2A("c++2a", true),
+ CXX20("c++20", true),
+ CXX23("c++23", true),
+ CXX26("c++26", true),
GNUXX98("gnu++98", true),
GNUXX11("gnu++11", true),
GNUXX14("gnu++14", true),
GNUXX17("gnu++17", true),
- GNUXX2A("gnu++2a", true),
+ GNUXX20("gnu++20", true),
+ GNUXX23("gnu++23", true),
+ GNUXX26("gnu++26", true),
OPENCL10("cl1.0"),
OPENCL12("cl1.2"),
OPENCL20("cl2.0"),
@@ -63,8 +71,8 @@ public enum Standard implements StringProvider {
private static final Lazy> ENUM_HELPER = EnumHelperWithValue
.newLazyHelperWithValue(Standard.class);
- private static final Set GNU_STANDARDS = SpecsCollections.asSet(GNU90, GNU99, GNU11, GNUXX98, GNUXX11,
- GNUXX14);
+ private static final Set GNU_STANDARDS = SpecsCollections.asSet(GNU90, GNU99, GNU11, GNU17, GNU18, GNU23,
+ GNUXX98, GNUXX11, GNUXX14, GNUXX17, GNUXX20, GNUXX23, GNUXX26);
public static EnumHelperWithValue getEnumHelper() {
return ENUM_HELPER.get();
diff --git a/ClavaWeaver/resources/clava/test/weaver/cpp/results/TypeTemplate.js.txt b/ClavaWeaver/resources/clava/test/weaver/cpp/results/TypeTemplate.js.txt
index 3030db32d..18167a321 100644
--- a/ClavaWeaver/resources/clava/test/weaver/cpp/results/TypeTemplate.js.txt
+++ b/ClavaWeaver/resources/clava/test/weaver/cpp/results/TypeTemplate.js.txt
@@ -10,4 +10,4 @@ Original template args: int,float
After second arg to double: int,double
After setting with array [double, int]: double,int
After setting typedef_to_change: std::vector::const_iterator
-After setting changed_typedef_type: std::vector::const_iterator
\ No newline at end of file
+After setting changed_typedef_type: std::vector::const_iterator
diff --git a/ClavaWeaver/src/pt/up/fe/specs/clava/weaver/importable/AstFactory.java b/ClavaWeaver/src/pt/up/fe/specs/clava/weaver/importable/AstFactory.java
index a0eac8c1e..df96d1bb6 100644
--- a/ClavaWeaver/src/pt/up/fe/specs/clava/weaver/importable/AstFactory.java
+++ b/ClavaWeaver/src/pt/up/fe/specs/clava/weaver/importable/AstFactory.java
@@ -680,7 +680,7 @@ public static ATypedefDecl> typedefDecl(CxxWeaver weaver, AType> underlyingT
public static AElaboratedType> structType(CxxWeaver weaver, AStruct> struct) {
var namedType = (Type) struct.getTypeImpl().getNodeImpl();
- var elaboratedType = weaver.getFactory().elaboratedType(ElaboratedTypeKeyword.STRUCT, namedType);
+ var elaboratedType = weaver.getFactory().elaboratedType(ElaboratedTypeKeyword.Struct, namedType);
return CxxJoinpoints.create(elaboratedType, weaver, AElaboratedType.class);
}