JetBrains / lincheck

Framework for testing concurrent data structures
Mozilla Public License 2.0
538 stars 31 forks source link

Bytecode Transformation Refactoring #322

Closed eupp closed 1 month ago

eupp commented 1 month ago

This PR implements several refactorings of the byte-code transformation part of the Lincheck.

Some of the refactorings are needed in preparation of the new model checking strategy merge, the others aim to just simplify the code.

  1. LincheckClassVisitor.kt is split into several files to simplify future development and reduce number of potential merge-conflicts. Independent bytecode transformers are moved into separate files and grouped logically.

  2. ObjectAtomicWriteTrackerTransformer is split into AtomicFieldUpdaterTransformer, VarHandleTransformer and UnsafeTransformer (but common methods of these transformers are still shared). This is because in the new strategy the byte-code transformation to track methods of these APIs (AFU, VH and Unsafe) is significantly more complicated. So it makes sense to split them and handle each API separatly.

    • as a part of this, I have also added handling of some missing VH and Unsafe methods, for example, putObject, putReference, compareAndSetReference, compareAndSetReferenceRelease, etc.
  3. Several common code patterns are extracted into separate functions and moved into TransformationUtils.kt:

    • storeLocals(valueTypes: Array<Type>, localTypes: Array<Type>) stores #N top values from the stack into local variables, performing boxing if requested by the caller.
    • copyLocals(valueTypes: Array<Type>, localTypes: Array<Type>) same as storeLocals but copies the values from the stack without modifying the stack.
    • storeArguments(methodDescriptor: String) and copyArguments(methodDescriptor: String) wrappers around storeLocals and copyLocals to save method arguments from the stack to local variables.
    • pushNull pushes null to the stack.
    • pushArray(locals: IntArray) wraps local variables into an array (boxing them if necessary) and pushes this array onto the stack.
  4. Leave only one beforeReadField and beforeWriteField methods, passing isStatic and isFinal as arguments. This change allows the ManagedStrategy itself decide how to handle static and final fields (because in the new strategy we might also want to track accesses to final fields).

  5. Minor renamings:

    • onMethodCallFinishedSuccessfully --> onMethodCallReturn
    • onMethodCallThrewException --> onMethodCallException
    • onWriteToObjectFieldOrArrayCell --> afterReflectiveSetter (because it is only called from reflections APIs, such as VarHandle or AFU).