├── .gitignore ├── .mvn ├── maven.config └── extensions.xml ├── Jenkinsfile ├── src ├── main │ ├── resources │ │ ├── index.jelly │ │ └── com │ │ │ └── absint │ │ │ └── astree │ │ │ └── AstreeBuilder │ │ │ ├── help-astree_server.html │ │ │ ├── help-skip_analysis.html │ │ │ ├── help-alauncher.html │ │ │ ├── help-dax_file.html │ │ │ ├── help-output_dir.html │ │ │ ├── help-dropAnalysis.html │ │ │ ├── help-analysis_id.html │ │ │ ├── global.jelly │ │ │ ├── help-failonswitch.html │ │ │ └── config.jelly │ └── java │ │ └── com │ │ └── absint │ │ └── astree │ │ ├── AstreeTool.java │ │ ├── AnalysisServerConfiguration.java │ │ ├── StatusPoller.java │ │ ├── FailonSwitch.java │ │ ├── AnalysisSummary.java │ │ ├── AstreeReportParser.java │ │ └── AstreeBuilder.java └── test │ └── java │ └── com │ └── absint │ └── astree │ └── AstreeReportParserTest.java ├── .github ├── dependabot.yml └── workflows │ ├── cd.yaml │ └── jenkins-security-scan.yml ├── INSTALL ├── LICENSE ├── README.adoc └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /work 3 | /.vscode 4 | *.swp 5 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -Dchangelist.format=%d.v%s 4 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | buildPlugin( 2 | useContainerAgent: true, 3 | configurations: [ 4 | [platform: 'linux', jdk: 21], 5 | [platform: 'windows', jdk: 17] 6 | ], 7 | ) 8 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 5 |
6 | Provides Jenkins integration for the Astrée static code analyzer. 7 |
8 | -------------------------------------------------------------------------------- /src/main/resources/com/absint/astree/AstreeBuilder/help-astree_server.html: -------------------------------------------------------------------------------- 1 |
2 | Hostname of the Astrée Server, specified as hostname:port. 3 |
4 | E.g. localhost:36000. 5 |
6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuring-dependabot-version-updates 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: maven 6 | directory: / 7 | schedule: 8 | interval: monthly 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: monthly 13 | -------------------------------------------------------------------------------- /src/main/resources/com/absint/astree/AstreeBuilder/help-skip_analysis.html: -------------------------------------------------------------------------------- 1 |
2 | This switch can be used to deactivate the Astrée analysis build step. 3 | This switch provides a more convenient method to temporarily deactivate analysis runs 4 | than removing the entire build step and reconfiguring the Astrée analysis run from scratch 5 | when later adding the build step again. 6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/com/absint/astree/AstreeBuilder/help-alauncher.html: -------------------------------------------------------------------------------- 1 |
2 | Absolute path to the a3c application on the machine hosting Jenkins that shall be used to invoke the a³ for C client. 3 | Typical path on Windows is C:\Program Files\AbsInt\common\bin\a3c.exe. 4 |
5 | Alternatively, the alauncher application may be specified as the preferred method to invoke the a³ for C client. 6 |
7 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.8 6 | 7 | 8 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | The AbsInt Astrée Jenkins Plugin will be part of the continuous integration build process on DEV@cloud. 2 | You will be able to directly access and install the plugin from your Jenkins -> Manage Jenkins -> Manage Plugins site. 3 | 4 | 5 | If you want to manually build the plugin you can use the following Maven command in the "pom.xml" project directory: 6 | "mvn package" 7 | 8 | A "target" directory including the "astree.hpi" binary version of the plugin will be generated. 9 | -------------------------------------------------------------------------------- /.github/workflows/cd.yaml: -------------------------------------------------------------------------------- 1 | # Note: additional setup is required, see https://www.jenkins.io/redirect/continuous-delivery-of-plugins 2 | 3 | name: cd 4 | on: 5 | workflow_dispatch: 6 | #check_run: 7 | # types: 8 | # - completed 9 | 10 | permissions: 11 | checks: read 12 | contents: write 13 | 14 | jobs: 15 | maven-cd: 16 | uses: jenkins-infra/github-reusable-workflows/.github/workflows/maven-cd.yml@v1 17 | secrets: 18 | MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} 19 | MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }} 20 | -------------------------------------------------------------------------------- /src/main/resources/com/absint/astree/AstreeBuilder/help-dax_file.html: -------------------------------------------------------------------------------- 1 |
2 | Absolute path to the DAX file containing the analysis specification and configuration. 3 |
4 | Note: In this setting, environment variables can be expanded. 5 | Expansion will affect all occurrences of the pattern ${name} where name is a valid environment variable name consisting solely of underscores, digits, and alphabetics from the portable character set and where the first character is not a digit. 6 |
7 | Paths and environment variables are evaluated on the machine running Jenkins. 8 |
9 | -------------------------------------------------------------------------------- /src/main/resources/com/absint/astree/AstreeBuilder/help-output_dir.html: -------------------------------------------------------------------------------- 1 |
2 | Absolute path to the folder into which the Summary Reports are to be generated. 3 | If left empty, reports will be generated into the project's Workspace directory and will be accessible from the Jenkins web interface. 4 |
5 | Note: In this setting, environment variables can be expanded. 6 | Expansion will affect all occurrences of the pattern ${name} where name is a valid environment variable name consisting solely of underscores, digits, and alphabetics from the portable character set and where the first character is not a digit. 7 |
8 | -------------------------------------------------------------------------------- /src/main/resources/com/absint/astree/AstreeBuilder/help-dropAnalysis.html: -------------------------------------------------------------------------------- 1 |
2 | When this option is activated, the analysis is not stored on the Astrée server, instead it is automatically deleted after the analysis run of the build step. 3 |
4 | This option is very helpful if the analysis run is only specified by a DAX file and it is not intended to archive each analysis run on the server. 5 |
6 | Please be aware that using this option in an analysis run only specified by an analysis ID will result in deleting the analysis with that ID on the Astrée server. 7 |
8 | This option corresponds to adding a --drop to a command line call to Astrée. 9 |
10 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: [ opened, synchronize, reopened ] 9 | workflow_dispatch: 10 | 11 | permissions: 12 | security-events: write 13 | contents: read 14 | actions: read 15 | 16 | jobs: 17 | security-scan: 18 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 19 | with: 20 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 21 | java-version: 11 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 22 | -------------------------------------------------------------------------------- /src/main/resources/com/absint/astree/AstreeBuilder/help-analysis_id.html: -------------------------------------------------------------------------------- 1 |
2 | ID of an existing, preconfigured analysis on the Astrée server that serves as a revisioning base for the analyses of the current Jenkins project. 3 | The analysis, as configured via the supported DAX file, of a build is imported as a new revision into the project with this ID on the server, if such a project exists. 4 | The new revision will become a child revision of the last existing revision. 5 | If no project with this ID exists on the server, the analysis will become the first revision of a new project with the specified ID. 6 |
7 | Note: In this setting, environment variables can be expanded. 8 | Expansion will affect all occurrences of the pattern ${name} where name is a valid environment variable name consisting solely of underscores, digits, and alphabetics from the portable character set and where the first character is not a digit. 9 |
10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 AbsInt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/com/absint/astree/AstreeTool.java: -------------------------------------------------------------------------------- 1 | package com.absint.astree; 2 | 3 | import com.absint.astree.AstreeReportParser; 4 | 5 | import javax.annotation.Nonnull; 6 | 7 | import org.kohsuke.stapler.DataBoundConstructor; 8 | 9 | import io.jenkins.plugins.analysis.core.model.ReportScanningTool; 10 | 11 | import hudson.Extension; 12 | 13 | /** 14 | * Tool registration to be picked up by warnings pluging 15 | */ 16 | public class AstreeTool extends ReportScanningTool { 17 | private static final long serialVersionUID = 1L; 18 | private final static String PLUGIN_ID = "absint-astree"; 19 | 20 | /** Creates a new instance of {@link AstreeTool}. */ 21 | @DataBoundConstructor 22 | public AstreeTool() { 23 | super(); 24 | // empty constructor required for stapler 25 | } 26 | 27 | @Override 28 | public AstreeReportParser createParser() { 29 | return new AstreeReportParser(); 30 | } 31 | 32 | /** Descriptor for this static analysis tool. */ 33 | @Extension 34 | public static class Descriptor extends ReportScanningToolDescriptor { 35 | /** Creates the descriptor instance. */ 36 | public Descriptor() { 37 | super(PLUGIN_ID); 38 | } 39 | 40 | @Nonnull 41 | @Override 42 | public String getDisplayName() { 43 | return "AbsInt Astrée Report Parser"; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/resources/com/absint/astree/AstreeBuilder/global.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 18 | 19 | 20 | 22 | 23 | 24 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/main/java/com/absint/astree/AnalysisServerConfiguration.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2016, AbsInt Angewandte Informatik GmbH 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.absint.astree; 26 | 27 | import org.kohsuke.stapler.DataBoundConstructor; 28 | 29 | /** 30 | * 31 | * @author AbsInt Angewandte Informatik GmbH 32 | */ 33 | 34 | public class AnalysisServerConfiguration { 35 | 36 | @DataBoundConstructor 37 | public AnalysisServerConfiguration(String a3c, boolean cmode, String srvurl, String user, String pwd) { 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/com/absint/astree/AstreeBuilder/help-failonswitch.html: -------------------------------------------------------------------------------- 1 |
2 | This option allow Astrée to fail a build depeneding on the types of detected code defects. 3 | The following settings are available: 4 | 49 |
50 | -------------------------------------------------------------------------------- /src/main/resources/com/absint/astree/AstreeBuilder/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 28 | 29 | 44 | 45 | 46 |
47 |
48 |
49 | 81 |
82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 |
96 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | AbsInt Astrée 2 | ============= 3 | 4 | Integration of AbsInt's static code analyzer https://www.absint.com/astree[Astrée] into the Jenkins continuous integration system. 5 | 6 | = Features 7 | 8 | * Configure an analyzer run as a Jenkins build step 9 | * Launch an Astrée analysis as a newly created analysis revision 10 | * Automatically mark a build step as erroneous depending on the categories of findings 11 | * Generate analysis reports directly in your Jenkins workspace 12 | * Access analysis results via the Jenkins web interface 13 | 14 | = Function 15 | 16 | The AbsInt Astrée plugin for Jenkins performs the following functions: 17 | 18 | * It transparently invokes Astrée during your build. 19 | * It can fail the build for different analysis outcomes (only on errors, also on alarms or also on data-flow anomalies reported). 20 | 21 | = Getting started 22 | 23 | * Install the plugin using the Plugin Manager, then restart Jenkins. 24 | * Go to the global configuration page (Manage Jenkins > Configure System). 25 | * Find the Astrée Configuration Section and specify the location of the AbsInt Launcher (alauncher) program and the Astrée server (hostname and port). 26 | 27 | = Project Setup and Project Configuration Settings 28 | 29 | * Create a Jenkins project, by creating it from scratch or by copying an existing project. 30 | * Under Configure, add a build step ‘Astrée Analysis Run.’ 31 | 32 | == Basic Settings 33 | 34 | Specify an analysis setup by providing an absolute path to the DAX file containing the analysis specification and configuration. 35 | You may also provide the ID of an existing, preconfigured analysis on the Astrée server that serves as a revisioning base for the analyses of the current Jenkins project. 36 | The analysis, as configured via the supported DAX file, of a build is imported as a new revision into the project with this ID on the server, if such a project exists. 37 | Furthermore, specify on which analysis outcome, Astrée may fail a build. 38 | 39 | == Options 40 | 41 | You may furthermore configure your analysis run to: 42 | 43 | * Delete the project from the server after the analysis run 44 | * Generate text report containing the detailed preprocessing output in project workspace 45 | 46 | Now start your build. 47 | After the build has completed, 48 | 49 | * a summary with the analysis results will be printed to the console output in Jenkins and 50 | * analysis reports can be found at the location specified in the Astrée build step configuration or in the Jenkins project workspace in case no other location has been configured. 51 | 52 | = Troubleshooting 53 | 54 | When you encounter problems while using the plugin, please provide the 55 | following information: 56 | 57 | * The error message from the Console Output. 58 | * The Jenkins server log file (the location is dependent on the container 59 | you use) 60 | * The content of ‘Manage Jenkins > System Information’ (_Jenkins 61 | root_/systemInfo) 62 | * The configuration file for the job (Jenkins root/jobs/_job 63 | name_/config.xml) 64 | * The global configuration file for Jenkins (Jenkins root/config.xml) 65 | * The Astrée analysis report and XML report files (if available). 66 | * In case of problems while saving the job configuration, a screenshot before submitting, the error message afterwards and the browser you are using. 67 | 68 | = Upgrading 69 | 70 | When upgrading, make sure that all jobs using the Astrée plugin are finished and not running during upgrade. For best results, restart your Jenkins after upgrade. 71 | -------------------------------------------------------------------------------- /src/main/java/com/absint/astree/StatusPoller.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2016, AbsInt Angewandte Informatik GmbH 5 | * Author: Dr.-Ing. Joerg Herter 6 | * Email: herter@absint.com 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | package com.absint.astree; 28 | 29 | import hudson.model.TaskListener; 30 | import java.io.*; 31 | 32 | 33 | /** 34 | * Helper thread to periodically update the Jenkins console output w.r.t. 35 | * a given (text) file. 36 | * 37 | * @author AbsInt Angewandte Informatik GmbH 38 | */ 39 | class StatusPoller extends Thread { 40 | 41 | static private long INITIAL_DELAY = 1 * 1000; 42 | static private String OFF_MSG = "[NOTE] Analyzer log not available in console. " + 43 | "Analysis may be running on a remote machine.\n"; 44 | 45 | private long interval; 46 | private boolean alive; 47 | 48 | private TaskListener listener; 49 | private hudson.FilePath log; 50 | 51 | 52 | /** 53 | * Constructor. 54 | * 55 | * @param interval update interval in milliseconds 56 | * @param listener TaskListener to provide access to Jenkins console via getLogger() 57 | * @param log input file (log to copy to console) 58 | */ 59 | StatusPoller(long interval, TaskListener listener, hudson.FilePath log) { 60 | this.alive = true; 61 | this.interval = interval; 62 | this.listener = listener; 63 | this.log = log; 64 | } 65 | 66 | 67 | public void run() { 68 | boolean active = true; 69 | boolean exceptionCaught = false; 70 | RandomAccessFile fileHandler = null; 71 | StringBuilder sb = null; 72 | 73 | long lpos = 0, cpos; 74 | try { this.sleep(INITIAL_DELAY); } 75 | catch(InterruptedException e) {} 76 | 77 | while(active && !exceptionCaught) { 78 | if(!alive) // last update 79 | active = false; 80 | try { 81 | this.sleep(interval); 82 | fileHandler = new RandomAccessFile(new java.io.File(log.getRemote()), "r"); 83 | if(fileHandler.length() == 0) continue; // no output there, yet 84 | cpos = fileHandler.length() - 1; 85 | sb = new StringBuilder(); 86 | for(long filePointer = lpos; filePointer <= cpos; filePointer++){ 87 | fileHandler.seek( filePointer ); 88 | sb.append((char)fileHandler.readByte()); 89 | } 90 | lpos = cpos; 91 | this.listener.getLogger().print(sb.toString()); 92 | } 93 | catch(InterruptedException ie) { 94 | } 95 | catch( FileNotFoundException e ) { 96 | if(!exceptionCaught) 97 | this.listener.getLogger().print(OFF_MSG); 98 | exceptionCaught = true; 99 | } 100 | catch( IOException e ) { 101 | if(!exceptionCaught) 102 | this.listener.getLogger().print(OFF_MSG); 103 | exceptionCaught = true; 104 | } 105 | finally { 106 | if (fileHandler != null ) 107 | try { 108 | fileHandler.close(); 109 | } 110 | catch (IOException e) { 111 | } 112 | } 113 | 114 | } 115 | } 116 | 117 | /** 118 | * Tells the thread to stop after a final update. 119 | */ 120 | public void kill() { 121 | this.alive = false; 122 | } 123 | } 124 | 125 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | 6 | 7 | org.jenkins-ci.plugins 8 | plugin 9 | 4.87 10 | 11 | 12 | 13 | 14 | absint-astree 15 | ${changelist} 16 | hpi 17 | 18 | 19 | 999999-SNAPSHOT 20 | 21 | 2.426.3 22 | 8 23 | 24 | 25 | AbsInt Astrée Plugin for Jenkins 26 | Integration of AbsInt's Astrée into the Jenkins continuous integration system 27 | https://github.com/jenkinsci/absint-astree-plugin 28 | 29 | 30 | 31 | 32 | 33 | MIT License 34 | http://opensource.org/licenses/MIT 35 | 36 | 37 | 38 | 39 | 40 | jherter 41 | Joerg Herter 42 | herter@absint.com 43 | 44 | 45 | mfrommberger 46 | Michael Frommberger 47 | michael.frommberger@gmx.net 48 | 49 | 50 | 51 | 52 | scm:git:https://github.com/jenkinsci/absint-astree-plugin.git 53 | scm:git:https://github.com/jenkinsci/absint-astree-plugin.git 54 | https://github.com/jenkinsci/absint-astree-plugin 55 | ${scmTag} 56 | 57 | 58 | 59 | 60 | io.jenkins.plugins 61 | warnings-ng 62 | 11.3.0 63 | 64 | 65 | 66 | com.github.spotbugs 67 | spotbugs-annotations 68 | 4.8.6 69 | 70 | 71 | 72 | org.jenkins-ci.plugins 73 | structs 74 | 338.v848422169819 75 | 76 | 77 | 78 | org.jenkins-ci.plugins.workflow 79 | workflow-step-api 80 | 657.v03b_e8115821b_ 81 | 82 | 83 | 84 | io.jenkins.plugins 85 | font-awesome-api 86 | 6.5.1-3 87 | 88 | 89 | 90 | com.github.ben-manes.caffeine 91 | caffeine 92 | 3.1.8 93 | 94 | 95 | 96 | io.jenkins.plugins 97 | caffeine-api 98 | 3.1.8-133.v17b_1ff2e0599 99 | 100 | 101 | 102 | org.jenkins-ci.plugins 103 | scm-api 104 | 683.vb_16722fb_b_80b_ 105 | 106 | 107 | 108 | org.json 109 | json 110 | 20240205 111 | 112 | 113 | 114 | org.slf4j 115 | slf4j-api 116 | 2.0.12 117 | 118 | 119 | 120 | com.google.errorprone 121 | error_prone_annotations 122 | 2.21.1 123 | 124 | 125 | 126 | io.jenkins.plugins 127 | commons-text-api 128 | 1.11.0-95.v22a_d30ee5d36 129 | 130 | 131 | 132 | 133 | 134 | 135 | repo.jenkins-ci.org 136 | https://repo.jenkins-ci.org/public/ 137 | 138 | 139 | 140 | 141 | repo.jenkins-ci.org 142 | https://repo.jenkins-ci.org/public/ 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /src/main/java/com/absint/astree/FailonSwitch.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2016, AbsInt Angewandte Informatik GmbH 5 | * Author: Dr.-Ing. Joerg Herter 6 | * Email: herter@absint.com 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | package com.absint.astree; 28 | 29 | import org.kohsuke.stapler.DataBoundConstructor; 30 | 31 | /** 32 | * Helper class for the com.absint.astree.AstreeBuilder. 33 | * 34 | * Stores the project-level settings of the fail-on-switch 35 | * that allows the analyzer to fail a Jenkins projects on 36 | * 43 | * 44 | * @author AbsInt Angewandte Informatik GmbH 45 | */ 46 | 47 | public class FailonSwitch { 48 | 49 | 50 | /** 51 | * Short name (visible in the (Jenkins) console output and 52 | * HTML pages) for the different situations in which 53 | * the plugin may fail a build. 54 | *
55 | * Valid names are: 56 | * 63 | */ 64 | private String failon; 65 | 66 | @DataBoundConstructor 67 | public FailonSwitch(String failon) { 68 | this.failon = failon; 69 | } 70 | 71 | public void setFailon(String failon) { 72 | this.failon = failon; 73 | } 74 | 75 | /** 76 | * Returns the short name for the currently 77 | * configured/set situations in which 78 | * the plugin may fail a build. 79 | *
80 | * Valid short names are: 81 | * 88 | * 89 | * @return java.lang.String 90 | */ 91 | public String getFailon() { 92 | return this.failon; 93 | } 94 | 95 | 96 | /* 97 | * Interface for Astrée PlugIn class. 98 | */ 99 | 100 | /** 101 | * Determines whether the configuration is set to fail a build 102 | * in case a definite runtime error is reported. 103 | * 104 | * 105 | * @return boolean 106 | */ 107 | public boolean failOnErrors() { 108 | if(failon == null) 109 | return false; 110 | return this.failon.equals("errors"); 111 | } 112 | 113 | /** 114 | * Determines whether the configuration is set to fail a build 115 | * in case a potential runtime error ("alarm") is reported. 116 | * 117 | * 118 | * @return boolean 119 | */ 120 | public boolean failOnAlarms() { 121 | if(failon == null) 122 | return false; 123 | return this.failon.equals("alarms"); 124 | } 125 | 126 | /* * 127 | * Determines whether the configuration is set to fail a build 128 | * in case a potential runtime error ("alarm") classified as "true" is reported. 129 | * 130 | * 131 | * @return boolean 132 | public boolean failOnTrueAlarms() { 133 | if(failon == null) 134 | return false; 135 | return this.failon.equals("true-alarms"); 136 | } 137 | */ 138 | 139 | /* * 140 | * Determines whether the configuration is set to fail a build 141 | * in case a potential runtime error ("alarm") which ist not commented is reported. 142 | * 143 | * 144 | * @return boolean 145 | public boolean failOnUncommentedAlarms() { 146 | if(failon == null) 147 | return false; 148 | return this.failon.equals("uncommented-alarms"); 149 | } 150 | */ 151 | 152 | /** 153 | * Determines whether the configuration is set to fail a build 154 | * in case a flow anomaly ("Type D alarm") is reported. 155 | * 156 | * 157 | * @return boolean 158 | */ 159 | public boolean failOnFlowAnomalies() { 160 | if(failon == null) 161 | return false; 162 | return this.failon.equals("flow-anomalies"); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/absint/astree/AnalysisSummary.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2016, AbsInt Angewandte Informatik GmbH 5 | * Author: Dr.-Ing. Joerg Herter 6 | * Email: herter@absint.com 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | package com.absint.astree; 28 | 29 | import java.io.*; 30 | 31 | 32 | /** 33 | * Stores an analysis (result) summary and provides an interface to the analysis result for the 34 | * Astrée PlugIn classes. 35 | * 36 | * 37 | * @author AbsInt Angewandte Informatik GmbH 38 | */ 39 | public class AnalysisSummary { 40 | 41 | private int numberOfErrors; 42 | private int numberOfAlarms; 43 | private int numberOfFlowAnomalies; 44 | private int numberOfRuleViolations; 45 | private int numberOfTrueAlarms; // not implemented yet 46 | private int numberOfUncommentedAlarms; // not implemented yet 47 | 48 | /** 49 | * Private constructor. 50 | */ 51 | private AnalysisSummary(int numberOfErrors, int numberOfAlarms, 52 | int numberOfFlowAnomalies, int numberOfRuleViolations, 53 | int numberOfTrueAlarms, int numberOfUncommentedAlarms ) { 54 | this.numberOfErrors = numberOfErrors; 55 | this.numberOfAlarms = numberOfAlarms; 56 | this.numberOfFlowAnomalies = numberOfFlowAnomalies; 57 | this.numberOfRuleViolations = numberOfRuleViolations; 58 | this.numberOfTrueAlarms = numberOfTrueAlarms; 59 | this.numberOfUncommentedAlarms = numberOfUncommentedAlarms; 60 | } 61 | 62 | /** 63 | * Constructs an AnalyisSummary object from a report file (txt version). 64 | * 65 | * @param path Path (as {@link java.lang.String}) to the Astrée text report 66 | * from which the object is to be constructed. 67 | * @return AnalysisSummary object providing easy access to data of an Astrée report 68 | */ 69 | static public AnalysisSummary readFromReportFile(String path) { 70 | int numberOfErrors = 0; 71 | int numberOfAlarms = 0; 72 | int numberOfFlowAnomalies = 0; 73 | int numberOfRuleViolations = 0; 74 | int numberOfTrueAlarms = 0; // not implemented yet 75 | int numberOfUncommentedAlarms = 0; // not implemented yet 76 | try{ 77 | BufferedReader br = new BufferedReader( 78 | new InputStreamReader( 79 | new FileInputStream(path), "UTF-8" )); 80 | String line = br.readLine(); 81 | boolean skipping = true; 82 | while(line != null) { 83 | if(!skipping) { 84 | if(line.trim().startsWith("Errors:")) 85 | numberOfErrors = Integer.parseInt( 86 | line.substring(line.indexOf(":") + 1, line.length()).trim()); 87 | else if(line.trim().startsWith("Run-time errors:")) 88 | numberOfAlarms = Integer.parseInt( 89 | line.substring(line.indexOf(":") + 1, line.length()).trim()); 90 | else if(line.trim().startsWith("Flow anomalies:")) 91 | numberOfFlowAnomalies = Integer.parseInt( 92 | line.substring(line.indexOf(":") + 1, line.length()).trim()); 93 | else if(line.trim().startsWith("Rule violations:")) 94 | numberOfRuleViolations = Integer.parseInt( 95 | line.substring(line.indexOf(":") + 1, line.length()).trim()); 96 | 97 | 98 | } 99 | // skip report until Summary section is reached 100 | if(skipping && line.trim().startsWith("/* Result summary */")) 101 | skipping = false; 102 | line = br.readLine(); 103 | } 104 | br.close(); 105 | } catch(IOException e) { 106 | return null; 107 | } 108 | return new AnalysisSummary(numberOfErrors, numberOfAlarms, 109 | numberOfFlowAnomalies, numberOfRuleViolations, 110 | numberOfTrueAlarms, numberOfUncommentedAlarms ); 111 | } 112 | 113 | 114 | /* 115 | * Interface for Astrée PlugIn class. 116 | */ 117 | 118 | /** 119 | * Returns the number of reported definite runtime errors ("errors"). 120 | * 121 | * @return int 122 | */ 123 | public int getNumberOfErrors() { 124 | return this.numberOfErrors; 125 | } 126 | 127 | /** 128 | * Returns the number of reported potential runtime errors ("alarms"). 129 | * 130 | * @return int 131 | */ 132 | public int getNumberOfAlarms() { 133 | return this.numberOfAlarms; 134 | } 135 | 136 | /** 137 | * Returns the number of reported flow anaomalies ("Type D alarms"). 138 | * 139 | * @return int 140 | */ 141 | public int getNumberOfFlowAnomalies() { 142 | return this.numberOfFlowAnomalies; 143 | } 144 | 145 | /** 146 | * Returns the number of reported rule violations ("Type R alarms"). 147 | * 148 | * @return int 149 | */ 150 | public int getNumberOfRuleViolations() { 151 | return this.numberOfRuleViolations; 152 | } 153 | 154 | /** 155 | * Returns the number of reported potential runtime errors ("alarms") classified as "true". 156 | * 157 | * @return int 158 | */ 159 | public int getNumberOfTrueAlarms() { 160 | return this.numberOfTrueAlarms; 161 | } 162 | 163 | /** 164 | * Returns the number of reported potential runtime errors ("alarms") not commented. 165 | * 166 | * @return int 167 | */ 168 | public int getNumberOfUncommentedAlarms() { 169 | return this.numberOfUncommentedAlarms; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/com/absint/astree/AstreeReportParser.java: -------------------------------------------------------------------------------- 1 | package com.absint.astree; 2 | 3 | import edu.hm.hafner.analysis.IssueBuilder; 4 | import edu.hm.hafner.analysis.IssueParser; 5 | import edu.hm.hafner.analysis.ParsingException; 6 | import edu.hm.hafner.analysis.ReaderFactory; 7 | import edu.hm.hafner.analysis.Report; 8 | import edu.hm.hafner.analysis.Severity; 9 | 10 | import org.apache.commons.text.StringEscapeUtils; 11 | import org.xml.sax.Attributes; 12 | import org.xml.sax.helpers.DefaultHandler; 13 | import org.xml.sax.SAXException; 14 | 15 | import java.util.ArrayList; 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | 20 | /** 21 | * Parser for AbsInt Astree XML reports. 22 | */ 23 | public class AstreeReportParser extends IssueParser { 24 | /** 25 | * ID for serialisation 26 | */ 27 | private static final long serialVersionUID = 1L; 28 | 29 | /** 30 | * Check if file format is acceptable. 31 | * 32 | * @param readerFactory factory to read XML document 33 | * 34 | * @return true if file is acceppted by this parser or false if not 35 | */ 36 | @Override 37 | public boolean accepts(final ReaderFactory readerFactory) { 38 | return isXmlFile(readerFactory); 39 | } 40 | 41 | /** 42 | * Parse a AbsInt Astree XML report. 43 | * 44 | * @param readerFactory factory to read XML document 45 | * 46 | * @return report with parsed information 47 | */ 48 | @Override 49 | public Report parse(final ReaderFactory readerFactory) throws ParsingException { 50 | // create template with basic settings for issues 51 | IssueBuilder issueBuilder = new IssueBuilder(); 52 | 53 | // parse document 54 | final Handler handler = new Handler(); 55 | readerFactory.parse(handler); 56 | 57 | // use project description as reference 58 | // FIXME(ivan): I have no idea why, that's what the previous version of the code did... 59 | issueBuilder.setReference(handler.projectDescription); 60 | 61 | // create new report 62 | Report report = new Report(); 63 | report.setOriginReportFile(readerFactory.getFileName()); 64 | 65 | final StringBuilder descriptionBuilder = new StringBuilder(); 66 | final StringBuilder categoryBuilder = new StringBuilder(); 67 | 68 | // process the messages 69 | for (Message message : handler.messages) { 70 | // build description out of code snippet 71 | final String code = handler.codeSnippets.get(message.locationID); 72 | if (code != null && !code.isEmpty()) { 73 | descriptionBuilder.append("

Code:

");
 74 |                 descriptionBuilder.append(code);
 75 |                 descriptionBuilder.append("
"); 76 | } 77 | 78 | // build description out of context 79 | final String context = message.context; 80 | if (context != null && !context.isEmpty()) { 81 | descriptionBuilder.append("

Context:

");
 82 |                 descriptionBuilder.append(context);
 83 |                 descriptionBuilder.append("
"); 84 | } 85 | 86 | // build category out of message type and category 87 | final AlarmType type = handler.types.get(message.typeID); 88 | if (type == null) { 89 | throw new ParsingException("Missing finding category " + message.typeID); 90 | } 91 | final String category = handler.categories.get(type.categoryID); 92 | if (category == null) { 93 | throw new ParsingException("Missing finding group " + type.categoryID); 94 | } 95 | categoryBuilder.append(type.type); 96 | categoryBuilder.append(" ["); 97 | categoryBuilder.append(category); 98 | categoryBuilder.append(']'); 99 | 100 | // retrieve location of message 101 | Location location = handler.locations.get(message.locationID); 102 | if (location == null) 103 | location = new Location(); 104 | 105 | // create new issue 106 | issueBuilder.setMessage(message.text) 107 | .setFileName(handler.files.get(location.fileID)) 108 | .setLineStart(location.startLine) 109 | .setLineEnd(location.endLine) 110 | .setColumnStart(location.startColumn) 111 | .setColumnEnd(location.endColumn) 112 | .setCategory(categoryBuilder.toString()) 113 | .setDescription(descriptionBuilder.toString()) 114 | .setSeverity(message.severity); 115 | 116 | // add issue to report 117 | report.add(issueBuilder.build()); 118 | 119 | descriptionBuilder.setLength(0); 120 | categoryBuilder.setLength(0); 121 | } 122 | 123 | issueBuilder.close(); 124 | 125 | return report; 126 | } 127 | 128 | private static class Handler extends DefaultHandler { 129 | public String projectDescription; 130 | public Map categories = new HashMap(); 131 | public Map types = new HashMap(); 132 | public Map codeSnippets = new HashMap(); 133 | public Map files = new HashMap(); 134 | public Map locations = new HashMap(); 135 | public List messages = new ArrayList(); 136 | 137 | private String currentId; 138 | private Message currentMessage; 139 | private AlarmType currentAlarmType; 140 | private boolean collectCurrentCharacters = false; 141 | private StringBuilder currentCharacters = new StringBuilder(); 142 | private StringBuilder auxiliaryStringBuilder = new StringBuilder(); 143 | 144 | @Override 145 | public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { 146 | if (qName.equals("alarm_category") || qName.equals("category_group")) { 147 | currentId = attributes.getValue("id"); 148 | collectCurrentCharacters = true; 149 | } else if (qName.equals("alarm_type")) { 150 | currentId = attributes.getValue("id"); 151 | currentAlarmType = new AlarmType(); 152 | currentAlarmType.categoryID = attributes.getValue("category_id"); 153 | collectCurrentCharacters = true; 154 | } else if (qName.equals("finding_category")) { 155 | currentId = attributes.getValue("finding_key"); 156 | currentAlarmType = new AlarmType(); 157 | currentAlarmType.categoryID = attributes.getValue("category_group_id"); 158 | collectCurrentCharacters = true; 159 | } else if (qName.equals("code-snippet")) { 160 | currentId = attributes.getValue("location_id"); 161 | } else if (qName.equals("line") || qName.equals("textline")) { 162 | collectCurrentCharacters = true; 163 | } else if (qName.equals("file")) { 164 | files.put(attributes.getValue("id"), attributes.getValue("name")); 165 | } else if (qName.equals("location")) { 166 | final Location location = new Location(); 167 | location.fileID = attributes.getValue("p_file"); 168 | location.startLine = attributes.getValue("p_start_line"); 169 | location.endLine = attributes.getValue("p_end_line"); 170 | location.startColumn = attributes.getValue("p_start_col"); 171 | location.endColumn = attributes.getValue("p_end_col"); 172 | locations.put(attributes.getValue("id"), location); 173 | } else if (qName.equals("finding")) { 174 | currentMessage = new Message(); 175 | currentMessage.locationID = attributes.getValue("location_id"); 176 | currentMessage.typeID = attributes.getValue("key"); 177 | currentMessage.context = attributes.getValue("context"); 178 | if (attributes.getValue("kind").equals("alarm")) { 179 | currentMessage.severity = Severity.WARNING_HIGH; 180 | } else { 181 | currentMessage.severity = Severity.ERROR; 182 | } 183 | } else if (qName.equals("alarm_message") || qName.equals("error_message") || qName.equals("note_message")) { 184 | currentMessage = new Message(); 185 | currentMessage.locationID = attributes.getValue("location_id"); 186 | currentMessage.typeID = attributes.getValue("type"); 187 | currentMessage.context = attributes.getValue("context"); 188 | if (qName.equals("alarm_message")) { 189 | currentMessage.severity = Severity.WARNING_HIGH; 190 | } else if (qName.equals("error_message")) { 191 | currentMessage.severity = Severity.ERROR; 192 | } else { 193 | currentMessage.severity = Severity.WARNING_LOW; 194 | } 195 | } else if (qName.equals("project")) { 196 | projectDescription = attributes.getValue("description"); 197 | } 198 | } 199 | 200 | @Override 201 | public void endElement(String uri, String localName, String qName) throws SAXException { 202 | if (qName.equals("alarm_category") || qName.equals("category_group")) { 203 | categories.put(currentId, currentCharacters.toString()); 204 | currentId = null; 205 | collectCurrentCharacters = false; 206 | currentCharacters.setLength(0); 207 | } else if (qName.equals("alarm_type") || qName.equals("finding_category")) { 208 | currentAlarmType.type = currentCharacters.toString(); 209 | types.put(currentId, currentAlarmType); 210 | currentId = null; 211 | currentAlarmType = null; 212 | collectCurrentCharacters = false; 213 | currentCharacters.setLength(0); 214 | } else if (qName.equals("code-snippet")) { 215 | codeSnippets.put(currentId, auxiliaryStringBuilder.toString()); 216 | currentId = null; 217 | auxiliaryStringBuilder.setLength(0); 218 | } else if (qName.equals("line") || qName.equals("textline")) { 219 | if (auxiliaryStringBuilder.length() != 0) { 220 | if (qName.equals("textline")) { 221 | auxiliaryStringBuilder.append("
"); 222 | } else { 223 | auxiliaryStringBuilder.append('\n'); 224 | } 225 | } 226 | auxiliaryStringBuilder.append(StringEscapeUtils.escapeHtml4(currentCharacters.toString())); 227 | collectCurrentCharacters = false; 228 | currentCharacters.setLength(0); 229 | } else if (qName.equals("finding") || qName.equals("alarm_message") || qName.equals("error_message") || qName.equals("note_message")) { 230 | currentMessage.text = auxiliaryStringBuilder.toString(); 231 | messages.add(currentMessage); 232 | currentMessage = null; 233 | auxiliaryStringBuilder.setLength(0); 234 | } 235 | } 236 | 237 | @Override 238 | public void characters(char[] ch, int start, int length) throws SAXException { 239 | if (collectCurrentCharacters) { 240 | currentCharacters.append(ch, start, length); 241 | } 242 | } 243 | } 244 | 245 | private static class AlarmType { 246 | public String categoryID; 247 | public String type; 248 | } 249 | 250 | private static class Message { 251 | public String locationID; 252 | public String typeID; 253 | public String context; 254 | public String text; 255 | public Severity severity; 256 | } 257 | 258 | private static class Location { 259 | public String fileID; 260 | public String startLine; 261 | public String endLine; 262 | public String startColumn; 263 | public String endColumn; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /src/test/java/com/absint/astree/AstreeReportParserTest.java: -------------------------------------------------------------------------------- 1 | package com.absint.astree; 2 | 3 | import java.io.File; 4 | import java.util.Set; 5 | 6 | import static org.junit.Assert.assertEquals; 7 | import static org.junit.Assert.assertTrue; 8 | 9 | import org.junit.Test; 10 | 11 | import edu.hm.hafner.analysis.FileReaderFactory; 12 | import edu.hm.hafner.analysis.Issue; 13 | import edu.hm.hafner.analysis.Report; 14 | import edu.hm.hafner.analysis.Severity; 15 | 16 | /** 17 | * Tests for {@link AstreeReportParser}. 18 | */ 19 | public class AstreeReportParserTest { 20 | @Test 21 | public void test() { 22 | final File dir = new File(getClass().getResource("").getPath()); 23 | for (File file : dir.listFiles()) { 24 | if (!file.getName().endsWith(".xml")) 25 | continue; 26 | final FileReaderFactory readerFactory = new FileReaderFactory(file.toPath()); 27 | final AstreeReportParser parser = new AstreeReportParser(); 28 | assertTrue(parser.accepts(readerFactory)); 29 | testReport(parser.parse(readerFactory)); 30 | } 31 | } 32 | 33 | private void testReport(Report report) { 34 | final File file = new File(report.getOriginReportFile()); 35 | final String fileName = file.getName(); 36 | 37 | assertEquals(fileName, 0, report.getDuplicatesSize()); 38 | 39 | final Set files = report.getAbsolutePaths(); 40 | if (fileName.compareTo("report-18.10.xml") <= 0) { 41 | assertEquals(fileName, 2, files.size()); 42 | assertTrue(fileName, files.contains("preprocessed/src/scenarios.c")); 43 | assertTrue(fileName, files.contains("-")); 44 | } else if (fileName.compareTo("report-21.04i2.xml") <= 0) { 45 | assertEquals(fileName, 5, files.size()); 46 | assertTrue(fileName, files.contains("preprocessed/src/astree.cfg")); 47 | assertTrue(fileName, files.contains("preprocessed/src/filter.c")); 48 | assertTrue(fileName, files.contains("preprocessed/src/scenarios.c")); 49 | assertTrue(fileName, files.contains("preprocessed/src/dhry/Proc1.c")); 50 | assertTrue(fileName, files.contains("preprocessed/src/dhry/Proc7.c")); 51 | } else { 52 | assertEquals(fileName, 6, files.size()); 53 | assertTrue(fileName, files.contains("preprocessed/src/state_machine.c")); 54 | assertTrue(fileName, files.contains("preprocessed/src/astree.cfg")); 55 | assertTrue(fileName, files.contains("preprocessed/src/filter.c")); 56 | assertTrue(fileName, files.contains("preprocessed/src/scenarios.c")); 57 | assertTrue(fileName, files.contains("preprocessed/src/dhry/Proc1.c")); 58 | assertTrue(fileName, files.contains("preprocessed/src/dhry/Proc7.c")); 59 | } 60 | 61 | final Set categories = report.getCategories(); 62 | if (fileName.compareTo("report-18.10.xml") <= 0) { 63 | assertEquals(fileName, 21, categories.size()); 64 | } else if (fileName.compareTo("report-21.04i2.xml") <= 0) { 65 | assertEquals(fileName, 11, categories.size()); 66 | assertTrue(fileName, categories.contains("Integer division by zero [Division or modulo by zero]")); 67 | assertTrue(fileName, categories.contains("Use of uninitialized variables [Uninitialized variables]")); 68 | assertTrue(fileName, categories.contains("Overflow in conversion (with unpredictable result) [Invalid ranges and overflows]")); 69 | if (fileName.compareTo("report-20.10.xml") <= 0) { 70 | assertTrue(fileName, categories.contains("Definite runtime error [Errors]")); 71 | } else { 72 | assertTrue(fileName, categories.contains("Analysis stopped [Errors]")); 73 | } 74 | assertTrue(fileName, categories.contains("Possible overflow upon dereference [Invalid usage of pointers and arrays]")); 75 | assertTrue(fileName, categories.contains("Assertion failure [Failed or invalid directives]")); 76 | assertTrue(fileName, categories.contains("Incompatible object pointer conversion [Failed coding rule checks]")); 77 | assertTrue(fileName, categories.contains("Infinite loop [Data and control flow alarms]")); 78 | assertTrue(fileName, categories.contains("Control flow anomaly [Data and control flow alarms]")); 79 | assertTrue(fileName, categories.contains("Out-of-bound array access [Invalid usage of pointers and arrays]")); 80 | assertTrue(fileName, categories.contains("Overflow in arithmetic [Invalid ranges and overflows]")); 81 | } else { 82 | assertEquals(fileName, 12, categories.size()); 83 | assertTrue(fileName, categories.contains("Integer division by zero [Division or modulo by zero]")); 84 | assertTrue(fileName, categories.contains("Use of uninitialized variables [Uninitialized variables]")); 85 | assertTrue(fileName, categories.contains("Analysis stopped in critical context [Errors]")); 86 | assertTrue(fileName, categories.contains("Unbounded loop [Data and control flow alarms]")); 87 | assertTrue(fileName, categories.contains("Overflow in conversion (with unpredictable result) [Invalid ranges and overflows]")); 88 | assertTrue(fileName, categories.contains("Possible overflow upon dereference [Invalid usage of pointers and arrays]")); 89 | assertTrue(fileName, categories.contains("Assertion failure [Failed or invalid directives]")); 90 | assertTrue(fileName, categories.contains("Incompatible object pointer conversion [Failed coding rule checks]")); 91 | assertTrue(fileName, categories.contains("Infinite loop [Data and control flow alarms]")); 92 | assertTrue(fileName, categories.contains("Control flow anomaly [Data and control flow alarms]")); 93 | assertTrue(fileName, categories.contains("Out-of-bound array access [Invalid usage of pointers and arrays]")); 94 | if (fileName.compareTo("report-22.10i4.xml") <= 0) { 95 | assertTrue(fileName, categories.contains("Overflow in arithmetic [Invalid ranges and overflows]")); 96 | } else { 97 | assertTrue(fileName, categories.contains("Overflow in arithmetic (with unpredictable result) [Invalid ranges and overflows]")); 98 | } 99 | } 100 | 101 | if (fileName.compareTo("report-18.10.xml") <= 0) { 102 | assertEquals(fileName, 38, report.size()); 103 | 104 | final Issue alarm1 = report.get(0); 105 | assertEquals(fileName, Severity.WARNING_HIGH, alarm1.getSeverity()); 106 | if (fileName.equals("report-18.04.xml")) { 107 | assertEquals(fileName, "Parameter name [Violation of coding rules]", alarm1.getCategory()); 108 | } else { 109 | assertEquals(fileName, "Parameter name [Failed coding rule checks]", alarm1.getCategory()); 110 | } 111 | assertEquals(fileName, "preprocessed/src/scenarios.c", alarm1.getFileName()); 112 | assertEquals(fileName, 20, alarm1.getLineStart()); 113 | assertEquals(fileName, 23, alarm1.getColumnStart()); 114 | assertEquals(fileName, 20, alarm1.getLineEnd()); 115 | assertEquals(fileName, 26, alarm1.getColumnEnd()); 116 | assertEquals(fileName, "ALARM (R): check parameter-name failed (violates M2012.8.2-required)", alarm1.getMessage()); 117 | if (fileName.equals("report-18.04.xml")) { 118 | assertEquals(fileName, "n/a", alarm1.getReference()); 119 | } else { 120 | assertEquals(fileName, "n/A", alarm1.getReference()); 121 | } 122 | assertEquals(fileName, "", alarm1.getDescription()); 123 | 124 | final Issue alarm2 = report.get(37); 125 | assertEquals(fileName, Severity.WARNING_HIGH, alarm2.getSeverity()); 126 | if (fileName.equals("report-18.04.xml")) { 127 | assertEquals(fileName, "Unreachable code [Violation of coding rules]", alarm2.getCategory()); 128 | } else { 129 | assertEquals(fileName, "Unreachable code [Failed coding rule checks]", alarm2.getCategory()); 130 | } 131 | assertEquals(fileName, "preprocessed/src/scenarios.c", alarm2.getFileName()); 132 | assertEquals(fileName, 134, alarm2.getLineStart()); 133 | assertEquals(fileName, 4, alarm2.getColumnStart()); 134 | assertEquals(fileName, 134, alarm2.getLineEnd()); 135 | assertEquals(fileName, 13, alarm2.getColumnEnd()); 136 | assertEquals(fileName, "ALARM (R): check unreachable-code failed (violates M2012.2.1-required)", alarm2.getMessage()); 137 | if (fileName.equals("report-18.04.xml")) { 138 | assertEquals(fileName, "n/a", alarm2.getReference()); 139 | } else { 140 | assertEquals(fileName, "n/A", alarm2.getReference()); 141 | } 142 | assertEquals(fileName, "", alarm2.getDescription()); 143 | 144 | final Issue error1 = report.get(21); 145 | assertEquals(fileName, Severity.ERROR, error1.getSeverity()); 146 | assertEquals(fileName, "preprocessed/src/scenarios.c", error1.getFileName()); 147 | assertEquals(fileName, "Definite runtime error [Errors]", error1.getCategory()); 148 | assertEquals(fileName, 73, error1.getLineStart()); 149 | assertEquals(fileName, 8, error1.getColumnStart()); 150 | assertEquals(fileName, 73, error1.getLineEnd()); 151 | assertEquals(fileName, 25, error1.getColumnEnd()); 152 | assertEquals(fileName, "ERROR: Definite runtime error during assignment in this context. Analysis stopped for this context.", error1.getMessage()); 153 | if (fileName.equals("report-18.04.xml")) { 154 | assertEquals(fileName, "n/a", error1.getReference()); 155 | } else { 156 | assertEquals(fileName, "n/A", error1.getReference()); 157 | } 158 | assertEquals(fileName, "

Context:

l32#call#main@48,l34#loop@72=11/12
", error1.getDescription()); 159 | 160 | final Issue error2 = report.get(24); 161 | assertEquals(fileName, Severity.ERROR, error2.getSeverity()); 162 | assertEquals(fileName, "preprocessed/src/scenarios.c", error2.getFileName()); 163 | assertEquals(fileName, "Definite runtime error [Errors]", error2.getCategory()); 164 | assertEquals(fileName, 78, error2.getLineStart()); 165 | assertEquals(fileName, 8, error2.getColumnStart()); 166 | assertEquals(fileName, 78, error2.getLineEnd()); 167 | assertEquals(fileName, 16, error2.getColumnEnd()); 168 | assertEquals(fileName, "ERROR: Definite runtime error during assignment in this context. Analysis stopped for this context.", error2.getMessage()); 169 | if (fileName.equals("report-18.04.xml")) { 170 | assertEquals(fileName, "n/a", error2.getReference()); 171 | } else { 172 | assertEquals(fileName, "n/A", error2.getReference()); 173 | } 174 | assertEquals(fileName, "

Context:

l32#call#main@48,l37#loop@77=11/12
", error2.getDescription()); 175 | 176 | final Issue note1 = report.get(30); 177 | assertEquals(fileName, Severity.WARNING_LOW, note1.getSeverity()); 178 | assertEquals(fileName, "preprocessed/src/scenarios.c", note1.getFileName()); 179 | assertEquals(fileName, "Control flow [Notifications]", note1.getCategory()); 180 | assertEquals(fileName, 48, note1.getLineStart()); 181 | assertEquals(fileName, 1, note1.getColumnStart()); 182 | assertEquals(fileName, 135, note1.getLineEnd()); 183 | assertEquals(fileName, 1, note1.getColumnEnd()); 184 | assertEquals(fileName, "NOTE: Analyzed entry-point main never returns.", note1.getMessage()); 185 | if (fileName.equals("report-18.04.xml")) { 186 | assertEquals(fileName, "n/a", note1.getReference()); 187 | } else { 188 | assertEquals(fileName, "n/A", note1.getReference()); 189 | } 190 | assertEquals(fileName, "", note1.getDescription()); 191 | 192 | final Issue note2 = report.get(35); 193 | assertEquals(fileName, Severity.WARNING_LOW, note2.getSeverity()); 194 | assertEquals(fileName, "preprocessed/src/scenarios.c", note2.getFileName()); 195 | assertEquals(fileName, "Control flow [Notifications]", note2.getCategory()); 196 | assertEquals(fileName, 124, note2.getLineStart()); 197 | assertEquals(fileName, 3, note2.getColumnStart()); 198 | assertEquals(fileName, 126, note2.getLineEnd()); 199 | assertEquals(fileName, 5, note2.getColumnEnd()); 200 | assertEquals(fileName, "NOTE: Loop may be unbounded", note2.getMessage()); 201 | if (fileName.equals("report-18.04.xml")) { 202 | assertEquals(fileName, "n/a", note2.getReference()); 203 | } else { 204 | assertEquals(fileName, "n/A", note2.getReference()); 205 | } 206 | assertEquals(fileName, "", note2.getDescription()); 207 | } else if (fileName.compareTo("report-21.04i2.xml") <= 0) { 208 | assertEquals(fileName, 125, report.size()); 209 | 210 | final Issue alarm1 = report.get(0); 211 | assertEquals(fileName, Severity.WARNING_HIGH, alarm1.getSeverity()); 212 | assertEquals(fileName, "Incompatible object pointer conversion [Failed coding rule checks]", alarm1.getCategory()); 213 | assertEquals(fileName, "preprocessed/src/dhry/Proc1.c", alarm1.getFileName()); 214 | assertEquals(fileName, 77, alarm1.getLineStart()); 215 | assertEquals(fileName, 44, alarm1.getColumnStart()); 216 | assertEquals(fileName, 77, alarm1.getLineEnd()); 217 | assertEquals(fileName, 54, alarm1.getColumnEnd()); 218 | if (fileName.compareTo("report-19.10.xml") <= 0) { 219 | assertEquals(fileName, "[ conversion between two incompatible pointer types: from <RecordType*> (aka <struct Record*>) to <char*>
ALARM (R): check incompatible-object-pointer-conversion failed (violates A.1.11)", alarm1.getMessage()); 220 | } else if (fileName.compareTo("report-20.04.xml") <= 0) { 221 | assertEquals(fileName, "conversion between two incompatible pointer types: from <RecordType*> (aka <struct Record*>) to <char*>
ALARM (R): check incompatible-object-pointer-conversion failed (violates A.1.11)", alarm1.getMessage()); 222 | } else if (fileName.compareTo("report-21.04.xml") <= 0) { 223 | assertEquals(fileName, "conversion between two incompatible pointer types: from <RecordType*> (aka <struct Record*>) to <char*>
ALARM (R) check_incompatible_object_pointer_conversion: check failed (violates A.1.11)", alarm1.getMessage()); 224 | } else { 225 | assertEquals(fileName, "conversion between two incompatible pointer types: from <RecordType *> (aka <struct Record *>) to <char *>
ALARM (R) check_incompatible_object_pointer_conversion: check failed (violates A.1.11)", alarm1.getMessage()); 226 | } 227 | assertEquals(fileName, "n/A", alarm1.getReference()); 228 | assertEquals(fileName, 229 | "

Code:

" +
230 |                 "memcpy_x(&((*(PtrParIn->PtrComp))), &(*PtrGlb), sizeof((*(PtrParIn->PtrComp))));\n" +
231 |                 "                                    ~~~~~~~~~~
", 232 | alarm1.getDescription()); 233 | 234 | final Issue alarm2 = report.get(119); 235 | assertEquals(fileName, Severity.WARNING_HIGH, alarm2.getSeverity()); 236 | assertEquals(fileName, "Integer division by zero [Division or modulo by zero]", alarm2.getCategory()); 237 | assertEquals(fileName, "preprocessed/src/dhry/Proc7.c", alarm2.getFileName()); 238 | assertEquals(fileName, 78, alarm2.getLineStart()); 239 | assertEquals(fileName, 12, alarm2.getColumnStart()); 240 | assertEquals(fileName, 78, alarm2.getLineEnd()); 241 | assertEquals(fileName, 22, alarm2.getColumnEnd()); 242 | if (fileName.compareTo("report-20.04.xml") <= 0) { 243 | assertEquals(fileName, "ALARM (A): integer division by zero {0}", alarm2.getMessage()); 244 | } else { 245 | assertEquals(fileName, "ALARM (A) int_division_by_zero: divisor in {0}", alarm2.getMessage()); 246 | } 247 | assertEquals(fileName, "n/A", alarm2.getReference()); 248 | assertTrue(fileName, alarm2.getDescription().startsWith("

Code:

IntLoc = IntParI1/0;\n         ~~~~~~~~~~

Context:

l"));
249 |             assertTrue(fileName, alarm2.getDescription().endsWith("#call#Proc7
")); 250 | 251 | final Issue error1 = report.get(8); 252 | assertEquals(fileName, Severity.ERROR, error1.getSeverity()); 253 | if (fileName.compareTo("report-20.10.xml") <= 0) { 254 | assertEquals(fileName, "Definite runtime error [Errors]", error1.getCategory()); 255 | if (fileName.compareTo("report-20.04.xml") <= 0) { 256 | assertEquals(fileName, "ERROR: Definite runtime error during assignment in this context. Analysis stopped for this context.", error1.getMessage()); 257 | } else { 258 | assertEquals(fileName, "ERROR: Definite runtime error during assignment in this context. Analysis stopped for this context", error1.getMessage()); 259 | } 260 | } else { 261 | assertEquals(fileName, "Analysis stopped [Errors]", error1.getCategory()); 262 | assertEquals(fileName, "ERROR analysis_stopped: Definite runtime error during assignment in this context. Analysis stopped for this context", error1.getMessage()); 263 | } 264 | assertEquals(fileName, "preprocessed/src/scenarios.c", error1.getFileName()); 265 | assertEquals(fileName, 73, error1.getLineStart()); 266 | assertEquals(fileName, 8, error1.getColumnStart()); 267 | assertEquals(fileName, 73, error1.getLineEnd()); 268 | assertEquals(fileName, 25, error1.getColumnEnd()); 269 | assertEquals(fileName, "n/A", error1.getReference()); 270 | if (fileName.compareTo("report-20.04.xml") <= 0) { 271 | assertTrue(fileName, error1.getDescription().startsWith("

Context:

l"));
272 |             } else {
273 |                 assertTrue(fileName, error1.getDescription().startsWith("

Code:

ArrayBlock[i] = i;\n~~~~~~~~~~~~~~~~~

Context:

l"));
274 |             }
275 |             assertTrue(fileName, error1.getDescription().endsWith("
")); 276 | 277 | final Issue error2 = report.get(124); 278 | assertEquals(fileName, Severity.ERROR, error2.getSeverity()); 279 | if (fileName.compareTo("report-20.10.xml") <= 0) { 280 | assertEquals(fileName, "Definite runtime error [Errors]", error2.getCategory()); 281 | } else { 282 | assertEquals(fileName, "Analysis stopped [Errors]", error2.getCategory()); 283 | } 284 | assertEquals(fileName, "preprocessed/src/dhry/Proc7.c", error2.getFileName()); 285 | assertEquals(fileName, 78, error2.getLineStart()); 286 | assertEquals(fileName, 3, error2.getColumnStart()); 287 | assertEquals(fileName, 78, error2.getLineEnd()); 288 | assertEquals(fileName, 22, error2.getColumnEnd()); 289 | if (fileName.compareTo("report-20.04.xml") <= 0) { 290 | assertEquals(fileName, "ERROR: Definite runtime error during assignment in this context. Analysis stopped for this context.", error2.getMessage()); 291 | } else if (fileName.compareTo("report-20.10.xml") <= 0) { 292 | assertEquals(fileName, "ERROR: Definite runtime error during assignment in this context. Analysis stopped for this context", error2.getMessage()); 293 | } else { 294 | assertEquals(fileName, "ERROR analysis_stopped: Definite runtime error during assignment in this context. Analysis stopped for this context", error2.getMessage()); 295 | } 296 | assertEquals(fileName, "n/A", error2.getReference()); 297 | if (fileName.compareTo("report-20.04.xml") <= 0) { 298 | assertTrue(fileName, error2.getDescription().startsWith("

Context:

l"));
299 |             } else {
300 |                 assertTrue(fileName, error2.getDescription().startsWith("

Code:

IntLoc = IntParI1/0;\n~~~~~~~~~~~~~~~~~~~

Context:

l"));
301 |             }
302 |             assertTrue(fileName, error2.getDescription().endsWith("
")); 303 | } else { 304 | assertEquals(fileName, 127, report.size()); 305 | 306 | final Issue alarm1 = report.get(0); 307 | assertEquals(fileName, Severity.WARNING_HIGH, alarm1.getSeverity()); 308 | assertEquals(fileName, "Incompatible object pointer conversion [Failed coding rule checks]", alarm1.getCategory()); 309 | assertEquals(fileName, "preprocessed/src/dhry/Proc1.c", alarm1.getFileName()); 310 | assertEquals(fileName, 77, alarm1.getLineStart()); 311 | assertEquals(fileName, 44, alarm1.getColumnStart()); 312 | assertEquals(fileName, 77, alarm1.getLineEnd()); 313 | assertEquals(fileName, 54, alarm1.getColumnEnd()); 314 | assertEquals(fileName, "conversion between two incompatible pointer types: from <RecordType *> (aka <struct Record *>) to <char *>
ALARM (R) check_incompatible_object_pointer_conversion: check failed (violates A.1.11)", alarm1.getMessage()); 315 | assertEquals(fileName, "n/A", alarm1.getReference()); 316 | assertEquals(fileName, 317 | "

Code:

" +
318 |                 "memcpy_x(&((*(PtrParIn->PtrComp))), &(*PtrGlb), sizeof((*(PtrParIn->PtrComp))));\n" +
319 |                 "                                    ~~~~~~~~~~
", 320 | alarm1.getDescription()); 321 | 322 | final Issue error1 = report.get(120); 323 | assertEquals(fileName, Severity.ERROR, error1.getSeverity()); 324 | assertEquals(fileName, "Analysis stopped in critical context [Errors]", error1.getCategory()); 325 | assertEquals(fileName, "preprocessed/src/dhry/Proc7.c", error1.getFileName()); 326 | assertEquals(fileName, 78, error1.getLineStart()); 327 | assertEquals(fileName, 3, error1.getColumnStart()); 328 | assertEquals(fileName, 78, error1.getLineEnd()); 329 | assertEquals(fileName, 22, error1.getColumnEnd()); 330 | assertEquals(fileName, "ERROR analysis_stopped: Definite runtime error during assignment in this context. Analysis stopped for this context", error1.getMessage()); 331 | assertEquals(fileName, "n/A", error1.getReference()); 332 | assertTrue(fileName, error1.getDescription().startsWith("

Code:

IntLoc = IntParI1/0;\n~~~~~~~~~~~~~~~~~~~

Context:

l"));
333 |             assertTrue(fileName, error1.getDescription().endsWith("#call#Proc7
")); 334 | 335 | final Issue error2 = report.get(124); 336 | assertEquals(fileName, Severity.ERROR, error2.getSeverity()); 337 | assertEquals(fileName, "Analysis stopped in critical context [Errors]", error2.getCategory()); 338 | assertEquals(fileName, "preprocessed/src/dhry/Proc7.c", error2.getFileName()); 339 | assertEquals(fileName, 78, error2.getLineStart()); 340 | assertEquals(fileName, 3, error2.getColumnStart()); 341 | assertEquals(fileName, 78, error2.getLineEnd()); 342 | assertEquals(fileName, 22, error2.getColumnEnd()); 343 | assertEquals(fileName, "ERROR analysis_stopped: Definite runtime error during assignment in this context. Analysis stopped for this context", error2.getMessage()); 344 | assertEquals(fileName, "n/A", error2.getReference()); 345 | assertTrue(fileName, error2.getDescription().startsWith("

Code:

IntLoc = IntParI1/0;\n~~~~~~~~~~~~~~~~~~~

Context:

l"));
346 |             assertTrue(fileName, error2.getDescription().endsWith("#call#Proc7
")); 347 | } 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /src/main/java/com/absint/astree/AstreeBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2016, AbsInt Angewandte Informatik GmbH 5 | * Author: Dr.-Ing. Joerg Herter 6 | * Email: herter@absint.com 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | * THE SOFTWARE. 25 | */ 26 | 27 | package com.absint.astree; 28 | import hudson.Proc; 29 | import hudson.Launcher; 30 | import hudson.EnvVars; 31 | import hudson.Extension; 32 | import hudson.FilePath; 33 | import hudson.util.ArgumentListBuilder; 34 | import hudson.util.FormValidation; 35 | import hudson.model.AbstractProject; 36 | import hudson.model.Run; 37 | import hudson.model.TaskListener; 38 | import hudson.tasks.Builder; 39 | import hudson.tasks.BuildStepDescriptor; 40 | import jenkins.tasks.SimpleBuildStep; 41 | import net.sf.json.JSONObject; 42 | import org.kohsuke.stapler.DataBoundConstructor; 43 | import org.kohsuke.stapler.StaplerRequest; 44 | import org.kohsuke.stapler.QueryParameter; 45 | 46 | import javax.servlet.ServletException; 47 | import java.io.*; 48 | import java.util.*; 49 | import java.util.regex.Pattern; 50 | import java.util.regex.Matcher; 51 | 52 | 53 | /** 54 | * 55 | * When the user configures the project and enables this builder, 56 | * {@link DescriptorImpl#newInstance(StaplerRequest)} is invoked 57 | * and a new {@link AstreeBuilder} is created. The created 58 | * instance is persisted to the project configuration XML by using 59 | * XStream, so this allows you to use instance fields 60 | * to remember the configuration. 61 | * 62 | * When a build is performed, the {@link #perform} method will be invoked. 63 | * 64 | * @author AbsInt Angewandte Informatik GmbH 65 | */ 66 | public class AstreeBuilder extends Builder implements SimpleBuildStep { 67 | private static final String PLUGIN_NAME = "Astrée for C Jenkins PlugIn"; 68 | private static final String BUILD_NR = "24.10"; 69 | 70 | private static final String TMP_REPORT_FILE = "absint_astree_analysis_report"; 71 | private static final String TMP_PREPROCESS_OUTPUT = "absint_astree_preprocess_output.txt"; 72 | 73 | private String dax_file, output_dir, analysis_id; 74 | private FailonSwitch failonswitch; 75 | private boolean genXMLOverview, genXMLCoverage, genXMLAlarmsByOccurence, 76 | genXMLAlarmsByCategory, genXMLAlarmsByFile, genXMLRulechecks, 77 | genPreprocessOutput, dropAnalysis; 78 | private boolean skip_analysis; 79 | 80 | private Proc proc; // reference to an associated a3c client process 81 | 82 | // Fields in config.jelly must match the parameter names in the "DataBoundConstructor" 83 | @DataBoundConstructor 84 | public AstreeBuilder( String dax_file, String analysis_id, String output_dir, boolean skip_analysis, 85 | boolean genXMLOverview, boolean genXMLCoverage, boolean genXMLAlarmsByOccurence, 86 | boolean genXMLAlarmsByCategory, boolean genXMLAlarmsByFile, boolean genXMLRulechecks, 87 | boolean dropAnalysis, boolean genPreprocessOutput, FailonSwitch failonswitch, 88 | AnalysisServerConfiguration analysisSrv 89 | ) 90 | { 91 | this.dax_file = dax_file; 92 | this.analysis_id = analysis_id; 93 | this.output_dir = output_dir; 94 | this.skip_analysis = skip_analysis; 95 | this.failonswitch = failonswitch; 96 | 97 | this.genXMLOverview = genXMLOverview; 98 | this.genXMLCoverage = genXMLCoverage; 99 | this.genXMLAlarmsByOccurence = genXMLAlarmsByOccurence; 100 | this.genXMLAlarmsByCategory = genXMLAlarmsByCategory; 101 | this.genXMLAlarmsByFile = genXMLAlarmsByFile; 102 | this.genXMLRulechecks = genXMLRulechecks; 103 | 104 | this.dropAnalysis = dropAnalysis; 105 | this.genPreprocessOutput = genPreprocessOutput; 106 | } 107 | 108 | /* 109 | * Interface to config.jelly. 110 | */ 111 | 112 | /** 113 | * Returns the currently set path to the DAX file used for the analysis run. 114 | * 115 | * @return java.lang.String 116 | */ 117 | public String getDax_file() { 118 | return dax_file; 119 | } 120 | 121 | /** 122 | * Returns the currently set analysis ID used for the analysis run. 123 | * 124 | * @return java.lang.String 125 | */ 126 | public String getAnalysis_id() { 127 | return analysis_id; 128 | } 129 | 130 | /** 131 | * Returns the currently set path used as output directory for the analyses. 132 | * 133 | * @return java.lang.String 134 | */ 135 | public String getOutput_dir() { 136 | return output_dir; 137 | } 138 | 139 | /** 140 | * Indicates whether the analysis run is configured to potentially fail a build. 141 | * 142 | * @return boolean 143 | */ 144 | public boolean isFailonswitch() { 145 | return (this.failonswitch != null); 146 | } 147 | 148 | /** 149 | * @return java.lang.String 150 | */ 151 | public String getFailon() { 152 | if(this.failonswitch == null) return ""; 153 | return this.failonswitch.getFailon(); 154 | } 155 | 156 | /** 157 | * Indicates whether the analysis run is configured to 158 | * be temporarily skipped (i.e., no analysis is to be done). 159 | * 160 | * @return boolean 161 | */ 162 | public boolean isSkip_analysis() { 163 | return this.skip_analysis; 164 | } 165 | 166 | /** 167 | * Indicates whether the analysis run is configured to produce the 168 | * XML overview summary. 169 | * 170 | * @return boolean 171 | */ 172 | public boolean isGenXMLOverview() { 173 | return this.genXMLOverview; 174 | } 175 | 176 | /** 177 | * Indicates whether the analysis run is configured to produce the 178 | * XML coverage summary. 179 | * 180 | * @return boolean 181 | */ 182 | public boolean isGenXMLCoverage() { 183 | return this.genXMLCoverage; 184 | } 185 | 186 | /** 187 | * Indicates whether the analysis run is configured to produce the 188 | * XML alarms-by-occurence summary. 189 | * 190 | * @return boolean 191 | */ 192 | public boolean isGenXMLAlarmsByOccurence() { 193 | return this.genXMLAlarmsByOccurence; 194 | } 195 | 196 | /** 197 | * Indicates whether the analysis run is configured to produce the 198 | * XML alarms-by-category summary. 199 | * 200 | * @return boolean 201 | */ 202 | public boolean isGenXMLAlarmsByCategory() { 203 | return this.genXMLAlarmsByCategory; 204 | } 205 | 206 | /** 207 | * Indicates whether the analysis run is configured to produce the 208 | * XML alarms-by-file summary. 209 | * 210 | * @return boolean 211 | */ 212 | public boolean isGenXMLAlarmsByFile() { 213 | return this.genXMLAlarmsByFile; 214 | } 215 | 216 | /** 217 | * Indicates whether the analysis run is configured to produce the 218 | * XML rule checks summary. 219 | * 220 | * @return boolean 221 | */ 222 | public boolean isGenXMLRulechecks() { 223 | return this.genXMLRulechecks; 224 | } 225 | 226 | /** 227 | * Indicates whether the analysis run is configured to produce the 228 | * (text) preprocess output report. 229 | * 230 | * @return boolean 231 | */ 232 | public boolean isGenPreprocessOutput() { 233 | return this.genPreprocessOutput; 234 | } 235 | 236 | /** 237 | * Indicates whether the project is to be deleted on the server after 238 | * the analysis run. 239 | * 240 | * @return boolean 241 | */ 242 | public boolean isDropAnalysis() { 243 | return this.dropAnalysis; 244 | } 245 | 246 | 247 | /* 248 | * end interface to config.jelly. 249 | */ 250 | 251 | 252 | /** 253 | * Expands environment variables of the form 254 | * ${VAR_NAME} 255 | * by their current value. 256 | * 257 | * @param cmdln the java.lang.String, usually a command line, 258 | * in which to expand variables 259 | * @param envMap a java.util.Map containing the environment variables 260 | * and their current values 261 | * @param isUnix boolean 262 | * @return the input String with environment variables expanded to their current value 263 | */ 264 | private static final String expandEnvironmentVarsHelper( 265 | String cmdln, Map envMap, boolean isUnix ) { 266 | final String pattern = "\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}"; 267 | final Pattern expr = Pattern.compile(pattern); 268 | Matcher matcher = expr.matcher(cmdln); 269 | String envValue; 270 | Pattern subexpr; 271 | while (matcher.find()) { 272 | envValue = envMap.get(matcher.group(1).toUpperCase()); 273 | if (envValue == null) { 274 | envValue = ""; 275 | } else { 276 | envValue = envValue.replace("\\", "\\\\"); 277 | } 278 | subexpr = Pattern.compile(Pattern.quote(matcher.group(0))); 279 | cmdln = subexpr.matcher(cmdln).replaceAll(envValue); 280 | } 281 | 282 | if(isUnix) { 283 | return cmdln.replace('\\','/'); 284 | } else { 285 | return cmdln.replace('/','\\'); 286 | } 287 | } 288 | 289 | 290 | /** 291 | */ 292 | private ArgumentListBuilder constructCommandLineCall(String reportfile, EnvVars envVars, boolean isUnix, FilePath preprocessOutput) { 293 | final ArgumentListBuilder builder = new ArgumentListBuilder(); 294 | 295 | builder.add(getDescriptor().getAlauncher()); 296 | builder.add("-b"); 297 | builder.add("-s", expandEnvironmentVarsHelper(getDescriptor().getAstree_server(), envVars, isUnix)); 298 | if (analysis_id != null && !analysis_id.trim().isEmpty()) { 299 | builder.add("--id", expandEnvironmentVarsHelper(analysis_id, envVars, isUnix)); 300 | } 301 | if (dax_file != null && !dax_file.trim().isEmpty()) { 302 | builder.add("--import", expandEnvironmentVarsHelper(dax_file, envVars, isUnix)); 303 | } 304 | builder.add("--report-file", reportfile + ".txt"); 305 | builder.add("--xml-report-file", reportfile + ".xml"); 306 | if (genPreprocessOutput) { 307 | builder.add("--preprocess-report-file", preprocessOutput.getRemote()); 308 | } 309 | if (dropAnalysis) { 310 | builder.add("--drop"); 311 | } 312 | 313 | return builder; 314 | } 315 | 316 | 317 | @Override 318 | public void perform(Run build, FilePath workspace, Launcher launcher, TaskListener listener) { 319 | int exitCode = -1; 320 | // Set some defaults and parameters. 321 | if(output_dir == null || output_dir.equals("")) 322 | output_dir = workspace.toString(); 323 | String reportfile = workspace.toString() + (launcher.isUnix() ? "/" : "\\") + TMP_REPORT_FILE; 324 | 325 | FilePath rfile; 326 | try { 327 | // Analysis run started. ID plugin in Jenkins output. 328 | listener.getLogger().println("This is " + PLUGIN_NAME + " in version " + BUILD_NR); 329 | // Clear log file 330 | rfile = new FilePath(workspace, TMP_REPORT_FILE + ".txt"); 331 | if( rfile.delete() ) 332 | listener.getLogger().println("Old log file erased."); 333 | rfile.touch(System.currentTimeMillis()); 334 | listener.getLogger().println("New log file created."); 335 | // Create log file reader thread 336 | StatusPoller sp = new StatusPoller(1000, listener, rfile); 337 | 338 | if(this.skip_analysis) { 339 | listener.getLogger().println("Analysis run has been (temporarily) deactivated. Skipping analysis run."); 340 | return; // nothing to do, exit method. 341 | } 342 | 343 | // Print some configuration info. 344 | if(failonswitch != null) 345 | listener.getLogger().println( "Astrée fails build on " + failonswitch.getFailon() ); 346 | 347 | String infoStringSummaryDest = "Summary reports will be generated in " + output_dir; 348 | infoStringSummaryDest = expandEnvironmentVarsHelper( 349 | "Summary reports will be generated in " + output_dir, 350 | build.getEnvironment(listener), 351 | launcher.isUnix()); 352 | listener.getLogger().println(infoStringSummaryDest); 353 | 354 | final EnvVars envVars = build.getEnvironment(listener); 355 | if (!getDescriptor().getUser().trim().isEmpty() && !getDescriptor().getPassword().trim().isEmpty()) { 356 | envVars.put("A3_SERVER_USER", getDescriptor().getUser()); 357 | envVars.put("A3_SERVER_PASSWORD", getDescriptor().getPassword()); 358 | } 359 | 360 | sp.start(); // Start log file reader 361 | proc = launcher.launch() 362 | .cmds(constructCommandLineCall(reportfile, build.getEnvironment(listener), launcher.isUnix(), workspace.child(TMP_PREPROCESS_OUTPUT))) 363 | .envs(envVars) 364 | .stdout(listener.getLogger()) 365 | .pwd(workspace) 366 | .start(); 367 | exitCode = proc.join(); // Wait for Astree to finish 368 | sp.kill(); // Stop log file reader 369 | sp.join(); // Wait for log file reader to finish 370 | if(exitCode == 0) 371 | listener.getLogger().println("Analysis run succeeded."); 372 | else 373 | listener.getLogger().println("Analysis run failed."); 374 | } catch (IOException e) { 375 | e.printStackTrace(); 376 | listener.getLogger().println("IOException caught during analysis run."); 377 | } catch (InterruptedException e) { 378 | e.printStackTrace(); 379 | listener.getLogger().println("InterruptedException caught during analysis run."); 380 | } 381 | if(exitCode == 0) { // activities after successful analysis run 382 | /* Read analysis summary and 383 | check whether Astrée shall fail the build due to reported errors etc */ 384 | AnalysisSummary summary = AnalysisSummary.readFromReportFile(reportfile + ".txt"); 385 | if( failonswitch != null && failonswitch.failOnErrors() 386 | && summary.getNumberOfErrors() > 0) { 387 | listener.getLogger().println( "Errors reported! Number of errors: " + 388 | summary.getNumberOfErrors()); 389 | build.setResult(hudson.model.Result.FAILURE); 390 | } 391 | else if( failonswitch != null && failonswitch.failOnAlarms() 392 | && summary.getNumberOfAlarms() > 0) { 393 | listener.getLogger().println( "Alarms reported! Number of alarms: " + 394 | summary.getNumberOfAlarms()); 395 | 396 | build.setResult(hudson.model.Result.FAILURE); 397 | } 398 | else if( failonswitch != null && failonswitch.failOnFlowAnomalies() 399 | && ( summary.getNumberOfFlowAnomalies() 400 | + summary.getNumberOfAlarms() > 0) ) { 401 | build.setResult(hudson.model.Result.FAILURE); 402 | } 403 | } else { // activities after unsuccessful analysis run 404 | // If Astrée cannot be invoked, conservatively fail the build... 405 | build.setResult(hudson.model.Result.FAILURE); 406 | } 407 | } 408 | 409 | /** 410 | * Override finalize method to ensure existing a3c client processes are killed upon destruction 411 | * of AstreeBuilder objects. 412 | */ 413 | protected void finalize() { 414 | try { 415 | if(proc != null) 416 | proc.kill(); 417 | } catch(Exception e) { 418 | } 419 | } 420 | 421 | // Overridden for better type safety. 422 | // If your plugin doesn't really define any property on Descriptor, 423 | // you don't have to do this. 424 | @Override 425 | public DescriptorImpl getDescriptor() { 426 | return (DescriptorImpl)super.getDescriptor(); 427 | } 428 | 429 | 430 | 431 | private void copyText2PrintStream( PrintStream dest, String srcPath ) { 432 | dest.println("Appending analysis report."); 433 | try{ 434 | BufferedReader br = new BufferedReader( 435 | new InputStreamReader( 436 | new FileInputStream(srcPath), "UTF-8" )); 437 | String line = br.readLine() ; 438 | while(line != null) { 439 | dest.println(line); 440 | line = br.readLine(); 441 | } 442 | br.close(); 443 | } catch(IOException e) { 444 | } 445 | } 446 | 447 | 448 | /** 449 | * Descriptor for {@link AstreeBuilder}. Used as a singleton. 450 | * The class is marked as public so that it can be accessed from views. 451 | * 452 | *
453 | */ 454 | @Extension // This indicates to Jenkins that this is an implementation of an extension point. 455 | public static final class DescriptorImpl extends BuildStepDescriptor { 456 | /* 457 | * To persist global configuration information, 458 | * simply store it in a field and call save(). 459 | * 460 | * 461 | * If you don't want fields to be persisted, use "transient". 462 | */ 463 | 464 | /* 465 | * Properties set by the Astree configuration mask: 466 | * Jenkins~~~Manage Jenkins~~~Configure System 467 | */ 468 | private String alauncher; 469 | private String astree_server; 470 | private String user, password; 471 | 472 | /** 473 | * Constructor. 474 | *
475 | * Constructs a new object of this class and 476 | * loads the persisted global configuration. 477 | */ 478 | public DescriptorImpl() { 479 | load(); 480 | } 481 | 482 | /** 483 | * Return the human readable name used in the configuration screen. 484 | * 485 | * @return java.lang.String 486 | */ 487 | public String getDisplayName() { 488 | return "Astrée Analysis Run"; 489 | } 490 | 491 | 492 | /** 493 | * Performs on-the-fly validation of the form field 'astree_server'. 494 | * 495 | * @param value The value that the user has typed. 496 | * @return 497 | * Indicates the outcome of the validation. This is sent to the browser. 498 | *
499 | * Note that returning {@link FormValidation#error(String)} does not 500 | * prevent the form from being saved. It just means that a message 501 | * will be displayed to the user. 502 | * @throws IOException as super class 503 | * @throws ServletException as super class 504 | **/ 505 | public FormValidation doCheckAstree_server(@QueryParameter String value) 506 | throws IOException, ServletException { 507 | if(value == null || value.trim().equals("") ) 508 | return FormValidation.error("Please set a valid server of form :"); 509 | if ( !( value.matches("[a-zA-Z][a-zA-Z0-9\\.\\-]{0,22}[a-zA-Z0-9]:\\d{1,5}") /* hostname */ 510 | || value.matches("(\\d{1,3}\\.){3,3}\\d{1,3}:\\d{1,5}" /* ip address */) ) ) 511 | return FormValidation.warning("The Astrée Server needs to be specified as a hostname followed by a colon followed by a port number."); 512 | return FormValidation.ok(); 513 | } 514 | 515 | /** 516 | * Performs on-the-fly validation of the form field 'alauncher'. 517 | * 518 | * @param value The value that the user has typed. 519 | * @param project unused 520 | * @return 521 | * Indicates the outcome of the validation. This is sent to the browser. 522 | *
523 | * Note that returning {@link FormValidation#error(String)} does not 524 | * prevent the form from being saved. It just means that a message 525 | * will be displayed to the user. 526 | * @throws IOException as super class 527 | * @throws ServletException as super class 528 | **/ 529 | public FormValidation doCheckAlauncher(@QueryParameter String value, AbstractProject project) 530 | throws IOException, ServletException { 531 | if(value == null || value.trim().equals("") ) 532 | return FormValidation.error("No file specified."); 533 | 534 | File ftmp = new File(value); 535 | if (!ftmp.exists()) 536 | return FormValidation.error("Specified file not found."); 537 | if(!ftmp.isFile()) 538 | return FormValidation.error("Specified file is not a normal file."); 539 | if (!ftmp.canExecute()) 540 | return FormValidation.error("Specified file cannot be executed."); 541 | return FormValidation.ok(); 542 | } 543 | 544 | 545 | 546 | /** 547 | * Helper method to check whether a string contains an environment variable of form 548 | *
${IDENTIFIER}
549 | * 550 | * @param s String to scan for environment variable expressions 551 | * @return Outcome of the check as a boolean (true if such an expression 552 | * was found, otherwise false). 553 | */ 554 | public static final boolean containsEnvVars(String s) 555 | { 556 | final String pattern = "\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}"; 557 | final Pattern expr = Pattern.compile(pattern); 558 | Matcher matcher = expr.matcher(s); 559 | return matcher.find(); 560 | } 561 | 562 | 563 | /** 564 | * Performs on-the-fly validation of the form field 'dax_file'. 565 | * 566 | * @param value 567 | * This parameter receives the value that the user has typed. 568 | * @return 569 | * Indicates the outcome of the validation. This is sent to the browser. 570 | *
571 | * Note that returning {@link FormValidation#error(String)} does not 572 | * prevent the form from being saved. It just means that a message 573 | * will be displayed to the user. 574 | * @throws IOException as super class 575 | * @throws ServletException as super class 576 | **/ 577 | public FormValidation doCheckDax_file(@QueryParameter String value) 578 | throws IOException, ServletException { 579 | if ( 580 | (value == null || value.trim().equals("")) 581 | ) 582 | return FormValidation.warning("No DAX file specified. Only ID will be used."); 583 | 584 | if(containsEnvVars(value)) { 585 | return FormValidation.warning("The specified path contains an environment variable, please make sure the constructed paths are correct."); 586 | } 587 | 588 | File ftmp = new File(value); 589 | if (!ftmp.exists()) 590 | return FormValidation.error("Specified file not found."); 591 | if (!ftmp.canRead()) 592 | return FormValidation.error("Specified file cannot be read."); 593 | if (!value.endsWith(".dax")) 594 | return FormValidation.warning("The specified file exists, but does not have the expected suffix (.dax)."); 595 | 596 | return FormValidation.ok(); 597 | } 598 | 599 | /** 600 | * Performs on-the-fly validation of the form field 'analysis_id'. 601 | * 602 | * @param value 603 | * This parameter receives the value that the user has typed. 604 | * @return 605 | * Indicates the outcome of the validation. This is sent to the browser. 606 | *
607 | * Note that returning {@link FormValidation#error(String)} does not 608 | * prevent the form from being saved. It just means that a message 609 | * will be displayed to the user. 610 | * @throws IOException as super class 611 | * @throws ServletException as super class 612 | **/ 613 | public FormValidation doCheckAnalysis_id(@QueryParameter String value) 614 | throws IOException, ServletException { 615 | if ( 616 | (value == null || value.trim().equals("")) 617 | ) 618 | return FormValidation.warning("No ID specified. Only DAX file will be used."); 619 | 620 | if(containsEnvVars(value)) { 621 | return FormValidation.warning("The ID contains an environment variable, please make sure that the constructed IDs are valid."); 622 | } 623 | 624 | 625 | if(!value.matches("\\d*")) 626 | return FormValidation.error("ID is not valid."); 627 | 628 | return FormValidation.ok(); 629 | } 630 | 631 | /** 632 | * Performs on-the-fly validation of the form field 'output_dir'. 633 | * 634 | * @param value 635 | * This parameter receives the value that the user has typed. 636 | * @return 637 | * Indicates the outcome of the validation. This is sent to the browser. 638 | *
639 | * Note that returning {@link FormValidation#error(String)} does not 640 | * prevent the form from being saved. It just means that a message 641 | * will be displayed to the user. 642 | * @throws IOException as super class 643 | * @throws ServletException as super class 644 | **/ 645 | public FormValidation doCheckOutput_dir(@QueryParameter String value) 646 | throws IOException, ServletException { 647 | if(value == null || value.trim().equals("") ) 648 | return FormValidation.warning("No directory specified."); 649 | 650 | if(containsEnvVars(value)) { 651 | return FormValidation.warning("The specified path contains an environment variable, please make sure that the constructed paths are correct."); 652 | } 653 | 654 | File ftmp = new File(value); 655 | if (!ftmp.exists()) 656 | return FormValidation.error("Specified directory not found."); 657 | if (!ftmp.isDirectory()) 658 | return FormValidation.error("Specified path is no directory."); 659 | if (!ftmp.canRead() || !ftmp.canWrite()) 660 | return FormValidation.warning("No permissions to read/write the specified directory."); 661 | 662 | return FormValidation.ok(); 663 | } 664 | 665 | 666 | 667 | /** 668 | * Indicates that this builder can be used with all kinds of project types. 669 | * 670 | * @return boolean 671 | */ 672 | public boolean isApplicable(Class aClass) { 673 | return true; 674 | } 675 | 676 | /** 677 | * Sets a new configuration. 678 | * 679 | * @throws FormException as super class 680 | */ 681 | @Override 682 | public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { 683 | // To persist global configuration information, 684 | // set that to properties and call save(). 685 | this.alauncher = formData.getString("alauncher"); 686 | this.astree_server = formData.getString("astree_server"); 687 | this.user = formData.getString("user"); 688 | this.password = formData.getString("password"); 689 | // ... data set, so call save(): 690 | save(); 691 | return super.configure(req,formData); 692 | } 693 | 694 | /** 695 | * Returns the currently configured Astrée server 696 | * (as host:port). 697 | * 698 | * @return java.lang.String 699 | */ 700 | public String getAstree_server() { 701 | return this.astree_server; 702 | } 703 | 704 | /** 705 | * Returns the currently configured alauncher. 706 | * 707 | * @return java.lang.String 708 | */ 709 | public String getAlauncher() { 710 | return this.alauncher; 711 | } 712 | 713 | 714 | /** 715 | * Returns the currently configured Astrée user. 716 | * 717 | * @return java.lang.String 718 | */ 719 | public String getUser() { 720 | return this.user; 721 | } 722 | 723 | /** 724 | * Returns the currently configured Astrée (user) password. 725 | * 726 | * @return java.lang.String 727 | */ 728 | public String getPassword() { 729 | return this.password; 730 | } 731 | } 732 | } 733 | --------------------------------------------------------------------------------