MPS 2021.1 Help

Removing sources from generated code

MPS bundles sources with generated models by default. Users of the models are thus able to navigate to definitions of the concepts that they are using in their code. It is a matter of a singleControl + click for them to see the implementation of a method that they are calling or a class they are instantiating, for example. This is very convenient for the users, since they can grasp many of the ideas of the language/library authors simply by peeking at the implementation side.
There may be situations in life when hiding the implementation could be desired, though. Especially closed-source projects need to protect carefully their intellectual property contained in the implementation. Their users should still be able to call the code, but after pressing Control + B  they only get to see the class and method signatures:

Obfuscated1

Combine that with obfuscated class files and chances for someone reverse-engineering the fruits of your hard work are pretty low. Please read on to find out how to do this.

Remove sources from BaseLanguage code

The BuildLanguage  offers a strip implementation  flag to indicate that a particular artifact should have the sources removed.

Obf2

Setting this flag to true  on one of the build layout commands (module, sources of, plugin) will instruct the build process to remove the sources of implementation from the generated artifacts. This flag will ensure that BaseLanguage  methods have their bodies replaced with an empty StatementList  and classes have static and instance initializers removed. May you wish to hide implementation of a Language, the flag will ensure the behavior methods have their bodies also replaced with an empty StatementList.

To summarize, MPS supports out of the box:

  • Hiding your implementations written in BaseLanguage

  • Hiding aspects of your language definitions

You only need to set the strip implementation  flag in your build scripts for the desired solution or a language.

A few handy notes

  1. The ability to  Invalidate caches  in MPS may come handy when switching between projects containing stripped and not-stripped versions of your language.

  2. Sometimes it may be necessary to manually introduce changes in your language so that the build script reflects the changes in the generated artifacts.

Customizing implementation stripping for your own language

Just as  BaseLanguage  code allows for hiding implementation, your languages can allow for implementation hiding, as well.

MPS gives you three marker interfaces to demarcate the intended behavior of the concepts of your language with respect to implementation stripping:

  • InterfacePart - concepts that are fully visible in the generated models. Users will be able to navigate to them and keep references to them in their code.

  • ImplementationPart - concepts that get removed from the generated models. Users will not be able to navigate to them or keep references to them in their code.

  • ImplementationWithStubPart - concepts that get replaced with empty stubs. It behaves like ImplementationPart  except that a stub replacement will be used to represent the nodes in the code. Think of /* compiled code */  marks for empty method bodies, for example.

Robot Kaja sample

If, for example, we wanted to the Robot Kaja sample language (bundled with MPS) to allow its usages to hide their implementation, we can achieve that in a few steps. Let's assume the following scenario:

  1. An end user is writing scripts as part of a SampleRobotScripts  project in the Robot Kaja  language. He wants to download and reuse a library called RobotRoutines  (MPS solution) of robot routines.

  2. The author of RobotRoutines  wants to hide the implementation of her library.

  3. The Robot Kaja  language needs to be modified to support hiding implementation of RobotRoutines . It needs to declare, which of its concepts form the public interface (contract) of applications/libraries written in that language and which hold the implementation. We picked a simple language, so it is pretty straightforward to identify that only three concepts need really to be made part of the interface. The others can have their sources removed during packaging.
    1. Script

    2. Library

    3. RoutineDefinition

Initial situation

The Robot Kaja language allows Libraries  of routines to be created. Library  is a root concept and holds a collection or RoutineDefinitions. The RobotRoutines  library that uses the Robot Kaja  language may create several Library  root nodes and implement a few handy routines in them.

Obf6

When used in the SampleRobotScripts  code, the developer can always navigate to definition and see the full implementation of the RobotRoutines  library.

Obf3

Obf4b

Goal

We want to offer the authors of libraries such as RobotRoutines  the ability to hide the implementation. Once implemented properly, the developers of SampleRobotScripts  would only see the routines signatures and their empty bodies:

Obf4a

Changes to the Robot Kaja language

The Robot Kaja  language needs to have its Script, Library  and RoutineDefinition  concepts marked with the InterfacePart  interface, because user code can refer to them.

Obf7

Obf8

To hide the implementation of routines we need to hide their bodies. The routine body is a  CommandList  concept. To hide it we have to mark it with either the ImplementationPart  interface or the ImplementationWithStubPart  interface. They both result in hiding the implementation, however, the latter lets you provide a replacement "stub" concept, that will be inserted instead of the removed implementation. The stub gives you the chance to provide a nicer look of the code with removed implementation and should always be considered for hidden implementation that gets occasionally seen by the end user.

Obf9

Stub creation

The overhead of  ImplementationWithStubPart  over  ImplementationPart  is in creating the stub concept. For our  CommandList  we'll need a StubCommandList  concept and mark it with IDontSubstituteByDefault  and IStubForAnotherConcept. The  IStubForAnotherConcept  is needed since StubCommandList  inherits the ImplementationWithStubPart  interface from CommandList, and thus MPS needs to be told explicitly that StubCommandList  is a stub itself and does not need to be stubbed by yet another stub.

Stubx2

If StubCommandList  was extending AbstractCommand  instead of CommandList, it would not have to be marked as IStubForAnotherConcept  as AbstractCommand  is not an ImplementationWithStubPart.

Stubx3

The stub editor should make it politely obvious to the reader that the implementation has been removed and is not to be seen here.

Obf11

Upon rebuild and packaging the language is ready to help the author of RobotRoutines  to hide the implementation of her library.

Build script for the library authors

Once the Robot Kaja  language supports implementation stripping, the author of the RobotRoutines  library can set the strip implementation  flag in her build script and thus have the sources of the implementation of her library removed from the generated plugin.

Obf5

General guidelines and additional notes

  1. The marker interfaces are inherited from super-concepts and super-concept-interfaces in a traditional way. In case multiple marker interfaces are applicable to a concept (directly or through inheritance),  InterfacePart  wins over others and  ImplementationWithStubPart  wins over  ImplementationPart.

  2. If none of the marker interface is specified a concept behaves as if the  InterfacePart  was set on it. Marking a concept with  InterfacePart  serves two purposes:
    • Documenting the fact that a concept is a necessary public element of your language

    • Preventing the concept from being accidentally marked as part of implementation, if, for example, it inherits any of the other flags.

  3. Use the  ImplementationPart  interface to mark the concepts of your language that need not to be referred from client code nor are directly accessible from InterfacePart  concepts through mandatory (cardinality 1 and 1..n) links. Sources of these concepts will be removed from the solution during build and so the user will not be able to see their definitions, for example using  Go To Concept Declaration.

  4. Use  ImplementationWithStubPart  interface to mark the concepts that should be removed just like with  ImplementationPart, but which need to be replaced with a place-holder instead of being simply removed from the sources, because the user may get to see them as part of definition of an InterfacePart  concept.

  5. ImplementationWithStubPart  is typically needed for concepts that represent children or target of references of cardinality  1  and  1..n  pointing from InterfacePart  concepts, because their containing links cannot remain empty and would report a validation error.

  6. Children and targets of references with cardinality of  0..1  and  0..n  can safely be marked as  ImplementationPart. The containing links will remain empty.

  7. Stubs should follow the naming convention  Stub + name of the concept being replaced  and must be located in the same package with the replaced concept.

  8. Stubs concepts could implement  ISuppressErrors  to avoid type-system errors being reported from their child nodes.

  9. Stubs should also implement  IDontSubstituteByDefault  so that they are not offered in code-completion menu.

  10. MPS will report a warning if a concept is both  InterfacePart  and  Implementation(WithStub)Part

  11. MPS will report an error if a concept declares or inherits the  ImplementationWithStubPart  interface and no suitable stub concept can be found in the same virtual package.

  12. Stubs that inherit the ImplementationWithStubPart  interface, perhaps by extending the stubbed concept, need to implement IStubForAnotherConcept  to indicate that they are stubs and thus do not need to be stubbed themselves. It is a good strategy to have all stubs implement the IStubForAnotherConcept  interface.

  13. Stubs cannot be defined to replace abstract super-concepts. They must always replace all concrete concept individually, one stub for one concrete concept implementing  ImplementationWithStubPart.

  14. Redefine the editors of the stub concepts so that they show some clear messages, aka "compiled code" to clearly indicate to the reader that the implementation has been removed.

  15. To avoid model validation errors that the users of stripped languages would see in stubbed concepts, consider "specializing" of all children and references with "at least 1" cardinality inherited into the stub concepts from their super-concepts. 

This is needed for concepts, such as the one below - the  modelAccessor  child has cardinality 1 and so is mandatory in the stubbed concept.

Hid1

The stub concept has to follow the naming convention of prepending  Stub  to the stubbed concept's name. It may or may not need to extend the stubbed concept - this depends on how the original concept is being referred to from the rest of the language. If the stub concept has to extend the stubbed concept, it also needs to somehow treat the mandatory child so that the child reference does not stay empty.

The suggested way is to  specialize  the child or reference relationship and change the target concept to  BaseConcept.

Stubx1

In the behavior aspect in the constructor we then set the reference to point to some dummy node, such as an  IntegerConstant. This will ensure the link does not stay empty, yet we avoid the need of creating nodes fully satisfying the original modelAccessor link in the stubbed concept.

Hid3

Last modified: 23 March 2021