Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Apply CC fixes to existing code #700

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft

Conversation

schuessf
Copy link
Contributor

@schuessf schuessf commented Dec 6, 2024

During the update to Java 21 (see #672), we also slightly changed our coding conventions.
This can lead to unnecessary diffs, when editing existing files (many existing file did not even follow our old coding conventions...).
In this PR I applied the new coding conventions automatically (starting with CACSL2BoogieTranslator) and we can discuss possible changes to the coding conventions here.


Eclipse does not actually allow to apply the save actions automatically to multiple files. But I found a way 😉

  • There is a similar command Clean Up that can be applied on a whole project under the Source menu (Note: There is also a command Format there, but it only applies the formatter).
  • The Clean Up actions can be set up under Preferences -> Java -> Code Style -> Cleanup and they are almost the same as the save actions, but their is no common way to set them.
  • We could also add these cleanup settings to our settings that the users should import, but we don't want to keep them in sync manually and also the import of the cleanup actions did not work.
  • Therefore I wrote a small python script (see below) that creates an .xml file that can be imported under the cleanup-settings from our .epf file.
  • After this file is imported, you can simply select some files / plugins / the whole workspace to run Source -> Clean up, which should apply our save actions on multiple files automatically.
def create_cleanup_settings(in_file, out_file):
    lines = ['<?xml version="1.0" encoding="UTF-8" standalone="no"?>',
             '<profiles version="2">',
             '\t<profile kind="CleanUpProfile" name="Ultimate" version="2">']
    with open(in_file) as f:
        settings = re.findall(r'/instance/org.eclipse.jdt.ui/sp_cleanup.(.*?)=(.*?)\s', f.read())
    for n, s in settings:
        lines.append(f'\t\t<setting id="cleanup.{n}" value="{s}"/>')
    lines.append('\t</profile>')
    lines.append('</profiles>')
    with open(out_file, "w") as f:
        print("\n".join(lines), file=f)

@@ -188,19 +188,17 @@ private IASTNode getMergedNode(final CLocation otherCloc) {
return otherNode;
} else if (otherNode == null) {
return myNode;
} else // we have two nodes and want to merge them; if one of both is a translation unit, we take the other
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merging else and if is a good idea in general, but it looks somehow weird, if there is a comment in between, but I don't know how to handle this.

"You selected the library mode (i.e., each procedure can be starting procedure and global "
+ "variables are not initialized). This program contains a \"main\" procedure. Maybe you "
+ "wanted to select the \"main\" procedure as starting procedure.";
final String msg = """
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's really a nice usage of multiline string, we were able to get rid of the quotes \".

result = prime * result + (mIsRestrict ? 1231 : 1237);
result = prime * result + (mIsVolatile ? 1231 : 1237);
return result;
return Objects.hash(mIsConst, mIsExtern, mIsInline, mIsRestrict, mIsVolatile);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's so much nicer, Boolean::hashCode returns exactly 1231 or 1237 and Objects::hash computes exactly this combination.

+ "if the program lost track of allocated memory. If this is set to false we are unsound (at SV-COMP) in "
+ "cases where not all memory is freed but pointers to that memory are live at the end of the "
+ "main procedure.";
public static final String DESC_SVCOMP_MEMTRACK_COMPATIBILITY_MODE = """
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I think these multiline Strings are generally nicer, I don't think we should change existing code, as also the line breaks are somehow strange now.

@@ -747,7 +748,7 @@ public IBacktranslatedCFG<String, CACSLLocation> translateCFG(final IBacktransla
// printCFG(cfg, mLogger::info);
final boolean oldValue = mGenerateBacktranslationWarnings;
mGenerateBacktranslationWarnings = false;
IBacktranslatedCFG<String, CACSLLocation> translated = translateCFG(cfg, (a, b, c) -> translateCFGEdge(a, b, c),
IBacktranslatedCFG<String, CACSLLocation> translated = translateCFG(cfg, (IFunction<Map<IExplicitEdgesMultigraph<?, ?, String, ? extends BoogieASTNode, ?>, Multigraph<String, CACSLLocation>>, IMultigraphEdge<?, ?, String, BoogieASTNode, ?>, Multigraph<String, CACSLLocation>, Multigraph<String, CACSLLocation>>) this::translateCFGEdge,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is really bad! In general, this::translateCFGEdge is a bit nicer than (a, b, c) -> translateCFGEdge(a, b, c), but the save actions introduced an unncessary cast here and for long class names C, I actually prefer c -> f(c) over C::f.

// we only update the table with a declaration, if there is no entry for that name yet.
// otherwise we might only keep the declaration and omit the implementation from
// reachableDeclarations.
if ((d instanceof IASTFunctionDeclarator) && !mFunctionTable.containsKey(rslvKey)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oddly enough, Eclipse inserts unnecessary parentheses here that would be removed again, if we run the save actions afterwards again 😆

@@ -134,7 +134,7 @@ public BacktranslatedExpression translateEnsuresExpression(

private Optional<Expression> computeOldVarEqualities(final String proc,
final Set<de.uni_freiburg.informatik.ultimate.boogie.ast.IdentifierExpression> modifiableGlobals) {
final var modifiableNames = modifiableGlobals.stream().map(e -> e.getIdentifier()).collect(Collectors.toSet());
final var modifiableNames = modifiableGlobals.stream().map(de.uni_freiburg.informatik.ultimate.boogie.ast.IdentifierExpression::getIdentifier).collect(Collectors.toSet());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

e -> e.getIdentifier()) is definitely nicer than de.uni_freiburg.informatik.ultimate.boogie.ast.IdentifierExpression::getIdentifier...
Also, Eclipse does not respect the maximum line length here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe even without this change, we should run Clean up twice.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up to the fixpoint :)

@Heizmann
Copy link
Member

The current rules remove variables that are seemingly unnecessary as for example in the following example.

Term fewerQuantifiers = myComplexMethod(arg0, arg1);
return fewerQuantifiers;

However these variables are not unnecessary they

  • serve as documentation and
  • they ease debugging. When I step through the code, I can immediately see the value of this variable.

@bahnwaerter
Copy link
Member

However these variables are not unnecessary they

  • serve as documentation and
  • they ease debugging. When I step through the code, I can immediately see the value of this variable.

Such measures tend to indicate an unfavorable naming of a function or its excessive complexity. In this case, one should rather give the function a meaningful name or reduce its complexity. To debug the return value of a function, you can use watch expressions in the Eclipse debugger. Watch expressions are also useful for expressions like foo.getBar().equals("xyz"); where you only want to inspect foo.getBar() without rewriting the code for debugging.

@Heizmann
Copy link
Member

Such measures tend to indicate an unfavorable naming of a function or its excessive complexity. In this case, one should rather give the function a meaningful name or reduce its complexity. To debug the return value of a function, you can use watch expressions in the Eclipse debugger. Watch expressions are also useful for expressions like foo.getBar().equals("xyz"); where you only want to inspect foo.getBar() without rewriting the code for debugging.

Sounds plausible but

  • You may not be able to modify the methods that you call.
  • The method name refers to its capabilities in general, but your return value may have additional properties that the method does not ensure.
  • In practice, watching expressions is (for a human) more difficult and time-consuming than inspecting values.
  • You cannot watch an expression that has side effects without changing the system's state.

@danieldietsch
Copy link
Member

... watch expressions ...

Newer Eclipse versions also display the return value of a called function without a watch expression. The only downside is that you see it only when returning from the function, i.e., not anymore in the next step (as far as I know).
As the next step is another function (after all, its a return), this again should never be a problem: either the other function also returns this value immediately, or it does something with it (and can then save the value in a local variable if this is appropriate for debugging/readability or necessary for other reasons.

You may not be able to modify the methods that you call.

In Ultimate, you are probably always able to rename the methods you are calling.

I think in many circumstances it is helpful and increases readability if you use appropriately named local variables instead of inlining expressions. If the return of your method is itself a method call, however, then the description of that value should already be clear from the method you are in (not the one you are calling), because this is now the value you are returning. If it is not clear from the caller name, the callers docs, the callees name or the callees docs, it is probably more appropriate to add a line comment there, as this value is not some intermediate result but the result of the function.

@Heizmann
Copy link
Member

Heizmann commented Jan 3, 2025

Newer Eclipse versions also display the return value of a called function without a watch expression. The only downside is that you see it only when returning from the function, i.e., not anymore in the next step (as far as I know). As the next step is another function (after all, its a return), this again should never be a problem: either the other function also returns this value immediately, or it does something with it (and can then save the value in a local variable if this is appropriate for debugging/readability or necessary for other reasons.

The question is not only what is doable, but what is efficiently doable. Debugging is time-consuming, we have to make sure that developers can debug efficiently. (E.g., maybe you want to set a conditional breakpoint that puts the return value of the method in relation to other values)

@danieldietsch
Copy link
Member

Yes, it seems that Eclipse does not support referencing return values in breakpoint conditions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants