[Bug] SonarQube has issues with symlinks

Must-share information (formatted with Markdown):

To recreate the sandboxed build environment Hedron Vision’s bazel compile command generator generates a symlink in <project_dir>/external. This links to <project_dir>/bazel-out/../../../external, which in turn links to /home/<user>/.cache/bazel/_bazel_<user>/<hash>/external.

When I run sonar-scanner on this project I get the following error message:

WARN: Not indexing due to symlink loop: <project_dir>/bazel-src/external/pcl/bin/X11
...
ERROR: Error during SonarScanner execution
java.lang.IllegalStateException: Failed to index files
        at org.sonar.scanner.scan.filesystem.ProjectFileIndexer.indexFiles(ProjectFileIndexer.java:214)
        at org.sonar.scanner.scan.filesystem.ProjectFileIndexer.index(ProjectFileIndexer.java:169)
        at org.sonar.scanner.scan.filesystem.ProjectFileIndexer.indexModulesRecursively(ProjectFileIndexer.java:148)
        at org.sonar.scanner.scan.filesystem.ProjectFileIndexer.index(ProjectFileIndexer.java:115)
        at org.sonar.scanner.scan.SpringProjectScanContainer.doAfterStart(SpringProjectScanContainer.java:363)
        at org.sonar.core.platform.SpringComponentContainer.startComponents(SpringComponentContainer.java:188)
        at org.sonar.core.platform.SpringComponentContainer.execute(SpringComponentContainer.java:167)
        at org.sonar.scanner.bootstrap.SpringGlobalContainer.doAfterStart(SpringGlobalContainer.java:137)
        at org.sonar.core.platform.SpringComponentContainer.startComponents(SpringComponentContainer.java:188)
        at org.sonar.core.platform.SpringComponentContainer.execute(SpringComponentContainer.java:167)
        at org.sonar.batch.bootstrapper.Batch.doExecute(Batch.java:72)
        at org.sonar.batch.bootstrapper.Batch.execute(Batch.java:66)
        at org.sonarsource.scanner.api.internal.batch.BatchIsolatedLauncher.execute(BatchIsolatedLauncher.java:46)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.base/java.lang.reflect.Method.invoke(Unknown Source)
        at org.sonarsource.scanner.api.internal.IsolatedLauncherProxy.invoke(IsolatedLauncherProxy.java:60)
        at com.sun.proxy.$Proxy0.execute(Unknown Source)
        at org.sonarsource.scanner.api.EmbeddedScanner.doExecute(EmbeddedScanner.java:189)
        at org.sonarsource.scanner.api.EmbeddedScanner.execute(EmbeddedScanner.java:138)
        at org.sonarsource.scanner.cli.Main.execute(Main.java:126)
        at org.sonarsource.scanner.cli.Main.execute(Main.java:81)
        at org.sonarsource.scanner.cli.Main.main(Main.java:62)
Caused by: java.nio.file.AccessDeniedException: <project_dir>/bazel-src/external/pcl/NX/var/tmp/nx
        at java.base/sun.nio.fs.UnixException.translateToIOException(Unknown Source)
        at java.base/sun.nio.fs.UnixException.rethrowAsIOException(Unknown Source)
        at java.base/sun.nio.fs.UnixException.rethrowAsIOException(Unknown Source)
        at java.base/sun.nio.fs.UnixFileSystemProvider.newDirectoryStream(Unknown Source)
        at java.base/java.nio.file.Files.newDirectoryStream(Unknown Source)
        at java.base/java.nio.file.FileTreeWalker.visit(Unknown Source)
        at java.base/java.nio.file.FileTreeWalker.next(Unknown Source)
        at java.base/java.nio.file.Files.walkFileTree(Unknown Source)
        at org.sonar.scanner.scan.filesystem.ProjectFileIndexer.indexDirectory(ProjectFileIndexer.java:221)
        at org.sonar.scanner.scan.filesystem.ProjectFileIndexer.indexFiles(ProjectFileIndexer.java:207)
        ... 23 more
ERROR: 
ERROR: Re-run SonarScanner using the -X switch to enable full debug logging.

If I run this under sudo to avoid the access denied issue, it loops forever printing different variations of finding symlink loops.

If I replace all the “external/…” in my compile_commands.json and replace them with the linked to location the problem disappears and sonar-scanner can execute.

  1. Maybe the indexing could be less eager and ignore locations it can’t scan. I think with build to1. ols like Bazel the assumption breaks that sonar-scanner should have access to every location that is accessible from the project space. There’s no need to index files and directories that aren’t included or used as source files.
  2. I can’t find any symlink loops where sonar-scanner mentions them. How is this determined?
  3. Adding the external folder to sonar.exclusions does not prevent this eager indexing, which IMHO it should.

Hello @Carl,

The analysis is not failing because of the symlink loop, but because of the access denied, as you guessed:

Symlink loops are detected in our code b Java itself, using the FileSystemLoopException class. I’m not saying that it’s foolproof, but it’s likely reliable, meaning that if it detects loops it’s because they exist.

In term of analysis scope, the scanner will simply try to access what is provided into sonar.sources, following symlinks. So if it tries to access a folder, it’s because it’s somehow in the sonar.sources property.
Now with symlinks, things might get a bit complicated, so if you can’t figure it out by yourselves, please upload here a sample project reproducing the issue so that we can try on our side and understand if there is any misbehavior.

I hope this helps.

Cheers,
Antoine

@Antoine The access denied thing is due to some system libraries being linked in by Bazel, e.g. PCL in this case. It would be really good if it was possible to apply the exclusions before trying to index the code here.

The symlink loops are also still causing massive amounts of prints with WARN: Not indexing due to symlink loop:. So while they are being detected, SonarQube still seems to descend further and further into detected loopy paths, it doesn’t seem to end. So this part is definitely broken.

@Carl,

I don’t have full debug logs, scanner parameters, or a minimal project reproducing the problem so I can’t really say much on your observation.

What I can say indeed is that:

  • if a file/folder is not excluded but the scanner has no access, then it will stop the analysis with an AccessDeniedException. Putting such file/folder into sonar.exclusions solves the problem.

This is how it works, files/folders into sonar.exclusions are not being read. You can confirm this by doing:

$> touch file.php
$> chmod 000 file.php
$> sonar-scanner -Dsonar.projectKey=test -Dsonar.sources=file.php
[...]
ERROR: Error during SonarScanner execution
org.sonar.plugins.php.AnalysisException: Unable to read file 'file.php'
[...]

$> sonar-scanner -Dsonar.projectKey=test -Dsonar.sources=file.php -Dsonar.exclusions=file.php
[...]
INFO: EXECUTION SUCCESS
[...]
  • SonarQube logs a warning for every symlink loop encountered. Those grow fast as each new symlink is a new entry point in the loop itself. You can’t have them excluded in sonar.exclusions because the evaluation of exclusions itself needs to read the file system, and then will throw those warnings before.
    However, if you make sure to not have them into sonar.sources, warnings will disappear as the scanner won’t even look into them.
    For example:
$> mkdir src folder1 folder2
$> ln -s ../folder2 folder1/symlink1
$> ln -s ../folder1 folder2/symlink2
$> sonar-scanner -Dsonar.projectKey=test -Dsonar.sources=src,folder1,folder2
[...]
WARN: Not indexing due to symlink loop: /Users/antoinevigneau/Downloads/folder1/symlink1/symlink2
WARN: Not indexing due to symlink loop: /Users/antoinevigneau/Downloads/folder2/symlink2/symlink1
[...]

$> sonar-scanner -Dsonar.projectKey=test -Dsonar.sources=src,folder1,folder2 -Dsonar.exclusions=folder1,folder2
[...]
WARN: Not indexing due to symlink loop: /Users/antoinevigneau/Downloads/folder1/symlink1/symlink2
WARN: Not indexing due to symlink loop: /Users/antoinevigneau/Downloads/folder2/symlink2/symlink1
[...]

$> sonar-scanner -Dsonar.projectKey=test -Dsonar.sources=src
[...]

There is no warning in the last execution.


So I don’t think there is any bug here, I think it’s just your project that is complex enough to make it hard to understand what’s wrong, but there is something wrong in its structure, at least for the purpose of the analysis.

I hope it will help.

Antoine

Hello Antoine,

I tried this, and it still failed with the same exception. The other exclusions work, analysis-wise, so I’m certain it’s not the wrong parameter.

That’s my point though, Bazel puts this external folder next to the sources. I would need to be able to exclude src/external while including the rest of src. However having tried that these exclusions seem to happen after this indexing process which makes this an issue. SonarQube would not need to index any of this if it stopped at src/external, and said, hey I don’t need to look in here.

This could be fixed in how SonarQube scans folders, I’m certain. The exclusions have to be applied earlier than how it’s done now.

Best regards,

Carl