diff --git a/frontend-apps/src/main/webapp/i18n/messageBundle.properties b/frontend-apps/src/main/webapp/i18n/messageBundle.properties index 3f71f9599..4cb842eac 100755 --- a/frontend-apps/src/main/webapp/i18n/messageBundle.properties +++ b/frontend-apps/src/main/webapp/i18n/messageBundle.properties @@ -61,12 +61,13 @@ ratioConstructApp=Application ratioConstructDeps=Total (app + dependencies) ratioConstructRatio=Percentage (app/total) # Metric names coming from the backend service -class_ratio=Classes +class_ratio=Classes, interfaces, enums package_ratio=Packages executable_ratio=Executable constructs (methods, constructors, static initializers) # Test coverage table packages=Application Package +interfaces=Interfaces (traced/total) constructors=Constructors (traced/total) methods=Methods (traced/total) modules=Modules (traced/total) diff --git a/frontend-apps/src/main/webapp/i18n/messageBundle_de.properties b/frontend-apps/src/main/webapp/i18n/messageBundle_de.properties index 67381ec06..7bcb3a6c6 100755 --- a/frontend-apps/src/main/webapp/i18n/messageBundle_de.properties +++ b/frontend-apps/src/main/webapp/i18n/messageBundle_de.properties @@ -48,12 +48,13 @@ ratioConstructApp=Anwendung ratioConstructDeps=Total (Anwendung + Abh�ngigkeiten) ratioConstructRatio=Prozent (Anwendung/Total) # Metric names coming from the backend service -class_ratio=Klassen +class_ratio=Klassen, Schnittstellen, Aufz�hlungstypen package_ratio=Pakete executable_ratio=Ausf�hrbare Konstrukte (Methoden, Konstruktoren, statische Initialisierer) # Test coverage table packages=Anwendungspaket +interfaces=Interfaces (traced/total) constructors=Konstruktoren (traced/total) methods=Methoden (traced/total) static_inits=Statische Initialisierer (traced/total) diff --git a/frontend-apps/src/main/webapp/i18n/messageBundle_en.properties b/frontend-apps/src/main/webapp/i18n/messageBundle_en.properties index 2ba7c3991..e98e22a59 100755 --- a/frontend-apps/src/main/webapp/i18n/messageBundle_en.properties +++ b/frontend-apps/src/main/webapp/i18n/messageBundle_en.properties @@ -54,12 +54,13 @@ ratioConstructApp=Application ratioConstructDeps=Total (app + dependencies) ratioConstructRatio=Percentage (app/total) # Metric names coming from the backend service -class_ratio=Classes +class_ratio=Classes, interfaces, enums package_ratio=Packages executable_ratio=Executable constructs (methods, constructors, static initializers) # Test coverage table packages=Application Package +interfaces=Interfaces (traced/total) constructors=Constructors (traced/total) methods=Methods (traced/total) static_inits=Static Initializers (traced/total) diff --git a/frontend-apps/src/main/webapp/i18n/messageBundle_en_US.properties b/frontend-apps/src/main/webapp/i18n/messageBundle_en_US.properties index 2ba7c3991..e98e22a59 100644 --- a/frontend-apps/src/main/webapp/i18n/messageBundle_en_US.properties +++ b/frontend-apps/src/main/webapp/i18n/messageBundle_en_US.properties @@ -54,12 +54,13 @@ ratioConstructApp=Application ratioConstructDeps=Total (app + dependencies) ratioConstructRatio=Percentage (app/total) # Metric names coming from the backend service -class_ratio=Classes +class_ratio=Classes, interfaces, enums package_ratio=Packages executable_ratio=Executable constructs (methods, constructors, static initializers) # Test coverage table packages=Application Package +interfaces=Interfaces (traced/total) constructors=Constructors (traced/total) methods=Methods (traced/total) static_inits=Static Initializers (traced/total) diff --git a/frontend-apps/src/main/webapp/view/Component.controller.js b/frontend-apps/src/main/webapp/view/Component.controller.js index dd5fbd717..9b230b882 100755 --- a/frontend-apps/src/main/webapp/view/Component.controller.js +++ b/frontend-apps/src/main/webapp/view/Component.controller.js @@ -600,6 +600,7 @@ sap.ui.controller("view.Component", { singlePackage.modules=packageCounters[pack].MODU; singlePackage.functions=packageCounters[pack].FUNC; singlePackage.static_inits=packageCounters[pack].INIT; + singlePackage.interfaces=packageCounters[pack].INTF; if(packageCountersTraced!=null&&packageCountersTraced[pack]){ //add if else to avoid traced CONSTRUCTORS > traced @@ -611,6 +612,7 @@ sap.ui.controller("view.Component", { singlePackage.modulesTested=packageCountersTraced[pack].MODU; singlePackage.functionsTested=packageCountersTraced[pack].FUNC; singlePackage.static_initsTested=packageCountersTraced[pack].INIT; + singlePackage.interfacesTested=packageCountersTraced[pack].INTF; traced = traced + packageCountersTraced[pack].countExecutable; } else { @@ -619,6 +621,7 @@ sap.ui.controller("view.Component", { singlePackage.static_initsTested = 0; singlePackage.modulesTested = 0; singlePackage.functionsTested = 0; + singlePackage.interfacesTested=0; } //TODO ADD MODU & FUNCTION? (INITIATED diff --git a/lang-java/pom.xml b/lang-java/pom.xml index cbd421211..532b49983 100644 --- a/lang-java/pom.xml +++ b/lang-java/pom.xml @@ -75,6 +75,12 @@ commons-io commons-io + + + org.vafer + jdependency + 2.7.0 + @@ -105,6 +111,7 @@ + ${basedir}/src/test/java org.apache.maven.plugins diff --git a/lang-java/src/main/java/org/eclipse/steady/java/ClassFileAnalyzer.java b/lang-java/src/main/java/org/eclipse/steady/java/ClassFileAnalyzer.java index 3bdf80621..cd76e3659 100644 --- a/lang-java/src/main/java/org/eclipse/steady/java/ClassFileAnalyzer.java +++ b/lang-java/src/main/java/org/eclipse/steady/java/ClassFileAnalyzer.java @@ -108,18 +108,15 @@ public Map getConstructs() throws FileAnalysisException // TODO HP, 4.4.16: This does not yet seem to work ClassPoolUpdater.getInstance().updateClasspath(ctclass, this.file); - // Only add constructs of ctclass is either a class or enum (no interfaces) - if (ctclass.isInterface()) { - ClassFileAnalyzer.log.debug("Interface [" + ctclass.getName() + "] skipped"); - } else { - // Use a class visitor to get all constructs from the class file - final ClassVisitor cv = new ClassVisitor(ctclass); - final Set temp_constructs = cv.getConstructs(); - - // Add all constructs with a "" body - // TODO: Change Construct so that string (for source files) and binary bodies (file - // compiled classes) can be covered - for (ConstructId c : temp_constructs) this.constructs.put(c, new Construct(c, "")); + // Use a class visitor to get all constructs from the class file + final ClassVisitor cv = new ClassVisitor(ctclass); + final Set temp_constructs = cv.getConstructs(); + + // Add all constructs with a "" body + // TODO: Change Construct so that string (for source files) and binary bodies (file + // compiled classes) can be covered + for (ConstructId c : temp_constructs) { + this.constructs.put(c, new Construct(c, "")); } } catch (FileNotFoundException e) { throw new FileAnalysisException(e.getMessage(), e); diff --git a/lang-java/src/main/java/org/eclipse/steady/java/JarAnalyzer.java b/lang-java/src/main/java/org/eclipse/steady/java/JarAnalyzer.java index aec5ad895..61b6b284f 100644 --- a/lang-java/src/main/java/org/eclipse/steady/java/JarAnalyzer.java +++ b/lang-java/src/main/java/org/eclipse/steady/java/JarAnalyzer.java @@ -526,62 +526,57 @@ else if (je.getName().endsWith("pom.xml")) { try { ctclass = JarAnalyzer.getClassPool().get(cn); - // Ignore interfaces (no executable code) and enums (rarely containing executable code, - // perhaps to be included later on) - if (ctclass.isInterface()) { - this.interfaceCount++; - } else { - - if (ctclass.isEnum()) this.enumCount++; - else this.classCount++; - - // Create ClassVisitor for the current Java class - cv = new ClassVisitor(ctclass); - this.constructs.addAll(cv.getConstructs()); - - // Instrument (if requested and not blacklisted) - if (this.instrument && !this.instrControl.isBlacklistedClass(cn)) { - cv.setOriginalArchiveDigest(this.getSHA1()); - cv.setAppContext(JarAnalyzer.getAppContext()); - if (cv.isInstrumented()) - this.instrControl.updateInstrumentationStatistics(cv.getJavaId(), null); - else { - try { - cv.visitMethods(true); - cv.visitConstructors(true); - cv.finalizeInstrumentation(); - this.instrumentedClasses.put(cv.getJavaId(), cv); - this.instrControl.updateInstrumentationStatistics( - cv.getJavaId(), Boolean.valueOf(true)); - } catch (IOException ioe) { - JarAnalyzer.log.error( - "I/O exception while instrumenting class [" - + cv.getJavaId().getQualifiedName() - + "]: " - + ioe.getMessage()); - this.instrControl.updateInstrumentationStatistics( - cv.getJavaId(), Boolean.valueOf(false)); - } catch (CannotCompileException cce) { - JarAnalyzer.log.warn( - "Cannot compile instrumented class [" - + cv.getJavaId().getQualifiedName() - + "]: " - + cce.getMessage()); - this.instrControl.updateInstrumentationStatistics( - cv.getJavaId(), Boolean.valueOf(false)); - } catch (Exception e) { - JarAnalyzer.log.error( - e.getClass().getName() - + " occured while instrumenting class [" - + cv.getJavaId().getQualifiedName() - + "]: " - + e.getMessage()); - this.instrControl.updateInstrumentationStatistics( - cv.getJavaId(), Boolean.valueOf(false)); - } + if (ctclass.isEnum()) this.enumCount++; + else if (ctclass.isInterface()) this.interfaceCount++; + else this.classCount++; + + // Create ClassVisitor for the current Java class + cv = new ClassVisitor(ctclass); + this.constructs.addAll(cv.getConstructs()); + + // Instrument (if requested and not blacklisted) + if (this.instrument && !this.instrControl.isBlacklistedClass(cn)) { + cv.setOriginalArchiveDigest(this.getSHA1()); + cv.setAppContext(JarAnalyzer.getAppContext()); + if (cv.isInstrumented()) + this.instrControl.updateInstrumentationStatistics(cv.getJavaId(), null); + else { + try { + cv.visitMethods(true); + cv.visitConstructors(true); + cv.finalizeInstrumentation(); + this.instrumentedClasses.put(cv.getJavaId(), cv); + this.instrControl.updateInstrumentationStatistics( + cv.getJavaId(), Boolean.valueOf(true)); + } catch (IOException ioe) { + JarAnalyzer.log.error( + "I/O exception while instrumenting class [" + + cv.getJavaId().getQualifiedName() + + "]: " + + ioe.getMessage()); + this.instrControl.updateInstrumentationStatistics( + cv.getJavaId(), Boolean.valueOf(false)); + } catch (CannotCompileException cce) { + JarAnalyzer.log.warn( + "Cannot compile instrumented class [" + + cv.getJavaId().getQualifiedName() + + "]: " + + cce.getMessage()); + this.instrControl.updateInstrumentationStatistics( + cv.getJavaId(), Boolean.valueOf(false)); + } catch (Exception e) { + JarAnalyzer.log.error( + e.getClass().getName() + + " occured while instrumenting class [" + + cv.getJavaId().getQualifiedName() + + "]: " + + e.getMessage()); + this.instrControl.updateInstrumentationStatistics( + cv.getJavaId(), Boolean.valueOf(false)); } } } + if (!this.instrument) { // only detach if no static instrumentation (otherwise it will fail // because the class was modified) in case the instrumentation is @@ -627,7 +622,7 @@ else if (je.getName().endsWith("pom.xml")) { + constructs.size() + "], enums [" + enumCount - + "], interfaces (ignored) [" + + "], interfaces [" + interfaceCount + "]"); else @@ -639,7 +634,7 @@ else if (je.getName().endsWith("pom.xml")) { + this.classCount + "], enums [" + enumCount - + "], interfaces (ignored) [" + + "], interfaces [" + interfaceCount + "]"); } diff --git a/lang-java/src/main/java/org/eclipse/steady/java/JavaFileAnalyzer2.java b/lang-java/src/main/java/org/eclipse/steady/java/JavaFileAnalyzer2.java index 13e8a7c4e..947bf78e3 100644 --- a/lang-java/src/main/java/org/eclipse/steady/java/JavaFileAnalyzer2.java +++ b/lang-java/src/main/java/org/eclipse/steady/java/JavaFileAnalyzer2.java @@ -181,7 +181,7 @@ public void exitEnumDeclaration(@NotNull JavaParser.EnumDeclarationContext ctx) /** * {@inheritDoc} * - * Interfaces are not added to {@link #constructs}. + * Interfaces are added to {@link #constructs}. */ @Override public void enterInterfaceDeclaration(@NotNull JavaParser.InterfaceDeclarationContext ctx) { @@ -191,6 +191,7 @@ public void enterInterfaceDeclaration(@NotNull JavaParser.InterfaceDeclarationCo (cse == null ? JavaPackageId.DEFAULT_PACKAGE : (JavaId) cse.getConstructId()); final JavaId id = new JavaInterfaceId(decl_ctx, ctx.IDENTIFIER().getText()); this.contextStack.push(id); + this.saveConstruct(id, this.getConstructContent(ctx)); } /** {@inheritDoc} */ @@ -231,6 +232,24 @@ public void enterClassBody(@NotNull JavaParser.ClassBodyContext ctx) { this.constructIdBuilder.resetCurrentDeclarationContext(); } + @Override + public void enterInterfaceMemberDeclaration(JavaParser.InterfaceMemberDeclarationContext ctx) { + if (ctx.interfaceMethodDeclaration() != null) { + // Peek JavaId and ensure it is an interface + final JavaId class_ctx = (JavaId) this.contextStack.peek().getConstructId(); + this.isOfExpectedType(class_ctx, new JavaId.Type[] {JavaId.Type.INTERFACE}, true); + + // Build the identifier + final JavaMethodId id = + new JavaMethodId( + (JavaId) class_ctx, + ctx.interfaceMethodDeclaration().IDENTIFIER().getText(), + this.getParameters( + ctx.interfaceMethodDeclaration().formalParameters().formalParameterList())); + this.saveConstruct(id, this.getConstructContent(ctx)); + } + } + /** {@inheritDoc} */ @Override public void exitClassBody(@NotNull JavaParser.ClassBodyContext ctx) { @@ -244,7 +263,10 @@ public void exitClassBody(@NotNull JavaParser.ClassBodyContext ctx) { public void enterMethodDeclaration(@NotNull JavaParser.MethodDeclarationContext ctx) { // Peek JavaId and ensure it is a class or enum final JavaId class_ctx = (JavaId) this.contextStack.peek().getConstructId(); - this.isOfExpectedType(class_ctx, new JavaId.Type[] {JavaId.Type.CLASS, JavaId.Type.ENUM}, true); + this.isOfExpectedType( + class_ctx, + new JavaId.Type[] {JavaId.Type.CLASS, JavaId.Type.ENUM, JavaId.Type.INTERFACE}, + true); // Build the identifier final JavaMethodId id = diff --git a/lang-java/src/main/java/org/eclipse/steady/java/JavaId.java b/lang-java/src/main/java/org/eclipse/steady/java/JavaId.java index 38faf702d..73a50479e 100644 --- a/lang-java/src/main/java/org/eclipse/steady/java/JavaId.java +++ b/lang-java/src/main/java/org/eclipse/steady/java/JavaId.java @@ -300,6 +300,8 @@ public static org.eclipse.steady.shared.enums.ConstructType toSharedType(JavaId. return ConstructType.ENUM; case CLASS: return ConstructType.CLAS; + case INTERFACE: + return ConstructType.INTF; default: throw new IllegalArgumentException("Unknown type [" + _core_type + "]"); } @@ -327,6 +329,8 @@ public static org.eclipse.steady.ConstructId toCoreType( return JavaId.parseEnumQName(_cid.getQname()); case CLAS: return JavaId.parseClassQName(_cid.getQname()); + case INTF: + return JavaId.parseInterfaceQName(_cid.getQname()); default: throw new IllegalArgumentException("Unknown type [" + _cid.getType() + "]"); } @@ -380,7 +384,7 @@ public static JavaEnumId parseEnumQName(@NotNull String _s) { class_name = _s.substring(i + 1); } - // Create Java class id (for regular or nested class) + // Create Java enum id (for regular or nested class) int j = 0, k = 0; do { // HP, 12.09.2015: $ is also permitted in class names, i.e., it can also exist w/o a class @@ -451,6 +455,53 @@ public static JavaClassId parseClassQName(@NotNull String _s) { return cid; } + /** + *

parseInterfaceQName.

+ * + * @param _s a {@link java.lang.String} object. + * @return a {@link org.eclipse.steady.java.JavaInterfaceId} object. + */ + public static JavaInterfaceId parseInterfaceQName(@NotNull String _s) { + if (_s == null || _s.equals("")) throw new IllegalArgumentException("String null or empty"); + + final int i = _s.lastIndexOf('.'); + String class_name = null; + JavaPackageId pid = null; + JavaInterfaceId cid = null; + + // Create Java package id + if (i != -1) { + pid = new JavaPackageId(_s.substring(0, i)); + class_name = _s.substring(i + 1); + } else { + pid = JavaPackageId.DEFAULT_PACKAGE; + class_name = _s.substring(i + 1); + } + + // Create Java interface id (for regular or nested class) + int j = 0, k = 0; + do { + // HP, 12.09.2015: $ is also permitted in class names, i.e., it can also exist w/o a class + // context + j = class_name.indexOf("$", k); + if (j != -1) { + if (cid == null) cid = new JavaInterfaceId(pid, class_name.substring(k, j)); + else cid = new JavaInterfaceId(cid, class_name.substring(k, j)); + k = j + 1; + } else { + if (cid == null) cid = new JavaInterfaceId(pid, class_name.substring(k)); + else cid = new JavaInterfaceId(cid, class_name.substring(k)); + k = j + 1; + } + } while (j != -1); + // if(j!=-1) + // cid = new JavaClassId(new JavaClassId(pid, class_name.substring(0, j)), + // class_name.substring(j+1)); + // else + // cid = new JavaClassId(pid, class_name); + return cid; + } + /** * Creates a {@link JavaMethodId} from the given string, whereby the definition context is defaulted to * type {@link JavaId.Type#CLASS}. @@ -474,9 +525,11 @@ public static JavaMethodId parseMethodQName(JavaId.Type _ctx_type, String _s) { if (_s == null || _s.equals("")) throw new IllegalArgumentException("String null or empty"); if (_ctx_type == null - || (!_ctx_type.equals(JavaId.Type.CLASS) && !_ctx_type.equals(JavaId.Type.ENUM))) + || (!_ctx_type.equals(JavaId.Type.CLASS) + && !_ctx_type.equals(JavaId.Type.ENUM) + && !_ctx_type.equals(JavaId.Type.INTERFACE))) throw new IllegalArgumentException( - "Accepts context types CLASS or ENUM, got [" + _ctx_type + "]"); + "Accepts context types CLASS, ENUM and INTERFACE, got [" + _ctx_type + "]"); final int i = _s.indexOf('('); if (i == -1 || !_s.endsWith(")")) @@ -491,9 +544,13 @@ public static JavaMethodId parseMethodQName(JavaId.Type _ctx_type, String _s) { JavaId def_ctx = null; JavaMethodId mid = null; try { - if (_ctx_type.equals(JavaId.Type.CLASS)) def_ctx = JavaId.parseClassQName(_s.substring(0, j)); - else if (_ctx_type.equals(JavaId.Type.ENUM)) + if (_ctx_type.equals(JavaId.Type.CLASS)) { + def_ctx = JavaId.parseClassQName(_s.substring(0, j)); + } else if (_ctx_type.equals(JavaId.Type.ENUM)) { def_ctx = JavaId.parseEnumQName(_s.substring(0, j)); + } else if (_ctx_type.equals(JavaId.Type.INTERFACE)) { + def_ctx = JavaId.parseInterfaceQName(_s.substring(0, j)); + } mid = new JavaMethodId( @@ -501,7 +558,7 @@ else if (_ctx_type.equals(JavaId.Type.ENUM)) _s.substring(j + 1, i), JavaId.parseParameterTypes(_s.substring(i + 1, _s.length() - 1))); } catch (StringIndexOutOfBoundsException e) { - JavaId.log.error("Exception while parsing the string '" + _s + "'"); + JavaId.log.error("Exception while parsing the string [" + _s + "]"); } return mid; } @@ -534,9 +591,11 @@ public static JavaConstructorId parseConstructorQName( if (_s == null || _s.equals("")) throw new IllegalArgumentException("String null or empty"); if (_ctx_type == null - || (!_ctx_type.equals(JavaId.Type.CLASS) && !_ctx_type.equals(JavaId.Type.ENUM))) + || (!_ctx_type.equals(JavaId.Type.CLASS) + && !_ctx_type.equals(JavaId.Type.ENUM) + && !_ctx_type.equals(JavaId.Type.INTERFACE))) throw new IllegalArgumentException( - "Accepts context types CLASS or ENUM, got [" + _ctx_type + "]"); + "Accepts context types CLASS, ENUM and INTERFACE, got [" + _ctx_type + "]"); final int i = _s.indexOf('('); if (i == -1 || !_s.endsWith(")")) @@ -568,7 +627,7 @@ else if (_ctx_type.equals(JavaId.Type.ENUM)) } coid = new JavaConstructorId(def_ctx, params); } catch (StringIndexOutOfBoundsException e) { - JavaId.log.error("Exception while parsing the string '" + _s + "'"); + JavaId.log.error("Exception while parsing the string [" + _s + "]"); } return coid; } @@ -585,7 +644,7 @@ public static JavaClassInit parseClassInitQName(String _s) { final int i = _s.indexOf(JavaClassInit.NAME); if (i == -1) throw new IllegalArgumentException( - "String does not contain brackets " + "String does not contain " + JavaClassInit.NAME + ", as required for qualified names for Java class initializers"); @@ -594,7 +653,7 @@ public static JavaClassInit parseClassInitQName(String _s) { final JavaClassId cid = JavaId.parseClassQName(_s.substring(0, i - 1)); clinit = cid.getClassInit(); } catch (StringIndexOutOfBoundsException e) { - JavaId.log.error("Exception while parsing the string '" + _s + "'"); + JavaId.log.error("Exception while parsing the string [" + _s + "]"); } return clinit; } diff --git a/lang-java/src/main/java/org/eclipse/steady/java/monitor/ClassVisitor.java b/lang-java/src/main/java/org/eclipse/steady/java/monitor/ClassVisitor.java index 4ee9aad53..d381a0c58 100644 --- a/lang-java/src/main/java/org/eclipse/steady/java/monitor/ClassVisitor.java +++ b/lang-java/src/main/java/org/eclipse/steady/java/monitor/ClassVisitor.java @@ -110,10 +110,13 @@ private static final Logger getLog() { */ public ClassVisitor(CtClass _c) { // Build the JavaId - if (_c.isInterface()) - throw new IllegalArgumentException("[" + _c.getName() + "]: Interfaces are not supported"); - else if (_c.isEnum()) this.javaId = JavaId.parseEnumQName(_c.getName()); - else this.javaId = JavaId.parseClassQName(_c.getName()); + if (_c.isInterface()) { + this.javaId = JavaId.parseInterfaceQName(_c.getName()); + } else if (_c.isEnum()) { + this.javaId = JavaId.parseEnumQName(_c.getName()); + } else { + this.javaId = JavaId.parseClassQName(_c.getName()); + } this.qname = this.javaId.getQualifiedName(); this.c = _c; diff --git a/lang-java/src/main/java/org/eclipse/steady/java/tasks/JavaDebloatTask.java b/lang-java/src/main/java/org/eclipse/steady/java/tasks/JavaDebloatTask.java new file mode 100644 index 000000000..3bd964189 --- /dev/null +++ b/lang-java/src/main/java/org/eclipse/steady/java/tasks/JavaDebloatTask.java @@ -0,0 +1,335 @@ +/** + * This file is part of Eclipse Steady. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors + */ +package org.eclipse.steady.java.tasks; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +import org.apache.logging.log4j.Logger; +import org.eclipse.steady.core.util.CoreConfiguration; +import org.eclipse.steady.goals.GoalExecutionException; +import org.eclipse.steady.java.JavaId; +import org.eclipse.steady.shared.enums.ConstructType; +import org.eclipse.steady.shared.enums.GoalClient; +import org.eclipse.steady.shared.enums.ProgrammingLanguage; +import org.eclipse.steady.shared.json.model.ConstructId; +import org.eclipse.steady.shared.json.model.Dependency; +import org.eclipse.steady.shared.util.FileUtil; +import org.eclipse.steady.shared.util.StringUtil; +import org.eclipse.steady.tasks.AbstractTask; +import org.eclipse.steady.tasks.DebloatTask; +import org.vafer.jdependency.Clazz; +import org.vafer.jdependency.Clazzpath; +import org.vafer.jdependency.ClazzpathUnit; + +/** + *

JavaBomTask class.

+ */ +public class JavaDebloatTask extends AbstractTask implements DebloatTask { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); + + private static final String[] EXT_FILTER = new String[] {"jar", "war", "class", "java", "aar"}; + + private Set traces = null; + + private Set dependencies = null; + + private static final List pluginGoalClients = + Arrays.asList(GoalClient.MAVEN_PLUGIN, GoalClient.GRADLE_PLUGIN); + + /** {@inheritDoc} */ + @Override + public Set getLanguage() { + return new HashSet( + Arrays.asList(new ProgrammingLanguage[] {ProgrammingLanguage.JAVA})); + } + + /** {@inheritDoc} */ + @Override + public void execute() throws GoalExecutionException { + + final Clazzpath cp = new Clazzpath(); + + // list of classpathunits to be used as application entrypoints + List app = new ArrayList(); + // list of classpathunits to be used as test entrypoints + List test = new ArrayList(); + + // list of classes from Steasy traces/reachableConstructs (also used as entrypoints) + final SortedSet used_classes = new TreeSet(); + // classpathunits for the application dependencies (as identified by maven) + Set maven_deps = new HashSet(); + // classpathunits considered used according to traces, reachable constructs and jdependency + // analysis + Set deps_used = new HashSet(); + + try { + // 1) Add application paths (to be then used as entrypoints) + if (this.hasSearchPath()) { + for (Path p : this.getSearchPath()) { + log.info("Add app path [" + p + "] to classpath"); + app.add(cp.addClazzpathUnit(p)); + } + } + for (Path p : + FileUtil.getPaths( + this.vulasConfiguration.getStringArray(CoreConfiguration.TEST_DIRS, null))) { + log.info("Add test path [" + p + "] to classpath"); + test.add(cp.addClazzpathUnit(p)); + } + // 2) Add dependencies to jdependency classpath object and populate set of dependencies + if (this.getKnownDependencies() != null) { + for (Path p : this.getKnownDependencies().keySet()) { + maven_deps.add(cp.addClazzpathUnit(p)); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + log.info("[" + app.size() + "] ClasspathUnit for the application to be used as entrypoints "); + log.info("[" + test.size() + "] ClasspathUnit for tests to be used as entrypoints "); + log.info("[" + cp.getUnits().length + "] classpathUnits in jdependency classpath object"); + + // Retrieve traces to be used as Clazz entrypoints (1 class may be part of multiple + // classpathUnits) + for (ConstructId t : traces) { + if (t.getType().equals(ConstructType.CLAS) + || t.getType().equals(ConstructType.INTF) + || t.getType().equals(ConstructType.ENUM)) { + used_classes.add(t.getQname()); + } + } + log.info("Retrieved [" + used_classes.size() + "] clazzes from steady traces"); + + // Loop reachable constructs (METH, CONS, INIT), find their definition + // context (CLASS, ENUM, INTF) and use those as jdependency clazz + // entrypoints (1 class may be part of multiple classpathUnits). + for (Dependency d : dependencies) { + log.info( + "Processing [" + + d.getReachableConstructIds().size() + + "] reachable constructs of " + + d.getLib().getLibraryId()); + if (d.getReachableConstructIds() != null) { + for (ConstructId c : d.getReachableConstructIds()) { + JavaId core_construct = (JavaId) JavaId.toCoreType(c); + JavaId def_context = (JavaId) core_construct.getDefinitionContext(); + used_classes.add(def_context.getQualifiedName()); + } + } + } + + log.info("Using [" + used_classes.size() + "] clazzes from Steady traces/reachable constructs"); + + // also collect "missing" classes to be able to check their relation with jre objects considered + // used + // to be removed from final version + SortedSet missing = new TreeSet(); + for (Clazz c : cp.getMissingClazzes()) { + missing.add(c); + } + + // Classes considered used + final SortedSet needed = new TreeSet(); + final Set removable = cp.getClazzes(); + + // loop over classpathunits (representing the application) marked as entrypoints to find needed + // classes + for (ClazzpathUnit u : app) { + removable.removeAll(u.getClazzes()); + removable.removeAll(u.getTransitiveDependencies()); + for (Clazz c : u.getClazzes()) { + needed.add(c.getName()); + } + for (Clazz c : u.getTransitiveDependencies()) { + needed.add(c.getName()); + deps_used.addAll(c.getClazzpathUnits()); + } + } + + // loop over class (representing traces and reachable constructs) and use them as entrypoints to + // find + // needed classes + for (String class_name : used_classes) { + needed.add(class_name); + + Clazz c = cp.getClazz(class_name); + + if (c == null) { + log.warn("Could not obtain jdependency clazz for steady class [" + class_name + "]"); + } else { + for (Clazz cl : c.getTransitiveDependencies()) { + needed.add(cl.getName()); + deps_used.addAll(cl.getClazzpathUnits()); + } + Set units = c.getClazzpathUnits(); + if (units.size() > 1) { + log.warn( + "Added as entrypoints multiple ClasspathUnits from single class [" + + c + + "] : [" + + StringUtil.join(units, ",") + + "]"); + } + deps_used.addAll(units); + + if (removable.contains(c)) { + removable.remove(c); + removable.removeAll(c.getTransitiveDependencies()); + } + } + } + + final SortedSet removable_sorted = new TreeSet(removable); + + Set remaining = cp.getClazzes(); + remaining.removeAll(removable_sorted); + final SortedSet remaining_sorted = new TreeSet(remaining); + log.info("jdependency classpath classes size: [" + cp.getClazzes().size() + "]"); + log.info("Needed classes: [" + needed.size() + "]"); + log.info("Needed by difference: [" + remaining_sorted.size() + "]"); + log.info("Removable classes: [" + removable_sorted.size() + "]"); + log.info("Used dependencies: [" + deps_used.size() + "] out of [" + maven_deps.size() + "]"); + + maven_deps.removeAll(deps_used); + + // Write names of needed/removable classes and dependencies to disk + try { + File f = + Paths.get( + this.vulasConfiguration.getDir(CoreConfiguration.SLICING_DIR).toString(), + "removable-classes.txt") + .toFile(); + FileUtil.writeToFile(f, StringUtil.join(removable_sorted, System.lineSeparator())); + log.info("List of removable classes written to [" + f.toPath() + "]"); + + f = + Paths.get( + this.vulasConfiguration.getDir(CoreConfiguration.SLICING_DIR).toString(), + "used-classes-steady.txt") + .toFile(); + FileUtil.writeToFile(f, StringUtil.join(used_classes, System.lineSeparator())); + log.info( + "List of used classes according to steady backend data (no jdependency) written to [" + + f.toPath() + + "]"); + + f = + Paths.get( + this.vulasConfiguration.getDir(CoreConfiguration.SLICING_DIR).toString(), + "needed-classes.txt") + .toFile(); + FileUtil.writeToFile(f, StringUtil.join(needed, System.lineSeparator())); + log.info("List of needed classes written to [" + f.toPath() + "]"); + + f = + Paths.get( + this.vulasConfiguration.getDir(CoreConfiguration.SLICING_DIR).toString(), + "remaining-classes.txt") + .toFile(); + FileUtil.writeToFile(f, StringUtil.join(remaining_sorted, System.lineSeparator())); + log.info("List of remaining classes written to [" + f.toPath() + "]"); + + f = + Paths.get( + this.vulasConfiguration.getDir(CoreConfiguration.SLICING_DIR).toString(), + "missing-classes.txt") + .toFile(); + FileUtil.writeToFile(f, StringUtil.join(missing, System.lineSeparator())); + log.info("List of missing classes written to [" + f.toPath() + "]"); + + f = + Paths.get( + this.vulasConfiguration.getDir(CoreConfiguration.SLICING_DIR).toString(), + "removable-deps.txt") + .toFile(); + FileUtil.writeToFile(f, StringUtil.join(maven_deps, System.lineSeparator())); + log.info("List of removable dependencies written to [" + f.toPath() + "]"); + } catch (IOException e) { + e.printStackTrace(); + } + + // also consider test classes to find needed classes + for (ClazzpathUnit u : test) { + removable.removeAll(u.getClazzes()); + removable.removeAll(u.getTransitiveDependencies()); + for (Clazz c : u.getClazzes()) { + needed.add(c.getName()); + } + for (Clazz c : u.getTransitiveDependencies()) { + needed.add(c.getName()); + deps_used.addAll(c.getClazzpathUnits()); + } + } + maven_deps.removeAll(deps_used); + log.info("Needed classes with test: [" + needed.size() + "]"); + log.info("Removable classes with test: [" + removable.size() + "]"); + log.info("Used dependencies with test: [" + deps_used.size() + "]"); + + try { + File f = + Paths.get( + this.vulasConfiguration.getDir(CoreConfiguration.SLICING_DIR).toString(), + "needed-classes-w-test.txt") + .toFile(); + FileUtil.writeToFile(f, StringUtil.join(needed, System.lineSeparator())); + log.info("List of needed classes with test written to [" + f.toPath() + "]"); + f = + Paths.get( + this.vulasConfiguration.getDir(CoreConfiguration.SLICING_DIR).toString(), + "removable-deps-w-test.txt") + .toFile(); + FileUtil.writeToFile(f, StringUtil.join(maven_deps, System.lineSeparator())); + log.info("List of removable dependencies with test written to [" + f.toPath() + "]"); + + f = + Paths.get( + this.vulasConfiguration.getDir(CoreConfiguration.SLICING_DIR).toString(), + "used-deps.txt") + .toFile(); + FileUtil.writeToFile(f, StringUtil.join(deps_used, System.lineSeparator())); + log.info("List of used dependencies written to [" + f.toPath() + "]"); + } catch (IOException e) { + e.printStackTrace(); + } + } + + /** {@inheritDoc} */ + @Override + public void setTraces(Set _traces) { + this.traces = _traces; + } + + /** {@inheritDoc} */ + @Override + public void setReachableConstructIds(Set _deps) { + this.dependencies = _deps; + } +} diff --git a/lang-java/src/main/resources/META-INF/services/org.eclipse.steady.tasks.DebloatTask b/lang-java/src/main/resources/META-INF/services/org.eclipse.steady.tasks.DebloatTask new file mode 100644 index 000000000..c1910b842 --- /dev/null +++ b/lang-java/src/main/resources/META-INF/services/org.eclipse.steady.tasks.DebloatTask @@ -0,0 +1 @@ +org.eclipse.steady.java.tasks.JavaDebloatTask \ No newline at end of file diff --git a/lang-java/src/test/java/org/eclipse/steady/java/JarAnalyzerTest.java b/lang-java/src/test/java/org/eclipse/steady/java/JarAnalyzerTest.java index 3fab39bed..e0e957dd7 100755 --- a/lang-java/src/test/java/org/eclipse/steady/java/JarAnalyzerTest.java +++ b/lang-java/src/test/java/org/eclipse/steady/java/JarAnalyzerTest.java @@ -131,7 +131,7 @@ public void testInvalidClassEntries() { ja.analyze( new File("./src/test/resources/org.apache.servicemix.bundles.jaxb-xjc-2.2.4_1.jar")); ja.call(); - assertEquals(8984, ja.getConstructIds().size()); + assertEquals(9884, ja.getConstructIds().size()); } catch (Exception e) { e.printStackTrace(); assertTrue(false); diff --git a/lang-java/src/test/java/org/eclipse/steady/java/JavaFileAnalyzer2Test.java b/lang-java/src/test/java/org/eclipse/steady/java/JavaFileAnalyzer2Test.java index d0243bdb7..cdc9582e7 100644 --- a/lang-java/src/test/java/org/eclipse/steady/java/JavaFileAnalyzer2Test.java +++ b/lang-java/src/test/java/org/eclipse/steady/java/JavaFileAnalyzer2Test.java @@ -91,6 +91,18 @@ public void testAnonClassInInterfaceInit() { // The parsing should produce the following 5 elements: final JavaPackageId p = new JavaPackageId("org.eclipse.steady.java.test"); + + final JavaInterfaceId itf = + JavaId.parseInterfaceQName("org.eclipse.steady.java.test.ConfigurationKey"); + final JavaMethodId itf_m1 = + JavaId.parseMethodQName( + "org.eclipse.steady.java.test.ConfigurationKey.getType(String,int)"); + final JavaMethodId itf_m2 = + JavaId.parseMethodQName("org.eclipse.steady.java.test.ConfigurationKey.getKey()"); + final JavaMethodId itf_m3 = + JavaId.parseMethodQName( + "org.eclipse.steady.java.test.ConfigurationKey.getDefaultValue()"); + final JavaClassId anon1 = JavaId.parseClassQName("org.eclipse.steady.java.test.ConfigurationKey$1"); final JavaMethodId anon1_m = @@ -103,8 +115,12 @@ public void testAnonClassInInterfaceInit() { "org.eclipse.steady.java.test.ConfigurationKey$2.compare(ConfigurationKey,ConfigurationKey)"); // Assertions - assertEquals(5, constructs.size()); + assertEquals(9, constructs.size()); assertTrue(constructs.containsKey(p)); + assertTrue(constructs.containsKey(itf)); + assertTrue(constructs.containsKey(itf_m1)); + assertTrue(constructs.containsKey(itf_m2)); + assertTrue(constructs.containsKey(itf_m3)); assertTrue(constructs.containsKey(anon1)); assertTrue(constructs.containsKey(anon1_m)); assertTrue(constructs.containsKey(anon2)); @@ -128,8 +144,10 @@ public void testNamedClassInInterfaceAndAnonClassInConstructor() { "./src/test/java/org/eclipse/steady/java/test/HttpRequestCompletionLog.java")); final Map constructs = jfa.getConstructs(); - // The parsing should produce the following 5 elements: + // The parsing should produce the following elements: final JavaPackageId p = new JavaPackageId("org.eclipse.steady.java.test"); + final JavaInterfaceId itf = + JavaId.parseInterfaceQName("org.eclipse.steady.java.test.HttpRequestCompletionLog"); // Named inner class final JavaClassId named_class = @@ -200,8 +218,9 @@ public void testNamedClassInInterfaceAndAnonClassInConstructor() { "org.eclipse.steady.java.test.HttpRequestCompletionLog$Builder$1.getResponseContentType()"); // Assertions - assertEquals(23, constructs.size()); + assertEquals(33, constructs.size()); assertTrue(constructs.containsKey(p)); + assertTrue(constructs.containsKey(itf)); assertTrue(constructs.containsKey(named_class)); assertTrue(constructs.containsKey(named_class_cons)); @@ -254,7 +273,7 @@ public void testEnumAndNamedClassesInMethod() { JavaId.parseConstructorQName( "org.eclipse.steady.java.test.ConfigKey(Class,String,String)"); final JavaMethodId enum_m1 = - JavaId.parseMethodQName("org.eclipse.steady.java.test.ConfigKey.getType()"); + JavaId.parseMethodQName("org.eclipse.steady.java.test.ConfigKey.getType(String,int)"); final JavaMethodId enum_m2 = JavaId.parseMethodQName("org.eclipse.steady.java.test.ConfigKey.getKey()"); final JavaMethodId enum_m3 = @@ -373,9 +392,16 @@ public void testNestedDeclarationMess() { final FileAnalyzer fa = FileAnalyzerFactory.buildFileAnalyzer(file); final Map constructs = fa.getConstructs(); - // The parsing should produce the following 5 elements: + // The parsing should produce the following elements: final JavaPackageId p = new JavaPackageId("org.eclipse.steady.java.test"); + final JavaInterfaceId itf = + JavaId.parseInterfaceQName( + "org.eclipse.steady.java.test.NestedDeclarations$DoSomethingElse"); + final JavaMethodId itf_m = + JavaId.parseMethodQName( + "org.eclipse.steady.java.test.NestedDeclarations$DoSomethingElse.doSomethingElse()"); + final JavaClassId cl1 = JavaId.parseClassQName("org.eclipse.steady.java.test.NestedDeclarations"); // line 5 final JavaMethodId cl1_m = @@ -457,9 +483,12 @@ public void testNestedDeclarationMess() { JavaId.parseMethodQName( "org.eclipse.steady.java.test.NestedDeclarations$Foo$1DoThis$1.doThat()"); // line 72 - // Assertions - assertEquals(25, constructs.size()); + assertEquals(27, constructs.size()); assertTrue(constructs.containsKey(p)); + + assertTrue(constructs.containsKey(itf)); + assertTrue(constructs.containsKey(itf_m)); + assertTrue(constructs.containsKey(cl1)); assertTrue(constructs.containsKey(cl1_m)); @@ -556,7 +585,8 @@ public void testCxfClass() { } /** - * Tests whether the constructs extracted from a Java file correspond to the ones obtained from the compiled file. + * Tests whether the constructs extracted from a Java file correspond to the + * ones obtained from the compiled file. */ @Test public void testCompareConstructCreation() { diff --git a/lang-java/src/test/java/org/eclipse/steady/java/JsonHelperTest.java b/lang-java/src/test/java/org/eclipse/steady/java/JsonHelperTest.java index 0d10c5fcf..5db61f90f 100644 --- a/lang-java/src/test/java/org/eclipse/steady/java/JsonHelperTest.java +++ b/lang-java/src/test/java/org/eclipse/steady/java/JsonHelperTest.java @@ -57,12 +57,9 @@ public void jarToJsonEqualityTest() throws FileAnalysisException { } assertEquals(json1, json2); - } catch (IllegalStateException ise) { - // TODO Auto-generated catch block - ise.printStackTrace(); - } catch (IOException ioe) { - // TODO Auto-generated catch block - ioe.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + assert (false); } } diff --git a/lang-java/src/test/java/org/eclipse/steady/java/test/ConfigKey.java b/lang-java/src/test/java/org/eclipse/steady/java/test/ConfigKey.java index 265b03624..7800bc34f 100644 --- a/lang-java/src/test/java/org/eclipse/steady/java/test/ConfigKey.java +++ b/lang-java/src/test/java/org/eclipse/steady/java/test/ConfigKey.java @@ -41,7 +41,7 @@ public enum ConfigKey implements ConfigurationKey { } @Override - public final Class getType() { + public final Class getType(String a, int b) { return type; } diff --git a/lang-java/src/test/java/org/eclipse/steady/java/test/ConfigurationKey.java b/lang-java/src/test/java/org/eclipse/steady/java/test/ConfigurationKey.java index efa73d658..06c3d53f4 100644 --- a/lang-java/src/test/java/org/eclipse/steady/java/test/ConfigurationKey.java +++ b/lang-java/src/test/java/org/eclipse/steady/java/test/ConfigurationKey.java @@ -41,7 +41,7 @@ public int compare(final ConfigurationKey key1, final ConfigurationKey key2) { } }; - Class getType(); + Class getType(String a, int b); String getKey(); diff --git a/lang-java/src/test/java/org/eclipse/steady/java/test/NestedDeclarations.java b/lang-java/src/test/java/org/eclipse/steady/java/test/NestedDeclarations.java index 135c3e5f4..20449264f 100644 --- a/lang-java/src/test/java/org/eclipse/steady/java/test/NestedDeclarations.java +++ b/lang-java/src/test/java/org/eclipse/steady/java/test/NestedDeclarations.java @@ -20,8 +20,7 @@ import java.io.Serializable; -public class NestedDeclarations { // Do not change this class, the corresponding test case - // (testNestedDeclarationMess) refers to the line numbers!!!!!!!!!!!!!!! +public class NestedDeclarations { // Member interface interface DoSomethingElse { diff --git a/lang/pom.xml b/lang/pom.xml index 782696d67..168c7ad90 100644 --- a/lang/pom.xml +++ b/lang/pom.xml @@ -78,7 +78,7 @@ com.google.code.gson gson - + org.apache.velocity velocity-engine-core diff --git a/lang/src/main/java/org/eclipse/steady/backend/BackendConnector.java b/lang/src/main/java/org/eclipse/steady/backend/BackendConnector.java index be798a3e2..0b4d78619 100755 --- a/lang/src/main/java/org/eclipse/steady/backend/BackendConnector.java +++ b/lang/src/main/java/org/eclipse/steady/backend/BackendConnector.java @@ -721,6 +721,9 @@ public Set getAppDependencies(GoalContext _ctx, @NotNull Application (org.eclipse.steady.shared.json.model.Dependency[]) JacksonUtil.asObject(json, org.eclipse.steady.shared.json.model.Dependency[].class); else backend_deps = new org.eclipse.steady.shared.json.model.Dependency[] {}; + for (org.eclipse.steady.shared.json.model.Dependency d : backend_deps) { + d.setApp(_app); + } deps.addAll(Arrays.asList(backend_deps)); BackendConnector.log.info("[" + deps.size() + "] dependencies received from backend"); } diff --git a/lang/src/main/java/org/eclipse/steady/core/util/CoreConfiguration.java b/lang/src/main/java/org/eclipse/steady/core/util/CoreConfiguration.java index 348207d67..3aa77bdaf 100755 --- a/lang/src/main/java/org/eclipse/steady/core/util/CoreConfiguration.java +++ b/lang/src/main/java/org/eclipse/steady/core/util/CoreConfiguration.java @@ -105,6 +105,8 @@ public enum ConnectType { // BOM, fka APP /** Constant APP_DIRS="vulas.core.app.sourceDir" */ public static final String APP_DIRS = "vulas.core.app.sourceDir"; + /** Constant TEST_DIRS="vulas.core.app.testDir" */ + public static final String TEST_DIRS = "vulas.core.app.testDir"; /** Constant APP_PREFIXES="vulas.core.app.appPrefixes" */ public static final String APP_PREFIXES = "vulas.core.app.appPrefixes"; /** Constant APP_JAR_NAMES="vulas.core.app.appJarNames" */ @@ -125,6 +127,9 @@ public enum ConnectType { /** Constant REP_LIB_ASSESS="vulas.report.createLibraryAssessments" */ public static final String REP_CREATE_AFF_LIB = "vulas.report.createLibraryAssessments"; + /** Constant SLICING_DIR="vulas.slicing.slicingDir" */ + public static final String SLICING_DIR = "vulas.slicing.slicingDir"; + /** Constant SEQ_DEFAULT="vulas.core.sequence.defaultGoals" */ public static final String SEQ_DEFAULT = "vulas.core.sequence.defaultGoals"; diff --git a/lang/src/main/java/org/eclipse/steady/goals/DebloatGoal.java b/lang/src/main/java/org/eclipse/steady/goals/DebloatGoal.java new file mode 100644 index 000000000..be03b87b0 --- /dev/null +++ b/lang/src/main/java/org/eclipse/steady/goals/DebloatGoal.java @@ -0,0 +1,138 @@ +/** + * This file is part of Eclipse Steady. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors + */ +package org.eclipse.steady.goals; + +import java.util.ServiceLoader; + +import org.apache.logging.log4j.Logger; +import org.eclipse.steady.backend.BackendConnector; +import org.eclipse.steady.shared.enums.GoalType; +import org.eclipse.steady.shared.json.model.Application; +import org.eclipse.steady.tasks.DebloatTask; + +/** + *

NeededGoal class.

+ */ +public class DebloatGoal extends AbstractAppGoal { + + private static final Logger log = org.apache.logging.log4j.LogManager.getLogger(); + + /** + *

Constructor for DebloatGoal.

+ */ + public DebloatGoal() { + super(GoalType.DEBLOAT); + } + + // /** + // * {@inheritDoc} + // * + // * Evaluates the configuration setting {@link CoreConfiguration#APP_PREFIXES}. + // */ + // @Override + // protected void prepareExecution() throws GoalConfigurationException { + // super.prepareExecution(); + // } + + /** {@inheritDoc} */ + @Override + protected void executeTasks() throws Exception { + + // The application to be completed + Application a = this.getApplicationContext(); + + // Create, configure and execute tasks + final ServiceLoader loader = ServiceLoader.load(DebloatTask.class); + for (DebloatTask t : loader) { + try { + + // Configure + t.setApplication(a); + t.setTraces(BackendConnector.getInstance().getAppTraces(this.getGoalContext(), a)); + t.setReachableConstructIds( + BackendConnector.getInstance().getAppDependencies(this.getGoalContext(), a)); + t.setSearchPaths(this.getAppPaths()); + t.setGoalClient(this.getGoalClient()); + t.setKnownDependencies(this.getKnownDependencies()); + t.configure(this.getConfiguration()); + + // Execute + t.execute(); + t.cleanUp(); + // t.getNeededConstructs(); + } catch (Exception e) { + log.error("Error running task " + t + ": " + e.getMessage(), e); + } + } + + // Upload libraries and binaries (if requested) + // if (a.getDependencies() != null) { + // for (Dependency dep : a.getDependencies()) { + // + // // Upload lib + // final Library lib = dep.getLib(); + // if (lib != null) { + // if (lib.hasValidDigest()) { + // BackendConnector.getInstance().uploadLibrary(this.getGoalContext(), lib); + // if + // (CoreConfiguration.isJarUploadEnabled(this.getGoalContext().getVulasConfiguration())) + // BackendConnector.getInstance() + // .uploadLibraryFile(lib.getDigest(), Paths.get(dep.getPath())); + // } else { + // log.error("Library of dependency [" + dep + "] has no valid digest"); + // } + // } else { + // log.error("Dependency [" + dep + "] has no library"); + // } + // } + // } + + // final boolean upload_empty = + // this.getConfiguration() + // .getConfiguration() + // .getBoolean(CoreConfiguration.APP_UPLOAD_EMPTY, false); + // final boolean app_exists_in_backend = + // BackendConnector.getInstance().isAppExisting(this.getGoalContext(), a); + // + // // Upload if non-empty or already exists in backend or empty ones shall be uploaded + // if (!a.isEmpty() || app_exists_in_backend || upload_empty) { + // log.info( + // "Save app " + // + a + // + " with [" + // + a.getDependencies().size() + // + "] dependencies and [" + // + a.getConstructs().size() + // + "] constructs (uploadEmpty=" + // + upload_empty + // + ")"); + // BackendConnector.getInstance().uploadApp(this.getGoalContext(), a); + // } else { + // log.warn( + // "Skip save of empty app " + // + this.getApplicationContext() + // + " (uploadEmpty=" + // + upload_empty + // + ", existsInBackend=" + // + app_exists_in_backend + // + ")"); + // this.skipGoalUpload(); + // } + } +} diff --git a/lang/src/main/java/org/eclipse/steady/goals/GoalFactory.java b/lang/src/main/java/org/eclipse/steady/goals/GoalFactory.java index d874c8d54..bf404d35d 100755 --- a/lang/src/main/java/org/eclipse/steady/goals/GoalFactory.java +++ b/lang/src/main/java/org/eclipse/steady/goals/GoalFactory.java @@ -117,6 +117,8 @@ public static AbstractGoal create(@NotNull GoalType _type) throw new IllegalStateException( "Cannot create instance of class [" + clazzname + "]: " + e.getMessage()); } + } else if (_type.equals(GoalType.DEBLOAT)) { + goal = new DebloatGoal(); } else { throw new IllegalArgumentException("Goal [" + _type + "] is not supported"); } diff --git a/lang/src/main/java/org/eclipse/steady/tasks/DebloatTask.java b/lang/src/main/java/org/eclipse/steady/tasks/DebloatTask.java new file mode 100644 index 000000000..62eeae901 --- /dev/null +++ b/lang/src/main/java/org/eclipse/steady/tasks/DebloatTask.java @@ -0,0 +1,55 @@ +/** + * This file is part of Eclipse Steady. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors + */ +package org.eclipse.steady.tasks; + +import java.util.Set; + +import org.eclipse.steady.shared.json.model.ConstructId; +import org.eclipse.steady.shared.json.model.Dependency; + +/** + * Methods required to + */ +public interface DebloatTask extends Task { + + // /** + // * Returns the {@link Application} including (a) all its {@link Construct}s of the respective + // {@link ProgrammingLanguage}, + // * and (b) the {@link Dependency}s of that application. The {@link Library} of each {@link + // Dependency} must contain + // * all details such as its {@link Construct}s and properties. + // * + // * @return a {@link org.eclipse.steady.shared.json.model.Application} object. + // */ + // public Set getNeededConstructs(); + + /** + * Sets the traced constructs to be used as starting point for debloating the application + * (traces resulting from the dynamic analysis) + * + */ + public void setTraces(Set _traces); + + /** + * Sets the reachable constructs to be used as starting point for debloating the application + * (resulting from the static analysis) + * + */ + public void setReachableConstructIds(Set _deps); +} diff --git a/lang/src/main/resources/steady-core.properties b/lang/src/main/resources/steady-core.properties index 4baee6953..5cc19275f 100644 --- a/lang/src/main/resources/steady-core.properties +++ b/lang/src/main/resources/steady-core.properties @@ -195,7 +195,7 @@ vulas.report.exceptionExcludeUnassessed = all # Note: The scope of this assessment is beyond a specific application, hence, the CURL command requires a security token. #vulas.report.createLibraryAssessments = -# Directory to where the reports (JSON, XML, HTML) will be written to +# Directory to which the reports (JSON, XML, HTML) will be written to # Default: # CLI: - # MVN: ${project.build.directory}/vulas/report @@ -208,6 +208,18 @@ vulas.report.reportDir = # Note: This setting is only relevant in the context of Maven #vulas.report.overridePomVersion = false + + +########## vulas:slicing + +# Directory to which files with needed/redundant classes and dependencies will be written to +# Default: +# CLI: - +# MVN: ${project.build.directory}/vulas/slicing +vulas.slicing.slicingDir = + + + ########## vulas:sequence # Sequence of goals executed by the sequence goal diff --git a/plugin-maven/src/main/java/org/eclipse/steady/java/mvn/AbstractVulasMojo.java b/plugin-maven/src/main/java/org/eclipse/steady/java/mvn/AbstractVulasMojo.java index 10e799e0d..11e5a7572 100644 --- a/plugin-maven/src/main/java/org/eclipse/steady/java/mvn/AbstractVulasMojo.java +++ b/plugin-maven/src/main/java/org/eclipse/steady/java/mvn/AbstractVulasMojo.java @@ -165,6 +165,9 @@ private final void setDefaults(VulasConfiguration _cfg, MavenProject _prj) { _cfg.setPropertyIfEmpty( CoreConfiguration.REP_DIR, Paths.get(_prj.getBuild().getDirectory(), "vulas", "report").toString()); + _cfg.setPropertyIfEmpty( + CoreConfiguration.SLICING_DIR, + Paths.get(_prj.getBuild().getDirectory(), "vulas", "debloat").toString()); // Read app constructs from src/main/java and target/classes final String p = @@ -172,6 +175,12 @@ private final void setDefaults(VulasConfiguration _cfg, MavenProject _prj) { + "," + Paths.get(_prj.getBuild().getSourceDirectory()).toString(); _cfg.setPropertyIfEmpty(CoreConfiguration.APP_DIRS, p); + + final String test_paths = + Paths.get(_prj.getBuild().getTestOutputDirectory()).toString() + + "," + + Paths.get(_prj.getBuild().getTestSourceDirectory()).toString(); + _cfg.setPropertyIfEmpty(CoreConfiguration.TEST_DIRS, test_paths); } /** diff --git a/plugin-maven/src/main/java/org/eclipse/steady/java/mvn/MvnPluginDebloat.java b/plugin-maven/src/main/java/org/eclipse/steady/java/mvn/MvnPluginDebloat.java new file mode 100644 index 000000000..02cb78256 --- /dev/null +++ b/plugin-maven/src/main/java/org/eclipse/steady/java/mvn/MvnPluginDebloat.java @@ -0,0 +1,42 @@ +/** + * This file is part of Eclipse Steady. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright (c) 2018-2020 SAP SE or an SAP affiliate company and Eclipse Steady contributors + */ +package org.eclipse.steady.java.mvn; + +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.eclipse.steady.goals.DebloatGoal; + +/** + * This Mojo identifies the constructs belonging to the application itself and belonging to all its dependencies. + * Those are then uploaded to the central Vulas engine for further analysis (test coverage, vulnerability assessments, archive integrity). + */ +@Mojo( + name = "debloat", + defaultPhase = LifecyclePhase.PROCESS_CLASSES, + requiresDependencyResolution = ResolutionScope.RUNTIME, + requiresOnline = true) +public class MvnPluginDebloat extends AbstractVulasMojo { + + /** {@inheritDoc} */ + @Override + protected void createGoal() { + this.goal = new DebloatGoal(); + } +} diff --git a/pom.xml b/pom.xml index b2d98f331..4c915175b 100755 --- a/pom.xml +++ b/pom.xml @@ -412,6 +412,7 @@ + ${basedir}/src/test/java